diff --git a/pipelined-cpu/srcs/tb/tb_saolei.v b/pipelined-cpu/srcs/tb/tb_saolei.v index 9a0ec15..f64cc74 100644 --- a/pipelined-cpu/srcs/tb/tb_saolei.v +++ b/pipelined-cpu/srcs/tb/tb_saolei.v @@ -127,12 +127,16 @@ module tb_saolei; // irq_rise: 该线的上升沿次数(CPU 按上升沿响应中断) integer ptr_ev = 0, irq_hi = 0, irq_rise = 0; reg irq_prev_q; - initial irq_prev_q = 1'b0; + initial + irq_prev_q = 1'b0; always @(posedge dut.clk_cpu) begin - if (dut.g_btn.u_ptr.ev_valid_o) ptr_ev = ptr_ev + 1; - if (dut.irq_vec[0]) irq_hi = irq_hi + 1; + if (dut.g_btn.u_ptr.ev_valid_o) + ptr_ev = ptr_ev + 1; + if (dut.irq_vec[0]) + irq_hi = irq_hi + 1; 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 // 每毫秒打一行,观察事件链路 @@ -171,7 +175,7 @@ module tb_saolei; jmp_log = jmp_log + 1; end if ((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)) + (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 end @@ -284,19 +288,29 @@ module tb_saolei; input integer gap_us; begin case (which) - 0: go = 1'b1; - 1: btnu = 1'b1; - 2: btnd = 1'b1; - 3: btnl = 1'b1; - default: btnr = 1'b1; + 0: + go = 1'b1; + 1: + btnu = 1'b1; + 2: + btnd = 1'b1; + 3: + btnl = 1'b1; + default: + btnr = 1'b1; endcase #(hold_us * 1000); case (which) - 0: go = 1'b0; - 1: btnu = 1'b0; - 2: btnd = 1'b0; - 3: btnl = 1'b0; - default: btnr = 1'b0; + 0: + go = 1'b0; + 1: + btnu = 1'b0; + 2: + btnd = 1'b0; + 3: + btnl = 1'b0; + default: + btnr = 1'b0; endcase #(gap_us * 1000); end diff --git a/pipelined-cpu/srcs/tb/tb_saolei_play.v b/pipelined-cpu/srcs/tb/tb_saolei_play.v index 036c070..682ee71 100644 --- a/pipelined-cpu/srcs/tb/tb_saolei_play.v +++ b/pipelined-cpu/srcs/tb/tb_saolei_play.v @@ -84,19 +84,29 @@ module tb_saolei_play; input integer hold_us; begin case (which) - 1: btnu = 1'b1; - 2: btnd = 1'b1; - 3: btnl = 1'b1; - 4: btnr = 1'b1; - default: go = 1'b1; + 1: + btnu = 1'b1; + 2: + btnd = 1'b1; + 3: + btnl = 1'b1; + 4: + btnr = 1'b1; + default: + go = 1'b1; endcase #(hold_us * 1000); case (which) - 1: btnu = 1'b0; - 2: btnd = 1'b0; - 3: btnl = 1'b0; - 4: btnr = 1'b0; - default: go = 1'b0; + 1: + btnu = 1'b0; + 2: + btnd = 1'b0; + 3: + btnl = 1'b0; + 4: + btnr = 1'b0; + default: + go = 1'b0; endcase #(200 * 1000); end @@ -109,12 +119,18 @@ module tb_saolei_play; input [7:0] ch; begin case (ch) - 8'h75, 8'h77: pulse(1, 20); // u / w 上 - 8'h64, 8'h73: pulse(2, 20); // d / s 下 - 8'h6C, 8'h61: 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'h75, 8'h77: + pulse(1, 20); // u / w 上 + 8'h64, 8'h73: + pulse(2, 20); // d / s 下 + 8'h6C, 8'h61: + 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 插旗模式点击 sw_flag = 1'b1; #(30 * 1000); @@ -133,7 +149,8 @@ module tb_saolei_play; rst_n = 1'b1; #(500 * 1000); end - default: ; // 其它字符忽略 + default: + ; // 其它字符忽略 endcase end endtask @@ -152,7 +169,8 @@ module tb_saolei_play; chc = $fgetc(cfp); $fclose(cfp); 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)) do_key(chc[7:0]); end diff --git a/pipelined-cpu/tools/play_saolei.tcl b/pipelined-cpu/tools/play_saolei.tcl index 6a987f9..f6b13a7 100644 --- a/pipelined-cpu/tools/play_saolei.tcl +++ b/pipelined-cpu/tools/play_saolei.tcl @@ -16,9 +16,15 @@ # k p 只切换插旗模式开关(不点击) # k z 复位(等于重新上电,回到开始界面) # k uucc 可以连写:上、上、点、点 -# shot 不按键,只重画一遍当前画面 +# 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 秒,比真实慢很多, @@ -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 f click in flag mode k p toggle flag switch only" puts " k z reset k uucc several actions at once" - puts " shot redraw help this text quit exit" - puts " PNG screenshot: py \"$::fb2png\" now.txt shot.png" + puts " shot redraw+screenshot view open latest PNG" + puts " help this text quit exit" + puts " every key saved one PNG: shots/NNN_.png and shots/latest.png" 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} { set s [string map {" " "c"} [string tolower $keys]] foreach ch [split $s ""] { @@ -80,11 +112,15 @@ proc k {keys} { puts " >> $ch" send_char $ch run 1500 us + save_shot $ch } render } -proc shot {} { render } +proc shot {} { + save_shot "shot" + render +} # ---- opening: run a little so the start screen is drawn, then show it ---- help diff --git a/saolei/tools/fb2png.py b/saolei/tools/fb2png.py index db1c164..0325f37 100644 --- a/saolei/tools/fb2png.py +++ b/saolei/tools/fb2png.py @@ -4,6 +4,7 @@ 用法: py saolei/tools/fb2png.py [输出.png] py saolei/tools/fb2png.py --term # 只在终端打印彩色棋盘(交互玩扫雷用) + py saolei/tools/fb2png.py --quiet [输出.png] # 只写 PNG,不打印任何东西 dump 格式(tb_saolei.v 里的 dump_fb 任务生成): CURSOR @@ -158,8 +159,14 @@ def draw_term(fb, cur, cursor_box, note=""): def main(): args = list(sys.argv[1:]) term_only = "--term" in args + quiet = "--quiet" in args if term_only: args.remove("--term") + if quiet: + args.remove("--quiet") + if quiet: # 只出图不说话(给脚本批量截图用) + import os + sys.stdout = open(os.devnull, "w") if not args: raise SystemExit(__doc__) src = args[0]