异常:CSR寄存器
为了能运行更多的C程序,并且在出现问题时记录下来,CSR寄存器是必不可缺少的。他也是切换特权模式的重要组件。虽然RISCV规范中已经将CSR从I指令集中移除,但是支持一下还是很重要的。
不多,支持最基础的机器模式相关寄存器即可。
CSR寄存器和通用寄存器堆类似,也是同步写异步读。但它不放在ID级,而是在EX级,因为我们的异常检测主要放在EX级进行。此外,CSR需要立即响应异常。另一方面,将CSR读写放在关键路径较长的EX级,也可以防止拉长其他级的关键路径。
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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
| `include "include/defines.svh"
module CSR ( input logic clk, input logic rst_n,
input logic csr_we, input logic [11:0] csr_addr, input logic [31:0] csr_wdata, input logic [ 2:0] csr_op, output logic [31:0] csr_rdata, input logic exception_valid, input logic [31:0] exception_pc, input logic [31:0] exception_cause, input logic [31:0] exception_tval, input logic mret_valid, output logic trap_to_mmode, output logic [31:0] trap_target, output logic [31:0] mret_target );
logic [31:0] mstatus; logic [31:0] mtvec; logic [31:0] mepc; logic [31:0] mcause; logic [31:0] mscratch; logic [31:0] mtval; logic [31:0] mie; logic [31:0] mip; logic [31:0] misa; logic [63:0] mcycle;
localparam integer MIE_BIT = 3; localparam integer MPIE_BIT = 7; localparam integer MPP_LOW = 11; localparam integer MPP_HIGH = 12;
always_comb begin case (csr_addr) `CSR_MSTATUS: csr_rdata = mstatus; `CSR_MTVEC: csr_rdata = mtvec; `CSR_MEPC: csr_rdata = mepc; `CSR_MCAUSE: csr_rdata = mcause; `CSR_MIE: csr_rdata = mie; `CSR_MIP: csr_rdata = mip; `CSR_MSCRATCH: csr_rdata = mscratch; `CSR_MTVAL: csr_rdata = mtval; `CSR_MISA: csr_rdata = misa; `CSR_MCYCLE, `CSR_CYCLE: csr_rdata = mcycle[31:0]; `CSR_MCYCLEH, `CSR_CYCLEH: csr_rdata = mcycle[63:32]; default: csr_rdata = 32'b0; endcase end
logic [31:0] csr_new_value; always_comb begin case (csr_op) `FUNCT3_CSRRW, `FUNCT3_CSRRWI: csr_new_value = csr_wdata; `FUNCT3_CSRRS, `FUNCT3_CSRRSI: csr_new_value = csr_rdata | csr_wdata; `FUNCT3_CSRRC, `FUNCT3_CSRRCI: csr_new_value = csr_rdata & (~csr_wdata); default: csr_new_value = csr_rdata; endcase end
always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) begin mstatus <= 32'h0000_1800; mtvec <= 32'h0114_5140; mepc <= 32'h0000_0000; mcause <= 32'h0000_0000; mscratch <= 32'h0000_0000; mtval <= 32'h0000_0000; mie <= 32'h0000_0000; mip <= 32'h0000_0000; misa <= 32'h4000_0100; end else if (exception_valid) begin mepc <= exception_pc; mcause <= exception_cause; mtval <= exception_tval; mstatus[MPIE_BIT] <= mstatus[MIE_BIT]; mstatus[MIE_BIT] <= 1'b0; mstatus[MPP_HIGH:MPP_LOW] <= 2'b11; end else if (mret_valid) begin mstatus[MIE_BIT] <= mstatus[MPIE_BIT]; mstatus[MPIE_BIT] <= 1'b1; mstatus[MPP_HIGH:MPP_LOW] <= 2'b11; end else if (csr_we) begin case (csr_addr) `CSR_MSTATUS: mstatus <= csr_new_value & 32'h0000_1888; `CSR_MTVEC: mtvec <= {csr_new_value[31:2], 2'b00}; `CSR_MEPC: mepc <= {csr_new_value[31:2], 2'b00}; `CSR_MCAUSE: mcause <= csr_new_value; `CSR_MIE: mie <= csr_new_value; `CSR_MSCRATCH: mscratch <= csr_new_value; `CSR_MTVAL: mtval <= csr_new_value; `CSR_MISA: misa <= misa; default: ; endcase end end
always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) begin mcycle <= 64'h0; end else if (csr_we && csr_addr == `CSR_MCYCLE) begin mcycle[31:0] <= csr_new_value; end else if (csr_we && csr_addr == `CSR_MCYCLEH) begin mcycle[63:32] <= csr_new_value; end else begin mcycle <= mcycle + 64'h1; end end
assign trap_to_mmode = exception_valid; assign trap_target = {mtvec[31:2], 2'b00}; assign mret_target = mepc;
endmodule
|
为什么在RISCV规范内,32位CPU的时钟周期计数器也要设计为64位?
为了避免计数器频繁溢出、简化软件、性能分析的使用RV32 的时钟/指令计数器也要以 64 位来实现。如果按照32位算,假设主频为100Mhz,只要42秒就会溢出——这也太折磨了。但32位的寄存器最大位宽不够,因此要分两个,一个MCYCLE一个MCYCLEH。
在读取时,要读三遍:
1 2 3
| csrr t0, cycleh # read high csrr t1, cycle # read low csrr t2, cycleh # read high again
|
因为这两个 CSR 的读取不是原子的:在读它们的间隙时,低 32 位可能发生溢出并把进位加到高 32 位上。我总不能为这个实现A扩展吧。
当然,对于64位的就简单多了,读取一遍就行了。
顶层模块修改
在顶层模块内加上相关的异常判断:
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 84 85 86
|
logic instr_misaligned_EX; logic [31:0] jalr_target_EX; assign jalr_target_EX = {alu_result_EX[31:1], 1'b0}; assign instr_misaligned_EX = (jump_type_EX == `JUMP_JALR) && (alu_result_EX[1] != 1'b0);
logic load_misaligned_EX; logic store_misaligned_EX; always_comb begin load_misaligned_EX = 1'b0; store_misaligned_EX = 1'b0; case (sl_type_EX) `MEM_LW: load_misaligned_EX = (alu_result_EX[1:0] != 2'b00); `MEM_LH, `MEM_LHU: load_misaligned_EX = (alu_result_EX[0] != 1'b0); `MEM_SW: store_misaligned_EX = (alu_result_EX[1:0] != 2'b00); `MEM_SH: store_misaligned_EX = (alu_result_EX[0] != 1'b0); default: begin load_misaligned_EX = 1'b0; store_misaligned_EX = 1'b0; end endcase end
logic exception_valid_EX; logic take_branch_normal; assign exception_valid_EX = (instr_misaligned_EX || load_misaligned_EX || store_misaligned_EX || is_ecall_EX) && valid_EX;
assign exception_valid = exception_valid_EX || (illegal_instr_exception_ID && !take_branch_normal);
always_comb begin if (instr_misaligned_EX && valid_EX) begin exception_pc = pc_EX; exception_cause = 32'd0; exception_tval = jalr_target_EX; end else if (load_misaligned_EX && valid_EX) begin exception_pc = pc_EX; exception_cause = 32'd4; exception_tval = alu_result_EX; end else if (store_misaligned_EX && valid_EX) begin exception_pc = pc_EX; exception_cause = 32'd6; exception_tval = alu_result_EX; end else if (is_ecall_EX && valid_EX) begin exception_pc = pc_EX; exception_cause = 32'd11; exception_tval = 32'b0; end else if (illegal_instr_exception_ID) begin exception_pc = illegal_instr_pc_ID; exception_cause = 32'd2; exception_tval = illegal_instr_encoding_ID; end else begin exception_pc = pc_EX; exception_cause = 32'd0; exception_tval = 32'b0; end end
|
然后跑一下riscv-test,一切顺利。