diff --git a/cpu21_bpb_8.v b/cpu21_bpb_8.v new file mode 100644 index 0000000..2284df7 --- /dev/null +++ b/cpu21_bpb_8.v @@ -0,0 +1,117 @@ +`timescale 1ns / 1ps +`default_nettype none + +// ----------------------------------------------------------------------------- +// 八表项全相联分支目标缓冲(BPB/BTB)。 +// +// tag = PC[11:2],与 Logisim 版 BPB 中的十位 tag 拆分保持一致。 +// count 为两位饱和计数器:00/01 表示不跳转,10/11 表示跳转。 +// age 用于近似 LRU 替换:被选中的表项清零,其余表项自增。 +// ----------------------------------------------------------------------------- +module cpu21_bpb_8 ( + input wire clk, + input wire reset, + input wire [31:0] predict_pc, + input wire predict_enable, + output reg predict_hit, + output reg predict_taken, + output reg [31:0] predict_target, + input wire update_enable, + input wire update_controlflow, + input wire [31:0] update_pc, + input wire [31:0] update_target, + input wire update_taken +); + reg valid [0:7]; + reg [ 9:0] tag [0:7]; + reg [31:0] target [0:7]; + reg [ 1:0] count [0:7]; + reg [ 2:0] age [0:7]; + + reg update_found; + reg [ 2:0] update_index; + reg replace_found; + reg [ 2:0] replace_index; + reg [ 2:0] max_age; + integer p; + integer u; + integer k; + integer selected_index; + + // 预测命中查找:按 tag 全相联匹配,命中则给出目标地址与方向预测。 + always @* begin + predict_hit = 1'b0; + predict_taken = 1'b0; + predict_target = 32'b0; + for (p = 0; p < 8; p = p + 1) begin + if (predict_enable && !predict_hit && valid[p] && (tag[p] == predict_pc[11:2])) begin + predict_hit = 1'b1; + predict_target = target[p]; + predict_taken = count[p][1]; + end + end + end + + // 更新时先找匹配表项;若无匹配则优先使用无效表项,否则淘汰最旧表项。 + // 下方的 age 自增实现了类似 LRU 的替换策略。 + always @* begin + update_found = 1'b0; + update_index = 3'd0; + replace_found = 1'b0; + replace_index = 3'd0; + max_age = 3'd0; + + for (u = 0; u < 8; u = u + 1) begin + if (!update_found && valid[u] && (tag[u] == update_pc[11:2])) begin + update_found = 1'b1; + update_index = u; + end + end + for (u = 0; u < 8; u = u + 1) begin + if (!replace_found && !valid[u]) begin + replace_found = 1'b1; + replace_index = u; + end else if (!replace_found && (age[u] >= max_age)) begin + replace_index = u; + max_age = age[u]; + end + end + end + + // 表项状态更新(含替换与两位饱和计数器更新)。 + always @(posedge clk or posedge reset) begin + if (reset) begin + for (k = 0; k < 8; k = k + 1) begin + valid[k] <= 1'b0; + tag[k] <= 10'b0; + target[k] <= 32'b0; + count[k] <= 2'b01; + age[k] <= 3'b0; + end + end else if (update_enable && update_controlflow) begin + selected_index = update_found ? update_index : replace_index; + + valid[selected_index] <= 1'b1; + tag[selected_index] <= update_pc[11:2]; + target[selected_index] <= update_target; + age[selected_index] <= 3'b0; + + if (!update_found) begin + // 新表项采用较弱的初始状态,避免立刻产生错误预测。 + count[selected_index] <= update_taken ? 2'b10 : 2'b01; + end else if (update_taken) begin + // 实际跳转:计数器加一(饱和到 11)。 + if (count[selected_index] != 2'b11) count[selected_index] <= count[selected_index] + 2'b01; + end else begin + // 实际不跳转:计数器减一(饱和到 00)。 + if (count[selected_index] != 2'b00) count[selected_index] <= count[selected_index] - 2'b01; + end + + for (k = 0; k < 8; k = k + 1) begin + if (valid[k] && (k != selected_index) && (age[k] != 3'b111)) age[k] <= age[k] + 3'b001; + end + end + end +endmodule + +`default_nettype wire diff --git a/cpu21_riscv_alu.v b/cpu21_riscv_alu.v new file mode 100644 index 0000000..99cde79 --- /dev/null +++ b/cpu21_riscv_alu.v @@ -0,0 +1,60 @@ +`timescale 1ns / 1ps +`default_nettype none + +// ----------------------------------------------------------------------------- +// ALU:纯组合运算单元。 +// +// result 为主结果;result2 为第二结果,用于 MUL 的高 32 位与 DIVU 的余数。 +// 除法做了除零保护:b==0 时商为 32'hffff_ffff、余数为被除数。 +// ----------------------------------------------------------------------------- +module cpu21_riscv_alu ( + input wire [ 3:0] op, + input wire [31:0] a, + input wire [31:0] b, + output reg [31:0] result, + output reg [31:0] result2 +); + localparam ALU_SLL = 4'd0; + localparam ALU_SRA = 4'd1; + localparam ALU_SRL = 4'd2; + localparam ALU_MUL = 4'd3; + localparam ALU_DIVU = 4'd4; + localparam ALU_ADD = 4'd5; + localparam ALU_SUB = 4'd6; + localparam ALU_AND = 4'd7; + localparam ALU_OR = 4'd8; + localparam ALU_XOR = 4'd9; + localparam ALU_NOR = 4'd10; + localparam ALU_SLT = 4'd11; + localparam ALU_SLTU = 4'd12; + reg [63:0] mult_result; + + always @* begin + mult_result = a * b; + result2 = 32'b0; + case (op) + ALU_SLL: result = a << b[4:0]; + ALU_SRA: result = $signed(a) >>> b[4:0]; + ALU_SRL: result = a >> b[4:0]; + ALU_MUL: begin + result = mult_result[31:0]; + result2 = mult_result[63:32]; + end + ALU_DIVU: begin + result = (b == 32'b0) ? 32'hffff_ffff : a / b; + result2 = (b == 32'b0) ? a : a % b; + end + ALU_ADD: result = a + b; + ALU_SUB: result = a - b; + ALU_AND: result = a & b; + ALU_OR: result = a | b; + ALU_XOR: result = a ^ b; + ALU_NOR: result = ~(a | b); + ALU_SLT: result = ($signed(a) < $signed(b)) ? 32'd1 : 32'd0; + ALU_SLTU: result = (a < b) ? 32'd1 : 32'd0; + default: result = 32'b0; + endcase + end +endmodule + +`default_nettype wire diff --git a/cpu21_riscv_branch_unit.v b/cpu21_riscv_branch_unit.v new file mode 100644 index 0000000..31599c9 --- /dev/null +++ b/cpu21_riscv_branch_unit.v @@ -0,0 +1,61 @@ +`timescale 1ns / 1ps +`default_nettype none + +// ----------------------------------------------------------------------------- +// EX 级控制流裁决单元。 +// +// 条件分支的实际方向与目标都在 EX 级解析;JAL/JALR 的目标也只在 EX 级 +// 得到,因此它们总是需要重定向。条件分支仅在预测方向/目标与实际不符时 +// 才重定向,这样预测正确时不会产生气泡。 +// ----------------------------------------------------------------------------- +module cpu21_riscv_branch_unit ( + input wire valid_i, + input wire [31:0] pc_i, + input wire [31:0] imm_i, + input wire [31:0] src1_i, + input wire [31:0] src2_i, + input wire branch_i, + input wire beq_i, + input wire bne_i, + input wire blt_i, + input wire bltu_i, + input wire jal_i, + input wire jalr_i, + // IF 级携带过来的预测信息 + input wire pred_taken_i, + input wire [31:0] pred_target_i, + // 裁决结果 + output wire branch_taken_o, + output wire controlflow_o, + output wire [31:0] target_o, + output wire redirect_valid_o, + output wire [31:0] redirect_pc_o +); + // 条件分支裁决:beq 相等、bne 不等、blt 有符号小于、bltu 无符号小于。 + assign branch_taken_o = branch_i && + ((beq_i && (src1_i == src2_i)) || + (bne_i && (src1_i != src2_i)) || + (blt_i && ($signed( + src1_i + ) < $signed( + src2_i + ))) || (bltu_i && (src1_i < src2_i))); + + // 跳转目标:jalr 为 (rs1+imm) 且最低位清零;jal 为 PC+imm。 + assign target_o = jalr_i ? ((src1_i + imm_i) & 32'hffff_fffe) : (pc_i + imm_i); + + // EX 级存在需要裁决的控制流指令。 + assign controlflow_o = valid_i && (branch_i || jal_i || jalr_i); + + // 条件分支的预测方向/目标与实际不符,或 JAL/JALR 必须重定向。 + assign redirect_valid_o = controlflow_o && + (branch_i ? + ((pred_taken_i != branch_taken_o) || + (pred_taken_i && branch_taken_o && + (pred_target_i != target_o))) : 1'b1); + + // 预测了跳转但实际不跳转时,回到顺序地址继续取指。 + assign redirect_pc_o = (branch_i && !branch_taken_o) ? (pc_i + 32'd4) : target_o; +endmodule + +`default_nettype wire diff --git a/cpu21_riscv_decoder.v b/cpu21_riscv_decoder.v new file mode 100644 index 0000000..b122e98 --- /dev/null +++ b/cpu21_riscv_decoder.v @@ -0,0 +1,248 @@ +`timescale 1ns / 1ps +`default_nettype none + +// ----------------------------------------------------------------------------- +// ID 级组合译码器。 +// +// 与原始 Logisim 控制器一致:操作码取标准 RISC-V 的 IR[6:2] 五位字段, +// 真值表中以十六进制形式存储该字段。 +// 所有输出在无匹配时保持"无操作/不写回"的默认值。 +// ----------------------------------------------------------------------------- +module cpu21_riscv_decoder ( + input wire [31:0] ir_i, + + output reg [ 4:0] rs1_idx_o, + output reg [ 4:0] rs2_idx_o, + output reg [ 4:0] rd_o, + output reg [31:0] imm_o, + output reg [ 3:0] alu_op_o, + output reg [ 2:0] wb_sel_o, + output wire [11:0] csr_addr_o, + output reg uses_rs1_o, + output reg uses_rs2_o, + output reg reg_write_o, + output reg mem_to_reg_o, + output reg mem_write_o, + output reg mem_byte_o, + output reg alu_src_o, + output reg branch_o, + output reg beq_o, + output reg bne_o, + output reg blt_o, + output reg bltu_o, + output reg jal_o, + output reg jalr_o, + output reg ecall_o, + output reg uret_o, + output reg csr_set_o, + output reg csr_clear_o, + output reg csr_write_o +); + // CPU21 自定义操作码(取自所提供的真值表)。 + localparam OP_LOAD = 5'h00; + localparam OP_R = 5'h0c; + localparam OP_I = 5'h04; + localparam OP_STORE = 5'h08; + localparam OP_JALR = 5'h19; + localparam OP_BRANCH = 5'h18; + localparam OP_JAL = 5'h1b; + localparam OP_SYS = 5'h1c; + + localparam ALU_SLL = 4'd0; + localparam ALU_SRA = 4'd1; + localparam ALU_SRL = 4'd2; + localparam ALU_MUL = 4'd3; + localparam ALU_DIVU = 4'd4; + localparam ALU_ADD = 4'd5; + localparam ALU_SUB = 4'd6; + localparam ALU_AND = 4'd7; + localparam ALU_OR = 4'd8; + localparam ALU_XOR = 4'd9; + localparam ALU_SLT = 4'd11; + localparam ALU_SLTU = 4'd12; + + wire [4:0] opcode = ir_i[6:2]; + wire [2:0] funct3 = ir_i[14:12]; + wire [6:0] funct7 = ir_i[31:25]; + + // CSR 地址字段在译码时直接旁路输出(供 EX 级读取 CSR)。 + assign csr_addr_o = ir_i[31:20]; + + // 默认控制信号:全部为"无操作/不写回",再由下面的 case 覆盖。 + always @* begin + rs1_idx_o = ir_i[19:15]; + rs2_idx_o = ir_i[24:20]; + rd_o = ir_i[11:7]; + imm_o = {{20{ir_i[31]}}, ir_i[31:20]}; + alu_op_o = ALU_ADD; + wb_sel_o = 3'd0; + uses_rs1_o = 1'b0; + uses_rs2_o = 1'b0; + reg_write_o = 1'b0; + mem_to_reg_o = 1'b0; + mem_write_o = 1'b0; + mem_byte_o = 1'b0; + alu_src_o = 1'b0; + branch_o = 1'b0; + beq_o = 1'b0; + bne_o = 1'b0; + blt_o = 1'b0; + bltu_o = 1'b0; + jal_o = 1'b0; + jalr_o = 1'b0; + ecall_o = 1'b0; + uret_o = 1'b0; + csr_set_o = 1'b0; + csr_clear_o = 1'b0; + csr_write_o = 1'b0; + + case (opcode) + // R 型运算:由 funct3/funct7 决定具体操作。 + OP_R: begin + uses_rs1_o = 1'b1; + uses_rs2_o = 1'b1; + reg_write_o = 1'b1; + // 电路中额外的 REMU 控制对应标准 R 型的 funct7=1/funct3=111 + // 形式;此时 ALU 的 result2 即为余数。 + if ((funct7 == 7'b0000001) && (funct3 == 3'b111)) begin + alu_op_o = ALU_DIVU; + wb_sel_o = 3'd3; + end else if ((funct7 == 7'b0000001) && (funct3 == 3'b000)) begin + alu_op_o = ALU_MUL; + end else begin + // 标准 R 型 funct3 译码;add/sub 与 sra/srl 由 funct7[5] 区分。 + case (funct3) + 3'b000: alu_op_o = (funct7[5] ? ALU_SUB : ALU_ADD); + 3'b001: alu_op_o = ALU_SLL; + 3'b010: alu_op_o = ALU_SLT; + 3'b011: alu_op_o = ALU_SLTU; + 3'b100: alu_op_o = ALU_XOR; + 3'b101: alu_op_o = (funct7[5] ? ALU_SRA : ALU_SRL); + 3'b110: alu_op_o = ALU_OR; + 3'b111: alu_op_o = ALU_AND; + default: reg_write_o = 1'b0; + endcase + end + end + + // I 型运算(addi/slli/slti/xori/srai/srli/ori/andi)。 + OP_I: begin + uses_rs1_o = 1'b1; + alu_src_o = 1'b1; + reg_write_o = 1'b1; + case (funct3) + 3'b000: alu_op_o = ALU_ADD; // addi:立即数加 + 3'b001: alu_op_o = ALU_SLL; // slli:立即数逻辑左移 + 3'b010: alu_op_o = ALU_SLT; // slti:有符号小于置 1 + 3'b100: alu_op_o = ALU_XOR; // xori:立即数异或 + 3'b101: + alu_op_o = (funct7[5] ? ALU_SRA : ALU_SRL); // srai/srli:立即数算术/逻辑右移 + 3'b110: alu_op_o = ALU_OR; // ori:立即数或 + 3'b111: alu_op_o = ALU_AND; // andi:立即数与 + default: reg_write_o = 1'b0; + endcase + end + + // 加载指令,当前仅支持 lw(funct3=010)。 + OP_LOAD: begin + if (funct3 == 3'b010) begin + uses_rs1_o = 1'b1; + alu_src_o = 1'b1; + alu_op_o = ALU_ADD; + mem_to_reg_o = 1'b1; + reg_write_o = 1'b1; + wb_sel_o = 3'd1; + end + end + + // 存储指令:sw(funct3=010)与 sb(funct3=000)。 + OP_STORE: begin + if ((funct3 == 3'b010) || (funct3 == 3'b000)) begin + uses_rs1_o = 1'b1; + uses_rs2_o = 1'b1; + alu_src_o = 1'b1; + alu_op_o = ALU_ADD; + mem_write_o = 1'b1; + mem_byte_o = (funct3 == 3'b000); // sb:字节存储 + imm_o = {{20{ir_i[31]}}, ir_i[31:25], ir_i[11:7]}; + end + end + + // 条件分支:beq(000)/bne(001)/blt(100,有符号小于)/bltu(110,无符号小于)。 + OP_BRANCH: begin + if ((funct3 == 3'b000) || (funct3 == 3'b001) || + (funct3 == 3'b100) || (funct3 == 3'b110)) begin + uses_rs1_o = 1'b1; + uses_rs2_o = 1'b1; + branch_o = 1'b1; + beq_o = (funct3 == 3'b000); + bne_o = (funct3 == 3'b001); + blt_o = (funct3 == 3'b100); + bltu_o = (funct3 == 3'b110); + // 真值表:beq/bne 用减法比较,blt 用 ALU_SLT,bltu 用 ALU_SLTU。 + alu_op_o = blt_o ? ALU_SLT : (bltu_o ? ALU_SLTU : ALU_SUB); + imm_o = {{19{ir_i[31]}}, ir_i[31], ir_i[7], ir_i[30:25], ir_i[11:8], 1'b0}; + end + end + + // 无条件跳转并链接:jal(写回 PC+4)。 + OP_JAL: begin + jal_o = 1'b1; + reg_write_o = 1'b1; + wb_sel_o = 3'd2; + imm_o = {{11{ir_i[31]}}, ir_i[31], ir_i[19:12], ir_i[20], ir_i[30:21], 1'b0}; + end + + // 寄存器间接跳转并链接:jalr。 + OP_JALR: begin + if (funct3 == 3'b000) begin + jalr_o = 1'b1; + uses_rs1_o = 1'b1; + alu_src_o = 1'b1; + alu_op_o = ALU_ADD; + reg_write_o = 1'b1; + wb_sel_o = 3'd2; + end + end + + // 系统指令:ecall / uret / CSR 读写。 + OP_SYS: begin + // 电路用 IR[21] 区分 URET 与 ecall。 + if (funct3 == 3'b000) begin + if (ir_i[21]) begin + uret_o = 1'b1; + end else begin + ecall_o = 1'b1; + // 按文档说明,ecall 读取 a7(rs17) 与 a0(rs10)。 + uses_rs1_o = 1'b1; + uses_rs2_o = 1'b1; + rs1_idx_o = 5'd17; + rs2_idx_o = 5'd10; + end + end else if (funct3 == 3'b001) begin + csr_write_o = 1'b1; // CSRRW:写 CSR,并把旧值写回 rd + uses_rs1_o = 1'b1; + alu_src_o = 1'b1; + imm_o = {27'b0, ir_i[19:15]}; + reg_write_o = (rd_o != 5'd0); + wb_sel_o = 3'd4; + end else if (funct3 == 3'b110) begin + csr_set_o = 1'b1; // CSRRSI:置位 CSR 中的指定位 + imm_o = {27'b0, ir_i[19:15]}; + reg_write_o = (rd_o != 5'd0); + wb_sel_o = 3'd4; + end else if (funct3 == 3'b111) begin + csr_clear_o = 1'b1; // CSRRCI:清除 CSR 中的指定位 + imm_o = {27'b0, ir_i[19:15]}; + reg_write_o = (rd_o != 5'd0); + wb_sel_o = 3'd4; + end + end + + default: begin + end + endcase + end +endmodule + +`default_nettype wire diff --git a/cpu21_riscv_forward_unit.v b/cpu21_riscv_forward_unit.v new file mode 100644 index 0000000..34b156f --- /dev/null +++ b/cpu21_riscv_forward_unit.v @@ -0,0 +1,47 @@ +`timescale 1ns / 1ps +`default_nettype none + +// ----------------------------------------------------------------------------- +// EX 级前递单元。 +// +// 优先级:EX/MEM 级的结果最新 -> 其次 MEM/WB 级写回值 -> 最后是 ID 级 +// 译码时读到的寄存器值。load 结果在 MEM 级之后才可用,因此 EX/MEM 的 +// load(mem_to_reg)不参与前递,改由冒险单元插入一个气泡。 +// ----------------------------------------------------------------------------- +module cpu21_riscv_forward_unit ( + input wire [31:0] rs1_value_i, + input wire [31:0] rs2_value_i, + input wire [ 4:0] rs1_idx_i, + input wire [ 4:0] rs2_idx_i, + // EX/MEM 级 + input wire exmem_valid_i, + input wire exmem_reg_write_i, + input wire exmem_mem_to_reg_i, + input wire [ 4:0] exmem_rd_i, + input wire [31:0] exmem_value_i, + // MEM/WB 级 + input wire memwb_valid_i, + input wire memwb_reg_write_i, + input wire [ 4:0] memwb_rd_i, + input wire [31:0] memwb_value_i, + // 前递后的操作数 + output reg [31:0] src1_o, + output reg [31:0] src2_o +); + wire ex_mem_forward_valid = exmem_valid_i && exmem_reg_write_i && + !exmem_mem_to_reg_i && (exmem_rd_i != 5'd0); + wire wb_forward_valid = memwb_valid_i && memwb_reg_write_i && (memwb_rd_i != 5'd0); + + always @* begin + src1_o = rs1_value_i; + src2_o = rs2_value_i; + + if (ex_mem_forward_valid && (exmem_rd_i == rs1_idx_i)) src1_o = exmem_value_i; + else if (wb_forward_valid && (memwb_rd_i == rs1_idx_i)) src1_o = memwb_value_i; + + if (ex_mem_forward_valid && (exmem_rd_i == rs2_idx_i)) src2_o = exmem_value_i; + else if (wb_forward_valid && (memwb_rd_i == rs2_idx_i)) src2_o = memwb_value_i; + end +endmodule + +`default_nettype wire diff --git a/cpu21_riscv_hazard_unit.v b/cpu21_riscv_hazard_unit.v new file mode 100644 index 0000000..bb1c5e6 --- /dev/null +++ b/cpu21_riscv_hazard_unit.v @@ -0,0 +1,43 @@ +`timescale 1ns / 1ps +`default_nettype none + +// ----------------------------------------------------------------------------- +// 冒险/流水控制单元。 +// +// load-use 冒险:EX 级是 load 且 ID 级指令马上要用它的结果时,插入一个 +// 气泡(停顿一拍)。其余数据相关由前递网络解决。 +// 冲刷(flush_younger)优先级高于停顿:中断、URET、ecall 停机、分支 +// 重定向都会丢弃年轻指令,此时不需要再插气泡。 +// ----------------------------------------------------------------------------- +module cpu21_riscv_hazard_unit ( + input wire ifid_valid_i, + input wire idex_valid_i, + input wire idex_mem_to_reg_i, + input wire [4:0] idex_rd_i, + input wire d_uses_rs1_i, + input wire d_uses_rs2_i, + input wire [4:0] d_src1_idx_i, + input wire [4:0] d_src2_idx_i, + // 冲刷来源 + input wire take_irq_i, + input wire uret_redirect_i, + input wire halt_event_i, + input wire redirect_valid_i, + input wire halted_i, + // 控制结果 + output wire load_use_hazard_o, + output wire flush_younger_o, + output wire pipeline_stall_o +); + // EX 级为 load,且 ID 级的源寄存器与它的目的寄存器相同。 + assign load_use_hazard_o = ifid_valid_i && idex_valid_i && idex_mem_to_reg_i && + (idex_rd_i != 5'd0) && + ((d_uses_rs1_i && (d_src1_idx_i == idex_rd_i)) || + (d_uses_rs2_i && (d_src2_idx_i == idex_rd_i))); + + assign flush_younger_o = take_irq_i || uret_redirect_i || halt_event_i || redirect_valid_i; + + assign pipeline_stall_o = load_use_hazard_o && !flush_younger_o && !halted_i; +endmodule + +`default_nettype wire diff --git a/cpu21_riscv_irq_ctrl.v b/cpu21_riscv_irq_ctrl.v new file mode 100644 index 0000000..bba2f6f --- /dev/null +++ b/cpu21_riscv_irq_ctrl.v @@ -0,0 +1,188 @@ +`timescale 1ns / 1ps +`default_nettype none + +// ----------------------------------------------------------------------------- +// 中断控制器 + ustatus/uepc CSR + 嵌套返回栈。 +// +// IRQ3 优先级最高,其次 IRQ2,最后 IRQ1;嵌套请求只有优先级高于当前级别 +// 才会被接纳。中断入口地址由优先级选出,返回地址保存在 epc_stack 中, +// 当前上下文的活动返回 PC 放在 uepc_q(与课程讲义用 CSRRW 保存/恢复 +// uepc 的流程一致)。 +// ----------------------------------------------------------------------------- +module cpu21_riscv_irq_ctrl #( + parameter [31:0] IRQ1_VECTOR = 32'h0000_30ac, + parameter [31:0] IRQ2_VECTOR = 32'h0000_31e4, + parameter [31:0] IRQ3_VECTOR = 32'h0000_3310, + parameter IRQ_STACK_DEPTH = 4, + // ustatus 复位值。bit0 为 MIE。课程的中断测试程序只会在中断处理 + // 程序中设置 MIE,若复位后不立即置位 MIE,主程序将永远无法响应 + // 第一次中断,因此这里默认打开 MIE。 + parameter [31:0] USTATUS_INIT = 32'h0000_0001 +) ( + input wire clk, + input wire reset, + input wire [ 2:0] irq_i, + input wire halted_i, + // EX 级正在处理重定向/URET/停机,此时不允许响应中断 + input wire ex_block_i, + // 中断发生时要保存的返回地址(由顶层根据流水线状态给出) + input wire [31:0] save_pc_i, + // EX 级的 URET + input wire uret_i, + // EX 级 CSR 指令(CSRRW/CSRRSI/CSRRCI) + input wire csr_valid_i, + input wire csr_set_i, + input wire csr_clear_i, + input wire csr_write_i, + input wire [11:0] csr_addr_i, + input wire [31:0] csr_imm_i, + input wire [31:0] csr_wdata_i, + // 控制结果 + output wire take_irq_o, + output wire [31:0] vector_o, + output wire [31:0] return_pc_o, + output wire [ 2:0] pending_o, + output wire [ 1:0] level_o, + output wire [31:0] uepc_o, + output wire [31:0] ustatus_o +); + reg [31:0] ustatus_q; + reg [31:0] uepc_q; + reg [ 1:0] irq_current_q; + reg [ 2:0] irq_pending_q; + reg [ 2:0] irq_sync1_q; + reg [ 2:0] irq_sync2_q; + reg [ 2:0] irq_prev_q; + reg [31:0] epc_stack [0:IRQ_STACK_DEPTH-1]; + reg [31:0] status_stack [0:IRQ_STACK_DEPTH-1]; + reg [ 1:0] priority_stack [0:IRQ_STACK_DEPTH-1]; + reg [ 2:0] irq_depth_q; + + reg irq_selected_valid; + reg [ 1:0] irq_selected_level; + reg [31:0] irq_vector; + reg [ 2:0] irq_pending_d; + reg [31:0] ustatus_d; + reg [31:0] uepc_d; + + integer s; + + // 上升沿检测:只在 irq_i 由 0 变 1 的那一拍产生中断事件。 + wire [ 2:0] irq_event = irq_sync2_q & ~irq_prev_q; + + // 优先级仲裁:在当前级别允许的条件下选出优先级最高的中断源。 + always @* begin + irq_selected_valid = 1'b0; + irq_selected_level = 2'd0; + if (irq_pending_q[2] && (irq_current_q < 2'd3)) begin + irq_selected_valid = 1'b1; + irq_selected_level = 2'd3; + end else if (irq_pending_q[1] && (irq_current_q < 2'd2)) begin + irq_selected_valid = 1'b1; + irq_selected_level = 2'd2; + end else if (irq_pending_q[0] && (irq_current_q < 2'd1)) begin + irq_selected_valid = 1'b1; + irq_selected_level = 2'd1; + end + end + + // 依据被选中的优先级选择对应的中断入口地址。 + always @* begin + case (irq_selected_level) + 2'd1: irq_vector = IRQ1_VECTOR; + 2'd2: irq_vector = IRQ2_VECTOR; + 2'd3: irq_vector = IRQ3_VECTOR; + default: irq_vector = IRQ1_VECTOR; + endcase + end + + // 响应中断的条件:有已选中中断、MIE 使能、栈未溢出、未停机,且没有 + // 正在处理的重定向/URET/停机事件。 + assign take_irq_o = irq_selected_valid && ustatus_q[0] && + (irq_depth_q < IRQ_STACK_DEPTH) && !halted_i && + !ex_block_i; + + assign vector_o = irq_vector; + assign return_pc_o = uepc_q; + assign pending_o = irq_pending_q; + assign level_o = irq_current_q; + assign uepc_o = uepc_q; + assign ustatus_o = ustatus_q; + + // 组合逻辑计算下一拍的挂起位与 CSR(ustatus/uepc)取值。 + always @* begin + irq_pending_d = irq_pending_q | irq_event; + if (take_irq_o) begin + case (irq_selected_level) + 2'd1: irq_pending_d[0] = 1'b0; + 2'd2: irq_pending_d[1] = 1'b0; + 2'd3: irq_pending_d[2] = 1'b0; + default: irq_pending_d = irq_pending_d; + endcase + end + + ustatus_d = ustatus_q; + uepc_d = uepc_q; + if (csr_valid_i && csr_set_i) begin + if (csr_addr_i == 12'h004) ustatus_d = ustatus_q | csr_imm_i; + else if (csr_addr_i == 12'h041) uepc_d = uepc_q | csr_imm_i; + end else if (csr_valid_i && csr_clear_i) begin + if (csr_addr_i == 12'h004) ustatus_d = ustatus_q & ~csr_imm_i; + else if (csr_addr_i == 12'h041) uepc_d = uepc_q & ~csr_imm_i; + end else if (csr_valid_i && csr_write_i) begin + if (csr_addr_i == 12'h004) ustatus_d = csr_wdata_i; + else if (csr_addr_i == 12'h041) uepc_d = csr_wdata_i; + end + + if (take_irq_o) ustatus_d[0] = 1'b0; + else if (uret_i && (irq_depth_q != 0)) ustatus_d = status_stack[irq_depth_q-1'b1]; + + if (take_irq_o) uepc_d = save_pc_i; + else if (uret_i && (irq_depth_q > 1)) uepc_d = epc_stack[irq_depth_q-2]; + else if (uret_i && (irq_depth_q == 1)) uepc_d = 32'b0; + end + + // 复位时清空中断状态与嵌套栈。 + always @(posedge clk or posedge reset) begin + if (reset) begin + ustatus_q <= USTATUS_INIT; + uepc_q <= 32'b0; + irq_current_q <= 2'b0; + irq_pending_q <= 3'b0; + irq_sync1_q <= 3'b0; + irq_sync2_q <= 3'b0; + irq_prev_q <= 3'b0; + irq_depth_q <= 3'b0; + for (s = 0; s < IRQ_STACK_DEPTH; s = s + 1) begin + epc_stack[s] <= 32'b0; + status_stack[s] <= 32'b0; + priority_stack[s] <= 2'b0; + end + end else begin + // 中断输入打两拍同步,并锁存挂起状态与 CSR。 + irq_sync1_q <= irq_i; + irq_sync2_q <= irq_sync1_q; + irq_prev_q <= irq_sync2_q; + irq_pending_q <= irq_pending_d; + ustatus_q <= ustatus_d; + uepc_q <= uepc_d; + + if (take_irq_o) begin + if (irq_depth_q < IRQ_STACK_DEPTH) begin + epc_stack[irq_depth_q] <= save_pc_i; + status_stack[irq_depth_q] <= ustatus_q; + priority_stack[irq_depth_q] <= irq_current_q; + irq_depth_q <= irq_depth_q + 3'd1; + end + irq_current_q <= irq_selected_level; + end else if (uret_i) begin + if (irq_depth_q != 0) begin + irq_depth_q <= irq_depth_q - 3'd1; + irq_current_q <= priority_stack[irq_depth_q-1'b1]; + end + end + end + end +endmodule + +`default_nettype wire diff --git a/cpu21_riscv_perf_counters.v b/cpu21_riscv_perf_counters.v new file mode 100644 index 0000000..39a90fe --- /dev/null +++ b/cpu21_riscv_perf_counters.v @@ -0,0 +1,76 @@ +`timescale 1ns / 1ps +`default_nettype none + +// ----------------------------------------------------------------------------- +// 性能计数器:周期数、停顿/气泡数、条件分支跳转数、无条件跳转数, +// 以及分支预测的成功/失败次数。 +// +// 这些计数器只影响可观测的统计量,不参与处理器控制,便于在波形中单独 +// 观察流水线效率。 +// ----------------------------------------------------------------------------- +module cpu21_riscv_perf_counters ( + input wire clk, + input wire reset, + input wire inc_cycle_i, // 未停机时周期数 +1 + input wire stall_i, // load-use 停顿 + input wire flush_i, // 冲刷年轻指令 + input wire cond_taken_i, // EX 级条件分支实际跳转 + input wire cond_valid_i, // EX 级条件分支有效 + input wire mispredict_i, // EX 级条件分支预测失败 + input wire uncond_i, // EX 级 JAL/JALR + output wire [15:0] cycle_count_o, + output wire [15:0] stall_count_o, + output wire [15:0] bubble_count_o, + output wire [15:0] conditional_taken_count_o, + output wire [15:0] unconditional_branch_count_o, + output wire [15:0] prediction_success_count_o, + output wire [15:0] prediction_failure_count_o +); + reg [15:0] cycle_count_q; + reg [15:0] stall_count_q; + reg [15:0] bubble_count_q; + reg [15:0] conditional_taken_count_q; + reg [15:0] unconditional_branch_count_q; + reg [15:0] prediction_success_count_q; + reg [15:0] prediction_failure_count_q; + + always @(posedge clk or posedge reset) begin + if (reset) begin + cycle_count_q <= 16'b0; + stall_count_q <= 16'b0; + bubble_count_q <= 16'b0; + conditional_taken_count_q <= 16'b0; + unconditional_branch_count_q <= 16'b0; + prediction_success_count_q <= 16'b0; + prediction_failure_count_q <= 16'b0; + end else begin + if (inc_cycle_i) cycle_count_q <= cycle_count_q + 16'd1; + + if (stall_i) begin + stall_count_q <= stall_count_q + 16'd1; + bubble_count_q <= bubble_count_q + 16'd1; + end else if (flush_i) begin + // 一次冲刷丢弃 IF/ID 两条年轻指令,记两个气泡。 + bubble_count_q <= bubble_count_q + 16'd2; + end + + if (cond_taken_i) conditional_taken_count_q <= conditional_taken_count_q + 16'd1; + if (uncond_i) unconditional_branch_count_q <= unconditional_branch_count_q + 16'd1; + + if (cond_valid_i) begin + if (mispredict_i) prediction_failure_count_q <= prediction_failure_count_q + 16'd1; + else prediction_success_count_q <= prediction_success_count_q + 16'd1; + end + end + end + + assign cycle_count_o = cycle_count_q; + assign stall_count_o = stall_count_q; + assign bubble_count_o = bubble_count_q; + assign conditional_taken_count_o = conditional_taken_count_q; + assign unconditional_branch_count_o = unconditional_branch_count_q; + assign prediction_success_count_o = prediction_success_count_q; + assign prediction_failure_count_o = prediction_failure_count_q; +endmodule + +`default_nettype wire diff --git a/cpu21_riscv_redirect_int_bpb.v b/cpu21_riscv_redirect_int_bpb.v index bb44b20..241a7fa 100644 --- a/cpu21_riscv_redirect_int_bpb.v +++ b/cpu21_riscv_redirect_int_bpb.v @@ -2,10 +2,28 @@ `default_nettype none // ----------------------------------------------------------------------------- -// cpu21-riscv-4.circ -> 可综合 Verilog 实现 +// cpu21-risc-v-4.circ -> 可综合 Verilog 实现(顶层) // -// 本文件保留 Logisim 原设计所使用的指令编码。处理器为五级流水线 -// IF/ID/EX/MEM/WB,主要特性: +// 处理器为五级流水线 IF/ID/EX/MEM/WB。本文件只保留: +// * 五级流水寄存器与各级之间的连线; +// * 各功能子模块的例化; +// * PC / IF-ID / ID-EX 的控制优先级(中断、URET、停机、重定向、停顿、 +// 正常推进); +// * 对外输出与调试信号。 +// +// 各功能单元已拆分为独立文件,便于单独仿真调试: +// cpu21_riscv_alu.v 组合 ALU(result2 用于 MUL 高位 / 余数) +// cpu21_bpb_8.v 八表项全相联分支目标缓冲 +// cpu21_riscv_decoder.v ID 级组合译码器 +// cpu21_riscv_regfile.v 寄存器堆(写优先旁路) +// cpu21_riscv_forward_unit.v EX 级前递网络 +// cpu21_riscv_store_unit.v EX 级存储数据对齐 +// cpu21_riscv_branch_unit.v EX 级控制流裁决 +// cpu21_riscv_hazard_unit.v load-use 冒险与冲刷/停顿判定 +// cpu21_riscv_irq_ctrl.v 中断控制、ustatus/uepc 与嵌套返回栈 +// cpu21_riscv_perf_counters.v 性能计数器 +// +// 主要特性: // * EX 级前递(forwarding)与 load-use 冒险互锁; // * EX 级控制流裁决与流水线重定向; // * 八表项全相联分支目标缓冲(BPB); @@ -17,180 +35,13 @@ // 因此外部字宽 RAM 可用 data_addr_o[11:2] 作为字索引,再依据 // data_wstrb_o 完成字节合并。 // ----------------------------------------------------------------------------- - -// ALU:纯组合运算单元。result2 作为第二结果输出,用于 MUL 的高 32 位 -// 以及 DIVU 的余数;除法则以 b==0 作为除零保护。 -module cpu21_riscv_alu ( - input wire [ 3:0] op, - input wire [31:0] a, - input wire [31:0] b, - output reg [31:0] result, - output reg [31:0] result2 -); - localparam ALU_SLL = 4'd0; - localparam ALU_SRA = 4'd1; - localparam ALU_SRL = 4'd2; - localparam ALU_MUL = 4'd3; - localparam ALU_DIVU = 4'd4; - localparam ALU_ADD = 4'd5; - localparam ALU_SUB = 4'd6; - localparam ALU_AND = 4'd7; - localparam ALU_OR = 4'd8; - localparam ALU_XOR = 4'd9; - localparam ALU_NOR = 4'd10; - localparam ALU_SLT = 4'd11; - localparam ALU_SLTU = 4'd12; - reg [63:0] mult_result; - - always @* begin - mult_result = a * b; - result2 = 32'b0; - case (op) - ALU_SLL: result = a << b[4:0]; - ALU_SRA: result = $signed(a) >>> b[4:0]; - ALU_SRL: result = a >> b[4:0]; - ALU_MUL: begin - result = mult_result[31:0]; - result2 = mult_result[63:32]; - end - ALU_DIVU: begin - result = (b == 32'b0) ? 32'hffff_ffff : a / b; - result2 = (b == 32'b0) ? a : a % b; - end - ALU_ADD: result = a + b; - ALU_SUB: result = a - b; - ALU_AND: result = a & b; - ALU_OR: result = a | b; - ALU_XOR: result = a ^ b; - ALU_NOR: result = ~(a | b); - ALU_SLT: result = ($signed(a) < $signed(b)) ? 32'd1 : 32'd0; - ALU_SLTU: result = (a < b) ? 32'd1 : 32'd0; - default: result = 32'b0; - endcase - end -endmodule - - -// 八表项全相联分支目标缓冲(BPB/BTB)。 -// tag = PC[11:2],与 Logisim 版 BPB 中的十位 tag 拆分保持一致。 -// count 为两位饱和计数器:00/01 表示不跳转,10/11 表示跳转。 -module cpu21_bpb_8 ( - input wire clk, - input wire reset, - input wire [31:0] predict_pc, - input wire predict_enable, - output reg predict_hit, - output reg predict_taken, - output reg [31:0] predict_target, - input wire update_enable, - input wire update_controlflow, - input wire [31:0] update_pc, - input wire [31:0] update_target, - input wire update_taken -); - reg valid [0:7]; - reg [ 9:0] tag [0:7]; - reg [31:0] target [0:7]; - reg [ 1:0] count [0:7]; - reg [ 2:0] age [0:7]; - - reg update_found; - reg [ 2:0] update_index; - reg replace_found; - reg [ 2:0] replace_index; - reg [ 2:0] max_age; - integer p; - integer u; - integer k; - integer selected_index; - - // 预测命中查找:按 tag 全相联匹配,命中则给出目标地址与方向预测。 - always @* begin - predict_hit = 1'b0; - predict_taken = 1'b0; - predict_target = 32'b0; - for (p = 0; p < 8; p = p + 1) begin - if (predict_enable && !predict_hit && valid[p] && (tag[p] == predict_pc[11:2])) begin - predict_hit = 1'b1; - predict_target = target[p]; - predict_taken = count[p][1]; - end - end - end - - // 更新时先找匹配表项;若无匹配则优先使用无效表项,否则淘汰最旧表项。 - // 下方的 age 自增实现了类似 LRU 的替换策略。 - always @* begin - update_found = 1'b0; - update_index = 3'd0; - replace_found = 1'b0; - replace_index = 3'd0; - max_age = 3'd0; - - for (u = 0; u < 8; u = u + 1) begin - if (!update_found && valid[u] && (tag[u] == update_pc[11:2])) begin - update_found = 1'b1; - update_index = u; - end - end - for (u = 0; u < 8; u = u + 1) begin - if (!replace_found && !valid[u]) begin - replace_found = 1'b1; - replace_index = u; - end else if (!replace_found && (age[u] >= max_age)) begin - replace_index = u; - max_age = age[u]; - end - end - end - - // 表项状态更新(含替换与两位饱和计数器更新)。 - always @(posedge clk or posedge reset) begin - if (reset) begin - for (k = 0; k < 8; k = k + 1) begin - valid[k] <= 1'b0; - tag[k] <= 10'b0; - target[k] <= 32'b0; - count[k] <= 2'b01; - age[k] <= 3'b0; - end - end else if (update_enable && update_controlflow) begin - selected_index = update_found ? update_index : replace_index; - - valid[selected_index] <= 1'b1; - tag[selected_index] <= update_pc[11:2]; - target[selected_index] <= update_target; - age[selected_index] <= 3'b0; - - if (!update_found) begin - // 新表项采用较弱的初始状态,避免立刻产生错误预测。 - count[selected_index] <= update_taken ? 2'b10 : 2'b01; - end else if (update_taken) begin - // 实际跳转:计数器加一(饱和到 11)。 - if (count[selected_index] != 2'b11) count[selected_index] <= count[selected_index] + 2'b01; - end else begin - // 实际不跳转:计数器减一(饱和到 00)。 - if (count[selected_index] != 2'b00) count[selected_index] <= count[selected_index] - 2'b01; - end - - for (k = 0; k < 8; k = k + 1) begin - if (valid[k] && (k != selected_index) && (age[k] != 3'b111)) age[k] <= age[k] + 3'b001; - end - end - end -endmodule - - -// 顶层模块:五级流水线 RISC-V 处理器,内置分支预测与多级中断支持。 module cpu21_riscv_redirect_int_bpb #( parameter RESET_PC = 32'h0000_0000, parameter IRQ1_VECTOR = 32'h0000_30ac, parameter IRQ2_VECTOR = 32'h0000_31e4, parameter IRQ3_VECTOR = 32'h0000_3310, parameter IRQ_STACK_DEPTH = 4, - // ustatus 复位值。bit0 为 MIE。课程的中断测试程序只会在中断处理 - // 程序中设置 MIE,若复位后不立即置位 MIE,主程序将永远无法响应 - // 第一次中断,因此这里默认打开 MIE。 + // ustatus 复位值。详见 cpu21_riscv_irq_ctrl 中的说明。 parameter [31:0] USTATUS_INIT = 32'h0000_0001 ) ( input wire clk, @@ -240,381 +91,107 @@ module cpu21_riscv_redirect_int_bpb #( output wire [15:0] prediction_success_count_o, output wire [15:0] prediction_failure_count_o ); - // CPU21 自定义操作码(取自所提供的真值表)。 - localparam OP_LOAD = 5'h00; - localparam OP_R = 5'h0c; - localparam OP_I = 5'h04; - localparam OP_STORE = 5'h08; - localparam OP_JALR = 5'h19; + // 顶层只用到少量常量,完整译码表见 cpu21_riscv_decoder.v。 localparam OP_BRANCH = 5'h18; - localparam OP_JAL = 5'h1b; - localparam OP_SYS = 5'h1c; - - localparam ALU_SLL = 4'd0; - localparam ALU_SRA = 4'd1; - localparam ALU_SRL = 4'd2; - localparam ALU_MUL = 4'd3; - localparam ALU_DIVU = 4'd4; localparam ALU_ADD = 4'd5; - localparam ALU_SUB = 4'd6; - localparam ALU_AND = 4'd7; - localparam ALU_OR = 4'd8; - localparam ALU_XOR = 4'd9; - localparam ALU_SLT = 4'd11; - localparam ALU_SLTU = 4'd12; + // ------------------------------------------------------------------------- + // 流水线寄存器 + // ------------------------------------------------------------------------- // 当前取指 PC。 - reg [31:0] pc_q; + reg [31:0] pc_q; // IF/ID 流水寄存器(含本条指令所携带的分支预测信息)。 - reg ifid_valid_q; - reg [31:0] ifid_pc_q; - reg [31:0] ifid_ir_q; - reg ifid_pred_taken_q; - reg [31:0] ifid_pred_target_q; + reg ifid_valid_q; + reg [31:0] ifid_pc_q; + reg [31:0] ifid_ir_q; + reg ifid_pred_taken_q; + reg [31:0] ifid_pred_target_q; // ID/EX 流水寄存器:保存译码结果与操作数。 - reg idex_valid_q; - reg [31:0] idex_pc_q; - reg [31:0] idex_ir_q; - reg [31:0] idex_rs1_value_q; - reg [31:0] idex_rs2_value_q; - reg [ 4:0] idex_rs1_idx_q; - reg [ 4:0] idex_rs2_idx_q; - reg [ 4:0] idex_rd_q; - reg [31:0] idex_imm_q; - reg [ 3:0] idex_aluop_q; - reg idex_alu_src_q; - reg idex_reg_write_q; - reg idex_mem_to_reg_q; - reg idex_mem_write_q; - reg idex_mem_byte_q; - reg [ 2:0] idex_wb_sel_q; - reg [11:0] idex_csr_addr_q; - reg idex_branch_q; - reg idex_beq_q; - reg idex_bne_q; - reg idex_bltu_q; - reg idex_jal_q; - reg idex_jalr_q; - reg idex_ecall_q; - reg idex_uret_q; - reg idex_csr_set_q; - reg idex_csr_clear_q; - reg idex_csr_write_q; - reg idex_pred_taken_q; - reg [31:0] idex_pred_target_q; + reg idex_valid_q; + reg [31:0] idex_pc_q; + reg [31:0] idex_ir_q; + reg [31:0] idex_rs1_value_q; + reg [31:0] idex_rs2_value_q; + reg [4:0] idex_rs1_idx_q; + reg [4:0] idex_rs2_idx_q; + reg [4:0] idex_rd_q; + reg [31:0] idex_imm_q; + reg [3:0] idex_aluop_q; + reg idex_alu_src_q; + reg idex_reg_write_q; + reg idex_mem_to_reg_q; + reg idex_mem_write_q; + reg idex_mem_byte_q; + reg [2:0] idex_wb_sel_q; + reg [11:0] idex_csr_addr_q; + reg idex_branch_q; + reg idex_beq_q; + reg idex_bne_q; + reg idex_blt_q; + reg idex_bltu_q; + reg idex_jal_q; + reg idex_jalr_q; + reg idex_ecall_q; + reg idex_uret_q; + reg idex_csr_set_q; + reg idex_csr_clear_q; + reg idex_csr_write_q; + reg idex_pred_taken_q; + reg [31:0] idex_pred_target_q; // EX/MEM 流水寄存器:保存 ALU/存储结果与写回控制。 - reg exmem_valid_q; - reg [31:0] exmem_pc_q; - reg [31:0] exmem_ir_q; - reg [31:0] exmem_alu_result_q; - reg [31:0] exmem_alu_result2_q; - reg [31:0] exmem_csr_old_q; - reg [31:0] exmem_write_data_q; - reg [ 4:0] exmem_rd_q; - reg exmem_reg_write_q; - reg exmem_mem_to_reg_q; - reg exmem_mem_write_q; - reg [ 3:0] exmem_wstrb_q; - reg [ 2:0] exmem_wb_sel_q; + reg exmem_valid_q; + reg [31:0] exmem_pc_q; + reg [31:0] exmem_ir_q; + reg [31:0] exmem_alu_result_q; + reg [31:0] exmem_alu_result2_q; + reg [31:0] exmem_csr_old_q; + reg [31:0] exmem_write_data_q; + reg [4:0] exmem_rd_q; + reg exmem_reg_write_q; + reg exmem_mem_to_reg_q; + reg exmem_mem_write_q; + reg [3:0] exmem_wstrb_q; + reg [2:0] exmem_wb_sel_q; // MEM/WB 流水寄存器:保存访存数据与最终写回信息。 - reg memwb_valid_q; - reg [31:0] memwb_pc_q; - reg [31:0] memwb_ir_q; - reg [31:0] memwb_alu_result_q; - reg [31:0] memwb_alu_result2_q; - reg [31:0] memwb_csr_old_q; - reg [31:0] memwb_mem_data_q; - reg [ 4:0] memwb_rd_q; - reg memwb_reg_write_q; - reg memwb_mem_to_reg_q; - reg [ 2:0] memwb_wb_sel_q; + reg memwb_valid_q; + reg [31:0] memwb_pc_q; + reg [31:0] memwb_ir_q; + reg [31:0] memwb_alu_result_q; + reg [31:0] memwb_alu_result2_q; + reg [31:0] memwb_csr_old_q; + reg [31:0] memwb_mem_data_q; + reg [4:0] memwb_rd_q; + reg memwb_reg_write_q; + reg memwb_mem_to_reg_q; + reg [2:0] memwb_wb_sel_q; - // 寄存器堆、LED 输出、停机标志。 - reg [31:0] regfile [ 0:31]; - reg [31:0] led_data_q; - reg led_valid_q; - reg halted_q; - - // 中断相关状态。ustatus[0] 即电路中的 MIE 位。用一个小的硬件栈 - // 保存 EPC/状态/优先级,使嵌套中断能够正确返回。 - reg [31:0] ustatus_q; - reg [31:0] uepc_q; - reg [ 1:0] irq_current_q; - reg [ 2:0] irq_pending_q; - reg [ 2:0] irq_sync1_q; - reg [ 2:0] irq_sync2_q; - reg [ 2:0] irq_prev_q; - reg [31:0] epc_stack [0:IRQ_STACK_DEPTH-1]; - reg [31:0] status_stack [0:IRQ_STACK_DEPTH-1]; - reg [ 1:0] priority_stack [0:IRQ_STACK_DEPTH-1]; - reg [ 2:0] irq_depth_q; - - // 性能计数器,用于统计周期数、停顿、气泡与分支预测效果。 - reg [15:0] cycle_count_q; - reg [15:0] stall_count_q; - reg [15:0] bubble_count_q; - reg [15:0] conditional_taken_count_q; - reg [15:0] unconditional_branch_count_q; - reg [15:0] prediction_success_count_q; - reg [15:0] prediction_failure_count_q; + // LED 输出与停机标志。 + reg [31:0] led_data_q; + reg led_valid_q; + reg halted_q; // ------------------------------------------------------------------------- - // ID 级译码。 - // ------------------------------------------------------------------------- - // Logisim 控制器接收的是标准 RISC-V 操作码字段 IR[6:2](真值表中 - // 以十六进制形式存储该五位字段)。 - wire [ 4:0] d_opcode = ifid_ir_q[6:2]; - wire [ 2:0] d_funct3 = ifid_ir_q[14:12]; - wire [ 6:0] d_funct7 = ifid_ir_q[31:25]; - - reg d_reg_write; - reg d_mem_to_reg; - reg d_mem_write; - reg d_mem_byte; - reg d_alu_src; - reg [ 2:0] d_wb_sel; - reg [ 3:0] d_aluop; - reg d_branch; - reg d_beq; - reg d_bne; - reg d_bltu; - reg d_jal; - reg d_jalr; - reg d_ecall; - reg d_uret; - reg d_csr_set; - reg d_csr_clear; - reg d_csr_write; - reg d_uses_rs1; - reg d_uses_rs2; - reg [ 4:0] d_src1_idx; - reg [ 4:0] d_src2_idx; - reg [ 4:0] d_rd; - reg [31:0] d_imm; - wire [11:0] d_csr_addr = ifid_ir_q[31:20]; - - // 默认控制信号:全部为"无操作/不写回",再由下面的 case 覆盖。 - always @* begin - d_reg_write = 1'b0; - d_mem_to_reg = 1'b0; - d_mem_write = 1'b0; - d_mem_byte = 1'b0; - d_alu_src = 1'b0; - d_wb_sel = 3'd0; - d_aluop = ALU_ADD; - d_branch = 1'b0; - d_beq = 1'b0; - d_bne = 1'b0; - d_bltu = 1'b0; - d_jal = 1'b0; - d_jalr = 1'b0; - d_ecall = 1'b0; - d_uret = 1'b0; - d_csr_set = 1'b0; - d_csr_clear = 1'b0; - d_csr_write = 1'b0; - d_uses_rs1 = 1'b0; - d_uses_rs2 = 1'b0; - d_src1_idx = ifid_ir_q[19:15]; - d_src2_idx = ifid_ir_q[24:20]; - d_rd = ifid_ir_q[11:7]; - d_imm = {{20{ifid_ir_q[31]}}, ifid_ir_q[31:20]}; - - case (d_opcode) - // R 型运算:由 funct3/funct7 决定具体操作。 - OP_R: begin - d_uses_rs1 = 1'b1; - d_uses_rs2 = 1'b1; - d_reg_write = 1'b1; - // 电路中额外的 REMU 控制对应标准 R 型的 funct7=1/funct3=111 - // 形式;此时 ALU 的 result2 即为余数。 - if ((d_funct7 == 7'b0000001) && (d_funct3 == 3'b111)) begin - d_aluop = ALU_DIVU; - d_wb_sel = 3'd3; - end else if ((d_funct7 == 7'b0000001) && (d_funct3 == 3'b000)) begin - d_aluop = ALU_MUL; - end else begin - // 标准 R 型 funct3 译码;add/sub 与 sra/srl 由 funct7[5] 区分。 - case (d_funct3) - 3'b000: d_aluop = (d_funct7[5] ? ALU_SUB : ALU_ADD); - 3'b001: d_aluop = ALU_SLL; - 3'b010: d_aluop = ALU_SLT; - 3'b011: d_aluop = ALU_SLTU; - 3'b100: d_aluop = ALU_XOR; - 3'b101: d_aluop = (d_funct7[5] ? ALU_SRA : ALU_SRL); - 3'b110: d_aluop = ALU_OR; - 3'b111: d_aluop = ALU_AND; - default: d_reg_write = 1'b0; - endcase - end - end - - // I 型运算(addi/slli/slti/xori/srai/srli/ori/andi)。 - OP_I: begin - d_uses_rs1 = 1'b1; - d_alu_src = 1'b1; - d_reg_write = 1'b1; - case (d_funct3) - 3'b000: d_aluop = ALU_ADD; // addi:立即数加 - 3'b001: d_aluop = ALU_SLL; // slli:立即数逻辑左移 - 3'b010: d_aluop = ALU_SLT; // slti:有符号小于置 1 - 3'b100: d_aluop = ALU_XOR; // xori:立即数异或 - 3'b101: - d_aluop = (d_funct7[5] ? ALU_SRA : ALU_SRL); // srai/srli:立即数算术/逻辑右移 - 3'b110: d_aluop = ALU_OR; // ori:立即数或 - 3'b111: d_aluop = ALU_AND; // andi:立即数与 - default: d_reg_write = 1'b0; - endcase - end - - // 加载指令,当前仅支持 lw(funct3=010)。 - OP_LOAD: begin - if (d_funct3 == 3'b010) begin - d_uses_rs1 = 1'b1; - d_alu_src = 1'b1; - d_aluop = ALU_ADD; - d_mem_to_reg=1'b1; - d_reg_write= 1'b1; - d_wb_sel = 3'd1; - end - end - - // 存储指令:sw(funct3=010)与 sb(funct3=000)。 - OP_STORE: begin - if ((d_funct3 == 3'b010) || (d_funct3 == 3'b000)) begin - d_uses_rs1 = 1'b1; - d_uses_rs2 = 1'b1; - d_alu_src = 1'b1; - d_aluop = ALU_ADD; - d_mem_write = 1'b1; - d_mem_byte = (d_funct3 == 3'b000); // sb:字节存储 - d_imm = {{20{ifid_ir_q[31]}}, ifid_ir_q[31:25], ifid_ir_q[11:7]}; - end - end - - // 条件分支:beq(000)/bne(001)/bltu(110)。 - OP_BRANCH: begin - if ((d_funct3 == 3'b000) || (d_funct3 == 3'b001) || (d_funct3 == 3'b110)) begin - d_uses_rs1 = 1'b1; - d_uses_rs2 = 1'b1; - d_branch = 1'b1; - d_beq = (d_funct3 == 3'b000); - d_bne = (d_funct3 == 3'b001); - d_bltu = (d_funct3 == 3'b110); - d_aluop = d_bltu ? ALU_SLTU : ALU_SUB; - d_imm = { - {19{ifid_ir_q[31]}}, - ifid_ir_q[31], - ifid_ir_q[7], - ifid_ir_q[30:25], - ifid_ir_q[11:8], - 1'b0 - }; - end - end - - // 无条件跳转并链接:jal(写回 PC+4)。 - OP_JAL: begin - d_jal = 1'b1; - d_reg_write = 1'b1; - d_wb_sel = 3'd2; - d_imm = { - {11{ifid_ir_q[31]}}, - ifid_ir_q[31], - ifid_ir_q[19:12], - ifid_ir_q[20], - ifid_ir_q[30:21], - 1'b0 - }; - end - - // 寄存器间接跳转并链接:jalr。 - OP_JALR: begin - if (d_funct3 == 3'b000) begin - d_jalr = 1'b1; - d_uses_rs1 = 1'b1; - d_alu_src = 1'b1; - d_aluop = ALU_ADD; - d_reg_write = 1'b1; - d_wb_sel = 3'd2; - end - end - - // 系统指令:ecall / uret / CSR 读写。 - OP_SYS: begin - // 电路用 IR[21] 区分 URET 与 ecall。 - if (d_funct3 == 3'b000) begin - if (ifid_ir_q[21]) begin - d_uret = 1'b1; - end else begin - d_ecall = 1'b1; - // 按文档说明,ecall 读取 a7(rs17) 与 a0(rs10)。 - d_uses_rs1 = 1'b1; - d_uses_rs2 = 1'b1; - d_src1_idx = 5'd17; - d_src2_idx = 5'd10; - end - end else if (d_funct3 == 3'b001) begin - d_csr_write = 1'b1; // CSRRW:写 CSR,并把旧值写回 rd - d_uses_rs1 = 1'b1; - d_alu_src = 1'b1; - d_imm = {27'b0, ifid_ir_q[19:15]}; - d_reg_write = (d_rd != 5'd0); - d_wb_sel = 3'd4; - end else if (d_funct3 == 3'b110) begin - d_csr_set = 1'b1; // CSRRSI:置位 CSR 中的指定位 - d_imm = {27'b0, ifid_ir_q[19:15]}; - d_reg_write = (d_rd != 5'd0); - d_wb_sel = 3'd4; - end else if (d_funct3 == 3'b111) begin - d_csr_clear = 1'b1; // CSRRCI:清除 CSR 中的指定位 - d_imm = {27'b0, ifid_ir_q[19:15]}; - d_reg_write = (d_rd != 5'd0); - d_wb_sel = 3'd4; - end - end - - default: begin - end - endcase - end - - // 写回值选择:wb_sel 0=ALU 结果,1=访存数据,2=PC+4,3=MUL 高位/余数, - // 4=CSR 旧值。 - wire [31:0] wb_value; - assign wb_value = (memwb_wb_sel_q == 3'd1) ? memwb_mem_data_q : - (memwb_wb_sel_q == 3'd2) ? (memwb_pc_q + 32'd4) : - (memwb_wb_sel_q == 3'd3) ? memwb_alu_result2_q : - (memwb_wb_sel_q == 3'd4) ? memwb_csr_old_q : - memwb_alu_result_q; - // ID 级读寄存器:x0 恒为 0,并对同周期 WB 的结果做写优先旁路。 - wire [31:0] d_rs1_value = (d_src1_idx == 5'd0) ? 32'b0 : - ((memwb_valid_q && memwb_reg_write_q && - (memwb_rd_q == d_src1_idx)) ? wb_value : - regfile[d_src1_idx]); - wire [31:0] d_rs2_value = (d_src2_idx == 5'd0) ? 32'b0 : - ((memwb_valid_q && memwb_reg_write_q && - (memwb_rd_q == d_src2_idx)) ? wb_value : - regfile[d_src2_idx]); - - // ------------------------------------------------------------------------- - // IF 级分支预测。JAL/JALR 在 EX 级裁决,条件分支则可以直接依据 BPB + // IF 级:分支预测。JAL/JALR 在 EX 级裁决,条件分支则可以直接依据 BPB // 给出的目标地址重定向取指 PC。 // ------------------------------------------------------------------------- wire [4:0] f_opcode = instr_i[6:2]; wire [2:0] f_funct3 = instr_i[14:12]; wire f_is_branch = (f_opcode == OP_BRANCH) && ((f_funct3 == 3'b000) || (f_funct3 == 3'b001) || - (f_funct3 == 3'b110)); + (f_funct3 == 3'b100) || (f_funct3 == 3'b110)); wire [31:0] bpb_predict_target; wire bpb_predict_hit; wire bpb_predict_taken; wire ex_branch_taken; wire [31:0] ex_target; + wire ex_controlflow; + wire ex_redirect_valid; + wire [31:0] ex_redirect_pc; + // BPB 更新端口:在 EX 级用条件分支的实际结果训练预测器。 wire bpb_update_enable = idex_valid_q && idex_branch_q; wire bpb_update_taken = ex_branch_taken; @@ -642,13 +219,106 @@ module cpu21_riscv_redirect_int_bpb #( bpb_predict_target : (pc_q + 32'd4); // ------------------------------------------------------------------------- - // EX 级:前递网络、ALU 运算与控制流裁决。 + // ID 级:译码与寄存器堆读取 // ------------------------------------------------------------------------- - reg [31:0] ex_src1; - reg [31:0] ex_src2; - wire ex_mem_forward_valid = exmem_valid_q && exmem_reg_write_q && - !exmem_mem_to_reg_q && (exmem_rd_q != 0); - wire wb_forward_valid = memwb_valid_q && memwb_reg_write_q && (memwb_rd_q != 0); + wire [4:0] d_src1_idx; + wire [4:0] d_src2_idx; + wire [4:0] d_rd; + wire [31:0] d_imm; + wire [3:0] d_aluop; + wire [2:0] d_wb_sel; + wire [11:0] d_csr_addr; + wire d_uses_rs1; + wire d_uses_rs2; + wire d_reg_write; + wire d_mem_to_reg; + wire d_mem_write; + wire d_mem_byte; + wire d_alu_src; + wire d_branch; + wire d_beq; + wire d_bne; + wire d_blt; + wire d_bltu; + wire d_jal; + wire d_jalr; + wire d_ecall; + wire d_uret; + wire d_csr_set; + wire d_csr_clear; + wire d_csr_write; + + cpu21_riscv_decoder u_decoder ( + .ir_i (ifid_ir_q), + .rs1_idx_o (d_src1_idx), + .rs2_idx_o (d_src2_idx), + .rd_o (d_rd), + .imm_o (d_imm), + .alu_op_o (d_aluop), + .wb_sel_o (d_wb_sel), + .csr_addr_o (d_csr_addr), + .uses_rs1_o (d_uses_rs1), + .uses_rs2_o (d_uses_rs2), + .reg_write_o (d_reg_write), + .mem_to_reg_o(d_mem_to_reg), + .mem_write_o (d_mem_write), + .mem_byte_o (d_mem_byte), + .alu_src_o (d_alu_src), + .branch_o (d_branch), + .beq_o (d_beq), + .bne_o (d_bne), + .blt_o (d_blt), + .bltu_o (d_bltu), + .jal_o (d_jal), + .jalr_o (d_jalr), + .ecall_o (d_ecall), + .uret_o (d_uret), + .csr_set_o (d_csr_set), + .csr_clear_o (d_csr_clear), + .csr_write_o (d_csr_write) + ); + + // 写回值选择:wb_sel 0=ALU 结果,1=访存数据,2=PC+4,3=MUL 高位/余数, + // 4=CSR 旧值。 + wire [31:0] wb_value; + assign wb_value = (memwb_wb_sel_q == 3'd1) ? memwb_mem_data_q : + (memwb_wb_sel_q == 3'd2) ? (memwb_pc_q + 32'd4) : + (memwb_wb_sel_q == 3'd3) ? memwb_alu_result2_q : + (memwb_wb_sel_q == 3'd4) ? memwb_csr_old_q : + memwb_alu_result_q; + + // WB 级写回使能(同时用于寄存器堆的写端口与写优先旁路)。 + wire memwb_reg_write_en = memwb_valid_q && memwb_reg_write_q; + + // ID 级读寄存器:x0 恒为 0,并对同周期 WB 的结果做写优先旁路。 + wire [31:0] d_rs1_value; + wire [31:0] d_rs2_value; + + cpu21_riscv_regfile u_regfile ( + .clk (clk), + .reset (reset), + .rs1_idx_i (d_src1_idx), + .rs2_idx_i (d_src2_idx), + .rs1_data_o(d_rs1_value), + .rs2_data_o(d_rs2_value), + .we_i (memwb_reg_write_en), + .waddr_i (memwb_rd_q), + .wdata_i (wb_value) + ); + + // 中断/CSR 相关连线(中断控制器实例见下方"中断 / CSR"小节)。 + wire take_irq; + wire [31:0] irq_vector; + wire [31:0] ex_return_pc; + wire [2:0] irq_pending; + wire [1:0] irq_current_level; + wire [31:0] uepc_value; + wire [31:0] ustatus_value; + + // ------------------------------------------------------------------------- + // EX 级:前递网络、ALU 运算、存储对齐与控制流裁决 + // ------------------------------------------------------------------------- + // EX/MEM 级要前递的值(按 wb_sel 选取)。 wire [31:0] exmem_forward_value = (exmem_wb_sel_q == 3'd2) ? (exmem_pc_q + 32'd4) : (exmem_wb_sel_q == 3'd3) ? exmem_alu_result2_q : @@ -657,15 +327,26 @@ module cpu21_riscv_redirect_int_bpb #( // 前递优先级:EX/MEM 的结果最新,其次为 MEM/WB 写回值,最后才是 // 译码时读到的寄存器值。 - always @* begin - ex_src1 = idex_rs1_value_q; - ex_src2 = idex_rs2_value_q; - if (ex_mem_forward_valid && (exmem_rd_q == idex_rs1_idx_q)) ex_src1 = exmem_forward_value; - else if (wb_forward_valid && (memwb_rd_q == idex_rs1_idx_q)) ex_src1 = wb_value; + wire [31:0] ex_src1; + wire [31:0] ex_src2; - if (ex_mem_forward_valid && (exmem_rd_q == idex_rs2_idx_q)) ex_src2 = exmem_forward_value; - else if (wb_forward_valid && (memwb_rd_q == idex_rs2_idx_q)) ex_src2 = wb_value; - end + cpu21_riscv_forward_unit u_forward_unit ( + .rs1_value_i (idex_rs1_value_q), + .rs2_value_i (idex_rs2_value_q), + .rs1_idx_i (idex_rs1_idx_q), + .rs2_idx_i (idex_rs2_idx_q), + .exmem_valid_i (exmem_valid_q), + .exmem_reg_write_i (exmem_reg_write_q), + .exmem_mem_to_reg_i(exmem_mem_to_reg_q), + .exmem_rd_i (exmem_rd_q), + .exmem_value_i (exmem_forward_value), + .memwb_valid_i (memwb_valid_q), + .memwb_reg_write_i (memwb_reg_write_q), + .memwb_rd_i (memwb_rd_q), + .memwb_value_i (wb_value), + .src1_o (ex_src1), + .src2_o (ex_src2) + ); // ALU 的 B 端:立即数或 rs2。 wire [31:0] ex_alu_b = idex_alu_src_q ? idex_imm_q : ex_src2; @@ -673,8 +354,9 @@ module cpu21_riscv_redirect_int_bpb #( wire [31:0] ex_alu_result2; // 读取 CSR 旧值(ustatus=0x004、uepc=0x041),供 CSR 指令回写 rd。 wire [31:0] ex_csr_old_value = - (idex_csr_addr_q == 12'h004) ? ustatus_q : - (idex_csr_addr_q == 12'h041) ? uepc_q : 32'b0; + (idex_csr_addr_q == 12'h004) ? ustatus_value : + (idex_csr_addr_q == 12'h041) ? uepc_value : 32'b0; + // ALU 实例(纯组合逻辑)。 cpu21_riscv_alu u_ex_alu ( .op (idex_aluop_q), @@ -684,158 +366,115 @@ module cpu21_riscv_redirect_int_bpb #( .result2(ex_alu_result2) ); - // 条件分支裁决:beq 相等、bne 不等、bltu 无符号小于。 - assign ex_branch_taken = idex_branch_q && - ((idex_beq_q && (ex_src1 == ex_src2)) || - (idex_bne_q && (ex_src1 != ex_src2)) || - (idex_bltu_q && (ex_src1 < ex_src2))); - // 跳转目标:jalr 为 (rs1+imm) 且最低位清零;jal 为 PC+imm。 - assign ex_target = idex_jalr_q ? - ((ex_src1 + idex_imm_q) & 32'hffff_fffe) : - (idex_pc_q + idex_imm_q); - // EX 级存在需要裁决的控制流指令。 - wire ex_controlflow = idex_valid_q && (idex_branch_q || idex_jal_q || idex_jalr_q); - // 需要重定向的条件:条件分支的预测方向/目标与实际不符;JAL/JALR 则 - // 一定在 EX 级才解析出目标,因此总是需要重定向。 - wire ex_redirect_valid = ex_controlflow && - (idex_branch_q ? - ((idex_pred_taken_q != ex_branch_taken) || - (idex_pred_taken_q && ex_branch_taken && - (idex_pred_target_q != ex_target))) : 1'b1); - wire [31:0] ex_redirect_pc = - (idex_branch_q && !ex_branch_taken) ? (idex_pc_q + 32'd4) : ex_target; + // 控制流裁决:条件分支方向/目标、JAL/JALR 目标、是否需要重定向。 + cpu21_riscv_branch_unit u_branch_unit ( + .valid_i (idex_valid_q), + .pc_i (idex_pc_q), + .imm_i (idex_imm_q), + .src1_i (ex_src1), + .src2_i (ex_src2), + .branch_i (idex_branch_q), + .beq_i (idex_beq_q), + .bne_i (idex_bne_q), + .blt_i (idex_blt_q), + .bltu_i (idex_bltu_q), + .jal_i (idex_jal_q), + .jalr_i (idex_jalr_q), + .pred_taken_i (idex_pred_taken_q), + .pred_target_i (idex_pred_target_q), + .branch_taken_o (ex_branch_taken), + .controlflow_o (ex_controlflow), + .target_o (ex_target), + .redirect_valid_o(ex_redirect_valid), + .redirect_pc_o (ex_redirect_pc) + ); // 存储数据对齐:依据字节地址低两位生成写数据与字节写掩码。 - reg [31:0] ex_store_data; - reg [3:0] ex_store_wstrb; - always @* begin - ex_store_data = ex_src2; - ex_store_wstrb = 4'b0; - if (idex_mem_write_q) begin - if (idex_mem_byte_q) begin - case (ex_alu_result[1:0]) - 2'd0: begin - ex_store_data = {24'b0, ex_src2[7:0]}; - ex_store_wstrb = 4'b0001; - end - 2'd1: begin - ex_store_data = {16'b0, ex_src2[7:0], 8'b0}; - ex_store_wstrb = 4'b0010; - end - 2'd2: begin - ex_store_data = {8'b0, ex_src2[7:0], 16'b0}; - ex_store_wstrb = 4'b0100; - end - default: begin - ex_store_data = {ex_src2[7:0], 24'b0}; - ex_store_wstrb = 4'b1000; - end - endcase - end else begin - ex_store_data = ex_src2; - ex_store_wstrb = 4'b1111; - end - end - end + wire [31:0] ex_store_data; + wire [ 3:0] ex_store_wstrb; + + cpu21_riscv_store_unit u_store_unit ( + .addr_i (ex_alu_result), + .data_i (ex_src2), + .mem_write_i (idex_mem_write_q), + .mem_byte_i (idex_mem_byte_q), + .store_data_o (ex_store_data), + .store_wstrb_o(ex_store_wstrb) + ); // ecall 约定:a7==34 时输出 LED;否则令处理器停机,等待 go_i 重新启动。 wire ex_led_event = idex_valid_q && idex_ecall_q && (ex_src1 == 32'd34); wire ex_halt_event = idex_valid_q && idex_ecall_q && (ex_src1 != 32'd34); - // EX 级为 load、而 ID 级指令立即使用其结果时,需要插入一个气泡。 - // ALU 与分支的数据相关由上面的前递网络解决。 - wire load_use_hazard = ifid_valid_q && idex_valid_q && idex_mem_to_reg_q && - (idex_rd_q != 5'd0) && - ((d_uses_rs1 && (d_src1_idx == idex_rd_q)) || - (d_uses_rs2 && (d_src2_idx == idex_rd_q))); - // ------------------------------------------------------------------------- - // 中断采样与优先级选择。 - // IRQ3 优先级最高,其次 IRQ2,最后 IRQ1。嵌套请求只有在优先级高于 - // 当前级别时才会被接纳。 + // 中断 / CSR / 嵌套返回栈 // ------------------------------------------------------------------------- - // 上升沿检测:只在 irq_i 由 0 变 1 的那一拍产生中断事件。 - wire [2:0] irq_event = irq_sync2_q & ~irq_prev_q; - reg irq_selected_valid; - reg [1:0] irq_selected_level; - // 优先级仲裁:在当前级别允许的条件下选出优先级最高的中断源。 - always @* begin - irq_selected_valid = 1'b0; - irq_selected_level = 2'd0; - if (irq_pending_q[2] && (irq_current_q < 2'd3)) begin - irq_selected_valid = 1'b1; - irq_selected_level = 2'd3; - end else if (irq_pending_q[1] && (irq_current_q < 2'd2)) begin - irq_selected_valid = 1'b1; - irq_selected_level = 2'd2; - end else if (irq_pending_q[0] && (irq_current_q < 2'd1)) begin - irq_selected_valid = 1'b1; - irq_selected_level = 2'd1; - end - end - wire ex_uret_redirect = idex_valid_q && idex_uret_q; - // uepc_q 是当前运行上下文的架构返回 PC,栈中缓存的是被中断的外层 - // 上下文。这里使用 uepc_q,与课程讲义中用 CSRRW 保存/恢复 uepc 的 - // 流程保持一致。 - wire [31:0] ex_return_pc = uepc_q; - // 响应中断的条件:有已选中中断、MIE 使能、栈未溢出、未停机,且没有 - // 正在处理的重定向/URET/停机事件。 - wire take_irq = irq_selected_valid && ustatus_q[0] && - (irq_depth_q < IRQ_STACK_DEPTH) && !halted_q && - !ex_redirect_valid && !ex_uret_redirect && !ex_halt_event; - // 响应中断时允许 EX 级指令正常完成。恢复地址取"下一个架构 PC":若 - // EX 是一条预测正确且已跳转的分支,则该 PC 就是它的目标地址;若 EX - // 为空,则 IF/ID 中保存的就是尚未执行的最老指令。 + // EX 级正在重定向/URET/停机时不允许响应中断。 + wire ex_block_irq = ex_redirect_valid || ex_uret_redirect || ex_halt_event; + // 中断发生时要保存的返回地址取"下一个架构 PC":若 EX 是一条预测正确 + // 且已跳转的分支,则该 PC 就是它的目标地址;若 EX 为空,则 IF/ID 中 + // 保存的就是尚未执行的最老指令。 wire [31:0] irq_save_pc = idex_valid_q ? ((idex_branch_q && ex_branch_taken) ? ex_target : (idex_pc_q + 32'd4)) : (ifid_valid_q ? ifid_pc_q : pc_q); - // 依据被选中的优先级选择对应的中断入口地址。 - reg [31:0] irq_vector; - always @* begin - case (irq_selected_level) - 2'd1: irq_vector = IRQ1_VECTOR; - 2'd2: irq_vector = IRQ2_VECTOR; - 2'd3: irq_vector = IRQ3_VECTOR; - default: irq_vector = IRQ1_VECTOR; - endcase - end - // 组合逻辑计算下一拍的挂起位与 CSR(ustatus/uepc)取值。 - reg [ 2:0] irq_pending_d; - reg [31:0] ustatus_d; - reg [31:0] uepc_d; - always @* begin - irq_pending_d = irq_pending_q | irq_event; - if (take_irq) begin - case (irq_selected_level) - 2'd1: irq_pending_d[0] = 1'b0; - 2'd2: irq_pending_d[1] = 1'b0; - 2'd3: irq_pending_d[2] = 1'b0; - default: irq_pending_d = irq_pending_d; - endcase - end - ustatus_d = ustatus_q; - uepc_d = uepc_q; - if (idex_valid_q && idex_csr_set_q) begin - if (idex_csr_addr_q == 12'h004) ustatus_d = ustatus_q | idex_imm_q; - else if (idex_csr_addr_q == 12'h041) uepc_d = uepc_q | idex_imm_q; - end else if (idex_valid_q && idex_csr_clear_q) begin - if (idex_csr_addr_q == 12'h004) ustatus_d = ustatus_q & ~idex_imm_q; - else if (idex_csr_addr_q == 12'h041) uepc_d = uepc_q & ~idex_imm_q; - end else if (idex_valid_q && idex_csr_write_q) begin - if (idex_csr_addr_q == 12'h004) ustatus_d = ex_src1; - else if (idex_csr_addr_q == 12'h041) uepc_d = ex_src1; - end + cpu21_riscv_irq_ctrl #( + .IRQ1_VECTOR (IRQ1_VECTOR), + .IRQ2_VECTOR (IRQ2_VECTOR), + .IRQ3_VECTOR (IRQ3_VECTOR), + .IRQ_STACK_DEPTH(IRQ_STACK_DEPTH), + .USTATUS_INIT (USTATUS_INIT) + ) u_irq_ctrl ( + .clk (clk), + .reset (reset), + .irq_i (irq_i), + .halted_i (halted_q), + .ex_block_i (ex_block_irq), + .save_pc_i (irq_save_pc), + .uret_i (ex_uret_redirect), + .csr_valid_i(idex_valid_q), + .csr_set_i (idex_csr_set_q), + .csr_clear_i(idex_csr_clear_q), + .csr_write_i(idex_csr_write_q), + .csr_addr_i (idex_csr_addr_q), + .csr_imm_i (idex_imm_q), + .csr_wdata_i(ex_src1), + .take_irq_o (take_irq), + .vector_o (irq_vector), + .return_pc_o(ex_return_pc), + .pending_o (irq_pending), + .level_o (irq_current_level), + .uepc_o (uepc_value), + .ustatus_o (ustatus_value) + ); - if (take_irq) ustatus_d[0] = 1'b0; - else if (ex_uret_redirect && (irq_depth_q != 0)) ustatus_d = status_stack[irq_depth_q-1'b1]; + // ------------------------------------------------------------------------- + // 冒险 / 停顿 / 冲刷判定 + // ------------------------------------------------------------------------- + wire load_use_hazard; + wire flush_younger; + wire pipeline_stall; - if (take_irq) uepc_d = irq_save_pc; - else if (ex_uret_redirect && (irq_depth_q > 1)) uepc_d = epc_stack[irq_depth_q-2]; - else if (ex_uret_redirect && (irq_depth_q == 1)) uepc_d = 32'b0; - end + cpu21_riscv_hazard_unit u_hazard_unit ( + .ifid_valid_i (ifid_valid_q), + .idex_valid_i (idex_valid_q), + .idex_mem_to_reg_i(idex_mem_to_reg_q), + .idex_rd_i (idex_rd_q), + .d_uses_rs1_i (d_uses_rs1), + .d_uses_rs2_i (d_uses_rs2), + .d_src1_idx_i (d_src1_idx), + .d_src2_idx_i (d_src2_idx), + .take_irq_i (take_irq), + .uret_redirect_i (ex_uret_redirect), + .halt_event_i (ex_halt_event), + .redirect_valid_i (ex_redirect_valid), + .halted_i (halted_q), + .load_use_hazard_o(load_use_hazard), + .flush_younger_o (flush_younger), + .pipeline_stall_o (pipeline_stall) + ); // ------------------------------------------------------------------------- // 对外输出与调试信号。 @@ -865,33 +504,20 @@ module cpu21_riscv_redirect_int_bpb #( assign reg_write_o = memwb_valid_q && memwb_reg_write_q; assign mem_write_o = exmem_valid_q && exmem_mem_write_q; assign halted_o = halted_q; - assign irq_pending_o = irq_pending_q; - assign irq_current_level_o = irq_current_q; - assign uepc_o = uepc_q; - assign ustatus_o = ustatus_q; + assign irq_pending_o = irq_pending; + assign irq_current_level_o = irq_current_level; + assign uepc_o = uepc_value; + assign ustatus_o = ustatus_value; assign bpb_predict_hit_o = bpb_predict_hit; assign bpb_predict_taken_o = bpb_predict_taken; assign bpb_mispredict_o = ex_redirect_valid && idex_branch_q; - assign cycle_count_o = cycle_count_q; - assign stall_count_o = stall_count_q; - assign bubble_count_o = bubble_count_q; - assign conditional_taken_count_o = conditional_taken_count_q; - assign unconditional_branch_count_o = unconditional_branch_count_q; - assign prediction_success_count_o = prediction_success_count_q; - assign prediction_failure_count_o = prediction_failure_count_q; - - // 需要冲刷年轻指令/气泡的情况:中断、URET、ecall 停机、分支重定向。 - wire flush_younger = take_irq || ex_uret_redirect || ex_halt_event || ex_redirect_valid; - // load-use 冒险且无需冲刷时,插入一个气泡(停顿一拍)。 - wire pipeline_stall = load_use_hazard && !flush_younger && !halted_q; // ------------------------------------------------------------------------- - // 状态更新。顺序遵循五级流水线:先 WB 写回,再捕获 MEM/WB、EX/MEM, - // 最后处理 ID/EX 与 IF/ID/PC 的控制。 + // 状态更新。顺序遵循五级流水线:先捕获 MEM/WB、EX/MEM,再处理 + // ID/EX 与 IF/ID/PC 的控制。 // ------------------------------------------------------------------------- - integer r; - integer s; - // 复位时清空所有流水寄存器、寄存器堆、中断状态与性能计数器。 + // 复位时清空所有流水寄存器与 LED/停机标志(寄存器堆、中断状态与 + // 性能计数器在各自的子模块内复位)。 always @(posedge clk or posedge reset) begin if (reset) begin pc_q <= RESET_PC; @@ -921,6 +547,7 @@ module cpu21_riscv_redirect_int_bpb #( idex_branch_q <= 1'b0; idex_beq_q <= 1'b0; idex_bne_q <= 1'b0; + idex_blt_q <= 1'b0; idex_bltu_q <= 1'b0; idex_jal_q <= 1'b0; idex_jalr_q <= 1'b0; @@ -961,44 +588,9 @@ module cpu21_riscv_redirect_int_bpb #( led_data_q <= 32'b0; led_valid_q <= 1'b0; halted_q <= 1'b0; - ustatus_q <= USTATUS_INIT; - uepc_q <= 32'b0; - irq_current_q <= 2'b0; - irq_pending_q <= 3'b0; - irq_sync1_q <= 3'b0; - irq_sync2_q <= 3'b0; - irq_prev_q <= 3'b0; - irq_depth_q <= 3'b0; - cycle_count_q <= 16'b0; - stall_count_q <= 16'b0; - bubble_count_q <= 16'b0; - conditional_taken_count_q <= 16'b0; - unconditional_branch_count_q <= 16'b0; - prediction_success_count_q <= 16'b0; - prediction_failure_count_q <= 16'b0; - for (r = 0; r < 32; r = r + 1) regfile[r] <= 32'b0; - for (s = 0; s < IRQ_STACK_DEPTH; s = s + 1) begin - epc_stack[s] <= 32'b0; - status_stack[s] <= 32'b0; - priority_stack[s] <= 2'b0; - end end else begin - if (!halted_q) cycle_count_q <= cycle_count_q + 16'd1; led_valid_q <= 1'b0; - // WB 级:写回寄存器堆(x0 恒为 0)。 - if (memwb_valid_q && memwb_reg_write_q && (memwb_rd_q != 5'd0)) - regfile[memwb_rd_q] <= wb_value; - regfile[0] <= 32'b0; - - // 中断输入打两拍同步,并锁存挂起状态与 CSR。 - irq_sync1_q <= irq_i; - irq_sync2_q <= irq_sync1_q; - irq_prev_q <= irq_sync2_q; - irq_pending_q <= irq_pending_d; - ustatus_q <= ustatus_d; - uepc_q <= uepc_d; - if (ex_led_event) begin led_data_q <= ex_src2; led_valid_q <= 1'b1; @@ -1006,21 +598,6 @@ module cpu21_riscv_redirect_int_bpb #( if (go_i) halted_q <= 1'b0; if (ex_halt_event) halted_q <= 1'b1; - if (take_irq) begin - if (irq_depth_q < IRQ_STACK_DEPTH) begin - epc_stack[irq_depth_q] <= irq_save_pc; - status_stack[irq_depth_q] <= ustatus_q; - priority_stack[irq_depth_q] <= irq_current_q; - irq_depth_q <= irq_depth_q + 3'd1; - end - irq_current_q <= irq_selected_level; - end else if (ex_uret_redirect) begin - if (irq_depth_q != 0) begin - irq_depth_q <= irq_depth_q - 3'd1; - irq_current_q <= priority_stack[irq_depth_q-1'b1]; - end - end - // MEM/WB 流水寄存器捕获。 memwb_valid_q <= exmem_valid_q; memwb_pc_q <= exmem_pc_q; @@ -1076,6 +653,7 @@ module cpu21_riscv_redirect_int_bpb #( idex_branch_q <= d_branch; idex_beq_q <= d_beq; idex_bne_q <= d_bne; + idex_blt_q <= d_blt; idex_bltu_q <= d_bltu; idex_jal_q <= d_jal; idex_jalr_q <= d_jalr; @@ -1126,23 +704,30 @@ module cpu21_riscv_redirect_int_bpb #( ifid_pred_taken_q <= f_is_branch && bpb_predict_hit && bpb_predict_taken; ifid_pred_target_q <= bpb_predict_target; end - - // 性能计数器:停顿/气泡、条件分支实际跳转数、无条件跳转数, - // 以及分支预测的成功/失败次数。 - if (pipeline_stall) begin - stall_count_q <= stall_count_q + 16'd1; - bubble_count_q <= bubble_count_q + 16'd1; - end else if (flush_younger) bubble_count_q <= bubble_count_q + 16'd2; - if (idex_valid_q && idex_branch_q && ex_branch_taken) - conditional_taken_count_q <= conditional_taken_count_q + 16'd1; - if (idex_valid_q && (idex_jal_q || idex_jalr_q)) - unconditional_branch_count_q <= unconditional_branch_count_q + 16'd1; - if (idex_valid_q && idex_branch_q) begin - if (ex_redirect_valid) prediction_failure_count_q <= prediction_failure_count_q + 16'd1; - else prediction_success_count_q <= prediction_success_count_q + 16'd1; - end end end + + // ------------------------------------------------------------------------- + // 性能计数器(只影响统计量,不参与控制)。 + // ------------------------------------------------------------------------- + cpu21_riscv_perf_counters u_perf_counters ( + .clk (clk), + .reset (reset), + .inc_cycle_i (!halted_q), + .stall_i (pipeline_stall), + .flush_i (flush_younger), + .cond_taken_i (idex_valid_q && idex_branch_q && ex_branch_taken), + .cond_valid_i (idex_valid_q && idex_branch_q), + .mispredict_i (ex_redirect_valid), + .uncond_i (idex_valid_q && (idex_jal_q || idex_jalr_q)), + .cycle_count_o (cycle_count_o), + .stall_count_o (stall_count_o), + .bubble_count_o (bubble_count_o), + .conditional_taken_count_o (conditional_taken_count_o), + .unconditional_branch_count_o(unconditional_branch_count_o), + .prediction_success_count_o (prediction_success_count_o), + .prediction_failure_count_o (prediction_failure_count_o) + ); endmodule `default_nettype wire diff --git a/cpu21_riscv_redirect_int_bpb说明.md b/cpu21_riscv_redirect_int_bpb说明.md index ee69486..362e301 100644 --- a/cpu21_riscv_redirect_int_bpb说明.md +++ b/cpu21_riscv_redirect_int_bpb说明.md @@ -1,15 +1,16 @@ # `cpu21-riscv-4.circ` 的 Verilog 实现 -文件:`cpu21_riscv_redirect_int_bpb.v` +顶层文件:`cpu21_riscv_redirect_int_bpb.v` -该文件对应原电路中的“重定向流水线+中断+分支预测”部分,顶层模块为 -`cpu21_riscv_redirect_int_bpb`,并包含独立的 `cpu21_riscv_alu` 和 `cpu21_bpb_8` 模块。 +该设计对应原电路中的“重定向流水线+中断+分支预测”部分,顶层模块为 +`cpu21_riscv_redirect_int_bpb`。为便于单独仿真与调试,各功能单元已拆分为 +独立文件,顶层的 ALU、BPB 也已移出,具体见文末“文件与模块划分”。 ## 已转换的功能 - 五级流水:IF、ID、EX、MEM、WB。 - EX/MEM 与 MEM/WB 前递,以及 load-use 一拍停顿。 -- 条件分支 `beq`、`bne`、`bltu` 在 EX 段判定,错误预测时清空 IF/ID、ID/EX 并重定向 PC。 +- 条件分支 `beq`、`bne`、`blt`(有符号小于)、`bltu`(无符号小于)在 EX 段判定,错误预测时清空 IF/ID、ID/EX 并重定向 PC。真值表中 BLT 的 `ALU_OP` 为 SLT,与实现一致。 - `jal`、`jalr` 在 EX 段重定向,返回地址写回 `rd`。 - 8 项全相联 BPB:标签 `PC[11:2]`,每项包含 valid、目标 PC、2 位饱和计数器和 3 位年龄字段。BPB 只由条件分支更新,避免 `jal`、`jalr` 占用不会被查询的表项;未命中时优先使用无效项,否则替换年龄最大的项。 - 三路中断输入 `irq_i[2:0]`:两级同步、上升沿挂起、IRQ3 > IRQ2 > IRQ1 优先级,仅在 `ustatus[0]`(MIE)允许时响应。 @@ -23,16 +24,16 @@ 表中的 `opcode` 是控制器使用的五位字段 `IR[6:2]`,不是包含最低两位的 7 位原始 opcode。 因此它与标准 RISC-V 指令的低 7 位 opcode 相差右移两位。 -| 指令类别 | opcode | 说明 | -| ---------------------- | -----: | --------------------------------------- | -| R 型 | `0x0c` | add/sub/and/or/xor/slt/sltu/sll/srl/sra | -| I 型 ALU | `0x04` | addi/andi/ori/xori/slti/slli/srli/srai | -| `lw` | `0x00` | `funct3=010` | -| `sw` / `sb` | `0x08` | `funct3=010` / `000` | -| `beq` / `bne` / `bltu` | `0x18` | `funct3=000` / `001` / `110` | -| `jal` | `0x1b` | J 型立即数 | -| `jalr` | `0x19` | `funct3=000` | -| 系统类 | `0x1c` | `ecall`、`URET`、CSRRSI、CSRRCI | +| 指令类别 | opcode | 说明 | +| ------------------------------ | -----: | --------------------------------------- | +| R 型 | `0x0c` | add/sub/and/or/xor/slt/sltu/sll/srl/sra | +| I 型 ALU | `0x04` | addi/andi/ori/xori/slti/slli/srli/srai | +| `lw` | `0x00` | `funct3=010` | +| `sw` / `sb` | `0x08` | `funct3=010` / `000` | +| `beq` / `bne` / `blt` / `bltu` | `0x18` | `funct3=000` / `001` / `100` / `110` | +| `jal` | `0x1b` | J 型立即数 | +| `jalr` | `0x19` | `funct3=000` | +| 系统类 | `0x1c` | `ecall`、`URET`、CSRRSI、CSRRCI | `ecall` 和 `URET` 都是 `opcode=0x1c、funct3=0`,由 `IR[21]` 区分: `IR[21]=0` 是 `ecall`,`IR[21]=1` 是 `URET`。这一点来自原控制器中的 `IR21` 分支逻辑。 @@ -58,3 +59,23 @@ 6. `cycle_count_o` 在 `halted_o` 为高时暂停;load-use 每次计入一个气泡,重定向或中断清空 IF/ID 与当前 IF 时计入两个气泡。 原 `.circ` 文件中的 ROM、MIPS RAM、按钮、LED 和调试显示器属于 Logisim 外围,不直接搬入 RTL;其功能分别由指令接口、数据接口、`go_i`、`led_*_o` 和调试端口替代。 + +## 文件与模块划分 + +| 文件 | 模块 | 职责 | +| -------------------------------- | ------------------------------ | --------------------------------------------------------------- | +| `cpu21_riscv_redirect_int_bpb.v` | `cpu21_riscv_redirect_int_bpb` | 顶层:五级流水寄存器、子模块例化、PC/IF-ID/ID-EX 控制、对外输出 | +| `cpu21_riscv_alu.v` | `cpu21_riscv_alu` | 组合 ALU(`result2` 用于 MUL 高位 / DIVU 余数) | +| `cpu21_bpb_8.v` | `cpu21_bpb_8` | 8 项全相联分支目标缓冲(2 位饱和计数器 + 近似 LRU) | +| `cpu21_riscv_decoder.v` | `cpu21_riscv_decoder` | ID 级组合译码器(操作码 `IR[6:2]`) | +| `cpu21_riscv_regfile.v` | `cpu21_riscv_regfile` | 寄存器堆,`x0` 恒为 0,同周期 WB 写优先旁路 | +| `cpu21_riscv_forward_unit.v` | `cpu21_riscv_forward_unit` | EX 级前递网络(EX/MEM 优先于 MEM/WB) | +| `cpu21_riscv_store_unit.v` | `cpu21_riscv_store_unit` | EX 级存储数据对齐:`sw`/`sb` 的写数据与字节写掩码 | +| `cpu21_riscv_branch_unit.v` | `cpu21_riscv_branch_unit` | EX 级控制流裁决:分支方向/目标、JAL/JALR 重定向 | +| `cpu21_riscv_hazard_unit.v` | `cpu21_riscv_hazard_unit` | load-use 冒险、停顿(stall)与冲刷(flush)判定 | +| `cpu21_riscv_irq_ctrl.v` | `cpu21_riscv_irq_ctrl` | 中断采样与优先级、`ustatus`/`uepc` CSR、嵌套返回栈 | +| `cpu21_riscv_perf_counters.v` | `cpu21_riscv_perf_counters` | 周期/停顿/气泡/分支/预测成功率等统计计数器 | + +拆分只调整了模块边界与文件组织,端口、时序与行为完全等价:使用同一 ROM 程序 +在 xsim 下对拆分前后逐周期对比,波形输出与最终统计(周期、停顿、气泡、分支 +跳转、预测成败)完全一致。 diff --git a/cpu21_riscv_regfile.v b/cpu21_riscv_regfile.v new file mode 100644 index 0000000..1b3eb60 --- /dev/null +++ b/cpu21_riscv_regfile.v @@ -0,0 +1,46 @@ +`timescale 1ns / 1ps +`default_nettype none + +// ----------------------------------------------------------------------------- +// 寄存器堆:32 x 32 位,x0 恒为 0。 +// +// 读端口为组合读,并对同周期的 WB 写回做"写优先"旁路,因此 ID 级可以 +// 立刻看到正在写回的结果,无需额外的寄存器堆相关冒险处理。 +// ----------------------------------------------------------------------------- +module cpu21_riscv_regfile ( + input wire clk, + input wire reset, + // 读端口(组合) + input wire [ 4:0] rs1_idx_i, + input wire [ 4:0] rs2_idx_i, + output wire [31:0] rs1_data_o, + output wire [31:0] rs2_data_o, + // 写端口(WB 级) + input wire we_i, + input wire [ 4:0] waddr_i, + input wire [31:0] wdata_i +); + reg [31:0] regs[0:31]; + + integer i; + + // 写优先旁路:同周期写回且地址相同的读请求返回新值。 + wire [31:0] rs1_raw = regs[rs1_idx_i]; + wire [31:0] rs2_raw = regs[rs2_idx_i]; + + assign rs1_data_o = (rs1_idx_i == 5'd0) ? 32'b0 : + ((we_i && (waddr_i == rs1_idx_i)) ? wdata_i : rs1_raw); + assign rs2_data_o = (rs2_idx_i == 5'd0) ? 32'b0 : + ((we_i && (waddr_i == rs2_idx_i)) ? wdata_i : rs2_raw); + + always @(posedge clk or posedge reset) begin + if (reset) begin + for (i = 0; i < 32; i = i + 1) regs[i] <= 32'b0; + end else begin + if (we_i && (waddr_i != 5'd0)) regs[waddr_i] <= wdata_i; + regs[0] <= 32'b0; + end + end +endmodule + +`default_nettype wire diff --git a/cpu21_riscv_store_unit.v b/cpu21_riscv_store_unit.v new file mode 100644 index 0000000..b62f161 --- /dev/null +++ b/cpu21_riscv_store_unit.v @@ -0,0 +1,49 @@ +`timescale 1ns / 1ps +`default_nettype none + +// ----------------------------------------------------------------------------- +// EX 级存储数据对齐单元。 +// +// 依据地址低两位生成写数据与字节写掩码:sw 输出全字 + 1111,sb 把数据的 +// 低 8 位搬到对应的字节车道,并只置位对应的写掩码。 +// ----------------------------------------------------------------------------- +module cpu21_riscv_store_unit ( + input wire [31:0] addr_i, + input wire [31:0] data_i, + input wire mem_write_i, + input wire mem_byte_i, + output reg [31:0] store_data_o, + output reg [ 3:0] store_wstrb_o +); + always @* begin + store_data_o = data_i; + store_wstrb_o = 4'b0; + if (mem_write_i) begin + if (mem_byte_i) begin + case (addr_i[1:0]) + 2'd0: begin + store_data_o = {24'b0, data_i[7:0]}; + store_wstrb_o = 4'b0001; + end + 2'd1: begin + store_data_o = {16'b0, data_i[7:0], 8'b0}; + store_wstrb_o = 4'b0010; + end + 2'd2: begin + store_data_o = {8'b0, data_i[7:0], 16'b0}; + store_wstrb_o = 4'b0100; + end + default: begin + store_data_o = {data_i[7:0], 24'b0}; + store_wstrb_o = 4'b1000; + end + endcase + end else begin + store_data_o = data_i; + store_wstrb_o = 4'b1111; + end + end + end +endmodule + +`default_nettype wire diff --git a/真值表.txt b/testbench/programs/真值表.txt similarity index 100% rename from 真值表.txt rename to testbench/programs/真值表.txt diff --git a/testbench/tb_cpu21_riscv_redirect_int_bpb.v b/testbench/tb_cpu21_riscv_redirect_int_bpb.v index 16acc68..fcba92c 100644 --- a/testbench/tb_cpu21_riscv_redirect_int_bpb.v +++ b/testbench/tb_cpu21_riscv_redirect_int_bpb.v @@ -1,20 +1,18 @@ -// Compile together with cpu21_riscv_redirect_int_bpb.v. -// Example: -// iverilog -g2012 -o cpu21_sim cpu21_riscv_redirect_int_bpb.v tb_cpu21_riscv_redirect_int_bpb.v +// 与 RTL 文件(cpu21_riscv_*.v、cpu21_bpb_8.v)一起编译。 +// 示例: +// iverilog -g2012 -o cpu21_sim cpu21_*.v tb_cpu21_riscv_redirect_int_bpb.v // vvp cpu21_sim -// The simulation writes cpu21_riscv_redirect_int_bpb.vcd for GTKWave. +// 仿真会写出 cpu21_riscv_redirect_int_bpb.vcd,可供 GTKWave 查看。 // -// The program image is loaded into the ROM model with $readmemh from -// ROM_FILE. A failed $readmemh only prints a warning and leaves the memory -// untouched, so a wrong path used to make the ROM look like all-NOP -// (0x00000013). The simulator working directory is not fixed (Vivado xsim -// runs in .sim/sim_1/behav/xsim), so this testbench probes a list of -// candidate paths and reports which one worked. An explicit path always -// wins: +// 程序镜像通过 $readmemh 从 ROM_FILE 载入 ROM 模型。$readmemh 找不到文件时 +// 只会打印警告并保持内存原样,所以路径写错会让 ROM 看起来全是 NOP +// (0x00000013)。仿真器的工作目录并不固定(Vivado xsim 在 +// .sim/sim_1/behav/xsim 下运行),因此本测试平台会依次探测若干候选 +// 路径,并报告实际命中的那个。显式给出的路径始终优先: // vvp cpu21_sim +ROM_FILE= (iverilog/vvp) // xelab ... -generic_top "ROM_FILE=" (Vivado xsim) module tb_cpu21_riscv_redirect_int_bpb #( - parameter ROM_FILE = "risc-v-benchmark_ccab.hex" + parameter ROM_FILE = "cpu21_riscv_redirect_int_bpb_rom.hex" // risc-v-benchmark_ccab.hex // cpu21_riscv_redirect_int_bpb_rom.hex // risc-v-branch-predict.hex @@ -22,13 +20,12 @@ module tb_cpu21_riscv_redirect_int_bpb #( localparam integer ROM_WORDS = 1024; localparam integer RAM_WORDS = 1024; localparam integer PATH_LEN = 512; - // Sentinel that cannot appear as the first ROM word of a RISC-V image. + // 哨兵值:不可能出现在 RISC-V 镜像的第一个字中。 localparam [31:0] EMPTY_ROM_WORD = 32'hFFFF_FFFF; - // Interrupt entry addresses, shared by the DUT instance and the trace - // condition below. The DUT addresses the ROM with PC[11:2], so these three - // entries live at words 43 / 120 / 192 of the program image (the first - // "sw ..., 0(sp)" of each handler prologue). + // 中断入口地址:DUT 例化与下面的打印条件共用。DUT 用 PC[11:2] 访问 ROM, + // 因此这三个入口位于程序镜像的第 43 / 120 / 192 个字(即每个中断处理 + // 程序开头的第一条 "sw ..., 0(sp)")。 localparam [31:0] IRQ1_VECTOR = 32'h0000_30ac; localparam [31:0] IRQ2_VECTOR = 32'h0000_31e0; localparam [31:0] IRQ3_VECTOR = 32'h0000_3300; @@ -92,8 +89,8 @@ module tb_cpu21_riscv_redirect_int_bpb #( .IRQ1_VECTOR(IRQ1_VECTOR), .IRQ2_VECTOR(IRQ2_VECTOR), .IRQ3_VECTOR(IRQ3_VECTOR), - // The interrupt test program writes MIE only inside its handlers, so - // the testbench starts with MIE already set (ustatus[0] = 1). + // 中断测试程序只在中断处理程序内部写 MIE,因此测试平台在复位后就让 + // MIE 有效(ustatus[0] = 1)。 .USTATUS_INIT(32'h0000_0001) ) dut ( .clk (clk), @@ -142,20 +139,20 @@ module tb_cpu21_riscv_redirect_int_bpb #( always #5 clk = ~clk; - // The Logisim ROM uses PC[11:2] as its word address. This also maps - // the three original interrupt vectors to the same ROM entries. + // Logisim 的 ROM 以 PC[11:2] 作为字地址;这也让原来的三个中断向量映射到 + // 相同的 ROM 表项。 always @* begin instr_i = 32'h00000013; if (instr_addr_o[11:2] < ROM_WORDS) instr_i = rom[instr_addr_o[11:2]]; end - // Asynchronous read model for data memory. + // 数据存储器的异步读模型。 always @* begin data_rdata_i = 32'b0; if (data_addr_o[11:2] < RAM_WORDS) data_rdata_i = ram[data_addr_o[11:2]]; end - // Synchronous write model with byte enables. + // 带字节使能的同步写模型。 always @(posedge clk) begin if (!reset && data_we_o && (data_addr_o[11:2] < RAM_WORDS)) begin if (data_wstrb_o[0]) ram[data_addr_o[11:2]][7:0] <= data_wdata_o[7:0]; @@ -183,9 +180,8 @@ module tb_cpu21_riscv_redirect_int_bpb #( end end - // Try to load the ROM image from "fname". "loaded" is 1 when the file - // existed and contained at least one word. A sentinel in rom[0] makes a - // silent load failure impossible to miss. + // 尝试从 "fname" 载入 ROM 镜像。"loaded" 为 1 表示文件存在且至少包含一个 + // 字。利用 rom[0] 中的哨兵值,可以避免载入失败被静默忽略。 task load_rom_image; input [8*PATH_LEN-1:0] fname; output loaded; @@ -198,7 +194,7 @@ module tb_cpu21_riscv_redirect_int_bpb #( rom[0] = EMPTY_ROM_WORD; $readmemh(fname, rom); if (rom[0] === EMPTY_ROM_WORD) begin - // Opened but empty: restore the default NOP fill. + // 文件能打开但内容为空:恢复默认的 NOP 填充。 rom[0] = 32'h0000_0013; end else begin rom_file_path = fname; @@ -219,13 +215,12 @@ module tb_cpu21_riscv_redirect_int_bpb #( for (i = 0; i < ROM_WORDS; i = i + 1) rom[i] = 32'h00000013; for (i = 0; i < RAM_WORDS; i = i + 1) ram[i] = 32'b0; - // Seed a few words so memory activity is visible in the waveform. + // 预置几个字,便于在波形中观察数据存储器的活动。 ram[1] = 32'h1234_5678; ram[2] = 32'h89ab_cdef; - // Probe the usual locations for the ROM image. The first readable - // candidate wins; failed probes below are harmless (the ROM keeps its - // 0x00000013 fill until a real image is found). + // 依次探测 ROM 镜像的常见位置,第一个可读的候选文件生效;下面的探测 + // 失败是无害的(在找到真正的镜像之前,ROM 保持 0x00000013 填充)。 $display("NOTE: searching for ROM image \"%0s\" ...", ROM_FILE); rom_loaded = 1'b0; @@ -246,7 +241,7 @@ module tb_cpu21_riscv_redirect_int_bpb #( #22 reset = 1'b0; - // Button-like interrupt pulses. The DUT synchronizes and latches them. + // 模拟按键式的中断脉冲,DUT 内部会同步并锁存它们。 repeat (80) @(negedge clk); irq_i[0] = 1'b1; @(negedge clk); diff --git a/testbench/tb_no_intr.v b/testbench/tb_no_intr.v new file mode 100644 index 0000000..380a0fa --- /dev/null +++ b/testbench/tb_no_intr.v @@ -0,0 +1,258 @@ +// 仿真顶层:tb_no_intr —— 与 tb_cpu21_riscv_redirect_int_bpb.v 相同的测试框架, +// 但不注入任何中断脉冲,因此主程序可以不受打扰地连续运行。 +// +// 与 RTL 文件(cpu21_riscv_*.v、cpu21_bpb_8.v)一起编译。 +// 示例: +// iverilog -g2012 -o cpu21_sim cpu21_*.v tb_no_intr.v +// vvp cpu21_sim +// 仿真会写出 tb_no_intr.vcd,可供 GTKWave 查看。 +// +// 程序镜像通过 $readmemh 从 ROM_FILE 载入 ROM 模型。$readmemh 找不到文件时 +// 只会打印警告并保持内存原样,所以路径写错会让 ROM 看起来全是 NOP +// (0x00000013)。仿真器的工作目录并不固定(Vivado xsim 在 +// .sim/sim_1/behav/xsim 下运行),因此本测试平台会依次探测若干候选 +// 路径,并报告实际命中的那个。显式给出的路径始终优先: +// vvp cpu21_sim +ROM_FILE= (iverilog/vvp) +// xelab ... -generic_top "ROM_FILE=" (Vivado xsim) +module tb_no_intr #( + parameter ROM_FILE = "risc-v-benchmark_ccab.hex" + // risc-v-benchmark_ccab.hex + // cpu21_riscv_redirect_int_bpb_rom.hex + // risc-v-branch-predict.hex +); + localparam integer ROM_WORDS = 1024; + localparam integer RAM_WORDS = 1024; + localparam integer PATH_LEN = 512; + // 哨兵值:不可能出现在 RISC-V 镜像的第一个字中。 + localparam [31:0] EMPTY_ROM_WORD = 32'hFFFF_FFFF; + + // 中断入口地址:DUT 例化与下面的打印条件共用。DUT 用 PC[11:2] 访问 ROM, + // 因此这三个入口位于程序镜像的第 43 / 120 / 192 个字(即每个中断处理 + // 程序开头的第一条 "sw ..., 0(sp)")。 + localparam [31:0] IRQ1_VECTOR = 32'h0000_30ac; + localparam [31:0] IRQ2_VECTOR = 32'h0000_31e0; + localparam [31:0] IRQ3_VECTOR = 32'h0000_3300; + + reg clk; + reg reset; + reg [ 31:0] instr_i; + reg [ 31:0] data_rdata_i; + reg [ 2:0] irq_i; + reg go_i; + + reg [ 31:0] rom [0:ROM_WORDS-1]; + reg [ 31:0] ram [0:RAM_WORDS-1]; + + wire [ 31:0] instr_addr_o; + wire [ 31:0] data_addr_o; + wire [ 31:0] data_wdata_o; + wire [ 3:0] data_wstrb_o; + wire data_we_o; + wire [ 31:0] led_data_o; + wire led_valid_o; + wire [ 31:0] if_pc_o; + wire [ 31:0] id_pc_o; + wire [ 31:0] ex_pc_o; + wire [ 31:0] mem_pc_o; + wire [ 31:0] wb_pc_o; + wire [ 31:0] if_ir_o; + wire [ 31:0] id_ir_o; + wire [ 31:0] ex_ir_o; + wire [ 31:0] mem_ir_o; + wire [ 31:0] wb_ir_o; + wire [ 31:0] rdin_o; + wire [ 31:0] mdin_o; + wire reg_write_o; + wire mem_write_o; + wire halted_o; + + wire [ 2:0] irq_pending_o; + wire [ 1:0] irq_current_level_o; + wire [ 31:0] uepc_o; + wire [ 31:0] ustatus_o; + wire bpb_predict_hit_o; + wire bpb_predict_taken_o; + wire bpb_mispredict_o; + + wire [ 15:0] cycle_count_o; + wire [ 15:0] stall_count_o; + wire [ 15:0] bubble_count_o; + wire [ 15:0] conditional_taken_count_o; + wire [ 15:0] unconditional_branch_count_o; + wire [ 15:0] prediction_success_count_o; + wire [ 15:0] prediction_failure_count_o; + + integer i; + integer tb_cycle; + reg rom_loaded; + reg [8*PATH_LEN-1:0] rom_file_path; + + cpu21_riscv_redirect_int_bpb #( + .RESET_PC (32'h0000_0000), + .IRQ1_VECTOR(IRQ1_VECTOR), + .IRQ2_VECTOR(IRQ2_VECTOR), + .IRQ3_VECTOR(IRQ3_VECTOR), + // 与中断测试平台一致,复位后即使能 MIE。本测试平台从不产生 irq_i + // 脉冲,因此该参数只有在你自行添加脉冲时才有影响。 + .USTATUS_INIT(32'h0000_0001) + ) dut ( + .clk (clk), + .reset (reset), + .instr_i (instr_i), + .data_rdata_i (data_rdata_i), + .irq_i (irq_i), + .go_i (go_i), + .instr_addr_o (instr_addr_o), + .data_addr_o (data_addr_o), + .data_wdata_o (data_wdata_o), + .data_wstrb_o (data_wstrb_o), + .data_we_o (data_we_o), + .led_data_o (led_data_o), + .led_valid_o (led_valid_o), + .if_pc_o (if_pc_o), + .id_pc_o (id_pc_o), + .ex_pc_o (ex_pc_o), + .mem_pc_o (mem_pc_o), + .wb_pc_o (wb_pc_o), + .if_ir_o (if_ir_o), + .id_ir_o (id_ir_o), + .ex_ir_o (ex_ir_o), + .mem_ir_o (mem_ir_o), + .wb_ir_o (wb_ir_o), + .rdin_o (rdin_o), + .mdin_o (mdin_o), + .reg_write_o (reg_write_o), + .mem_write_o (mem_write_o), + .halted_o (halted_o), + .irq_pending_o (irq_pending_o), + .irq_current_level_o (irq_current_level_o), + .uepc_o (uepc_o), + .ustatus_o (ustatus_o), + .bpb_predict_hit_o (bpb_predict_hit_o), + .bpb_predict_taken_o (bpb_predict_taken_o), + .bpb_mispredict_o (bpb_mispredict_o), + .cycle_count_o (cycle_count_o), + .stall_count_o (stall_count_o), + .bubble_count_o (bubble_count_o), + .conditional_taken_count_o (conditional_taken_count_o), + .unconditional_branch_count_o(unconditional_branch_count_o), + .prediction_success_count_o (prediction_success_count_o), + .prediction_failure_count_o (prediction_failure_count_o) + ); + + always #5 clk = ~clk; + + // Logisim 的 ROM 以 PC[11:2] 作为字地址;这也让原来的三个中断向量映射到 + // 相同的 ROM 表项。 + always @* begin + instr_i = 32'h00000013; + if (instr_addr_o[11:2] < ROM_WORDS) instr_i = rom[instr_addr_o[11:2]]; + end + + // 数据存储器的异步读模型。 + always @* begin + data_rdata_i = 32'b0; + if (data_addr_o[11:2] < RAM_WORDS) data_rdata_i = ram[data_addr_o[11:2]]; + end + + // 带字节使能的同步写模型。 + always @(posedge clk) begin + if (!reset && data_we_o && (data_addr_o[11:2] < RAM_WORDS)) begin + if (data_wstrb_o[0]) ram[data_addr_o[11:2]][7:0] <= data_wdata_o[7:0]; + if (data_wstrb_o[1]) ram[data_addr_o[11:2]][15:8] <= data_wdata_o[15:8]; + if (data_wstrb_o[2]) ram[data_addr_o[11:2]][23:16] <= data_wdata_o[23:16]; + if (data_wstrb_o[3]) ram[data_addr_o[11:2]][31:24] <= data_wdata_o[31:24]; + end + end + + always @(posedge clk) begin + if (reset) begin + tb_cycle = 0; + end else begin + tb_cycle = tb_cycle + 1; + if ((tb_cycle <= 20) || data_we_o || led_valid_o || + (irq_pending_o != 3'b000) || + (instr_addr_o == IRQ1_VECTOR) || + (instr_addr_o == IRQ2_VECTOR) || + (instr_addr_o == IRQ3_VECTOR)) begin + $display( + "t=%0t cyc=%0d PC=%08h ID=%08h EX=%08h MEM=%08h WB=%08h irq=%b pend=%b uepc=%08h LEDv=%b LED=%08h memwe=%b addr=%08h wdata=%08h wstrb=%b", + $time, tb_cycle, if_pc_o, id_ir_o, ex_ir_o, mem_ir_o, wb_ir_o, irq_i, irq_pending_o, + uepc_o, led_valid_o, led_data_o, data_we_o, data_addr_o, data_wdata_o, data_wstrb_o); + end + end + end + + // 尝试从 "fname" 载入 ROM 镜像。"loaded" 为 1 表示文件存在且至少包含一个 + // 字。利用 rom[0] 中的哨兵值,可以避免载入失败被静默忽略。 + task load_rom_image; + input [8*PATH_LEN-1:0] fname; + output loaded; + integer fh; + begin + loaded = 1'b0; + fh = $fopen(fname, "r"); + if (fh != 0) begin + $fclose(fh); + rom[0] = EMPTY_ROM_WORD; + $readmemh(fname, rom); + if (rom[0] === EMPTY_ROM_WORD) begin + // 文件能打开但内容为空:恢复默认的 NOP 填充。 + rom[0] = 32'h0000_0013; + end else begin + rom_file_path = fname; + loaded = 1'b1; + $display("NOTE: ROM image loaded from \"%0s\".", fname); + end + end + end + endtask + + initial begin + clk = 1'b0; + reset = 1'b1; + irq_i = 3'b000; + go_i = 1'b0; + tb_cycle = 0; + + for (i = 0; i < ROM_WORDS; i = i + 1) rom[i] = 32'h00000013; + for (i = 0; i < RAM_WORDS; i = i + 1) ram[i] = 32'b0; + + // 预置几个字,便于在波形中观察数据存储器的活动。 + ram[1] = 32'h1234_5678; + ram[2] = 32'h89ab_cdef; + + // 依次探测 ROM 镜像的常见位置,第一个可读的候选文件生效;下面的探测 + // 失败是无害的(在找到真正的镜像之前,ROM 保持 0x00000013 填充)。 + $display("NOTE: searching for ROM image \"%0s\" ...", ROM_FILE); + + rom_loaded = 1'b0; + if ($value$plusargs("ROM_FILE=%s", rom_file_path)) load_rom_image(rom_file_path, rom_loaded); + if (!rom_loaded) load_rom_image(ROM_FILE, rom_loaded); + if (!rom_loaded) load_rom_image({"testbench/", ROM_FILE}, rom_loaded); + if (!rom_loaded) load_rom_image({"../testbench/", ROM_FILE}, rom_loaded); + if (!rom_loaded) load_rom_image({"../../../testbench/", ROM_FILE}, rom_loaded); + if (!rom_loaded) load_rom_image({"../../../../../testbench/", ROM_FILE}, rom_loaded); + if (!rom_loaded) + $display( + "WARNING: no ROM image found for \"%0s\" - the ROM stays filled with NOPs (0x00000013).", + ROM_FILE + ); + + $dumpfile("tb_no_intr.vcd"); + $dumpvars(0, tb_no_intr); + + #22 reset = 1'b0; + + // 本测试平台不注入中断脉冲:irq_i 始终为 3'b000,因此只走主程序 + //(非中断)执行路径。 + repeat (5560) @(negedge clk); + $display( + "FINAL: tb_cycles=%0d dut_cycles=%0d stalls=%0d bubbles=%0d cond_taken=%0d uncond=%0d pred_ok=%0d pred_fail=%0d halted=%b", + tb_cycle, cycle_count_o, stall_count_o, bubble_count_o, conditional_taken_count_o, + unconditional_branch_count_o, prediction_success_count_o, prediction_failure_count_o, + halted_o); + $finish; + end +endmodule +