feat(saolei): 交互玩扫雷每一步自动出 PNG
- play_saolei.tcl: k 每执行一个按键动作就存 shots/NNN_<按键>.png,并覆盖 shots/latest.png(在 VS Code 里开着 latest.png 即可每步自动刷新);新增 view 命令 - fb2png.py: 新增 --quiet(只写 PNG 不打印,供脚本批量截图) - tb_saolei*.v: 仅格式调整(case 分支拆行),无语义变化 实测:k cuuuuc -> shots/001_c.png ... 006_c.png,每张 640x480 正常
This commit is contained in:
@@ -127,12 +127,16 @@ module tb_saolei;
|
|||||||
// irq_rise: 该线的上升沿次数(CPU 按上升沿响应中断)
|
// irq_rise: 该线的上升沿次数(CPU 按上升沿响应中断)
|
||||||
integer ptr_ev = 0, irq_hi = 0, irq_rise = 0;
|
integer ptr_ev = 0, irq_hi = 0, irq_rise = 0;
|
||||||
reg irq_prev_q;
|
reg irq_prev_q;
|
||||||
initial irq_prev_q = 1'b0;
|
initial
|
||||||
|
irq_prev_q = 1'b0;
|
||||||
always @(posedge dut.clk_cpu) begin
|
always @(posedge dut.clk_cpu) begin
|
||||||
if (dut.g_btn.u_ptr.ev_valid_o) ptr_ev = ptr_ev + 1;
|
if (dut.g_btn.u_ptr.ev_valid_o)
|
||||||
if (dut.irq_vec[0]) irq_hi = irq_hi + 1;
|
ptr_ev = ptr_ev + 1;
|
||||||
|
if (dut.irq_vec[0])
|
||||||
|
irq_hi = irq_hi + 1;
|
||||||
irq_prev_q <= dut.irq_vec[0];
|
irq_prev_q <= dut.irq_vec[0];
|
||||||
if (dut.irq_vec[0] && !irq_prev_q) irq_rise = irq_rise + 1;
|
if (dut.irq_vec[0] && !irq_prev_q)
|
||||||
|
irq_rise = irq_rise + 1;
|
||||||
end
|
end
|
||||||
|
|
||||||
// 每毫秒打一行,观察事件链路
|
// 每毫秒打一行,观察事件链路
|
||||||
@@ -284,19 +288,29 @@ module tb_saolei;
|
|||||||
input integer gap_us;
|
input integer gap_us;
|
||||||
begin
|
begin
|
||||||
case (which)
|
case (which)
|
||||||
0: go = 1'b1;
|
0:
|
||||||
1: btnu = 1'b1;
|
go = 1'b1;
|
||||||
2: btnd = 1'b1;
|
1:
|
||||||
3: btnl = 1'b1;
|
btnu = 1'b1;
|
||||||
default: btnr = 1'b1;
|
2:
|
||||||
|
btnd = 1'b1;
|
||||||
|
3:
|
||||||
|
btnl = 1'b1;
|
||||||
|
default:
|
||||||
|
btnr = 1'b1;
|
||||||
endcase
|
endcase
|
||||||
#(hold_us * 1000);
|
#(hold_us * 1000);
|
||||||
case (which)
|
case (which)
|
||||||
0: go = 1'b0;
|
0:
|
||||||
1: btnu = 1'b0;
|
go = 1'b0;
|
||||||
2: btnd = 1'b0;
|
1:
|
||||||
3: btnl = 1'b0;
|
btnu = 1'b0;
|
||||||
default: btnr = 1'b0;
|
2:
|
||||||
|
btnd = 1'b0;
|
||||||
|
3:
|
||||||
|
btnl = 1'b0;
|
||||||
|
default:
|
||||||
|
btnr = 1'b0;
|
||||||
endcase
|
endcase
|
||||||
#(gap_us * 1000);
|
#(gap_us * 1000);
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -84,19 +84,29 @@ module tb_saolei_play;
|
|||||||
input integer hold_us;
|
input integer hold_us;
|
||||||
begin
|
begin
|
||||||
case (which)
|
case (which)
|
||||||
1: btnu = 1'b1;
|
1:
|
||||||
2: btnd = 1'b1;
|
btnu = 1'b1;
|
||||||
3: btnl = 1'b1;
|
2:
|
||||||
4: btnr = 1'b1;
|
btnd = 1'b1;
|
||||||
default: go = 1'b1;
|
3:
|
||||||
|
btnl = 1'b1;
|
||||||
|
4:
|
||||||
|
btnr = 1'b1;
|
||||||
|
default:
|
||||||
|
go = 1'b1;
|
||||||
endcase
|
endcase
|
||||||
#(hold_us * 1000);
|
#(hold_us * 1000);
|
||||||
case (which)
|
case (which)
|
||||||
1: btnu = 1'b0;
|
1:
|
||||||
2: btnd = 1'b0;
|
btnu = 1'b0;
|
||||||
3: btnl = 1'b0;
|
2:
|
||||||
4: btnr = 1'b0;
|
btnd = 1'b0;
|
||||||
default: go = 1'b0;
|
3:
|
||||||
|
btnl = 1'b0;
|
||||||
|
4:
|
||||||
|
btnr = 1'b0;
|
||||||
|
default:
|
||||||
|
go = 1'b0;
|
||||||
endcase
|
endcase
|
||||||
#(200 * 1000);
|
#(200 * 1000);
|
||||||
end
|
end
|
||||||
@@ -109,12 +119,18 @@ module tb_saolei_play;
|
|||||||
input [7:0] ch;
|
input [7:0] ch;
|
||||||
begin
|
begin
|
||||||
case (ch)
|
case (ch)
|
||||||
8'h75, 8'h77: pulse(1, 20); // u / w 上
|
8'h75, 8'h77:
|
||||||
8'h64, 8'h73: pulse(2, 20); // d / s 下
|
pulse(1, 20); // u / w 上
|
||||||
8'h6C, 8'h61: pulse(3, 20); // l / a 左
|
8'h64, 8'h73:
|
||||||
8'h72: pulse(4, 20); // r 右
|
pulse(2, 20); // d / s 下
|
||||||
8'h63: pulse(0, 30); // c 左键点击
|
8'h6C, 8'h61:
|
||||||
8'h67: pulse(0, 30); // g go 键(同一次点击)
|
pulse(3, 20); // l / a 左
|
||||||
|
8'h72:
|
||||||
|
pulse(4, 20); // r 右
|
||||||
|
8'h63:
|
||||||
|
pulse(0, 30); // c 左键点击
|
||||||
|
8'h67:
|
||||||
|
pulse(0, 30); // g go 键(同一次点击)
|
||||||
8'h66: begin // f 插旗模式点击
|
8'h66: begin // f 插旗模式点击
|
||||||
sw_flag = 1'b1;
|
sw_flag = 1'b1;
|
||||||
#(30 * 1000);
|
#(30 * 1000);
|
||||||
@@ -133,7 +149,8 @@ module tb_saolei_play;
|
|||||||
rst_n = 1'b1;
|
rst_n = 1'b1;
|
||||||
#(500 * 1000);
|
#(500 * 1000);
|
||||||
end
|
end
|
||||||
default: ; // 其它字符忽略
|
default:
|
||||||
|
; // 其它字符忽略
|
||||||
endcase
|
endcase
|
||||||
end
|
end
|
||||||
endtask
|
endtask
|
||||||
@@ -152,7 +169,8 @@ module tb_saolei_play;
|
|||||||
chc = $fgetc(cfp);
|
chc = $fgetc(cfp);
|
||||||
$fclose(cfp);
|
$fclose(cfp);
|
||||||
cfp = $fopen("cmd.txt", "w"); // 处理掉就清空,避免重复执行
|
cfp = $fopen("cmd.txt", "w"); // 处理掉就清空,避免重复执行
|
||||||
if (cfp != 0) $fclose(cfp);
|
if (cfp != 0)
|
||||||
|
$fclose(cfp);
|
||||||
if ((chc > 0) && (chc != 8'h0A) && (chc != 8'h0D) && (chc != 8'h20))
|
if ((chc > 0) && (chc != 8'h0A) && (chc != 8'h0D) && (chc != 8'h20))
|
||||||
do_key(chc[7:0]);
|
do_key(chc[7:0]);
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -16,9 +16,15 @@
|
|||||||
# k p 只切换插旗模式开关(不点击)
|
# k p 只切换插旗模式开关(不点击)
|
||||||
# k z 复位(等于重新上电,回到开始界面)
|
# k z 复位(等于重新上电,回到开始界面)
|
||||||
# k uucc 可以连写:上、上、点、点
|
# k uucc 可以连写:上、上、点、点
|
||||||
# shot 不按键,只重画一遍当前画面
|
# shot 不按键,只重画一遍当前画面 + 存一张 PNG
|
||||||
|
# view 用系统看图器打开最新那张 PNG
|
||||||
# help 键位说明
|
# help 键位说明
|
||||||
#
|
#
|
||||||
|
# 截图:k 每执行**一个**按键动作就自动存一张 PNG 到 shots/NNN_<按键>.png
|
||||||
|
# (可以直接放进报告),同时覆盖 shots/latest.png。
|
||||||
|
# 在 VS Code 里打开 shots/latest.png,每次操作后它都会自动刷新 —— 这就是
|
||||||
|
# "每一步都能看到 PNG" 的用法。
|
||||||
|
#
|
||||||
# 原理:k 把字符写进 cmd.txt;tb_saolei_play 每 100 us 仿真时间轮询一次并执行按键,
|
# 原理:k 把字符写进 cmd.txt;tb_saolei_play 每 100 us 仿真时间轮询一次并执行按键,
|
||||||
# 同时每 1 ms 把显存 dump 到 now.txt;k 再调 fb2png.py --term 把画面画到终端。
|
# 同时每 1 ms 把显存 dump 到 now.txt;k 再调 fb2png.py --term 把画面画到终端。
|
||||||
# 仿真速度大约是 1 ms 仿真时间 = 0.5 s 墙钟,所以每步等 1~2 秒,比真实慢很多,
|
# 仿真速度大约是 1 ms 仿真时间 = 0.5 s 墙钟,所以每步等 1~2 秒,比真实慢很多,
|
||||||
@@ -67,12 +73,38 @@ proc help {} {
|
|||||||
puts " k u/d/l/r move cursor up/down/left/right (w/s/a/d work too)"
|
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 f click in flag mode k p toggle flag switch only"
|
||||||
puts " k z reset k uucc several actions at once"
|
puts " k z reset k uucc several actions at once"
|
||||||
puts " shot redraw help this text quit exit"
|
puts " shot redraw+screenshot view open latest PNG"
|
||||||
puts " PNG screenshot: py \"$::fb2png\" now.txt shot.png"
|
puts " help this text quit exit"
|
||||||
|
puts " every key saved one PNG: shots/NNN_<key>.png and shots/latest.png"
|
||||||
puts " ---------------------------------------------------------------------------"
|
puts " ---------------------------------------------------------------------------"
|
||||||
}
|
}
|
||||||
|
|
||||||
# one step: send the key -> advance simulation -> redraw
|
# ---- 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
|
||||||
|
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]} {
|
||||||
|
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} {
|
proc k {keys} {
|
||||||
set s [string map {" " "c"} [string tolower $keys]]
|
set s [string map {" " "c"} [string tolower $keys]]
|
||||||
foreach ch [split $s ""] {
|
foreach ch [split $s ""] {
|
||||||
@@ -80,11 +112,15 @@ proc k {keys} {
|
|||||||
puts " >> $ch"
|
puts " >> $ch"
|
||||||
send_char $ch
|
send_char $ch
|
||||||
run 1500 us
|
run 1500 us
|
||||||
|
save_shot $ch
|
||||||
}
|
}
|
||||||
render
|
render
|
||||||
}
|
}
|
||||||
|
|
||||||
proc shot {} { render }
|
proc shot {} {
|
||||||
|
save_shot "shot"
|
||||||
|
render
|
||||||
|
}
|
||||||
|
|
||||||
# ---- opening: run a little so the start screen is drawn, then show it ----
|
# ---- opening: run a little so the start screen is drawn, then show it ----
|
||||||
help
|
help
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
用法:
|
用法:
|
||||||
py saolei/tools/fb2png.py <dump.txt> [输出.png]
|
py saolei/tools/fb2png.py <dump.txt> [输出.png]
|
||||||
py saolei/tools/fb2png.py --term <dump.txt> # 只在终端打印彩色棋盘(交互玩扫雷用)
|
py saolei/tools/fb2png.py --term <dump.txt> # 只在终端打印彩色棋盘(交互玩扫雷用)
|
||||||
|
py saolei/tools/fb2png.py --quiet <dump.txt> [输出.png] # 只写 PNG,不打印任何东西
|
||||||
|
|
||||||
dump 格式(tb_saolei.v 里的 dump_fb 任务生成):
|
dump 格式(tb_saolei.v 里的 dump_fb 任务生成):
|
||||||
CURSOR <row> <col>
|
CURSOR <row> <col>
|
||||||
@@ -158,8 +159,14 @@ def draw_term(fb, cur, cursor_box, note=""):
|
|||||||
def main():
|
def main():
|
||||||
args = list(sys.argv[1:])
|
args = list(sys.argv[1:])
|
||||||
term_only = "--term" in args
|
term_only = "--term" in args
|
||||||
|
quiet = "--quiet" in args
|
||||||
if term_only:
|
if term_only:
|
||||||
args.remove("--term")
|
args.remove("--term")
|
||||||
|
if quiet:
|
||||||
|
args.remove("--quiet")
|
||||||
|
if quiet: # 只出图不说话(给脚本批量截图用)
|
||||||
|
import os
|
||||||
|
sys.stdout = open(os.devnull, "w")
|
||||||
if not args:
|
if not args:
|
||||||
raise SystemExit(__doc__)
|
raise SystemExit(__doc__)
|
||||||
src = args[0]
|
src = args[0]
|
||||||
|
|||||||
Reference in New Issue
Block a user