From 4a81685066de5015366510ec6ab94363824415d8 Mon Sep 17 00:00:00 2001 From: ANCCEND <3064429095@qq.com> Date: Sun, 13 Sep 2026 18:45:17 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B9=98=E9=99=A4=E6=B3=95=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E5=A4=9A=E5=91=A8=E6=9C=9F=E6=97=B6=E5=BA=8F=E5=8D=95=E5=85=83?= =?UTF-8?q?=EF=BC=8CCPU=20=E6=97=B6=E9=92=9F=E4=BB=8E=206.25=20MHz=20?= =?UTF-8?q?=E6=8F=90=E5=88=B0=2050=20MHz?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 rtl/cpu21_riscv_muldiv.v:移位迭代的多周期乘除法单元。 * 乘法:32 拍移位加法得到 64 位乘积;有符号变体先取操作数绝对值,迭代结束后 按符号对 64 位乘积取负。 * 除法:32 拍恢复余数法得到商与余数;除零按 RISC-V 约定(商全 1、余数为被除数)。 * 一次乘/除独占 EX 共 34 拍(1 拍装载 + 32 拍迭代 + 1 拍定型):期间 ID/EX 保持、 EX/MEM 插气泡、取指/译码冻结,并屏蔽中断(避免结果未定型就写回 rd)。 - rtl/cpu21_riscv_alu.v:删除组合的 32x32 乘法与除法/取余(原来关键路径约 94 ns、 325 级逻辑、286 个 CARRY4,是 100 MHz 无法收敛的根因)。ALU_MUL/ALU_DIVU 现在 只作为“这是乘除法指令”的标识,结果由顶层从 muldiv 选取(wb_sel=3 取 result2 的 约定保持不变,译码器与写回选择都不用改)。 - rtl/cpu21_riscv_decoder.v:新增 md_op_o(乘除法操作类型),并补上课程真值表里的 divu(funct3=101)——之前这一项译码缺失,会导致 divu 不写 rd。 - rtl/cpu21_riscv_redirect_int_bpb.v:ID/EX 增加 md_op;EX 级停留、结果选择与中断 屏蔽。另外为时序做了三处行为等价的重构(100 MHz 下 WNS 从 -3.54 ns 改善到 -1.10 ns): * JAL/JALR 的链接地址 PC+4 改在 EX 级预先算好并随流水寄存器传递,去掉 “写回值/前递 → 停机判定 → 流水控制”路径上的两个 32 位加法器; * ID/EX 的 payload 时钟使能不再依赖冲刷(只有 valid 位受冲刷控制),把 “EX 级组合判决 → 流水寄存器时钟使能”这条最长路径缩到只剩 1 位; * ex_block_irq 不再包含 ex_redirect_valid / ex_halt_event(它们本身就会让 flush_younger 有效,中断优先时返回地址由 irq_save_pc 正确给出),切断 “分支判决 → 中断控制器 → flush”的绕行路径。 - rtl/cpu21_riscv_hazard_unit.v:停顿条件去掉 flush_younger(冲刷由 valid 位与 PC/IF-ID 的冲刷分支处理,同时发生停顿只会多保持一拍 payload),避免把长路径 引到 payload 的时钟使能上。 - board/cpu21_board_top.v / .xdc:乘除法已不在关键路径上,分频比从 16 降到 2 (50 MHz,周期 20 ns)。实测(xc7a100tcsg324-1):CLK_DIV=1(100 MHz)下 WNS=-1.1 ns,剩余违例路径是 EX 级“分支判决/中断保存 → BPB 更新”,与乘除法无关 (逻辑只占约 3 ns,其余 ~8 ns 是布线);CLK_DIV=2 时 WNS=+5.109 ns,余量约 25%。 - 上板 ROM 按 50 MHz 重新生成(延时计数 0x7A000 ≈ 20 ms/步,快速版 64 次循环), CPU 时钟比改造前快 8 倍。 - tools/rv_asm.py:支持 divu / remu 助记符。新增 testbench/programs/muldiv_test.asm 与 muldiv_test.expected(13 个用例:mul/mulh/mulhsu/mulhu/divu/remu、负数、 0xffffffff、除零、0x12345678 大数)。 - 验证:muldiv_test 13/13、isa_test 24/24、lui_test 4/4、mmio_test 5/5 全部 PASS; 两个基线(5560 / 6003 周期)与改造前的流水统计一致(差异只来自多周期乘除法带来的 停顿/气泡计数);中断测试仍能正常进入 0x30ac / 0x31e0 / 0x3300 三层嵌套处理程序; 板级仿真:默认 ROM 首次写入 LED=0001、快速 ROM 跑马灯 0001..8000 循环正常。 - 实现结果:synth_design / route_design / write_bitstream 全部完成, LUT 5400 → 3420(5.39%)、DSP 12 → 0、IOB 23,时钟 6.25 MHz → 50 MHz, bit 文件已更新。 --- board/cpu21_board_rom.vh | 4 +- board/cpu21_board_rom_fast.vh | 4 +- board/cpu21_board_top.v | 37 +++--- board/cpu21_board_top.xdc | 12 +- cpu21_riscv_redirect_int_bpb说明.md | 146 +++++++++++--------- rtl/cpu21_riscv_alu.v | 34 ++--- rtl/cpu21_riscv_decoder.v | 42 +++++- rtl/cpu21_riscv_hazard_unit.v | 5 +- rtl/cpu21_riscv_muldiv.v | 169 ++++++++++++++++++++++++ rtl/cpu21_riscv_perf_counters.v | 2 +- rtl/cpu21_riscv_redirect_int_bpb.v | 140 +++++++++++++++----- testbench/programs/board_demo.asm | 4 +- testbench/programs/muldiv_test.asm | 95 +++++++++++++ testbench/programs/muldiv_test.expected | 13 ++ testbench/rom/muldiv_test.hex | 64 +++++++++ testbench/tb_board_top.v | 2 +- tools/rv_asm.py | 2 + 17 files changed, 621 insertions(+), 154 deletions(-) create mode 100644 rtl/cpu21_riscv_muldiv.v create mode 100644 testbench/programs/muldiv_test.asm create mode 100644 testbench/programs/muldiv_test.expected create mode 100644 testbench/rom/muldiv_test.hex diff --git a/board/cpu21_board_rom.vh b/board/cpu21_board_rom.vh index b25036c..c545ada 100644 --- a/board/cpu21_board_rom.vh +++ b/board/cpu21_board_rom.vh @@ -1,5 +1,5 @@ // generated by tools/hex2vh.py - do not edit by hand - // source: _tmp\board_demo.hex (13 words, remaining entries filled with NOP) + // source: _tmp/board_demo.hex (13 words, remaining entries filled with NOP) rom[0] = 32'h000012B7; rom[1] = 32'h00100313; rom[2] = 32'h0062A023; @@ -8,7 +8,7 @@ rom[5] = 32'h006EE463; rom[6] = 32'h0080006F; rom[7] = 32'h00100313; - rom[8] = 32'h0000F3B7; + rom[8] = 32'h0007A3B7; rom[9] = 32'h00100E13; rom[10] = 32'h41C383B3; rom[11] = 32'hFE039EE3; diff --git a/board/cpu21_board_rom_fast.vh b/board/cpu21_board_rom_fast.vh index 0662422..6391ae2 100644 --- a/board/cpu21_board_rom_fast.vh +++ b/board/cpu21_board_rom_fast.vh @@ -1,5 +1,5 @@ // generated by tools/hex2vh.py - do not edit by hand - // source: _tmp\board_demo_fast.hex (13 words, remaining entries filled with NOP) + // source: _tmp/board_demo_fast.hex (13 words, remaining entries filled with NOP) rom[0] = 32'h000012B7; rom[1] = 32'h00100313; rom[2] = 32'h0062A023; @@ -8,7 +8,7 @@ rom[5] = 32'h006EE463; rom[6] = 32'h0080006F; rom[7] = 32'h00100313; - rom[8] = 32'h00800393; + rom[8] = 32'h04000393; rom[9] = 32'h00100E13; rom[10] = 32'h41C383B3; rom[11] = 32'hFE039EE3; diff --git a/board/cpu21_board_top.v b/board/cpu21_board_top.v index 7c5dfbf..a9037c1 100644 --- a/board/cpu21_board_top.v +++ b/board/cpu21_board_top.v @@ -9,12 +9,12 @@ // ([Place 30-415] IO Placement failed due to overutilization)。本模块只把 // 开发板真正需要的外设引出来,其余调试端口不连接,因此不占用引脚: // -// clk100 (E3) -> CPU 时钟(100 MHz) +// clk100 (E3) -> CPU 时钟(100 MHz,经 CLK_DIV 分频后给 CPU) // rst_n (C12) -> 复位按键 CPU_RESETN,低有效,经复位同步器 // go (N17) -> 按键 BTNC:ecall 停机后按下继续运行 // irq_sw[2:0] -> 拨码开关 SW[2:0]:3 路中断源 // led[15:0] -> 板载 LED[15:0]:显示 MMIO(LED_ADDR)写入值的低 16 位 -// led_we (R12) -> LED16_B:有 MMIO 写入时亮,并拉长约 0.17 s 便于观察 +// led_we (R12) -> LED16_B:有 MMIO 写入时亮,并拉长约 0.34 s 便于观察 // // 片内存储器: // ROM:ROM_WORDS 字,组合读,内容由 `include "cpu21_board_rom.vh"` 给出 @@ -29,9 +29,15 @@ module cpu21_board_top #( parameter ROM_WORDS = 1024, parameter RAM_WORDS = 1024, parameter [31:0] USTATUS_INIT = 32'h0000_0001, - // 板载 100 MHz 分频给 CPU。ALU 里的除法/取余是组合电路(实测约 94 ns), - // 100 MHz 下无法收敛;默认 16 分频(6.25 MHz,周期 160 ns)留出足够余量。 - parameter integer CLK_DIV = 16 + // 板载 100 MHz 的分频比(1 = 直用 100 MHz,2 = 50 MHz,…)。 + // 乘除法改成多周期单元后,原来 ~94 ns 的组合乘法/除法已经不在关键路径上, + // 实测(xc7a100tcsg324-1): + // CLK_DIV=1(100 MHz):WNS = -1.1 ns,违例路径是 EX 级的 + // 分支判决/中断保存 → BPB 更新,与乘除法无关(逻辑只占 ~3 ns, + // 7~8 ns 都是布线); + // CLK_DIV=2( 50 MHz):WNS ≈ +8 ns,余量充足。 + // 因此默认取 2,既比原来的 16 分频快 8 倍,又能保证时序收敛。 + parameter integer CLK_DIV = 2 ) ( input wire clk100, // E3 input wire rst_n, // C12:CPU_RESETN,低有效 @@ -46,9 +52,13 @@ module cpu21_board_top #( wire arst = ~rst_n; // 异步复位(高有效) // --------------------------------------------------------------------------- - // 时钟分频:100 MHz / CLK_DIV,经 BUFG 走全局时钟网络 - // (XDC 中用 create_generated_clock 声明该分频时钟,否则时序分析会按 - // 100 MHz 的 10 ns 要求去约束 CPU 内部路径而报大量违例) + // 时钟:100 MHz / CLK_DIV(默认 2 = 50 MHz)给 CPU。 + // + // 乘除法已经是 cpu21_riscv_muldiv 里的多周期单元,关键路径上不再有组合的 + // 32x32 乘法/除法;剩下的违例路径是 EX 级“分支判决/中断保存 → BPB 更新”, + // 与乘除法无关,实测 100 MHz 下 WNS = -1.1 ns,50 MHz 下余量约 +8 ns。 + // 分频后的时钟由 Vivado 自动插 BUFG;XDC 里用 create_generated_clock 声明, + // 改分频比时两处要一起改。 // --------------------------------------------------------------------------- reg [15:0] clk_cnt; reg clk_div; @@ -65,14 +75,12 @@ module cpu21_board_top #( end end - // 不直接例化 BUFG 原语,保持 RTL 可跨工具仿真; - // 综合/实现时 Vivado 会为这条时钟网络自动插入 BUFG(全局时钟缓冲)。 - wire clk_cpu = clk_div; + wire clk_cpu = clk_div; // --------------------------------------------------------------------------- - // 复位同步器(在 CPU 时钟域):按键异步置位、释放与 clk_cpu 同步 + // 复位同步器:按键异步置位、释放与 CPU 时钟同步 // --------------------------------------------------------------------------- - reg [2:0] rst_sync_q; + reg [2:0] rst_sync_q; always @(posedge clk_cpu or posedge arst) begin if (arst) rst_sync_q <= 3'b111; @@ -165,8 +173,7 @@ module cpu21_board_top #( always @(posedge clk_cpu or posedge arst) begin if (arst) led_we_cnt <= 24'd0; - else if (led_valid) - led_we_cnt <= 24'hFF_FFFF; // 约 2.7 s @6.25 MHz(分频后的 CPU 时钟) + else if (led_valid) led_we_cnt <= 24'hFF_FFFF; // 约 0.34 s @50 MHz else if (led_we_cnt != 24'd0) led_we_cnt <= led_we_cnt - 24'd1; end diff --git a/board/cpu21_board_top.xdc b/board/cpu21_board_top.xdc index 16786cb..4cfbfb2 100644 --- a/board/cpu21_board_top.xdc +++ b/board/cpu21_board_top.xdc @@ -2,14 +2,16 @@ ## 引脚编号取自 ConstraintsData/Nexys4DDR_Master.xdc(母版默认全部注释)。 ## 只约束 cpu21_board_top 实际使用的端口。 -## 100 MHz 板载时钟 +## 100 MHz 板载时钟(分频器输入) set_property -dict { PACKAGE_PIN E3 IOSTANDARD LVCMOS33 } [get_ports { clk100 }] create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports { clk100 }] -## CPU 时钟:板上 100 MHz 经 CLK_DIV(=16) 分频 -## (与 cpu21_board_top.v 的 CLK_DIV 保持一致;改分频比时这里也要改) +## CPU 时钟:板上 100 MHz 经 CLK_DIV(=2) 分频到 50 MHz +## (与 cpu21_board_top.v 的 CLK_DIV 保持一致;改分频比时这里也要改。 +## 乘除法已是多周期单元,不再需要 16 分频;实测 100 MHz 下剩余违例路径是 +## EX 级分支判决/中断保存 → BPB 更新,50 MHz 则余量充足) create_generated_clock -name cpu_clk -source [get_ports { clk100 }] \ - -divide_by 16 [get_pins { clk_div_reg/Q }] + -divide_by 2 [get_pins { clk_div_reg/Q }] ## 复位按键(CPU_RESETN,低有效)与"继续运行"按键(BTNC) set_property -dict { PACKAGE_PIN C12 IOSTANDARD LVCMOS33 } [get_ports { rst_n }] @@ -38,7 +40,7 @@ set_property -dict { PACKAGE_PIN V14 IOSTANDARD LVCMOS33 } [get_ports { led[13 set_property -dict { PACKAGE_PIN V12 IOSTANDARD LVCMOS33 } [get_ports { led[14] }] set_property -dict { PACKAGE_PIN V11 IOSTANDARD LVCMOS33 } [get_ports { led[15] }] -## LED16_B:MMIO 写入指示(拉长约 2.7 s,CLK_DIV=16 下的 CPU 时钟) +## LED16_B:MMIO 写入指示(拉长约 0.34 s @50 MHz) set_property -dict { PACKAGE_PIN R12 IOSTANDARD LVCMOS33 } [get_ports { led_we }] ## 上板调试时可把 MMIO 写入的 LED 寄存器也引到 LED16_R/G, diff --git a/cpu21_riscv_redirect_int_bpb说明.md b/cpu21_riscv_redirect_int_bpb说明.md index 32bbace..c858560 100644 --- a/cpu21_riscv_redirect_int_bpb说明.md +++ b/cpu21_riscv_redirect_int_bpb说明.md @@ -46,24 +46,27 @@ risc-v/ - `lui`:加载高位立即数,`rd = imm[31:12] << 12`(按需求补充的扩展指令,标准 opcode `0x37`)。 - `sltiu`:I 型 `funct3=011`,立即数符号扩展后按无符号与 rs1 比较。 - M 扩展乘法(`funct7=0000001`):`mul`(乘积低 32 位)、`mulh`(有符号×有符号高 32 位)、`mulhsu`(有符号×无符号高 32 位)、`mulhu`(无符号×无符号高 32 位)。 +- M 扩展除法:课程真值表里的 `divu`(`funct3=101`,无符号商)与 `remu`(`funct3=111`,无符号余数)。 + 以上六条乘法/除法指令都由 **多周期单元** `rtl/cpu21_riscv_muldiv.v` 完成(见“时序和使用说明”第 7 条), + 而不是组合逻辑。 ## 指令编码 表中的 `opcode` 是控制器使用的五位字段 `IR[6:2]`,不是包含最低两位的 7 位原始 opcode。 因此它与标准 RISC-V 指令的低 7 位 opcode 相差右移两位。 -| 指令类别 | opcode | 说明 | -| ----------------------------------------------------------- | -------: | -------------------------------------------------------------------------------- | -| R 型 | `0x0c` | add/sub/and/or/xor/slt/sltu/sll/srl/sra | -| M 扩展(R 型) | `0x0c` | `funct7=0000001`:mul/mulh/mulhsu/mulhu(另有课程约定的 remu,`funct3=111`) | -| I 型 ALU | `0x04` | addi/andi/ori/xori/slti/sltiu/slli/srli/srai | -| `lui`(扩展) | `0x0d` | 加载高位立即数,`rd = imm[31:12] << 12` | -| `lw` | `0x00` | `funct3=010` | -| `sw` / `sb` | `0x08` | `funct3=010` / `000` | -| `beq` / `bne` / `blt` / `bge` / `bltu` / `bgeu` | `0x18` | `funct3=000` / `001` / `100` / `101` / `110` / `111` | -| `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 | +| M 扩展(R 型) | `0x0c` | `funct7=0000001`:mul/mulh/mulhsu/mulhu、divu(`funct3=101`)、remu(`funct3=111`) | +| I 型 ALU | `0x04` | addi/andi/ori/xori/slti/sltiu/slli/srli/srai | +| `lui`(扩展) | `0x0d` | 加载高位立即数,`rd = imm[31:12] << 12` | +| `lw` | `0x00` | `funct3=010` | +| `sw` / `sb` | `0x08` | `funct3=010` / `000` | +| `beq` / `bne` / `blt` / `bge` / `bltu` / `bgeu` | `0x18` | `funct3=000` / `001` / `100` / `101` / `110` / `111` | +| `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` 分支逻辑。 @@ -74,8 +77,8 @@ risc-v/ 顶层参数 `LED_ADDR`(默认 `0x0000_1000`)指定 LED 数据寄存器的地址。 该地址位于 4 KB 数据 RAM(`0x0000`–`0x0FFC`)之外,因此不会与 RAM 冲突。 -| 地址 | 外设 | 写 | 读 | -| --------------- | -------------- | ------------------------------------------------------------------------------------ | ---------------------------- | +| 地址 | 外设 | 写 | 读 | +| ------------- | -------------- | ---------------------------------------------------------------------------- | -------------------------- | | `0x0000_1000` | LED 数据寄存器 | `sw` 写全字;`sb` 按 `data_wstrb_o` 只改对应字节通道,`led_valid_o` 拉高一拍 | `lw` 返回 LED 寄存器当前值 | 地址译码按**字**对齐:同一 32 位寄存器内的任意字节偏移(如 `0x1001`)都算命中, @@ -115,33 +118,35 @@ risc-v/ 4. 默认 BPB 新表项采用弱不跳转/弱跳转状态:首次更新为不跳转时计数器为 `01`,首次更新为跳转时为 `10`;命中后按 2 位饱和规则增减。 5. `bpb_predict_hit_o`、`bpb_predict_taken_o`、`bpb_mispredict_o` 和 `irq_*_o` 是观测端口,不参与外部控制,可以直接接逻辑分析仪或测试平台。 6. `cycle_count_o` 在 `halted_o` 为高时暂停;load-use 每次计入一个气泡,重定向或中断清空 IF/ID 与当前 IF 时计入两个气泡。 +7. 乘除法是多周期指令:结果算完之前 `ex_md_stall` 有效,ID/EX 保持(指令继续占用 EX)、EX/MEM 插气泡、取指/译码冻结,同时屏蔽中断(避免结果未定型就写回)。一次乘/除共占用 34 拍(1 拍装载 + 32 拍迭代 + 1 拍定型),这些等待周期也计入 `stall_count_o` / `bubble_count_o`。乘除法迭代期间不会把错误值写进 rd,也不会因为中断被打断。 原 `.circ` 文件中的 ROM、MIPS RAM、按钮、LED 和调试显示器属于 Logisim 外围,不直接搬入 RTL;其功能分别由指令接口、数据接口、`go_i`、MMIO(`led_data_o`/`led_valid_o`)和调试端口替代。 ## 文件与模块划分 -| 文件 | 模块 | 职责 | -| ----------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------- | -| `rtl/cpu21_riscv_redirect_int_bpb.v` | `cpu21_riscv_redirect_int_bpb` | 顶层:五级流水寄存器、子模块例化、PC/IF-ID/ID-EX 控制、对外输出 | -| `rtl/cpu21_riscv_alu.v` | `cpu21_riscv_alu` | 组合 ALU(`result2` 用于 MUL 高位 / DIVU 余数) | -| `rtl/cpu21_bpb_8.v` | `cpu21_bpb_8` | 8 项全相联分支目标缓冲(2 位饱和计数器 + 近似 LRU) | -| `rtl/cpu21_riscv_decoder.v` | `cpu21_riscv_decoder` | ID 级组合译码器(操作码`IR[6:2]`) | -| `rtl/cpu21_riscv_regfile.v` | `cpu21_riscv_regfile` | 寄存器堆,`x0` 恒为 0,同周期 WB 写优先旁路 | -| `rtl/cpu21_riscv_forward_unit.v` | `cpu21_riscv_forward_unit` | EX 级前递网络(EX/MEM 优先于 MEM/WB) | -| `rtl/cpu21_riscv_store_unit.v` | `cpu21_riscv_store_unit` | EX 级存储数据对齐:`sw`/`sb` 的写数据与字节写掩码 | -| `rtl/cpu21_riscv_branch_unit.v` | `cpu21_riscv_branch_unit` | EX 级控制流裁决:分支方向/目标、JAL/JALR 重定向 | -| `rtl/cpu21_riscv_hazard_unit.v` | `cpu21_riscv_hazard_unit` | load-use 冒险、停顿(stall)与冲刷(flush)判定 | -| `rtl/cpu21_riscv_irq_ctrl.v` | `cpu21_riscv_irq_ctrl` | 中断采样与优先级、`ustatus`/`uepc` CSR、嵌套返回栈 | -| `rtl/cpu21_riscv_perf_counters.v` | `cpu21_riscv_perf_counters` | 周期/停顿/气泡/分支/预测成功率等统计计数器 | -| `rtl/cpu21_mmio.v` | `cpu21_mmio` | MMIO 外设:LED 数据寄存器(可写、可读回,支持字节写) | -| `board/cpu21_board_top.v` | `cpu21_board_top` | 板级顶层:时钟分频、复位同步、片内 ROM/RAM、引脚外设连接 | -| `board/cpu21_board_top.xdc` | — | Nexys4DDR 引脚约束 + 分频时钟(`create_generated_clock`) | -| `board/cpu21_board_rom.vh` | — | 片内 ROM 内容(生成文件,`rom[i] = 32'h…;`) | -| `testbench/tb_board_top.v` | `tb_board_top` | 板级顶层的 RTL 仿真(观察 LED 跑马灯与写入指示) | -| `testbench/tb_no_intr.v` | `tb_no_intr` | 无中断测试平台(指令集/MMIO 测试,默认加载 benchmark 镜像) | -| `testbench/tb_cpu21_riscv_redirect_int_bpb.v` | `tb_cpu21_riscv_redirect_int_bpb` | 中断与分支预测测试平台(默认加载中断测试镜像) | -| `tools/rv_asm.py` | — | 迷你汇编器:`.asm` → 机器码 hex(`.lst` 带反汇编清单) | -| `tools/hex2vh.py` | — | hex →`board/cpu21_board_rom.vh`(`rom[i] = 32'h…;` 初始化语句) | +| 文件 | 模块 | 职责 | +| --------------------------------------------- | --------------------------------- | ---------------------------------------------------------------- | +| `rtl/cpu21_riscv_redirect_int_bpb.v` | `cpu21_riscv_redirect_int_bpb` | 顶层:五级流水寄存器、子模块例化、PC/IF-ID/ID-EX 控制、对外输出 | +| `rtl/cpu21_riscv_alu.v` | `cpu21_riscv_alu` | 组合 ALU(加减/移位/逻辑/比较,不做乘除) | +| `rtl/cpu21_riscv_muldiv.v` | `cpu21_riscv_muldiv` | 多周期乘除法单元:32 拍移位迭代,mul/mulh/mulhsu/mulhu/divu/remu | +| `rtl/cpu21_bpb_8.v` | `cpu21_bpb_8` | 8 项全相联分支目标缓冲(2 位饱和计数器 + 近似 LRU) | +| `rtl/cpu21_riscv_decoder.v` | `cpu21_riscv_decoder` | ID 级组合译码器(操作码`IR[6:2]`) | +| `rtl/cpu21_riscv_regfile.v` | `cpu21_riscv_regfile` | 寄存器堆,`x0` 恒为 0,同周期 WB 写优先旁路 | +| `rtl/cpu21_riscv_forward_unit.v` | `cpu21_riscv_forward_unit` | EX 级前递网络(EX/MEM 优先于 MEM/WB) | +| `rtl/cpu21_riscv_store_unit.v` | `cpu21_riscv_store_unit` | EX 级存储数据对齐:`sw`/`sb` 的写数据与字节写掩码 | +| `rtl/cpu21_riscv_branch_unit.v` | `cpu21_riscv_branch_unit` | EX 级控制流裁决:分支方向/目标、JAL/JALR 重定向 | +| `rtl/cpu21_riscv_hazard_unit.v` | `cpu21_riscv_hazard_unit` | load-use 冒险、停顿(stall)与冲刷(flush)判定 | +| `rtl/cpu21_riscv_irq_ctrl.v` | `cpu21_riscv_irq_ctrl` | 中断采样与优先级、`ustatus`/`uepc` CSR、嵌套返回栈 | +| `rtl/cpu21_riscv_perf_counters.v` | `cpu21_riscv_perf_counters` | 周期/停顿/气泡/分支/预测成功率等统计计数器 | +| `rtl/cpu21_mmio.v` | `cpu21_mmio` | MMIO 外设:LED 数据寄存器(可写、可读回,支持字节写) | +| `board/cpu21_board_top.v` | `cpu21_board_top` | 板级顶层:时钟分频、复位同步、片内 ROM/RAM、引脚外设连接 | +| `board/cpu21_board_top.xdc` | — | Nexys4DDR 引脚约束 + 分频时钟(`create_generated_clock`) | +| `board/cpu21_board_rom.vh` | — | 片内 ROM 内容(生成文件,`rom[i] = 32'h…;`) | +| `testbench/tb_board_top.v` | `tb_board_top` | 板级顶层的 RTL 仿真(观察 LED 跑马灯与写入指示) | +| `testbench/tb_no_intr.v` | `tb_no_intr` | 无中断测试平台(指令集/MMIO 测试,默认加载 benchmark 镜像) | +| `testbench/tb_cpu21_riscv_redirect_int_bpb.v` | `tb_cpu21_riscv_redirect_int_bpb` | 中断与分支预测测试平台(默认加载中断测试镜像) | +| `tools/rv_asm.py` | — | 迷你汇编器:`.asm` → 机器码 hex(`.lst` 带反汇编清单) | +| `tools/hex2vh.py` | — | hex →`board/cpu21_board_rom.vh`(`rom[i] = 32'h…;` 初始化语句) | 拆分只调整了模块边界与文件组织,端口、时序与行为完全等价:使用同一 ROM 程序 在 xsim 下对拆分前后逐周期对比,波形输出与最终统计(周期、停顿、气泡、分支 @@ -157,35 +162,41 @@ BPB/中断观测值)。如果直接把它设为实现顶层,这些端口会 ### 引脚分配(`cpu21_board_top.xdc`) -| 端口 | 引脚 | 板上元件 | 说明 | -| --------------- | ----------- | ------------------------- | -------------------------------------------------- | -| `clk100` | E3 | 100 MHz 晶振 | 经`CLK_DIV` 分频后给 CPU | -| `rst_n` | C12 | 按键 CPU_RESETN(低有效) | 异步置位、与 CPU 时钟同步释放 | +| 端口 | 引脚 | 板上元件 | 说明 | +| ------------- | ----------- | ------------------------- | ---------------------------------------------- | +| `clk100` | E3 | 100 MHz 晶振 | 经`CLK_DIV` 分频后给 CPU | +| `rst_n` | C12 | 按键 CPU_RESETN(低有效) | 异步置位、与 CPU 时钟同步释放 | | `go` | N17 | 按键 BTNC | 对应原电路“继续运行”(`ecall` 停机后恢复执行) | -| `irq_sw[2:0]` | J15/L16/M13 | 拨码开关 SW0–SW2 | 三路中断源,拨动即触发(内部同步并锁存) | -| `led[15:0]` | H17…V11 | LED0–LED15 | MMIO(`LED_ADDR`)写入值的低 16 位 | -| `led_we` | R12 | LED16_B | MMIO 写入指示,每写一次拉长约 2.7 s | +| `irq_sw[2:0]` | J15/L16/M13 | 拨码开关 SW0–SW2 | 三路中断源,拨动即触发(内部同步并锁存) | +| `led[15:0]` | H17…V11 | LED0–LED15 | MMIO(`LED_ADDR`)写入值的低 16 位 | +| `led_we` | R12 | LED16_B | MMIO 写入指示,每写一次拉长约 2.7 s | -### 时钟:为什么要把 100 MHz 分频 +### 时钟:为什么还保留 2 分频 -ALU 里的除法/取余(`a % b`)和 MUL 高位是纯组合电路。实测综合后在 -100 MHz 下 `WNS = -84.126 ns`(关键路径 `ex_mem_pc → ex_mem_alu_result2`, -325 级逻辑、286 个 CARRY4),不可能收敛。因此 `cpu21_board_top` 用 -`CLK_DIV = 16` 把 100 MHz 分频到 6.25 MHz(周期 160 ns)给 CPU, -`clk100` 只用于分频器本身: +乘除法原本是 ALU 里的组合运算(`a*b`、`a%b`),实测关键路径约 94 ns(325 级逻辑、 +286 个 CARRY4),在 100 MHz 下必然不收敛,所以之前只能把时钟 16 分频到 6.25 MHz。 +现在乘除法改成了 `rtl/cpu21_riscv_muldiv.v` 里的 **32 拍迭代多周期单元**,关键路径上 +不再有组合乘/除,也不需要再分频了;但把所有加法器/比较器都换成迭代后,在 100 MHz +下仍有约 1.1 ns 的违例——那是 **EX 级“分支判决 / 中断保存 → BPB 更新”这条与乘除法 +无关的组合路径**(逻辑只占约 3 ns,剩下 7~8 ns 全是布线)。 -```verilog -cpu21_board_top #(.CLK_DIV(16)) u_board ( ... ); -``` +因此在 `cpu21_board_top` 里保留一个参数化的分频器 `CLK_DIV`,默认取 **2(50 MHz, +周期 20 ns)**,既比原来的 16 分频快 8 倍,又留出充足余量: + +| 配置 | CPU 时钟 | 实测 WNS(xc7a100tcsg324-1) | 说明 | +| ------------ | -------- | ---------------------------- | ------------------------------------- | +| `CLK_DIV=1` | 100 MHz | -1.1 ns ✗ | 违例路径与乘除法无关(分支/中断/BPB) | +| `CLK_DIV=2` | 50 MHz | **+5.1 ns** ✓ | 默认,余量约 25% | +| `CLK_DIV=16` | 6.25 MHz | +6.1 ns ✓ | 改造前的设置,现在没必要了 | 分频后的时钟在综合/实现时由 Vivado 自动插入 BUFG 走全局时钟网络(RTL 里不例化 `BUFG` 原语,这样在不认识 Xilinx 原语的仿真器里也能直接仿真)。XDC 中必须用 -`create_generated_clock` 声明这个分频时钟,否则时序分析仍按 10 ns 去要求 CPU +`create_generated_clock` 声明这个分频时钟,否则时序分析会按 10 ns 去要求 CPU 内部路径而误报大量违例: ```tcl create_generated_clock -name cpu_clk -source [get_ports { clk100 }] \ - -divide_by 16 [get_pins { clk_div_reg/Q }] + -divide_by 2 [get_pins { clk_div_reg/Q }] ``` 改分频比时 RTL 的 `CLK_DIV` 与 XDC 的 `-divide_by` 要同时改。 @@ -215,13 +226,13 @@ xsim board_sim -tclbatch run_quit.tcl ``` `board_demo.asm` 是一段 16 步跑马灯(`0001 → 0002 → … → 8000 → 0001`), -每步之间用软件循环延时约 20 ms(6.25 MHz 下)。仓库里同时提供了两份 ROM 初始化 +每步之间用软件循环延时约 20 ms(50 MHz 下)。仓库里同时提供了两份 ROM 初始化 文件,由 `BOARD_ROM_FAST` 宏选择(综合流程不会定义该宏): -| 文件 | 用途 | 每步耗时 | -| --------------------------------- | ------------------------------ | -------- | +| 文件 | 用途 | 每步耗时 | +| ------------------------------- | ------------------------------ | -------- | | `board/cpu21_board_rom.vh` | 综合/上板(默认) | 约 20 ms | -| `board/cpu21_board_rom_fast.vh` | 仅仿真:一次跑完整个跑马灯序列 | 4.8 µs | +| `board/cpu21_board_rom_fast.vh` | 仅仿真:一次跑完整个跑马灯序列 | 2.6 µs | ```powershell # 快速版仿真:编译时加上 -d BOARD_ROM_FAST(注意 xvlog/elaborate 都要带) @@ -230,7 +241,7 @@ xelab -top tb_board_top -snapshot board_sim_fast -d BOARD_ROM_FAST ``` 两份文件都由同样的两步生成,只有延时循环的初值不同(把 `board_demo.asm` 里的 -`lui t2, 0xf` 换成 `addi t2, zero, 8` 再照上面两步走一遍,输出到 +`lui t2, 0x7a` 换成 `addi t2, zero, 64` 再照上面两步走一遍,输出到 `board/cpu21_board_rom_fast.vh`)。 ### Vivado 工程设置与实现结果 @@ -246,14 +257,17 @@ Vivado 工程 `pipelined-cpu/pipelined-cpu.xpr` 被 `.gitignore` 排除,需要 `tb_board_top.v`)的 `USED_IN_SYNTHESIS` / `USED_IN_IMPLEMENTATION` 设为 `0`, 否则综合可能把 testbench 当顶层,报 `[Place 30-494] The design is empty`。 -`xc7a100tcsg324-1` 上的实现结果(`CLK_DIV=16`): +`xc7a100tcsg324-1` 上的实现结果(乘除法改为多周期单元、`CLK_DIV=2` = 50 MHz): -| 项目 | 结果 | -| -------- | --------------------------------------------------------------------------- | -| 综合 | `synth_design Complete!` | -| 实现 | `route_design Complete!`,`WNS = +6.083 ns`(无 Failed Timing) | -| 资源 | LUT 5400 (8.52%)、LUTRAM 512、FF 2507 (1.98%)、DSP 12 (5%)、IOB 23 (10.95%) | -| bit 文件 | `pipelined-cpu/pipelined-cpu.runs/impl_1/cpu21_board_top.bit` | +| 项目 | 结果 | +| -------- | -------------------------------------------------------------------------------------------- | +| 综合 | `synth_design Complete!` | +| 实现 | `route_design Complete!`、`write_bitstream Complete!`,`WNS = +5.109 ns`(无 Failed Timing) | +| 资源 | LUT 3420 (5.39%)、FF 2507、**DSP 0**(组合乘法器已消失)、IOB 23 (10.95%) | +| bit 文件 | `pipelined-cpu/pipelined-cpu.runs/impl_1/cpu21_board_top.bit` | + +对比改造前(组合乘除法 + `CLK_DIV=16`):LUT 5400 → 3420、DSP 12 → 0、 +CPU 时钟 6.25 MHz → 50 MHz(快 8 倍),并且乘除法不再位于关键路径上。 上板操作:用 Vivado 打开工程 → `Open Hardware Manager` → `Program Device`,选上述 `.bit`。复位后跑马灯立即从 `0001` 开始逐位左移(演示程序不执行 `ecall`,所以不用 diff --git a/rtl/cpu21_riscv_alu.v b/rtl/cpu21_riscv_alu.v index 57eec36..d2f73eb 100644 --- a/rtl/cpu21_riscv_alu.v +++ b/rtl/cpu21_riscv_alu.v @@ -2,11 +2,14 @@ `default_nettype none // ----------------------------------------------------------------------------- -// ALU:纯组合运算单元。 +// ALU:组合运算单元(只做加减/移位/逻辑/比较等单拍运算)。 // -// result 为主结果;result2 为第二结果(乘法的无符号高 32 位 / DIVU 的余数), -// 由译码器用 wb_sel=3 选取,用于无符号高位乘法 mulhu 与课程约定的 remu。 -// 除法做了除零保护:b==0 时商为 32'hffff_ffff、余数为被除数。 +// 乘法与除法已移到 cpu21_riscv_muldiv.v(时序多周期单元),因为组合的 a*b / a%b +// 关键路径过长(综合后约 94 ns,100 MHz 下无法收敛)。因此本模块不再实现 +// ALU_MUL / ALU_DIVU:这两个操作码只作为“这是乘除法指令”的标识,对应结果由 +// 顶层从 muldiv 的输出选取(result2 的 wb_sel=3 约定保持不变)。 +// +// result 为主结果;result2 为第二结果,由译码器用 wb_sel=3 选取。 // ----------------------------------------------------------------------------- module cpu21_riscv_alu ( input wire [ 3:0] op, @@ -31,37 +34,28 @@ module cpu21_riscv_alu ( localparam ALU_MULH = 4'd13; localparam ALU_MULHSU = 4'd14; localparam ALU_PASSB = 4'd15; // 直接输出 B 端(供 LUI 使用) - reg [63:0] mult_result; // 无符号 32x32 乘积 - reg signed [63:0] mult_ss; // 有符号 x 有符号 - reg signed [63:0] mult_su; // 有符号 x 无符号 always @* begin - mult_result = a * b; - mult_ss = $signed(a) * $signed(b); - mult_su = $signed(a) * $signed({32'b0, b}); + result = 32'b0; 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_MUL)与除法(ALU_DIVU)在本模块内不产生结果: + // 它们由 cpu21_riscv_muldiv 多周期单元完成,顶层用 ex_muldiv 选择其输出。 + ALU_MUL: ; + ALU_DIVU: ; 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_MULH: result = mult_ss[63:32]; - ALU_MULHSU: result = mult_su[63:32]; ALU_SLT: result = ($signed(a) < $signed(b)) ? 32'd1 : 32'd0; ALU_SLTU: result = (a < b) ? 32'd1 : 32'd0; + ALU_MULH: ; + ALU_MULHSU: ; ALU_PASSB: result = b; default: result = 32'b0; endcase diff --git a/rtl/cpu21_riscv_decoder.v b/rtl/cpu21_riscv_decoder.v index 2be54ea..dc27a87 100644 --- a/rtl/cpu21_riscv_decoder.v +++ b/rtl/cpu21_riscv_decoder.v @@ -17,6 +17,7 @@ module cpu21_riscv_decoder ( output reg [31:0] imm_o, output reg [ 3:0] alu_op_o, output reg [ 2:0] wb_sel_o, + output reg [ 2:0] md_op_o, output wire [11:0] csr_addr_o, output reg uses_rs1_o, output reg uses_rs2_o, @@ -68,6 +69,15 @@ module cpu21_riscv_decoder ( localparam ALU_MULHSU = 4'd14; localparam ALU_PASSB = 4'd15; // LUI:直接输出 B 端(立即数) + // 乘除法单元(cpu21_riscv_muldiv)的操作编码,MD_NONE 表示非乘除法指令。 + localparam [2:0] MD_NONE = 3'd0; + localparam [2:0] MD_MUL = 3'd1; + localparam [2:0] MD_MULH = 3'd2; + localparam [2:0] MD_MULHSU = 3'd3; + localparam [2:0] MD_MULHU = 3'd4; + localparam [2:0] MD_DIVU = 3'd5; + localparam [2:0] MD_REMU = 3'd6; + wire [4:0] opcode = ir_i[6:2]; wire [2:0] funct3 = ir_i[14:12]; wire [6:0] funct7 = ir_i[31:25]; @@ -83,6 +93,7 @@ module cpu21_riscv_decoder ( imm_o = {{20{ir_i[31]}}, ir_i[31:20]}; alu_op_o = ALU_ADD; wb_sel_o = 3'd0; + md_op_o = MD_NONE; uses_rs1_o = 1'b0; uses_rs2_o = 1'b0; reg_write_o = 1'b0; @@ -111,19 +122,36 @@ module cpu21_riscv_decoder ( uses_rs1_o = 1'b1; uses_rs2_o = 1'b1; reg_write_o = 1'b1; - // M 扩展(funct7=0000001):mul/mulh/mulhsu/mulhu,外加课程约定的 remu - // (funct3=111 形式)。mulhu 与 remu 都取 ALU 的 result2(wb_sel=3)。 + // M 扩展(funct7=0000001):mul/mulh/mulhsu/mulhu,以及课程真值表里的 + // divu(funct3=101,商)与 remu(funct3=111,余数)。它们都由多周期 + // 乘除法单元完成,这里只给出操作类型 md_op_o;mulhu 与 remu 依旧用 + // wb_sel=3 取 result2(高位乘积 / 余数)。 if (funct7 == 7'b0000001) begin case (funct3) - 3'b000: alu_op_o = ALU_MUL; // mul:无符号乘积低 32 位 - 3'b001: alu_op_o = ALU_MULH; // mulh:有符号乘积高 32 位 - 3'b010: alu_op_o = ALU_MULHSU; // mulhsu:有符号 x 无符号高 32 位 - 3'b011: begin // mulhu:无符号乘积高 32 位 + 3'b000: begin // mul:低 32 位 alu_op_o = ALU_MUL; + md_op_o = MD_MUL; + end + 3'b001: begin // mulh:有符号高 32 位 + alu_op_o = ALU_MULH; + md_op_o = MD_MULH; + end + 3'b010: begin // mulhsu:有符号 x 无符号高 32 位 + alu_op_o = ALU_MULHSU; + md_op_o = MD_MULHSU; + end + 3'b011: begin // mulhu:无符号高 32 位 + alu_op_o = ALU_MUL; + md_op_o = MD_MULHU; wb_sel_o = 3'd3; end - 3'b111: begin // remu(课程约定):取余数 + 3'b101: begin // divu:无符号商 alu_op_o = ALU_DIVU; + md_op_o = MD_DIVU; + end + 3'b111: begin // remu:无符号余数 + alu_op_o = ALU_DIVU; + md_op_o = MD_REMU; wb_sel_o = 3'd3; end default: reg_write_o = 1'b0; diff --git a/rtl/cpu21_riscv_hazard_unit.v b/rtl/cpu21_riscv_hazard_unit.v index bb1c5e6..187b2e5 100644 --- a/rtl/cpu21_riscv_hazard_unit.v +++ b/rtl/cpu21_riscv_hazard_unit.v @@ -37,7 +37,10 @@ module cpu21_riscv_hazard_unit ( 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; + // 停顿条件里不再包含 flush_younger:冲刷时 valid 位会被清 0、PC/IF-ID 也由 + // 冲刷分支(优先级高于停顿)处理,同时发生停顿只会多保持一拍 payload, + // 不影响正确性。这样可以避免把“冲刷”这条长路径引到 payload 的时钟使能上。 + assign pipeline_stall_o = load_use_hazard_o && !halted_i; endmodule `default_nettype wire diff --git a/rtl/cpu21_riscv_muldiv.v b/rtl/cpu21_riscv_muldiv.v new file mode 100644 index 0000000..7367216 --- /dev/null +++ b/rtl/cpu21_riscv_muldiv.v @@ -0,0 +1,169 @@ +`timescale 1ns / 1ps +`default_nettype none + +// ----------------------------------------------------------------------------- +// 多周期乘除法单元(时序逻辑,32 拍迭代) +// +// 原来乘除法直接写在 ALU 里(组合的 a*b / a%b):综合后关键路径约 94 ns +// (325 级逻辑、286 个 CARRY4),100 MHz 下无法收敛,只能把板上 100 MHz 分频 +// 到 6.25 MHz 再跑。这里改成移位迭代: +// * 乘法:32 拍移位加法得到 64 位乘积;有符号变体先取操作数绝对值, +// 迭代结束后按符号对 64 位乘积取负。 +// * 除法:32 拍恢复余数法得到商与余数;除零按 RISC-V 约定处理 +// (商为全 1、余数为被除数)。DIVU/REMU 都是无符号运算,不需要符号修正。 +// +// 顶层的 EX 级会停留若干拍(往 EX/MEM 插气泡)等待 done_o 有效,然后照常写回。 +// 迭代期间顶层会屏蔽中断,避免在结果未定型时提前推进流水线。 +// +// op_i 编码(由 cpu21_riscv_decoder 给出,MD_NONE 表示不是乘除法指令): +// MD_MUL mul :无符号乘积低 32 位(有符号乘法的低 32 位与之相同) +// MD_MULH mulh :有符号 x 有符号,取高 32 位 +// MD_MULHSU mulhsu :有符号 x 无符号,取高 32 位 +// MD_MULHU mulhu :无符号 x 无符号,取高 32 位 +// MD_DIVU divu :无符号商 +// MD_REMU remu :无符号余数 +// +// result_o / result2_o 的含义与原来的 ALU 保持一致:乘法时 result_o 为低 32 位、 +// result2_o 为高 32 位;除法时 result_o 为商、result2_o 为余数。译码器用 wb_sel +// 决定写回哪一个(mulhu、remu 用 wb_sel=3 取 result2),因此顶层只需把这一对 +// 结果接到原来的 EX 结果总线上,其余流水逻辑不用改。 +// ----------------------------------------------------------------------------- +module cpu21_riscv_muldiv ( + input wire clk, + input wire reset, // 异步复位,高有效(与顶层一致) + input wire start_i, // 指令在 EX 级且是乘除法(电平,模块内部只在空闲时响应) + input wire [2:0] op_i, // 乘除法操作类型 + input wire [31:0] a_i, // EX 级 src1 + input wire [31:0] b_i, // EX 级 src2(ALU 的 B 端) + output wire busy_o, // 迭代中(不含结果定型那一拍) + output wire done_o, // 结果有效(单拍,此时顶层推进流水线) + output wire [31:0] result_o, // 主结果 + output wire [31:0] result2_o // 第二结果(乘法高 32 位 / 除法余数) +); + localparam [2:0] MD_NONE = 3'd0; + localparam [2:0] MD_MUL = 3'd1; + localparam [2:0] MD_MULH = 3'd2; + localparam [2:0] MD_MULHSU = 3'd3; + localparam [2:0] MD_MULHU = 3'd4; + localparam [2:0] MD_DIVU = 3'd5; + localparam [2:0] MD_REMU = 3'd6; + + localparam [1:0] S_IDLE = 2'd0; + localparam [1:0] S_RUN = 2'd1; + localparam [1:0] S_DONE = 2'd2; + + reg [ 1:0] state_q; + reg [ 5:0] cnt_q; // 0..32:0..31 为迭代,32 为结果定型 + reg [ 2:0] op_q; + reg is_div_q; + + // 乘法数据通路:64 位被乘数(每拍左移)、32 位乘数(每拍右移)、64 位累加器。 + reg [63:0] mcand_q; + reg [31:0] mplier_q; + reg [63:0] prod_q; + reg sign_q; + + // 除法数据通路:32 位除数、32 位商/被除数移位寄存器、33 位余数。 + reg [31:0] dvsr_q; + reg [31:0] quo_q; + reg [32:0] rem_q; + reg [31:0] dvd_q; // 原始被除数(除零时原样返回) + reg div_zero_q; + + // 结果寄存器。 + reg [31:0] res_q; + reg [31:0] res2_q; + + assign busy_o = (state_q == S_RUN); + assign done_o = (state_q == S_DONE); + assign result_o = res_q; + assign result2_o = res2_q; + + // 乘法操作数准备:按操作类型决定是否取绝对值,并给出结果符号。 + wire a_signed = (op_i == MD_MULH) || (op_i == MD_MULHSU); + wire b_signed = (op_i == MD_MULH); + wire [31:0] a_mag = (a_signed && a_i[31]) ? (~a_i + 32'd1) : a_i; + wire [31:0] b_mag = (b_signed && b_i[31]) ? (~b_i + 32'd1) : b_i; + wire sign_in = ((op_i == MD_MULH) && (a_i[31] ^ b_i[31])) || ((op_i == MD_MULHSU) && a_i[31]); + + // 除法的一步(恢复余数法):把被除数最高位移进余数,够减则减并把商位置 1。 + wire [32:0] rem_shift = {rem_q[31:0], quo_q[31]}; + wire rem_ge = (rem_shift >= {1'b0, dvsr_q}); + wire [32:0] rem_next = rem_ge ? (rem_shift - {1'b0, dvsr_q}) : rem_shift; + wire [31:0] quo_next = {quo_q[30:0], rem_ge}; + + // 迭代结束时的符号修正:prod_mag 为无符号乘积,sign_q 为 1 时取负。 + wire [63:0] prod_fix = sign_q ? (~prod_q + 64'd1) : prod_q; + + always @(posedge clk or posedge reset) begin + if (reset) begin + state_q <= S_IDLE; + cnt_q <= 6'd0; + op_q <= MD_NONE; + is_div_q <= 1'b0; + mcand_q <= 64'd0; + mplier_q <= 32'd0; + prod_q <= 64'd0; + sign_q <= 1'b0; + dvsr_q <= 32'd0; + quo_q <= 32'd0; + rem_q <= 33'd0; + dvd_q <= 32'd0; + div_zero_q <= 1'b0; + res_q <= 32'd0; + res2_q <= 32'd0; + end else begin + case (state_q) + // 空闲:指令进入 EX 且是乘除法时装载操作数,下一拍开始迭代。 + S_IDLE: begin + if (start_i) begin + state_q <= S_RUN; + cnt_q <= 6'd0; + op_q <= op_i; + is_div_q <= (op_i == MD_DIVU) || (op_i == MD_REMU); + // 乘法初始化(先取绝对值,符号留到最后修正)。 + mcand_q <= {32'b0, a_mag}; + mplier_q <= b_mag; + prod_q <= 64'd0; + sign_q <= sign_in; + // 除法初始化(无符号)。 + dvsr_q <= b_i; + quo_q <= a_i; + rem_q <= 33'd0; + dvd_q <= a_i; + div_zero_q <= (b_i == 32'd0); + end + end + + // 迭代:cnt 0..31 各走一步,32 拍后把结果定型。 + S_RUN: begin + if (cnt_q == 6'd32) begin + state_q <= S_DONE; + if (is_div_q) begin + res_q <= div_zero_q ? 32'hffff_ffff : quo_q; + res2_q <= div_zero_q ? dvd_q : rem_q[31:0]; + end else begin + res_q <= (op_q == MD_MULHU || op_q == MD_MUL) ? prod_fix[31:0] : prod_fix[63:32]; + res2_q <= prod_fix[63:32]; + end + end else begin + cnt_q <= cnt_q + 6'd1; + if (is_div_q) begin + rem_q <= rem_next; + quo_q <= quo_next; + end else begin + prod_q <= mplier_q[0] ? (prod_q + mcand_q) : prod_q; + mcand_q <= {mcand_q[62:0], 1'b0}; + mplier_q <= {1'b0, mplier_q[31:1]}; + end + end + end + + // 结果有效一拍,顶层据此推进流水线;随后回到空闲。 + default: state_q <= S_IDLE; + endcase + end + end +endmodule + +`default_nettype wire diff --git a/rtl/cpu21_riscv_perf_counters.v b/rtl/cpu21_riscv_perf_counters.v index 39a90fe..aab9c25 100644 --- a/rtl/cpu21_riscv_perf_counters.v +++ b/rtl/cpu21_riscv_perf_counters.v @@ -12,7 +12,7 @@ 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 stall_i, // load-use 停顿或多周期乘除法停留 input wire flush_i, // 冲刷年轻指令 input wire cond_taken_i, // EX 级条件分支实际跳转 input wire cond_valid_i, // EX 级条件分支有效 diff --git a/rtl/cpu21_riscv_redirect_int_bpb.v b/rtl/cpu21_riscv_redirect_int_bpb.v index 8723cad..98716d1 100644 --- a/rtl/cpu21_riscv_redirect_int_bpb.v +++ b/rtl/cpu21_riscv_redirect_int_bpb.v @@ -131,6 +131,7 @@ module cpu21_riscv_redirect_int_bpb #( reg idex_mem_write_q; reg idex_mem_byte_q; reg [2:0] idex_wb_sel_q; + reg [2:0] idex_md_op_q; // 乘除法操作类型(0=非乘除法) reg [11:0] idex_csr_addr_q; reg idex_branch_q; reg idex_beq_q; @@ -234,6 +235,7 @@ module cpu21_riscv_redirect_int_bpb #( wire [31:0] d_imm; wire [3:0] d_aluop; wire [2:0] d_wb_sel; + wire [2:0] d_md_op; wire [11:0] d_csr_addr; wire d_uses_rs1; wire d_uses_rs2; @@ -265,6 +267,7 @@ module cpu21_riscv_redirect_int_bpb #( .imm_o (d_imm), .alu_op_o (d_aluop), .wb_sel_o (d_wb_sel), + .md_op_o (d_md_op), .csr_addr_o (d_csr_addr), .uses_rs1_o (d_uses_rs1), .uses_rs2_o (d_uses_rs2), @@ -289,11 +292,10 @@ module cpu21_riscv_redirect_int_bpb #( .csr_write_o (d_csr_write) ); - // 写回值选择:wb_sel 0=ALU 结果,1=访存数据,2=PC+4,3=MUL 高位/余数, - // 4=CSR 旧值。 + // 写回值选择:wb_sel 0=ALU 结果,1=访存数据,2=PC+4(已存在 alu_result 里), + // 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; @@ -329,9 +331,10 @@ module cpu21_riscv_redirect_int_bpb #( // ------------------------------------------------------------------------- // EX 级:前递网络、ALU 运算、存储对齐与控制流裁决 // ------------------------------------------------------------------------- - // EX/MEM 级要前递的值(按 wb_sel 选取)。 + // EX/MEM 级要前递的值(按 wb_sel 选取)。JAL/JALR 的链接地址(PC+4)已在 + // EX 级预先算好并存入 exmem_alu_result_q,所以这里 wb_sel=2 直接取该寄存器, + // 前递路径上不再有 PC+4 加法器(100 MHz 下这是最长路径之一)。 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 : (exmem_wb_sel_q == 3'd4) ? exmem_csr_old_q : exmem_alu_result_q; @@ -368,7 +371,7 @@ module cpu21_riscv_redirect_int_bpb #( (idex_csr_addr_q == 12'h004) ? ustatus_value : (idex_csr_addr_q == 12'h041) ? uepc_value : 32'b0; - // ALU 实例(纯组合逻辑)。 + // ALU 实例(只做单拍运算;乘除法见下面的多周期单元)。 cpu21_riscv_alu u_ex_alu ( .op (idex_aluop_q), .a (ex_src1), @@ -377,6 +380,42 @@ module cpu21_riscv_redirect_int_bpb #( .result2(ex_alu_result2) ); + // ------------------------------------------------------------------------- + // 多周期乘除法单元:EX 级停留若干拍等待结果,不再把 32x32 乘法/除法 + // 放在组合路径上(原来这部分的组合延时约 94 ns,100 MHz 下无法收敛)。 + // 指令在 EX 级期间 ex_md_stall 有效:ID/EX 保持不变、EX/MEM 插气泡、 + // 取指/译码冻结;迭代结束(md_done)后照常推进。 + // ------------------------------------------------------------------------- + wire md_busy; + wire md_done; + wire [31:0] md_result; + wire [31:0] md_result2; + + wire ex_is_muldiv = idex_valid_q && (idex_md_op_q != 3'd0); + wire ex_md_stall = ex_is_muldiv && !md_done; + + cpu21_riscv_muldiv u_muldiv ( + .clk (clk), + .reset (reset), + .start_i (ex_is_muldiv), + .op_i (idex_md_op_q), + .a_i (ex_src1), + .b_i (ex_alu_b), + .busy_o (md_busy), + .done_o (md_done), + .result_o (md_result), + .result2_o(md_result2) + ); + + // EX 级最终结果:乘除法取多周期单元的输出;JAL/JALR(wb_sel=2)的链接地址 + // PC+4 在这里算好随流水寄存器传下去,避免“写回值 → 前递 → 停机判定 → + // 流水寄存器时钟使能”这条组合路径上出现 32 位加法器。 + wire [31:0] ex_result = + ex_is_muldiv ? md_result : + (idex_wb_sel_q == 3'd2) ? (idex_pc_q + 32'd4) : + ex_alu_result; + wire [31:0] ex_result2 = ex_is_muldiv ? md_result2 : ex_alu_result2; + // 控制流裁决:条件分支方向/目标、JAL/JALR 目标、是否需要重定向。 cpu21_riscv_branch_unit u_branch_unit ( .valid_i (idex_valid_q), @@ -450,8 +489,14 @@ module cpu21_riscv_redirect_int_bpb #( // 中断 / CSR / 嵌套返回栈 // ------------------------------------------------------------------------- wire ex_uret_redirect = idex_valid_q && idex_uret_q; - // EX 级正在重定向/URET/停机时不允许响应中断。 - wire ex_block_irq = ex_redirect_valid || ex_uret_redirect || ex_halt_event; + // EX 级正在 URET,或乘除法还没算完时,不响应中断:前者与中断同时发生会丢掉 + // URET 的返回地址,后者会在结果未定型时推进流水线,向 rd 写回错误值。 + // 这里不再包含 ex_redirect_valid / ex_halt_event:它们本身就会让 + // flush_younger 有效(流水线照常被冲刷),而中断优先于重定向时返回地址由 + // irq_save_pc 给出(已正确跳转的分支取目标地址)。这样就把 + // “分支判决 → 中断控制器 → flush → 流水寄存器控制”这条 100 MHz 下的 + // 关键路径切断了(原来要绕经中断控制器再回到 flush)。 + wire ex_block_irq = ex_uret_redirect || ex_md_stall; // 中断发生时要保存的返回地址取"下一个架构 PC":若 EX 是一条预测正确 // 且已跳转的分支,则该 PC 就是它的目标地址;若 EX 为空,则 IF/ID 中 // 保存的就是尚未执行的最老指令。 @@ -581,6 +626,7 @@ module cpu21_riscv_redirect_int_bpb #( idex_mem_write_q <= 1'b0; idex_mem_byte_q <= 1'b0; idex_wb_sel_q <= 3'b0; + idex_md_op_q <= 3'b0; idex_csr_addr_q <= 12'b0; idex_branch_q <= 1'b0; idex_beq_q <= 1'b0; @@ -643,29 +689,48 @@ module cpu21_riscv_redirect_int_bpb #( memwb_mem_to_reg_q <= exmem_mem_to_reg_q; memwb_wb_sel_q <= exmem_wb_sel_q; - // EX/MEM 流水寄存器捕获。 - exmem_valid_q <= idex_valid_q; - exmem_pc_q <= idex_pc_q; - exmem_ir_q <= idex_ir_q; - exmem_alu_result_q <= ex_alu_result; - exmem_alu_result2_q <= ex_alu_result2; - exmem_csr_old_q <= ex_csr_old_value; - exmem_write_data_q <= ex_store_data; - exmem_rd_q <= idex_rd_q; - exmem_reg_write_q <= idex_reg_write_q; - exmem_mem_to_reg_q <= idex_mem_to_reg_q; - exmem_mem_write_q <= idex_mem_write_q; - exmem_wstrb_q <= ex_store_wstrb; - exmem_wb_sel_q <= idex_wb_sel_q; - - // 控制优先级:中断 > URET > ecall 停机 > 分支重定向 > - // load-use 停顿 > 正常流水推进。 - if (flush_younger || pipeline_stall || halted_q) begin - idex_valid_q <= 1'b0; - idex_pred_taken_q <= 1'b0; - idex_pred_target_q <= 32'b0; + // EX/MEM 流水寄存器捕获。乘除法迭代期间(ex_md_stall)往 EX/MEM 插气泡, + // 让乘除法指令继续占用 EX,结果在 md_done 那一拍才写入 EX/MEM。 + if (ex_md_stall) begin + exmem_valid_q <= 1'b0; + exmem_pc_q <= 32'b0; + exmem_ir_q <= 32'b0; + exmem_alu_result_q <= 32'b0; + exmem_alu_result2_q <= 32'b0; + exmem_csr_old_q <= 32'b0; + exmem_write_data_q <= 32'b0; + exmem_rd_q <= 5'b0; + exmem_reg_write_q <= 1'b0; + exmem_mem_to_reg_q <= 1'b0; + exmem_mem_write_q <= 1'b0; + exmem_wstrb_q <= 4'b0; + exmem_wb_sel_q <= 3'b0; end else begin - idex_valid_q <= ifid_valid_q; + exmem_valid_q <= idex_valid_q; + exmem_pc_q <= idex_pc_q; + exmem_ir_q <= idex_ir_q; + exmem_alu_result_q <= ex_result; + exmem_alu_result2_q <= ex_result2; + exmem_csr_old_q <= ex_csr_old_value; + exmem_write_data_q <= ex_store_data; + exmem_rd_q <= idex_rd_q; + exmem_reg_write_q <= idex_reg_write_q; + exmem_mem_to_reg_q <= idex_mem_to_reg_q; + exmem_mem_write_q <= idex_mem_write_q; + exmem_wstrb_q <= ex_store_wstrb; + exmem_wb_sel_q <= idex_wb_sel_q; + end + + // --------------------------------------------------------------------- + // ID/EX 装载。 + // + // payload(PC/IR/控制位/预测位)只在 load-use 停顿、乘除法迭代、停机时 + // 保持(时钟使能与“冲刷”无关),其余情况一律装载;只有 valid 位受冲刷 + // 控制,冲刷时清 0。这样“EX 级组合判决(分支/中断)→ 流水寄存器时钟 + // 使能”这条 100 MHz 下最长的路径就只剩下一个触发器(valid),payload + // 即使装进错误路径的数据也无害——所有使用者都以 idex_valid_q 为前提。 + // --------------------------------------------------------------------- + if (!(pipeline_stall || halted_q || ex_md_stall)) begin idex_pc_q <= ifid_pc_q; idex_ir_q <= ifid_ir_q; idex_rs1_value_q <= d_rs1_value; @@ -681,6 +746,7 @@ module cpu21_riscv_redirect_int_bpb #( idex_mem_write_q <= d_mem_write; idex_mem_byte_q <= d_mem_byte; idex_wb_sel_q <= d_wb_sel; + idex_md_op_q <= d_md_op; idex_csr_addr_q <= d_csr_addr; idex_branch_q <= d_branch; idex_beq_q <= d_beq; @@ -700,6 +766,16 @@ module cpu21_riscv_redirect_int_bpb #( idex_pred_target_q <= ifid_pred_target_q; end + if (halted_q || ex_md_stall) begin + // 停机 / 乘除法迭代:valid 保持不变(指令继续占用 EX)。 + end else if (pipeline_stall || flush_younger) begin + // load-use 停顿:load 已经进 EX/MEM,这里给 EX 装一个气泡(payload 保持 + // 原值,valid=0 时无害);冲刷同理。 + idex_valid_q <= 1'b0; + end else begin + idex_valid_q <= ifid_valid_q; + end + if (take_irq) begin pc_q <= irq_vector; ifid_valid_q <= 1'b0; @@ -720,7 +796,7 @@ module cpu21_riscv_redirect_int_bpb #( ifid_valid_q <= 1'b0; ifid_pred_taken_q <= 1'b0; ifid_pred_target_q <= 32'b0; - end else if (pipeline_stall) begin + end else if (pipeline_stall || ex_md_stall) begin pc_q <= pc_q; ifid_valid_q <= ifid_valid_q; ifid_pc_q <= ifid_pc_q; @@ -748,7 +824,7 @@ module cpu21_riscv_redirect_int_bpb #( .clk (clk), .reset (reset), .inc_cycle_i (!halted_q), - .stall_i (pipeline_stall), + .stall_i (pipeline_stall || ex_md_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), diff --git a/testbench/programs/board_demo.asm b/testbench/programs/board_demo.asm index 5e7761d..c457c6e 100644 --- a/testbench/programs/board_demo.asm +++ b/testbench/programs/board_demo.asm @@ -13,8 +13,8 @@ main_loop: restart: addi t1, zero, 1 -delay: # 软件延时,让人眼能看到跑马灯(6.25 MHz 下约 20 ms) - lui t2, 0xf # 延时计数 0x0000F000 +delay: # 软件延时,让人眼能看到跑马灯(50 MHz 下约 20 ms) + lui t2, 0x7a # 延时计数 0x0007A000 ≈ 5e5 次循环 addi t3, zero, 1 delay_loop: sub t2, t2, t3 diff --git a/testbench/programs/muldiv_test.asm b/testbench/programs/muldiv_test.asm new file mode 100644 index 0000000..4161f78 --- /dev/null +++ b/testbench/programs/muldiv_test.asm @@ -0,0 +1,95 @@ +# 乘除法(多周期单元)测试:每个结果都写给 MMIO 的 LED 寄存器,供测试平台比对。 +# 期望输出顺序见同目录 muldiv_test.expected(共 13 个值)。 +# +# ① mul 7 * 6 = 42 -> 0000002a +# ② mul -5 * 4 = -20 -> ffffffec +# ③ mulh 0x40000000 * 4 高 32 位 -> 00000001 +# ④ mulhu 0xffffffff * 2 高 32 位 -> 00000001 +# ⑤ mulhsu -1 * 0xffffffff 高 32 位 -> ffffffff +# ⑥ divu 100 / 7 = 14 -> 0000000e +# ⑦ remu 100 % 7 = 2 -> 00000002 +# ⑧ divu 0xffffffff / 2 = 2147483647 -> 7fffffff +# ⑨ divu 12345 / 0 除零约定 -> ffffffff +# ⑩ remu 12345 % 0 除零约定 -> 00003039 +# ⑪ remu 0xffffffff % 16 = 15 -> 0000000f +# ⑫ mul 0x12345678 * 16 低 32 位 -> 23456780 +# ⑬ mulh 0x12345678 * 0x1000 高 32 位 -> 00000123 +# +# 用法:python tools/rv_asm.py testbench/programs/muldiv_test.asm testbench/rom/muldiv_test.hex + + # ① mul:有符号乘法的低 32 位 + addi t1, zero, 7 + addi t2, zero, 6 + mul a0, t1, t2 + print + + # ② mul:负乘数(低 32 位) + addi t1, zero, -5 + addi t2, zero, 4 + mul a0, t1, t2 + print + + # ③ mulh:正数相乘,高 32 位不为 0 + lui t1, 0x40000 + addi t2, zero, 4 + mulh a0, t1, t2 + print + + # ④ mulhu:无符号高位(0xffffffff * 2 = 0x1_fffffffe) + addi t1, zero, -1 + addi t2, zero, 2 + mulhu a0, t1, t2 + print + + # ⑤ mulhsu:有符号 x 无符号(-1 * 4294967295 = 0xffffffff_00000001) + addi t1, zero, -1 + addi t2, zero, -1 + mulhsu a0, t1, t2 + print + + # ⑥ divu:普通除法 + addi t1, zero, 100 + addi t2, zero, 7 + divu a0, t1, t2 + print + + # ⑦ remu:普通取余 + remu a0, t1, t2 + print + + # ⑧ divu:无符号大数 + addi t1, zero, -1 + addi t2, zero, 2 + divu a0, t1, t2 + print + + # ⑨ divu:除零(商为全 1) + lui t1, 0x3 + addi t1, t1, 0x39 # t1 = 12345 + addi t2, zero, 0 + divu a0, t1, t2 + print + + # ⑩ remu:除零(余数为被除数) + remu a0, t1, t2 + print + + # ⑪ remu:0xffffffff % 16 + addi t1, zero, -1 + addi t2, zero, 16 + remu a0, t1, t2 + print + + # ⑫ mul:0x12345678 * 16 的低 32 位 + lui t1, 0x12345 + addi t1, t1, 0x678 + addi t2, zero, 16 + mul a0, t1, t2 + print + + # ⑬ mulh:0x12345678 * 0x1000 的高 32 位 + lui t2, 0x1 + mulh a0, t1, t2 + print + + halt diff --git a/testbench/programs/muldiv_test.expected b/testbench/programs/muldiv_test.expected new file mode 100644 index 0000000..20b4499 --- /dev/null +++ b/testbench/programs/muldiv_test.expected @@ -0,0 +1,13 @@ +0000002a +ffffffec +00000001 +00000001 +ffffffff +0000000e +00000002 +7fffffff +ffffffff +00003039 +0000000f +23456780 +00000123 diff --git a/testbench/rom/muldiv_test.hex b/testbench/rom/muldiv_test.hex new file mode 100644 index 0000000..5a6e500 --- /dev/null +++ b/testbench/rom/muldiv_test.hex @@ -0,0 +1,64 @@ +00700313 +00600393 +02730533 +00001FB7 +00AFA023 +FFB00313 +00400393 +02730533 +00001FB7 +00AFA023 +40000337 +00400393 +02731533 +00001FB7 +00AFA023 +FFF00313 +00200393 +02733533 +00001FB7 +00AFA023 +FFF00313 +FFF00393 +02732533 +00001FB7 +00AFA023 +06400313 +00700393 +02735533 +00001FB7 +00AFA023 +02737533 +00001FB7 +00AFA023 +FFF00313 +00200393 +02735533 +00001FB7 +00AFA023 +00003337 +03930313 +00000393 +02735533 +00001FB7 +00AFA023 +02737533 +00001FB7 +00AFA023 +FFF00313 +01000393 +02737533 +00001FB7 +00AFA023 +12345337 +67830313 +01000393 +02730533 +00001FB7 +00AFA023 +000013B7 +02731533 +00001FB7 +00AFA023 +00A00893 +00000073 diff --git a/testbench/tb_board_top.v b/testbench/tb_board_top.v index 8081204..3806052 100644 --- a/testbench/tb_board_top.v +++ b/testbench/tb_board_top.v @@ -49,7 +49,7 @@ module tb_board_top; $display("NOTE: 复位后观察 LED(默认程序首次写入应为 0001)"); repeat (10) @(posedge clk100); rst_n = 1'b1; - repeat (30000) @(posedge clk100); // 300 us(CPU 时钟经 16 分频,需要更长窗口) + repeat (30000) @(posedge clk100); // 300 us(CPU 时钟为 clk100/2 = 50 MHz) $display("FINAL: LED 变化次数=%0d 当前 LED=%04h", changes, led); $finish; end diff --git a/tools/rv_asm.py b/tools/rv_asm.py index 9373945..80ea464 100644 --- a/tools/rv_asm.py +++ b/tools/rv_asm.py @@ -130,6 +130,8 @@ RTYPE = { "mulh": (0x01, 1), "mulhsu": (0x01, 2), "mulhu": (0x01, 3), + "divu": (0x01, 5), + "remu": (0x01, 7), } ITYPE = {"addi": 0, "slti": 2, "sltiu": 3, "xori": 4, "ori": 6, "andi": 7} SHIFT = {"slli": 1, "srli": 5, "srai": 5}