feat(saolei): 显存扩成每格 2 字节 + 内置 A-Z 字模;开始界面加文字、难度选择和黑背景

显示(RTL)
- cpu21_vga.v:显存从"每格 1 字节"改成"每格 2 字节"(第 1 字节是 6 位字形、
  第 2 字节是 3 位颜色),字地址由 cidx[10:2] 改为 cidx[10:1](1 字 = 2 格);
  字模表由 4 位扩到 6 位(0..63),新增 A-Z 大写字母(16..41),原 0..15 的
  数字/雷/旗/问号/横线/实心字形不变
- cpu21_mmio.v:显存窗口 0x2000-0x27FF -> 0x2000-0x2FFF(300 -> 600 字),
  fb_widx 相应取 addr_i[11:2]
- cpu21_board_top.v:FB_WORDS 300 -> 600(1200 -> 2400 字节)

软件
- mmio.h:fb_write() -> fb_write_cell(idx, glyph, color)(一格写两个 sb);
  删掉 CELL(),新增 G_A..G_Z 字形常量
- saolei.c:draw_cell / clear_screen 改用 fb_write_cell;开始界面显示
  MINES / EASY / NORMAL / HARD / START 文字(START 直接写在绿色按钮条上),
  不再是"两排雷图案 + 纯色条"
- 难度选择:新增 g_difficulty / g_mines,三档 20 / 40 / 60 雷;place_mines、
  胜利用的 opened_cnt 比较、toggle_flag 的剩余雷数都改读 g_mines;点难度那一行
  即切换并重画菜单,点 START 才把雷数交给关卡
- 全部索引运算仍只用移位/掩码(棋盘固定 16x16),没有引入除法

配色(修"数字 1 看不见")
- 数字 1 用 C_BLUE、游戏背景原来也是 C_BLUE,调色板里是同一个深蓝,所以数字 1
  完全看不见;现在菜单与关卡背景统一改成 C_BLACK,胜负闪动仍是绿/红/黑,
  数字本身的配色不动

工具与文档
- build.ps1:Python 启动器兼容 —— 有 py 用 py,没有就回退到 python
  (本机只有 python,之前 check_isa.py / elf2hex.py 两处都会中断)
- saolei/README.md:显存表(0x2000-0x2FFF、每格 2 字节)、格子编码(6 位字形、
  16..41 是 A-Z)、难度说明同步更新
- README.md / cpu21_board_top.v:仅格式化工具重排(Markdown 表格对齐、
  if/else 合并成一行),无语义变化

