- 新增 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 文件已更新。
57 lines
1.8 KiB
Verilog
57 lines
1.8 KiB
Verilog
`timescale 1ns / 1ps
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// cpu21_board_top 的仿真测试(不上板,纯 RTL 仿真)
|
||
//
|
||
// 检查内容:
|
||
// 1) 复位释放后 CPU 能从片内 ROM 取指(ROM 由 cpu21_board_rom.vh 初始化);
|
||
// 2) 程序通过 MMIO 写 LED,板载 LED 端口上能看到 16 位跑马灯序列。
|
||
//
|
||
// 默认的 board_demo 程序带约 20 ms 软件延时,仿真只需确认第一次写入;
|
||
// 若要完整验证跑马灯序列,可生成一份短延时的 ROM 并用 -d BOARD_ROM_FAST 编译:
|
||
// xvlog -d BOARD_ROM_FAST board/cpu21_board_top.v <rtl/*.v> testbench/tb_board_top.v
|
||
// -----------------------------------------------------------------------------
|
||
module tb_board_top;
|
||
reg clk100 = 1'b0;
|
||
reg rst_n = 1'b0;
|
||
reg go = 1'b0;
|
||
reg [2:0] irq_sw = 3'b000;
|
||
|
||
wire [15:0] led;
|
||
wire led_we;
|
||
|
||
always #5 clk100 = ~clk100; // 100 MHz
|
||
|
||
cpu21_board_top #(
|
||
.LED_ADDR(32'h0000_1000)
|
||
) dut (
|
||
.clk100(clk100),
|
||
.rst_n (rst_n),
|
||
.go (go),
|
||
.irq_sw(irq_sw),
|
||
.led (led),
|
||
.led_we(led_we)
|
||
);
|
||
|
||
integer changes = 0;
|
||
reg [15:0] led_prev = 16'hxxxx;
|
||
|
||
always @(posedge clk100) begin
|
||
if (led !== led_prev) begin
|
||
changes = changes + 1;
|
||
led_prev = led;
|
||
$display("t=%t LED=%04h led_we=%b", $time, led, led_we);
|
||
end
|
||
end
|
||
|
||
initial begin
|
||
$timeformat(-9, 3, " ns", 12); // %t 按 ns 显示(默认按精度 ps 显示)
|
||
$display("NOTE: 复位后观察 LED(默认程序首次写入应为 0001)");
|
||
repeat (10) @(posedge clk100);
|
||
rst_n = 1'b1;
|
||
repeat (30000) @(posedge clk100); // 300 us(CPU 时钟为 clk100/2 = 50 MHz)
|
||
$display("FINAL: LED 变化次数=%0d 当前 LED=%04h", changes, led);
|
||
$finish;
|
||
end
|
||
endmodule
|