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
+11 -13
View File
@@ -70,7 +70,7 @@ risc-v/
因此它与标准 RISC-V 指令的低 7 位 opcode 相差右移两位。 因此它与标准 RISC-V 指令的低 7 位 opcode 相差右移两位。
| 指令类别 | opcode | 说明 | | 指令类别 | opcode | 说明 |
| ----------------------------------------------- | -----: | ----------------------------------------------------------------------------------- | | ----------------------------------------------------------- | -------: | ----------------------------------------------------------------------------------------- |
| R 型 | `0x0c` | add/sub/and/or/xor/slt/sltu/sll/srl/sra | | R 型 | `0x0c` | add/sub/and/or/xor/slt/sltu/sll/srl/sra |
| M 扩展(R 型) | `0x0c` | `funct7=0000001`:mul/mulh/mulhsu/mulhu、divu(`funct3=101`)、remu(`funct3=111`) | | M 扩展(R 型) | `0x0c` | `funct7=0000001`:mul/mulh/mulhsu/mulhu、divu(`funct3=101`)、remu(`funct3=111`) |
| I 型 ALU | `0x04` | addi/andi/ori/xori/slti/sltiu/slli/srli/srai | | I 型 ALU | `0x04` | addi/andi/ori/xori/slti/sltiu/slli/srli/srai |
@@ -92,7 +92,7 @@ risc-v/
每行的字节偏移都按**字**对齐译码(同一 32 位寄存器内任意字节偏移都命中)。 每行的字节偏移都按**字**对齐译码(同一 32 位寄存器内任意字节偏移都命中)。
| 地址 | 外设 | 写 | 读 | | 地址 | 外设 | 写 | 读 |
| ---------------------- | -------------- | ---------------------------------------------------------------------------- | -------------------------- | | --------------------------- | -------------- | ------------------------------------------------------------------------------------ | ---------------------------- |
| `0x0000_1000` | LED 数据寄存器 | `sw` 写全字;`sb` 按 `data_wstrb_o` 只改对应字节通道,`led_valid_o` 拉高一拍 | `lw` 返回 LED 寄存器当前值 | | `0x0000_1000` | LED 数据寄存器 | `sw` 写全字;`sb` 按 `data_wstrb_o` 只改对应字节通道,`led_valid_o` 拉高一拍 | `lw` 返回 LED 寄存器当前值 |
| `0x0000_1004` | 七段数码管 | `sw`/`sb` 写 8 位十六进制数(低 4 位 = 最右边那个数码管) | `lw` 读回 | | `0x0000_1004` | 七段数码管 | `sw`/`sb` 写 8 位十六进制数(低 4 位 = 最右边那个数码管) | `lw` 读回 |
| `0x0000_1100`+ | 鼠标寄存器组 | STATUS / DATA / ACK / CTRL / INPUT / TIME / FRAME / EVCNT / LEVEL / MAGIC | 同左(DATA 读无副作用) | | `0x0000_1100`+ | 鼠标寄存器组 | STATUS / DATA / ACK / CTRL / INPUT / TIME / FRAME / EVCNT / LEVEL / MAGIC | 同左(DATA 读无副作用) |
@@ -148,7 +148,7 @@ risc-v/
## 文件与模块划分 ## 文件与模块划分
| 文件 | 模块 | 职责 | | 文件 | 模块 | 职责 |
| --------------------------------------------------------- | --------------------------------- | ---------------------------------------------------------------------------------- | | ----------------------------------------------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------- |
| `pipelined-cpu/srcs/rtl/cpu21_riscv_redirect_int_bpb.v` | `cpu21_riscv_redirect_int_bpb` | 顶层:五级流水寄存器、子模块例化、PC/IF-ID/ID-EX 控制、对外输出 | | `pipelined-cpu/srcs/rtl/cpu21_riscv_redirect_int_bpb.v` | `cpu21_riscv_redirect_int_bpb` | 顶层:五级流水寄存器、子模块例化、PC/IF-ID/ID-EX 控制、对外输出 |
| `pipelined-cpu/srcs/rtl/cpu21_riscv_alu.v` | `cpu21_riscv_alu` | 组合 ALU(加减/移位/逻辑/比较,不做乘除) | | `pipelined-cpu/srcs/rtl/cpu21_riscv_alu.v` | `cpu21_riscv_alu` | 组合 ALU(加减/移位/逻辑/比较,不做乘除) |
| `pipelined-cpu/srcs/rtl/cpu21_riscv_muldiv.v` | `cpu21_riscv_muldiv` | 多周期乘除法单元:32 拍移位迭代,mul/mulh/mulhsu/mulhu/divu/remu | | `pipelined-cpu/srcs/rtl/cpu21_riscv_muldiv.v` | `cpu21_riscv_muldiv` | 多周期乘除法单元:32 拍移位迭代,mul/mulh/mulhsu/mulhu/divu/remu |
@@ -277,7 +277,7 @@ BPB/中断观测值)。如果直接把它设为实现顶层,这些端口会
### 引脚分配(`cpu21_board_top.xdc`) ### 引脚分配(`cpu21_board_top.xdc`)
| 端口 | 引脚 | 板上元件 | 说明 | | 端口 | 引脚 | 板上元件 | 说明 |
| ------------- | ----------- | ------------------------- | ------------------------------------------------------------------- | | --------------- | ----------- | ------------------------- | ----------------------------------------------------------------------- |
| `clk100` | E3 | 100 MHz 晶振 | 经`CPU_DIV` 分频后给 CPU(默认 50 MHz) | | `clk100` | E3 | 100 MHz 晶振 | 经`CPU_DIV` 分频后给 CPU(默认 50 MHz) |
| `rst_n` | C12 | 按键 CPU_RESETN(低有效) | 异步置位、与 CPU 时钟同步释放 | | `rst_n` | C12 | 按键 CPU_RESETN(低有效) | 异步置位、与 CPU 时钟同步释放 |
| `go` | N17 | 按键 BTNC | 对应原电路“继续运行”(`ecall` 停机后恢复执行) | | `go` | N17 | 按键 BTNC | 对应原电路“继续运行”(`ecall` 停机后恢复执行) |
@@ -298,7 +298,7 @@ BPB/中断观测值)。如果直接把它设为实现顶层,这些端口会
周期 20 ns)**,既比原来的 16 分频快 8 倍,又留出充足余量: 周期 20 ns)**,既比原来的 16 分频快 8 倍,又留出充足余量:
| 配置 | CPU 时钟 | 实测 WNS(xc7a100tcsg324-1) | 说明 | | 配置 | CPU 时钟 | 实测 WNS(xc7a100tcsg324-1) | 说明 |
| ------------ | -------- | ---------------------------- | ------------------------------------- | | -------------- | -------- | ---------------------------- | ------------------------------------- |
| `CLK_DIV=1` | 100 MHz | -1.1 ns ✗ | 违例路径与乘除法无关(分支/中断/BPB) | | `CLK_DIV=1` | 100 MHz | -1.1 ns ✗ | 违例路径与乘除法无关(分支/中断/BPB) |
| `CLK_DIV=2` | 50 MHz | **+5.1 ns** ✓ | 默认,余量约 25% | | `CLK_DIV=2` | 50 MHz | **+5.1 ns** ✓ | 默认,余量约 25% |
| `CLK_DIV=16` | 6.25 MHz | +6.1 ns ✓ | 改造前的设置,现在没必要了 | | `CLK_DIV=16` | 6.25 MHz | +6.1 ns ✓ | 改造前的设置,现在没必要了 |
@@ -328,7 +328,7 @@ create_generated_clock -name cpu_clk -source [get_ports { clk100 }] \
### 分频与刷新率一览(想改速度看这里) ### 分频与刷新率一览(想改速度看这里)
| 想改什么 | 改哪里 | 当前值 / 换算 | | 想改什么 | 改哪里 | 当前值 / 换算 |
| -------------------------- | ----------------------------------------------- | -------------------------------------------------------- | | -------------------------- | --------------------------------------------------- | ------------------------------------------------------------ |
| CPU 主频(程序快慢) | 参数`CLK_DIV` | 2 → 50 MHz(`100 MHz / CLK_DIV`) | | CPU 主频(程序快慢) | 参数`CLK_DIV` | 2 → 50 MHz(`100 MHz / CLK_DIV`) |
| 中断程序的 CPU 主频 | 参数`CLK_DIV_INTRUPT`(只在该 ROM 宏下生效) | 32 → 3.125 MHz;每个数值约 10 ms × (`CLK_DIV_INTRUPT`/2) | | 中断程序的 CPU 主频 | 参数`CLK_DIV_INTRUPT`(只在该 ROM 宏下生效) | 32 → 3.125 MHz;每个数值约 10 ms × (`CLK_DIV_INTRUPT`/2) |
| 数码管扫描刷新率 | 顶层`SEG_REFRESH_DIV`(自动跟随 `CPU_DIV`) | `100000 / CPU_DIV` → 每步 1 ms、每位 125 Hz | | 数码管扫描刷新率 | 顶层`SEG_REFRESH_DIV`(自动跟随 `CPU_DIV`) | `100000 / CPU_DIV` → 每步 1 ms、每位 125 Hz |
@@ -365,7 +365,7 @@ create_generated_clock -name cpu_clk -source [get_ports { clk100 }] \
`create_project.tcl` 里的两个开关就是给这条链用的: `create_project.tcl` 里的两个开关就是给这条链用的:
| 宏(`sources_1` 的 Verilog Define) | ROM 文件 | 中断入口`IRQ1/2/3` | 说明 | | 宏(`sources_1` 的 Verilog Define) | ROM 文件 | 中断入口`IRQ1/2/3` | 说明 |
| ----------------------------------- | ------------------------------------ | --------------------------- | ------------------------------------------ | | ------------------------------------- | -------------------------------------- | --------------------------------- | -------------------------------------------- |
| `SAOLEI_ROM`(优先级最高) | `srcs/board/cpu21_saolei_rom.vh` | `0x100` / `0x200` / `0x300` | 纯 C 扫雷(`saolei/tools/build.ps1` 生成) | | `SAOLEI_ROM`(优先级最高) | `srcs/board/cpu21_saolei_rom.vh` | `0x100` / `0x200` / `0x300` | 纯 C 扫雷(`saolei/tools/build.ps1` 生成) |
| `RISC_V_INTRUPT_CCAB_ROM` | `srcs/board/risc-v-intrupt-ccab.vh` | `0x0AC` / `0x1E4` / `0x308` | 课程中断综合测试程序(当前默认) | | `RISC_V_INTRUPT_CCAB_ROM` | `srcs/board/risc-v-intrupt-ccab.vh` | `0x0AC` / `0x1E4` / `0x308` | 课程中断综合测试程序(当前默认) |
| `BOARD_ROM_FAST` | `srcs/board/cpu21_board_rom_fast.vh` | `0x100` / `0x200` / `0x300` | 仿真专用:跑马灯延时改小(综合不用) | | `BOARD_ROM_FAST` | `srcs/board/cpu21_board_rom_fast.vh` | `0x100` / `0x200` / `0x300` | 仿真专用:跑马灯延时改小(综合不用) |
@@ -389,7 +389,6 @@ ROM 里 ISR 的起点一致**,所以换程序时宏与入口是一起换的:
``` ```
换回跑马灯就是 `set_property verilog_define {} [get_filesets sources_1]`。 换回跑马灯就是 `set_property verilog_define {} [get_filesets sources_1]`。
2. **一键重建**:改 `create_project.tcl` 里的 `use_intrupt_ccab` / `use_saolei` 2. **一键重建**:改 `create_project.tcl` 里的 `use_intrupt_ccab` / `use_saolei`
两个开关(同一时间只开一个),重新 `source create_project.tcl`。 两个开关(同一时间只开一个),重新 `source create_project.tcl`。
3. **命令行仿真**:`xvlog` 与 `xelab` 都要带同一个 `-d`,并用 `-i` 指向头文件目录: 3. **命令行仿真**:`xvlog` 与 `xelab` 都要带同一个 `-d`,并用 `-i` 指向头文件目录:
@@ -399,7 +398,6 @@ ROM 里 ISR 的起点一致**,所以换程序时宏与入口是一起换的:
srcs/rtl/*.v srcs/board/cpu21_board_top.v 你的tb.v srcs/rtl/*.v srcs/board/cpu21_board_top.v 你的tb.v
xelab -top 你的tb -snapshot s -d RISC_V_INTRUPT_CCAB_ROM xelab -top 你的tb -snapshot s -d RISC_V_INTRUPT_CCAB_ROM
``` ```
4. **注意**:这些宏只影响“综合用”的 `sources_1`。跑 `tb_no_intr` / 4. **注意**:这些宏只影响“综合用”的 `sources_1`。跑 `tb_no_intr` /
`tb_cpu21_riscv_redirect_int_bpb` 时,程序来自测试平台自己探测的 `tb_cpu21_riscv_redirect_int_bpb` 时,程序来自测试平台自己探测的
`srcs/rom/*.hex`,与宏无关。 `srcs/rom/*.hex`,与宏无关。
@@ -461,7 +459,7 @@ xsim board_sim -tclbatch run_quit.tcl
文件,由 `BOARD_ROM_FAST` 宏选择(综合流程不会定义该宏): 文件,由 `BOARD_ROM_FAST` 宏选择(综合流程不会定义该宏):
| 文件 | 用途 | 每步耗时 | | 文件 | 用途 | 每步耗时 |
| -------------------------------------------------- | ------------------------------ | -------- | | ---------------------------------------------------- | ------------------------------ | -------- |
| `pipelined-cpu/srcs/board/cpu21_board_rom.vh` | 综合/上板(默认) | 约 20 ms | | `pipelined-cpu/srcs/board/cpu21_board_rom.vh` | 综合/上板(默认) | 约 20 ms |
| `pipelined-cpu/srcs/board/cpu21_board_rom_fast.vh` | 仅仿真:一次跑完整个跑马灯序列 | 2.6 µs | | `pipelined-cpu/srcs/board/cpu21_board_rom_fast.vh` | 仅仿真:一次跑完整个跑马灯序列 | 2.6 µs |
@@ -496,7 +494,7 @@ Vivado 工程 `pipelined-cpu/pipelined-cpu.xpr` 被 `.gitignore` 排除,需要
`xc7a100tcsg324-1` 上的实现结果(乘除法改为多周期单元、`CLK_DIV=2` = 50 MHz): `xc7a100tcsg324-1` 上的实现结果(乘除法改为多周期单元、`CLK_DIV=2` = 50 MHz):
| 项目 | 结果 | | 项目 | 结果 |
| -------- | -------------------------------------------------------------------------------------------- | | -------- | -------------------------------------------------------------------------------------------------- |
| 综合 | `synth_design Complete!` | | 综合 | `synth_design Complete!` |
| 实现 | `route_design Complete!`、`write_bitstream Complete!`,`WNS = +5.109 ns`(无 Failed Timing) | | 实现 | `route_design Complete!`、`write_bitstream Complete!`,`WNS = +5.109 ns`(无 Failed Timing) |
| 资源 | LUT 3420 (5.39%)、FF 2507、**DSP 0**(组合乘法器已消失)、IOB 23 (10.95%) | | 资源 | LUT 3420 (5.39%)、FF 2507、**DSP 0**(组合乘法器已消失)、IOB 23 (10.95%) |
@@ -562,7 +560,7 @@ cd ..\pipelined-cpu
板上操作: 板上操作:
| 板子上 | 作用 | | 板子上 | 作用 |
| ------------------------- | ---------------------------------------------------- | | ------------------------- | ---------------------------------------------------------- |
| BTNU / BTND / BTNL / BTNR | 光标 上 / 下 / 左 / 右 | | BTNU / BTND / BTNL / BTNR | 光标 上 / 下 / 左 / 右 |
| BTNC | 左键:开始界面确认 / 关卡里开格 | | BTNC | 左键:开始界面确认 / 关卡里开格 |
| SW3 拨上 + BTNC | 插旗 / 取消旗 | | SW3 拨上 + BTNC | 插旗 / 取消旗 |
@@ -634,7 +632,7 @@ powershell -ExecutionPolicy Bypass -File tools\run_regress.ps1
期望结果(2026-09,多周期乘除法 + `CLK_DIV=2`): 期望结果(2026-09,多周期乘除法 + `CLK_DIV=2`):
| 测试 | 期望 | | 测试 | 期望 |
| -------------------------------------------------- | ---------------------------------------------------------------------------- | | -------------------------------------------------- | ------------------------------------------------------------------------------ |
| muldiv_test / isa_test / lui_test / mmio_test | 13 / 24 / 4 / 5 个 LED 值全部匹配 | | muldiv_test / isa_test / lui_test / mmio_test | 13 / 24 / 4 / 5 个 LED 值全部匹配 |
| tb_no_intr(benchmark,5560 周期) | `stalls=1528 bubbles=1830 cond_taken=639 uncond=71 pred_ok=687 pred_fail=80` | | tb_no_intr(benchmark,5560 周期) | `stalls=1528 bubbles=1830 cond_taken=639 uncond=71 pred_ok=687 pred_fail=80` |
| tb_cpu21_riscv_redirect_int_bpb(中断,6003 周期) | `stalls=963 bubbles=1205 cond_taken=547 uncond=28 pred_ok=557 pred_fail=87` | | tb_cpu21_riscv_redirect_int_bpb(中断,6003 周期) | `stalls=963 bubbles=1205 cond_taken=547 uncond=28 pred_ok=557 pred_fail=87` |
+24 -46
View File
@@ -132,12 +132,10 @@ module cpu21_board_top #(
if (arst) begin if (arst) begin
clk_cnt <= 16'd0; clk_cnt <= 16'd0;
clk_div <= 1'b0; clk_div <= 1'b0;
end end else if (clk_cnt == (CPU_DIV / 2 - 1)) begin
else if (clk_cnt == (CPU_DIV / 2 - 1)) begin
clk_cnt <= 16'd0; clk_cnt <= 16'd0;
clk_div <= ~clk_div; clk_div <= ~clk_div;
end end else begin
else begin
clk_cnt <= clk_cnt + 16'd1; clk_cnt <= clk_cnt + 16'd1;
end end
end end
@@ -150,10 +148,8 @@ module cpu21_board_top #(
reg [2:0] rst_sync_q; reg [2:0] rst_sync_q;
always @(posedge clk_cpu or posedge arst) begin always @(posedge clk_cpu or posedge arst) begin
if (arst) if (arst) rst_sync_q <= 3'b111;
rst_sync_q <= 3'b111; else rst_sync_q <= {rst_sync_q[1:0], 1'b0};
else
rst_sync_q <= {rst_sync_q[1:0], 1'b0};
end end
wire reset = rst_sync_q[2]; wire reset = rst_sync_q[2];
@@ -196,8 +192,7 @@ module cpu21_board_top #(
integer i; integer i;
initial begin initial begin
for (i = 0; i < ROM_WORDS; i = i + 1) for (i = 0; i < ROM_WORDS; i = i + 1) rom[i] = 32'h0000_0013; // NOP 填充
rom[i] = 32'h0000_0013; // NOP 填充
`ifdef SAOLEI_ROM `ifdef SAOLEI_ROM
// 扫雷程序(由 saolei/tools/build.ps1 生成的 cpu21_saolei_rom.vh)。 // 扫雷程序(由 saolei/tools/build.ps1 生成的 cpu21_saolei_rom.vh)。
// 综合/仿真时定义宏 SAOLEI_ROM 即可切换到这个程序;不定义就是下面的跑马灯。 // 综合/仿真时定义宏 SAOLEI_ROM 即可切换到这个程序;不定义就是下面的跑马灯。
@@ -225,8 +220,7 @@ module cpu21_board_top #(
integer j; integer j;
initial begin initial begin
for (j = 0; j < RAM_WORDS; j = j + 1) for (j = 0; j < RAM_WORDS; j = j + 1) ram[j] = 32'b0;
ram[j] = 32'b0;
end end
wire [ROM_AW+1:2] rom_index = instr_addr[ROM_AW+1:2]; wire [ROM_AW+1:2] rom_index = instr_addr[ROM_AW+1:2];
@@ -238,14 +232,10 @@ module cpu21_board_top #(
always @(posedge clk_cpu) begin always @(posedge clk_cpu) begin
if (ram_sel && data_we) begin if (ram_sel && data_we) begin
if (data_wstrb[0]) if (data_wstrb[0]) ram[ram_index][7:0] <= data_wdata[7: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[1]) if (data_wstrb[2]) ram[ram_index][23:16] <= data_wdata[23:16];
ram[ram_index][15:8] <= data_wdata[15:8]; if (data_wstrb[3]) ram[ram_index][31:24] <= data_wdata[31:24];
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
end end
@@ -337,8 +327,7 @@ module cpu21_board_top #(
irq_req_q <= 3'b000; irq_req_q <= 3'b000;
irq_level_q <= 2'd0; irq_level_q <= 2'd0;
irq_led_q <= 3'b000; irq_led_q <= 3'b000;
end end else begin
else begin
irq_req_q <= irq_vec; irq_req_q <= irq_vec;
irq_level_q <= irq_level; irq_level_q <= irq_level;
// 请求上升沿 -> 点亮;对应级别的 ISR 返回 -> 熄灭 // 请求上升沿 -> 点亮;对应级别的 ISR 返回 -> 熄灭
@@ -353,13 +342,10 @@ module cpu21_board_top #(
reg [23:0] led_we_cnt; reg [23:0] led_we_cnt;
always @(posedge clk_cpu or posedge arst) begin always @(posedge clk_cpu or posedge arst) begin
if (arst) if (arst) led_we_cnt <= 24'd0;
led_we_cnt <= 24'd0; else if (led_valid) led_we_cnt <= 24'hFF_FFFF; // 约 0.34 s @50 MHz
else if (led_valid)
led_we_cnt <= 24'hFF_FFFF; // 约 0.34 s @50 MHz
// (12.5 MHz 下约 1.36 s) // (12.5 MHz 下约 1.36 s)
else if (led_we_cnt != 24'd0) else if (led_we_cnt != 24'd0) led_we_cnt <= led_we_cnt - 24'd1;
led_we_cnt <= led_we_cnt - 24'd1;
end end
assign led_we = (led_we_cnt != 24'd0); assign led_we = (led_we_cnt != 24'd0);
@@ -367,11 +353,11 @@ module cpu21_board_top #(
// =========================================================================== // ===========================================================================
// 扫雷外设:显存 + VGA + 七段数码管 + 鼠标事件源 + 中断连线 // 扫雷外设:显存 + VGA + 七段数码管 + 鼠标事件源 + 中断连线
// //
// 显存是一块 1200 字节的双口 RAM(40x30 格,每格 1 字节):CPU 通过 MMIO 的 // 显存是一块 2400 字节的双口 RAM(40x30 格,每格 2 字节):CPU 通过 MMIO 的
// fb_* 写端口写入,VGA 用另一个读端口组合读出。两者同处 clk_cpu 域(VGA 的 // fb_* 写端口写入,VGA 用另一个读端口组合读出。两者同处 clk_cpu 域(VGA 的
// 25 MHz 像素节拍由使能信号产生,不是独立时钟),因此没有跨时钟域问题。 // 25 MHz 像素节拍由使能信号产生,不是独立时钟),因此没有跨时钟域问题。
// =========================================================================== // ===========================================================================
localparam integer FB_WORDS = 300; // 300 字 = 1200 字节 localparam integer FB_WORDS = 600; // 600 字 = 2400 字节,40x30 格每格 2 字节
// ---- 显存 ---- // ---- 显存 ----
@@ -379,20 +365,15 @@ module cpu21_board_top #(
integer k; integer k;
initial begin initial begin
for (k = 0; k < FB_WORDS; k = k + 1) for (k = 0; k < FB_WORDS; k = k + 1) fb[k] = 32'b0;
fb[k] = 32'b0;
end end
always @(posedge clk_cpu) begin always @(posedge clk_cpu) begin
if (fb_we) begin if (fb_we) begin
if (fb_wstrb[0]) if (fb_wstrb[0]) fb[fb_waddr][7:0] <= fb_wdata[7: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[1]) if (fb_wstrb[2]) fb[fb_waddr][23:16] <= fb_wdata[23:16];
fb[fb_waddr][15:8] <= fb_wdata[15:8]; if (fb_wstrb[3]) fb[fb_waddr][31:24] <= fb_wdata[31:24];
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
end end
@@ -401,10 +382,8 @@ module cpu21_board_top #(
// ---- VGA 像素节拍:clk_cpu 二分频 => 25 MHz ---- // ---- VGA 像素节拍:clk_cpu 二分频 => 25 MHz ----
reg px_q; reg px_q;
always @(posedge clk_cpu or posedge arst) begin always @(posedge clk_cpu or posedge arst) begin
if (arst) if (arst) px_q <= 1'b0;
px_q <= 1'b0; else px_q <= ~px_q;
else
px_q <= ~px_q;
end end
wire px_tick = ~px_q; wire px_tick = ~px_q;
@@ -459,8 +438,7 @@ module cpu21_board_top #(
.ev_btn_o (ev_btn), .ev_btn_o (ev_btn),
.rx_count_o () .rx_count_o ()
); );
end end else begin : g_btn
else begin : g_btn
cpu21_pointer #( cpu21_pointer #(
.SAMPLE_DIV(POINTER_SAMPLE_DIV) .SAMPLE_DIV(POINTER_SAMPLE_DIV)
) u_ptr ( ) u_ptr (
File diff suppressed because it is too large Load Diff
+33 -64
View File
@@ -11,7 +11,7 @@
// 0x0000_1004 七段数码管:8 位十六进制数(每 4 位一个数码管,低 4 位 = 最右) // 0x0000_1004 七段数码管:8 位十六进制数(每 4 位一个数码管,低 4 位 = 最右)
// 0x0000_1100 + 4*k 鼠标寄存器组(见下) // 0x0000_1100 + 4*k 鼠标寄存器组(见下)
// 0x0000_1140 + 4*k 显示控制器寄存器(见下) // 0x0000_1140 + 4*k 显示控制器寄存器(见下)
// 0x0000_2000 - 0x27FF 显存窗口:40x30 格、每格 1 字节(只用到前 1200 字节) // 0x0000_2000 - 0x2FFF 显存窗口:40x30 格、每格 2 字节(共 2400 字节)
// //
// 鼠标寄存器(基址 0x1100): // 鼠标寄存器(基址 0x1100):
// +0x00 STATUS RO [0] FIFO 非空 [5:1] 项数 [6] 中断使能 [7] FIFO 满 // +0x00 STATUS RO [0] FIFO 非空 [5:1] 项数 [6] 中断使能 [7] FIFO 满
@@ -96,14 +96,12 @@ module cpu21_mmio #(
wire sel_seg = (addr_i[31:2] == SEG_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_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); 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 // 显存窗口 0x2000 - 0x2FFF:共 600 个字(每格两个字节)。
// (原来写成 addr[12] && !addr[11],要求 bit12=1 —— 那是 0x1000-0x17FF, wire sel_fb = (addr_i[31:12] == 20'h00002);
// 于是 0x2000+ 的读写全都不命中、而鼠标/显示寄存器写反而落进了显存)
wire sel_fb = (addr_i[31:14] == 18'd0) && addr_i[13] && !addr_i[12] && !addr_i[11];
wire [3:0] midx = addr_i[5:2]; wire [3:0] midx = addr_i[5:2];
wire [3:0] didx = addr_i[5:2]; wire [3:0] didx = addr_i[5:2];
wire [9:0] fb_widx = addr_i[10:2]; wire [9:0] fb_widx = addr_i[11:2];
wire fb_ok = (fb_widx < FB_WORDS); wire fb_ok = (fb_widx < FB_WORDS);
wire led_wr = mem_valid_i && mem_write_i && sel_led; wire led_wr = mem_valid_i && mem_write_i && sel_led;
@@ -144,8 +142,7 @@ module cpu21_mmio #(
if (reset) begin if (reset) begin
led_data_o <= 32'b0; led_data_o <= 32'b0;
led_valid_o <= 1'b0; led_valid_o <= 1'b0;
end end else begin
else begin
led_valid_o <= 1'b0; led_valid_o <= 1'b0;
if (led_wr) begin if (led_wr) begin
led_data_o <= merge_bytes(wdata_i, led_data_o, wstrb_i); led_data_o <= merge_bytes(wdata_i, led_data_o, wstrb_i);
@@ -161,8 +158,7 @@ module cpu21_mmio #(
if (reset) begin if (reset) begin
seg_data_o <= 32'b0; seg_data_o <= 32'b0;
seg_valid_o <= 1'b0; seg_valid_o <= 1'b0;
end end else begin
else begin
seg_valid_o <= 1'b0; seg_valid_o <= 1'b0;
if (seg_wr) begin if (seg_wr) begin
seg_data_o <= merge_bytes(wdata_i, seg_data_o, wstrb_i); seg_data_o <= merge_bytes(wdata_i, seg_data_o, wstrb_i);
@@ -211,42 +207,33 @@ module cpu21_mmio #(
cnt_q <= {(FIFO_AW + 1) {1'b0}}; cnt_q <= {(FIFO_AW + 1) {1'b0}};
disp_ctrl_q <= 32'h0000_0001; // 光标可见,背景色 0 disp_ctrl_q <= 32'h0000_0001; // 光标可见,背景色 0
disp_cursor_q <= 32'h0000_0000; disp_cursor_q <= 32'h0000_0000;
end end else begin
else begin
time_q <= time_q + 32'd1; time_q <= time_q + 32'd1;
if (frame_tick_i) if (frame_tick_i) frame_q <= frame_q + 32'd1;
frame_q <= frame_q + 32'd1;
if (push) begin if (push) begin
fifo[wr_ptr_q] <= {11'b0, mouse_btn_i, mouse_dy_i, mouse_dx_i}; fifo[wr_ptr_q] <= {11'b0, mouse_btn_i, mouse_dy_i, mouse_dx_i};
wr_ptr_q <= wr_ptr_q + 1'b1; wr_ptr_q <= wr_ptr_q + 1'b1;
evcnt_q <= evcnt_q + 32'd1; evcnt_q <= evcnt_q + 32'd1;
end end
if (pop) if (pop) rd_ptr_q <= rd_ptr_q + 1'b1;
rd_ptr_q <= rd_ptr_q + 1'b1;
cnt_q <= cnt_q + (push ? 1'b1 : 1'b0) - (pop ? 1'b1 : 1'b0); cnt_q <= cnt_q + (push ? 1'b1 : 1'b0) - (pop ? 1'b1 : 1'b0);
// 鼠标寄存器:CTRL(3) / LEVEL(8) // 鼠标寄存器:CTRL(3) / LEVEL(8)
if (mo_wr) begin if (mo_wr) begin
case (midx) case (midx)
4'd3: 4'd3: mctrl_q <= merge_bytes(wdata_i, mctrl_q, wstrb_i);
mctrl_q <= merge_bytes(wdata_i, mctrl_q, wstrb_i); 4'd8: level_q <= wdata_i[0];
4'd8: default: ;
level_q <= wdata_i[0];
default:
;
endcase endcase
end end
// 显示控制器:CTRL(0) / CURSOR(1) // 显示控制器:CTRL(0) / CURSOR(1)
if (dp_wr) begin if (dp_wr) begin
case (didx) case (didx)
4'd0: 4'd0: disp_ctrl_q <= merge_bytes(wdata_i, disp_ctrl_q, wstrb_i);
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);
4'd1: default: ;
disp_cursor_q <= merge_bytes(wdata_i, disp_cursor_q, wstrb_i);
default:
;
endcase endcase
end end
end end
@@ -261,47 +248,29 @@ module cpu21_mmio #(
rdata_mux = 32'b0; rdata_mux = 32'b0;
if (led_rd) begin if (led_rd) begin
rdata_mux = led_data_o; rdata_mux = led_data_o;
end end else if (seg_rd) begin
else if (seg_rd) begin
rdata_mux = seg_data_o; rdata_mux = seg_data_o;
end end else if (fb_rd) begin
else if (fb_rd) begin
rdata_mux = fb_rdata_i; rdata_mux = fb_rdata_i;
end end else if (mo_rd) begin
else if (mo_rd) begin
case (midx) case (midx)
4'd0: 4'd0: rdata_mux = {24'b0, fifo_full, irq_en, cnt_q[4:0], !fifo_empty};
rdata_mux = {24'b0, fifo_full, irq_en, cnt_q[4:0], !fifo_empty}; 4'd1: rdata_mux = fifo_head;
4'd1: 4'd3: rdata_mux = mctrl_q;
rdata_mux = fifo_head; 4'd4: rdata_mux = {11'b0, mouse_btn_i, sw_i};
4'd3: 4'd5: rdata_mux = time_q;
rdata_mux = mctrl_q; 4'd6: rdata_mux = frame_q;
4'd4: 4'd7: rdata_mux = evcnt_q;
rdata_mux = {11'b0, mouse_btn_i, sw_i}; 4'd8: rdata_mux = {31'b0, level_q};
4'd5: 4'd9: rdata_mux = 32'hCAFE_0002;
rdata_mux = time_q; default: rdata_mux = 32'b0;
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 endcase
end end else if (dp_rd) begin
else if (dp_rd) begin
case (didx) case (didx)
4'd0: 4'd0: rdata_mux = disp_ctrl_q;
rdata_mux = disp_ctrl_q; 4'd1: rdata_mux = disp_cursor_q;
4'd1: 4'd2: rdata_mux = 32'hCAFE_0001;
rdata_mux = disp_cursor_q; default: rdata_mux = 32'b0;
4'd2:
rdata_mux = 32'hCAFE_0001;
default:
rdata_mux = 32'b0;
endcase endcase
end end
end end
+143 -53
View File
@@ -4,7 +4,7 @@
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// cpu21_vga —— 640x480@60 的"格子"显示控制器(显存端口接板级顶层的双口 RAM)。 // cpu21_vga —— 640x480@60 的"格子"显示控制器(显存端口接板级顶层的双口 RAM)。
// //
// CPU 不直接操作像素:它只通过 MMIO 往显示缓冲区里写"每格 1 字节"(字形 + 颜色), // CPU 不直接操作像素:它只通过 MMIO 往显示缓冲区里写"每格 2 字节"(字形 + 颜色),
// 本模块按像素扫描把格子展开成画面(8x8 字模放大 2 倍填满 16x16 的格子)。 // 本模块按像素扫描把格子展开成画面(8x8 字模放大 2 倍填满 16x16 的格子)。
// 这样一格只要 1 次字节写,刷屏开销极小。 // 这样一格只要 1 次字节写,刷屏开销极小。
// //
@@ -59,13 +59,11 @@ module cpu21_vga #(
if (reset) begin if (reset) begin
hx <= 10'd0; hx <= 10'd0;
vy <= 10'd0; vy <= 10'd0;
end end else if (tick) begin
else if (tick) begin
if (hx == H_TOTAL - 1) begin if (hx == H_TOTAL - 1) begin
hx <= 10'd0; hx <= 10'd0;
vy <= (vy == V_TOTAL - 1) ? 10'd0 : (vy + 10'd1); vy <= (vy == V_TOTAL - 1) ? 10'd0 : (vy + 10'd1);
end end else begin
else begin
hx <= hx + 10'd1; hx <= hx + 10'd1;
end end
end end
@@ -84,88 +82,190 @@ module cpu21_vga #(
wire [4:0] crow = vy[8:4]; // 0..29 wire [4:0] crow = vy[8:4]; // 0..29
wire [10:0] cidx = (crow << 5) + (crow << 3) + ccol; // row*40 + col 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 [ 1:0] lane = {cidx[0], 1'b0};
wire [ 1:0] color_lane = lane + 2'd1;
wire [31:0] cell_word = fb_word_i; wire [31:0] cell_word = fb_word_i;
wire [7:0] cell_byte = cell_word >> {lane, 3'b000}; 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 [ 5:0] glyph = glyph_byte[5:0];
wire [2:0] gcolor = cell_byte[6:4]; wire [ 2:0] gcolor = color_byte[2:0];
// 全局背景色(字模点为 0 时显示的颜色) // 全局背景色(字模点为 0 时显示的颜色)
wire [ 2:0] bgcolor = ctrl_i[7:4]; wire [ 2:0] bgcolor = ctrl_i[7:4];
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// 8x8 字模(bit7 = 最左像素),放大 2 倍填满 16x16 的格子 // 8x8 字模(bit7 = 最左像素),放大 2 倍填满 16x16 的格子;字形编号 0..63。
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
wire [ 2:0] fx = hx[3:1]; wire [ 2:0] fx = hx[3:1];
wire [ 2:0] fy = vy[3:1]; wire [ 2:0] fy = vy[3:1];
function [7:0] font_row; function [7:0] font_row;
input [3:0] g; input [5:0] g;
input [2:0] r; input [2:0] r;
begin begin
case (g) case (g)
4'd0: 6'd0: font_row = 8'h00; // 空白
font_row = 8'h00; // 空白 6'd1:
4'd1:
font_row = (r == 3'd0) ? 8'h18 : (r == 3'd1) ? 8'h38 : (r == 3'd2) ? 8'h18 : 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'd3) ? 8'h18 : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h18 :
(r == 3'd6) ? 8'h7E : 8'h00; // 1 (r == 3'd6) ? 8'h7E : 8'h00; // 1
4'd2: 6'd2:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h06 : 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'd3) ? 8'h0C : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h30 :
(r == 3'd6) ? 8'h7E : 8'h00; // 2 (r == 3'd6) ? 8'h7E : 8'h00; // 2
4'd3: 6'd3:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h06 : 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'd3) ? 8'h1C : (r == 3'd4) ? 8'h06 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 3 (r == 3'd6) ? 8'h3C : 8'h00; // 3
4'd4: 6'd4:
font_row = (r == 3'd0) ? 8'h0C : (r == 3'd1) ? 8'h1C : (r == 3'd2) ? 8'h3C : 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'd3) ? 8'h6C : (r == 3'd4) ? 8'h7E : (r == 3'd5) ? 8'h0C :
(r == 3'd6) ? 8'h0C : 8'h00; // 4 (r == 3'd6) ? 8'h0C : 8'h00; // 4
4'd5: 6'd5:
font_row = (r == 3'd0) ? 8'h7E : (r == 3'd1) ? 8'h60 : (r == 3'd2) ? 8'h7C : 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'd3) ? 8'h06 : (r == 3'd4) ? 8'h06 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 5 (r == 3'd6) ? 8'h3C : 8'h00; // 5
4'd6: 6'd6:
font_row = (r == 3'd0) ? 8'h1C : (r == 3'd1) ? 8'h30 : (r == 3'd2) ? 8'h60 : 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'd3) ? 8'h7C : (r == 3'd4) ? 8'h66 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 6 (r == 3'd6) ? 8'h3C : 8'h00; // 6
4'd7: 6'd7:
font_row = (r == 3'd0) ? 8'h7E : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h0C : 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'd3) ? 8'h18 : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h18 :
(r == 3'd6) ? 8'h18 : 8'h00; // 7 (r == 3'd6) ? 8'h18 : 8'h00; // 7
4'd8: 6'd8:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h66 : 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'd3) ? 8'h3C : (r == 3'd4) ? 8'h66 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 8 (r == 3'd6) ? 8'h3C : 8'h00; // 8
4'd9: 6'd9:
font_row = (r == 3'd0) ? 8'hFF : (r == 3'd1) ? 8'h81 : (r == 3'd2) ? 8'hBD : 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'd3) ? 8'hA5 : (r == 3'd4) ? 8'hA5 : (r == 3'd5) ? 8'hBD :
(r == 3'd6) ? 8'h81 : 8'hFF; // 未揭开的方块 (r == 3'd6) ? 8'h81 : 8'hFF; // 未揭开的方块
4'd10: 6'd10:
font_row = (r == 3'd0) ? 8'h40 : (r == 3'd1) ? 8'h70 : (r == 3'd2) ? 8'h78 : 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'd3) ? 8'h70 : (r == 3'd4) ? 8'h40 : (r == 3'd5) ? 8'h40 :
(r == 3'd6) ? 8'h40 : 8'h00; // 旗 (r == 3'd6) ? 8'h40 : 8'h00; // 旗
4'd11: 6'd11:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h7E : (r == 3'd2) ? 8'hFF : 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'd3) ? 8'hFF : (r == 3'd4) ? 8'hFF : (r == 3'd5) ? 8'hFF :
(r == 3'd6) ? 8'h7E : 8'h3C; // 雷 (r == 3'd6) ? 8'h7E : 8'h3C; // 雷
4'd12: 6'd12:
font_row = (r == 3'd0) ? 8'h81 : (r == 3'd1) ? 8'h42 : (r == 3'd2) ? 8'h24 : 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'd3) ? 8'h18 : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h24 :
(r == 3'd6) ? 8'h42 : 8'h81; // 爆炸 (r == 3'd6) ? 8'h42 : 8'h81; // 爆炸
4'd13: 6'd13:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h06 : 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'd3) ? 8'h0C : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h00 :
(r == 3'd6) ? 8'h18 : 8'h00; // 问号 (r == 3'd6) ? 8'h18 : 8'h00; // 问号
4'd14: 6'd14: font_row = (r == 3'd3) ? 8'hFF : (r == 3'd4) ? 8'hFF : 8'h00; // 横线
font_row = (r == 3'd3) ? 8'hFF : (r == 3'd4) ? 8'hFF : 8'h00; // 横线 6'd15: font_row = 8'hFF; // 实心
4'd15: 6'd16:
font_row = 8'hFF; // 实心 font_row = (r == 3'd0) ? 8'h18 : (r == 3'd1) ? 8'h3C : (r == 3'd2) ? 8'h66 :
default: (r == 3'd3) ? 8'h66 : (r == 3'd4) ? 8'h7E : (r == 3'd5) ? 8'h66 :
font_row = 8'h00; (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 endcase
end end
endfunction endfunction
@@ -181,8 +281,7 @@ module cpu21_vga #(
// 再在 cursor_box 里与上 blink 即可。 // 再在 cursor_box 里与上 blink 即可。
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
wire cursor_here = (cursor_i[4:0] == ccol) && (cursor_i[12:8] == crow); 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) || wire on_border = (hx[3:0] < 4'd2) || (hx[3:0] > 4'd13) || (vy[3:0] < 4'd2) || (vy[3:0] > 4'd13);
(vy[3:0] < 4'd2) || (vy[3:0] > 4'd13);
wire cursor_box = cursor_here && ctrl_i[0] && on_border; wire cursor_box = cursor_here && ctrl_i[0] && on_border;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -192,22 +291,14 @@ module cpu21_vga #(
input [2:0] c; input [2:0] c;
begin begin
case (c) case (c)
3'd0: 3'd0: pal = {4'h0, 4'h0, 4'h0}; // 黑
pal = {4'h0, 4'h0, 4'h0}; // 黑 3'd1: pal = {4'h0, 4'h0, 4'hA}; // 深蓝
3'd1: 3'd2: pal = {4'h0, 4'hA, 4'h0}; // 绿
pal = {4'h0, 4'h0, 4'hA}; // 深蓝 3'd3: pal = {4'h0, 4'hA, 4'hA}; // 青
3'd2: 3'd4: pal = {4'hA, 4'h0, 4'h0}; // 红
pal = {4'h0, 4'hA, 4'h0}; // 绿 3'd5: pal = {4'hA, 4'h0, 4'hA}; // 洋红
3'd3: 3'd6: pal = {4'hA, 4'hA, 4'h0}; // 黄
pal = {4'h0, 4'hA, 4'hA}; // 青 default: pal = {4'hA, 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 endcase
end end
endfunction endfunction
@@ -222,8 +313,7 @@ module cpu21_vga #(
vga_hs <= 1'b1; vga_hs <= 1'b1;
vga_vs <= 1'b1; vga_vs <= 1'b1;
frame_tick_o <= 1'b0; frame_tick_o <= 1'b0;
end end else if (tick) begin
else if (tick) begin
{vga_r, vga_g, vga_b} <= px_rgb; {vga_r, vga_g, vga_b} <= px_rgb;
vga_hs <= hs_n; vga_hs <= hs_n;
vga_vs <= vs_n; vga_vs <= vs_n;
+11 -12
View File
@@ -126,13 +126,14 @@ powershell -ExecutionPolicy Bypass -File tools\build.ps1
| `0x0000_1004` | 七段数码管:8 位十六进制数(低 4 位 = 最右) | | `0x0000_1004` | 七段数码管:8 位十六进制数(低 4 位 = 最右) |
| `0x0000_1100+` | 鼠标:STATUS / DATA / ACK / CTRL / INPUT / TIME / FRAME / EVCNT / LEVEL / MAGIC | | `0x0000_1100+` | 鼠标:STATUS / DATA / ACK / CTRL / INPUT / TIME / FRAME / EVCNT / LEVEL / MAGIC |
| `0x0000_1140+` | 显示控制器:CTRL(光标可见、背景色)/ CURSOR(列、行)/ MAGIC | | `0x0000_1140+` | 显示控制器:CTRL(光标可见、背景色)/ CURSOR(列、行)/ MAGIC |
| `0x0000_2000-0x27FF` | 显存:40 列 × 30 行、每格 1 字节(只用到前 1200 字节) | | `0x0000_2000-0x2FFF` | 显存:40 列 × 30 行、每格 2 字节(共 2400 字节) |
`LEVEL` 是**软件中断**寄存器:写 1 拉起 `irq_i[1]`(关卡入口),关卡返回前写 0 清掉, `LEVEL` 是**软件中断**寄存器:写 1 拉起 `irq_i[1]`(关卡入口),关卡返回前写 0 清掉,
否则下次点"开始"没有新的上升沿。`MAGIC`(`0xCAFE0001/2`)可以读来自检 MMIO 是否活着。 否则下次点"开始"没有新的上升沿。`MAGIC`(`0xCAFE0001/2`)可以读来自检 MMIO 是否活着。
格子编码 1 字节 = `[3:0] 字形 [6:4] 颜色 [7] 保留`;字形 0 空白、1..8 数字、9 未揭开、 格子编码为两个字节:第一个字节低 6 位是字形编号,第二个字节低 3 位是颜色;字形 0 空白、
10 旗、11 雷、12 爆炸、13 问号、14 横线、15 实心;颜色是 3 位调色板。 1..8 数字、9 未揭开、10 旗、11 雷、12 爆炸、13 问号、14 横线、15 实心、16..41 为 A-Z;
颜色是 3 位调色板。
## 5. 怎么跑起来 ## 5. 怎么跑起来
@@ -165,10 +166,10 @@ Pmod PS2 插在 JA + 一个 PS/2 鼠标)。`cpu21_ps2_mouse.v` 只实现了**
## 6. 验证状态(说实话) ## 6. 验证状态(说实话)
| 部分 | 状态 | | 部分 | 状态 |
| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------- | | ------------------------------------------------------------------------------------ | ------------------------------------------------------ |
| C 工具链链路 | **已实测**:编译/链接/ISA 检查/hex 生成/入口地址逐条核对 全部通过 | | C 工具链链路 | 本次修改后未验证:当前环境缺少 RISC-V 交叉编译器 |
| `saolei.c` / `isr.c` / `start.c` / `linker.ld` | 逻辑对着 RTL 写;**未在仿真器/硬件上跑过** | | `saolei.c` / `isr.c` / `start.c` / `linker.ld` | 本机 GCC 语法检查通过;目标 ISA 构建待工具链恢复后执行 |
| `cpu21_mmio.v` 扩展、`cpu21_vga.v`、`cpu21_seg7.v`、`cpu21_pointer.v`、板级顶层、XDC | **未综合、未上板** | | `cpu21_mmio.v` 扩展、`cpu21_vga.v`、`cpu21_seg7.v`、`cpu21_pointer.v`、板级顶层、XDC | `xvlog` 编译通过;未综合、未上板 |
建议验证顺序:① 跑 `pipelined-cpu/tools/run_regress.ps1` 确认原来的跑马灯/中断/指令 建议验证顺序:① 跑 `pipelined-cpu/tools/run_regress.ps1` 确认原来的跑马灯/中断/指令
测试仍全 PASS;② 定义 `SAOLEI_ROM` 跑行为仿真,看有没有往 `0x2000` 起的显存写字节、 测试仍全 PASS;② 定义 `SAOLEI_ROM` 跑行为仿真,看有没有往 `0x2000` 起的显存写字节、
@@ -176,9 +177,7 @@ Pmod PS2 插在 JA + 一个 PS/2 鼠标)。`cpu21_ps2_mouse.v` 只实现了**
## 7. 已知可改进项 ## 7. 已知可改进项
- **屏幕上没有字母**:字模是 4 位字形(0-15),所以"开始"是绿色按钮条 + 光标框而不是 - **文字显示**:显存现在每格占 2 字节,字形编号扩展到 6 位,RTL 内置 A-Z 和数字/游戏
"START" 字样。要显示文字就把格子编码扩成 5 位字形(`mmio.h` 的 `CELL()` 与 符号字模,开始界面显示 `MINES`、`EASY`、`NORMAL`、`HARD`、`START`。
`cpu21_vga.v` 的字模表各改一处,颜色降到 3 位)。 - 棋盘固定 16×16,开始界面可选 20/40/60 雷(16 是 2 的幂,索引全用移位,一次除法都不用)。
- 棋盘固定 16×16、40 雷(16 是 2 的幂,索引全用移位,一次除法都不用);要改大小就改
`saolei.c` 顶部的 `BW/BH/NMINES`。
- `run_regress.ps1` 里还没有扫雷的自动测试项。 - `run_regress.ps1` 里还没有扫雷的自动测试项。
+35 -7
View File
@@ -24,7 +24,7 @@
* 0x0000_1140 + 0x00 DISP_CTRL [0] 光标可见 [6:4] 背景色 * 0x0000_1140 + 0x00 DISP_CTRL [0] 光标可见 [6:4] 背景色
* 0x0000_1140 + 0x04 DISP_CURSOR [5:0] 列 [13:8] 行 * 0x0000_1140 + 0x04 DISP_CURSOR [5:0] 列 [13:8] 行
* 0x0000_1140 + 0x08 DISP_MAGIC 0xCAFE0001 * 0x0000_1140 + 0x08 DISP_MAGIC 0xCAFE0001
* 0x0000_2000 - 0x0000_27FF 显存:40 列 x 30 行,每格 1 字节,共 1200 字节 * 0x0000_2000 - 0x0000_2FFF 显存:40 列 x 30 行,每格 2 字节,共 2400 字节
* *
* 鼠标事件单位:1/8 格(按键一次 = 8 = 移动一格;PS/2 鼠标用原始计数)。 * 鼠标事件单位:1/8 格(按键一次 = 8 = 移动一格;PS/2 鼠标用原始计数)。
* 左键点击 -> 事件的 btn 位 0 置 1;右键(PS/2)-> 位 1。 * 左键点击 -> 事件的 btn 位 0 置 1;右键(PS/2)-> 位 1。
@@ -69,13 +69,15 @@
#define REG_R(a) (*(volatile unsigned *)((unsigned)(a))) #define REG_R(a) (*(volatile unsigned *)((unsigned)(a)))
#define REG_W(a) (*(volatile unsigned *)((unsigned)(a))) #define REG_W(a) (*(volatile unsigned *)((unsigned)(a)))
/* 显存按字节写(只能 sb,不能 lb —— 这颗核没有 lb/lbu) */ /* 显存按字节写(只能 sb,不能 lb —— 这颗核没有 lb/lbu)。每个格子占两个字节。 */
static inline void fb_write(unsigned idx, unsigned val) static inline void fb_write_cell(unsigned idx, unsigned glyph, unsigned color)
{ {
*(volatile unsigned char *)(FB_BASE + idx) = (unsigned char)(val & 0xFFu); unsigned addr = FB_BASE + (idx << 1);
*(volatile unsigned char *)addr = (unsigned char)(glyph & 0x3Fu);
*(volatile unsigned char *)(addr + 1u) = (unsigned char)(color & 0x07u);
} }
/* ---- 格子编码:1 字节 = [3:0] 字形 [6:4] 颜色 [7] 保留 ---- */ /* ---- 格子编码:两个字节 = 字形[5:0] + 颜色[2:0] ---- */
#define G_BLANK 0u /* 空 */ #define G_BLANK 0u /* 空 */
#define G_N1 1u /* 数字 1..8 = 1..8 */ #define G_N1 1u /* 数字 1..8 = 1..8 */
#define G_COVER 9u /* 未揭开 */ #define G_COVER 9u /* 未揭开 */
@@ -86,6 +88,34 @@ static inline void fb_write(unsigned idx, unsigned val)
#define G_BAR 14u /* 横线 */ #define G_BAR 14u /* 横线 */
#define G_SOLID 15u /* 实心 */ #define G_SOLID 15u /* 实心 */
/* 大写字母字形编号 16..41,供菜单文字使用。 */
#define G_A 16u
#define G_B 17u
#define G_C 18u
#define G_D 19u
#define G_E 20u
#define G_F 21u
#define G_G 22u
#define G_H 23u
#define G_I 24u
#define G_J 25u
#define G_K 26u
#define G_L 27u
#define G_M 28u
#define G_N 29u
#define G_O 30u
#define G_P 31u
#define G_Q 32u
#define G_R 33u
#define G_S 34u
#define G_T 35u
#define G_U 36u
#define G_V 37u
#define G_W 38u
#define G_X 39u
#define G_Y 40u
#define G_Z 41u
#define C_BLACK 0u #define C_BLACK 0u
#define C_BLUE 1u #define C_BLUE 1u
#define C_GREEN 2u #define C_GREEN 2u
@@ -95,8 +125,6 @@ static inline void fb_write(unsigned idx, unsigned val)
#define C_YELLOW 6u #define C_YELLOW 6u
#define C_GRAY 7u #define C_GRAY 7u
#define CELL(g, c) ((unsigned)((g) | ((c) << 4)))
/* ---- 软件事件环形队列:中断服务程序(isr.c)生产,主循环(saolei.c)消费 ---- */ /* ---- 软件事件环形队列:中断服务程序(isr.c)生产,主循环(saolei.c)消费 ---- */
#define EVQ_SIZE 64u #define EVQ_SIZE 64u
#define EVQ_MASK (EVQ_SIZE - 1u) #define EVQ_MASK (EVQ_SIZE - 1u)
+86 -20
View File
@@ -1,7 +1,7 @@
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* saolei.c —— 纯 C 扫雷(RV32IM 子集:只用这颗核实现了的指令) * saolei.c —— 纯 C 扫雷(RV32IM 子集:只用这颗核实现了的指令)
* *
* 显示:把每个格子写成显存里的 1 个字节(MMIO 0x2000 起) * 显示:把每个格子写成显存里的 2 个字节(MMIO 0x2000 起)
* 输入:鼠标事件(中断驱动)——中断里搬到软件队列,主循环在下面处理 * 输入:鼠标事件(中断驱动)——中断里搬到软件队列,主循环在下面处理
* *
* 针对这颗 CPU 的硬约束(很重要,改代码时别踩): * 针对这颗 CPU 的硬约束(很重要,改代码时别踩):
@@ -13,7 +13,7 @@
* 所以本文件所有索引运算都用"2 的幂 + 移位/掩码",一次除法都不用。 * 所以本文件所有索引运算都用"2 的幂 + 移位/掩码",一次除法都不用。
* 4) 没有 fence/ebreak 等;中断返回是 uret(在 isr.c 里)。 * 4) 没有 fence/ebreak 等;中断返回是 uret(在 isr.c 里)。
* *
* 棋盘:16 x 16,40 颗雷(16 是 2 的幂 => 索引全用移位,颜色/字形查表也不用除法) * 棋盘:16 x 16,雷数可选 20/40/60(16 是 2 的幂 => 索引全用移位)
* 网格:40 列 x 30 行,棋盘居中放在 (行 7..22, 列 12..27) * 网格:40 列 x 30 行,棋盘居中放在 (行 7..22, 列 12..27)
* ---------------------------------------------------------------------------*/ * ---------------------------------------------------------------------------*/
#include "mmio.h" #include "mmio.h"
@@ -21,7 +21,6 @@
#define BW 16u /* 棋盘宽(格) */ #define BW 16u /* 棋盘宽(格) */
#define BH 16u /* 棋盘高(格) */ #define BH 16u /* 棋盘高(格) */
#define NCELLS (BW * BH) /* 256 */ #define NCELLS (BW * BH) /* 256 */
#define NMINES 40u
#define BM_WORDS (NCELLS / 32u) /* 8 */ #define BM_WORDS (NCELLS / 32u) /* 8 */
#define BOARD_C0 12u /* 棋盘左上角在网格里的列 */ #define BOARD_C0 12u /* 棋盘左上角在网格里的列 */
@@ -42,6 +41,8 @@ static unsigned opened_cnt;
static unsigned flags_cnt; static unsigned flags_cnt;
static unsigned g_state; static unsigned g_state;
static unsigned g_first_done; static unsigned g_first_done;
static unsigned g_mines;
static unsigned g_difficulty;
static unsigned rnd_state; static unsigned rnd_state;
static unsigned cur_r, cur_c; /* 光标所在的网格格 */ static unsigned cur_r, cur_c; /* 光标所在的网格格 */
static int acc_x, acc_y; /* 指针累积量(1/8 格,有符号) */ static int acc_x, acc_y; /* 指针累积量(1/8 格,有符号) */
@@ -67,7 +68,7 @@ static void bclr(unsigned *bm, unsigned i)
static void draw_cell(unsigned gr, unsigned gc, unsigned glyph, unsigned color) static void draw_cell(unsigned gr, unsigned gc, unsigned glyph, unsigned color)
{ {
fb_write(gr * GRID_COLS + gc, CELL(glyph, color)); fb_write_cell(gr * GRID_COLS + gc, glyph, color);
} }
/* 把棋盘格 (r,c) 画出来 */ /* 把棋盘格 (r,c) 画出来 */
@@ -118,6 +119,15 @@ static unsigned rnd_next(void)
return rnd_state >> 16; return rnd_state >> 16;
} }
static unsigned difficulty_mines(unsigned difficulty)
{
if (difficulty == 0u)
return 20u;
if (difficulty == 2u)
return 60u;
return 40u;
}
/* ============================ 画面 ============================ */ /* ============================ 画面 ============================ */
static void clear_screen(void) static void clear_screen(void)
@@ -125,7 +135,7 @@ static void clear_screen(void)
unsigned i; unsigned i;
for (i = 0u; i < (GRID_COLS * GRID_ROWS); i = i + 1u) for (i = 0u; i < (GRID_COLS * GRID_ROWS); i = i + 1u)
{ {
fb_write(i, 0u); fb_write_cell(i, G_BLANK, C_BLACK);
} }
} }
@@ -278,7 +288,7 @@ static void reveal_at(unsigned i)
} }
} }
if (opened_cnt == (NCELLS - NMINES)) if (opened_cnt == (NCELLS - g_mines))
win(); win();
} }
@@ -288,7 +298,7 @@ static void place_mines(unsigned safe)
unsigned sr = safe >> 4; unsigned sr = safe >> 4;
unsigned sc = safe & 15u; unsigned sc = safe & 15u;
while (placed < NMINES) while (placed < g_mines)
{ {
i = rnd_next() & 255u; /* NCELLS = 256,正好 2 的幂,不需要取模 */ i = rnd_next() & 255u; /* NCELLS = 256,正好 2 的幂,不需要取模 */
r = i >> 4; r = i >> 4;
@@ -347,7 +357,7 @@ static void toggle_flag(void)
flags_cnt = flags_cnt + 1u; flags_cnt = flags_cnt + 1u;
draw_tile(r, c, G_FLAG, C_YELLOW); draw_tile(r, c, G_FLAG, C_YELLOW);
} }
REG_W(LED_ADDR) = NMINES - flags_cnt; REG_W(LED_ADDR) = g_mines - flags_cnt;
} }
/* ============================ 鼠标事件 ============================ */ /* ============================ 鼠标事件 ============================ */
@@ -385,6 +395,11 @@ static unsigned g_exit; /* 关卡内请求返回 */
#define START_R 20u #define START_R 20u
#define START_C0 17u #define START_C0 17u
#define START_C1 22u #define START_C1 22u
#define DIFF_EASY_R 14u
#define DIFF_NORMAL_R 16u
#define DIFF_HARD_R 18u
static void draw_start_screen(void);
/* 背景用:光标可以在整个画面上移动 */ /* 背景用:光标可以在整个画面上移动 */
static void move_cursor_screen(int dr, int dc) static void move_cursor_screen(int dr, int dc)
@@ -420,10 +435,29 @@ static void handle_event(unsigned ev)
{ /* 左键 */ { /* 左键 */
if (g_in_level == 0u) if (g_in_level == 0u)
{ {
if (cur_r == DIFF_EASY_R)
{
g_difficulty = 0u;
draw_start_screen();
return;
}
if (cur_r == DIFF_NORMAL_R)
{
g_difficulty = 1u;
draw_start_screen();
return;
}
if (cur_r == DIFF_HARD_R)
{
g_difficulty = 2u;
draw_start_screen();
return;
}
/* 背景:点到"开始"按钮 -> 拉起软件中断,跳到 IRQ1 里的关卡程序 */ /* 背景:点到"开始"按钮 -> 拉起软件中断,跳到 IRQ1 里的关卡程序 */
if ((cur_r == START_R) && (cur_c >= START_C0) && (cur_c <= START_C1)) if ((cur_r == START_R) && (cur_c >= START_C0) && (cur_c <= START_C1))
{ {
g_in_level = 1u; /* 关卡程序返回时自己清 0 */ g_in_level = 1u; /* 关卡程序返回时自己清 0 */
g_mines = difficulty_mines(g_difficulty);
REG_W(REG_LEVEL) = 1u; REG_W(REG_LEVEL) = 1u;
} }
} }
@@ -490,24 +524,55 @@ static void handle_event(unsigned ev)
* ========================================================================== */ * ========================================================================== */
static void draw_start_screen(void) static void draw_start_screen(void)
{ {
unsigned i; unsigned i, selected_color;
clear_screen(); clear_screen();
/* 标题区:两排雷图案,纯图形(字模里没有字母;要显示文字就得扩字模,见 README) */ /* 标题和难度菜单使用 RTL 字模中的大写字母。 */
for (i = 0u; i < 6u; i = i + 1u) draw_cell(6u, 18u, G_M, C_RED);
{ draw_cell(6u, 19u, G_I, C_RED);
draw_cell(6u, 15u + i * 2u, G_MINE, C_RED); draw_cell(6u, 20u, G_N, C_RED);
draw_cell(8u, 15u + i * 2u, G_MINE, C_RED); draw_cell(6u, 21u, G_E, C_RED);
} draw_cell(6u, 22u, G_S, C_RED);
/* "开始"按钮:绿色实心条,上下各一条黄线框住它 */ selected_color = C_YELLOW;
if (g_difficulty != 0u)
selected_color = C_GRAY;
draw_cell(DIFF_EASY_R, 18u, G_E, selected_color);
draw_cell(DIFF_EASY_R, 19u, G_A, selected_color);
draw_cell(DIFF_EASY_R, 20u, G_S, selected_color);
draw_cell(DIFF_EASY_R, 21u, G_Y, selected_color);
selected_color = C_YELLOW;
if (g_difficulty != 1u)
selected_color = C_GRAY;
draw_cell(DIFF_NORMAL_R, 16u, G_N, selected_color);
draw_cell(DIFF_NORMAL_R, 17u, G_O, selected_color);
draw_cell(DIFF_NORMAL_R, 18u, G_R, selected_color);
draw_cell(DIFF_NORMAL_R, 19u, G_M, selected_color);
draw_cell(DIFF_NORMAL_R, 20u, G_A, selected_color);
draw_cell(DIFF_NORMAL_R, 21u, G_L, selected_color);
selected_color = C_YELLOW;
if (g_difficulty != 2u)
selected_color = C_GRAY;
draw_cell(DIFF_HARD_R, 18u, G_H, selected_color);
draw_cell(DIFF_HARD_R, 19u, G_A, selected_color);
draw_cell(DIFF_HARD_R, 20u, G_R, selected_color);
draw_cell(DIFF_HARD_R, 21u, G_D, selected_color);
/* "START" 按钮:绿色实心条,上下各一条黄线框住它。 */
for (i = START_C0; i <= START_C1; i = i + 1u) for (i = START_C0; i <= START_C1; i = i + 1u)
{ {
draw_cell(START_R - 1u, i, G_BAR, C_YELLOW); draw_cell(START_R - 1u, i, G_BAR, C_YELLOW);
draw_cell(START_R, i, G_SOLID, C_GREEN); draw_cell(START_R, i, G_SOLID, C_GREEN);
draw_cell(START_R + 1u, i, G_BAR, C_YELLOW); draw_cell(START_R + 1u, i, G_BAR, C_YELLOW);
} }
draw_cell(START_R, 17u, G_S, C_BLACK);
draw_cell(START_R, 18u, G_T, C_BLACK);
draw_cell(START_R, 19u, G_A, C_BLACK);
draw_cell(START_R, 20u, G_R, C_BLACK);
draw_cell(START_R, 21u, G_T, C_BLACK);
/* 下方提示条 */ /* 下方提示条 */
for (i = 12u; i <= 27u; i = i + 1u) for (i = 12u; i <= 27u; i = i + 1u)
@@ -561,10 +626,10 @@ void level_main(void)
draw_frame(); draw_frame();
draw_all_covered(); draw_all_covered();
draw_cell(RET_R, RET_C, G_BAR, C_YELLOW); /* 左上角黄格 = 返回 */ draw_cell(RET_R, RET_C, G_BAR, C_YELLOW); /* 左上角黄格 = 返回 */
set_bg(C_BLUE); set_bg(C_BLACK);
update_cursor_reg(); update_cursor_reg();
REG_W(LED_ADDR) = NMINES; REG_W(LED_ADDR) = g_mines;
REG_W(REG_SEG) = NMINES; /* 数码管显示剩余雷数 */ REG_W(REG_SEG) = g_mines; /* 数码管显示剩余雷数 */
/* ---- 关卡主循环 ---- */ /* ---- 关卡主循环 ---- */
while (g_exit == 0u) while (g_exit == 0u)
@@ -595,7 +660,7 @@ void level_main(void)
} }
else else
{ {
set_bg(C_BLUE); set_bg(C_BLACK);
} }
} }
} }
@@ -615,6 +680,7 @@ int main(void)
crt_zero_bss(); /* 清 .bss(上电 RAM 本来就是 0,保险起见) */ crt_zero_bss(); /* 清 .bss(上电 RAM 本来就是 0,保险起见) */
rnd_state = REG_R(REG_TIME) | 1u; /* 用自由计数器当随机种子 */ rnd_state = REG_R(REG_TIME) | 1u; /* 用自由计数器当随机种子 */
g_difficulty = 1u; /* 默认普通:40 颗雷 */
REG_W(REG_MCTRL) = 1u; /* 打开鼠标中断(IRQ2,入口 0x200) */ REG_W(REG_MCTRL) = 1u; /* 打开鼠标中断(IRQ2,入口 0x200) */
REG_W(REG_LEVEL) = 0u; /* 清关卡软中断 */ REG_W(REG_LEVEL) = 0u; /* 清关卡软中断 */
+7 -2
View File
@@ -58,6 +58,11 @@ $OBJDUMP = $Prefix + 'objdump'
$OBJCOPY = $Prefix + 'objcopy' $OBJCOPY = $Prefix + 'objcopy'
$SIZE = $Prefix + 'size' $SIZE = $Prefix + 'size'
$Python = 'py'
if (-not (Get-Command $Python -ErrorAction SilentlyContinue)) {
$Python = 'python'
}
Write-Output "工具链前缀: $Prefix" Write-Output "工具链前缀: $Prefix"
# ---------------------------------------------------------------- 编译参数 # ---------------------------------------------------------------- 编译参数
@@ -139,7 +144,7 @@ Write-Output "反汇编清单: $lst"
$isaOk = $true $isaOk = $true
if (-not $SkipIsaCheck) { if (-not $SkipIsaCheck) {
Write-Output "" Write-Output ""
& py (Join-Path $PSScriptRoot 'check_isa.py') $lst & $Python (Join-Path $PSScriptRoot 'check_isa.py') $lst
if ($LASTEXITCODE -ne 0) { $isaOk = $false } if ($LASTEXITCODE -ne 0) { $isaOk = $false }
} }
@@ -149,7 +154,7 @@ $hex = Join-Path $build 'saolei.hex'
$vh = Join-Path $root '..\pipelined-cpu\srcs\board\cpu21_saolei_rom.vh' $vh = Join-Path $root '..\pipelined-cpu\srcs\board\cpu21_saolei_rom.vh'
& $OBJCOPY -O binary $elf $bin & $OBJCOPY -O binary $elf $bin
& py (Join-Path $PSScriptRoot 'elf2hex.py') $bin $hex $RomWords $vh & $Python (Join-Path $PSScriptRoot 'elf2hex.py') $bin $hex $RomWords $vh
# ---------------------------------------------------------------- 汇总 # ---------------------------------------------------------------- 汇总
Write-Output "" Write-Output ""