6 Commits
Author SHA1 Message Date
Anccend 556fbe9530 目录整理:RTL/上板/仿真分开存放,测试 ROM 镜像归入 testbench/rom
- rtl/:12 个可综合的处理器源文件(顶层 cpu21_riscv_redirect_int_bpb.v,其余为各功能单元)。
- board/:板级顶层 cpu21_board_top.v、引脚约束 cpu21_board_top.xdc、片内 ROM 初始化
  cpu21_board_rom.vh 与短延时版本 cpu21_board_rom_fast.vh。
- testbench/:保留三个测试平台;ROM 镜像统一放到 testbench/rom/,测试程序源码与期望值
  仍在 testbench/programs/(其中的 .hex 是 RARS 原始输出,与 rom/ 里改过显示方式的版本
  内容不同,两者都保留)。
- 同步更新:两个测试平台的 ROM 探测路径改为 testbench/rom/(并在头注释里补充说明:
  PowerShell 会把 -generic_top/--testplusarg 参数里的 "=" 拆开、Verilog 字符串中的
  路径要用正斜杠);tools/hex2vh.py 的用法示例指向 board/cpu21_board_top.v;
  说明文档新增“目录结构”一节并同步所有路径引用;.gitignore 补充 .Xil/。
- 清理:删除 *.hex.lst 中间文件与空的 .Xil/ 目录。
- 验证:isa_test 24/24、lui_test 4/4、mmio_test 5/5 三项回归通过;两个基线(5560 / 6003
  周期)与整理前一致;板级仿真(默认 ROM 首次写入 LED=0001、快速 ROM 跑马灯循环)正常;
  Vivado 工程源文件路径同步到 rtl/ 与 board/ 后综合通过(LUT 5276、23 个引脚,与整理前
  完全一致),因此已有 bit 文件仍然有效。
2026-09-13 18:06:21 +08:00
Anccend 1ae6fe6ebe 上板支持:Nexys4DDR 顶层、片内 ROM/RAM 与 MMIO 跑马灯演示
- 新增 cpu21_board_top.v:上板顶层。处理器顶层暴露了 775 位调试端口,直接作为
  实现顶层会因 IO 超限无法布局布线([Place 30-415] IO Placement failed due to
  overutilization),本模块只引出板上真正需要的 23 个引脚
  (clk100 / rst_n / go / irq_sw[2:0] / led[15:0] / led_we)。
  * 100 MHz 经 CLK_DIV=16 分频到 6.25 MHz:ALU 的除法/取余是组合电路,
    100 MHz 下 WNS=-84.126 ns(325 级逻辑)无法收敛,分频后 WNS=+6.083 ns。
  * 分频时钟由 Vivado 自动插入 BUFG;RTL 中不例化原语,以便跨工具仿真。
  * 复位同步器、片内 ROM(1024x32,组合读,`include cpu21_board_rom.vh`)、
    片内 RAM(1024x32,字节写使能,只响应地址 <4KB,避免与 MMIO 地址别名)。
- 新增 cpu21_board_top.xdc:Nexys4DDR 引脚约束 + create_generated_clock。
- 新增 tools/hex2vh.py:把 hex 机器码转成 rom[i] = 32'h...; 初始化包含文件。
- 新增 testbench/programs/board_demo.asm 与 testbench/tb_board_top.v:
  16 步跑马灯演示程序(通过 MMIO 写 LED)。仿真验证:默认 ROM 下首次写入
  LED=0001,快速 ROM 下完整序列 0001..8000 循环。
- 新增 cpu21_board_rom.vh / cpu21_board_rom_fast.vh,由 BOARD_ROM_FAST 宏选择。
- tools/rv_asm.py:读源文件时兼容带 BOM 的 UTF-8。
- 测试资源:补齐 lui_test.expected;修正 mmio_test.expected(print 由
  lui+sw 实现,本身就是一次 LED 写,共 5 次)。三个回归全部通过:
  isa_test 24/24、lui_test 4/4、mmio_test 5/5。
- .gitignore:忽略 _tmp/、_vivado/、*.vcd、*.pb、*.hex.lst。
- 说明文档:模块划分表补上上板相关模块,新增“上板(Nexys4DDR + Vivado)”
  章节(引脚分配、分频原因、ROM/RAM 组织、ROM 生成流程、Vivado 工程设置、
  实现结果)。

实现结果:synth_design / route_design / write_bitstream 全部完成,
LUT 5400 (8.52%)、LUTRAM 512、FF 2507、DSP 12、IOB 23 (10.95%),
bit 文件为 pipelined-cpu/pipelined-cpu.runs/impl_1/cpu21_board_top.bit
(Vivado 工程目录被 .gitignore 排除,不入库)。
2026-09-13 17:37:44 +08:00
Anccend 192e28795a 实现 MMIO,LED 显示从 ecall 改为内存映射存储
cpu21_mmio.v(新增)
- 在 MEM 级做地址译码,把外设寄存器映射进数据地址空间;目前实现 LED 数据
  寄存器(地址由顶层 parameter LED_ADDR 指定,默认 0x0000_1000,位于 4 KB
  数据 RAM 之外,不会与 RAM 冲突);
- 地址译码按“字”对齐:同一 32 位寄存器内任意字节偏移都算命中,具体修改
  哪几个字节由 wstrb 决定,因此 sw 写全字、sb 只写单字节(与 RAM 字节写
  语义一致);
- 读命中时由顶层用 rdata_o 取代 data_rdata_i,从而支持把 LED 值读回;
- led_valid_o 在写入后拉高一拍,作为“本周期有 LED 写入”的指示。

cpu21_riscv_redirect_int_bpb.v
- 顶层参数新增 LED_ADDR;例化 cpu21_mmio 并新增访存读数据选择
  mem_rdata = mmio_rdata_hit ? mmio_rdata : data_rdata_i(MEM/WB 捕获用它);
- 删除 ecall 驱动的 LED 逻辑(原 ex_led_event 与 led_data_q/led_valid_q 寄存器
  移入 MMIO 模块),LED 输出端口改由 cpu21_mmio 驱动;
- ecall 保留停机语义:a7==34 的历史“显示”调用保留为无操作,其余(如 a7=10)
  仍置 halted_o。

测试资产
- tools/rv_asm.py:print 伪指令改为“lui t6,LED_ADDR>>12 + sw a0,0(t6)”(写 MMIO
  LED 寄存器,仍需两个字);新增 sb 支持;文末说明同步更新;
- testbench/programs/mmio_test.asm + mmio_test.expected(新增):覆盖全字写、
  读回、字节写三种情况;
- isa_test.asm / lui_test.asm 迁移到 MMIO 输出方式;
- 两个 testbench 的 RAM 模型处注明 MMIO 区域不属于 RAM。

验证(xsim,按提交后的正式路径生成 hex)
- mmio_test:4 项输出全部匹配(12345678 / 12345678 / ffffffff / ffff7fff,
  最后一项为 sb 覆盖 led[15:8] 后读回);
- isa_test:24 项输出与 isa_test.expected 逐项一致;lui_test:4 项输出正确;
- 中断测试程序回归:IRQ1@cyc85、IRQ2@cyc266、IRQ3@cyc447 与改动前一致,
  FINAL 统计完全一致(stalls=11 bubbles=297 cond_taken=648 pred_ok=663
  pred_fail=102,6003 周期);
- benchmark 长仿真回归:FINAL 统计与改动前完全一致(stalls=3195 bubbles=6955
  cond_taken=8857 pred_ok=9455 pred_fail=1050,60000 周期),且 LED 输出次数为 0,
  确认 ecall 已不再驱动 LED;
- cpu21_mmio.v 已加入 Vivado 工程 sources_1。
2026-09-13 16:30:57 +08:00
Anccend ccd1b917d0 新增 ISA 验证资产:迷你汇编器 + 指令测试程序
tools/rv_asm.py
- 极简 RISC-V 汇编器(Python 3,无第三方依赖),支持
  add/sub/and/or/xor/sll/srl/sra/slt/sltu、mul/mulh/mulhsu/mulhu、
  addi/slti/sltiu/xori/ori/andi、slli/srli/srai、lw/sw、
  beq/bne/blt/bge/bltu/bgeu、lui、jal/jalr、ecall、标签与 # 注释;
- 提供 print / halt 两条双字伪指令(分别等价于 addi a7,zero,34 + ecall、
  addi a7,zero,10 + ecall);
- I 型与访存立即数做 12 位有符号范围检查,越界直接报错,避免静默截断;
- 除 hex 外同时输出 .lst 编码清单,便于与 .asm 逐行核对。

testbench/programs/isa_test.asm + isa_test.expected
- 指令扩展验证程序:sltiu(两个方向)、xor/sll/srl/sra、mul/mulh/mulhu/mulhsu、
  bltu/bge/bgeu 的有符号与无符号区分,以及 bge/bgeu/bltu 三个循环,
  共 24 项 LED 输出,期望值逐行保存在 isa_test.expected。

testbench/programs/lui_test.asm
- LUI 验证程序:0x12345 / 0xfffff / 0x80000 与 lui+addi 组合,共 4 项输出。

另外包含编辑器/格式化工具对 cpu21_riscv_alu.v、cpu21_riscv_branch_unit.v、
cpu21_riscv_decoder.v 的重排(纯格式,语义等价)。

