- 新增 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 文件已更新。
184 lines
7.4 KiB
Verilog
184 lines
7.4 KiB
Verilog
`timescale 1ns / 1ps
|
||
`default_nettype none
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// 上板顶层(Nexys4DDR,xc7a100tcsg324-1)
|
||
//
|
||
// 处理器顶层 cpu21_riscv_redirect_int_bpb 暴露了 775 位调试端口(各级 PC/IR、
|
||
// 计数器、BPB/中断观测等),直接作为实现顶层会因 I/O 超限而无法布局布线
|
||
// ([Place 30-415] IO Placement failed due to overutilization)。本模块只把
|
||
// 开发板真正需要的外设引出来,其余调试端口不连接,因此不占用引脚:
|
||
//
|
||
// 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.34 s 便于观察
|
||
//
|
||
// 片内存储器:
|
||
// ROM:ROM_WORDS 字,组合读,内容由 `include "cpu21_board_rom.vh"` 给出
|
||
// (该文件由 tools/hex2vh.py 从 testbench/programs/*.hex 生成);
|
||
// RAM:RAM_WORDS 字,字节写使能 + 组合读;只响应地址 < 2^(RAM_AW+2),
|
||
// 因此 MMIO 地址不会落进 RAM(避免与 LED_ADDR 别名)。
|
||
//
|
||
// 注意:ROM_WORDS / RAM_WORDS 需为 2 的幂(默认 1024,对应地址位 [11:2])。
|
||
// -----------------------------------------------------------------------------
|
||
module cpu21_board_top #(
|
||
parameter [31:0] LED_ADDR = 32'h0000_1000,
|
||
parameter ROM_WORDS = 1024,
|
||
parameter RAM_WORDS = 1024,
|
||
parameter [31:0] USTATUS_INIT = 32'h0000_0001,
|
||
// 板载 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,低有效
|
||
input wire go, // N17:BTNC
|
||
input wire [ 2:0] irq_sw, // SW[2:0]
|
||
output wire [15:0] led, // LED[15:0]
|
||
output wire led_we // LED16_B:MMIO 写入指示
|
||
);
|
||
localparam ROM_AW = $clog2(ROM_WORDS);
|
||
localparam RAM_AW = $clog2(RAM_WORDS);
|
||
|
||
wire arst = ~rst_n; // 异步复位(高有效)
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 时钟: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;
|
||
|
||
always @(posedge clk100 or posedge arst) begin
|
||
if (arst) begin
|
||
clk_cnt <= 16'd0;
|
||
clk_div <= 1'b0;
|
||
end else if (clk_cnt == (CLK_DIV / 2 - 1)) begin
|
||
clk_cnt <= 16'd0;
|
||
clk_div <= ~clk_div;
|
||
end else begin
|
||
clk_cnt <= clk_cnt + 16'd1;
|
||
end
|
||
end
|
||
|
||
wire clk_cpu = clk_div;
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 复位同步器:按键异步置位、释放与 CPU 时钟同步
|
||
// ---------------------------------------------------------------------------
|
||
reg [2:0] rst_sync_q;
|
||
|
||
always @(posedge clk_cpu or posedge arst) begin
|
||
if (arst) rst_sync_q <= 3'b111;
|
||
else rst_sync_q <= {rst_sync_q[1:0], 1'b0};
|
||
end
|
||
|
||
wire reset = rst_sync_q[2];
|
||
|
||
// CPU 接口连线(先声明:后面的 ROM/RAM 逻辑与例化都要用到)
|
||
wire [31:0] instr_addr;
|
||
wire [31:0] data_addr;
|
||
wire [31:0] data_wdata;
|
||
wire [ 3:0] data_wstrb;
|
||
wire data_we;
|
||
wire [31:0] led_data;
|
||
wire led_valid;
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 指令 ROM:组合读
|
||
// ---------------------------------------------------------------------------
|
||
reg [31:0] rom [0:ROM_WORDS-1];
|
||
integer i;
|
||
|
||
initial begin
|
||
for (i = 0; i < ROM_WORDS; i = i + 1) rom[i] = 32'h0000_0013; // NOP 填充
|
||
`ifdef BOARD_ROM_FAST
|
||
// 仿真专用:把演示程序的延时改小的版本(综合流程不会用到)
|
||
`include "cpu21_board_rom_fast.vh"
|
||
`else
|
||
`include "cpu21_board_rom.vh"
|
||
`endif
|
||
end
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 数据 RAM:字节写使能 + 组合读
|
||
// ---------------------------------------------------------------------------
|
||
reg [31:0] ram[0:RAM_WORDS-1];
|
||
integer j;
|
||
|
||
initial begin
|
||
for (j = 0; j < RAM_WORDS; j = j + 1) ram[j] = 32'b0;
|
||
end
|
||
|
||
wire [ROM_AW+1:2] rom_index = instr_addr[ROM_AW+1:2];
|
||
wire [RAM_AW+1:2] ram_index = data_addr[RAM_AW+1:2];
|
||
wire ram_sel = (data_addr[31:RAM_AW+2] == 0);
|
||
|
||
wire [31:0] instr_word = rom[rom_index];
|
||
wire [31:0] data_rdata = ram_sel ? ram[ram_index] : 32'b0;
|
||
|
||
always @(posedge clk_cpu) begin
|
||
if (ram_sel && data_we) begin
|
||
if (data_wstrb[0]) ram[ram_index][7:0] <= data_wdata[7:0];
|
||
if (data_wstrb[1]) ram[ram_index][15:8] <= data_wdata[15:8];
|
||
if (data_wstrb[2]) ram[ram_index][23:16] <= data_wdata[23:16];
|
||
if (data_wstrb[3]) ram[ram_index][31:24] <= data_wdata[31:24];
|
||
end
|
||
end
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 处理器:只连接上板需要的端口,调试端口留空(不产生 I/O)
|
||
// ---------------------------------------------------------------------------
|
||
cpu21_riscv_redirect_int_bpb #(
|
||
.LED_ADDR (LED_ADDR),
|
||
.USTATUS_INIT(USTATUS_INIT)
|
||
) u_cpu (
|
||
.clk (clk_cpu),
|
||
.reset (reset),
|
||
.instr_i (instr_word),
|
||
.data_rdata_i(data_rdata),
|
||
.irq_i (irq_sw),
|
||
.go_i (go),
|
||
|
||
.instr_addr_o(instr_addr),
|
||
.data_addr_o (data_addr),
|
||
.data_wdata_o(data_wdata),
|
||
.data_wstrb_o(data_wstrb),
|
||
.data_we_o (data_we),
|
||
|
||
.led_data_o (led_data),
|
||
.led_valid_o(led_valid)
|
||
);
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 板载 LED:显示 MMIO 写入值的低 16 位;写入指示拉长便于观察
|
||
// ---------------------------------------------------------------------------
|
||
assign led = led_data[15:0];
|
||
|
||
reg [23:0] led_we_cnt;
|
||
|
||
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; // 约 0.34 s @50 MHz
|
||
else if (led_we_cnt != 24'd0) led_we_cnt <= led_we_cnt - 24'd1;
|
||
end
|
||
|
||
assign led_we = (led_we_cnt != 24'd0);
|
||
endmodule
|
||
|
||
`default_nettype wire
|