Verilog 实现和同步问题中分支的 1 指令延迟

1-instruction delay of branching in Verilog implementation and syncing issue

我有一个 16 位单周期、非常稀疏的 MIPS 实现,我一直在用 Verilog 开发它。一切正常,除了分支延迟了一个完整的时钟周期。

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

以上代码用于更新程序counter/instruction地址,来自一个模块,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 偏移量问题就解决了,除非有真正的 "bubble" 或硬件引起的空操作,但延迟仍然存在。

有人可以向我解释造成延迟的原因以及发生延迟的原因吗?

答案是在 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;

通过将电路更改为组合电路,我们不再需要存储状态,从而减少了流程中的 "buffer"。 PC 现在只需 (T) 时间即可更新,而不是 (2T)。

另一种编写组合电路的方法:

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 时,assign 语句很难阅读。

注意事项

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

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

有关意外闩锁的详细信息,请参阅 this