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
+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