验证(xsim + testbench/tb_no_intr.v,按提交后的正式路径生成 hex)
- isa_test:24 项 LED 输出与 isa_test.expected 逐项一致,143 周期后 halt 停机;
- lui_test:12345000 / fffff000 / 80000000 / 12345678,21 周期后停机;
- 重新编译无 error / warning。
2026-09-12 19:10:24 +08:00
Anccend 9f6f4c9ada ISA 扩展:sltiu、bge/bgeu、mulh/mulhsu/mulhu(并回归 xor/sll/srl/sra/bltu/mul)
cpu21_riscv_alu.v
- 新增 ALU_MULH(4'd13)、ALU_MULHSU(4'd14):用 64 位有符号乘积
  mult_ss = $signed(a)*$signed(b)、mult_su = $signed(a)*$signed({32'b0,b})
  取高 32 位;mulhu 复用 ALU_MUL 的 result2(无符号乘积高 32 位),
  与课程约定的 remu 共用 wb_sel=3 通道,因此 ALU 操作码仍为 4 位。

cpu21_riscv_decoder.v
- OP_I 新增 funct3=011 -> sltiu(立即数符号扩展后无符号比较);
- OP_R 的 funct7=0000001 分支改为完整 M 扩展译码:
  000=mul、001=mulh、010=mulhsu、011=mulhu,并保留 111=remu(wb_sel=3);
- OP_BRANCH 新增 funct3=101(bge)、111(bgeu),新增 bge_o/bgeu_o 输出,
  ALU_OP 仍按比较类型给出 SLT/SLTU/SUB。

cpu21_riscv_branch_unit.v
- 新增 bge_i、bgeu_i;把四种比较预计算成短 wire(有符号 <、>= 与无符号 <、>=)
  再组合出 branch_taken,顺带修掉之前被自动换行拆散的 $signed 表达式。

cpu21_riscv_redirect_int_bpb.v
- 新增 d_bge/d_bgeu 连线与 idex_bge_q/idex_bgeu_q 流水寄存器(含复位与捕获);
- IF 级 f_is_branch 纳入 funct3=101/111,使 bge/bgeu 也参与 BPB 预测。

说明文档
- 功能列表与编码表补充 sltiu、bge/bgeu 与 M 扩展乘法。

验证(xsim + tb_no_intr;测试程序由临时小汇编器生成,共 24 项 LED 输出逐项比对)
- sltiu:1 <u 0xffffffff = 00000001;0xffffffff <u 1 = 00000000
- xor:0xff ^ 0xffffffff = ffffff00
- sll:1 << 31 = 80000000;srl:0xffffffff >> 28 = 0000000f;sra:同左 = ffffffff
- mul:1234*567 = 000aad1e;mulh:(-1)*(-1) 高 = 00000000
  mulhu:0xffffffff^2 高 = fffffffe;mulhsu:(-1)*2 高 = ffffffff
- bltu:0 <u 0xffffffff 成立(取跳转分支);bge:(-1) >= 0 不成立(走 fall-through);
  bgeu:0xffffffff >=u 0 成立
- bge / bgeu / bltu 三个循环计数正确(fffffffd..00000000 / 3..1 / 4..1),最后 halt 停机
  (143 周期,halted=1)
- 回归:中断测试程序 6003 周期统计与改动前逐项一致
  (stalls=11 bubbles=297 cond_taken=648 pred_ok=663 pred_fail=102,
  IRQ1@cyc85、IRQ2@cyc266、IRQ3@cyc447);
  benchmark 的 BLT 段落仍连续输出 fffffff1..ffffffff,
  60000 周期统计与改动前完全一致(stalls=3195 bubbles=6955 cond_taken=8857
  pred_ok=9455 pred_fail=1050)。
2026-09-12 19:02:24 +08:00
Anccend 036a57d907 实现 LUI 指令(加载高位立即数)
- cpu21_riscv_alu.v:新增 ALU_PASSB(4'd15),直接把 B 端送到 result;
- cpu21_riscv_decoder.v:新增 OP_LUI(IR[6:2]=0x0d,即标准 RISC-V opcode 0x37),
  imm = {IR[31:12], 12'b0},用 ALU_SRC 选择立即数、ALU 直通输出写回 rd,
  不读源寄存器(uses_rs1/rs2 保持 0);
- 说明文档补充 lui 的功能说明与编码表行。

验证(xsim + tb_no_intr,自编的小汇编器生成测试程序):
依次输出 12345000 / fffff000 / 80000000,以及 lui+addi 组合得到 12345678,
随后 a7=10 停机(共 21 周期,halted=1),无编译错误与警告。
2026-09-12 18:59:04 +08:00
37 changed files with 3406 additions and 95 deletions
+10
View File
@@ -1 +1,11 @@
pipelined-cpu/* pipelined-cpu/*
# 本地临时目录(仿真脚本、日志、中间文件)
_tmp/
_vivado/
# 仿真/综合中间产物
*.vcd
*.pb
*.hex.lst
.Xil/
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+176
View File
@@ -0,0 +1,176 @@
`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)
// 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 便于观察
//
// 片内存储器:
// 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 分频给 CPU。ALU 里的除法/取余是组合电路(实测约 94 ns),
// 100 MHz 下无法收敛;默认 16 分频(6.25 MHz,周期 160 ns)留出足够余量。
parameter integer CLK_DIV = 16
) (
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,经 BUFG 走全局时钟网络
// (XDC 中用 create_generated_clock 声明该分频时钟,否则时序分析会按
// 100 MHz 的 10 ns 要求去约束 CPU 内部路径而报大量违例)
// ---------------------------------------------------------------------------
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
// 不直接例化 BUFG 原语,保持 RTL 可跨工具仿真;
// 综合/实现时 Vivado 会为这条时钟网络自动插入 BUFG(全局时钟缓冲)。
wire clk_cpu = clk_div;
// ---------------------------------------------------------------------------
// 复位同步器(在 CPU 时钟域):按键异步置位、释放与 clk_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; // 约 2.7 s @6.25 MHz(分频后的 CPU 时钟)
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
+45
View File
@@ -0,0 +1,45 @@
## cpu21_board_top.xdc —— Nexys4DDR (xc7a100tcsg324-1) 上板引脚
## 引脚编号取自 ConstraintsData/Nexys4DDR_Master.xdc(母版默认全部注释)。
## 只约束 cpu21_board_top 实际使用的端口。
## 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 保持一致;改分频比时这里也要改)
create_generated_clock -name cpu_clk -source [get_ports { clk100 }] \
-divide_by 16 [get_pins { clk_div_reg/Q }]
## 复位按键(CPU_RESETN,低有效)与"继续运行"按键(BTNC)
set_property -dict { PACKAGE_PIN C12 IOSTANDARD LVCMOS33 } [get_ports { rst_n }]
set_property -dict { PACKAGE_PIN N17 IOSTANDARD LVCMOS33 } [get_ports { go }]
## 拨码开关 SW[2:0] 作为三路中断源
set_property -dict { PACKAGE_PIN J15 IOSTANDARD LVCMOS33 } [get_ports { irq_sw[0] }]
set_property -dict { PACKAGE_PIN L16 IOSTANDARD LVCMOS33 } [get_ports { irq_sw[1] }]
set_property -dict { PACKAGE_PIN M13 IOSTANDARD LVCMOS33 } [get_ports { irq_sw[2] }]
## 16 位 LED:显示 MMIO 写入值的低 16 位
set_property -dict { PACKAGE_PIN H17 IOSTANDARD LVCMOS33 } [get_ports { led[0] }]
set_property -dict { PACKAGE_PIN K15 IOSTANDARD LVCMOS33 } [get_ports { led[1] }]
set_property -dict { PACKAGE_PIN J13 IOSTANDARD LVCMOS33 } [get_ports { led[2] }]
set_property -dict { PACKAGE_PIN N14 IOSTANDARD LVCMOS33 } [get_ports { led[3] }]
set_property -dict { PACKAGE_PIN R18 IOSTANDARD LVCMOS33 } [get_ports { led[4] }]
set_property -dict { PACKAGE_PIN V17 IOSTANDARD LVCMOS33 } [get_ports { led[5] }]
set_property -dict { PACKAGE_PIN U17 IOSTANDARD LVCMOS33 } [get_ports { led[6] }]
set_property -dict { PACKAGE_PIN U16 IOSTANDARD LVCMOS33 } [get_ports { led[7] }]
set_property -dict { PACKAGE_PIN V16 IOSTANDARD LVCMOS33 } [get_ports { led[8] }]
set_property -dict { PACKAGE_PIN T15 IOSTANDARD LVCMOS33 } [get_ports { led[9] }]
set_property -dict { PACKAGE_PIN U14 IOSTANDARD LVCMOS33 } [get_ports { led[10] }]
set_property -dict { PACKAGE_PIN T16 IOSTANDARD LVCMOS33 } [get_ports { led[11] }]
set_property -dict { PACKAGE_PIN V15 IOSTANDARD LVCMOS33 } [get_ports { led[12] }]
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 时钟)
set_property -dict { PACKAGE_PIN R12 IOSTANDARD LVCMOS33 } [get_ports { led_we }]
## 上板调试时可把 MMIO 写入的 LED 寄存器也引到 LED16_R/G,
## 但默认只用一个引脚,避免与 led_we 争用。
+206 -27
View File
@@ -1,43 +1,101 @@
# `cpu21-riscv-4.circ` 的 Verilog 实现 # `cpu21-riscv-4.circ` 的 Verilog 实现
顶层文件:`cpu21_riscv_redirect_int_bpb.v` 处理器顶层文件:`rtl/cpu21_riscv_redirect_int_bpb.v`
该设计对应原电路中的“重定向流水线+中断+分支预测”部分,顶层模块为 该设计对应原电路中的“重定向流水线+中断+分支预测”部分,顶层模块为
`cpu21_riscv_redirect_int_bpb`。为便于单独仿真与调试,各功能单元已拆分为 `cpu21_riscv_redirect_int_bpb`。为便于单独仿真与调试,各功能单元已拆分为
独立文件,顶层的 ALU、BPB 也已移出,具体见文末“文件与模块划分”。 独立文件,顶层的 ALU、BPB 也已移出,具体见文末“文件与模块划分”。
## 目录结构
```text
risc-v/
├─ rtl/ 处理器 RTL(13 个文件,cpu21_riscv_redirect_int_bpb.v 为顶层)
├─ board/ 上板(Nexys4DDR):板级顶层、引脚约束、片内 ROM 初始化
│ ├─ cpu21_board_top.v 板级顶层(时钟分频 + 片内 ROM/RAM + 引脚)
│ ├─ cpu21_board_top.xdc 引脚约束与分频时钟约束
│ ├─ cpu21_board_rom.vh 片内 ROM 内容(由 tools/hex2vh.py 生成,上板用)
│ └─ cpu21_board_rom_fast.vh 短延时的 ROM 内容(仅仿真,-d BOARD_ROM_FAST 选择)
├─ testbench/ 仿真
│ ├─ tb_cpu21_riscv_redirect_int_bpb.v 中断/分支预测测试平台
│ ├─ tb_no_intr.v 无中断测试平台(指令集测试用)
│ ├─ tb_board_top.v 板级顶层仿真
│ ├─ rom/ 仿真实际加载的 ROM 镜像(.hex)
│ └─ programs/ 汇编源程序、期望值、课程原始资产(RARS)
├─ tools/ rv_asm.py(迷你汇编器)、hex2vh.py(hex → ROM 初始化)
├─ pipelined-cpu/ Vivado 工程(本地目录,不入库)
└─ cpu21_riscv_redirect_int_bpb说明.md 本文档
```
约定:`rtl/` 只放可综合的处理器源码;板级/仿真相关的东西都在 `board/`、
`testbench/` 下;`testbench/rom/` 里的镜像是测试平台默认加载的那个版本
(已按 MMIO 写 LED 修改),`testbench/programs/` 保留课程原始程序与 RARS 输出。
## 已转换的功能 ## 已转换的功能
- 五级流水:IF、ID、EX、MEM、WB。 - 五级流水:IF、ID、EX、MEM、WB。
- EX/MEM 与 MEM/WB 前递,以及 load-use 一拍停顿。 - EX/MEM 与 MEM/WB 前递,以及 load-use 一拍停顿。
- 条件分支 `beq`、`bne`、`blt`(有符号小于)、`bltu`(无符号小于)在 EX 段判定,错误预测时清空 IF/ID、ID/EX 并重定向 PC。真值表中 BLT 的 `ALU_OP` 为 SLT,与实现一致。 - 条件分支 `beq`、`bne`、`blt`/`bge`(有符号)、`bltu`/`bgeu`(无符号)在 EX 段判定,错误预测时清空 IF/ID、ID/EX 并重定向 PC。真值表中 BLT 的 `ALU_OP` 为 SLT,与实现一致。
- `jal`、`jalr` 在 EX 段重定向,返回地址写回 `rd`。 - `jal`、`jalr` 在 EX 段重定向,返回地址写回 `rd`。
- 8 项全相联 BPB:标签 `PC[11:2]`,每项包含 valid、目标 PC、2 位饱和计数器和 3 位年龄字段。BPB 只由条件分支更新,避免 `jal`、`jalr` 占用不会被查询的表项;未命中时优先使用无效项,否则替换年龄最大的项。 - 8 项全相联 BPB:标签 `PC[11:2]`,每项包含 valid、目标 PC、2 位饱和计数器和 3 位年龄字段。BPB 只由条件分支更新,避免 `jal`、`jalr` 占用不会被查询的表项;未命中时优先使用无效项,否则替换年龄最大的项。
- 三路中断输入 `irq_i[2:0]`:两级同步、上升沿挂起、IRQ3 > IRQ2 > IRQ1 优先级,仅在 `ustatus[0]`(MIE)允许时响应。 - 三路中断输入 `irq_i[2:0]`:两级同步、上升沿挂起、IRQ3 > IRQ2 > IRQ1 优先级,仅在 `ustatus[0]`(MIE)允许时响应。
- 支持嵌套中断:保存 EPC、`ustatus` 和当前优先级;`URET` 使用当前 `uepc` 返回,并从硬件栈恢复外层上下文。三个入口按照原电路的 `INTpc` 选择:IRQ1=`0x30ac`、IRQ2=`0x31e4`、IRQ3=`0x3310`。`0x22` 是 `ecall` 的服务号 34,不是中断向量。 - 支持嵌套中断:保存 EPC、`ustatus` 和当前优先级;`URET` 使用当前 `uepc` 返回,并从硬件栈恢复外层上下文。三个入口按照原电路的 `INTpc` 选择:IRQ1=`0x30ac`、IRQ2=`0x31e4`、IRQ3=`0x3310`。`0x22` 是 `ecall` 的服务号 34,不是中断向量。
- CSRRSI、CSRRCI,以及 CSRRW:支持 CSR 地址 `0x004`(`ustatus`)和 `0x041`(`uepc`)。立即数为 `IR[19:15]` 的零扩展值,并按 RISC-V 规则把旧 CSR 值写回 `rd`;这覆盖任务书中的 `csrrsi s6,0x41,0` 和 `csrrw zero,0x41,s6` 用法。 - CSRRSI、CSRRCI,以及 CSRRW:支持 CSR 地址 `0x004`(`ustatus`)和 `0x041`(`uepc`)。立即数为 `IR[19:15]` 的零扩展值,并按 RISC-V 规则把旧 CSR 值写回 `rd`;这覆盖任务书中的 `csrrsi s6,0x41,0` 和 `csrrw zero,0x41,s6` 用法。
- `ecall`:`a7 == 34` 时把 `a0` 锁存到 `led_data_o`;否则置 `halted_o`,直到 `go_i` 为高。 - `ecall`:停机和调试用。`a7 == 34` 是历史遗留的“显示”调用,现已保留为无操作;其余取值(例如程序结尾惯用的 `a7 = 10`)置 `halted_o`,直到 `go_i` 为高。LED 显示改由 MMIO 存储完成(见下文“MMIO 与外设”)。
- `sw` 和 `sb`:输出 `data_wstrb_o`,支持按字节写入。 - `sw` 和 `sb`:输出 `data_wstrb_o`,支持按字节写入。
- `lui`:加载高位立即数,`rd = imm[31:12] << 12`(按需求补充的扩展指令,标准 opcode `0x37`)。
- `sltiu`:I 型 `funct3=011`,立即数符号扩展后按无符号与 rs1 比较。
- M 扩展乘法(`funct7=0000001`):`mul`(乘积低 32 位)、`mulh`(有符号×有符号高 32 位)、`mulhsu`(有符号×无符号高 32 位)、`mulhu`(无符号×无符号高 32 位)。
## 指令编码 ## 指令编码
表中的 `opcode` 是控制器使用的五位字段 `IR[6:2]`,不是包含最低两位的 7 位原始 opcode。 表中的 `opcode` 是控制器使用的五位字段 `IR[6:2]`,不是包含最低两位的 7 位原始 opcode。
因此它与标准 RISC-V 指令的低 7 位 opcode 相差右移两位。 因此它与标准 RISC-V 指令的低 7 位 opcode 相差右移两位。
| 指令类别 | opcode | 说明 | | 指令类别 | opcode | 说明 |
| ------------------------------ | -----: | --------------------------------------- | | ----------------------------------------------------------- | -------: | -------------------------------------------------------------------------------- |
| R 型 | `0x0c` | add/sub/and/or/xor/slt/sltu/sll/srl/sra | | R 型 | `0x0c` | add/sub/and/or/xor/slt/sltu/sll/srl/sra |
| I 型 ALU | `0x04` | addi/andi/ori/xori/slti/slli/srli/srai | | M 扩展(R 型) | `0x0c` | `funct7=0000001`:mul/mulh/mulhsu/mulhu(另有课程约定的 remu,`funct3=111`) |
| `lw` | `0x00` | `funct3=010` | | I 型 ALU | `0x04` | addi/andi/ori/xori/slti/sltiu/slli/srli/srai |
| `sw` / `sb` | `0x08` | `funct3=010` / `000` | | `lui`(扩展) | `0x0d` | 加载高位立即数,`rd = imm[31:12] << 12` |
| `beq` / `bne` / `blt` / `bltu` | `0x18` | `funct3=000` / `001` / `100` / `110` | | `lw` | `0x00` | `funct3=010` |
| `jal` | `0x1b` | J 型立即数 | | `sw` / `sb` | `0x08` | `funct3=010` / `000` |
| `jalr` | `0x19` | `funct3=000` | | `beq` / `bne` / `blt` / `bge` / `bltu` / `bgeu` | `0x18` | `funct3=000` / `001` / `100` / `101` / `110` / `111` |
| 系统类 | `0x1c` | `ecall`、`URET`、CSRRSI、CSRRCI | | `jal` | `0x1b` | J 型立即数 |
| `jalr` | `0x19` | `funct3=000` |
| 系统类 | `0x1c` | `ecall`、`URET`、CSRRSI、CSRRCI |
`ecall` 和 `URET` 都是 `opcode=0x1c、funct3=0`,由 `IR[21]` 区分: `ecall` 和 `URET` 都是 `opcode=0x1c、funct3=0`,由 `IR[21]` 区分:
`IR[21]=0` 是 `ecall`,`IR[21]=1` 是 `URET`。这一点来自原控制器中的 `IR21` 分支逻辑。 `IR[21]=0` 是 `ecall`,`IR[21]=1` 是 `URET`。这一点来自原控制器中的 `IR21` 分支逻辑。
## MMIO 与外设
外设寄存器映射在数据存储器地址空间,由 `rtl/cpu21_mmio.v` 在 MEM 级译码,
顶层参数 `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`)都算命中,
具体修改哪几个字节由 `data_wstrb_o` 决定。`data_addr_o` 在命中 MMIO 时不会
访问外部 RAM;读数据由 `cpu21_mmio` 的输出在顶层与 `data_rdata_i` 做选择。
典型用法(LED 写 `0x12345678`,可用 `tools/rv_asm.py` 汇编):
```asm
lui t0, 0x1 # t0 = LED_ADDR = 0x00001000
lui t1, 0x12345
addi t1, t1, 0x678 # t1 = 0x12345678
sw t1, 0(t0) # MMIO 写 LED -> LED 输出 12345678
lw a0, 0(t0) # MMIO 读回
```
`testbench/programs/mmio_test.asm`(期望值见 `mmio_test.expected`)覆盖了
全字写、读回、字节写三种情况。加入新外设(拨码开关、UART 等)时只需在
`rtl/cpu21_mmio.v` 里扩展地址译码,处理器流水线无需改动。
## 顶层端口约定 ## 顶层端口约定
- `instr_i`:当前 `instr_addr_o` 对应的 32 位指令。原 Logisim 中由 ROM 提供。 - `instr_i`:当前 `instr_addr_o` 对应的 32 位指令。原 Logisim 中由 ROM 提供。
@@ -58,24 +116,145 @@
5. `bpb_predict_hit_o`、`bpb_predict_taken_o`、`bpb_mispredict_o` 和 `irq_*_o` 是观测端口,不参与外部控制,可以直接接逻辑分析仪或测试平台。 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 时计入两个气泡。 6. `cycle_count_o` 在 `halted_o` 为高时暂停;load-use 每次计入一个气泡,重定向或中断清空 IF/ID 与当前 IF 时计入两个气泡。
原 `.circ` 文件中的 ROM、MIPS RAM、按钮、LED 和调试显示器属于 Logisim 外围,不直接搬入 RTL;其功能分别由指令接口、数据接口、`go_i`、`led_*_o` 和调试端口替代。 原 `.circ` 文件中的 ROM、MIPS RAM、按钮、LED 和调试显示器属于 Logisim 外围,不直接搬入 RTL;其功能分别由指令接口、数据接口、`go_i`、MMIO(`led_data_o`/`led_valid_o`)和调试端口替代。
## 文件与模块划分 ## 文件与模块划分
| 文件 | 模块 | 职责 | | 文件 | 模块 | 职责 |
| -------------------------------- | ------------------------------ | --------------------------------------------------------------- | | ----------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------- |
| `cpu21_riscv_redirect_int_bpb.v` | `cpu21_riscv_redirect_int_bpb` | 顶层:五级流水寄存器、子模块例化、PC/IF-ID/ID-EX 控制、对外输出 | | `rtl/cpu21_riscv_redirect_int_bpb.v` | `cpu21_riscv_redirect_int_bpb` | 顶层:五级流水寄存器、子模块例化、PC/IF-ID/ID-EX 控制、对外输出 |
| `cpu21_riscv_alu.v` | `cpu21_riscv_alu` | 组合 ALU(`result2` 用于 MUL 高位 / DIVU 余数) | | `rtl/cpu21_riscv_alu.v` | `cpu21_riscv_alu` | 组合 ALU(`result2` 用于 MUL 高位 / DIVU 余数) |
| `cpu21_bpb_8.v` | `cpu21_bpb_8` | 8 项全相联分支目标缓冲(2 位饱和计数器 + 近似 LRU) | | `rtl/cpu21_bpb_8.v` | `cpu21_bpb_8` | 8 项全相联分支目标缓冲(2 位饱和计数器 + 近似 LRU) |
| `cpu21_riscv_decoder.v` | `cpu21_riscv_decoder` | ID 级组合译码器(操作码 `IR[6:2]`) | | `rtl/cpu21_riscv_decoder.v` | `cpu21_riscv_decoder` | ID 级组合译码器(操作码`IR[6:2]`) |
| `cpu21_riscv_regfile.v` | `cpu21_riscv_regfile` | 寄存器堆,`x0` 恒为 0,同周期 WB 写优先旁路 | | `rtl/cpu21_riscv_regfile.v` | `cpu21_riscv_regfile` | 寄存器堆,`x0` 恒为 0,同周期 WB 写优先旁路 |
| `cpu21_riscv_forward_unit.v` | `cpu21_riscv_forward_unit` | EX 级前递网络(EX/MEM 优先于 MEM/WB) | | `rtl/cpu21_riscv_forward_unit.v` | `cpu21_riscv_forward_unit` | EX 级前递网络(EX/MEM 优先于 MEM/WB) |
| `cpu21_riscv_store_unit.v` | `cpu21_riscv_store_unit` | EX 级存储数据对齐:`sw`/`sb` 的写数据与字节写掩码 | | `rtl/cpu21_riscv_store_unit.v` | `cpu21_riscv_store_unit` | EX 级存储数据对齐:`sw`/`sb` 的写数据与字节写掩码 |
| `cpu21_riscv_branch_unit.v` | `cpu21_riscv_branch_unit` | EX 级控制流裁决:分支方向/目标、JAL/JALR 重定向 | | `rtl/cpu21_riscv_branch_unit.v` | `cpu21_riscv_branch_unit` | EX 级控制流裁决:分支方向/目标、JAL/JALR 重定向 |
| `cpu21_riscv_hazard_unit.v` | `cpu21_riscv_hazard_unit` | load-use 冒险、停顿(stall)与冲刷(flush)判定 | | `rtl/cpu21_riscv_hazard_unit.v` | `cpu21_riscv_hazard_unit` | load-use 冒险、停顿(stall)与冲刷(flush)判定 |
| `cpu21_riscv_irq_ctrl.v` | `cpu21_riscv_irq_ctrl` | 中断采样与优先级、`ustatus`/`uepc` CSR、嵌套返回栈 | | `rtl/cpu21_riscv_irq_ctrl.v` | `cpu21_riscv_irq_ctrl` | 中断采样与优先级、`ustatus`/`uepc` CSR、嵌套返回栈 |
| `cpu21_riscv_perf_counters.v` | `cpu21_riscv_perf_counters` | 周期/停顿/气泡/分支/预测成功率等统计计数器 | | `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 程序 拆分只调整了模块边界与文件组织,端口、时序与行为完全等价:使用同一 ROM 程序
在 xsim 下对拆分前后逐周期对比,波形输出与最终统计(周期、停顿、气泡、分支 在 xsim 下对拆分前后逐周期对比,波形输出与最终统计(周期、停顿、气泡、分支
跳转、预测成败)完全一致。 跳转、预测成败)完全一致。
## 上板(Nexys4DDR + Vivado)
处理器顶层 `cpu21_riscv_redirect_int_bpb` 有 775 位调试端口(各级 PC/IR、计数器、
BPB/中断观测值)。如果直接把它设为实现顶层,这些端口会全部变成引脚,导致
`[Place 30-415] IO Placement failed due to overutilization`(占用 775 个 IO,
而 `xc7a100tcsg324-1` 只有 210 个)。因此上板时用 `cpu21_board_top` 作为顶层,
只引出板上真正需要的 23 个引脚,其余调试端口不连接。
### 引脚分配(`cpu21_board_top.xdc`)
| 端口 | 引脚 | 板上元件 | 说明 |
| --------------- | ----------- | ------------------------- | -------------------------------------------------- |
| `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 |
### 时钟:为什么要把 100 MHz 分频
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` 只用于分频器本身:
```verilog
cpu21_board_top #(.CLK_DIV(16)) u_board ( ... );
```
分频后的时钟在综合/实现时由 Vivado 自动插入 BUFG 走全局时钟网络(RTL 里不例化
`BUFG` 原语,这样在不认识 Xilinx 原语的仿真器里也能直接仿真)。XDC 中必须用
`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 }]
```
改分频比时 RTL 的 `CLK_DIV` 与 XDC 的 `-divide_by` 要同时改。
### 片内 ROM / RAM
- ROM:`ROM_WORDS = 1024` 字(4 KB),组合读,取指地址 `instr_addr[11:2]`;
内容由 `` `include "cpu21_board_rom.vh" `` 给出(与 `cpu21_board_top.v` 同目录,
文件里是 1024 条
`rom[i] = 32'h…;`,放在 `initial` 里,等价于 `$readmemh`,且不依赖文件路径)。
- RAM:`RAM_WORDS = 1024` 字(4 KB),字节写使能 + 组合读,只响应
`data_addr < 4 KB`,因此 MMIO 地址(默认 `0x1000`)不会落进 RAM。
- 两个 `initial` 块都用 NOP(`32'h0000_0013`)或 0 预填充,ROM 未使用区域是 NOP,
这样即使程序跑飞也不会执行到随机指令而乱写寄存器。
### 演示程序的生成与仿真
```powershell
# 1) 汇编 + 生成 ROM 初始化文件(在仓库根目录执行)
python tools/rv_asm.py testbench/programs/board_demo.asm _tmp/board_demo.hex
python tools/hex2vh.py _tmp/board_demo.hex board/cpu21_board_rom.vh 1024
# 2) 上板顶层的 RTL 仿真(默认程序延时约 20 ms,只需确认第一次写入 LED=0001)
xvlog rtl/*.v board/cpu21_board_top.v testbench/tb_board_top.v
xelab -top tb_board_top -snapshot board_sim
xsim board_sim -tclbatch run_quit.tcl
```
`board_demo.asm` 是一段 16 步跑马灯(`0001 → 0002 → … → 8000 → 0001`),
每步之间用软件循环延时约 20 ms(6.25 MHz 下)。仓库里同时提供了两份 ROM 初始化
文件,由 `BOARD_ROM_FAST` 宏选择(综合流程不会定义该宏):
| 文件 | 用途 | 每步耗时 |
| --------------------------------- | ------------------------------ | -------- |
| `board/cpu21_board_rom.vh` | 综合/上板(默认) | 约 20 ms |
| `board/cpu21_board_rom_fast.vh` | 仅仿真:一次跑完整个跑马灯序列 | 4.8 µs |
```powershell
# 快速版仿真:编译时加上 -d BOARD_ROM_FAST(注意 xvlog/elaborate 都要带)
xvlog -d BOARD_ROM_FAST rtl/*.v board/cpu21_board_top.v testbench/tb_board_top.v
xelab -top tb_board_top -snapshot board_sim_fast -d BOARD_ROM_FAST
```
两份文件都由同样的两步生成,只有延时循环的初值不同(把 `board_demo.asm` 里的
`lui t2, 0xf` 换成 `addi t2, zero, 8` 再照上面两步走一遍,输出到
`board/cpu21_board_rom_fast.vh`)。
### Vivado 工程设置与实现结果
Vivado 工程 `pipelined-cpu/pipelined-cpu.xpr` 被 `.gitignore` 排除,需要手动设置:
1. `sources_1` 顶层设为 `cpu21_board_top`(纯 RTL 仿真时也可切回
`cpu21_riscv_redirect_int_bpb`);
2. 加入 `board/cpu21_board_top.v`、`board/cpu21_board_top.xdc`,
`board/cpu21_board_rom.vh` 的文件类型设成 `Verilog Header`(不能当普通源码,
否则会被当成顶层候选);
3. 三个 testbench(`testbench/tb_cpu21_riscv_redirect_int_bpb.v`、`tb_no_intr.v`、
`tb_board_top.v`)的 `USED_IN_SYNTHESIS` / `USED_IN_IMPLEMENTATION` 设为 `0`,
否则综合可能把 testbench 当顶层,报 `[Place 30-494] The design is empty`。
`xc7a100tcsg324-1` 上的实现结果(`CLK_DIV=16`):
| 项目 | 结果 |
| -------- | --------------------------------------------------------------------------- |
| 综合 | `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` |
上板操作:用 Vivado 打开工程 → `Open Hardware Manager` → `Program Device`,选上述
`.bit`。复位后跑马灯立即从 `0001` 开始逐位左移(演示程序不执行 `ecall`,所以不用
按 `go`;`go` 只对执行了 `ecall` 停机、等待继续运行的程序有意义),LED16_B 每次写入时点亮。
View File
+62
View File
@@ -0,0 +1,62 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// MMIO:把外设寄存器映射到数据存储器地址空间。
//
// 目前实现 LED 数据寄存器(LED_ADDR 默认 0x0000_1000,位于 4 KB 数据 RAM 之外):
// * 写:sw / sb 命中该地址(按字对齐,即同一 32 位寄存器内的任意字节偏移
// 都算命中)时更新 LED 寄存器,led_valid_o 拉高一拍;sb 依据 wstrb_i 只
// 修改对应的字节通道(与 RAM 的字节写语义一致)。
// * 读:lw 命中该地址时 rdata_hit_o 拉高,顶层据此选择本模块的 rdata_o
// 而不是外部 data_rdata_i,从而支持把 LED 值读回。
//
// 以后要增加外设(拨码开关、UART 等)时只需在这里扩展地址译码,
// 处理器核心与流水线时序不需要改动。
// -----------------------------------------------------------------------------
module cpu21_mmio #(
parameter [31:0] LED_ADDR = 32'h0000_1000
) (
input wire clk,
input wire reset,
// MEM 级的访存请求(来自 EX/MEM 流水寄存器)
input wire mem_valid_i,
input wire mem_write_i,
input wire mem_read_i,
input wire [31:0] addr_i,
input wire [31:0] wdata_i,
input wire [ 3:0] wstrb_i,
// MMIO 读回
output wire [31:0] rdata_o,
output wire rdata_hit_o,
// LED 外设
output reg [31:0] led_data_o,
output reg led_valid_o
);
// 地址译码按“字”对齐:同一 32 位寄存器内的任意字节偏移都算命中,
// 具体修改哪几个字节由 wstrb_i 决定(sw 全字、sb 单字节)。
wire led_wr = mem_valid_i && mem_write_i && (addr_i[31:2] == LED_ADDR[31:2]);
wire led_rd = mem_valid_i && mem_read_i && (addr_i[31:2] == LED_ADDR[31:2]);
assign rdata_hit_o = led_rd;
assign rdata_o = led_data_o;
always @(posedge clk or posedge reset) begin
if (reset) begin
led_data_o <= 32'b0;
led_valid_o <= 1'b0;
end else begin
// led_valid_o 只拉高一拍,作为“本周期有 LED 写入”的指示。
led_valid_o <= 1'b0;
if (led_wr) begin
if (wstrb_i[0]) led_data_o[7:0] <= wdata_i[7:0];
if (wstrb_i[1]) led_data_o[15:8] <= wdata_i[15:8];
if (wstrb_i[2]) led_data_o[23:16] <= wdata_i[23:16];
if (wstrb_i[3]) led_data_o[31:24] <= wdata_i[31:24];
led_valid_o <= 1'b1;
end
end
end
endmodule
`default_nettype wire
+24 -13
View File
@@ -4,7 +4,8 @@
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// ALU:纯组合运算单元。 // ALU:纯组合运算单元。
// //
// result 为主结果;result2 为第二结果,用于 MUL 的高 32 位与 DIVU 的余数。 // result 为主结果;result2 为第二结果(乘法的无符号高 32 位 / DIVU 的余数),
// 由译码器用 wb_sel=3 选取,用于无符号高位乘法 mulhu 与课程约定的 remu。
// 除法做了除零保护:b==0 时商为 32'hffff_ffff、余数为被除数。 // 除法做了除零保护:b==0 时商为 32'hffff_ffff、余数为被除数。
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
module cpu21_riscv_alu ( module cpu21_riscv_alu (
@@ -27,15 +28,22 @@ module cpu21_riscv_alu (
localparam ALU_NOR = 4'd10; localparam ALU_NOR = 4'd10;
localparam ALU_SLT = 4'd11; localparam ALU_SLT = 4'd11;
localparam ALU_SLTU = 4'd12; localparam ALU_SLTU = 4'd12;
reg [63:0] mult_result; 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 always @* begin
mult_result = a * b; mult_result = a * b;
mult_ss = $signed(a) * $signed(b);
mult_su = $signed(a) * $signed({32'b0, b});
result2 = 32'b0; result2 = 32'b0;
case (op) case (op)
ALU_SLL: result = a << b[4:0]; ALU_SLL: result = a << b[4:0];
ALU_SRA: result = $signed(a) >>> b[4:0]; ALU_SRA: result = $signed(a) >>> b[4:0];
ALU_SRL: result = a >> b[4:0]; ALU_SRL: result = a >> b[4:0];
ALU_MUL: begin ALU_MUL: begin
result = mult_result[31:0]; result = mult_result[31:0];
result2 = mult_result[63:32]; result2 = mult_result[63:32];
@@ -44,15 +52,18 @@ module cpu21_riscv_alu (
result = (b == 32'b0) ? 32'hffff_ffff : a / b; result = (b == 32'b0) ? 32'hffff_ffff : a / b;
result2 = (b == 32'b0) ? a : a % b; result2 = (b == 32'b0) ? a : a % b;
end end
ALU_ADD: result = a + b; ALU_ADD: result = a + b;
ALU_SUB: result = a - b; ALU_SUB: result = a - b;
ALU_AND: result = a & b; ALU_AND: result = a & b;
ALU_OR: result = a | b; ALU_OR: result = a | b;
ALU_XOR: result = a ^ b; ALU_XOR: result = a ^ b;
ALU_NOR: result = ~(a | b); ALU_NOR: result = ~(a | b);
ALU_SLT: result = ($signed(a) < $signed(b)) ? 32'd1 : 32'd0; 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_SLTU: result = (a < b) ? 32'd1 : 32'd0;
default: result = 32'b0; ALU_PASSB: result = b;
default: result = 32'b0;
endcase endcase
end end
endmodule endmodule
@@ -18,7 +18,9 @@ module cpu21_riscv_branch_unit (
input wire beq_i, input wire beq_i,
input wire bne_i, input wire bne_i,
input wire blt_i, input wire blt_i,
input wire bge_i,
input wire bltu_i, input wire bltu_i,
input wire bgeu_i,
input wire jal_i, input wire jal_i,
input wire jalr_i, input wire jalr_i,
// IF 级携带过来的预测信息 // IF 级携带过来的预测信息
@@ -31,15 +33,20 @@ module cpu21_riscv_branch_unit (
output wire redirect_valid_o, output wire redirect_valid_o,
output wire [31:0] redirect_pc_o output wire [31:0] redirect_pc_o
); );
// 条件分支裁决:beq 相等、bne 不等、blt 有符号小于、bltu 无符号小于。 // 比较结果预先算好,避免表达式过长被自动换行拆散。
wire lt_signed = $signed(src1_i) < $signed(src2_i);
wire ge_signed = $signed(src1_i) >= $signed(src2_i);
wire lt_unsigned = src1_i < src2_i;
wire ge_unsigned = src1_i >= src2_i;
// 条件分支裁决:beq 相等、bne 不等、blt/bge 有符号比较、bltu/bgeu 无符号比较。
assign branch_taken_o = branch_i && assign branch_taken_o = branch_i &&
((beq_i && (src1_i == src2_i)) || ((beq_i && (src1_i == src2_i)) ||
(bne_i && (src1_i != src2_i)) || (bne_i && (src1_i != src2_i)) ||
(blt_i && ($signed( (blt_i && lt_signed) ||
src1_i (bge_i && ge_signed) ||
) < $signed( (bltu_i && lt_unsigned) ||
src2_i (bgeu_i && ge_unsigned));
))) || (bltu_i && (src1_i < src2_i)));
// 跳转目标:jalr 为 (rs1+imm) 且最低位清零;jal 为 PC+imm。 // 跳转目标:jalr 为 (rs1+imm) 且最低位清零;jal 为 PC+imm。
assign target_o = jalr_i ? ((src1_i + imm_i) & 32'hffff_fffe) : (pc_i + imm_i); assign target_o = jalr_i ? ((src1_i + imm_i) & 32'hffff_fffe) : (pc_i + imm_i);
@@ -29,7 +29,9 @@ module cpu21_riscv_decoder (
output reg beq_o, output reg beq_o,
output reg bne_o, output reg bne_o,
output reg blt_o, output reg blt_o,
output reg bge_o,
output reg bltu_o, output reg bltu_o,
output reg bgeu_o,
output reg jal_o, output reg jal_o,
output reg jalr_o, output reg jalr_o,
output reg ecall_o, output reg ecall_o,
@@ -47,6 +49,8 @@ module cpu21_riscv_decoder (
localparam OP_BRANCH = 5'h18; localparam OP_BRANCH = 5'h18;
localparam OP_JAL = 5'h1b; localparam OP_JAL = 5'h1b;
localparam OP_SYS = 5'h1c; localparam OP_SYS = 5'h1c;
// LUI 为扩展指令(标准 RISC-V opcode 0x37 -> IR[6:2]=0x0d)。
localparam OP_LUI = 5'h0d;
localparam ALU_SLL = 4'd0; localparam ALU_SLL = 4'd0;
localparam ALU_SRA = 4'd1; localparam ALU_SRA = 4'd1;
@@ -60,6 +64,9 @@ module cpu21_riscv_decoder (
localparam ALU_XOR = 4'd9; localparam ALU_XOR = 4'd9;
localparam ALU_SLT = 4'd11; localparam ALU_SLT = 4'd11;
localparam ALU_SLTU = 4'd12; localparam ALU_SLTU = 4'd12;
localparam ALU_MULH = 4'd13;
localparam ALU_MULHSU = 4'd14;
localparam ALU_PASSB = 4'd15; // LUI:直接输出 B 端(立即数)
wire [4:0] opcode = ir_i[6:2]; wire [4:0] opcode = ir_i[6:2];
wire [2:0] funct3 = ir_i[14:12]; wire [2:0] funct3 = ir_i[14:12];
@@ -87,7 +94,9 @@ module cpu21_riscv_decoder (
beq_o = 1'b0; beq_o = 1'b0;
bne_o = 1'b0; bne_o = 1'b0;
blt_o = 1'b0; blt_o = 1'b0;
bge_o = 1'b0;
bltu_o = 1'b0; bltu_o = 1'b0;
bgeu_o = 1'b0;
jal_o = 1'b0; jal_o = 1'b0;
jalr_o = 1'b0; jalr_o = 1'b0;
ecall_o = 1'b0; ecall_o = 1'b0;
@@ -102,13 +111,23 @@ module cpu21_riscv_decoder (
uses_rs1_o = 1'b1; uses_rs1_o = 1'b1;
uses_rs2_o = 1'b1; uses_rs2_o = 1'b1;
reg_write_o = 1'b1; reg_write_o = 1'b1;
// 电路中额外的 REMU 控制对应标准 R 型的 funct7=1/funct3=111 // M 扩展(funct7=0000001):mul/mulh/mulhsu/mulhu,外加课程约定的 remu
// 形式;此时 ALU 的 result2 即为余数。 // (funct3=111 形式)。mulhu 与 remu 都取 ALU 的 result2(wb_sel=3)。
if ((funct7 == 7'b0000001) && (funct3 == 3'b111)) begin if (funct7 == 7'b0000001) begin
alu_op_o = ALU_DIVU; case (funct3)
wb_sel_o = 3'd3; 3'b000: alu_op_o = ALU_MUL; // mul:无符号乘积低 32 位
end else if ((funct7 == 7'b0000001) && (funct3 == 3'b000)) begin 3'b001: alu_op_o = ALU_MULH; // mulh:有符号乘积高 32 位
alu_op_o = ALU_MUL; 3'b010: alu_op_o = ALU_MULHSU; // mulhsu:有符号 x 无符号高 32 位
3'b011: begin // mulhu:无符号乘积高 32 位
alu_op_o = ALU_MUL;
wb_sel_o = 3'd3;
end
3'b111: begin // remu(课程约定):取余数
alu_op_o = ALU_DIVU;
wb_sel_o = 3'd3;
end
default: reg_write_o = 1'b0;
endcase
end else begin end else begin
// 标准 R 型 funct3 译码;add/sub 与 sra/srl 由 funct7[5] 区分。 // 标准 R 型 funct3 译码;add/sub 与 sra/srl 由 funct7[5] 区分。
case (funct3) case (funct3)
@@ -125,7 +144,7 @@ module cpu21_riscv_decoder (
end end
end end
// I 型运算(addi/slli/slti/xori/srai/srli/ori/andi)。 // I 型运算(addi/slli/slti/sltiu/xori/srai/srli/ori/andi)。
OP_I: begin OP_I: begin
uses_rs1_o = 1'b1; uses_rs1_o = 1'b1;
alu_src_o = 1'b1; alu_src_o = 1'b1;
@@ -134,6 +153,7 @@ module cpu21_riscv_decoder (
3'b000: alu_op_o = ALU_ADD; // addi:立即数加 3'b000: alu_op_o = ALU_ADD; // addi:立即数加
3'b001: alu_op_o = ALU_SLL; // slli:立即数逻辑左移 3'b001: alu_op_o = ALU_SLL; // slli:立即数逻辑左移
3'b010: alu_op_o = ALU_SLT; // slti:有符号小于置 1 3'b010: alu_op_o = ALU_SLT; // slti:有符号小于置 1
3'b011: alu_op_o = ALU_SLTU; // sltiu:无符号小于置 1(立即数符号扩展)
3'b100: alu_op_o = ALU_XOR; // xori:立即数异或 3'b100: alu_op_o = ALU_XOR; // xori:立即数异或
3'b101: 3'b101:
alu_op_o = (funct7[5] ? ALU_SRA : ALU_SRL); // srai/srli:立即数算术/逻辑右移 alu_op_o = (funct7[5] ? ALU_SRA : ALU_SRL); // srai/srli:立即数算术/逻辑右移
@@ -143,6 +163,16 @@ module cpu21_riscv_decoder (
endcase endcase
end end
// 加载高位立即数 lui:rd = imm[31:12] << 12。
// 不读寄存器;用 ALU_SRC 选立即数,ALU 直通 B 端输出。
OP_LUI: begin
reg_write_o = 1'b1;
alu_src_o = 1'b1;
alu_op_o = ALU_PASSB;
imm_o = {ir_i[31:12], 12'b0};
wb_sel_o = 3'd0;
end
// 加载指令,当前仅支持 lw(funct3=010)。 // 加载指令,当前仅支持 lw(funct3=010)。
OP_LOAD: begin OP_LOAD: begin
if (funct3 == 3'b010) begin if (funct3 == 3'b010) begin
@@ -168,19 +198,22 @@ module cpu21_riscv_decoder (
end end
end end
// 条件分支:beq(000)/bne(001)/blt(100,有符号小于)/bltu(110,无符号小于)。 // 条件分支:beq(000)/bne(001)/blt(100)/bge(101)/bltu(110)/bgeu(111)。
OP_BRANCH: begin OP_BRANCH: begin
if ((funct3 == 3'b000) || (funct3 == 3'b001) || if ((funct3 == 3'b000) || (funct3 == 3'b001) || (funct3 == 3'b100) ||
(funct3 == 3'b100) || (funct3 == 3'b110)) begin (funct3 == 3'b101) || (funct3 == 3'b110) || (funct3 == 3'b111)) begin
uses_rs1_o = 1'b1; uses_rs1_o = 1'b1;
uses_rs2_o = 1'b1; uses_rs2_o = 1'b1;
branch_o = 1'b1; branch_o = 1'b1;
beq_o = (funct3 == 3'b000); beq_o = (funct3 == 3'b000);
bne_o = (funct3 == 3'b001); bne_o = (funct3 == 3'b001);
blt_o = (funct3 == 3'b100); blt_o = (funct3 == 3'b100);
bge_o = (funct3 == 3'b101);
bltu_o = (funct3 == 3'b110); bltu_o = (funct3 == 3'b110);
// 真值表:beq/bne 用减法比较,blt 用 ALU_SLT,bltu 用 ALU_SLTU。 bgeu_o = (funct3 == 3'b111);
alu_op_o = blt_o ? ALU_SLT : (bltu_o ? ALU_SLTU : ALU_SUB); // 有符号比较用 ALU_SLT、无符号比较用 ALU_SLTU,相等/不等用减法;
// 分支是否成立由 EX 级的分支裁决单元决定,这里只与真值表保持一致。
alu_op_o = (blt_o || bge_o) ? ALU_SLT : ((bltu_o || bgeu_o) ? ALU_SLTU : ALU_SUB);
imm_o = {{19{ir_i[31]}}, ir_i[31], ir_i[7], ir_i[30:25], ir_i[11:8], 1'b0}; imm_o = {{19{ir_i[31]}}, ir_i[31], ir_i[7], ir_i[30:25], ir_i[11:8], 1'b0};
end end
end end
@@ -22,13 +22,17 @@
// cpu21_riscv_hazard_unit.v load-use 冒险与冲刷/停顿判定 // cpu21_riscv_hazard_unit.v load-use 冒险与冲刷/停顿判定
// cpu21_riscv_irq_ctrl.v 中断控制、ustatus/uepc 与嵌套返回栈 // cpu21_riscv_irq_ctrl.v 中断控制、ustatus/uepc 与嵌套返回栈
// cpu21_riscv_perf_counters.v 性能计数器 // cpu21_riscv_perf_counters.v 性能计数器
// cpu21_mmio.v MMIO:LED 数据寄存器(可写、可读回)
// //
// 主要特性: // 主要特性:
// * EX 级前递(forwarding)与 load-use 冒险互锁; // * EX 级前递(forwarding)与 load-use 冒险互锁;
// * EX 级控制流裁决与流水线重定向; // * EX 级控制流裁决与流水线重定向;
// * 八表项全相联分支目标缓冲(BPB); // * 八表项全相联分支目标缓冲(BPB);
// * 三路采样的中断源,带优先级与可嵌套的 URET 返回; // * 三路采样的中断源,带优先级与可嵌套的 URET 返回;
// * 沿用原 ecall 约定:a7==34 时写出 LED 值,否则流水线等待 go_i。 // * LED 显示通过 MMIO 完成:向 LED_ADDR(默认 0x0000_1000)存储即写 LED,
// 也可以从该地址读回 LED 当前值;
// * ecall 仍用于停机:a7==34 的历史“显示”调用保留为无操作,其余(如 a7=10)
// 令流水线停机并等待 go_i。
// //
// 存储器接口约定: // 存储器接口约定:
// instr_addr_o 与 data_addr_o 均为字节地址。data_wstrb_o 是字节写掩码, // instr_addr_o 与 data_addr_o 均为字节地址。data_wstrb_o 是字节写掩码,
@@ -41,6 +45,8 @@ module cpu21_riscv_redirect_int_bpb #(
parameter IRQ2_VECTOR = 32'h0000_31e4, parameter IRQ2_VECTOR = 32'h0000_31e4,
parameter IRQ3_VECTOR = 32'h0000_3310, parameter IRQ3_VECTOR = 32'h0000_3310,
parameter IRQ_STACK_DEPTH = 4, parameter IRQ_STACK_DEPTH = 4,
// MMIO:LED 数据寄存器地址(默认 0x0000_1000,位于 4 KB 数据 RAM 之外)。
parameter [31:0] LED_ADDR = 32'h0000_1000,
// ustatus 复位值。详见 cpu21_riscv_irq_ctrl 中的说明。 // ustatus 复位值。详见 cpu21_riscv_irq_ctrl 中的说明。
parameter [31:0] USTATUS_INIT = 32'h0000_0001 parameter [31:0] USTATUS_INIT = 32'h0000_0001
) ( ) (
@@ -130,7 +136,9 @@ module cpu21_riscv_redirect_int_bpb #(
reg idex_beq_q; reg idex_beq_q;
reg idex_bne_q; reg idex_bne_q;
reg idex_blt_q; reg idex_blt_q;
reg idex_bge_q;
reg idex_bltu_q; reg idex_bltu_q;
reg idex_bgeu_q;
reg idex_jal_q; reg idex_jal_q;
reg idex_jalr_q; reg idex_jalr_q;
reg idex_ecall_q; reg idex_ecall_q;
@@ -169,9 +177,7 @@ module cpu21_riscv_redirect_int_bpb #(
reg memwb_mem_to_reg_q; reg memwb_mem_to_reg_q;
reg [2:0] memwb_wb_sel_q; reg [2:0] memwb_wb_sel_q;
// LED 输出与停机标志。 // 停机标志(LED 寄存器已移入 cpu21_mmio.v)。
reg [31:0] led_data_q;
reg led_valid_q;
reg halted_q; reg halted_q;
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@@ -182,7 +188,8 @@ module cpu21_riscv_redirect_int_bpb #(
wire [2:0] f_funct3 = instr_i[14:12]; wire [2:0] f_funct3 = instr_i[14:12];
wire f_is_branch = (f_opcode == OP_BRANCH) && wire f_is_branch = (f_opcode == OP_BRANCH) &&
((f_funct3 == 3'b000) || (f_funct3 == 3'b001) || ((f_funct3 == 3'b000) || (f_funct3 == 3'b001) ||
(f_funct3 == 3'b100) || (f_funct3 == 3'b110)); (f_funct3 == 3'b100) || (f_funct3 == 3'b101) ||
(f_funct3 == 3'b110) || (f_funct3 == 3'b111));
wire [31:0] bpb_predict_target; wire [31:0] bpb_predict_target;
wire bpb_predict_hit; wire bpb_predict_hit;
wire bpb_predict_taken; wire bpb_predict_taken;
@@ -239,7 +246,9 @@ module cpu21_riscv_redirect_int_bpb #(
wire d_beq; wire d_beq;
wire d_bne; wire d_bne;
wire d_blt; wire d_blt;
wire d_bge;
wire d_bltu; wire d_bltu;
wire d_bgeu;
wire d_jal; wire d_jal;
wire d_jalr; wire d_jalr;
wire d_ecall; wire d_ecall;
@@ -268,7 +277,9 @@ module cpu21_riscv_redirect_int_bpb #(
.beq_o (d_beq), .beq_o (d_beq),
.bne_o (d_bne), .bne_o (d_bne),
.blt_o (d_blt), .blt_o (d_blt),
.bge_o (d_bge),
.bltu_o (d_bltu), .bltu_o (d_bltu),
.bgeu_o (d_bgeu),
.jal_o (d_jal), .jal_o (d_jal),
.jalr_o (d_jalr), .jalr_o (d_jalr),
.ecall_o (d_ecall), .ecall_o (d_ecall),
@@ -377,7 +388,9 @@ module cpu21_riscv_redirect_int_bpb #(
.beq_i (idex_beq_q), .beq_i (idex_beq_q),
.bne_i (idex_bne_q), .bne_i (idex_bne_q),
.blt_i (idex_blt_q), .blt_i (idex_blt_q),
.bge_i (idex_bge_q),
.bltu_i (idex_bltu_q), .bltu_i (idex_bltu_q),
.bgeu_i (idex_bgeu_q),
.jal_i (idex_jal_q), .jal_i (idex_jal_q),
.jalr_i (idex_jalr_q), .jalr_i (idex_jalr_q),
.pred_taken_i (idex_pred_taken_q), .pred_taken_i (idex_pred_taken_q),
@@ -402,8 +415,35 @@ module cpu21_riscv_redirect_int_bpb #(
.store_wstrb_o(ex_store_wstrb) .store_wstrb_o(ex_store_wstrb)
); );
// ecall 约定:a7==34 时输出 LED;否则令处理器停机,等待 go_i 重新启动。 // -------------------------------------------------------------------------
wire ex_led_event = idex_valid_q && idex_ecall_q && (ex_src1 == 32'd34); // MMIO:LED 等外设映射到数据地址空间,在 MEM 级做地址译码。
// -------------------------------------------------------------------------
wire [31:0] mmio_rdata;
wire mmio_rdata_hit;
cpu21_mmio #(
.LED_ADDR(LED_ADDR)
) u_mmio (
.clk (clk),
.reset (reset),
.mem_valid_i(exmem_valid_q),
.mem_write_i(exmem_mem_write_q),
.mem_read_i (exmem_mem_to_reg_q),
.addr_i (exmem_alu_result_q),
.wdata_i (exmem_write_data_q),
.wstrb_i (exmem_wstrb_q),
.rdata_o (mmio_rdata),
.rdata_hit_o(mmio_rdata_hit),
.led_data_o (led_data_o),
.led_valid_o(led_valid_o)
);
// 访存读数据:命中 MMIO 时用外设数据,否则用外部存储器数据。
wire [31:0] mem_rdata = mmio_rdata_hit ? mmio_rdata : data_rdata_i;
// ecall 约定:LED 显示已改由 MMIO 存储完成(见上方的 cpu21_mmio 例化)。
// a7==34 的历史“显示”调用保留为无操作;其余(例如 a7=10)令处理器停机,
// 等待 go_i 重新启动。
wire ex_halt_event = idex_valid_q && idex_ecall_q && (ex_src1 != 32'd34); wire ex_halt_event = idex_valid_q && idex_ecall_q && (ex_src1 != 32'd34);
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@@ -486,9 +526,7 @@ module cpu21_riscv_redirect_int_bpb #(
assign data_wstrb_o = (exmem_valid_q && exmem_mem_write_q) ? exmem_wstrb_q : 4'b0; assign data_wstrb_o = (exmem_valid_q && exmem_mem_write_q) ? exmem_wstrb_q : 4'b0;
assign data_we_o = |data_wstrb_o; assign data_we_o = |data_wstrb_o;
// LED 输出与流水线各级 PC/指令回显(用于调试与波形观察)。 // LED 输出由 cpu21_mmio 驱动,这里只保留流水线各级 PC/指令回显。
assign led_data_o = led_data_q;
assign led_valid_o = led_valid_q;
assign if_pc_o = pc_q; assign if_pc_o = pc_q;
assign id_pc_o = ifid_valid_q ? ifid_pc_q : 32'b0; assign id_pc_o = ifid_valid_q ? ifid_pc_q : 32'b0;
assign ex_pc_o = idex_valid_q ? idex_pc_q : 32'b0; assign ex_pc_o = idex_valid_q ? idex_pc_q : 32'b0;
@@ -548,7 +586,9 @@ module cpu21_riscv_redirect_int_bpb #(
idex_beq_q <= 1'b0; idex_beq_q <= 1'b0;
idex_bne_q <= 1'b0; idex_bne_q <= 1'b0;
idex_blt_q <= 1'b0; idex_blt_q <= 1'b0;
idex_bge_q <= 1'b0;
idex_bltu_q <= 1'b0; idex_bltu_q <= 1'b0;
idex_bgeu_q <= 1'b0;
idex_jal_q <= 1'b0; idex_jal_q <= 1'b0;
idex_jalr_q <= 1'b0; idex_jalr_q <= 1'b0;
idex_ecall_q <= 1'b0; idex_ecall_q <= 1'b0;
@@ -585,16 +625,8 @@ module cpu21_riscv_redirect_int_bpb #(
memwb_mem_to_reg_q <= 1'b0; memwb_mem_to_reg_q <= 1'b0;
memwb_wb_sel_q <= 3'b0; memwb_wb_sel_q <= 3'b0;
led_data_q <= 32'b0;
led_valid_q <= 1'b0;
halted_q <= 1'b0; halted_q <= 1'b0;
end else begin end else begin
led_valid_q <= 1'b0;
if (ex_led_event) begin
led_data_q <= ex_src2;
led_valid_q <= 1'b1;
end
if (go_i) halted_q <= 1'b0; if (go_i) halted_q <= 1'b0;
if (ex_halt_event) halted_q <= 1'b1; if (ex_halt_event) halted_q <= 1'b1;
@@ -605,7 +637,7 @@ module cpu21_riscv_redirect_int_bpb #(
memwb_alu_result_q <= exmem_alu_result_q; memwb_alu_result_q <= exmem_alu_result_q;
memwb_alu_result2_q <= exmem_alu_result2_q; memwb_alu_result2_q <= exmem_alu_result2_q;
memwb_csr_old_q <= exmem_csr_old_q; memwb_csr_old_q <= exmem_csr_old_q;
memwb_mem_data_q <= data_rdata_i; memwb_mem_data_q <= mem_rdata;
memwb_rd_q <= exmem_rd_q; memwb_rd_q <= exmem_rd_q;
memwb_reg_write_q <= exmem_reg_write_q; memwb_reg_write_q <= exmem_reg_write_q;
memwb_mem_to_reg_q <= exmem_mem_to_reg_q; memwb_mem_to_reg_q <= exmem_mem_to_reg_q;
@@ -654,7 +686,9 @@ module cpu21_riscv_redirect_int_bpb #(
idex_beq_q <= d_beq; idex_beq_q <= d_beq;
idex_bne_q <= d_bne; idex_bne_q <= d_bne;
idex_blt_q <= d_blt; idex_blt_q <= d_blt;
idex_bge_q <= d_bge;
idex_bltu_q <= d_bltu; idex_bltu_q <= d_bltu;
idex_bgeu_q <= d_bgeu;
idex_jal_q <= d_jal; idex_jal_q <= d_jal;
idex_jalr_q <= d_jalr; idex_jalr_q <= d_jalr;
idex_ecall_q <= d_ecall; idex_ecall_q <= d_ecall;
+22
View File
@@ -0,0 +1,22 @@
# 上板演示程序(Nexys4DDR):通过 MMIO 写 LED 数据寄存器,做 16 位跑马灯。
# LED_ADDR = 0x0000_1000(与 cpu21_board_top.v / cpu21_mmio.v 的默认参数一致)。
# 用 tools/hex2vh.py 把它转成 cpu21_board_rom.vh,供综合时初始化片内 ROM。
lui t0, 0x1 # t0 = LED_ADDR = 0x00001000
addi t1, zero, 1 # 跑马灯初值:bit0
main_loop:
sw t1, 0(t0) # MMIO 写 LED -> 板载 LED[15:0]
slli t1, t1, 1 # 左移一位
lui t4, 0x8 # t4 = 0x00008000
bltu t4, t1, restart # 移出 16 位后重新开始
jal zero, delay
restart:
addi t1, zero, 1
delay: # 软件延时,让人眼能看到跑马灯(6.25 MHz 下约 20 ms)
lui t2, 0xf # 延时计数 0x0000F000
addi t3, zero, 1
delay_loop:
sub t2, t2, t3
bne t2, zero, delay_loop
jal zero, main_loop
+110
View File
@@ -0,0 +1,110 @@
# 指令扩展验证程序:每条被测指令的结果都通过 MMIO 写 LED 输出,最后用 halt 停机。
# 期望输出顺序见每行注释(共 24 次输出),同时保存在同目录 isa_test.expected(每行一项 LED 值)。
#
# 用法(在仓库根目录执行):
# python tools/rv_asm.py testbench/programs/isa_test.asm <仿真工作目录>/risc-v-benchmark_ccab.hex
# 再用 testbench/tb_no_intr.v 仿真;hex 命名为 risc-v-benchmark_ccab.hex 时会被测试
# 平台优先从仿真工作目录载入(参见 tb_no_intr.v 的 ROM 探测顺序)。
# print / halt 是 tools/rv_asm.py 提供的双字伪指令(print 会占用 t6):
# print = lui t6,0x1000>>12 + sw a0,0(t6)(写 MMIO 的 LED 寄存器);
# halt = addi a7,zero,10 + ecall(ecall 停机)。
# ---- sltiu:立即数符号扩展后做无符号比较 ----
addi t0, zero, 1
addi t1, zero, -1 # t1 = 0xffffffff
sltiu a0, t0, -1 # 1 <u 0xffffffff -> 00000001
print
sltiu a0, t1, 1 # 0xffffffff <u 1 -> 00000000
print
# ---- xor / sll / srl / sra(原有指令回归)----
addi t0, zero, 0xff
addi t1, zero, -1
xor a0, t0, t1 # 0xff ^ 0xffffffff -> ffffff00
print
addi t0, zero, 1
addi t1, zero, 31
sll a0, t0, t1 # 1 << 31 -> 80000000
print
addi t0, zero, -1
addi t1, zero, 28
srl a0, t0, t1 # 逻辑右移 -> 0000000f
print
sra a0, t0, t1 # 算术右移 -> ffffffff
print
# ---- mul:无符号乘积低 32 位 ----
addi t0, zero, 1234
addi t1, zero, 567
mul a0, t0, t1 # 1234*567 = 0x000aad1e
print
# ---- mulh / mulhu / mulhsu:64 位乘积的高 32 位 ----
addi t0, zero, -1 # 有符号 -1 / 无符号 0xffffffff
addi t1, zero, -1
mulh a0, t0, t1 # (-1)*(-1)=1 -> 00000000
print
mulhu a0, t0, t1 # 0xffffffff^2 -> fffffffe
print
addi t0, zero, -1
addi t1, zero, 2
mulhsu a0, t0, t1 # (-1)*2=-2 -> ffffffff
print
# ---- bltu:0 <u 0xffffffff 成立(按有符号比较则不成立)----
addi t0, zero, -1
bltu zero, t0, bltu_ok
addi a0, zero, 0x101 # 不应执行
print
bltu_ok:
addi a0, zero, 0x601 # 00000601
print
# ---- bge:有符号 >= ,(-1) >= 0 不成立,应走 fall-through ----
addi t0, zero, -1
bge t0, zero, bge_bad
addi a0, zero, 0x602 # 00000602
print
jal zero, bge_done
bge_bad:
addi a0, zero, 0x102 # 不应执行
print
bge_done:
# ---- bgeu:无符号 >= ,0xffffffff >=u 0 成立 ----
addi t0, zero, -1
bgeu t0, zero, bgeu_ok
addi a0, zero, 0x103 # 不应执行
print
bgeu_ok:
addi a0, zero, 0x603 # 00000603
print
# ---- bge 循环:从 -3 递增到 0(共 4 次输出)----
addi s1, zero, -3
bge_loop:
add a0, zero, s1
print # fffffffd / fffffffe / ffffffff / 00000000
addi s1, s1, 1
bge zero, s1, bge_loop
# ---- bgeu 循环:3 -> 1(共 3 次输出)----
addi t0, zero, 3
bgeu_loop:
add a0, zero, t0
print # 00000003 / 00000002 / 00000001
addi t0, t0, -1
addi t2, zero, 1
bgeu t0, t2, bgeu_loop
# ---- bltu 循环:4 -> 1(共 4 次输出)----
addi s1, zero, 4
bltu_loop:
add a0, zero, s1
print # 00000004 / 00000003 / 00000002 / 00000001
addi s1, s1, -1
bltu zero, s1, bltu_loop
halt
+24
View File
@@ -0,0 +1,24 @@
00000001
00000000
ffffff00
80000000
0000000f
ffffffff
000aad1e
00000000
fffffffe
ffffffff
00000601
00000602
00000603
fffffffd
fffffffe
ffffffff
00000000
00000003
00000002
00000001
00000004
00000003
00000002
00000001
+21
View File
@@ -0,0 +1,21 @@
# LUI 指令测试:依次把 lui 的结果写到 MMIO 的 LED 寄存器,最后停机。
# 期望输出:12345000 / fffff000 / 80000000 / 12345678,随后停机(halted=1)。
# 用法:python tools/rv_asm.py testbench/programs/lui_test.asm <仿真工作目录>/risc-v-benchmark_ccab.hex
lui t0, 0x12345 # t0 = 0x12345000
add a0, zero, t0
print # MMIO 写 LED -> 12345000
lui t0, 0xfffff # t0 = 0xfffff000
add a0, zero, t0
print # -> fffff000
lui t0, 0x80000 # t0 = 0x80000000
add a0, zero, t0
print # -> 80000000
lui t0, 0x12345 # 常见用法 lui+addi:0x12345678
addi t0, t0, 0x678
add a0, zero, t0
print # -> 12345678
halt # 停机
+4
View File
@@ -0,0 +1,4 @@
12345000
fffff000
80000000
12345678
+25
View File
@@ -0,0 +1,25 @@
# MMIO 验证程序:写 LED 寄存器(0x0000_1000)-> 读回 -> 字节写 -> 再读回。
# 期望 LED 输出顺序(共 5 次,见同目录 mmio_test.expected):
# ① 12345678 sw 0x12345678
# ② 12345678 lw 读回后 print(print = lui t6 + sw a0,0(t6),本身就是一次 MMIO 写)
# ③ ffffffff sw 0xffffffff
# ④ ffff7fff sb 0x7f 覆盖 led[15:8]
# ⑤ ffff7fff 读回后 print
# 用法:python tools/rv_asm.py testbench/programs/mmio_test.asm <仿真工作目录>/risc-v-benchmark_ccab.hex
lui t0, 0x1 # t0 = LED_ADDR = 0x00001000
lui t1, 0x12345
addi t1, t1, 0x678 # t1 = 0x12345678
sw t1, 0(t0) # ① MMIO 写 LED
lw a0, 0(t0) # MMIO 读回
print # ② 应为 12345678
addi t2, zero, -1 # t2 = 0xffffffff
sw t2, 0(t0) # ③ 应为 ffffffff
addi t3, zero, 0x7f
sb t3, 1(t0) # 字节写:led[15:8] = 0x7f
lw a0, 0(t0) # 读回
print # ④ 应为 ffff7fff
halt
+5
View File
@@ -0,0 +1,5 @@
12345678
12345678
ffffffff
ffff7fff
ffff7fff
+13
View File
@@ -0,0 +1,13 @@
000012B7
00100313
0062A023
00131313
00008EB7
006EE463
0080006F
00100313
0000F3B7
00100E13
41C383B3
FE039EE3
FD9FF06F
+90
View File
@@ -0,0 +1,90 @@
00100293
FFF00313
FFF2B513
00001FB7
00AFA023
00133513
00001FB7
00AFA023
0FF00293
FFF00313
0062C533
00001FB7
00AFA023
00100293
01F00313
00629533
00001FB7
00AFA023
FFF00293
01C00313
0062D533
00001FB7
00AFA023
4062D533
00001FB7
00AFA023
4D200293
23700313
02628533
00001FB7
00AFA023
FFF00293
FFF00313
02629533
00001FB7
00AFA023
0262B533
00001FB7
00AFA023
FFF00293
00200313
0262A533
00001FB7
00AFA023
FFF00293
00506863
10100513
00001FB7
00AFA023
60100513
00001FB7
00AFA023
FFF00293
0002DA63
60200513
00001FB7
00AFA023
0100006F
10200513
00001FB7
00AFA023
FFF00293
0002F863
10300513
00001FB7
00AFA023
60300513
00001FB7
00AFA023
FFD00493
00900533
00001FB7
00AFA023
00148493
FE9058E3
00300293
00500533
00001FB7
00AFA023
FFF28293
00100393
FE72F6E3
00400493
00900533
00001FB7
00AFA023
FFF48493
FE9068E3
00A00893
00000073
+19
View File
@@ -0,0 +1,19 @@
123452B7
00500533
00001FB7
00AFA023
FFFFF2B7
00500533
00001FB7
00AFA023
800002B7
00500533
00001FB7
00AFA023
123452B7
67828293
00500533
00001FB7
00AFA023
00A00893
00000073
+16
View File
@@ -0,0 +1,16 @@
000012B7
12345337
67830313
0062A023
0002A503
00001FB7
00AFA023
FFF00393
0072A023
07F00E13
01C280A3
0002A503
00001FB7
00AFA023
00A00893
00000073
+56
View File
@@ -0,0 +1,56 @@
`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 时钟经 16 分频,需要更长窗口)
$display("FINAL: LED 变化次数=%0d 当前 LED=%04h", changes, led);
$finish;
end
endmodule
+13 -8
View File
@@ -1,6 +1,6 @@
// 与 RTL 文件(cpu21_riscv_*.v、cpu21_bpb_8.v)一起编译。 // 与 RTL 文件(rtl/cpu21_*.v)一起编译,仿真相关文件都在 testbench/ 下。
// 示例: // 示例:
// iverilog -g2012 -o cpu21_sim cpu21_*.v tb_cpu21_riscv_redirect_int_bpb.v // iverilog -g2012 -o cpu21_sim rtl/*.v testbench/tb_cpu21_riscv_redirect_int_bpb.v
// vvp cpu21_sim // vvp cpu21_sim
// 仿真会写出 cpu21_riscv_redirect_int_bpb.vcd,可供 GTKWave 查看。 // 仿真会写出 cpu21_riscv_redirect_int_bpb.vcd,可供 GTKWave 查看。
// //
@@ -8,9 +8,13 @@
// 只会打印警告并保持内存原样,所以路径写错会让 ROM 看起来全是 NOP // 只会打印警告并保持内存原样,所以路径写错会让 ROM 看起来全是 NOP
// (0x00000013)。仿真器的工作目录并不固定(Vivado xsim 在 // (0x00000013)。仿真器的工作目录并不固定(Vivado xsim 在
// <proj>.sim/sim_1/behav/xsim 下运行),因此本测试平台会依次探测若干候选 // <proj>.sim/sim_1/behav/xsim 下运行),因此本测试平台会依次探测若干候选
// 路径,并报告实际命中的那个。显式给出的路径始终优先: // 路径(testbench/rom/、../testbench/rom/、…,相对仓库根目录),并报告实际
// 命中的那个。显式给出的路径始终优先:
// vvp cpu21_sim +ROM_FILE=<path> (iverilog/vvp) // vvp cpu21_sim +ROM_FILE=<path> (iverilog/vvp)
// xelab ... -generic_top "ROM_FILE=<path>" (Vivado xsim) // xelab ... -generic_top "ROM_FILE=<path>" (Vivado xsim)
// 注意:PowerShell 会把命令行参数里的 "=" 拆开(xelab/xsim 报
// "Expected a switch but found …"),此时直接改本文件的 ROM_FILE 参数即可;
// ROM 镜像都在 testbench/rom/ 下,路径请用正斜杠(Verilog 字符串里 "\" 是转义符)。
module tb_cpu21_riscv_redirect_int_bpb #( module tb_cpu21_riscv_redirect_int_bpb #(
parameter ROM_FILE = "cpu21_riscv_redirect_int_bpb_rom.hex" parameter ROM_FILE = "cpu21_riscv_redirect_int_bpb_rom.hex"
// risc-v-benchmark_ccab.hex // risc-v-benchmark_ccab.hex
@@ -152,7 +156,8 @@ module tb_cpu21_riscv_redirect_int_bpb #(
if (data_addr_o[11:2] < RAM_WORDS) data_rdata_i = ram[data_addr_o[11:2]]; if (data_addr_o[11:2] < RAM_WORDS) data_rdata_i = ram[data_addr_o[11:2]];
end end
// 带字节使能的同步写模型。 // 带字节使能的同步写模型。MMIO 区域(LED_ADDR=0x1000 起)不在本模型的
// 译码范围(data_addr_o[11:2] < 1024)内,因此对 LED 的存储不会写进 RAM。
always @(posedge clk) begin always @(posedge clk) begin
if (!reset && data_we_o && (data_addr_o[11:2] < RAM_WORDS)) begin if (!reset && data_we_o && (data_addr_o[11:2] < RAM_WORDS)) begin
if (data_wstrb_o[0]) ram[data_addr_o[11:2]][7:0] <= data_wdata_o[7:0]; if (data_wstrb_o[0]) ram[data_addr_o[11:2]][7:0] <= data_wdata_o[7:0];
@@ -226,10 +231,10 @@ module tb_cpu21_riscv_redirect_int_bpb #(
rom_loaded = 1'b0; rom_loaded = 1'b0;
if ($value$plusargs("ROM_FILE=%s", rom_file_path)) load_rom_image(rom_file_path, rom_loaded); if ($value$plusargs("ROM_FILE=%s", rom_file_path)) load_rom_image(rom_file_path, rom_loaded);
if (!rom_loaded) load_rom_image(ROM_FILE, rom_loaded); if (!rom_loaded) load_rom_image(ROM_FILE, rom_loaded);
if (!rom_loaded) load_rom_image({"testbench/", ROM_FILE}, rom_loaded); if (!rom_loaded) load_rom_image({"testbench/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../testbench/", ROM_FILE}, rom_loaded); if (!rom_loaded) load_rom_image({"../testbench/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../../../testbench/", ROM_FILE}, rom_loaded); if (!rom_loaded) load_rom_image({"../../../testbench/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../../../../../testbench/", ROM_FILE}, rom_loaded); if (!rom_loaded) load_rom_image({"../../../../../testbench/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) if (!rom_loaded)
$display( $display(
"WARNING: no ROM image found for \"%0s\" - the ROM stays filled with NOPs (0x00000013).", "WARNING: no ROM image found for \"%0s\" - the ROM stays filled with NOPs (0x00000013).",
+13 -8
View File
@@ -1,9 +1,9 @@
// 仿真顶层:tb_no_intr —— 与 tb_cpu21_riscv_redirect_int_bpb.v 相同的测试框架, // 仿真顶层:tb_no_intr —— 与 tb_cpu21_riscv_redirect_int_bpb.v 相同的测试框架,
// 但不注入任何中断脉冲,因此主程序可以不受打扰地连续运行。 // 但不注入任何中断脉冲,因此主程序可以不受打扰地连续运行。
// //
// 与 RTL 文件(cpu21_riscv_*.v、cpu21_bpb_8.v)一起编译。 // 与 RTL 文件(rtl/cpu21_*.v)一起编译,仿真相关文件都在 testbench/ 下。
// 示例: // 示例:
// iverilog -g2012 -o cpu21_sim cpu21_*.v tb_no_intr.v // iverilog -g2012 -o cpu21_sim rtl/*.v testbench/tb_no_intr.v
// vvp cpu21_sim // vvp cpu21_sim
// 仿真会写出 tb_no_intr.vcd,可供 GTKWave 查看。 // 仿真会写出 tb_no_intr.vcd,可供 GTKWave 查看。
// //
@@ -11,9 +11,13 @@
// 只会打印警告并保持内存原样,所以路径写错会让 ROM 看起来全是 NOP // 只会打印警告并保持内存原样,所以路径写错会让 ROM 看起来全是 NOP
// (0x00000013)。仿真器的工作目录并不固定(Vivado xsim 在 // (0x00000013)。仿真器的工作目录并不固定(Vivado xsim 在
// <proj>.sim/sim_1/behav/xsim 下运行),因此本测试平台会依次探测若干候选 // <proj>.sim/sim_1/behav/xsim 下运行),因此本测试平台会依次探测若干候选
// 路径,并报告实际命中的那个。显式给出的路径始终优先: // 路径(testbench/rom/、../testbench/rom/、…,相对仓库根目录),并报告实际
// 命中的那个。显式给出的路径始终优先:
// vvp cpu21_sim +ROM_FILE=<path> (iverilog/vvp) // vvp cpu21_sim +ROM_FILE=<path> (iverilog/vvp)
// xelab ... -generic_top "ROM_FILE=<path>" (Vivado xsim) // xelab ... -generic_top "ROM_FILE=<path>" (Vivado xsim)
// 注意:PowerShell 会把命令行参数里的 "=" 拆开(xelab/xsim 报
// "Expected a switch but found …"),此时直接改本文件的 ROM_FILE 参数即可;
// ROM 镜像都在 testbench/rom/ 下,路径请用正斜杠(Verilog 字符串里 "\" 是转义符)。
module tb_no_intr #( module tb_no_intr #(
parameter ROM_FILE = "risc-v-benchmark_ccab.hex" parameter ROM_FILE = "risc-v-benchmark_ccab.hex"
// risc-v-benchmark_ccab.hex // risc-v-benchmark_ccab.hex
@@ -155,7 +159,8 @@ module tb_no_intr #(
if (data_addr_o[11:2] < RAM_WORDS) data_rdata_i = ram[data_addr_o[11:2]]; if (data_addr_o[11:2] < RAM_WORDS) data_rdata_i = ram[data_addr_o[11:2]];
end end
// 带字节使能的同步写模型。 // 带字节使能的同步写模型。MMIO 区域(LED_ADDR=0x1000 起)不在本模型的
// 译码范围(data_addr_o[11:2] < 1024)内,因此对 LED 的存储不会写进 RAM。
always @(posedge clk) begin always @(posedge clk) begin
if (!reset && data_we_o && (data_addr_o[11:2] < RAM_WORDS)) begin if (!reset && data_we_o && (data_addr_o[11:2] < RAM_WORDS)) begin
if (data_wstrb_o[0]) ram[data_addr_o[11:2]][7:0] <= data_wdata_o[7:0]; if (data_wstrb_o[0]) ram[data_addr_o[11:2]][7:0] <= data_wdata_o[7:0];
@@ -229,10 +234,10 @@ module tb_no_intr #(
rom_loaded = 1'b0; rom_loaded = 1'b0;
if ($value$plusargs("ROM_FILE=%s", rom_file_path)) load_rom_image(rom_file_path, rom_loaded); if ($value$plusargs("ROM_FILE=%s", rom_file_path)) load_rom_image(rom_file_path, rom_loaded);
if (!rom_loaded) load_rom_image(ROM_FILE, rom_loaded); if (!rom_loaded) load_rom_image(ROM_FILE, rom_loaded);
if (!rom_loaded) load_rom_image({"testbench/", ROM_FILE}, rom_loaded); if (!rom_loaded) load_rom_image({"testbench/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../testbench/", ROM_FILE}, rom_loaded); if (!rom_loaded) load_rom_image({"../testbench/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../../../testbench/", ROM_FILE}, rom_loaded); if (!rom_loaded) load_rom_image({"../../../testbench/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../../../../../testbench/", ROM_FILE}, rom_loaded); if (!rom_loaded) load_rom_image({"../../../../../testbench/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) if (!rom_loaded)
$display( $display(
"WARNING: no ROM image found for \"%0s\" - the ROM stays filled with NOPs (0x00000013).", "WARNING: no ROM image found for \"%0s\" - the ROM stays filled with NOPs (0x00000013).",
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env python3
"""把 $readmemh 格式的 hex 文件转换成 Verilog include 文件,供综合时初始化 ROM。
用法: python tools/hex2vh.py <input.hex> <output.vh> [ROM_WORDS]
输出内容形如(每行一条赋值,未用到的地址填 NOP 0x00000013):
rom[0] = 32'h00001037;
rom[1] = 32'h00100093;
配套 RTL 用法(见 board/cpu21_board_top.v,两者同目录,所以 include 能直接解析):
initial begin
for (i = 0; i < ROM_WORDS; i = i + 1) rom[i] = 32'h0000_0013; // NOP 填充
`include "cpu21_board_rom.vh"
end
这样 ROM 内容直接跟着 RTL 走,不依赖综合时的当前工作目录,
避免了 $readmemh 相对路径在 Vivado 综合流程里找不到文件(且只报 warning)的坑。
"""
import sys
NOP = 0x00000013
def main():
src, dst = sys.argv[1], sys.argv[2]
words_total = int(sys.argv[3]) if len(sys.argv) > 3 else 1024
words = []
with open(src, encoding="ascii") as f:
for line in f:
line = line.split("//")[0].strip()
if not line:
continue
words.append(int(line, 16))
if len(words) > words_total:
raise SystemExit(
"程序 %d 个字,超过 ROM 容量 %d 个字" % (len(words), words_total)
)
with open(dst, "w", encoding="utf-8") as f:
f.write(" // generated by tools/hex2vh.py - do not edit by hand\n")
f.write(
" // source: %s (%d words, remaining entries filled with NOP)\n"
% (src, len(words))
)
for addr in range(words_total):
w = words[addr] if addr < len(words) else NOP
f.write(" rom[%d] = 32'h%08X;\n" % (addr, w))
print("已生成 %s(%d 个字,源程序 %d 个字)" % (dst, words_total, len(words)))
main()
+230
View File
@@ -0,0 +1,230 @@
#!/usr/bin/env python3
"""极简 RISC-V 汇编器:仅支持本项目测试所需的指令子集。
用法: python tools/rv_asm.py <input.asm> <output.hex>
同时生成 <output.hex>.lst 反汇编/编码清单,便于核对。
支持:add/sub/and/or/xor/sll/srl/sra/slt/sltu、
mul/mulh/mulhsu/mulhu、
addi/slti/sltiu/xori/ori/andi、slli/srli/srai、
lw/sw/sb、beq/bne/blt/bge/bltu/bgeu、lui、jal/jalr、ecall、标签、# 注释
伪指令:print(= lui t6,LED_ADDR>>12 + sw a0,0(t6),向 MMIO 的 LED 寄存器写入 a0)、
halt(= addi a7,zero,10 + ecall),各占两个字。
示例:python tools/rv_asm.py testbench/programs/isa_test.asm out.hex
"""
import re
import sys
REGS = {
"zero": 0,
"ra": 1,
"sp": 2,
"gp": 3,
"tp": 4,
"t0": 5,
"t1": 6,
"t2": 7,
"s0": 8,
"s1": 9,
"a0": 10,
"a1": 11,
"a2": 12,
"a3": 13,
"a4": 14,
"a5": 15,
"a6": 16,
"a7": 17,
"s2": 18,
"s3": 19,
"s4": 20,
"s5": 21,
"s6": 22,
"s7": 23,
"s8": 24,
"s9": 25,
"s10": 26,
"s11": 27,
"t3": 28,
"t4": 29,
"t5": 30,
"t6": 31,
}
def reg(t):
t = t.strip()
if t in REGS:
return REGS[t]
m = re.fullmatch(r"x(\d+)", t)
if m:
return int(m.group(1))
raise SystemExit("未知寄存器: " + t)
def num(t):
return int(t.strip(), 0)
def enc_r(f7, rs2, rs1, f3, rd):
return (f7 << 25) | (rs2 << 20) | (rs1 << 15) | (f3 << 12) | (rd << 7) | 0x33
def enc_i(imm, rs1, f3, rd, op=0x13):
return ((imm & 0xFFF) << 20) | (rs1 << 15) | (f3 << 12) | (rd << 7) | op
def enc_s(imm, rs2, rs1, f3):
imm &= 0xFFF
return (
((imm >> 5) << 25)
| (rs2 << 20)
| (rs1 << 15)
| (f3 << 12)
| ((imm & 0x1F) << 7)
| 0x23
)
def enc_b(imm, rs2, rs1, f3):
imm &= 0x1FFF
return (
(((imm >> 12) & 1) << 31)
| (((imm >> 5) & 0x3F) << 25)
| (rs2 << 20)
| (rs1 << 15)
| (f3 << 12)
| (((imm >> 1) & 0xF) << 8)
| (((imm >> 11) & 1) << 7)
| 0x63
)
def enc_u(imm, rd):
return ((imm & 0xFFFFF) << 12) | (rd << 7) | 0x37
def enc_j(imm, rd):
imm &= 0x1FFFFF
return (
(((imm >> 20) & 1) << 31)
| (((imm >> 1) & 0x3FF) << 21)
| (((imm >> 11) & 1) << 20)
| (((imm >> 12) & 0xFF) << 12)
| (rd << 7)
| 0x6F
)
RTYPE = {
"add": (0x00, 0),
"sub": (0x20, 0),
"sll": (0x00, 1),
"slt": (0x00, 2),
"sltu": (0x00, 3),
"xor": (0x00, 4),
"srl": (0x00, 5),
"sra": (0x20, 5),
"or": (0x00, 6),
"and": (0x00, 7),
"mul": (0x01, 0),
"mulh": (0x01, 1),
"mulhsu": (0x01, 2),
"mulhu": (0x01, 3),
}
ITYPE = {"addi": 0, "slti": 2, "sltiu": 3, "xori": 4, "ori": 6, "andi": 7}
SHIFT = {"slli": 1, "srli": 5, "srai": 5}
BTYPE = {"beq": 0, "bne": 1, "blt": 4, "bge": 5, "bltu": 6, "bgeu": 7}
# MMIO:LED 数据寄存器地址,需与 RTL 顶层 parameter LED_ADDR 保持一致
LED_ADDR = 0x1000
# 双字伪指令:print 向 MMIO 的 LED 写寄存器写入 a0;halt 停机
PSEUDO_SIZE = {"print": 2, "halt": 2}
def assemble(lines):
labels, items, addr = {}, [], 0
for raw in lines:
line = raw.split("#")[0].strip()
if not line:
continue
while True:
m = re.match(r"^([A-Za-z_.$][\w.$]*)\s*:\s*(.*)$", line)
if not m:
break
labels[m.group(1)] = addr
line = m.group(2).strip()
if not line:
break
if not line:
continue
items.append((addr, line))
addr += 4 * PSEUDO_SIZE.get(line.split()[0].lower(), 1)
out = []
for addr, line in items:
parts = re.split(r"[\s,]+", line)
mn, a = parts[0].lower(), parts[1:]
if mn in RTYPE:
f7, f3 = RTYPE[mn]
w = enc_r(f7, reg(a[2]), reg(a[1]), f3, reg(a[0]))
elif mn in ITYPE:
im = num(a[2])
if not (-2048 <= im <= 2047):
raise SystemExit("I 型立即数超出 12 位有符号范围: " + line)
w = enc_i(im, reg(a[1]), ITYPE[mn], reg(a[0]))
elif mn in SHIFT:
w = enc_i(num(a[2]) & 0x1F, reg(a[1]), SHIFT[mn], reg(a[0]))
elif mn == "lui":
w = enc_u(num(a[1]), reg(a[0]))
elif mn in ("lw", "sw", "sb"):
m = re.fullmatch(r"(-?\w+)\((\w+)\)", a[1])
if not m:
raise SystemExit("内存操作数格式应为 imm(rs1): " + line)
im = num(m.group(1))
if not (-2048 <= im <= 2047):
raise SystemExit("访存偏移超出 12 位有符号范围: " + line)
if mn == "lw":
w = enc_i(im, reg(m.group(2)), 2, reg(a[0]), 0x03)
elif mn == "sw":
w = enc_s(im, reg(a[0]), reg(m.group(2)), 2)
else: # sb
w = enc_s(im, reg(a[0]), reg(m.group(2)), 0)
elif mn in BTYPE:
tgt = labels[a[2]] if a[2] in labels else num(a[2])
w = enc_b(tgt - addr, reg(a[1]), reg(a[0]), BTYPE[mn])
elif mn == "jal":
tgt = labels[a[1]] if a[1] in labels else num(a[1])
w = enc_j(tgt - addr, reg(a[0]))
elif mn == "jalr":
w = enc_i(num(a[2]), reg(a[1]), 0, reg(a[0]), 0x67)
elif mn == "ecall":
w = 0x00000073
elif mn == "print":
# 写 MMIO 的 LED 寄存器:lui t6, LED_ADDR>>12 ; sw a0, 0(t6)
w = [
enc_u(LED_ADDR >> 12, reg("t6")),
enc_s(0, reg("a0"), reg("t6"), 2),
]
elif mn == "halt":
w = [enc_i(10, 0, 0, 17), 0x00000073]
else:
raise SystemExit("未知助记符: " + mn)
out.append((addr, line, w if isinstance(w, list) else [w]))
return out
def main():
src, dst = sys.argv[1], sys.argv[2]
with open(src, encoding="utf-8-sig") as f: # 兼容带/不带 BOM
prog = assemble(f.readlines())
with open(dst, "w", encoding="ascii") as f:
for _, _, ws in prog:
for w in ws:
f.write("%08X\n" % w)
with open(dst + ".lst", "w", encoding="utf-8") as f:
for addr, line, ws in prog:
f.write("%04X: %s %s\n" % (addr, " ".join("%08X" % w for w in ws), line))
print("已生成 %s(%d 个字)" % (dst, sum(len(ws) for _, _, ws in prog)))
main()