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 已修好
146 lines
5.8 KiB
Tcl
146 lines
5.8 KiB
Tcl
# =============================================================================
|
||
# play_saolei.tcl —— 没有 VGA 显示器时,在 xsim 命令行里"玩"扫雷
|
||
#
|
||
# 用法(建议用 source -encoding utf-8 加载本文件,否则 xsim 默认按 GBK 读,中文注释会乱):
|
||
# cd <某个空目录,例如 _tmp/saolei_sim>
|
||
# xvlog -d SAOLEI_ROM -i <pipelined-cpu/srcs/board> -i <pipelined-cpu/srcs/rtl> \
|
||
# <pipelined-cpu/srcs/rtl/*.v> <pipelined-cpu/srcs/board/cpu21_board_top.v> \
|
||
# <pipelined-cpu/srcs/tb/tb_saolei_play.v>
|
||
# xelab -d SAOLEI_ROM -top tb_saolei_play -snapshot saolei_play
|
||
# xsim saolei_play -tclbatch <本脚本绝对路径>
|
||
#
|
||
# 然后在 xsim 提示符下敲命令(一个字母就是一步,会立刻重画棋盘):
|
||
# k c 左键点击(= 板上 BTNC)
|
||
# k u / k d / k l / k r 光标 上/下/左/右(也认 w s a d)
|
||
# k f 插旗模式点击(点一下插旗,再点取消;= 按住 SW3 再点)
|
||
# k p 只切换插旗模式开关(不点击)
|
||
# k z 复位(等于重新上电,回到开始界面)
|
||
# k uucc 可以连写:上、上、点、点
|
||
# shot 不按键,只重画一遍当前画面 + 存一张 PNG
|
||
# view 用系统看图器打开最新那张 PNG
|
||
# help 键位说明
|
||
#
|
||
# 截图:k 每执行**一个**按键动作就自动存一张 PNG 到 shots/NNN_<按键>.png
|
||
# (可以直接放进报告),同时覆盖 shots/latest.png。
|
||
# 在 VS Code 里打开 shots/latest.png,每次操作后它都会自动刷新 —— 这就是
|
||
# "每一步都能看到 PNG" 的用法。
|
||
#
|
||
# 原理:k 把字符写进 cmd.txt;tb_saolei_play 每 100 us 仿真时间轮询一次并执行按键,
|
||
# 同时每 1 ms 把显存 dump 到 now.txt;k 再调 fb2png.py --term 把画面画到终端。
|
||
# 仿真速度大约是 1 ms 仿真时间 = 0.5 s 墙钟,所以每步等 1~2 秒,比真实慢很多,
|
||
# 但足够用来验证"这个程序真的能玩"。
|
||
# =============================================================================
|
||
|
||
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 "下"
|
||
l "左" a "左" r "右"
|
||
f "插旗点击" p "切插旗开关" g "go 键" z "复位"
|
||
}
|
||
|
||
# 每次只把一个字符写进 cmd.txt(TB 每 100 us 读走一个,读完就清空)
|
||
proc send_char {c} {
|
||
set fh [open "cmd.txt" "w"]
|
||
puts -nonewline $fh $c
|
||
close $fh
|
||
}
|
||
|
||
# 先确保 cmd.txt 存在,否则 TB 每 100 us 都会刷一条 could not be opened 警告
|
||
if {![file exists "cmd.txt"]} {
|
||
set fh [open "cmd.txt" "w"]
|
||
close $fh
|
||
}
|
||
|
||
# 把 now.txt 渲染到终端(ANSI 彩色棋盘)
|
||
proc render {} {
|
||
global fb2png python
|
||
if {![file exists "now.txt"]} {
|
||
puts " (now.txt not ready yet - run a bit first)"
|
||
return
|
||
}
|
||
if {[catch { exec $python $fb2png --term now.txt >@stdout 2>@stderr } err]} {
|
||
puts " render failed: $err"
|
||
puts " try manually: py \"$fb2png\" --term now.txt"
|
||
}
|
||
}
|
||
|
||
proc help {} {
|
||
puts " ---------------- SAOLEI controls (type at the xsim prompt) ----------------"
|
||
puts " k c left click (board BTNC)"
|
||
puts " k u/d/l/r move cursor up/down/left/right (w/s/a/d work too)"
|
||
puts " k f click in flag mode k p toggle flag switch only"
|
||
puts " k z reset k uucc several actions at once"
|
||
puts " shot redraw+screenshot view open latest PNG"
|
||
puts " help this text quit exit"
|
||
puts " every key saved one PNG: shots/NNN_<key>.png and shots/latest.png"
|
||
puts " ---------------------------------------------------------------------------"
|
||
}
|
||
|
||
# ---- every step saves one PNG: shots/001_c.png ... plus shots/latest.png ----
|
||
set shot_dir "shots"
|
||
file mkdir $shot_dir
|
||
set shot_n 0
|
||
|
||
proc save_shot {tag} {
|
||
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 $python $fb2png --quiet now.txt $name } err]} {
|
||
puts " (screenshot failed: $err)"
|
||
return
|
||
}
|
||
catch { file copy -force $name "$shot_dir/latest.png" }
|
||
puts " png -> $name"
|
||
}
|
||
|
||
proc view {} {
|
||
global shot_dir
|
||
set f [file normalize "$shot_dir/latest.png"]
|
||
if {![file exists $f]} { puts " (no screenshot yet)"; return }
|
||
catch { exec cmd /c start "" $f }
|
||
}
|
||
|
||
# one step: send the key -> advance simulation -> save PNG -> redraw
|
||
proc k {keys} {
|
||
set s [string map {" " "c"} [string tolower $keys]]
|
||
foreach ch [split $s ""] {
|
||
if {[string length $ch] == 0} { continue }
|
||
puts " >> $ch"
|
||
send_char $ch
|
||
run 1500 us
|
||
save_shot $ch
|
||
}
|
||
render
|
||
}
|
||
|
||
proc shot {} {
|
||
save_shot "shot"
|
||
render
|
||
}
|
||
|
||
# ---- opening: run a little so the start screen is drawn, then show it ----
|
||
help
|
||
puts "booting... (init + draw start screen)"
|
||
run 3 ms
|
||
render
|