一、hold和flush谁的优先级高?
结论:flush的优先级更高
原因很简单:
flush的含义是“这条/这几条指令无效了,必须冲掉”hold的含义是“先别往前走,暂时停住”
如果某一拍同时出现flush_i=1和hold_i=1,通常说明:
- 控制逻辑要求这一级原来的内容不能再保留
- 那就应该优先清空,而不是继续保持旧指令
所以应该执行flush。
二、为什么不在id.v中设置一个reg_wdata_o?因为LUI指令的写回寄存器的数据就是inst_i的高20位。
现在的做法是:
op1_o={inst_i[31:12],12'b0};op2_o=`ZeroWord;而不是:
reg_wdata_o={inst_i[31:12],12'h0};这样做的理由是:
id.v的职责是“译码 + 准备操作数”,不是“最终执行并写回结果”。ex.v的职责是“根据操作数和指令类型生成执行结果”。如果
LUI也走统一通路,那么 EX 级只要做一次“op1 + op2或直通某路结果”的规则,就能处理很多指令。
那能不能新增reg_wdata_o?可以,语法和架构上都不是不行。
但这样做意味着你要回答一个更大的问题:
reg_wdata_o是不是只给LUI用?- 如果
AUIPC也能在 ID 级算出来,要不要也给它用? JAL的PC+4要不要也在 ID 级直接给?- CSR 指令要不要也部分在 ID 级给?
一旦开了这个口子,你的写回数据来源会开始分散,id.v的职责就会慢慢变重。
三、为什么jal和jalr的立即数不一样?
JAL是Jump And Link,作用是:跳转到目标地址,同时把返回地址保存到寄存器rd。
它的行为可以概括成两步:
rd = PC + 4PC = PC + imm_jimm_j={{12{inst_i[31]}}, inst_i[19:12], inst_i[20], inst_i[30:21], 1'b0}
JALR是Jump And Link Register,作用也是:跳转 + 保存返回地址,但它的跳转目标不是PC + offset,而是rs1 + imm。
它的行为是:
rd = PC + 4PC = (rs1 + imm_i){{20{inst_i[31]}}, inst_i[31:20]}
为什么二者的立即数不一样?
它们俩虽然名字相近,但是,不是“同一种指令换个名字”,而是两种不同设计目标的跳转指令。
它们服务于两种不同的跳转方式:
JAL是PC 相对远跳转,所以用更大的J-type立即数;JALR是寄存器基址间接跳转,所以用带rs1的I-type立即数。
四、为什么LUI指令只有opcode就可以区分,不用func3或者func7区分?
比如:B型指令下有6 条具体指令
一句话总结
opcode = 7'b0110111:可以直接认为就是LUIopcode = 7'b1100011:只能说明它是B 型分支类,至于具体是哪一条,还要看funct3
为什么这样设计呢?
本质原因是:32 位指令的编码空间有限,ISA 设计必须在“指令数量、译码复杂度、立即数字段、寄存器字段”之间做平衡。
所以 RISC-V 才会设计成:
opcode先区分大类funct3/funct7再区分具体指令
而不是“每条指令一个独立 opcode”。
为什么不能每条指令都独占一个 opcode?因为opcode只有 7 位:
- 理论上最多
2^7 = 128种编码 - 如果每条具体指令都占一个
opcode,会很快不够用
为什么有的 opcode 只有一条主指令,有的却是一组?因为需求不同。
LUI、AUIPC、JAL这类指令语义很独特,单独给一个opcode很自然。B-type、Load、Store这类指令结构非常像,只是“比较条件不同”或“访存宽度不同”,所以共享同一个opcode更划算。
五、在id.v中加不加jump_flag_o?因为jal jalr指令肯定是要跳转的
tinyriscv中的id.v并没有设置jump_flag_o端口。
需要明确的是:
如果跳转最终还是要等ex.v决定,那么id.v里提前拉高jump_flag_o的意义不大;只有当它能真正提前影响 PC 或流水线控制时,才值得加。
还是这样模块分工最清楚:
id:准备比较数和跳转地址计算所需操作数ex:真正执行比较、求目标地址、决定是否跳转