- 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
184 lines
7.1 KiB
Verilog
184 lines
7.1 KiB
Verilog
`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
|