【问题标题】:1-instruction delay of branching in Verilog implementation and syncing issueVerilog实现中分支的1指令延迟和同步问题
【发布时间】:2016-03-06 04:33:43
【问题描述】:

我有一个 16 位单周期、非常稀疏的 MIPS 实现,我一直在 Verilog 中工作。除了分支延迟了一个完整的时钟周期之外,一切正常。

always @(posedge clock) begin
    // Necessary to add this in order to ensure PC => PC_next
    iaddr <= pc_next 
end

以上代码用于更新程序计数器/指令地址,来自一个模块,PCLogic:

module PCLogic(
        pc_next,    // next value of the pc
        pc,     // current pc value
        signext,    // from sign extend circuit
        branch, // beq instruction
        alu_zero,   // zero from ALU, used in cond branch
        reset       // reset input
        );

output [15:0] pc_next;
input [15:0] pc;
input [15:0] signext;  // From sign extend circuit
input branch;
input alu_zero;
input reset;

reg [15:0] pc_next; 

    always @(pc or reset) begin
        if (reset == 1)
            pc_next = 0;
        else if (branch == 1 && alu_zero == 1)
            pc_next = pc+2+(signext << 1);
        else
            pc_next = pc+2;
    end

endmodule

iaddr 是一个简单的 16 位寄存器,用于存储程序计数器。

我不明白为什么这个电路可能有问题,但由于某种原因,整个电路延迟了一个时钟周期,直到它分支(例如,如果我在 0x16 处有一条 BEQ 指令总是跳转,它将在 0x18 处执行下一条指令,然后跳转到相对偏移量,但从 0x20 开始。

我几乎可以感觉到解决方案就在我面前,但我不知道我在语义上缺少什么。如果我删除始终隐含的+2,则偏移问题得到解决,除非存在真正的“气泡”或硬件引起的无操作,但延迟仍然存在。

谁能向我解释造成延迟的原因以及发生的原因?

【问题讨论】:

  • 逻辑表明,每当pc发生变化并且branchalu_zeroanding,那么pc_next必须增加2。从 sn-p 来看,我猜 alu_zerobranch 没有正确更新。你能为此分享一些伪测试台或波形吗?
  • @sharvil111 其实我刚刚解决了这个问题!我将发布我自己问题的答案。
  • 是的,连续分配应该有效。
  • 在组合 always 块内读取的信号必须出现在敏感度列表中。这是一个编码规则,因此综合工具可以将其转换为实际的逻辑门。 感谢@sharvil111

标签: synchronization mips verilog hardware-programming


【解决方案1】:

答案是在 PCLogic 模块中使用状态会导致额外的传播延迟。通过删除 PCLogic 中的寄存器,我们删除了模块本身的隐式状态步骤,将其传播减少到可以忽略不计的 0。

所以答案是将pc_nextalways @(pc) 块计算为基于声明性表达式的块:

wire [15:0] pc_next = (reset == 1)? 0 : (branch == 1 && alu_zero == 1)? pc+2+(signext << 1) : pc+2;

通过将我们的电路更改为组合电路,我们不再需要存储状态,从而减少了流程中的“缓冲区”。 PC 现在可以在 (T) 时间内更新,而不是 (2T)。

【讨论】:

    【解决方案2】:

    另一种组合电路的编码方式:

    reg [15:0] pc_next; 
    
    always @* begin
        if (reset == 1)
            pc_next = 0;
        else if (branch == 1 && alu_zero == 1)
            pc_next = pc+2+(signext << 1);
        else
            pc_next = pc+2; // latch will be inferred without this
    end
    

    当您的组合电路变得更加复杂时,您将需要它,因为当存在大量嵌套 if-else 时,分配语句难以阅读。

    注意这一点

    pc_next = pc+2; // latch will be inferred without this
    

    组合块应该有一个默认值。当条件语句中没有定义默认值时,它将保留其值并导致不正确的行为。组合块不能包含值。

    有关意外闩锁的更多信息,请参阅this

    【讨论】:

    • 这就是我最初对电路进行编码的方式。如果它是这样的,它是行不通的。但是等等……always @(*) 会改变吗?或者这本质上是一样的?这和我原来的代码有什么区别?
    • always @(*) 对所有输入信号都很敏感。这对于复杂的电路来说是相同的,但更易读,尤其是当有很多嵌套的 if-else 语句时。
    • 必须为组合块的所有输入信号指定灵敏度列表。在您的代码中,灵敏度列表必须为always @(pc, reset, alu_zero, branch, signext),因为这些都是您组合电路的输入信号。为简化起见,只需对所有组合块使用always @*
    • 只要确保你定义了一个默认值,以避免导致意外行为的闩锁
    • 哇,真奇怪
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-02
    • 1970-01-01
    • 2012-06-02
    相关资源
    最近更新 更多