上板支持: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 排除,不入库)。
This commit is contained in:
2026-09-13 17:37:44 +08:00
parent 192e28795a
commit 1ae6fe6ebe
17 changed files with 2676 additions and 8 deletions
+175
View File
@@ -0,0 +1,175 @@
`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