验证:build.ps1 全流程通过(972 条指令 ISA 检查全过;text=3888 B / data=0 /
bss=1444 B;入口 _start=0x0、isr1/2/3=0x100/0x200/0x300);xvlog 不带宏与带
SAOLEI_ROM 两种配置编译均无错;tools/check_rtl_text.ps1 体检 OK。
未验证:未跑行为仿真、未上板。
This commit is contained in:
2026-09-23 19:23:45 +08:00
parent db0a174bb7
commit cfcf2f4923
9 changed files with 2010 additions and 1877 deletions
+375 -397
View File
@@ -31,449 +31,427 @@
// 注意: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);
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% 占空比)。
// 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;
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;
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;
// 数码管扫描分频:目标是“每位数码管约 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; // 异步复位(高有效)
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;
// ---------------------------------------------------------------------------
// 时钟: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
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;
wire clk_cpu = clk_div;
// ---------------------------------------------------------------------------
// 复位同步器:按键异步置位、释放与 CPU 时钟同步
// ---------------------------------------------------------------------------
reg [2:0] rst_sync_q;
// ---------------------------------------------------------------------------
// 复位同步器:按键异步置位、释放与 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
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];
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 与"关卡入口"共用一个入口。
// 中断线对应关系(必须与 saolei/sw/linker.ld、mmio.h 的约定一致):
// irq_vec[0] = IRQ1 -> 入口 0x100 = 关卡(游戏),由 LEVEL 软件中断拉起
// irq_vec[1] = IRQ2 -> 入口 0x200 = 鼠标读取,由事件 FIFO 非空拉起
// irq_vec[2] = IRQ3 -> 入口 0x300 = 备用
// (原来 mouse 接在 [0]、level 接在 [1],正好对调:第一个鼠标事件会跳进关卡
// 入口把棋盘画出来,而鼠标 ISR 永不执行,FIFO 不弹空、中断线恒高 —— 于是
// 只能收到一次事件。拨码开关 SW[2:0] 仍按位 OR 进来,兼容原中断测试程序。)
wire [2:0] irq_vec = {irq_sw[2], (mouse_irq | irq_sw[1]), (level_irq | irq_sw[0])};
// 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 与"关卡入口"共用一个入口。
// 中断线对应关系(必须与 saolei/sw/linker.ld、mmio.h 的约定一致):
// irq_vec[0] = IRQ1 -> 入口 0x100 = 关卡(游戏),由 LEVEL 软件中断拉起
// irq_vec[1] = IRQ2 -> 入口 0x200 = 鼠标读取,由事件 FIFO 非空拉起
// irq_vec[2] = IRQ3 -> 入口 0x300 = 备用
// (原来 mouse 接在 [0]、level 接在 [1],正好对调:第一个鼠标事件会跳进关卡
// 入口把棋盘画出来,而鼠标 ISR 永不执行,FIFO 不弹空、中断线恒高 —— 于是
// 只能收到一次事件。拨码开关 SW[2:0] 仍按位 OR 进来,兼容原中断测试程序。)
wire [2:0] irq_vec = {irq_sw[2], (mouse_irq | irq_sw[1]), (level_irq | irq_sw[0])};
// ---------------------------------------------------------------------------
// 指令 ROM:组合读(未用到的地址填 NOP)
// ---------------------------------------------------------------------------
reg [31:0] rom[0:ROM_WORDS-1];
integer i;
// ---------------------------------------------------------------------------
// 指令 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 填充
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"
// 扫雷程序(由 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"
`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"
`include "cpu21_board_rom.vh"
`endif
end
end
// ---------------------------------------------------------------------------
// 数据 RAM:字节写使能 + 组合读
// ---------------------------------------------------------------------------
reg [31:0] ram[0:RAM_WORDS-1];
integer j;
// ---------------------------------------------------------------------------
// 数据 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;
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
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 起点一致。
// ---------------------------------------------------------------------------
// 处理器:只连接上板需要的端口,调试端口留空(不产生 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)
// 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)
// 扫雷程序: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),
) 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),
.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_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)
);
// 扫雷扩展外设
.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; // 三路中断指示
// ---------------------------------------------------------------------------
// 板载 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);
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
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};
assign led = {{13{1'b0}}, irq_led_q};
reg [23:0] led_we_cnt;
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;
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 + 七段数码管 + 鼠标事件源 + 中断连线
//
// 显存是一块 2400 字节的双口 RAM(40x30 格,每格 2 字节):CPU 通过 MMIO 的
// fb_* 写端口写入,VGA 用另一个读端口组合读出。两者同处 clk_cpu 域(VGA 的
// 25 MHz 像素节拍由使能信号产生,不是独立时钟),因此没有跨时钟域问题。
// ===========================================================================
localparam integer FB_WORDS = 600; // 600 字 = 2400 字节,40x30 格每格 2 字节
// ---- 显存 ----
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
assign led_we = (led_we_cnt != 24'd0);
wire [31:0] fb_rdata = fb[fb_raddr];
// ===========================================================================
// 扫雷外设:显存 + VGA + 七段数码管 + 鼠标事件源 + 中断连线
//
// 显存是一块 1200 字节的双口 RAM(40x30 格,每格 1 字节):CPU 通过 MMIO 的
// fb_* 写端口写入,VGA 用另一个读端口组合读出。两者同处 clk_cpu 域(VGA 的
// 25 MHz 像素节拍由使能信号产生,不是独立时钟),因此没有跨时钟域问题。
// ===========================================================================
localparam integer FB_WORDS = 300; // 300 字 = 1200 字节
// ---- 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)
);
// ---- 显存 ----
reg [31:0] fb[0:FB_WORDS-1];
integer k;
// ---- 七段数码管(显示程序写入 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)
);
initial begin
for (k = 0; k < FB_WORDS; k = k + 1)
fb[k] = 32'b0;
// ---- 鼠标事件源 ----
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
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
endgenerate
endmodule
File diff suppressed because it is too large Load Diff
+221 -252
View File
@@ -11,7 +11,7 @@
// 0x0000_1004 七段数码管:8 位十六进制数(每 4 位一个数码管,低 4 位 = 最右)
// 0x0000_1100 + 4*k 鼠标寄存器组(见下)
// 0x0000_1140 + 4*k 显示控制器寄存器(见下)
// 0x0000_2000 - 0x27FF 显存窗口:40x30 格、每格 1 字节(只用到前 1200 字节)
// 0x0000_2000 - 0x2FFF 显存窗口:40x30 格、每格 2 字节(共 2400 字节)
//
// 鼠标寄存器(基址 0x1100):
// +0x00 STATUS RO [0] FIFO 非空 [5:1] 项数 [6] 中断使能 [7] FIFO 满
@@ -41,272 +41,241 @@
// 以后要增加外设(UART、SD 卡等)时只需在这里扩展地址译码。
// -----------------------------------------------------------------------------
module cpu21_mmio #(
parameter [31:0] LED_ADDR = 32'h0000_1000,
parameter integer FB_WORDS = 300,
parameter integer FIFO_DEPTH = 16
) (
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,
// 七段数码管:8 位十六进制数
output reg [31:0] seg_data_o,
output reg seg_valid_o,
// 显存写端口(板级顶层的双口 RAM)
output wire fb_we_o,
output wire [ 9:0] fb_waddr_o,
output wire [31:0] fb_wdata_o,
output wire [ 3:0] fb_wstrb_o,
input wire [31:0] fb_rdata_i,
// 显示控制器寄存器(给 VGA 用)
output wire [31:0] disp_ctrl_o,
output wire [31:0] disp_cursor_o,
// 鼠标事件源(来自板级顶层:按键去抖 / PS/2 解析)
input wire mouse_ev_valid_i,
input wire [ 7:0] mouse_dx_i,
input wire [ 7:0] mouse_dy_i,
input wire [ 4:0] mouse_btn_i,
input wire [15:0] sw_i,
input wire frame_tick_i,
// 中断
output wire mouse_irq_o,
output wire level_irq_o
);
// ---------------------------------------------------------------------------
// 地址译码(都按"字"对齐:同一 32 位寄存器内的任意字节偏移都算命中)
// ---------------------------------------------------------------------------
localparam [31:0] SEG_ADDR = 32'h0000_1004;
localparam [31:0] MOUSE_BASE = 32'h0000_1100;
localparam [31:0] DISP_BASE = 32'h0000_1140;
localparam integer FIFO_AW = 4;
localparam [FIFO_AW:0] FIFO_FULL_V = FIFO_DEPTH[FIFO_AW:0];
parameter [31:0] LED_ADDR = 32'h0000_1000,
parameter integer FB_WORDS = 300,
parameter integer FIFO_DEPTH = 16
) (
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,
// 七段数码管:8 位十六进制数
output reg [31:0] seg_data_o,
output reg seg_valid_o,
// 显存写端口(板级顶层的双口 RAM)
output wire fb_we_o,
output wire [ 9:0] fb_waddr_o,
output wire [31:0] fb_wdata_o,
output wire [ 3:0] fb_wstrb_o,
input wire [31:0] fb_rdata_i,
// 显示控制器寄存器(给 VGA 用)
output wire [31:0] disp_ctrl_o,
output wire [31:0] disp_cursor_o,
// 鼠标事件源(来自板级顶层:按键去抖 / PS/2 解析)
input wire mouse_ev_valid_i,
input wire [ 7:0] mouse_dx_i,
input wire [ 7:0] mouse_dy_i,
input wire [ 4:0] mouse_btn_i,
input wire [15:0] sw_i,
input wire frame_tick_i,
// 中断
output wire mouse_irq_o,
output wire level_irq_o
);
// ---------------------------------------------------------------------------
// 地址译码(都按"字"对齐:同一 32 位寄存器内的任意字节偏移都算命中)
// ---------------------------------------------------------------------------
localparam [31:0] SEG_ADDR = 32'h0000_1004;
localparam [31:0] MOUSE_BASE = 32'h0000_1100;
localparam [31:0] DISP_BASE = 32'h0000_1140;
localparam integer FIFO_AW = 4;
localparam [FIFO_AW:0] FIFO_FULL_V = FIFO_DEPTH[FIFO_AW:0];
wire sel_led = (addr_i[31:2] == LED_ADDR[31:2]);
wire sel_seg = (addr_i[31:2] == SEG_ADDR[31:2]);
wire sel_mouse = (addr_i[31:8] == MOUSE_BASE[31:8]) && (addr_i[7:6] == 2'b00);
wire sel_disp = (addr_i[31:8] == DISP_BASE[31:8]) && (addr_i[7:6] == 2'b01);
// 显存窗口 0x2000 - 0x27FF:bit13=1、bit12=0、bit11=0,高位全 0
// (原来写成 addr[12] && !addr[11],要求 bit12=1 —— 那是 0x1000-0x17FF,
// 于是 0x2000+ 的读写全都不命中、而鼠标/显示寄存器写反而落进了显存)
wire sel_fb = (addr_i[31:14] == 18'd0) && addr_i[13] && !addr_i[12] && !addr_i[11];
wire sel_led = (addr_i[31:2] == LED_ADDR[31:2]);
wire sel_seg = (addr_i[31:2] == SEG_ADDR[31:2]);
wire sel_mouse = (addr_i[31:8] == MOUSE_BASE[31:8]) && (addr_i[7:6] == 2'b00);
wire sel_disp = (addr_i[31:8] == DISP_BASE[31:8]) && (addr_i[7:6] == 2'b01);
// 显存窗口 0x2000 - 0x2FFF:共 600 个字(每格两个字节)。
wire sel_fb = (addr_i[31:12] == 20'h00002);
wire [3:0] midx = addr_i[5:2];
wire [3:0] didx = addr_i[5:2];
wire [9:0] fb_widx = addr_i[10:2];
wire fb_ok = (fb_widx < FB_WORDS);
wire [3:0] midx = addr_i[5:2];
wire [3:0] didx = addr_i[5:2];
wire [9:0] fb_widx = addr_i[11:2];
wire fb_ok = (fb_widx < FB_WORDS);
wire led_wr = mem_valid_i && mem_write_i && sel_led;
wire led_rd = mem_valid_i && mem_read_i && sel_led;
wire seg_wr = mem_valid_i && mem_write_i && sel_seg;
wire seg_rd = mem_valid_i && mem_read_i && sel_seg;
wire mo_wr = mem_valid_i && mem_write_i && sel_mouse;
wire mo_rd = mem_valid_i && mem_read_i && sel_mouse;
wire dp_wr = mem_valid_i && mem_write_i && sel_disp;
wire dp_rd = mem_valid_i && mem_read_i && sel_disp;
wire fb_wr = mem_valid_i && mem_write_i && sel_fb && fb_ok;
wire fb_rd = mem_valid_i && mem_read_i && sel_fb && fb_ok;
wire led_wr = mem_valid_i && mem_write_i && sel_led;
wire led_rd = mem_valid_i && mem_read_i && sel_led;
wire seg_wr = mem_valid_i && mem_write_i && sel_seg;
wire seg_rd = mem_valid_i && mem_read_i && sel_seg;
wire mo_wr = mem_valid_i && mem_write_i && sel_mouse;
wire mo_rd = mem_valid_i && mem_read_i && sel_mouse;
wire dp_wr = mem_valid_i && mem_write_i && sel_disp;
wire dp_rd = mem_valid_i && mem_read_i && sel_disp;
wire fb_wr = mem_valid_i && mem_write_i && sel_fb && fb_ok;
wire fb_rd = mem_valid_i && mem_read_i && sel_fb && fb_ok;
assign fb_we_o = fb_wr;
assign fb_waddr_o = fb_widx;
assign fb_wdata_o = wdata_i;
assign fb_wstrb_o = wstrb_i;
assign fb_we_o = fb_wr;
assign fb_waddr_o = fb_widx;
assign fb_wdata_o = wdata_i;
assign fb_wstrb_o = wstrb_i;
assign rdata_hit_o = led_rd | seg_rd | mo_rd | dp_rd | fb_rd;
assign rdata_hit_o = led_rd | seg_rd | mo_rd | dp_rd | fb_rd;
// 字节写合并:sw 改 4 字节、sb 只改 1 字节(与 RAM 的字节写语义一致)
function [31:0] merge_bytes;
input [31:0] new_v;
input [31:0] old_v;
input [ 3:0] strb;
begin
merge_bytes[7:0] = strb[0] ? new_v[7:0] : old_v[7:0];
merge_bytes[15:8] = strb[1] ? new_v[15:8] : old_v[15:8];
merge_bytes[23:16] = strb[2] ? new_v[23:16] : old_v[23:16];
merge_bytes[31:24] = strb[3] ? new_v[31:24] : old_v[31:24];
end
endfunction
// ---------------------------------------------------------------------------
// LED:保持原有语义(led_valid_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 <= 1'b0;
if (led_wr) begin
led_data_o <= merge_bytes(wdata_i, led_data_o, wstrb_i);
led_valid_o <= 1'b1;
end
end
// 字节写合并:sw 改 4 字节、sb 只改 1 字节(与 RAM 的字节写语义一致)
function [31:0] merge_bytes;
input [31:0] new_v;
input [31:0] old_v;
input [3:0] strb;
begin
merge_bytes[7:0] = strb[0] ? new_v[7:0] : old_v[7:0];
merge_bytes[15:8] = strb[1] ? new_v[15:8] : old_v[15:8];
merge_bytes[23:16] = strb[2] ? new_v[23:16] : old_v[23:16];
merge_bytes[31:24] = strb[3] ? new_v[31:24] : old_v[31:24];
end
endfunction
// ---------------------------------------------------------------------------
// 七段数码管:8 位十六进制数(低 4 位 = 最右边那个数码管)
// ---------------------------------------------------------------------------
always @(posedge clk or posedge reset) begin
if (reset) begin
seg_data_o <= 32'b0;
seg_valid_o <= 1'b0;
end
else begin
seg_valid_o <= 1'b0;
if (seg_wr) begin
seg_data_o <= merge_bytes(wdata_i, seg_data_o, wstrb_i);
seg_valid_o <= 1'b1;
end
end
// ---------------------------------------------------------------------------
// LED:保持原有语义(led_valid_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 <= 1'b0;
if (led_wr) begin
led_data_o <= merge_bytes(wdata_i, led_data_o, wstrb_i);
led_valid_o <= 1'b1;
end
end
end
// ---------------------------------------------------------------------------
// 显示控制器寄存器(显存本体在板级顶层)
// ---------------------------------------------------------------------------
reg [31:0] disp_ctrl_q, disp_cursor_q;
assign disp_ctrl_o = disp_ctrl_q;
assign disp_cursor_o = disp_cursor_q;
// ---------------------------------------------------------------------------
// 鼠标:事件 FIFO + 寄存器 + 中断
// ---------------------------------------------------------------------------
reg [31:0] fifo [0:FIFO_DEPTH-1];
reg [FIFO_AW-1:0] wr_ptr_q, rd_ptr_q;
reg [FIFO_AW:0] cnt_q;
reg [31:0] mctrl_q, time_q, frame_q, evcnt_q;
reg level_q;
wire fifo_empty = (cnt_q == {FIFO_AW+1{1'b0}});
wire fifo_full = (cnt_q == FIFO_FULL_V);
wire [31:0] fifo_head = fifo[rd_ptr_q];
wire irq_en = mctrl_q[0];
assign mouse_irq_o = irq_en && !fifo_empty;
assign level_irq_o = level_q;
wire push = mouse_ev_valid_i && !fifo_full;
wire pop = mo_wr && (midx == 4'd2) && !fifo_empty;
always @(posedge clk or posedge reset) begin
if (reset) begin
mctrl_q <= 32'h0000_0001; // 鼠标中断默认打开
time_q <= 32'd0;
frame_q <= 32'd0;
evcnt_q <= 32'd0;
level_q <= 1'b0;
wr_ptr_q <= {FIFO_AW{1'b0}};
rd_ptr_q <= {FIFO_AW{1'b0}};
cnt_q <= {(FIFO_AW+1){1'b0}};
disp_ctrl_q <= 32'h0000_0001; // 光标可见,背景色 0
disp_cursor_q <= 32'h0000_0000;
end
else begin
time_q <= time_q + 32'd1;
if (frame_tick_i)
frame_q <= frame_q + 32'd1;
if (push) begin
fifo[wr_ptr_q] <= {11'b0, mouse_btn_i, mouse_dy_i, mouse_dx_i};
wr_ptr_q <= wr_ptr_q + 1'b1;
evcnt_q <= evcnt_q + 32'd1;
end
if (pop)
rd_ptr_q <= rd_ptr_q + 1'b1;
cnt_q <= cnt_q + (push ? 1'b1 : 1'b0) - (pop ? 1'b1 : 1'b0);
// 鼠标寄存器:CTRL(3) / LEVEL(8)
if (mo_wr) begin
case (midx)
4'd3:
mctrl_q <= merge_bytes(wdata_i, mctrl_q, wstrb_i);
4'd8:
level_q <= wdata_i[0];
default:
;
endcase
end
// 显示控制器:CTRL(0) / CURSOR(1)
if (dp_wr) begin
case (didx)
4'd0:
disp_ctrl_q <= merge_bytes(wdata_i, disp_ctrl_q, wstrb_i);
4'd1:
disp_cursor_q <= merge_bytes(wdata_i, disp_cursor_q, wstrb_i);
default:
;
endcase
end
end
// ---------------------------------------------------------------------------
// 七段数码管:8 位十六进制数(低 4 位 = 最右边那个数码管)
// ---------------------------------------------------------------------------
always @(posedge clk or posedge reset) begin
if (reset) begin
seg_data_o <= 32'b0;
seg_valid_o <= 1'b0;
end else begin
seg_valid_o <= 1'b0;
if (seg_wr) begin
seg_data_o <= merge_bytes(wdata_i, seg_data_o, wstrb_i);
seg_valid_o <= 1'b1;
end
end
end
// ---------------------------------------------------------------------------
// 读数据(组合,与"指令存储器组合读/数据存储器组合读"的时序一致)
// ---------------------------------------------------------------------------
reg [31:0] rdata_mux;
// ---------------------------------------------------------------------------
// 显示控制器寄存器(显存本体在板级顶层)
// ---------------------------------------------------------------------------
reg [31:0] disp_ctrl_q, disp_cursor_q;
always @* begin
rdata_mux = 32'b0;
if (led_rd) begin
rdata_mux = led_data_o;
end
else if (seg_rd) begin
rdata_mux = seg_data_o;
end
else if (fb_rd) begin
rdata_mux = fb_rdata_i;
end
else if (mo_rd) begin
case (midx)
4'd0:
rdata_mux = {24'b0, fifo_full, irq_en, cnt_q[4:0], !fifo_empty};
4'd1:
rdata_mux = fifo_head;
4'd3:
rdata_mux = mctrl_q;
4'd4:
rdata_mux = {11'b0, mouse_btn_i, sw_i};
4'd5:
rdata_mux = time_q;
4'd6:
rdata_mux = frame_q;
4'd7:
rdata_mux = evcnt_q;
4'd8:
rdata_mux = {31'b0, level_q};
4'd9:
rdata_mux = 32'hCAFE_0002;
default:
rdata_mux = 32'b0;
endcase
end
else if (dp_rd) begin
case (didx)
4'd0:
rdata_mux = disp_ctrl_q;
4'd1:
rdata_mux = disp_cursor_q;
4'd2:
rdata_mux = 32'hCAFE_0001;
default:
rdata_mux = 32'b0;
endcase
end
assign disp_ctrl_o = disp_ctrl_q;
assign disp_cursor_o = disp_cursor_q;
// ---------------------------------------------------------------------------
// 鼠标:事件 FIFO + 寄存器 + 中断
// ---------------------------------------------------------------------------
reg [31:0] fifo[0:FIFO_DEPTH-1];
reg [FIFO_AW-1:0] wr_ptr_q, rd_ptr_q;
reg [FIFO_AW:0] cnt_q;
reg [31:0] mctrl_q, time_q, frame_q, evcnt_q;
reg level_q;
wire fifo_empty = (cnt_q == {FIFO_AW + 1{1'b0}});
wire fifo_full = (cnt_q == FIFO_FULL_V);
wire [31:0] fifo_head = fifo[rd_ptr_q];
wire irq_en = mctrl_q[0];
assign mouse_irq_o = irq_en && !fifo_empty;
assign level_irq_o = level_q;
wire push = mouse_ev_valid_i && !fifo_full;
wire pop = mo_wr && (midx == 4'd2) && !fifo_empty;
always @(posedge clk or posedge reset) begin
if (reset) begin
mctrl_q <= 32'h0000_0001; // 鼠标中断默认打开
time_q <= 32'd0;
frame_q <= 32'd0;
evcnt_q <= 32'd0;
level_q <= 1'b0;
wr_ptr_q <= {FIFO_AW{1'b0}};
rd_ptr_q <= {FIFO_AW{1'b0}};
cnt_q <= {(FIFO_AW + 1) {1'b0}};
disp_ctrl_q <= 32'h0000_0001; // 光标可见,背景色 0
disp_cursor_q <= 32'h0000_0000;
end else begin
time_q <= time_q + 32'd1;
if (frame_tick_i) frame_q <= frame_q + 32'd1;
if (push) begin
fifo[wr_ptr_q] <= {11'b0, mouse_btn_i, mouse_dy_i, mouse_dx_i};
wr_ptr_q <= wr_ptr_q + 1'b1;
evcnt_q <= evcnt_q + 32'd1;
end
if (pop) rd_ptr_q <= rd_ptr_q + 1'b1;
cnt_q <= cnt_q + (push ? 1'b1 : 1'b0) - (pop ? 1'b1 : 1'b0);
// 鼠标寄存器:CTRL(3) / LEVEL(8)
if (mo_wr) begin
case (midx)
4'd3: mctrl_q <= merge_bytes(wdata_i, mctrl_q, wstrb_i);
4'd8: level_q <= wdata_i[0];
default: ;
endcase
end
// 显示控制器:CTRL(0) / CURSOR(1)
if (dp_wr) begin
case (didx)
4'd0: disp_ctrl_q <= merge_bytes(wdata_i, disp_ctrl_q, wstrb_i);
4'd1: disp_cursor_q <= merge_bytes(wdata_i, disp_cursor_q, wstrb_i);
default: ;
endcase
end
end
end
assign rdata_o = rdata_mux;
// ---------------------------------------------------------------------------
// 读数据(组合,与"指令存储器组合读/数据存储器组合读"的时序一致)
// ---------------------------------------------------------------------------
reg [31:0] rdata_mux;
always @* begin
rdata_mux = 32'b0;
if (led_rd) begin
rdata_mux = led_data_o;
end else if (seg_rd) begin
rdata_mux = seg_data_o;
end else if (fb_rd) begin
rdata_mux = fb_rdata_i;
end else if (mo_rd) begin
case (midx)
4'd0: rdata_mux = {24'b0, fifo_full, irq_en, cnt_q[4:0], !fifo_empty};
4'd1: rdata_mux = fifo_head;
4'd3: rdata_mux = mctrl_q;
4'd4: rdata_mux = {11'b0, mouse_btn_i, sw_i};
4'd5: rdata_mux = time_q;
4'd6: rdata_mux = frame_q;
4'd7: rdata_mux = evcnt_q;
4'd8: rdata_mux = {31'b0, level_q};
4'd9: rdata_mux = 32'hCAFE_0002;
default: rdata_mux = 32'b0;
endcase
end else if (dp_rd) begin
case (didx)
4'd0: rdata_mux = disp_ctrl_q;
4'd1: rdata_mux = disp_cursor_q;
4'd2: rdata_mux = 32'hCAFE_0001;
default: rdata_mux = 32'b0;
endcase
end
end
assign rdata_o = rdata_mux;
endmodule
`default_nettype wire
+264 -174
View File
@@ -4,7 +4,7 @@
// -----------------------------------------------------------------------------
// cpu21_vga —— 640x480@60 的"格子"显示控制器(显存端口接板级顶层的双口 RAM)。
//
// CPU 不直接操作像素:它只通过 MMIO 往显示缓冲区里写"每格 1 字节"(字形 + 颜色),
// CPU 不直接操作像素:它只通过 MMIO 往显示缓冲区里写"每格 2 字节"(字形 + 颜色),
// 本模块按像素扫描把格子展开成画面(8x8 字模放大 2 倍填满 16x16 的格子)。
// 这样一格只要 1 次字节写,刷屏开销极小。
//
@@ -18,218 +18,308 @@
// (CTRL[0] 控制显示与否)。缓冲区字节的 bit7 在本模块中不使用(保留)。
// -----------------------------------------------------------------------------
module cpu21_vga #(
parameter integer H_VISIBLE = 640,
parameter integer H_FP = 16,
parameter integer H_SYNC = 96,
parameter integer H_BP = 48,
parameter integer V_VISIBLE = 480,
parameter integer V_FP = 10,
parameter integer V_SYNC = 2,
parameter integer V_BP = 33,
parameter integer COLS = 40,
parameter integer ROWS = 30
) (
input wire clk,
input wire reset,
input wire tick, // 25 MHz 像素使能(每 2 个 clk 一拍)
// 显示缓冲区读端口
output wire [ 9:0] fb_raddr_o,
input wire [31:0] fb_word_i,
// 控制
input wire [31:0] ctrl_i,
input wire [31:0] cursor_i,
// VGA 输出
output reg [ 3:0] vga_r,
output reg [ 3:0] vga_g,
output reg [ 3:0] vga_b,
output reg vga_hs,
output reg vga_vs,
output reg frame_tick_o
);
parameter integer H_VISIBLE = 640,
parameter integer H_FP = 16,
parameter integer H_SYNC = 96,
parameter integer H_BP = 48,
parameter integer V_VISIBLE = 480,
parameter integer V_FP = 10,
parameter integer V_SYNC = 2,
parameter integer V_BP = 33,
parameter integer COLS = 40,
parameter integer ROWS = 30
) (
input wire clk,
input wire reset,
input wire tick, // 25 MHz 像素使能(每 2 个 clk 一拍)
// 显示缓冲区读端口
output wire [ 9:0] fb_raddr_o,
input wire [31:0] fb_word_i,
// 控制
input wire [31:0] ctrl_i,
input wire [31:0] cursor_i,
// VGA 输出
output reg [ 3:0] vga_r,
output reg [ 3:0] vga_g,
output reg [ 3:0] vga_b,
output reg vga_hs,
output reg vga_vs,
output reg frame_tick_o
);
localparam integer H_TOTAL = H_VISIBLE + H_FP + H_SYNC + H_BP; // 800
localparam integer V_TOTAL = V_VISIBLE + V_FP + V_SYNC + V_BP; // 525
localparam integer H_TOTAL = H_VISIBLE + H_FP + H_SYNC + H_BP; // 800
localparam integer V_TOTAL = V_VISIBLE + V_FP + V_SYNC + V_BP; // 525
// ---------------------------------------------------------------------------
// 扫描计数
// ---------------------------------------------------------------------------
reg [9:0] hx, vy;
// ---------------------------------------------------------------------------
// 扫描计数
// ---------------------------------------------------------------------------
reg [9:0] hx, vy;
always @(posedge clk or posedge reset) begin
if (reset) begin
hx <= 10'd0;
vy <= 10'd0;
end
else if (tick) begin
if (hx == H_TOTAL - 1) begin
hx <= 10'd0;
vy <= (vy == V_TOTAL - 1) ? 10'd0 : (vy + 10'd1);
end
else begin
hx <= hx + 10'd1;
end
end
always @(posedge clk or posedge reset) begin
if (reset) begin
hx <= 10'd0;
vy <= 10'd0;
end else if (tick) begin
if (hx == H_TOTAL - 1) begin
hx <= 10'd0;
vy <= (vy == V_TOTAL - 1) ? 10'd0 : (vy + 10'd1);
end else begin
hx <= hx + 10'd1;
end
end
end
wire de = (hx < H_VISIBLE) && (vy < V_VISIBLE);
wire de = (hx < H_VISIBLE) && (vy < V_VISIBLE);
// 同步信号在 tick 上寄存输出
wire hs_n = ~((hx >= (H_VISIBLE + H_FP)) && (hx < (H_VISIBLE + H_FP + H_SYNC)));
wire vs_n = ~((vy >= (V_VISIBLE + V_FP)) && (vy < (V_VISIBLE + V_FP + V_SYNC)));
// 同步信号在 tick 上寄存输出
wire hs_n = ~((hx >= (H_VISIBLE + H_FP)) && (hx < (H_VISIBLE + H_FP + H_SYNC)));
wire vs_n = ~((vy >= (V_VISIBLE + V_FP)) && (vy < (V_VISIBLE + V_FP + V_SYNC)));
// ---------------------------------------------------------------------------
// 格子索引与缓冲区读出
// ---------------------------------------------------------------------------
wire [5:0] ccol = hx[9:4]; // 0..39
wire [4:0] crow = vy[8:4]; // 0..29
wire [10:0] cidx = (crow << 5) + (crow << 3) + ccol; // row*40 + col
// ---------------------------------------------------------------------------
// 格子索引与缓冲区读出
// ---------------------------------------------------------------------------
wire [5:0] ccol = hx[9:4]; // 0..39
wire [4:0] crow = vy[8:4]; // 0..29
wire [10:0] cidx = (crow << 5) + (crow << 3) + ccol; // row*40 + col
assign fb_raddr_o = cidx[10:2]; // 字地址(1 字 = 4 格)
assign fb_raddr_o = cidx[10:1]; // 字地址(1 字 = 2 格)
wire [1:0] lane = cidx[1:0];
wire [31:0] cell_word = fb_word_i;
wire [7:0] cell_byte = cell_word >> {lane, 3'b000};
wire [ 1:0] lane = {cidx[0], 1'b0};
wire [ 1:0] color_lane = lane + 2'd1;
wire [31:0] cell_word = fb_word_i;
wire [ 7:0] glyph_byte = cell_word >> {lane, 3'b000};
wire [ 7:0] color_byte = cell_word >> {color_lane, 3'b000};
wire [3:0] glyph = cell_byte[3:0];
wire [2:0] gcolor = cell_byte[6:4];
// 全局背景色(字模点为 0 时显示的颜色)
wire [2:0] bgcolor = ctrl_i[7:4];
wire [ 5:0] glyph = glyph_byte[5:0];
wire [ 2:0] gcolor = color_byte[2:0];
// 全局背景色(字模点为 0 时显示的颜色)
wire [ 2:0] bgcolor = ctrl_i[7:4];
// ---------------------------------------------------------------------------
// 8x8 字模(bit7 = 最左像素),放大 2 倍填满 16x16 的格子
// ---------------------------------------------------------------------------
wire [2:0] fx = hx[3:1];
wire [2:0] fy = vy[3:1];
// ---------------------------------------------------------------------------
// 8x8 字模(bit7 = 最左像素),放大 2 倍填满 16x16 的格子;字形编号 0..63。
// ---------------------------------------------------------------------------
wire [ 2:0] fx = hx[3:1];
wire [ 2:0] fy = vy[3:1];
function [7:0] font_row;
input [3:0] g;
input [2:0] r;
begin
case (g)
4'd0:
font_row = 8'h00; // 空白
4'd1:
font_row = (r == 3'd0) ? 8'h18 : (r == 3'd1) ? 8'h38 : (r == 3'd2) ? 8'h18 :
function [7:0] font_row;
input [5:0] g;
input [2:0] r;
begin
case (g)
6'd0: font_row = 8'h00; // 空白
6'd1:
font_row = (r == 3'd0) ? 8'h18 : (r == 3'd1) ? 8'h38 : (r == 3'd2) ? 8'h18 :
(r == 3'd3) ? 8'h18 : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h18 :
(r == 3'd6) ? 8'h7E : 8'h00; // 1
4'd2:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h06 :
6'd2:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h06 :
(r == 3'd3) ? 8'h0C : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h30 :
(r == 3'd6) ? 8'h7E : 8'h00; // 2
4'd3:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h06 :
6'd3:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h06 :
(r == 3'd3) ? 8'h1C : (r == 3'd4) ? 8'h06 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 3
4'd4:
font_row = (r == 3'd0) ? 8'h0C : (r == 3'd1) ? 8'h1C : (r == 3'd2) ? 8'h3C :
6'd4:
font_row = (r == 3'd0) ? 8'h0C : (r == 3'd1) ? 8'h1C : (r == 3'd2) ? 8'h3C :
(r == 3'd3) ? 8'h6C : (r == 3'd4) ? 8'h7E : (r == 3'd5) ? 8'h0C :
(r == 3'd6) ? 8'h0C : 8'h00; // 4
4'd5:
font_row = (r == 3'd0) ? 8'h7E : (r == 3'd1) ? 8'h60 : (r == 3'd2) ? 8'h7C :
6'd5:
font_row = (r == 3'd0) ? 8'h7E : (r == 3'd1) ? 8'h60 : (r == 3'd2) ? 8'h7C :
(r == 3'd3) ? 8'h06 : (r == 3'd4) ? 8'h06 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 5
4'd6:
font_row = (r == 3'd0) ? 8'h1C : (r == 3'd1) ? 8'h30 : (r == 3'd2) ? 8'h60 :
6'd6:
font_row = (r == 3'd0) ? 8'h1C : (r == 3'd1) ? 8'h30 : (r == 3'd2) ? 8'h60 :
(r == 3'd3) ? 8'h7C : (r == 3'd4) ? 8'h66 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 6
4'd7:
font_row = (r == 3'd0) ? 8'h7E : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h0C :
6'd7:
font_row = (r == 3'd0) ? 8'h7E : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h0C :
(r == 3'd3) ? 8'h18 : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h18 :
(r == 3'd6) ? 8'h18 : 8'h00; // 7
4'd8:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h66 :
6'd8:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h66 :
(r == 3'd3) ? 8'h3C : (r == 3'd4) ? 8'h66 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 8
4'd9:
font_row = (r == 3'd0) ? 8'hFF : (r == 3'd1) ? 8'h81 : (r == 3'd2) ? 8'hBD :
6'd9:
font_row = (r == 3'd0) ? 8'hFF : (r == 3'd1) ? 8'h81 : (r == 3'd2) ? 8'hBD :
(r == 3'd3) ? 8'hA5 : (r == 3'd4) ? 8'hA5 : (r == 3'd5) ? 8'hBD :
(r == 3'd6) ? 8'h81 : 8'hFF; // 未揭开的方块
4'd10:
font_row = (r == 3'd0) ? 8'h40 : (r == 3'd1) ? 8'h70 : (r == 3'd2) ? 8'h78 :
6'd10:
font_row = (r == 3'd0) ? 8'h40 : (r == 3'd1) ? 8'h70 : (r == 3'd2) ? 8'h78 :
(r == 3'd3) ? 8'h70 : (r == 3'd4) ? 8'h40 : (r == 3'd5) ? 8'h40 :
(r == 3'd6) ? 8'h40 : 8'h00; // 旗
4'd11:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h7E : (r == 3'd2) ? 8'hFF :
6'd11:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h7E : (r == 3'd2) ? 8'hFF :
(r == 3'd3) ? 8'hFF : (r == 3'd4) ? 8'hFF : (r == 3'd5) ? 8'hFF :
(r == 3'd6) ? 8'h7E : 8'h3C; // 雷
4'd12:
font_row = (r == 3'd0) ? 8'h81 : (r == 3'd1) ? 8'h42 : (r == 3'd2) ? 8'h24 :
6'd12:
font_row = (r == 3'd0) ? 8'h81 : (r == 3'd1) ? 8'h42 : (r == 3'd2) ? 8'h24 :
(r == 3'd3) ? 8'h18 : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h24 :
(r == 3'd6) ? 8'h42 : 8'h81; // 爆炸
4'd13:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h06 :
6'd13:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h06 :
(r == 3'd3) ? 8'h0C : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h00 :
(r == 3'd6) ? 8'h18 : 8'h00; // 问号
4'd14:
font_row = (r == 3'd3) ? 8'hFF : (r == 3'd4) ? 8'hFF : 8'h00; // 横线
4'd15:
font_row = 8'hFF; // 实心
default:
font_row = 8'h00;
endcase
end
endfunction
wire [7:0] frow = font_row(glyph, fy);
wire glyph_px = frow[3'd7 - fx]; // 1 = 画字模点
// ---------------------------------------------------------------------------
// 光标框:黄色、常亮、2 像素宽
// 原来是 1 像素浅灰 + 闪动(blink 周期约 1.3 s):在白色未揭开格上几乎看不清,
// 而且截图有一半概率正好截到"灭"的那个相位。改成黄色常亮 2 像素后,
// VGA 上和图里都能一眼看到指针。想改回闪动:把 blink_q 加回来,
// 再在 cursor_box 里与上 blink 即可。
// ---------------------------------------------------------------------------
wire cursor_here = (cursor_i[4:0] == ccol) && (cursor_i[12:8] == crow);
wire on_border = (hx[3:0] < 4'd2) || (hx[3:0] > 4'd13) ||
(vy[3:0] < 4'd2) || (vy[3:0] > 4'd13);
wire cursor_box = cursor_here && ctrl_i[0] && on_border;
// ---------------------------------------------------------------------------
// 颜色(3 位调色板 -> 4:4:4 RGB)
// ---------------------------------------------------------------------------
function [11:0] pal;
input [2:0] c;
begin
case (c)
3'd0:
pal = {4'h0, 4'h0, 4'h0}; // 黑
3'd1:
pal = {4'h0, 4'h0, 4'hA}; // 深蓝
3'd2:
pal = {4'h0, 4'hA, 4'h0}; // 绿
3'd3:
pal = {4'h0, 4'hA, 4'hA}; // 青
3'd4:
pal = {4'hA, 4'h0, 4'h0}; // 红
3'd5:
pal = {4'hA, 4'h0, 4'hA}; // 洋红
3'd6:
pal = {4'hA, 4'hA, 4'h0}; // 黄
default:
pal = {4'hA, 4'hA, 4'hA}; // 浅灰
endcase
end
endfunction
wire [2:0] px_color = glyph_px ? gcolor : bgcolor;
wire [2:0] px_final = cursor_box ? 3'd6 : px_color; // 6 = 黄,光标框
wire [11:0] px_rgb = de ? pal(px_final) : 12'h000;
always @(posedge clk or posedge reset) begin
if (reset) begin
{vga_r, vga_g, vga_b} <= 12'h000;
vga_hs <= 1'b1;
vga_vs <= 1'b1;
frame_tick_o <= 1'b0;
end
else if (tick) begin
{vga_r, vga_g, vga_b} <= px_rgb;
vga_hs <= hs_n;
vga_vs <= vs_n;
frame_tick_o <= (hx == H_TOTAL - 1) && (vy == V_TOTAL - 1);
end
6'd14: font_row = (r == 3'd3) ? 8'hFF : (r == 3'd4) ? 8'hFF : 8'h00; // 横线
6'd15: font_row = 8'hFF; // 实心
6'd16:
font_row = (r == 3'd0) ? 8'h18 : (r == 3'd1) ? 8'h3C : (r == 3'd2) ? 8'h66 :
(r == 3'd3) ? 8'h66 : (r == 3'd4) ? 8'h7E : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h66 : 8'h00; // A
6'd17:
font_row = (r == 3'd0) ? 8'h7C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h66 :
(r == 3'd3) ? 8'h7C : (r == 3'd4) ? 8'h66 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h7C : 8'h00; // B
6'd18:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h60 :
(r == 3'd3) ? 8'h60 : (r == 3'd4) ? 8'h60 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // C
6'd19:
font_row = (r == 3'd0) ? 8'h78 : (r == 3'd1) ? 8'h6C : (r == 3'd2) ? 8'h66 :
(r == 3'd3) ? 8'h66 : (r == 3'd4) ? 8'h66 : (r == 3'd5) ? 8'h6C :
(r == 3'd6) ? 8'h78 : 8'h00; // D
6'd20:
font_row = (r == 3'd0) ? 8'h7E : (r == 3'd1) ? 8'h60 : (r == 3'd2) ? 8'h60 :
(r == 3'd3) ? 8'h7C : (r == 3'd4) ? 8'h60 : (r == 3'd5) ? 8'h60 :
(r == 3'd6) ? 8'h7E : 8'h00; // E
6'd21:
font_row = (r == 3'd0) ? 8'h7E : (r == 3'd1) ? 8'h60 : (r == 3'd2) ? 8'h60 :
(r == 3'd3) ? 8'h7C : (r == 3'd4) ? 8'h60 : (r == 3'd5) ? 8'h60 :
(r == 3'd6) ? 8'h60 : 8'h00; // F
6'd22:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h60 :
(r == 3'd3) ? 8'h6E : (r == 3'd4) ? 8'h66 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3E : 8'h00; // G
6'd23:
font_row = (r == 3'd0) ? 8'h66 : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h66 :
(r == 3'd3) ? 8'h7E : (r == 3'd4) ? 8'h66 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h66 : 8'h00; // H
6'd24:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h18 : (r == 3'd2) ? 8'h18 :
(r == 3'd3) ? 8'h18 : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h18 :
(r == 3'd6) ? 8'h3C : 8'h00; // I
6'd25:
font_row = (r == 3'd0) ? 8'h1E : (r == 3'd1) ? 8'h0C : (r == 3'd2) ? 8'h0C :
(r == 3'd3) ? 8'h0C : (r == 3'd4) ? 8'h0C : (r == 3'd5) ? 8'h6C :
(r == 3'd6) ? 8'h38 : 8'h00; // J
6'd26:
font_row = (r == 3'd0) ? 8'h66 : (r == 3'd1) ? 8'h6C : (r == 3'd2) ? 8'h78 :
(r == 3'd3) ? 8'h70 : (r == 3'd4) ? 8'h78 : (r == 3'd5) ? 8'h6C :
(r == 3'd6) ? 8'h66 : 8'h00; // K
6'd27:
font_row = (r == 3'd0) ? 8'h60 : (r == 3'd1) ? 8'h60 : (r == 3'd2) ? 8'h60 :
(r == 3'd3) ? 8'h60 : (r == 3'd4) ? 8'h60 : (r == 3'd5) ? 8'h60 :
(r == 3'd6) ? 8'h7E : 8'h00; // L
6'd28:
font_row = (r == 3'd0) ? 8'h63 : (r == 3'd1) ? 8'h77 : (r == 3'd2) ? 8'h7F :
(r == 3'd3) ? 8'h6B : (r == 3'd4) ? 8'h63 : (r == 3'd5) ? 8'h63 :
(r == 3'd6) ? 8'h63 : 8'h00; // M
6'd29:
font_row = (r == 3'd0) ? 8'h66 : (r == 3'd1) ? 8'h76 : (r == 3'd2) ? 8'h7E :
(r == 3'd3) ? 8'h6E : (r == 3'd4) ? 8'h66 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h66 : 8'h00; // N
6'd30:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h66 :
(r == 3'd3) ? 8'h66 : (r == 3'd4) ? 8'h66 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // O
6'd31:
font_row = (r == 3'd0) ? 8'h7C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h66 :
(r == 3'd3) ? 8'h7C : (r == 3'd4) ? 8'h60 : (r == 3'd5) ? 8'h60 :
(r == 3'd6) ? 8'h60 : 8'h00; // P
6'd32:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h66 :
(r == 3'd3) ? 8'h66 : (r == 3'd4) ? 8'h6E : (r == 3'd5) ? 8'h3C :
(r == 3'd6) ? 8'h0E : 8'h00; // Q
6'd33:
font_row = (r == 3'd0) ? 8'h7C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h66 :
(r == 3'd3) ? 8'h7C : (r == 3'd4) ? 8'h78 : (r == 3'd5) ? 8'h6C :
(r == 3'd6) ? 8'h66 : 8'h00; // R
6'd34:
font_row = (r == 3'd0) ? 8'h3E : (r == 3'd1) ? 8'h60 : (r == 3'd2) ? 8'h60 :
(r == 3'd3) ? 8'h3C : (r == 3'd4) ? 8'h06 : (r == 3'd5) ? 8'h06 :
(r == 3'd6) ? 8'h7C : 8'h00; // S
6'd35:
font_row = (r == 3'd0) ? 8'h7E : (r == 3'd1) ? 8'h18 : (r == 3'd2) ? 8'h18 :
(r == 3'd3) ? 8'h18 : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h18 :
(r == 3'd6) ? 8'h18 : 8'h00; // T
6'd36:
font_row = (r == 3'd0) ? 8'h66 : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h66 :
(r == 3'd3) ? 8'h66 : (r == 3'd4) ? 8'h66 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // U
6'd37:
font_row = (r == 3'd0) ? 8'h66 : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h66 :
(r == 3'd3) ? 8'h66 : (r == 3'd4) ? 8'h66 : (r == 3'd5) ? 8'h3C :
(r == 3'd6) ? 8'h18 : 8'h00; // V
6'd38:
font_row = (r == 3'd0) ? 8'h63 : (r == 3'd1) ? 8'h63 : (r == 3'd2) ? 8'h63 :
(r == 3'd3) ? 8'h6B : (r == 3'd4) ? 8'h7F : (r == 3'd5) ? 8'h77 :
(r == 3'd6) ? 8'h63 : 8'h00; // W
6'd39:
font_row = (r == 3'd0) ? 8'h66 : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h3C :
(r == 3'd3) ? 8'h18 : (r == 3'd4) ? 8'h3C : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h66 : 8'h00; // X
6'd40:
font_row = (r == 3'd0) ? 8'h66 : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h3C :
(r == 3'd3) ? 8'h18 : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h18 :
(r == 3'd6) ? 8'h18 : 8'h00; // Y
6'd41:
font_row = (r == 3'd0) ? 8'h7E : (r == 3'd1) ? 8'h06 : (r == 3'd2) ? 8'h0C :
(r == 3'd3) ? 8'h18 : (r == 3'd4) ? 8'h30 : (r == 3'd5) ? 8'h60 :
(r == 3'd6) ? 8'h7E : 8'h00; // Z
default: font_row = 8'h00;
endcase
end
endfunction
wire [7:0] frow = font_row(glyph, fy);
wire glyph_px = frow[3'd7-fx]; // 1 = 画字模点
// ---------------------------------------------------------------------------
// 光标框:黄色、常亮、2 像素宽
// 原来是 1 像素浅灰 + 闪动(blink 周期约 1.3 s):在白色未揭开格上几乎看不清,
// 而且截图有一半概率正好截到"灭"的那个相位。改成黄色常亮 2 像素后,
// VGA 上和图里都能一眼看到指针。想改回闪动:把 blink_q 加回来,
// 再在 cursor_box 里与上 blink 即可。
// ---------------------------------------------------------------------------
wire cursor_here = (cursor_i[4:0] == ccol) && (cursor_i[12:8] == crow);
wire on_border = (hx[3:0] < 4'd2) || (hx[3:0] > 4'd13) || (vy[3:0] < 4'd2) || (vy[3:0] > 4'd13);
wire cursor_box = cursor_here && ctrl_i[0] && on_border;
// ---------------------------------------------------------------------------
// 颜色(3 位调色板 -> 4:4:4 RGB)
// ---------------------------------------------------------------------------
function [11:0] pal;
input [2:0] c;
begin
case (c)
3'd0: pal = {4'h0, 4'h0, 4'h0}; // 黑
3'd1: pal = {4'h0, 4'h0, 4'hA}; // 深蓝
3'd2: pal = {4'h0, 4'hA, 4'h0}; // 绿
3'd3: pal = {4'h0, 4'hA, 4'hA}; // 青
3'd4: pal = {4'hA, 4'h0, 4'h0}; // 红
3'd5: pal = {4'hA, 4'h0, 4'hA}; // 洋红
3'd6: pal = {4'hA, 4'hA, 4'h0}; // 黄
default: pal = {4'hA, 4'hA, 4'hA}; // 浅灰
endcase
end
endfunction
wire [ 2:0] px_color = glyph_px ? gcolor : bgcolor;
wire [ 2:0] px_final = cursor_box ? 3'd6 : px_color; // 6 = 黄,光标框
wire [11:0] px_rgb = de ? pal(px_final) : 12'h000;
always @(posedge clk or posedge reset) begin
if (reset) begin
{vga_r, vga_g, vga_b} <= 12'h000;
vga_hs <= 1'b1;
vga_vs <= 1'b1;
frame_tick_o <= 1'b0;
end else if (tick) begin
{vga_r, vga_g, vga_b} <= px_rgb;
vga_hs <= hs_n;
vga_vs <= vs_n;
frame_tick_o <= (hx == H_TOTAL - 1) && (vy == V_TOTAL - 1);
end
end
endmodule