feat(saolei): 命令行交互式玩扫雷(无需 VGA 显示器)
- srcs/tb/tb_saolei_play.v:受 cmd.txt 驱动的交互式 TB,每 100us 轮询一个按键字符, 每 1ms 把显存 dump 到 now.txt - tools/play_saolei.tcl:xsim 里 k c / k u / k f 一步一按键,自动把 now.txt 画到终端 - saolei/tools/fb2png.py:新增 --term,用 ANSI 24 位色在终端画 40x30 棋盘,光标格反色 - .gitignore:放行 create_project_saolei.tcl 实测:k c 进关卡、k uuuu 光标精确移动 4 格、k c 首击展开出数字 1/2
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
# ============================================================================
|
||||
# create_project_saolei.tcl —— 一键重建【扫雷专用】Vivado 工程
|
||||
# FPGA:Nexys4DDR / xc7a100tcsg324-1(Vivado 2020.2)
|
||||
#
|
||||
# 与 create_project.tcl 的区别(那个是"通用/可切换程序"的脚本):
|
||||
# * 只装配扫雷需要的东西:rtl/*.v + board/cpu21_board_top.v + 扫雷 ROM 头文件
|
||||
# * sources_1 与 sim_1 都固定定义宏 **SAOLEI_ROM**(不用再改任何开关)
|
||||
# * 工程名是独立的 pipelined-cpu-saolei,跟通用工程 pipelined-cpu 互不影响,
|
||||
# 两个工程可以并存;本脚本只清理自己的生成物。
|
||||
# * sim_1 顶层直接就是 tb_saolei(GUI 里 Run Simulation 就能出画面 dump)
|
||||
#
|
||||
# 用法(在 Vivado Tcl Shell 里):
|
||||
# source create_project_saolei.tcl
|
||||
# 或命令行:
|
||||
# vivado -mode batch -source create_project_saolei.tcl -log create_saolei.log
|
||||
#
|
||||
# 前置条件:扫雷的 ROM 镜像必须先由 C 程序生成
|
||||
# cd saolei
|
||||
# powershell -ExecutionPolicy Bypass -File tools\build.ps1 `
|
||||
# -Prefix 'C:\Users\29653\tools\riscv\xpack-riscv-none-elf-gcc-15.2.0-1\bin\riscv-none-elf-'
|
||||
# → 产出 saolei/build/saolei.hex 与 pipelined-cpu/srcs/board/cpu21_saolei_rom.vh
|
||||
# (改了 saolei/sw/*.c 之后要重跑这条命令,再综合/再跑仿真)
|
||||
#
|
||||
# 不想自动清理旧工程:把下面的 clean_first 改成 0。
|
||||
# 想建完就顺手综合:把 run_synth 改成 1(2020.2 上约 1~2 分钟)。
|
||||
#
|
||||
# 附:没有 VGA 显示器时怎么"看/玩"扫雷
|
||||
# 看:本工程的 sim_1 跑 tb_saolei,会把显存 dump 成文本;再用
|
||||
# py saolei\tools\fb2png.py <dump.txt> <out.png> 还原成 640x480 画面。
|
||||
# 玩:见 pipelined-cpu/tools/play_saolei.tcl(命令行交互式仿真,一步一截图)。
|
||||
# ============================================================================
|
||||
|
||||
set origin_dir [file dirname [file normalize [info script]]]
|
||||
set proj_name "pipelined-cpu-saolei"
|
||||
set part_name "xc7a100tcsg324-1"
|
||||
|
||||
# ---------------------------------------------------------------- 自检
|
||||
set saolei_rom "$origin_dir/srcs/board/cpu21_saolei_rom.vh"
|
||||
set required [list \
|
||||
"$origin_dir/srcs/board/cpu21_board_top.v" \
|
||||
"$origin_dir/srcs/constrs/cpu21_board_top.xdc" \
|
||||
"$origin_dir/srcs/tb/tb_saolei.v"]
|
||||
if {[llength [glob -nocomplain "$origin_dir/srcs/rtl/*.v"]] == 0} {
|
||||
lappend required "$origin_dir/srcs/rtl/*.v"
|
||||
}
|
||||
set missing {}
|
||||
foreach f $required { if {![file exists $f]} { lappend missing $f } }
|
||||
if {[llength $missing] > 0} {
|
||||
puts "ERROR: 缺少下列文件,请确认 srcs/ 目录完整:"
|
||||
foreach f $missing { puts " $f" }
|
||||
return 1
|
||||
}
|
||||
if {![file exists $saolei_rom]} {
|
||||
puts "ERROR: 还没有扫雷 ROM 镜像:$saolei_rom"
|
||||
puts " 先在 saolei/ 目录跑 tools\\build.ps1 生成它(见本脚本头部注释)。"
|
||||
return 1
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- 清理旧工程
|
||||
# 和 create_project.tcl 同一套策略:只删生成物,srcs/ 等源文件一律不动。
|
||||
set clean_first 1
|
||||
set run_synth 0
|
||||
set synth_jobs 4
|
||||
|
||||
if {$clean_first} {
|
||||
set proj_items [list \
|
||||
"$origin_dir/$proj_name.xpr" \
|
||||
"$origin_dir/$proj_name.srcs" \
|
||||
"$origin_dir/$proj_name.runs" \
|
||||
"$origin_dir/$proj_name.sim" \
|
||||
"$origin_dir/$proj_name.cache" \
|
||||
"$origin_dir/$proj_name.hw" \
|
||||
"$origin_dir/$proj_name.gen" \
|
||||
"$origin_dir/$proj_name.ip_user_files" \
|
||||
"$origin_dir/$proj_name.lpr" \
|
||||
"$origin_dir/.Xil"]
|
||||
set junk_items [list \
|
||||
"$origin_dir/xsim.dir" \
|
||||
"$origin_dir/webtalk.jou" \
|
||||
"$origin_dir/webtalk.log" \
|
||||
"$origin_dir/usage_statistics_webtalk.html" \
|
||||
"$origin_dir/usage_statistics_webtalk.xml"]
|
||||
foreach pat {vivado*.jou vivado*.log vivado*.str} {
|
||||
foreach f [glob -nocomplain "$origin_dir/$pat"] { lappend junk_items $f }
|
||||
}
|
||||
|
||||
set blocked {}
|
||||
set removed 0
|
||||
foreach {group items} [list proj $proj_items junk $junk_items] {
|
||||
foreach d $items {
|
||||
if {![file exists $d]} { continue }
|
||||
if {[catch {file delete -force $d} err]} {
|
||||
puts " WARNING: 删不掉 [file tail $d]($err)"
|
||||
if {$group eq "proj"} { lappend blocked [file tail $d] }
|
||||
} else {
|
||||
puts " 已删除:[file tail $d]"
|
||||
incr removed
|
||||
}
|
||||
}
|
||||
}
|
||||
puts "旧工程清理完成(删除 $removed 项,只动 $proj_name.* 与自己产生的临时文件)"
|
||||
if {[llength $blocked] > 0} {
|
||||
puts "ERROR: 这些工程目录删不掉:$blocked"
|
||||
puts " 它们正被另一个 Vivado(GUI / Hardware Manager)占用,关掉后重跑。"
|
||||
return 1
|
||||
}
|
||||
} else {
|
||||
if {[file exists "$origin_dir/$proj_name.xpr"] || [file exists "$origin_dir/$proj_name.runs"]} {
|
||||
puts "ERROR: 工程已存在且 clean_first = 0:$origin_dir/$proj_name.xpr"
|
||||
puts " 请先删掉它,或把 clean_first 改成 1。"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- 建工程
|
||||
# 只用 -part,不依赖 Digilent 板卡文件(引脚全在 srcs/constrs/*.xdc 里)。
|
||||
create_project $proj_name $origin_dir -part $part_name -force
|
||||
|
||||
# 设计源文件:处理器 RTL + 板级顶层 + 扫雷 ROM
|
||||
add_files -norecurse -fileset sources_1 [glob "$origin_dir/srcs/rtl/*.v"]
|
||||
add_files -norecurse -fileset sources_1 [list \
|
||||
"$origin_dir/srcs/board/cpu21_board_top.v" \
|
||||
$saolei_rom]
|
||||
set_property file_type {Verilog Header} [get_files $saolei_rom]
|
||||
|
||||
# 关键:两个 fileset 都定义 SAOLEI_ROM,cpu21_board_top.v 的 `ifdef 链才会选中扫雷 ROM。
|
||||
# (sources_1 管综合/上板,sim_1 管 GUI 仿真;sim_1 不定义的话会跑成走马灯。)
|
||||
set_property verilog_define {SAOLEI_ROM} [get_filesets sources_1]
|
||||
set_property verilog_define {SAOLEI_ROM} [get_filesets sim_1]
|
||||
|
||||
# 约束(引脚 + 时钟分频)
|
||||
add_files -norecurse -fileset constrs_1 [glob "$origin_dir/srcs/constrs/*.xdc"]
|
||||
|
||||
# 仿真:扫雷调试台。POINTER_SAMPLE_DIV 由 tb 自己 defparam 压小,上板仍是 50000。
|
||||
add_files -norecurse -fileset sim_1 [list "$origin_dir/srcs/tb/tb_saolei.v"]
|
||||
|
||||
# ---------------------------------------------------------------- 顶层与顺序
|
||||
set_property top cpu21_board_top [get_filesets sources_1]
|
||||
set_property top tb_saolei [get_filesets sim_1]
|
||||
update_compile_order -fileset sources_1
|
||||
update_compile_order -fileset sim_1
|
||||
|
||||
# GUI 仿真默认只跑 1000 ns,这里改成跑完(否则看起来像卡住)
|
||||
catch { set_property -name {xsim.simulate.runtime} -value {all} -objects [get_filesets sim_1] }
|
||||
|
||||
# ---------------------------------------------------------------- 可选:顺手综合
|
||||
if {$run_synth} {
|
||||
puts "开始综合 synth_1(run_synth = 1)..."
|
||||
reset_run synth_1
|
||||
launch_runs synth_1 -jobs $synth_jobs
|
||||
wait_on_run synth_1
|
||||
if {[get_property PROGRESS [get_runs synth_1]] ne "100%"} {
|
||||
puts "ERROR: 综合未完成,状态 = [get_property STATUS [get_runs synth_1]]"
|
||||
return 1
|
||||
}
|
||||
puts "综合完成:0 error 即可 Run Implementation → Generate Bitstream。"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- 结果汇总
|
||||
puts ""
|
||||
puts "工程已创建:$origin_dir/$proj_name.xpr"
|
||||
puts " sources_1 顶层 = cpu21_board_top,程序 = 扫雷(已定义 SAOLEI_ROM)"
|
||||
puts " sources_1 ROM = [file tail $saolei_rom]"
|
||||
puts " sim_1 顶层 = tb_saolei(跑仿真会打印显存 dump,用 fb2png.py 还原成 PNG)"
|
||||
puts " 约束 = [file tail [lindex [glob "$origin_dir/srcs/constrs/*.xdc"] 0]]"
|
||||
puts " 下一步:Run Synthesis → Run Implementation → Generate Bitstream"
|
||||
puts " (本工程已固定扫雷,不用再改 create_project.tcl 里的三个开关)"
|
||||
puts " 上板操作:BTNU/D/L/R 移动光标,BTNC = 左键,SW3 = 插旗模式"
|
||||
puts " 注意:CPU 时钟 = 100 MHz / CLK_DIV(默认 2) = 50 MHz;改分频比时"
|
||||
puts " cpu21_board_top.v 的 CLK_DIV 与 cpu21_board_top.xdc 的 -divide_by 要一起改。"
|
||||
@@ -0,0 +1,183 @@
|
||||
`timescale 1ns / 1ps
|
||||
// =============================================================================
|
||||
// tb_saolei_play —— 没有 VGA 显示器时"在命令行里玩扫雷"用的测试平台
|
||||
//
|
||||
// 与 tb_saolei 的区别:没有写死的游戏脚本,改成受外部文件驱动:
|
||||
//
|
||||
// cmd.txt 外部(xsim 的 Tcl,见 pipelined-cpu/tools/play_saolei.tcl)往里写
|
||||
// 按键字符;本 TB 每 100 us 仿真时间轮询一次,读到**一个**字符就执行
|
||||
// 对应的按键脉冲,然后把文件清空。(多个动作用多次发送实现,
|
||||
// play_saolei.tcl 的 k 命令会自动把 "uuc" 拆成三次发。)
|
||||
// u/w 光标上移 d/s 光标下移
|
||||
// l/a 光标左移 r 光标右移
|
||||
// c 左键点击(BTNC)
|
||||
// f 插旗模式点击(先按 SW3 再点击,点上/取消旗帜)
|
||||
// p 只切换 SW3(插旗模式开关,不点击)
|
||||
// g 按板上的 go 键
|
||||
// z 复位(拉低 rst_n 再放开,等于重新上电)
|
||||
//
|
||||
// now.txt 每 1 ms 仿真时间把当前显存 dump 一次(格式同 tb_saolei):
|
||||
// py saolei/tools/fb2png.py --term now.txt -> 终端里的彩色棋盘
|
||||
// py saolei/tools/fb2png.py now.txt a.png -> 640x480 截图
|
||||
//
|
||||
// 编译(两个 -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_play.v
|
||||
// xelab -d SAOLEI_ROM -top tb_saolei_play -snapshot saolei_play
|
||||
// xsim saolei_play -tclbatch tools/play_saolei.tcl
|
||||
//
|
||||
// 说明:和 tb_saolei 一样用 POINTER_SAMPLE_DIV=50 压缩按键去抖(上板仍是 50000)。
|
||||
// =============================================================================
|
||||
|
||||
module tb_saolei_play;
|
||||
|
||||
// ---- 板级顶层端口(全部由本 TB / cmd.txt 驱动,不用 add_force)----
|
||||
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;
|
||||
|
||||
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)
|
||||
);
|
||||
|
||||
// ---- 一个按键脉冲:按住 hold_us 再松开,然后留 200 us 让 CPU 消化事件 ----
|
||||
task pulse;
|
||||
input integer which;
|
||||
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;
|
||||
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;
|
||||
endcase
|
||||
#(200 * 1000);
|
||||
end
|
||||
endtask
|
||||
|
||||
// ---- 把 cmd.txt 里的一个字符变成按键动作 ----
|
||||
// 注意:一次只取一个字符($fgetc),不用 $fgets/字符串,
|
||||
// 这样不会踩 "reg [8*N:1] 里第一个字符落在哪个字节" 的坑。
|
||||
task do_key;
|
||||
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'h66: begin // f 插旗模式点击
|
||||
sw_flag = 1'b1;
|
||||
#(30 * 1000);
|
||||
pulse(0, 30);
|
||||
#(30 * 1000);
|
||||
sw_flag = 1'b0;
|
||||
#(30 * 1000);
|
||||
end
|
||||
8'h70: begin // p 只切换插旗开关
|
||||
sw_flag = ~sw_flag;
|
||||
#(50 * 1000);
|
||||
end
|
||||
8'h7A: begin // z 复位
|
||||
rst_n = 1'b0;
|
||||
#(2 * 1000);
|
||||
rst_n = 1'b1;
|
||||
#(500 * 1000);
|
||||
end
|
||||
default: ; // 其它字符忽略
|
||||
endcase
|
||||
end
|
||||
endtask
|
||||
|
||||
integer cfp;
|
||||
integer chc;
|
||||
|
||||
// ---- 命令轮询:每 100 us 看一次 cmd.txt(只读第一个字符)----
|
||||
initial begin
|
||||
#2000;
|
||||
rst_n = 1'b1; // 上电复位释放
|
||||
forever begin
|
||||
#(100 * 1000);
|
||||
cfp = $fopen("cmd.txt", "r");
|
||||
if (cfp != 0) begin
|
||||
chc = $fgetc(cfp);
|
||||
$fclose(cfp);
|
||||
cfp = $fopen("cmd.txt", "w"); // 处理掉就清空,避免重复执行
|
||||
if (cfp != 0) $fclose(cfp);
|
||||
if ((chc > 0) && (chc != 8'h0A) && (chc != 8'h0D) && (chc != 8'h20))
|
||||
do_key(chc[7:0]);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ---- 画面 dump:每 1 ms 覆盖写一次 now.txt ----
|
||||
integer df, w2, b2;
|
||||
|
||||
initial begin
|
||||
forever begin
|
||||
#(1 * 1000 * 1000);
|
||||
df = $fopen("now.txt", "w");
|
||||
if (df != 0) begin
|
||||
$fwrite(df, "TIME %0t\n", $time);
|
||||
$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)
|
||||
for (b2 = 0; b2 < 4; b2 = b2 + 1)
|
||||
$fwrite(df, "%02X\n", dut.fb[w2][b2*8 +: 8]);
|
||||
$fclose(df);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,93 @@
|
||||
# =============================================================================
|
||||
# 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 不按键,只重画一遍当前画面
|
||||
# help 键位说明
|
||||
#
|
||||
# 原理: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]]
|
||||
|
||||
# 按键字符 -> 人类可读的动作(只用于回显)
|
||||
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
|
||||
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]} {
|
||||
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 help this text quit exit"
|
||||
puts " PNG screenshot: py \"$::fb2png\" now.txt shot.png"
|
||||
puts " ---------------------------------------------------------------------------"
|
||||
}
|
||||
|
||||
# one step: send the key -> advance simulation -> 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
|
||||
}
|
||||
render
|
||||
}
|
||||
|
||||
proc 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
|
||||
Reference in New Issue
Block a user