feat(tools): 仿真调试台适配新版显存,并修好 xsim 里跑 python 的环境问题
tb_saolei.v / tb_saolei_play.v: - dump 从 1200 字节(1 字节/格)改成 2400 字节(每格 2 字节) - 变量地址按新 ELF 更新(g_state 0x7AC、mine_bm 0x86C、g_ncells 0x8B4 ...) - 已揭开格数直接读 opened_cnt(0x7B4),通关目标改成 g_ncells - g_mines fb2png.py: - 支持每格 2 字节(byte0 字形[5:0] / byte1 颜色[2:0]) - 字库不再手工抄表,改成直接解析 cpu21_vga.v 的 font_row(),RTL 加字模会自动跟随 - 字形 16..41 显示成 A-Z play_saolei.tcl: - 用绝对路径找系统 Python:xsim 的环境里 py 会选中 Vivado 自带的 python3.8, 报 SRE module mismatch - 清掉 PYTHONHOME/PYTHONPATH,否则系统 python 也会去 Vivado 的 lib 找模块 - render/save_shot 补 global python 实测:hard 模式 30x16 棋盘在终端正常显示,点击展开的区域横跨屏幕第 13~22 行 (含中线以下的 8 行)-> 证实 FB_WORDS 那个"只能写前 15 行"的 bug 已修好
This commit is contained in:
@@ -215,18 +215,23 @@ module tb_saolei;
|
||||
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
|
||||
// 最新版(显存每格 2 字节、难度三档不同尺寸)变量地址:
|
||||
// g_exit=0x04 g_in_level=0x08 acc_y=0x78C acc_x=0x790 cur_c=0x794
|
||||
// cur_r=0x798 g_difficulty=0x7A0 g_mines=0x7A4 g_first_done=0x7A8
|
||||
// g_state=0x7AC flags_cnt=0x7B0 opened_cnt=0x7B4
|
||||
// flag_bm=0x7F4 open_bm=0x830 mine_bm=0x86C
|
||||
// g_ncells=0x8B4 g_evq_lost=0x8C0 g_evq_tail=0x8C4 g_evq_head=0x8C8
|
||||
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)",
|
||||
$display(" [vars] exit=%0d in_level=%0d cur=(r%0d,c%0d) acc=(%0d,%0d) diff=%0d mines=%0d first=%0d state=%0d flags=%0d opened=%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]);
|
||||
dut.ram[32'h0798 >> 2], dut.ram[32'h0794 >> 2],
|
||||
dut.ram[32'h0790 >> 2], dut.ram[32'h078C >> 2],
|
||||
dut.ram[32'h07A0 >> 2], dut.ram[32'h07A4 >> 2],
|
||||
dut.ram[32'h07A8 >> 2], dut.ram[32'h07AC >> 2],
|
||||
dut.ram[32'h07B0 >> 2], dut.ram[32'h07B4 >> 2],
|
||||
dut.ram[32'h08C0 >> 2],
|
||||
dut.ram[32'h08C8 >> 2], dut.ram[32'h08C4 >> 2]);
|
||||
end
|
||||
endtask
|
||||
|
||||
@@ -235,7 +240,7 @@ module tb_saolei;
|
||||
integer k2, b2;
|
||||
begin
|
||||
nz_cnt = 0;
|
||||
for (k2 = 0; k2 < 300; k2 = k2 + 1)
|
||||
for (k2 = 0; k2 < 600; 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;
|
||||
@@ -247,8 +252,8 @@ module tb_saolei;
|
||||
$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)
|
||||
$fwrite(fh, "FB 2400\n");
|
||||
for (w = 0; w < 600; w = w + 1)
|
||||
for (b = 0; b < 4; b = b + 1)
|
||||
$fwrite(fh, "%02X\n", dut.fb[w][b*8 +: 8]);
|
||||
$fclose(fh);
|
||||
@@ -261,7 +266,7 @@ module tb_saolei;
|
||||
endtask
|
||||
|
||||
// ---------------------------------------------------------------- 完整一局用的工具
|
||||
// 从 RAM 的位图里取一位(mine_bm @0x470 / open_bm @0x450,字内小端)
|
||||
// 从 RAM 的位图里取一位(mine_bm @0x86C / open_bm @0x830 / flag_bm @0x7F4)
|
||||
function integer bitof;
|
||||
input integer base;
|
||||
input integer idx;
|
||||
@@ -270,14 +275,11 @@ module tb_saolei;
|
||||
end
|
||||
endfunction
|
||||
|
||||
// 已揭开格数(直接数 open_bm 的 1),目标 = 256 - 40 = 216
|
||||
// 已揭开格数:直接读程序里的 opened_cnt(0x7B4)
|
||||
integer open_n;
|
||||
task count_open;
|
||||
integer k3;
|
||||
begin
|
||||
open_n = 0;
|
||||
for (k3 = 0; k3 < 256; k3 = k3 + 1)
|
||||
open_n = open_n + bitof(32'h0000_0450, k3);
|
||||
open_n = dut.ram[32'h07B4 >> 2];
|
||||
end
|
||||
endtask
|
||||
|
||||
@@ -376,7 +378,7 @@ module tb_saolei;
|
||||
for (cc = 0; cc < 16; cc = cc + 1) begin
|
||||
idx = rr * 16 + cc;
|
||||
goto_cell(rr, cc);
|
||||
if (bitof(32'h0000_0470, idx) != 0) begin
|
||||
if (bitof(32'h0000_086C, idx) != 0) begin
|
||||
sw_flag = 1'b1;
|
||||
#(30 * 1000);
|
||||
click_t;
|
||||
@@ -388,18 +390,20 @@ module tb_saolei;
|
||||
// 中途退出关卡就立即报警并收手
|
||||
if (dut.ram[32'h0008 >> 2] == 0) begin
|
||||
$display(" [game] *** 在盘面 (%0d,%0d) 处退出了关卡!cur=(%0d,%0d) state=%0d",
|
||||
rr, cc, dut.ram[32'h0418 >> 2], dut.ram[32'h0414 >> 2],
|
||||
dut.ram[32'h0424 >> 2]);
|
||||
rr, cc, dut.ram[32'h0798 >> 2], dut.ram[32'h0794 >> 2],
|
||||
dut.ram[32'h07AC >> 2]);
|
||||
disable sweep_blk;
|
||||
end
|
||||
// 赢了/输了也收手
|
||||
if (dut.ram[32'h0424 >> 2] != 0)
|
||||
if (dut.ram[32'h07AC >> 2] != 0)
|
||||
disable sweep_blk;
|
||||
end
|
||||
count_open;
|
||||
$display(" [game] 第 %0d 行扫完:已揭开 %0d / 216, state=%0d, 旗=%0d, cur=(%0d,%0d)",
|
||||
rr, open_n, dut.ram[32'h0424 >> 2], dut.ram[32'h0428 >> 2],
|
||||
dut.ram[32'h0418 >> 2], dut.ram[32'h0414 >> 2]);
|
||||
$display(" [game] 第 %0d 行扫完:已揭开 %0d / %0d, state=%0d, 旗=%0d, cur=(%0d,%0d)",
|
||||
rr, open_n,
|
||||
(dut.ram[32'h08B4 >> 2] - dut.ram[32'h07A4 >> 2]),
|
||||
dut.ram[32'h07AC >> 2], dut.ram[32'h07B0 >> 2],
|
||||
dut.ram[32'h0798 >> 2], dut.ram[32'h0794 >> 2]);
|
||||
if (rr == 3)
|
||||
dump_fb("fb_10_sweep_a.txt");
|
||||
if (rr == 7)
|
||||
@@ -424,31 +428,36 @@ module tb_saolei;
|
||||
click_t;
|
||||
#(2 * 1000000);
|
||||
dump_fb("fb_01_level.txt");
|
||||
$display(" [game] 进关卡:in_level=%0d state=%0d", dut.ram[32'h0008 >> 2], dut.ram[32'h0424 >> 2]);
|
||||
$display(" [game] 进关卡:in_level=%0d state=%0d", dut.ram[32'h0008 >> 2], dut.ram[32'h07AC >> 2]);
|
||||
|
||||
// 3) 关卡内点盘面中心一格:放雷 + 首次展开(第一下必安全)
|
||||
click;
|
||||
#(3 * 1000000);
|
||||
dump_fb("fb_02_firstclick.txt");
|
||||
count_open;
|
||||
$display(" [game] 首次点击后:first=%0d 已揭开 %0d / 216 state=%0d",
|
||||
dut.ram[32'h0420 >> 2], open_n, dut.ram[32'h0424 >> 2]);
|
||||
$display(" [game] 首次点击后:first=%0d 已揭开 %0d / %0d state=%0d diff=%0d",
|
||||
dut.ram[32'h07A8 >> 2], open_n,
|
||||
(dut.ram[32'h08B4 >> 2] - dut.ram[32'h07A4 >> 2]),
|
||||
dut.ram[32'h07AC >> 2], dut.ram[32'h07A0 >> 2]);
|
||||
|
||||
// 4) 蛇形扫完全盘:揭开全部 216 个安全格 + 给 40 颗雷插旗 -> 目标通关
|
||||
sweep_board;
|
||||
#(3 * 1000000);
|
||||
dump_fb("fb_13_after_sweep.txt");
|
||||
count_open;
|
||||
$display(" [game] 扫完后:已揭开 %0d / 216, 旗=%0d, state=%0d, 丢事件=%0d",
|
||||
open_n, dut.ram[32'h0428 >> 2], dut.ram[32'h0424 >> 2], dut.ram[32'h0490 >> 2]);
|
||||
$display(" [game] 扫完后:已揭开 %0d / %0d, 旗=%0d, state=%0d, 丢事件=%0d",
|
||||
open_n, (dut.ram[32'h08B4 >> 2] - dut.ram[32'h07A4 >> 2]),
|
||||
dut.ram[32'h07B0 >> 2], dut.ram[32'h07AC >> 2], dut.ram[32'h08C0 >> 2]);
|
||||
|
||||
// 5) 通关后再点一下 = 返回背景(回到开始界面)
|
||||
click_t;
|
||||
#(3 * 1000000);
|
||||
dump_fb("fb_14_back_to_start.txt");
|
||||
|
||||
$display(" [game] === 结束:state=%0d(1=WIN 2=LOSE)已揭开 %0d/216 旗=%0d 丢事件=%0d ===",
|
||||
dut.ram[32'h0424 >> 2], open_n, dut.ram[32'h0428 >> 2], dut.ram[32'h0490 >> 2]);
|
||||
$display(" [game] === 结束:state=%0d(1=WIN 2=LOSE)已揭开 %0d/%0d 旗=%0d 丢事件=%0d ===",
|
||||
dut.ram[32'h07AC >> 2], open_n,
|
||||
(dut.ram[32'h08B4 >> 2] - dut.ram[32'h07A4 >> 2]),
|
||||
dut.ram[32'h07B0 >> 2], dut.ram[32'h08C0 >> 2]);
|
||||
$finish;
|
||||
end
|
||||
|
||||
|
||||
@@ -189,8 +189,8 @@ module tb_saolei_play;
|
||||
$fwrite(df, "CURSOR %0d %0d\n",
|
||||
dut.u_vga.cursor_i[12:8], dut.u_vga.cursor_i[4:0]);
|
||||
$fwrite(df, "CTRL 0x%02X\n", dut.u_vga.ctrl_i[7:0]);
|
||||
$fwrite(df, "FB 1200\n");
|
||||
for (w2 = 0; w2 < 300; w2 = w2 + 1)
|
||||
$fwrite(df, "FB 2400\n");
|
||||
for (w2 = 0; w2 < 600; w2 = w2 + 1)
|
||||
for (b2 = 0; b2 < 4; b2 = b2 + 1)
|
||||
$fwrite(df, "%02X\n", dut.fb[w2][b2*8 +: 8]);
|
||||
$fclose(df);
|
||||
|
||||
@@ -34,6 +34,22 @@
|
||||
set script_dir [file dirname [file normalize [info script]]]
|
||||
set fb2png [file normalize [file join $script_dir .. .. saolei tools fb2png.py]]
|
||||
|
||||
# 找系统 Python:xsim 的 PATH 里带着 Vivado 自带的 python3.8,直接 exec py 会撞上它
|
||||
# (SRE module mismatch),所以用绝对路径优先。
|
||||
proc find_python {} {
|
||||
foreach ver {314 313 312 311 310} {
|
||||
set c [file join $::env(LOCALAPPDATA) Programs Python "Python$ver" python.exe]
|
||||
if {[file exists $c]} { return $c }
|
||||
}
|
||||
return "py"
|
||||
}
|
||||
set python [find_python]
|
||||
# xsim 的进程环境里带着 Vivado 的 PYTHONHOME,会让**系统 python** 也去 Vivado 的
|
||||
# lib 里找模块(SRE module mismatch)。清掉它,子进程才不会继承。
|
||||
catch { unset ::env(PYTHONHOME) }
|
||||
catch { unset ::env(PYTHONPATH) }
|
||||
puts " (python: $python)"
|
||||
|
||||
# 按键字符 -> 人类可读的动作(只用于回显)
|
||||
array set KEYNAME {
|
||||
c "左键点击" u "上" w "上" d "下" s "下"
|
||||
@@ -56,12 +72,12 @@ if {![file exists "cmd.txt"]} {
|
||||
|
||||
# 把 now.txt 渲染到终端(ANSI 彩色棋盘)
|
||||
proc render {} {
|
||||
global fb2png
|
||||
global fb2png python
|
||||
if {![file exists "now.txt"]} {
|
||||
puts " (now.txt not ready yet - run a bit first)"
|
||||
return
|
||||
}
|
||||
if {[catch { exec py $fb2png --term now.txt >@stdout 2>@stderr } err]} {
|
||||
if {[catch { exec $python $fb2png --term now.txt >@stdout 2>@stderr } err]} {
|
||||
puts " render failed: $err"
|
||||
puts " try manually: py \"$fb2png\" --term now.txt"
|
||||
}
|
||||
@@ -85,11 +101,11 @@ file mkdir $shot_dir
|
||||
set shot_n 0
|
||||
|
||||
proc save_shot {tag} {
|
||||
global fb2png shot_dir
|
||||
global fb2png shot_dir python
|
||||
if {![file exists "now.txt"]} { return }
|
||||
incr ::shot_n
|
||||
set name [format "%s/%03d_%s.png" $shot_dir $::shot_n $tag]
|
||||
if {[catch { exec py $fb2png --quiet now.txt $name } err]} {
|
||||
if {[catch { exec $python $fb2png --quiet now.txt $name } err]} {
|
||||
puts " (screenshot failed: $err)"
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user