Compare commits

3 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
36 changed files with 2947 additions and 84 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 争用。
+190 -16
View File
@@ -1,11 +1,36 @@
# `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。
@@ -16,7 +41,7 @@
- 三路中断输入 `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`)。 - `lui`:加载高位立即数,`rd = imm[31:12] << 12`(按需求补充的扩展指令,标准 opcode `0x37`)。
- `sltiu`:I 型 `funct3=011`,立即数符号扩展后按无符号与 rs1 比较。 - `sltiu`:I 型 `funct3=011`,立即数符号扩展后按无符号与 rs1 比较。
@@ -28,7 +53,7 @@
因此它与标准 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 |
| M 扩展(R 型) | `0x0c` | `funct7=0000001`:mul/mulh/mulhsu/mulhu(另有课程约定的 remu,`funct3=111`) | | M 扩展(R 型) | `0x0c` | `funct7=0000001`:mul/mulh/mulhsu/mulhu(另有课程约定的 remu,`funct3=111`) |
| I 型 ALU | `0x04` | addi/andi/ori/xori/slti/sltiu/slli/srli/srai | | I 型 ALU | `0x04` | addi/andi/ori/xori/slti/sltiu/slli/srli/srai |
@@ -43,6 +68,34 @@
`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 提供。
@@ -63,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
@@ -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
) ( ) (
@@ -171,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;
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@@ -411,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);
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@@ -495,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;
@@ -596,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;
@@ -616,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;
+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
+4 -3
View File
@@ -1,12 +1,13 @@
# 指令扩展验证程序:每条被测指令的结果都通过 ecall(a7=34) 输出,最后用 halt 停机。 # 指令扩展验证程序:每条被测指令的结果都通过 MMIO 写 LED 输出,最后用 halt 停机。
# 期望输出顺序见每行注释(共 24 次输出),同时保存在同目录 isa_test.expected(每行一项 LED 值)。 # 期望输出顺序见每行注释(共 24 次输出),同时保存在同目录 isa_test.expected(每行一项 LED 值)。
# #
# 用法(在仓库根目录执行): # 用法(在仓库根目录执行):
# python tools/rv_asm.py testbench/programs/isa_test.asm <仿真工作目录>/risc-v-benchmark_ccab.hex # 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 时会被测试 # 再用 testbench/tb_no_intr.v 仿真;hex 命名为 risc-v-benchmark_ccab.hex 时会被测试
# 平台优先从仿真工作目录载入(参见 tb_no_intr.v 的 ROM 探测顺序)。 # 平台优先从仿真工作目录载入(参见 tb_no_intr.v 的 ROM 探测顺序)。
# print / halt 是 tools/rv_asm.py 提供的双字伪指令: # print / halt 是 tools/rv_asm.py 提供的双字伪指令(print 会占用 t6):
# print = addi a7,zero,34 + ecall;halt = addi a7,zero,10 + ecall。 # print = lui t6,0x1000>>12 + sw a0,0(t6)(写 MMIO 的 LED 寄存器);
# halt = addi a7,zero,10 + ecall(ecall 停机)。
# ---- sltiu:立即数符号扩展后做无符号比较 ---- # ---- sltiu:立即数符号扩展后做无符号比较 ----
addi t0, zero, 1 addi t0, zero, 1
+6 -11
View File
@@ -1,26 +1,21 @@
# LUI 指令测试:依次打印 lui 的结果,最后停机。 # LUI 指令测试:依次把 lui 的结果写到 MMIO 的 LED 寄存器,最后停机。
# 期望输出:12345000 / fffff000 / 80000000 / 12345678,随后停机(halted=1)。 # 期望输出:12345000 / fffff000 / 80000000 / 12345678,随后停机(halted=1)。
# 用法:python tools/rv_asm.py testbench/programs/lui_test.asm <仿真工作目录>/risc-v-benchmark_ccab.hex # 用法:python tools/rv_asm.py testbench/programs/lui_test.asm <仿真工作目录>/risc-v-benchmark_ccab.hex
lui t0, 0x12345 # t0 = 0x12345000 lui t0, 0x12345 # t0 = 0x12345000
add a0, zero, t0 add a0, zero, t0
addi a7, zero, 34 print # MMIO 写 LED -> 12345000
ecall
lui t0, 0xfffff # t0 = 0xfffff000 lui t0, 0xfffff # t0 = 0xfffff000
add a0, zero, t0 add a0, zero, t0
addi a7, zero, 34 print # -> fffff000
ecall
lui t0, 0x80000 # t0 = 0x80000000 lui t0, 0x80000 # t0 = 0x80000000
add a0, zero, t0 add a0, zero, t0
addi a7, zero, 34 print # -> 80000000
ecall
lui t0, 0x12345 # 常见用法 lui+addi:0x12345678 lui t0, 0x12345 # 常见用法 lui+addi:0x12345678
addi t0, t0, 0x678 addi t0, t0, 0x678
add a0, zero, t0 add a0, zero, t0
addi a7, zero, 34 print # -> 12345678
ecall
addi a7, zero, 10 # a7=10 -> 停机 halt # 停机
ecall
+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()
+17 -7
View File
@@ -6,8 +6,9 @@
支持:add/sub/and/or/xor/sll/srl/sra/slt/sltu、 支持:add/sub/and/or/xor/sll/srl/sra/slt/sltu、
mul/mulh/mulhsu/mulhu、 mul/mulh/mulhsu/mulhu、
addi/slti/sltiu/xori/ori/andi、slli/srli/srai、 addi/slti/sltiu/xori/ori/andi、slli/srli/srai、
lw/sw、beq/bne/blt/bge/bltu/bgeu、lui、jal/jalr、ecall、标签、# 注释 lw/sw/sb、beq/bne/blt/bge/bltu/bgeu、lui、jal/jalr、ecall、标签、# 注释
伪指令:print(= addi a7,zero,34 + ecall)、halt(= addi a7,zero,10 + 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 示例:python tools/rv_asm.py testbench/programs/isa_test.asm out.hex
""" """
@@ -133,7 +134,10 @@ RTYPE = {
ITYPE = {"addi": 0, "slti": 2, "sltiu": 3, "xori": 4, "ori": 6, "andi": 7} ITYPE = {"addi": 0, "slti": 2, "sltiu": 3, "xori": 4, "ori": 6, "andi": 7}
SHIFT = {"slli": 1, "srli": 5, "srai": 5} SHIFT = {"slli": 1, "srli": 5, "srai": 5}
BTYPE = {"beq": 0, "bne": 1, "blt": 4, "bge": 5, "bltu": 6, "bgeu": 7} BTYPE = {"beq": 0, "bne": 1, "blt": 4, "bge": 5, "bltu": 6, "bgeu": 7}
# 双字伪指令:print = addi a7,zero,34 + ecall;halt = addi a7,zero,10 + ecall # MMIO:LED 数据寄存器地址,需与 RTL 顶层 parameter LED_ADDR 保持一致
LED_ADDR = 0x1000
# 双字伪指令:print 向 MMIO 的 LED 写寄存器写入 a0;halt 停机
PSEUDO_SIZE = {"print": 2, "halt": 2} PSEUDO_SIZE = {"print": 2, "halt": 2}
@@ -172,7 +176,7 @@ def assemble(lines):
w = enc_i(num(a[2]) & 0x1F, reg(a[1]), SHIFT[mn], reg(a[0])) w = enc_i(num(a[2]) & 0x1F, reg(a[1]), SHIFT[mn], reg(a[0]))
elif mn == "lui": elif mn == "lui":
w = enc_u(num(a[1]), reg(a[0])) w = enc_u(num(a[1]), reg(a[0]))
elif mn in ("lw", "sw"): elif mn in ("lw", "sw", "sb"):
m = re.fullmatch(r"(-?\w+)\((\w+)\)", a[1]) m = re.fullmatch(r"(-?\w+)\((\w+)\)", a[1])
if not m: if not m:
raise SystemExit("内存操作数格式应为 imm(rs1): " + line) raise SystemExit("内存操作数格式应为 imm(rs1): " + line)
@@ -181,8 +185,10 @@ def assemble(lines):
raise SystemExit("访存偏移超出 12 位有符号范围: " + line) raise SystemExit("访存偏移超出 12 位有符号范围: " + line)
if mn == "lw": if mn == "lw":
w = enc_i(im, reg(m.group(2)), 2, reg(a[0]), 0x03) w = enc_i(im, reg(m.group(2)), 2, reg(a[0]), 0x03)
else: elif mn == "sw":
w = enc_s(im, reg(a[0]), reg(m.group(2)), 2) 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: elif mn in BTYPE:
tgt = labels[a[2]] if a[2] in labels else num(a[2]) 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]) w = enc_b(tgt - addr, reg(a[1]), reg(a[0]), BTYPE[mn])
@@ -194,7 +200,11 @@ def assemble(lines):
elif mn == "ecall": elif mn == "ecall":
w = 0x00000073 w = 0x00000073
elif mn == "print": elif mn == "print":
w = [enc_i(34, 0, 0, 17), 0x00000073] # 写 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": elif mn == "halt":
w = [enc_i(10, 0, 0, 17), 0x00000073] w = [enc_i(10, 0, 0, 17), 0x00000073]
else: else:
@@ -205,7 +215,7 @@ def assemble(lines):
def main(): def main():
src, dst = sys.argv[1], sys.argv[2] src, dst = sys.argv[1], sys.argv[2]
with open(src, encoding="utf-8") as f: with open(src, encoding="utf-8-sig") as f: # 兼容带/不带 BOM
prog = assemble(f.readlines()) prog = assemble(f.readlines())
with open(dst, "w", encoding="ascii") as f: with open(dst, "w", encoding="ascii") as f:
for _, _, ws in prog: for _, _, ws in prog: