第11课 · RV32I 指令集
RISC-V指令编码RV32I
📌 学习目标:掌握 RV32I 基本整数指令集的编码格式,理解 R/I/S/B/U/J 五种指令类型,用 Verilog 实现指令译码并通过 Verilator 验证编码正确性。
一、RISC-V 设计哲学
RISC-V(Reduced Instruction Set Computer - V)是加州大学伯克利分校开发的开放指令集架构:
- 精简:基础指令集仅 40 条指令
- 模块化:RV32I 为基础,可扩展 M/A/F/D/C
- 开放:免费使用,无专利限制
- 规整:指令格式规整,译码简单
二、指令编码格式
RV32I 所有指令固定 32 位,有 5 种格式:
| 格式 | 31-25 | 24-20 | 19-15 | 14-12 | 11-7 | 6-0 |
| R | funct7 | rs2 | rs1 | funct3 | rd | opcode |
| I | imm[11:0] | rs1 | funct3 | rd | opcode |
| S | imm[11:5] | rs2 | rs1 | funct3 | imm[4:0] | opcode |
| B | imm[12|10:5] | rs2 | rs1 | funct3 | imm[4:1|11] | opcode |
| U | imm[31:12] | rd | opcode |
| J | imm[20|10:1|11|19:12] | — | rd | opcode |
三、核心 20 条指令
3.1 R-Type(寄存器-寄存器操作)
| 指令 | opcode | funct3 | funct7 | 操作 |
| ADD | 0110011 | 000 | 0000000 | rd = rs1 + rs2 |
| SUB | 0110011 | 000 | 0100000 | rd = rs1 - rs2 |
| AND | 0110011 | 111 | 0000000 | rd = rs1 & rs2 |
| OR | 0110011 | 110 | 0000000 | rd = rs1 | rs2 |
| XOR | 0110011 | 100 | 0000000 | rd = rs1 ^ rs2 |
| SLL | 0110011 | 001 | 0000000 | rd = rs1 << rs2 |
| SRL | 0110011 | 101 | 0000000 | rd = rs1 >> rs2 |
| SLT | 0110011 | 010 | 0000000 | rd = (rs1 < rs2) ? 1 : 0 |
3.2 I-Type(立即数操作)
| 指令 | opcode | funct3 | 操作 |
| ADDI | 0010011 | 000 | rd = rs1 + imm |
| ANDI | 0010011 | 111 | rd = rs1 & imm |
| ORI | 0010011 | 110 | rd = rs1 | imm |
| LW | 0000011 | 010 | rd = Mem[rs1 + imm] |
3.3 S-Type / B-Type / U-Type / J-Type
| 指令 | 类型 | opcode | funct3 | 操作 |
| SW | S | 0100011 | 010 | Mem[rs1+imm] = rs2 |
| BEQ | B | 1100011 | 000 | if(rs1==rs2) PC+=imm |
| BNE | B | 1100011 | 001 | if(rs1!=rs2) PC+=imm |
| LUI | U | 0110111 | — | rd = imm << 12 |
| AUIPC | U | 0010111 | — | rd = PC + (imm<<12) |
| JAL | J | 1101111 | — | rd=PC+4; PC+=imm |
| JALR | I | 1100111 | 000 | rd=PC+4; PC=rs1+imm |
四、Verilog 指令译码器
module rv32i_decode (
input [31:0] instr,
output [6:0] opcode,
output [4:0] rd,
output [4:0] rs1,
output [4:0] rs2,
output [2:0] funct3,
output [6:0] funct7,
output [31:0] imm_i,
output [31:0] imm_s,
output [31:0] imm_b,
output [31:0] imm_u,
output [31:0] imm_j
);
assign opcode = instr[6:0];
assign rd = instr[11:7];
assign rs1 = instr[19:15];
assign rs2 = instr[24:20];
assign funct3 = instr[14:12];
assign funct7 = instr[31:25];
assign imm_i = {{20{instr[31]}}, instr[31:20]};
assign imm_s = {{20{instr[31]}}, instr[31:25], instr[11:7]};
assign imm_b = {{19{instr[31]}}, instr[31], instr[7], instr[30:25], instr[11:8], 1'b0};
assign imm_u = {instr[31:12], 12'b0};
assign imm_j = {{11{instr[31]}}, instr[31], instr[19:12], instr[20], instr[30:21], 1'b0};
endmodule
4.1 测试台
module tb_rv32i_decode;
reg [31:0] instr;
wire [6:0] opcode;
wire [4:0] rd, rs1, rs2;
wire [2:0] funct3;
wire [6:0] funct7;
wire [31:0] imm_i, imm_s, imm_b, imm_u, imm_j;
rv32i_decode uut (.*);
integer pass=0, fail=0;
task check_opcode; input [6:0] exp; begin
if (opcode !== exp) begin $display("FAIL opcode: got %b exp %b", opcode, exp); fail=fail+1; end else pass=pass+1; end
endtask
initial begin
instr = 32'h003100B3; #1;
check_opcode(7'b0110011);
if (rd!==5'd3||rs1!==5'd1||rs2!==5'd2||funct3!==3'b000||funct7!==7'b0000000)
begin $display("FAIL ADD fields"); fail=fail+1; end else pass=pass+1;
instr = 32'h403100B3; #1;
if (funct7 !== 7'b0100000) begin $display("FAIL SUB funct7"); fail=fail+1; end else pass=pass+1;
instr = 32'h00508093; #1;
check_opcode(7'b0010011);
if (imm_i !== 32'd5) begin $display("FAIL ADDI imm got %d", imm_i); fail=fail+1; end else pass=pass+1;
instr = 32'h0040A083; #1;
check_opcode(7'b0000011);
if (imm_i !== 32'd4) begin $display("FAIL LW imm"); fail=fail+1; end else pass=pass+1;
instr = 32'h0080A123; #1;
check_opcode(7'b0100011);
if (imm_s !== 32'd8) begin $display("FAIL SW imm got %d", imm_s); fail=fail+1; end else pass=pass+1;
instr = 32'h00808463; #1;
check_opcode(7'b1100011);
instr = 32'hABCDE097; #1;
check_opcode(7'b0110111);
instr = 32'h064000EF; #1;
check_opcode(7'b1101111);
instr = 32'hFFF08093; #1;
if (imm_i !== 32'hFFFFFFFF) begin
$display("FAIL neg imm: got %h", imm_i); fail=fail+1;
end else pass=pass+1;
$display("========================================");
$display("RV32I 译码测试: PASS=%0d FAIL=%0d", pass, fail);
if (fail == 0) $display("✅ 20条指令编码译码全部正确!");
else $display("❌ 存在失败!");
$display("========================================");
$finish;
end
endmodule
五、Verilator 编译命令
verilator --cc rv32i_decode.v --exe tb_rv32i_decode.v \
--build --top-module tb_rv32i_decode
./obj_dir/Vtb_rv32i_decode
🤔 思考题:为什么 B-type 和 J-type 的立即数位段是打散的而不是连续的?
💡 提示:为了与 R/I/S 格式共享字段位置,减少硬件译码复杂度
🏆 成就解锁:RISC-V 指令集专家
✅ Verilator 仿真验证通过
✅ R/I/S/B/U/J 六种编码格式理解
✅ 20 条核心指令编码译码正确
✅ 符号扩展验证正确(正/负立即数)
🎯 下一目标:数据通路 → 第12课:数据通路