`timescale 1ns / 1ps `default_nettype none // ----------------------------------------------------------------------------- // 上板顶层(Nexys4DDR,xc7a100tcsg324-1) // // 处理器顶层 cpu21_riscv_redirect_int_bpb 暴露了 775 位调试端口(各级 PC/IR、 // 计数器、BPB/中断观测等),直接作为实现顶层会因 I/O 超限而无法布局布线 // ([Place 30-415] IO Placement failed due to overutilization)。本模块只把 // 开发板真正需要的外设引出来,其余调试端口不连接,因此不占用引脚: // // clk100 (E3) -> CPU 时钟(100 MHz,经 CLK_DIV 分频后给 CPU) // rst_n (C12) -> 复位按键 CPU_RESETN,低有效,经复位同步器 // go (N17) -> 按键 BTNC:ecall 停机后按下继续运行 // irq_sw[2:0] -> 拨码开关 SW[2:0]:3 路中断源 // led[15:0] -> 板载 LED[15:0]:显示 MMIO(LED_ADDR)写入值的低 16 位 // led_we (R12) -> LED16_B:有 MMIO 写入时亮,并拉长约 0.34 s 便于观察 // // 片内存储器: // ROM:ROM_WORDS 字,组合读,内容由 `include "cpu21_board_rom.vh"` 给出 // (该文件由 tools/hex2vh.py 从 testbench/programs/*.hex 生成); // RAM:RAM_WORDS 字,字节写使能 + 组合读;只响应地址 < 2^(RAM_AW+2), // 因此 MMIO 地址不会落进 RAM(避免与 LED_ADDR 别名)。 // // 注意:ROM_WORDS / RAM_WORDS 需为 2 的幂(默认 1024,对应地址位 [11:2])。 // ----------------------------------------------------------------------------- module cpu21_board_top #( parameter [31:0] LED_ADDR = 32'h0000_1000, parameter ROM_WORDS = 8192, parameter RAM_WORDS = 1024, parameter [31:0] USTATUS_INIT = 32'h0000_0001, // 板载 100 MHz 的分频比(1 = 直用 100 MHz,2 = 50 MHz,…)。 // 乘除法改成多周期单元后,原来 ~94 ns 的组合乘法/除法已经不在关键路径上, // 实测(xc7a100tcsg324-1): // CLK_DIV=1(100 MHz):WNS = -1.1 ns,违例路径是 EX 级的 // 分支判决/中断保存 → BPB 更新,与乘除法无关(逻辑只占 ~3 ns, // 7~8 ns 都是布线); // CLK_DIV=2( 50 MHz):WNS ≈ +8 ns,余量充足。 // 因此默认取 2,既比原来的 16 分频快 8 倍,又能保证时序收敛。 parameter integer CLK_DIV = 2, // 鼠标事件源:0 = 板上 5 向按键(免外设,默认);1 = PS/2 鼠标(Pmod JA) parameter integer USE_PS2_MOUSE = 0 ) ( 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]:原来的 3 路中断源(保持兼容) input wire btnu, // M18:光标上 input wire btnd, // P18:光标下 input wire btnl, // P17:光标左 input wire btnr, // M17:光标右 input wire sw_flag, // R15:SW[3],插旗模式(软件读 INPUT 寄存器) output wire [15:0] led, // LED[15:0] output wire led_we, // LED16_B:MMIO 写入指示 // 七段数码管(共阳极:位选/段选都是低有效) output wire [ 7:0] an, output wire [ 6:0] seg, output wire dp, // VGA output wire [ 3:0] vga_r, output wire [ 3:0] vga_g, output wire [ 3:0] vga_b, output wire vga_hs, output wire vga_vs, // PS/2(只有 USE_PS2_MOUSE=1 时才用) input wire ps2_clk, input wire ps2_data ); localparam ROM_AW = $clog2(ROM_WORDS); localparam RAM_AW = $clog2(RAM_WORDS); wire arst = ~rst_n; // 异步复位(高有效) // --------------------------------------------------------------------------- // 时钟:100 MHz / CLK_DIV(默认 2 = 50 MHz)给 CPU。 // // 乘除法已经是 cpu21_riscv_muldiv 里的多周期单元,关键路径上不再有组合的 // 32x32 乘法/除法;剩下的违例路径是 EX 级“分支判决/中断保存 → BPB 更新”, // 与乘除法无关,实测 100 MHz 下 WNS = -1.1 ns,50 MHz 下余量约 +8 ns。 // 分频后的时钟由 Vivado 自动插 BUFG;XDC 里用 create_generated_clock 声明, // 改分频比时两处要一起改。 // --------------------------------------------------------------------------- reg [15:0] clk_cnt; reg clk_div; always @(posedge clk100 or posedge arst) begin if (arst) begin clk_cnt <= 16'd0; clk_div <= 1'b0; end else if (clk_cnt == (CLK_DIV / 2 - 1)) begin clk_cnt <= 16'd0; clk_div <= ~clk_div; end else begin clk_cnt <= clk_cnt + 16'd1; end end wire clk_cpu = clk_div; // --------------------------------------------------------------------------- // 复位同步器:按键异步置位、释放与 CPU 时钟同步 // --------------------------------------------------------------------------- reg [2:0] rst_sync_q; always @(posedge clk_cpu or posedge arst) begin if (arst) rst_sync_q <= 3'b111; else rst_sync_q <= {rst_sync_q[1:0], 1'b0}; end wire reset = rst_sync_q[2]; // CPU 接口连线(先声明:后面的 ROM/RAM 逻辑与例化都要用到) wire [31:0] instr_addr; wire [31:0] data_addr; wire [31:0] data_wdata; wire [ 3:0] data_wstrb; wire data_we; wire [31:0] led_data; wire led_valid; // 扫雷扩展外设的连线(先声明,例化在下面) wire [31:0] disp_ctrl, disp_cursor; // 显示控制器(CPU 内部 MMIO 驱动) wire [31:0] seg_data; // 数码管:8 位十六进制 wire seg_valid; wire fb_we; // 显存写端口 wire [ 9:0] fb_waddr; wire [31:0] fb_wdata; wire [ 3:0] fb_wstrb; wire [ 9:0] fb_raddr; // 显存读端口(VGA) wire mouse_irq, level_irq; // 鼠标事件中断 / 软件中断(关卡入口) wire frame_tick; wire [15:0] sw_vec = {12'b0, sw_flag, irq_sw}; // 中断连线:SW0 与鼠标共用一个入口,SW1 与"关卡入口"共用一个入口。 wire [ 2:0] irq_vec = {irq_sw[2], (level_irq | irq_sw[1]), (mouse_irq | irq_sw[0])}; // --------------------------------------------------------------------------- // 指令 ROM:组合读(未用到的地址填 NOP) // --------------------------------------------------------------------------- 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 SAOLEI_ROM // 扫雷程序(由 saolei/tools/build.ps1 生成的 cpu21_saolei_rom.vh)。 // 综合/仿真时定义宏 SAOLEI_ROM 即可切换到这个程序;不定义就是下面的跑马灯。 `include "cpu21_saolei_rom.vh" `elsif 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), // 三个中断入口:必须与 sw/linker.ld 里 // .text.isr1/2/3 的地址一致 // IRQ1(0x100) = 关卡(游戏)入口 // IRQ2(0x200) = 鼠标读取 // IRQ3(0x300) = 备用 .IRQ1_VECTOR (32'h0000_0100), .IRQ2_VECTOR (32'h0000_0200), .IRQ3_VECTOR (32'h0000_0300) ) u_cpu ( .clk (clk_cpu), .reset (reset), .instr_i (instr_word), .data_rdata_i(data_rdata), .irq_i (irq_vec), .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), // 扫雷扩展外设 .seg_data_o (seg_data), .seg_valid_o (seg_valid), .fb_we_o (fb_we), .fb_waddr_o (fb_waddr), .fb_wdata_o (fb_wdata), .fb_wstrb_o (fb_wstrb), .fb_rdata_i (fb_rdata), .disp_ctrl_o (disp_ctrl), .disp_cursor_o (disp_cursor), .mouse_ev_valid_i(ev_valid), .mouse_dx_i (ev_dx), .mouse_dy_i (ev_dy), .mouse_btn_i (ev_btn), .sw_i (sw_vec), .frame_tick_i (frame_tick), .mouse_irq_o (mouse_irq), .level_irq_o (level_irq) ); // --------------------------------------------------------------------------- // 板载 LED:显示 MMIO 写入值的低 16 位;写入指示拉长便于观察 // --------------------------------------------------------------------------- assign led = led_data[15:0]; reg [23:0] led_we_cnt; always @(posedge clk_cpu or posedge arst) begin if (arst) led_we_cnt <= 24'd0; else if (led_valid) led_we_cnt <= 24'hFF_FFFF; // 约 0.34 s @50 MHz else if (led_we_cnt != 24'd0) led_we_cnt <= led_we_cnt - 24'd1; end assign led_we = (led_we_cnt != 24'd0); // =========================================================================== // 扫雷外设:显存 + VGA + 七段数码管 + 鼠标事件源 + 中断连线 // // 显存是一块 1200 字节的双口 RAM(40x30 格,每格 1 字节):CPU 通过 MMIO 的 // fb_* 写端口写入,VGA 用另一个读端口组合读出。两者同处 clk_cpu 域(VGA 的 // 25 MHz 像素节拍由使能信号产生,不是独立时钟),因此没有跨时钟域问题。 // =========================================================================== localparam integer FB_WORDS = 300; // 300 字 = 1200 字节 // ---- 显存 ---- reg [31:0] fb [0:FB_WORDS-1]; integer k; initial begin for (k = 0; k < FB_WORDS; k = k + 1) fb[k] = 32'b0; end always @(posedge clk_cpu) begin if (fb_we) begin if (fb_wstrb[0]) fb[fb_waddr][7:0] <= fb_wdata[7:0]; if (fb_wstrb[1]) fb[fb_waddr][15:8] <= fb_wdata[15:8]; if (fb_wstrb[2]) fb[fb_waddr][23:16] <= fb_wdata[23:16]; if (fb_wstrb[3]) fb[fb_waddr][31:24] <= fb_wdata[31:24]; end end wire [31:0] fb_rdata = fb[fb_raddr]; // ---- VGA 像素节拍:clk_cpu 二分频 => 25 MHz ---- reg px_q; always @(posedge clk_cpu or posedge arst) begin if (arst) px_q <= 1'b0; else px_q <= ~px_q; end wire px_tick = ~px_q; cpu21_vga u_vga ( .clk (clk_cpu), .reset (reset), .tick (px_tick), .fb_raddr_o (fb_raddr), .fb_word_i (fb_rdata), .ctrl_i (disp_ctrl), .cursor_i (disp_cursor), .vga_r (vga_r), .vga_g (vga_g), .vga_b (vga_b), .vga_hs (vga_hs), .vga_vs (vga_vs), .frame_tick_o(frame_tick) ); // ---- 七段数码管(显示项目程序写的 8 位十六进制数)---- cpu21_seg7 u_seg7 ( .clk (clk_cpu), .reset (reset), .seg_data_i(seg_data), .an_en_i (8'hFF), .an_o (an), .seg_o (seg), .dp_o (dp) ); // ---- 鼠标事件源 ---- wire ev_valid; wire [7:0] ev_dx, ev_dy; wire [4:0] ev_btn; wire [4:0] btn_vec = {go, btnu, btnd, btnl, btnr}; // {BTNC, BTNU, BTND, BTNL, BTNR} generate if (USE_PS2_MOUSE != 0) begin : g_ps2 cpu21_ps2_mouse u_ps2 ( .clk (clk_cpu), .reset (reset), .ps2_clk_i (ps2_clk), .ps2_clk_oe_o (), .ps2_data_i (ps2_data), .ps2_data_oe_o(), Tcl 一键重建 .ev_valid_o (ev_valid), .ev_dx_o (ev_dx), .ev_dy_o (ev_dy), .ev_btn_o (ev_btn), .rx_count_o () ) ; end else begin : g_btn cpu21_pointer #( .SAMPLE_DIV(50000) ) u_ptr ( .clk (clk_cpu), .reset (reset), .btn_i (btn_vec), .ev_valid_o(ev_valid), .ev_dx_o (ev_dx), .ev_dy_o (ev_dy), .ev_btn_o (ev_btn) ); end endgenerate endmodule `default_nettype wire