- cpu21_mmio.v: sel_fb 原写成 addr[12] && !addr[11](那是 0x1000-0x17FF), 导致 0x2000+ 的显存读写全不命中, 而鼠标/显示寄存器写反而落进显存。 改为 bit13=1 && bit12=0 && bit11=0 后扫雷画面才正常: 修复前仿真 fb_we 只有 6 次写(画面全空), 修复后开始界面 1246 次、进关卡后 2775 次 - cpu21_board_top.v: 新增 POINTER_SAMPLE_DIV 参数(默认仍为上板的 50000), 仿真传 50 即可让按键在几十微秒内被识别(defparam 对 generate 块内实例无效) - saolei/tools/elf2hex.py: .text.isr1/2/3 之间的空洞(0 字)填 NOP —— objcopy 留下的 0x00000000 在本 CPU 上等同自跳指令, PC 掉进去就出不来 - saolei/tools/fb2png.py + srcs/tb/tb_saolei.v: 无 VGA 显示器的调试台 —— 跑板级顶层、 按键当鼠标、把 40x30 显存 dump 成文本, 再按 cpu21_vga.v 的字库/调色板还原 PNG, 并附 ASCII 图 + RAM 变量视图 + PC 热点直方图
473 lines
23 KiB
Verilog
473 lines
23 KiB
Verilog
`timescale 1ns / 1ps
|
||
`default_nettype none
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// 上板顶层(Nexys4DDR,xc7a100tcsg324-1)
|
||
//
|
||
// 处理器顶层 cpu21_riscv_redirect_int_bpb 暴露了 775 位调试端口(各级 PC/IR、
|
||
// 计数器、BPB/中断观测等),直接作为实现顶层会因 I/O 超限而无法布局布线
|
||
// ([Place 30-415] IO Placement failed due to overutilization)。本模块只把
|
||
// 开发板真正需要的外设引出来,其余调试端口不连接,因此不占用引脚:
|
||
//
|
||
// clk100 (E3) -> CPU 时钟(100 MHz,经 CPU_DIV 分频后给 CPU;默认 50 MHz,
|
||
// 跑中断综合测试程序时用更慢的 CLK_DIV_INTRUPT,
|
||
// 让数码管上每个数值停留更久,便于观察)
|
||
// rst_n (C12) -> 复位按键 CPU_RESETN,低有效,经复位同步器
|
||
// go (N17) -> 按键 BTNC:ecall 停机后按下继续运行
|
||
// irq_sw[2:0] -> 拨码开关 SW[2:0]:3 路中断源
|
||
// led[15:0] -> 板载 LED[15:0]:LED[2:0] = 三路中断指示,从该路中断
|
||
// 请求出现开始点亮,一直到它的 ISR 执行 uret 返回后才熄灭;
|
||
// LED[15:3] 熄灭
|
||
// led_we (R12) -> LED16_B:有 MMIO 写入时亮,并拉长约 0.34 s 便于观察
|
||
// an/seg/dp -> 板载 8 位七段数码管:显示程序写入 LED_ADDR 的 32 位
|
||
// 十六进制数据(每 4 位对应一个数码管)
|
||
//
|
||
// 片内存储器:
|
||
// 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,
|
||
// 中断综合测试程序(RISC_V_INTRUPT_CCAB_ROM)专用的 CPU 分频比。
|
||
// 该程序每次显示之间的延时常量是按 50 MHz 调的:50 MHz 下每个数值只停留
|
||
// 约 10 ms,数码管上根本读不出来。停留时间与分频比成正比,例如:
|
||
// 8 → 12.5 MHz(每个数值约 40 ms)、16 → 6.25 MHz(约 80 ms)、
|
||
// 32 → 3.125 MHz(约 160 ms)、64 → 1.5625 MHz(约 320 ms)。
|
||
// 取大于 2 的值只是把实际时钟变慢,XDC 的 cpu_clk 仍按最坏情况 50 MHz 约束,
|
||
// 所以不会掩盖时序违例(约束偏严)。
|
||
parameter integer CLK_DIV_INTRUPT = 32,
|
||
// 鼠标事件源:0 = 板上 5 向按键(免外设,默认);1 = PS/2 鼠标(Pmod JA)
|
||
parameter integer USE_PS2_MOUSE = 0,
|
||
// 5 向按键去抖的采样周期(CPU 周期数):上板默认 50000(1 ms @50 MHz)。
|
||
// 仿真里可以传一个小值,让"按一下"在几十微秒内就被识别。
|
||
parameter integer POINTER_SAMPLE_DIV = 50000
|
||
) (
|
||
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);
|
||
|
||
// CPU 时钟实际用的分频比:中断综合测试程序用更慢的 CLK_DIV_INTRUPT,
|
||
// 其余程序用 CLK_DIV。两者必须都是偶数(保证 50% 占空比)。
|
||
`ifdef RISC_V_INTRUPT_CCAB_ROM
|
||
|
||
localparam integer CPU_DIV = CLK_DIV_INTRUPT;
|
||
`elsif RISC_V_INTRUPT_SLLSB_ROM
|
||
localparam integer CPU_DIV = CLK_DIV_INTRUPT;
|
||
`else
|
||
localparam integer CPU_DIV = CLK_DIV;
|
||
`endif
|
||
|
||
// 数码管扫描分频:目标是“每位数码管约 125 Hz 刷新”(即每步 1 ms),与 CPU
|
||
// 时钟频率无关。每步时间 = REFRESH_DIV / f_cpu,而 f_cpu = 100 MHz / CPU_DIV,
|
||
// 所以 REFRESH_DIV = 1 ms × f_cpu = 100000 / CPU_DIV:
|
||
// CPU_DIV = 2( 50 MHz)-> 50000(每步 1 ms,每位数码管 125 Hz)
|
||
// CPU_DIV = 8(12.5 MHz)-> 12500(同上)
|
||
// 例:CPU_DIV = 32(3.125 MHz)-> 3125(每步仍是 1 ms)。
|
||
// 不这样缩放的话,CPU 降频时扫描会按同样比例变慢,肉眼能看到闪烁。
|
||
localparam integer SEG_REFRESH_DIV = 100000 / CPU_DIV;
|
||
|
||
wire arst = ~rst_n; // 异步复位(高有效)
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 时钟:100 MHz / CPU_DIV 给 CPU。默认 50 MHz(CLK_DIV=2);跑中断综合测试
|
||
// 程序时用更慢的 CLK_DIV_INTRUPT(见该参数说明),让数码管上的数值停留更久。
|
||
//
|
||
// 乘除法已经是 cpu21_riscv_muldiv 里的多周期单元,关键路径上不再有组合的
|
||
// 32x32 乘法/除法;剩下的违例路径是 EX 级“分支判决/中断保存 → BPB 更新”,
|
||
// 与乘除法无关,实测 100 MHz 下 WNS = -1.1 ns,50 MHz 下余量约 +8 ns。
|
||
// 分频后的时钟由 Vivado 自动插 BUFG;XDC 里用 create_generated_clock 声明。
|
||
// XDC 按最坏情况固定成 -divide_by 2(50 MHz),所以这里把实际时钟变慢属于
|
||
// “约束偏严”,不会掩盖时序违例。
|
||
//
|
||
// 中断综合测试程序会用更慢的 CPU_DIV(见 CLK_DIV_INTRUPT):程序里的延时常量
|
||
// 按 50 MHz 调好,50 MHz 下每个数值只停留约 10 ms,读数根本看不清;
|
||
// 停留时间与分频比成正比,具体见 CLK_DIV_INTRUPT 的说明。
|
||
// 注意:VGA 的 25 MHz 像素节拍也是从这个 CPU 时钟分频出来的,CPU 降频时
|
||
// VGA 行场时序会跟着变慢(超出 VGA 规范)。中断测试程序不用 VGA,无影响。
|
||
// ---------------------------------------------------------------------------
|
||
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 == (CPU_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; // LED_ADDR 写入值,同时作为七段数码管显示数据
|
||
wire led_valid;
|
||
// 扫雷扩展外设的连线(先声明,例化在下面)
|
||
wire [31:0] disp_ctrl, disp_cursor; // 显示控制器(CPU 内部 MMIO 驱动)
|
||
wire [31:0] seg_data; // 保留的独立数码管 MMIO 数据
|
||
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 [1:0] irq_level; // 当前正在服务的中断级别(0 = 不在中断里,1/2/3 = 在 ISR 里)
|
||
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 RISC_V_INTRUPT_CCAB_ROM
|
||
// 中断综合测试程序:三个中断入口由镜像中的实际标签位置决定。
|
||
`include "risc-v-intrupt-ccab.vh"
|
||
`elsif RISC_V_INTRUPT_SLLSB_ROM
|
||
// 课程新版中断测试(附加 SLL/SB/BLTU),ISR 入口 0x0AC / 0x240 / 0x3E8。
|
||
`include "risc-v-intrupt-sllsb.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),
|
||
// 中断入口必须与所选 ROM 镜像中的 ISR 起点一致。
|
||
`ifdef RISC_V_INTRUPT_CCAB_ROM
|
||
// risc-v-intrupt-ccab.hex:字 43/121/194
|
||
.IRQ1_VECTOR (32'h0000_00AC),
|
||
.IRQ2_VECTOR (32'h0000_01E4),
|
||
.IRQ3_VECTOR (32'h0000_0308)
|
||
`elsif RISC_V_INTRUPT_SLLSB_ROM
|
||
// risc-v-intrupt-sllsb.hex:字 43/144/250(加了 SLL/SB/BLTU 后 ISR2/3 后移)
|
||
.IRQ1_VECTOR (32'h0000_00AC),
|
||
.IRQ2_VECTOR (32'h0000_0240),
|
||
.IRQ3_VECTOR (32'h0000_03E8)
|
||
`else
|
||
// 扫雷程序:linker.ld 中固定的 0x100/0x200/0x300
|
||
.IRQ1_VECTOR (32'h0000_0100),
|
||
.IRQ2_VECTOR (32'h0000_0200),
|
||
.IRQ3_VECTOR (32'h0000_0300)
|
||
`endif
|
||
) 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 使用(见下面 LED 那一段)
|
||
.irq_current_level_o(irq_level)
|
||
);
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 板载 LED:三路中断指示;十六进制数据改由七段数码管显示。
|
||
//
|
||
// 第 k 路中断从"请求出现"开始点亮,一直亮到它的 ISR 执行完 uret(中断返回)
|
||
// 之后才熄灭:
|
||
// 置位:irq_vec[k] 的上升沿。中断控制器内部也是按上升沿响应中断的
|
||
// (cpu21_riscv_irq_ctrl 里的 irq_event),两者行为一致;开关一直
|
||
// 接通不会重复点亮,只有新的上升沿才会重新置位。
|
||
// 清零:服务级别 irq_level 从 k+1 **降下来**,说明第 k+1 级的中断已经返回。
|
||
// 注意必须判断“下降”:级别从 1 升到 2 是深层中断抢占,此时第 1 级
|
||
// 的中断还在服务中,LED 不能熄。中断可以嵌套,返回时按级别逐层降
|
||
// 下来,因此每一路都在自己的 ISR 结束时熄灭。
|
||
// ---------------------------------------------------------------------------
|
||
reg [2:0] irq_req_q; // 中断请求打一拍,用于取上升沿
|
||
reg [1:0] irq_level_q; // 服务级别打一拍,用于检测"返回"
|
||
reg [2:0] irq_led_q; // 三路中断指示
|
||
|
||
wire isr_ret0 = (irq_level_q == 2'd1) && (irq_level < irq_level_q);
|
||
wire isr_ret1 = (irq_level_q == 2'd2) && (irq_level < irq_level_q);
|
||
wire isr_ret2 = (irq_level_q == 2'd3) && (irq_level < irq_level_q);
|
||
|
||
always @(posedge clk_cpu or posedge arst) begin
|
||
if (arst) begin
|
||
irq_req_q <= 3'b000;
|
||
irq_level_q <= 2'd0;
|
||
irq_led_q <= 3'b000;
|
||
end
|
||
else begin
|
||
irq_req_q <= irq_vec;
|
||
irq_level_q <= irq_level;
|
||
// 请求上升沿 -> 点亮;对应级别的 ISR 返回 -> 熄灭
|
||
irq_led_q[0] <= (irq_vec[0] & ~irq_req_q[0]) | (irq_led_q[0] & ~isr_ret0);
|
||
irq_led_q[1] <= (irq_vec[1] & ~irq_req_q[1]) | (irq_led_q[1] & ~isr_ret1);
|
||
irq_led_q[2] <= (irq_vec[2] & ~irq_req_q[2]) | (irq_led_q[2] & ~isr_ret2);
|
||
end
|
||
end
|
||
|
||
assign led = {{13{1'b0}}, irq_led_q};
|
||
|
||
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
|
||
// (12.5 MHz 下约 1.36 s)
|
||
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)
|
||
);
|
||
|
||
// ---- 七段数码管(显示程序写入 LED_ADDR 的 8 位十六进制数)----
|
||
// 扫描分频按 CPU 时钟频率缩放,保持每位数码管约 125 Hz 刷新(不闪烁)。
|
||
cpu21_seg7 #(
|
||
.REFRESH_DIV(SEG_REFRESH_DIV)
|
||
) u_seg7 (
|
||
.clk (clk_cpu),
|
||
.reset (reset),
|
||
.seg_data_i(led_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(),
|
||
.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(POINTER_SAMPLE_DIV)
|
||
) 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
|