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_next
由 always @(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。
我有一个 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_next
由 always @(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。