Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47d2f22d07 | ||
|
|
3f765ae4c5 |
@@ -53,7 +53,10 @@ module cpu21_board_top #(
|
|||||||
// 所以不会掩盖时序违例(约束偏严)。
|
// 所以不会掩盖时序违例(约束偏严)。
|
||||||
parameter integer CLK_DIV_INTRUPT = 32,
|
parameter integer CLK_DIV_INTRUPT = 32,
|
||||||
// 鼠标事件源:0 = 板上 5 向按键(免外设,默认);1 = PS/2 鼠标(Pmod JA)
|
// 鼠标事件源:0 = 板上 5 向按键(免外设,默认);1 = PS/2 鼠标(Pmod JA)
|
||||||
parameter integer USE_PS2_MOUSE = 0
|
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 clk100, // E3
|
||||||
input wire rst_n, // C12:CPU_RESETN,低有效
|
input wire rst_n, // C12:CPU_RESETN,低有效
|
||||||
@@ -177,7 +180,14 @@ module cpu21_board_top #(
|
|||||||
wire frame_tick;
|
wire frame_tick;
|
||||||
wire [15:0] sw_vec = {12'b0, sw_flag, irq_sw};
|
wire [15:0] sw_vec = {12'b0, sw_flag, irq_sw};
|
||||||
// 中断连线:SW0 与鼠标共用一个入口,SW1 与"关卡入口"共用一个入口。
|
// 中断连线:SW0 与鼠标共用一个入口,SW1 与"关卡入口"共用一个入口。
|
||||||
wire [2:0] irq_vec = {irq_sw[2], (level_irq | irq_sw[1]), (mouse_irq | irq_sw[0])};
|
// 中断线对应关系(必须与 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)
|
// 指令 ROM:组合读(未用到的地址填 NOP)
|
||||||
@@ -192,6 +202,7 @@ module cpu21_board_top #(
|
|||||||
// 扫雷程序(由 saolei/tools/build.ps1 生成的 cpu21_saolei_rom.vh)。
|
// 扫雷程序(由 saolei/tools/build.ps1 生成的 cpu21_saolei_rom.vh)。
|
||||||
// 综合/仿真时定义宏 SAOLEI_ROM 即可切换到这个程序;不定义就是下面的跑马灯。
|
// 综合/仿真时定义宏 SAOLEI_ROM 即可切换到这个程序;不定义就是下面的跑马灯。
|
||||||
`include "cpu21_saolei_rom.vh"
|
`include "cpu21_saolei_rom.vh"
|
||||||
|
|
||||||
`elsif RISC_V_INTRUPT_CCAB_ROM
|
`elsif RISC_V_INTRUPT_CCAB_ROM
|
||||||
// 中断综合测试程序:三个中断入口由镜像中的实际标签位置决定。
|
// 中断综合测试程序:三个中断入口由镜像中的实际标签位置决定。
|
||||||
`include "risc-v-intrupt-ccab.vh"
|
`include "risc-v-intrupt-ccab.vh"
|
||||||
@@ -451,7 +462,7 @@ module cpu21_board_top #(
|
|||||||
end
|
end
|
||||||
else begin : g_btn
|
else begin : g_btn
|
||||||
cpu21_pointer #(
|
cpu21_pointer #(
|
||||||
.SAMPLE_DIV(50000)
|
.SAMPLE_DIV(POINTER_SAMPLE_DIV)
|
||||||
) u_ptr (
|
) u_ptr (
|
||||||
.clk (clk_cpu),
|
.clk (clk_cpu),
|
||||||
.reset (reset),
|
.reset (reset),
|
||||||
|
|||||||
@@ -5,65 +5,65 @@
|
|||||||
rom[2] = 32'h000012B7;
|
rom[2] = 32'h000012B7;
|
||||||
rom[3] = 32'hE3028293;
|
rom[3] = 32'hE3028293;
|
||||||
rom[4] = 32'h00028067;
|
rom[4] = 32'h00028067;
|
||||||
rom[5] = 32'h00000000;
|
rom[5] = 32'h00000013;
|
||||||
rom[6] = 32'h00000000;
|
rom[6] = 32'h00000013;
|
||||||
rom[7] = 32'h00000000;
|
rom[7] = 32'h00000013;
|
||||||
rom[8] = 32'h00000000;
|
rom[8] = 32'h00000013;
|
||||||
rom[9] = 32'h00000000;
|
rom[9] = 32'h00000013;
|
||||||
rom[10] = 32'h00000000;
|
rom[10] = 32'h00000013;
|
||||||
rom[11] = 32'h00000000;
|
rom[11] = 32'h00000013;
|
||||||
rom[12] = 32'h00000000;
|
rom[12] = 32'h00000013;
|
||||||
rom[13] = 32'h00000000;
|
rom[13] = 32'h00000013;
|
||||||
rom[14] = 32'h00000000;
|
rom[14] = 32'h00000013;
|
||||||
rom[15] = 32'h00000000;
|
rom[15] = 32'h00000013;
|
||||||
rom[16] = 32'h00000000;
|
rom[16] = 32'h00000013;
|
||||||
rom[17] = 32'h00000000;
|
rom[17] = 32'h00000013;
|
||||||
rom[18] = 32'h00000000;
|
rom[18] = 32'h00000013;
|
||||||
rom[19] = 32'h00000000;
|
rom[19] = 32'h00000013;
|
||||||
rom[20] = 32'h00000000;
|
rom[20] = 32'h00000013;
|
||||||
rom[21] = 32'h00000000;
|
rom[21] = 32'h00000013;
|
||||||
rom[22] = 32'h00000000;
|
rom[22] = 32'h00000013;
|
||||||
rom[23] = 32'h00000000;
|
rom[23] = 32'h00000013;
|
||||||
rom[24] = 32'h00000000;
|
rom[24] = 32'h00000013;
|
||||||
rom[25] = 32'h00000000;
|
rom[25] = 32'h00000013;
|
||||||
rom[26] = 32'h00000000;
|
rom[26] = 32'h00000013;
|
||||||
rom[27] = 32'h00000000;
|
rom[27] = 32'h00000013;
|
||||||
rom[28] = 32'h00000000;
|
rom[28] = 32'h00000013;
|
||||||
rom[29] = 32'h00000000;
|
rom[29] = 32'h00000013;
|
||||||
rom[30] = 32'h00000000;
|
rom[30] = 32'h00000013;
|
||||||
rom[31] = 32'h00000000;
|
rom[31] = 32'h00000013;
|
||||||
rom[32] = 32'h00000000;
|
rom[32] = 32'h00000013;
|
||||||
rom[33] = 32'h00000000;
|
rom[33] = 32'h00000013;
|
||||||
rom[34] = 32'h00000000;
|
rom[34] = 32'h00000013;
|
||||||
rom[35] = 32'h00000000;
|
rom[35] = 32'h00000013;
|
||||||
rom[36] = 32'h00000000;
|
rom[36] = 32'h00000013;
|
||||||
rom[37] = 32'h00000000;
|
rom[37] = 32'h00000013;
|
||||||
rom[38] = 32'h00000000;
|
rom[38] = 32'h00000013;
|
||||||
rom[39] = 32'h00000000;
|
rom[39] = 32'h00000013;
|
||||||
rom[40] = 32'h00000000;
|
rom[40] = 32'h00000013;
|
||||||
rom[41] = 32'h00000000;
|
rom[41] = 32'h00000013;
|
||||||
rom[42] = 32'h00000000;
|
rom[42] = 32'h00000013;
|
||||||
rom[43] = 32'h00000000;
|
rom[43] = 32'h00000013;
|
||||||
rom[44] = 32'h00000000;
|
rom[44] = 32'h00000013;
|
||||||
rom[45] = 32'h00000000;
|
rom[45] = 32'h00000013;
|
||||||
rom[46] = 32'h00000000;
|
rom[46] = 32'h00000013;
|
||||||
rom[47] = 32'h00000000;
|
rom[47] = 32'h00000013;
|
||||||
rom[48] = 32'h00000000;
|
rom[48] = 32'h00000013;
|
||||||
rom[49] = 32'h00000000;
|
rom[49] = 32'h00000013;
|
||||||
rom[50] = 32'h00000000;
|
rom[50] = 32'h00000013;
|
||||||
rom[51] = 32'h00000000;
|
rom[51] = 32'h00000013;
|
||||||
rom[52] = 32'h00000000;
|
rom[52] = 32'h00000013;
|
||||||
rom[53] = 32'h00000000;
|
rom[53] = 32'h00000013;
|
||||||
rom[54] = 32'h00000000;
|
rom[54] = 32'h00000013;
|
||||||
rom[55] = 32'h00000000;
|
rom[55] = 32'h00000013;
|
||||||
rom[56] = 32'h00000000;
|
rom[56] = 32'h00000013;
|
||||||
rom[57] = 32'h00000000;
|
rom[57] = 32'h00000013;
|
||||||
rom[58] = 32'h00000000;
|
rom[58] = 32'h00000013;
|
||||||
rom[59] = 32'h00000000;
|
rom[59] = 32'h00000013;
|
||||||
rom[60] = 32'h00000000;
|
rom[60] = 32'h00000013;
|
||||||
rom[61] = 32'h00000000;
|
rom[61] = 32'h00000013;
|
||||||
rom[62] = 32'h00000000;
|
rom[62] = 32'h00000013;
|
||||||
rom[63] = 32'h00000000;
|
rom[63] = 32'h00000013;
|
||||||
rom[64] = 32'hFC010113;
|
rom[64] = 32'hFC010113;
|
||||||
rom[65] = 32'h00112023;
|
rom[65] = 32'h00112023;
|
||||||
rom[66] = 32'h00512223;
|
rom[66] = 32'h00512223;
|
||||||
@@ -101,97 +101,97 @@
|
|||||||
rom[98] = 32'h00012083;
|
rom[98] = 32'h00012083;
|
||||||
rom[99] = 32'h04010113;
|
rom[99] = 32'h04010113;
|
||||||
rom[100] = 32'h00200073;
|
rom[100] = 32'h00200073;
|
||||||
rom[101] = 32'h00000000;
|
rom[101] = 32'h00000013;
|
||||||
rom[102] = 32'h00000000;
|
rom[102] = 32'h00000013;
|
||||||
rom[103] = 32'h00000000;
|
rom[103] = 32'h00000013;
|
||||||
rom[104] = 32'h00000000;
|
rom[104] = 32'h00000013;
|
||||||
rom[105] = 32'h00000000;
|
rom[105] = 32'h00000013;
|
||||||
rom[106] = 32'h00000000;
|
rom[106] = 32'h00000013;
|
||||||
rom[107] = 32'h00000000;
|
rom[107] = 32'h00000013;
|
||||||
rom[108] = 32'h00000000;
|
rom[108] = 32'h00000013;
|
||||||
rom[109] = 32'h00000000;
|
rom[109] = 32'h00000013;
|
||||||
rom[110] = 32'h00000000;
|
rom[110] = 32'h00000013;
|
||||||
rom[111] = 32'h00000000;
|
rom[111] = 32'h00000013;
|
||||||
rom[112] = 32'h00000000;
|
rom[112] = 32'h00000013;
|
||||||
rom[113] = 32'h00000000;
|
rom[113] = 32'h00000013;
|
||||||
rom[114] = 32'h00000000;
|
rom[114] = 32'h00000013;
|
||||||
rom[115] = 32'h00000000;
|
rom[115] = 32'h00000013;
|
||||||
rom[116] = 32'h00000000;
|
rom[116] = 32'h00000013;
|
||||||
rom[117] = 32'h00000000;
|
rom[117] = 32'h00000013;
|
||||||
rom[118] = 32'h00000000;
|
rom[118] = 32'h00000013;
|
||||||
rom[119] = 32'h00000000;
|
rom[119] = 32'h00000013;
|
||||||
rom[120] = 32'h00000000;
|
rom[120] = 32'h00000013;
|
||||||
rom[121] = 32'h00000000;
|
rom[121] = 32'h00000013;
|
||||||
rom[122] = 32'h00000000;
|
rom[122] = 32'h00000013;
|
||||||
rom[123] = 32'h00000000;
|
rom[123] = 32'h00000013;
|
||||||
rom[124] = 32'h00000000;
|
rom[124] = 32'h00000013;
|
||||||
rom[125] = 32'h00000000;
|
rom[125] = 32'h00000013;
|
||||||
rom[126] = 32'h00000000;
|
rom[126] = 32'h00000013;
|
||||||
rom[127] = 32'h00000000;
|
rom[127] = 32'h00000013;
|
||||||
rom[128] = 32'hFF810113;
|
rom[128] = 32'hFF810113;
|
||||||
rom[129] = 32'h00112023;
|
rom[129] = 32'h00112023;
|
||||||
rom[130] = 32'h4A1000EF;
|
rom[130] = 32'h4A1000EF;
|
||||||
rom[131] = 32'h00012083;
|
rom[131] = 32'h00012083;
|
||||||
rom[132] = 32'h00810113;
|
rom[132] = 32'h00810113;
|
||||||
rom[133] = 32'h00200073;
|
rom[133] = 32'h00200073;
|
||||||
rom[134] = 32'h00000000;
|
rom[134] = 32'h00000013;
|
||||||
rom[135] = 32'h00000000;
|
rom[135] = 32'h00000013;
|
||||||
rom[136] = 32'h00000000;
|
rom[136] = 32'h00000013;
|
||||||
rom[137] = 32'h00000000;
|
rom[137] = 32'h00000013;
|
||||||
rom[138] = 32'h00000000;
|
rom[138] = 32'h00000013;
|
||||||
rom[139] = 32'h00000000;
|
rom[139] = 32'h00000013;
|
||||||
rom[140] = 32'h00000000;
|
rom[140] = 32'h00000013;
|
||||||
rom[141] = 32'h00000000;
|
rom[141] = 32'h00000013;
|
||||||
rom[142] = 32'h00000000;
|
rom[142] = 32'h00000013;
|
||||||
rom[143] = 32'h00000000;
|
rom[143] = 32'h00000013;
|
||||||
rom[144] = 32'h00000000;
|
rom[144] = 32'h00000013;
|
||||||
rom[145] = 32'h00000000;
|
rom[145] = 32'h00000013;
|
||||||
rom[146] = 32'h00000000;
|
rom[146] = 32'h00000013;
|
||||||
rom[147] = 32'h00000000;
|
rom[147] = 32'h00000013;
|
||||||
rom[148] = 32'h00000000;
|
rom[148] = 32'h00000013;
|
||||||
rom[149] = 32'h00000000;
|
rom[149] = 32'h00000013;
|
||||||
rom[150] = 32'h00000000;
|
rom[150] = 32'h00000013;
|
||||||
rom[151] = 32'h00000000;
|
rom[151] = 32'h00000013;
|
||||||
rom[152] = 32'h00000000;
|
rom[152] = 32'h00000013;
|
||||||
rom[153] = 32'h00000000;
|
rom[153] = 32'h00000013;
|
||||||
rom[154] = 32'h00000000;
|
rom[154] = 32'h00000013;
|
||||||
rom[155] = 32'h00000000;
|
rom[155] = 32'h00000013;
|
||||||
rom[156] = 32'h00000000;
|
rom[156] = 32'h00000013;
|
||||||
rom[157] = 32'h00000000;
|
rom[157] = 32'h00000013;
|
||||||
rom[158] = 32'h00000000;
|
rom[158] = 32'h00000013;
|
||||||
rom[159] = 32'h00000000;
|
rom[159] = 32'h00000013;
|
||||||
rom[160] = 32'h00000000;
|
rom[160] = 32'h00000013;
|
||||||
rom[161] = 32'h00000000;
|
rom[161] = 32'h00000013;
|
||||||
rom[162] = 32'h00000000;
|
rom[162] = 32'h00000013;
|
||||||
rom[163] = 32'h00000000;
|
rom[163] = 32'h00000013;
|
||||||
rom[164] = 32'h00000000;
|
rom[164] = 32'h00000013;
|
||||||
rom[165] = 32'h00000000;
|
rom[165] = 32'h00000013;
|
||||||
rom[166] = 32'h00000000;
|
rom[166] = 32'h00000013;
|
||||||
rom[167] = 32'h00000000;
|
rom[167] = 32'h00000013;
|
||||||
rom[168] = 32'h00000000;
|
rom[168] = 32'h00000013;
|
||||||
rom[169] = 32'h00000000;
|
rom[169] = 32'h00000013;
|
||||||
rom[170] = 32'h00000000;
|
rom[170] = 32'h00000013;
|
||||||
rom[171] = 32'h00000000;
|
rom[171] = 32'h00000013;
|
||||||
rom[172] = 32'h00000000;
|
rom[172] = 32'h00000013;
|
||||||
rom[173] = 32'h00000000;
|
rom[173] = 32'h00000013;
|
||||||
rom[174] = 32'h00000000;
|
rom[174] = 32'h00000013;
|
||||||
rom[175] = 32'h00000000;
|
rom[175] = 32'h00000013;
|
||||||
rom[176] = 32'h00000000;
|
rom[176] = 32'h00000013;
|
||||||
rom[177] = 32'h00000000;
|
rom[177] = 32'h00000013;
|
||||||
rom[178] = 32'h00000000;
|
rom[178] = 32'h00000013;
|
||||||
rom[179] = 32'h00000000;
|
rom[179] = 32'h00000013;
|
||||||
rom[180] = 32'h00000000;
|
rom[180] = 32'h00000013;
|
||||||
rom[181] = 32'h00000000;
|
rom[181] = 32'h00000013;
|
||||||
rom[182] = 32'h00000000;
|
rom[182] = 32'h00000013;
|
||||||
rom[183] = 32'h00000000;
|
rom[183] = 32'h00000013;
|
||||||
rom[184] = 32'h00000000;
|
rom[184] = 32'h00000013;
|
||||||
rom[185] = 32'h00000000;
|
rom[185] = 32'h00000013;
|
||||||
rom[186] = 32'h00000000;
|
rom[186] = 32'h00000013;
|
||||||
rom[187] = 32'h00000000;
|
rom[187] = 32'h00000013;
|
||||||
rom[188] = 32'h00000000;
|
rom[188] = 32'h00000013;
|
||||||
rom[189] = 32'h00000000;
|
rom[189] = 32'h00000013;
|
||||||
rom[190] = 32'h00000000;
|
rom[190] = 32'h00000013;
|
||||||
rom[191] = 32'h00000000;
|
rom[191] = 32'h00000013;
|
||||||
rom[192] = 32'h00200073;
|
rom[192] = 32'h00200073;
|
||||||
rom[193] = 32'h00000793;
|
rom[193] = 32'h00000793;
|
||||||
rom[194] = 32'h59C00713;
|
rom[194] = 32'h59C00713;
|
||||||
|
|||||||
@@ -35,8 +35,8 @@
|
|||||||
// 同一块 RAM),读操作把 fb_rdata_i 组合回读数据,保持"组合读"的时序不变。
|
// 同一块 RAM),读操作把 fb_rdata_i 组合回读数据,保持"组合读"的时序不变。
|
||||||
//
|
//
|
||||||
// 两个中断源:
|
// 两个中断源:
|
||||||
// mouse_irq_o = 中断使能 && 事件 FIFO 非空(接 irq_i[0])
|
// mouse_irq_o = 中断使能 && 事件 FIFO 非空(顶层接到 irq_i[1] = IRQ2 = 入口 0x200)
|
||||||
// level_irq_o = LEVEL 寄存器(软件中断,接 irq_i[1],用来"进入关卡")
|
// level_irq_o = LEVEL 寄存器(软件中断,顶层接到 irq_i[0] = IRQ1 = 入口 0x100 = 关卡)
|
||||||
//
|
//
|
||||||
// 以后要增加外设(UART、SD 卡等)时只需在这里扩展地址译码。
|
// 以后要增加外设(UART、SD 卡等)时只需在这里扩展地址译码。
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -96,8 +96,10 @@ 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(addr[12]=1 且 addr[11]=0)
|
// 显存窗口 0x2000 - 0x27FF:bit13=1、bit12=0、bit11=0,高位全 0
|
||||||
wire sel_fb = (addr_i[31:13] == 19'd0) && addr_i[12] && !addr_i[11];
|
// (原来写成 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 [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];
|
||||||
|
|||||||
@@ -76,16 +76,20 @@ module cpu21_pointer #(
|
|||||||
reg [4:0] sel;
|
reg [4:0] sel;
|
||||||
always @* begin
|
always @* begin
|
||||||
sel = 5'b0;
|
sel = 5'b0;
|
||||||
|
// 注意:sel 的每一位必须和下面的输出一致,也必须和 pend_next 一一对应!
|
||||||
|
// sel[4]=点击 sel[3]=上(-dy) sel[2]=下(+dy) sel[1]=左(-dx) sel[0]=右(+dx)
|
||||||
|
// 而且 press_pend_q <= pend_next & ~sel 要求 sel 的位数与 pend_next 同域,
|
||||||
|
// 否则被选中的那一位清不掉、ev_valid_o 会永久为 1(中断线一直挂着)。
|
||||||
if (pend_next[4])
|
if (pend_next[4])
|
||||||
sel = 5'b10000; // BTNC:点击
|
sel = 5'b10000; // BTNC:点击
|
||||||
else if (pend_next[3])
|
else if (pend_next[3])
|
||||||
sel = 5'b01000; // BTNU:上
|
sel = 5'b01000; // BTNU:上
|
||||||
else if (pend_next[2])
|
else if (pend_next[2])
|
||||||
sel = 5'b00010; // BTND:下
|
sel = 5'b00100; // BTND:下
|
||||||
else if (pend_next[1])
|
else if (pend_next[1])
|
||||||
sel = 5'b00001; // BTNL:左
|
sel = 5'b00010; // BTNL:左
|
||||||
else if (pend_next[0])
|
else if (pend_next[0])
|
||||||
sel = 5'b00100; // BTNR:右
|
sel = 5'b00001; // BTNR:右
|
||||||
end
|
end
|
||||||
|
|
||||||
always @(posedge clk or posedge reset) begin
|
always @(posedge clk or posedge reset) begin
|
||||||
|
|||||||
@@ -0,0 +1,274 @@
|
|||||||
|
`timescale 1ns / 1ps
|
||||||
|
// =============================================================================
|
||||||
|
// tb_saolei —— 没有 VGA 显示器时的"扫雷调试台"
|
||||||
|
//
|
||||||
|
// 做法:跑真实的板级顶层 cpu21_board_top(宏 SAOLEI_ROM = 扫雷程序),用 5 向按键
|
||||||
|
// 模拟鼠标(板上 BTNC = 左键、BTNU/D/L/R = 上下左右,R15=SW3 = 插旗模式),
|
||||||
|
// 定期把显存 1200 字节 dump 成文本;再由 saolei/tools/fb2png.py 按 cpu21_vga.v
|
||||||
|
// 里的 8x8 字库与 3 位调色板还原成 PNG —— 等价于给 VGA 画面"截图"。
|
||||||
|
//
|
||||||
|
// 编译(注意两个 -d 都要带 SAOLEI_ROM):
|
||||||
|
// xvlog -d SAOLEI_ROM -i srcs/board -i srcs/rtl srcs/rtl/*.v \
|
||||||
|
// srcs/board/cpu21_board_top.v srcs/tb/tb_saolei.v
|
||||||
|
// xelab -d SAOLEI_ROM -top tb_saolei -snapshot saolei_sim
|
||||||
|
// xsim saolei_sim -tclbatch run_all.tcl
|
||||||
|
//
|
||||||
|
// 说明:为了在仿真里按键快一点,用 defparam 把 cpu21_pointer 的去抖采样周期
|
||||||
|
// 从 50000 个 CPU 周期(1 ms @50 MHz)压到 50(1 us),这样每次"按键"只要几微秒
|
||||||
|
// 就能产生一个事件;上板时那个参数仍然是 50000,行为不变。
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
module tb_saolei;
|
||||||
|
|
||||||
|
// ---- 板级顶层端口 ----
|
||||||
|
reg clk100 = 1'b0;
|
||||||
|
reg rst_n = 1'b0;
|
||||||
|
reg go = 1'b0;
|
||||||
|
reg [ 2:0] irq_sw = 3'b000;
|
||||||
|
reg btnu = 1'b0;
|
||||||
|
reg btnd = 1'b0;
|
||||||
|
reg btnl = 1'b0;
|
||||||
|
reg btnr = 1'b0;
|
||||||
|
reg sw_flag = 1'b0;
|
||||||
|
|
||||||
|
wire [15:0] led;
|
||||||
|
wire led_we;
|
||||||
|
wire [ 7:0] an;
|
||||||
|
wire [ 6:0] seg;
|
||||||
|
wire dp;
|
||||||
|
wire [ 3:0] vga_r, vga_g, vga_b;
|
||||||
|
wire vga_hs, vga_vs;
|
||||||
|
|
||||||
|
// 100 MHz 参考时钟
|
||||||
|
always #5 clk100 = ~clk100;
|
||||||
|
|
||||||
|
cpu21_board_top #(
|
||||||
|
.POINTER_SAMPLE_DIV(50) // 仿真里加速按键去抖(上板默认 50000)
|
||||||
|
) dut (
|
||||||
|
.clk100 (clk100),
|
||||||
|
.rst_n (rst_n),
|
||||||
|
.go (go),
|
||||||
|
.irq_sw (irq_sw),
|
||||||
|
.btnu (btnu),
|
||||||
|
.btnd (btnd),
|
||||||
|
.btnl (btnl),
|
||||||
|
.btnr (btnr),
|
||||||
|
.sw_flag (sw_flag),
|
||||||
|
.led (led),
|
||||||
|
.led_we (led_we),
|
||||||
|
.an (an),
|
||||||
|
.seg (seg),
|
||||||
|
.dp (dp),
|
||||||
|
.vga_r (vga_r),
|
||||||
|
.vga_g (vga_g),
|
||||||
|
.vga_b (vga_b),
|
||||||
|
.vga_hs (vga_hs),
|
||||||
|
.vga_vs (vga_vs),
|
||||||
|
.ps2_clk (1'b1),
|
||||||
|
.ps2_data(1'b1)
|
||||||
|
);
|
||||||
|
|
||||||
|
// (按键去抖已通过上面的 POINTER_SAMPLE_DIV(50) 参数加速,不用 defparam)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- 按键
|
||||||
|
// which: 0=go(左键) 1=btnu 2=btnd 3=btnl 4=btnr
|
||||||
|
task press;
|
||||||
|
input integer which;
|
||||||
|
input integer us; // 按住时长(微秒)
|
||||||
|
begin
|
||||||
|
case (which)
|
||||||
|
0:
|
||||||
|
go = 1'b1;
|
||||||
|
1:
|
||||||
|
btnu = 1'b1;
|
||||||
|
2:
|
||||||
|
btnd = 1'b1;
|
||||||
|
3:
|
||||||
|
btnl = 1'b1;
|
||||||
|
4:
|
||||||
|
btnr = 1'b1;
|
||||||
|
endcase
|
||||||
|
#(us * 1000);
|
||||||
|
case (which)
|
||||||
|
0:
|
||||||
|
go = 1'b0;
|
||||||
|
1:
|
||||||
|
btnu = 1'b0;
|
||||||
|
2:
|
||||||
|
btnd = 1'b0;
|
||||||
|
3:
|
||||||
|
btnl = 1'b0;
|
||||||
|
4:
|
||||||
|
btnr = 1'b0;
|
||||||
|
endcase
|
||||||
|
#(200 * 1000); // 松开后留 200 us 让 CPU 消费事件
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
task click; // 左键点一下
|
||||||
|
begin
|
||||||
|
press(0, 50);
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- 显存 dump
|
||||||
|
integer fh, w, b;
|
||||||
|
integer cursor_row, cursor_col;
|
||||||
|
|
||||||
|
// ---- 统计:MMIO 显存写脉冲数 + 当前非零格数 ----
|
||||||
|
integer fb_we_cnt = 0;
|
||||||
|
always @(posedge dut.clk_cpu)
|
||||||
|
if (dut.fb_we)
|
||||||
|
fb_we_cnt = fb_we_cnt + 1;
|
||||||
|
|
||||||
|
integer nz_cnt = 0; // 当前非零格数(dump 时统计)
|
||||||
|
|
||||||
|
// ---- PC 热点直方图:确定 CPU 此刻卡在哪个循环 ----
|
||||||
|
integer pc_hist[0:255];
|
||||||
|
integer pi;
|
||||||
|
initial for (pi = 0; pi < 256; pi = pi + 1) pc_hist[pi] = 0;
|
||||||
|
always @(posedge dut.clk_cpu)
|
||||||
|
pc_hist[dut.u_cpu.pc_q[9:2]] = pc_hist[dut.u_cpu.pc_q[9:2]] + 1;
|
||||||
|
|
||||||
|
// ---- 探针:PC 什么时候"跳进" ISR 段之间的空洞(0x194-0x1FF / 0x218-0x2FF)----
|
||||||
|
// 这些空洞在 ROM 里是 0x00000000,在本 CPU 上会被当成自跳指令,PC 掉进去出不来。
|
||||||
|
reg [31:0] pc_prev;
|
||||||
|
initial pc_prev = 32'hFFFF_FFFF;
|
||||||
|
always @(posedge dut.clk_cpu) begin
|
||||||
|
pc_prev <= dut.u_cpu.pc_q;
|
||||||
|
if (dut.u_cpu.pc_q != (pc_prev + 32'd4) &&
|
||||||
|
((dut.u_cpu.pc_q >= 32'h0000_0194 && dut.u_cpu.pc_q < 32'h0000_0200) ||
|
||||||
|
(dut.u_cpu.pc_q >= 32'h0000_0218 && dut.u_cpu.pc_q < 32'h0000_0300)))
|
||||||
|
$display("[gap] %0t ns: PC 0x%03h -> 0x%03h (掉进空洞)", $time, pc_prev, dut.u_cpu.pc_q);
|
||||||
|
end
|
||||||
|
|
||||||
|
task dump_pc;
|
||||||
|
integer i1, i2, best, besti;
|
||||||
|
begin
|
||||||
|
$display(" [pc] 热点(pc, 命中数):");
|
||||||
|
for (i1 = 0; i1 < 3; i1 = i1 + 1) begin
|
||||||
|
best = -1;
|
||||||
|
besti = -1;
|
||||||
|
for (i2 = 0; i2 < 256; i2 = i2 + 1)
|
||||||
|
if (pc_hist[i2] > best) begin
|
||||||
|
best = pc_hist[i2];
|
||||||
|
besti = i2;
|
||||||
|
end
|
||||||
|
if (besti >= 0 && best > 0) begin
|
||||||
|
$display(" pc=0x%03h count=%0d", besti << 2, best);
|
||||||
|
pc_hist[besti] = 0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
// ---- RAM 窗口(核对变量地址映射)----
|
||||||
|
task dump_ramwin;
|
||||||
|
integer i3;
|
||||||
|
begin
|
||||||
|
$display(" [ram] 0x00..0x3F: %08h %08h %08h %08h %08h %08h %08h %08h",
|
||||||
|
dut.ram[0], dut.ram[1], dut.ram[2], dut.ram[3],
|
||||||
|
dut.ram[4], dut.ram[5], dut.ram[6], dut.ram[7]);
|
||||||
|
$display(" [ram] 0x100..0x11F: %08h %08h %08h %08h %08h %08h %08h %08h",
|
||||||
|
dut.ram[32'h0100 >> 2], dut.ram[32'h0104 >> 2],
|
||||||
|
dut.ram[32'h0108 >> 2], dut.ram[32'h010C >> 2],
|
||||||
|
dut.ram[32'h0110 >> 2], dut.ram[32'h0114 >> 2],
|
||||||
|
dut.ram[32'h0118 >> 2], dut.ram[32'h011C >> 2]);
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
// g_exit=0x04 g_in_level=0x08 acc_y=0x40C acc_x=0x410 cur_c=0x414
|
||||||
|
// cur_r=0x418 g_first_done=0x420 g_state=0x424
|
||||||
|
// g_evq_lost=0x490 g_evq_tail=0x494 g_evq_head=0x498
|
||||||
|
task dump_vars;
|
||||||
|
begin
|
||||||
|
$display(" [vars] exit=%0d in_level=%0d cur=(r%0d,c%0d) acc=(%0d,%0d) first=%0d state=%0d lost=%0d evq(h=%0d,t=%0d)",
|
||||||
|
dut.ram[32'h0004 >> 2], dut.ram[32'h0008 >> 2],
|
||||||
|
dut.ram[32'h0418 >> 2], dut.ram[32'h0414 >> 2],
|
||||||
|
dut.ram[32'h0410 >> 2], dut.ram[32'h040C >> 2],
|
||||||
|
dut.ram[32'h0420 >> 2], dut.ram[32'h0424 >> 2],
|
||||||
|
dut.ram[32'h0490 >> 2],
|
||||||
|
dut.ram[32'h0498 >> 2], dut.ram[32'h0494 >> 2]);
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
task dump_fb;
|
||||||
|
input [1023:0] fname;
|
||||||
|
integer k2, b2;
|
||||||
|
begin
|
||||||
|
nz_cnt = 0;
|
||||||
|
for (k2 = 0; k2 < 300; k2 = k2 + 1)
|
||||||
|
for (b2 = 0; b2 < 4; b2 = b2 + 1)
|
||||||
|
if (dut.fb[k2][b2*8 +: 8] != 8'h00)
|
||||||
|
nz_cnt = nz_cnt + 1;
|
||||||
|
cursor_row = dut.u_vga.cursor_i[12:8];
|
||||||
|
cursor_col = dut.u_vga.cursor_i[4:0];
|
||||||
|
fh = $fopen(fname, "w");
|
||||||
|
$fwrite(fh, "# tb_saolei framebuffer dump\n");
|
||||||
|
$fwrite(fh, "TIME %0t\n", $time);
|
||||||
|
$fwrite(fh, "CURSOR %0d %0d\n", cursor_row, cursor_col);
|
||||||
|
$fwrite(fh, "CTRL 0x%02X\n", dut.u_vga.ctrl_i[7:0]);
|
||||||
|
$fwrite(fh, "STATS fb_we=%0d nonzero=%0d\n", fb_we_cnt, nz_cnt);
|
||||||
|
$fwrite(fh, "FB 1200\n");
|
||||||
|
for (w = 0; w < 300; w = w + 1)
|
||||||
|
for (b = 0; b < 4; b = b + 1)
|
||||||
|
$fwrite(fh, "%02X\n", dut.fb[w][b*8 +: 8]);
|
||||||
|
$fclose(fh);
|
||||||
|
$display("[tb_saolei] %0t ns dump -> %0s (cursor=%0d,%0d fb_we=%0d nonzero=%0d)",
|
||||||
|
$time, fname, cursor_row, cursor_col, fb_we_cnt, nz_cnt);
|
||||||
|
dump_vars;
|
||||||
|
dump_pc;
|
||||||
|
dump_ramwin;
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- 主流程
|
||||||
|
initial begin
|
||||||
|
$display("[tb_saolei] === start (SAOLEI_ROM) ===");
|
||||||
|
#2000;
|
||||||
|
rst_n = 1'b1;
|
||||||
|
|
||||||
|
// 1) 背景开始界面:光标初始就停在 START 按钮上(行 20,列 17..22)
|
||||||
|
#(2 * 1000000);
|
||||||
|
dump_fb("fb_00_start.txt");
|
||||||
|
|
||||||
|
// 2) 验证移动的方向与步长:下 2 格、右 2 格,再原路回位
|
||||||
|
repeat (2) press(2, 200);
|
||||||
|
repeat (2) press(4, 200);
|
||||||
|
dump_fb("fb_01_moved.txt");
|
||||||
|
repeat (2) press(1, 200);
|
||||||
|
repeat (2) press(3, 200);
|
||||||
|
dump_fb("fb_02_back.txt");
|
||||||
|
|
||||||
|
// 3) 点"开始" -> 写 LEVEL -> IRQ1 进关卡
|
||||||
|
click;
|
||||||
|
#(2 * 1000000);
|
||||||
|
dump_fb("fb_03_level.txt");
|
||||||
|
|
||||||
|
// 4) 关卡内:往下 2 格、往右 2 格,再点一下揭开
|
||||||
|
repeat (2) press(2, 200);
|
||||||
|
repeat (2) press(4, 200);
|
||||||
|
dump_fb("fb_04_cursor.txt");
|
||||||
|
click;
|
||||||
|
#(2 * 1000000);
|
||||||
|
dump_fb("fb_05_firstclick.txt");
|
||||||
|
|
||||||
|
// 5) 插旗(SW3 = 插旗模式 + 左键)
|
||||||
|
sw_flag = 1'b1;
|
||||||
|
#(100 * 1000);
|
||||||
|
click;
|
||||||
|
#(2 * 1000000);
|
||||||
|
sw_flag = 1'b0;
|
||||||
|
dump_fb("fb_06_flag.txt");
|
||||||
|
|
||||||
|
// 6) 往右一格再揭开
|
||||||
|
press(4, 200);
|
||||||
|
click;
|
||||||
|
#(2 * 1000000);
|
||||||
|
dump_fb("fb_07_click2.txt");
|
||||||
|
|
||||||
|
$display("[tb_saolei] === done, LED=%04h led_we=%b ===", led, led_we);
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
@@ -40,6 +40,21 @@ def main():
|
|||||||
for i in range(0, len(data), 4):
|
for i in range(0, len(data), 4):
|
||||||
words.append(int.from_bytes(data[i:i + 4], "little"))
|
words.append(int.from_bytes(data[i:i + 4], "little"))
|
||||||
|
|
||||||
|
# 段间空洞填 NOP:objcopy 出来的 .bin 在 .text.isr1 / .text.isr2 / .text.isr3
|
||||||
|
# 这些"绝对地址段"之间会留下 0x00000000,而本 CPU 把 0x00000000 译成自跳指令
|
||||||
|
# (beq x0,x0,0 形式),PC 一旦落进去就永远出不来(仿真里已复现:PC 热点全在
|
||||||
|
# 0x194-0x1FF / 0x218-0x2FF 这些空洞地址上)。这里把"最后一条非零字之前"的
|
||||||
|
# 0 字统一换成 NOP,让 PC 即使误入也能顺势走过。
|
||||||
|
last_nz = -1
|
||||||
|
for i, w in enumerate(words):
|
||||||
|
if w != 0:
|
||||||
|
last_nz = i
|
||||||
|
filled = 0
|
||||||
|
for i in range(last_nz):
|
||||||
|
if words[i] == 0:
|
||||||
|
words[i] = NOP
|
||||||
|
filled += 1
|
||||||
|
|
||||||
if len(words) > words_total:
|
if len(words) > words_total:
|
||||||
print("错误:程序 %d 个字,超过 ROM 容量 %d 个字(改大 ROM_WORDS 或精简程序)"
|
print("错误:程序 %d 个字,超过 ROM 容量 %d 个字(改大 ROM_WORDS 或精简程序)"
|
||||||
% (len(words), words_total))
|
% (len(words), words_total))
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""fb2png.py —— 把 tb_saolei 导出的显存 dump 还原成 PNG(等价于 VGA 截图)。
|
||||||
|
|
||||||
|
用法:
|
||||||
|
py saolei/tools/fb2png.py <dump.txt> [输出.png]
|
||||||
|
|
||||||
|
dump 格式(tb_saolei.v 里的 dump_fb 任务生成):
|
||||||
|
CURSOR <row> <col>
|
||||||
|
CTRL 0x<byte> # [0] 光标可见 [6:4] 背景色
|
||||||
|
FB 1200 # 随后 1200 行十六进制字节,按格号 0..1199
|
||||||
|
|
||||||
|
渲染依据(与 RTL 严格对应,改 RTL 时这两个表要同步):
|
||||||
|
* 字库 cpu21_vga.v 的 font_row():8x8、bit7 是最左像素,放大 2 倍 = 16x16 一格
|
||||||
|
* 调色板 cpu21_vga.v 的 pal():3 位 -> 4:4:4 RGB(0xA 分量 = 170)
|
||||||
|
* 光标 cpu21_vga.v 的 cursor_box:格子四边一圈(1 像素宽)显示浅灰
|
||||||
|
* 画面 40x30 格 -> 640x480
|
||||||
|
|
||||||
|
另外还会在终端打印一份 ASCII 图,方便在没有图片查看器的地方快速看结果。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import zlib
|
||||||
|
|
||||||
|
COLS, ROWS = 40, 30
|
||||||
|
CELL_PX = 16 # 8x8 字模放大 2 倍
|
||||||
|
|
||||||
|
# --- 字库(8 行/字,bit7 = 最左),抄自 cpu21_vga.v 的 font_row() ---
|
||||||
|
FONT = {
|
||||||
|
0: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # 空白
|
||||||
|
1: [0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00], # 1
|
||||||
|
2: [0x3C, 0x66, 0x06, 0x0C, 0x18, 0x30, 0x7E, 0x00], # 2
|
||||||
|
3: [0x3C, 0x66, 0x06, 0x1C, 0x06, 0x66, 0x3C, 0x00], # 3
|
||||||
|
4: [0x0C, 0x1C, 0x3C, 0x6C, 0x7E, 0x0C, 0x0C, 0x00], # 4
|
||||||
|
5: [0x7E, 0x60, 0x7C, 0x06, 0x06, 0x66, 0x3C, 0x00], # 5
|
||||||
|
6: [0x1C, 0x30, 0x60, 0x7C, 0x66, 0x66, 0x3C, 0x00], # 6
|
||||||
|
7: [0x7E, 0x66, 0x0C, 0x18, 0x18, 0x18, 0x18, 0x00], # 7
|
||||||
|
8: [0x3C, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00], # 8
|
||||||
|
9: [0xFF, 0x81, 0xBD, 0xA5, 0xA5, 0xBD, 0x81, 0xFF], # 未揭开
|
||||||
|
10: [0x40, 0x70, 0x78, 0x70, 0x40, 0x40, 0x40, 0x00], # 旗
|
||||||
|
11: [0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C], # 雷
|
||||||
|
12: [0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81], # 爆炸
|
||||||
|
13: [0x3C, 0x66, 0x06, 0x0C, 0x18, 0x00, 0x18, 0x00], # 问号
|
||||||
|
14: [0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00], # 横线
|
||||||
|
15: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], # 实心
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 调色板(3 位 -> RGB),抄自 cpu21_vga.v 的 pal(),4 位分量按 *17 扩到 8 位 ---
|
||||||
|
PAL = [
|
||||||
|
(0x0, 0x0, 0x0), # 0 黑
|
||||||
|
(0x0, 0x0, 0xA), # 1 深蓝
|
||||||
|
(0x0, 0xA, 0x0), # 2 绿
|
||||||
|
(0x0, 0xA, 0xA), # 3 青
|
||||||
|
(0xA, 0x0, 0x0), # 4 红
|
||||||
|
(0xA, 0x0, 0xA), # 5 洋红
|
||||||
|
(0xA, 0xA, 0x0), # 6 黄
|
||||||
|
(0xA, 0xA, 0xA), # 7 浅灰
|
||||||
|
]
|
||||||
|
|
||||||
|
GLYPH_CH = {0: ' ', 9: '#', 10: 'P', 11: '*', 12: 'X', 13: '?', 14: '-', 15: '='}
|
||||||
|
COLOR_CH = {0: '.', 1: 'b', 2: 'g', 3: 'c', 4: 'r', 5: 'm', 6: 'y', 7: 'w'}
|
||||||
|
|
||||||
|
|
||||||
|
def write_png(path, width, height, rows):
|
||||||
|
"""rows: 每行一个 bytes(长度 width*3),最小 PNG 编码。"""
|
||||||
|
raw = b"".join(b"\x00" + r for r in rows)
|
||||||
|
|
||||||
|
def chunk(tag, data):
|
||||||
|
return (struct.pack(">I", len(data)) + tag + data
|
||||||
|
+ struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF))
|
||||||
|
|
||||||
|
png = b"\x89PNG\r\n\x1a\n"
|
||||||
|
png += chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0))
|
||||||
|
png += chunk(b"IDAT", zlib.compress(raw, 9))
|
||||||
|
png += chunk(b"IEND", b"")
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(png)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_dump(path):
|
||||||
|
cur = (None, None)
|
||||||
|
ctrl = 0
|
||||||
|
fb = []
|
||||||
|
with open(path, encoding="utf-8", errors="replace") as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith("CURSOR"):
|
||||||
|
p = line.split()
|
||||||
|
cur = (int(p[1]), int(p[2]))
|
||||||
|
elif line.startswith("CTRL"):
|
||||||
|
ctrl = int(line.split()[1], 16)
|
||||||
|
elif line.startswith("FB"):
|
||||||
|
fb = []
|
||||||
|
elif len(line) == 2 and fb is not None:
|
||||||
|
try:
|
||||||
|
fb.append(int(line, 16))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
if len(fb) != 1200:
|
||||||
|
raise SystemExit("显存字节数不对:%d(应为 1200)" % len(fb))
|
||||||
|
return cur, ctrl, fb
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
raise SystemExit(__doc__)
|
||||||
|
src = sys.argv[1]
|
||||||
|
dst = sys.argv[2] if len(sys.argv) > 2 else src.rsplit(".", 1)[0] + ".png"
|
||||||
|
cur, ctrl, fb = parse_dump(src)
|
||||||
|
|
||||||
|
bg = (ctrl >> 4) & 0x7
|
||||||
|
cursor_box = bool(ctrl & 1) and cur[0] is not None
|
||||||
|
print("光标 = (%s, %s) 光标可见 = %s 背景色 = %d"
|
||||||
|
% (cur[0], cur[1], bool(ctrl & 1), bg))
|
||||||
|
|
||||||
|
# ---- 渲染 ----
|
||||||
|
rows = []
|
||||||
|
for y in range(ROWS * CELL_PX):
|
||||||
|
row = bytearray()
|
||||||
|
cy, fy = y // CELL_PX, (y % CELL_PX) // 2
|
||||||
|
for x in range(COLS * CELL_PX):
|
||||||
|
cx, fx = x // CELL_PX, (x % CELL_PX) // 2
|
||||||
|
cell = fb[cy * COLS + cx]
|
||||||
|
g, c = cell & 0xF, (cell >> 4) & 0x7
|
||||||
|
px_on = (FONT[g][fy] >> (7 - fx)) & 1
|
||||||
|
col = c if px_on else bg
|
||||||
|
if cursor_box and (cx, cy) == cur:
|
||||||
|
# 格子四边一圈(1 像素)显示浅灰
|
||||||
|
ox, oy = x % CELL_PX, y % CELL_PX
|
||||||
|
if ox == 0 or ox == CELL_PX - 1 or oy == 0 or oy == CELL_PX - 1:
|
||||||
|
col = 7
|
||||||
|
r, gg, b = PAL[col]
|
||||||
|
row += bytes((r * 17, gg * 17, b * 17))
|
||||||
|
rows.append(bytes(row))
|
||||||
|
write_png(dst, COLS * CELL_PX, ROWS * CELL_PX, rows)
|
||||||
|
print("已生成 %s(%dx%d)" % (dst, COLS * CELL_PX, ROWS * CELL_PX))
|
||||||
|
|
||||||
|
# ---- 终端 ASCII 图 ----
|
||||||
|
print("+" + "-" * COLS + "+")
|
||||||
|
for y in range(ROWS):
|
||||||
|
line = ""
|
||||||
|
for x in range(COLS):
|
||||||
|
cell = fb[y * COLS + x]
|
||||||
|
g, c = cell & 0xF, (cell >> 4) & 0x7
|
||||||
|
if cursor_box and (x, y) == cur:
|
||||||
|
line += "["
|
||||||
|
elif g in GLYPH_CH:
|
||||||
|
line += GLYPH_CH[g]
|
||||||
|
else:
|
||||||
|
line += chr(ord('0') + g) # 字形 1..8 就是数字 1..8
|
||||||
|
line = line.replace("[", "[]" * 1) # 光标只提示左边界,避免打乱列数
|
||||||
|
print("|" + (line + " " * COLS)[:COLS] + "|")
|
||||||
|
print("+" + "-" * COLS + "+")
|
||||||
|
print("图例: #=未揭开 P=旗 *=雷 X=爆炸 ?=问号 -=横线 ==实心 1..8=数字")
|
||||||
|
|
||||||
|
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user