十二、ex.v为什么先把div_reg_wadd除法运算结果要写入的寄存器地址先给div,再由div输入给ex.v?
ex.v中端口定义:
c
output reg[`RegAddrBus] div_reg_waddr_o,
input wire[`RegAddrBus] div_reg_waddr_i,
ex.v不给div结果写入地址的前提是ex.v能自己在div计算期间(大约33个周期)保存写入寄存器地址,但是ex.v并不能保存计算结果地址。
首先明确ex.v是组合逻辑,输出会随着输入改变而改变。ex.v的输入主要是id_ex.v,这是一个时序逻辑,会在时钟上升沿改变输出,也就是ex.v的输入。所以说,ex.v随着id_ex.v在时钟上升沿改变。
然后思考id_ex.v在除法计算期间做什么?会把hold_en拉高,但是要注意这里的hold_en不是stall的逻辑,而是flush的逻辑,看寄存器的代码:
c
module gen_pipe_dff #(
parameter DW = 32)(
input wire clk,
input wire rst,
input wire hold_en,
input wire[DW-1:0] def_val,
input wire[DW-1:0] din,
output wire[DW-1:0] qout
);
reg[DW-1:0] qout_r;
always @ (posedge clk) begin
if (!rst | hold_en) begin
qout_r <= def_val;
end else begin
qout_r <= din;
end
end
assign qout = qout_r;
endmodule
id_ex.v中的实例化代码:
c
gen_pipe_dff #(32) inst_ff(clk, rst, hold_en, `INST_NOP, inst_i, inst);
所以,在除法运算期间,id_ex.v会把传给ex.v的inst变为nop指令,所以,ex.v中存不住除法计算结果要写回的寄存器。
所以要传递除法计算结果要写回的寄存器给div模块自己存着,到计算结束时再回传给ex模块去回写寄存器。
十三、ex.v中为什么分成三个线程也就是三个always块,一个处理乘法,一个处理除法,一个处理剩余指令?
可以写成一个 always 块,但是并行写逻辑更清晰。
十四、为什么乘法要写在两个always块中?
一个块负责乘法输入预处理(决定 mul_op1/mul_op2),另一个主执行块负责根据乘法指令类型选择 mul_temp 的哪一部分写回。
能不能写在一个块里,当然可以,但是不推荐。因为那样会让每条乘法分支都更长,后期也不好维护。
十五、为什么要先 assign op1_add_op2_res = op1_i + op2_i?不能在always块中写吗?这样写的好处是啥?
现在是使用中间结果op1_add_op2_res :
c
`INST_TYPE_I: begin
case (funct3)
`INST_ADDI: begin
jump_flag = `JumpDisable;
hold_flag = `HoldDisable;
jump_addr = `ZeroWord;
mem_wdata_o = `ZeroWord;
mem_raddr_o = `ZeroWord;
mem_waddr_o = `ZeroWord;
mem_we = `WriteDisable;
reg_wdata = op1_add_op2_res;
end
好处是:主 always 块更短;避免重复写同一表达式;更符合"数据通路"和"控制通路"分离
要注意,这种做法并不会缩短关键路径,因为没有切成两拍,也没有减少组合级数,只是写法的变化,功能并没有改变。