执行 EX级 设计
EX级以负责核心运算功能的ALU著称。可以说,它是CPU的灵魂。来一人一句ALU牛逼来。
算数逻辑单元 ALU.sv
ALU需要完成一系列R-Type、I-Type指令的运算。对于L-Type和S-Type,它们的处理逻辑其实差不多,都是相加——这样才能让ALU输出基地址和偏移量之和,算出目标地址。
同样地,使用字符串打印的方式来增强可读性,优化Debug体验,并更好的看到指令在流水线中的流动。同样使用DEBUG宏进行包装,在给综合器生成最终电路前取消宏定义即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| `include "include/defines.svh"
module ALU ( input logic [ 4:0] alu_op, input logic [31:0] src1, input logic [31:0] src2, input logic [31:0] imm, input logic alu_src2_sel, output logic [31:0] alu_result, output logic zero, output logic sign, output logic alu_unsigned ); logic [31:0] src2_inner;
assign src2_inner = (alu_src2_sel) ? imm : src2;
always_comb begin : alu_operation case (alu_op) `ALU_ADD: alu_result = src1 + src2_inner; `ALU_SUB: alu_result = src1 - src2_inner; `ALU_OR: alu_result = src1 | src2_inner; `ALU_AND: alu_result = src1 & src2_inner; `ALU_XOR: alu_result = src1 ^ src2_inner; `ALU_SLL: alu_result = src1 << src2_inner[4:0]; `ALU_SRL: alu_result = src1 >> src2_inner[4:0]; `ALU_SRA: alu_result = $signed(src1) >>> src2_inner[4:0]; `ALU_SLT: alu_result = ($signed(src1) < $signed(src2_inner)) ? 1 : 0; `ALU_SLTU: alu_result = (src1 < src2_inner) ? 1 : 0; `ALU_RIGHT: alu_result = src2_inner; `ALU_LW: alu_result = src1 + src2_inner; `ALU_SW: alu_result = src1 + src2_inner; default: alu_result = 0; endcase end
always_comb begin zero = (alu_result == 32'b0); sign = alu_result[31]; alu_unsigned = (src1 < src2_inner); end
`ifdef DEBUG logic [64-1:0] aluop_ascii; always_comb begin : aluop_ascii_output case (alu_op) `ALU_ADD: aluop_ascii = "AADD"; `ALU_SUB: aluop_ascii = "ASUB"; `ALU_OR: aluop_ascii = "AOR"; `ALU_AND: aluop_ascii = "AAND"; `ALU_XOR: aluop_ascii = "AXOR"; `ALU_SLL: aluop_ascii = "ASLL"; `ALU_SRL: aluop_ascii = "ASRL"; `ALU_SRA: aluop_ascii = "ASRA"; `ALU_SLT: aluop_ascii = "ASLT"; `ALU_SLTU: aluop_ascii = "ASLTU"; `ALU_RIGHT: aluop_ascii = "ARGHT"; `ALU_LW: aluop_ascii = "ALW"; `ALU_SW: aluop_ascii = "ASW"; default: aluop_ascii = "ANOP"; endcase end `endif
endmodule
|
对于许多CPU,会将ALU运算的源操作数选择列为一个单独的二选一MUX。这里直接合并在ALU内:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| module ALU ( input logic [31:0] src1, input logic [31:0] src2, input logic [31:0] imm, input logic alu_src2_sel, ... );
logic [31:0] src2_inner; assign src2_inner = (alu_src2_sel) ? imm : src2; ...
endmodule
|
那为什么后面的MUX不合并呢?这是为了看起来更方便。
回写数据来源选择MUX
这一块实际上没有写成一个单独的模块,而是最终要集成在封装好的CPU模块内。想单独写一个也行,反正最终会被综合成一个多选一MUX。
我们需要选择哪些数据?看之前的 译码级设计 ,看Decoder.sv输出的选择信号wd_sel对应哪些指令:
- R-Type和I-Type指令的写回数据来源于ALU;
- B-Type指令不需要选择回写数据,但也得给个默认值;
jal和jalr两个是跳转指令,需要选择pc+4作为数据写入寄存器;
lui和auipc这两个很特殊,是立即数扩展,因此使用扩展后从ID级传入的数据;
- L-Type需要从DRAM中读取数据,因此需要在MEM级选择;
- 不应该有其余情况,这里写为
unique case可以不写default。不过补一个最好。
1 2 3 4 5 6 7 8 9 10 11 12 13
|
always_comb begin : wd_EX_MUX case (wd_sel_EX) `WD_SEL_FROM_ALU: rf_wd_EX = alu_result_EX; `WD_SEL_FROM_PC4: rf_wd_EX = pc4_EX; `WD_SEL_FROM_IEXT: rf_wd_EX = (is_auipc_EX) ? branch_target_EX : imm_EX; default: rf_wd_EX = 32'b0; endcase end
|
下一PC计算模块 NextPC_Generator.sv
我们的指令执行完怎么办?取下一个。那下一个从哪里取呢?
不是所有的指令地址都是+4+4变化。对于分支跳转指令等,我们在运算并比较后,要根据结果来进行跳转,而运算完成的最早时间便是EX级。此外,在EX作比较还能利用前向数据通路来加快运算,这是好的。
我们首先要知道,在EX级执行的指令是什么?是分支,还是跳转,或者根本不用去刻意变化pc?我们还需要获取跳转的目标地址,对于jalr还要进行相应的计算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| `include "include/defines.svh"
module NextPC_Generator ( input logic is_branch_instr, input logic [ 2:0] branch_type, input logic [ 1:0] jump_type, input logic [31:0] alu_result, input logic [31:0] branch_target_i, input logic alu_zero, input logic alu_sign, input logic alu_unsigned, output logic take_branch, output logic [31:0] branch_target_NextPC
);
always_comb begin if (jump_type) take_branch = 1'b1; else if (is_branch_instr) begin unique case (branch_type) `BRANCH_BEQ: take_branch = alu_zero; `BRANCH_BNE: take_branch = ~alu_zero; `BRANCH_BLT: take_branch = alu_sign; `BRANCH_BGE: take_branch = ~alu_sign; `BRANCH_BLTU: take_branch = alu_unsigned; `BRANCH_BGEU: take_branch = ~alu_unsigned; default: take_branch = 1'b0; endcase end else take_branch = 1'b0;
end
assign branch_target_NextPC = (jump_type == `JUMP_JALR) ? {alu_result[31:1], 1'b0} : branch_target_i;
endmodule
|
要注意:jalr指令要将最低位归零。
因RISC‑V 要求所有指令地址至少要对齐到 16 位(半字,2 字节),而jalr是寄存器间接跳转,rs1 + imm的值可能是奇数,因此需要手动将低位置为0。
EX/MEM级 流水线寄存器 PR_EX_MEM.sv
对于EX向MEM级进发,我们需要传递来自MUX的回写数据wd,以及DRAM的写使能信号dram_we。MEM级的回写数据来源MUX控制信号wd_sel和EX级的是一个,直接复用即可。此外,ALU的计算结果也不能忽略,因为对于L-Type指令,需要作为地址传入MEM级。来自ID/EX级的rD2数据也需要传入,作为DRAM的写入数据。此外,对于S-Type指令,寄存器堆写使能信号wr也需要继续传递。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| module PR_EX_MEM ( input logic clk, input logic rst_n, input logic [31:0] pc_ex_i, output logic [31:0] pc_mem_o, input logic instr_valid_ex_i, output logic instr_valid_mem_o, input logic dram_we_ex_i, output logic dram_we_mem_o, input logic rf_we_ex_i, output logic rf_we_mem_o, input logic [ 1:0] wd_sel_ex_i, output logic [ 1:0] wd_sel_mem_o, input logic [ 4:0] wr_ex_i, output logic [ 4:0] wr_mem_o, input logic [31:0] alu_result_ex_i, output logic [31:0] alu_result_mem_o, input logic [31:0] wd_ex_i, output logic [31:0] wd_mem_o, input logic [31:0] rD2_ex_i, output logic [31:0] rD2_mem_o ); always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) begin pc_mem_o <= 32'b0; instr_valid_mem_o <= 1'b0; dram_we_mem_o <= 1'b0; rf_we_mem_o <= 1'b0; wd_sel_mem_o <= 2'b0; alu_result_mem_o <= 32'b0; wd_mem_o <= 32'b0; rD2_mem_o <= 32'b0; end else begin pc_mem_o <= pc_ex_i; instr_valid_mem_o <= instr_valid_ex_i; dram_we_mem_o <= dram_we_ex_i; rf_we_mem_o <= rf_we_ex_i; wd_sel_mem_o <= wd_sel_ex_i; alu_result_mem_o <= alu_result_ex_i; wd_mem_o <= wd_ex_i; rD2_mem_o <= rD2_ex_i; end end
always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) begin wr_mem_o <= 5'b0; end else begin wr_mem_o <= wr_ex_i; end end
endmodule
|
写到这里,已经完成30%了,可喜可贺!
为什么已经写了三级,才完成30%?
因为大的在后面呢。