diff --git a/pipelined-cpu/srcs/tb/tb_saolei.v b/pipelined-cpu/srcs/tb/tb_saolei.v index b33359e..9a0ec15 100644 --- a/pipelined-cpu/srcs/tb/tb_saolei.v +++ b/pipelined-cpu/srcs/tb/tb_saolei.v @@ -43,8 +43,8 @@ module tb_saolei; always #5 clk100 = ~clk100; cpu21_board_top #( - .POINTER_SAMPLE_DIV(50) // 仿真里加速按键去抖(上板默认 50000) - ) dut ( + .POINTER_SAMPLE_DIV(50) // 仿真里加速按键去抖(上板默认 50000) + ) dut ( .clk100 (clk100), .rst_n (rst_n), .go (go), @@ -121,25 +121,59 @@ module tb_saolei; if (dut.fb_we) fb_we_cnt = fb_we_cnt + 1; + // ---- 鼠标事件链路三个关键节点 ---- + // ptr_ev : cpu21_pointer 产生事件(ev_valid_o 高的周期数) + // irq_hi : 鼠标中断线 irq_vec[0] 为高的周期数 + // irq_rise: 该线的上升沿次数(CPU 按上升沿响应中断) + integer ptr_ev = 0, irq_hi = 0, irq_rise = 0; + reg irq_prev_q; + 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; + irq_prev_q <= dut.irq_vec[0]; + if (dut.irq_vec[0] && !irq_prev_q) irq_rise = irq_rise + 1; + end + + // 每毫秒打一行,观察事件链路 + integer tick_ms; + initial + for (tick_ms = 1; tick_ms <= 20; tick_ms = tick_ms + 1) begin + #1000000; + $display("[mouse] %0t ns: ptr_ev=%0d irq_hi=%0d irq_rise=%0d", $time, ptr_ev, irq_hi, irq_rise); + end + integer nz_cnt = 0; // 当前非零格数(dump 时统计) // ---- PC 热点直方图:确定 CPU 此刻卡在哪个循环 ---- integer pc_hist[0:255]; integer pi; - initial for (pi = 0; pi < 256; pi = pi + 1) pc_hist[pi] = 0; + initial + for (pi = 0; pi < 256; pi = pi + 1) + pc_hist[pi] = 0; always @(posedge dut.clk_cpu) pc_hist[dut.u_cpu.pc_q[9:2]] = pc_hist[dut.u_cpu.pc_q[9:2]] + 1; - // ---- 探针:PC 什么时候"跳进" ISR 段之间的空洞(0x194-0x1FF / 0x218-0x2FF)---- - // 这些空洞在 ROM 里是 0x00000000,在本 CPU 上会被当成自跳指令,PC 掉进去出不来。 + // ---- 探针:控制流日志 ---- + // 1) 记录所有非顺序跳转(1 ms 之后、最多 300 条),用来复盘"跑飞"的路径; + // 2) 单独标出跳进 ISR 段之间空洞(0x194-0x1FF / 0x218-0x2FF)的时刻。 reg [31:0] pc_prev; - initial pc_prev = 32'hFFFF_FFFF; + integer jmp_log; + initial begin + pc_prev = 32'hFFFF_FFFF; + jmp_log = 0; + end always @(posedge dut.clk_cpu) begin pc_prev <= dut.u_cpu.pc_q; - if (dut.u_cpu.pc_q != (pc_prev + 32'd4) && - ((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))) - $display("[gap] %0t ns: PC 0x%03h -> 0x%03h (掉进空洞)", $time, pc_prev, dut.u_cpu.pc_q); + if (dut.u_cpu.pc_q != (pc_prev + 32'd4)) begin + if ($time > 1000000 && jmp_log < 300) begin + $display("[jmp] %0t ns: 0x%03h -> 0x%03h", $time, pc_prev, dut.u_cpu.pc_q); + 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)) + $display("[gap] %0t ns: PC 0x%03h -> 0x%03h (跳进空洞)", $time, pc_prev, dut.u_cpu.pc_q); + end end task dump_pc; @@ -222,52 +256,185 @@ module tb_saolei; end endtask + // ---------------------------------------------------------------- 完整一局用的工具 + // 从 RAM 的位图里取一位(mine_bm @0x470 / open_bm @0x450,字内小端) + function integer bitof; + input integer base; + input integer idx; + begin + bitof = (dut.ram[(base >> 2) + (idx >> 5)] >> (idx & 31)) & 1; + end + endfunction + + // 已揭开格数(直接数 open_bm 的 1),目标 = 256 - 40 = 216 + integer open_n; + task count_open; + integer k3; + begin + open_n = 0; + for (k3 = 0; k3 < 256; k3 = k3 + 1) + open_n = open_n + bitof(32'h0000_0450, k3); + end + endtask + + // 快速按键(按住/松开时长都可控):去抖周期已由 POINTER_SAMPLE_DIV=50 缩短 + task press_t; + input integer which; + input integer hold_us; + 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; + 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; + endcase + #(gap_us * 1000); + end + endtask + + task click_t; + begin + press_t(0, 30, 150); + end + endtask + + // 把光标移到盘面格 (tr, tc):每次按键后等软件把事件消化掉再回读位置, + // 丢了/多了事件都能自动纠正。盘面格 (0,0) 对应网格 (BOARD_R0, BOARD_C0) = (7, 12)。 + task move_to_cell; + input integer tr; + input integer tc; + integer cr, cc2; + begin + cr = dut.ram[32'h0418 >> 2]; + cc2 = dut.ram[32'h0414 >> 2]; + while (cr > (7 + tr)) begin + press_t(1, 20, 120); + cr = dut.ram[32'h0418 >> 2]; + end + while (cr < (7 + tr)) begin + press_t(2, 20, 120); + cr = dut.ram[32'h0418 >> 2]; + end + while (cc2 > (12 + tc)) begin + press_t(3, 20, 120); + cc2 = dut.ram[32'h0414 >> 2]; + end + while (cc2 < (12 + tc)) begin + press_t(4, 20, 120); + cc2 = dut.ram[32'h0414 >> 2]; + end + end + endtask + + // 落位 + 校验:没到位就重试(最多 6 次),彻底避免“以为到了、实际差一格”去点错格 + task goto_cell; + input integer tr; + input integer tc; + integer tries; + begin + tries = 0; + while (((dut.ram[32'h0418 >> 2] != (7 + tr)) || (dut.ram[32'h0414 >> 2] != (12 + tc))) && (tries < 6)) begin + move_to_cell(tr, tc); + #(300 * 1000); + tries = tries + 1; + end + if (tries >= 6) + $display(" [game] !!! 定位 (%0d,%0d) 失败,实际 cur=(%0d,%0d)", tr, tc, + dut.ram[32'h0418 >> 2], dut.ram[32'h0414 >> 2]); + end + endtask + + // 扫完全盘:安全格揭开、雷格插旗;雷区位置直接从 RAM 里的 mine_bm 读 + task sweep_board; + integer rr, cc, idx; + begin : sweep_blk + for (rr = 0; rr < 16; rr = rr + 1) begin + for (cc = 0; cc < 16; cc = cc + 1) begin + idx = rr * 16 + cc; + goto_cell(rr, cc); + if (bitof(32'h0000_0470, idx) != 0) begin + sw_flag = 1'b1; + #(30 * 1000); + click_t; + sw_flag = 1'b0; + #(30 * 1000); + end + else + click_t; + // 中途退出关卡就立即报警并收手 + if (dut.ram[32'h0008 >> 2] == 0) begin + $display(" [game] *** 在盘面 (%0d,%0d) 处退出了关卡!cur=(%0d,%0d) state=%0d", + rr, cc, dut.ram[32'h0418 >> 2], dut.ram[32'h0414 >> 2], + dut.ram[32'h0424 >> 2]); + disable sweep_blk; + end + // 赢了/输了也收手 + if (dut.ram[32'h0424 >> 2] != 0) + disable sweep_blk; + end + count_open; + $display(" [game] 第 %0d 行扫完:已揭开 %0d / 216, state=%0d, 旗=%0d, cur=(%0d,%0d)", + rr, open_n, dut.ram[32'h0424 >> 2], dut.ram[32'h0428 >> 2], + dut.ram[32'h0418 >> 2], dut.ram[32'h0414 >> 2]); + if (rr == 3) + dump_fb("fb_10_sweep_a.txt"); + if (rr == 7) + dump_fb("fb_11_sweep_b.txt"); + if (rr == 11) + dump_fb("fb_12_sweep_c.txt"); + end + end + endtask + // ---------------------------------------------------------------- 主流程 initial begin - $display("[tb_saolei] === start (SAOLEI_ROM) ==="); + $display("[tb_saolei] === start (SAOLEI_ROM):完整一局 ==="); #2000; rst_n = 1'b1; - // 1) 背景开始界面:光标初始就停在 START 按钮上(行 20,列 17..22) + // 1) 开始界面 #(2 * 1000000); dump_fb("fb_00_start.txt"); - // 2) 验证移动的方向与步长:下 2 格、右 2 格,再原路回位 - repeat (2) press(2, 200); - repeat (2) press(4, 200); - dump_fb("fb_01_moved.txt"); - repeat (2) press(1, 200); - repeat (2) press(3, 200); - dump_fb("fb_02_back.txt"); - - // 3) 点"开始" -> 写 LEVEL -> IRQ1 进关卡 - click; + // 2) 点 START(光标初始就停在按钮上)-> 写 LEVEL -> IRQ1 进关卡 + click_t; #(2 * 1000000); - dump_fb("fb_03_level.txt"); + dump_fb("fb_01_level.txt"); + $display(" [game] 进关卡:in_level=%0d state=%0d", dut.ram[32'h0008 >> 2], dut.ram[32'h0424 >> 2]); - // 4) 关卡内:往下 2 格、往右 2 格,再点一下揭开 - repeat (2) press(2, 200); - repeat (2) press(4, 200); - dump_fb("fb_04_cursor.txt"); + // 3) 关卡内点盘面中心一格:放雷 + 首次展开(第一下必安全) click; - #(2 * 1000000); - dump_fb("fb_05_firstclick.txt"); + #(3 * 1000000); + dump_fb("fb_02_firstclick.txt"); + count_open; + $display(" [game] 首次点击后:first=%0d 已揭开 %0d / 216 state=%0d", + dut.ram[32'h0420 >> 2], open_n, dut.ram[32'h0424 >> 2]); - // 5) 插旗(SW3 = 插旗模式 + 左键) - sw_flag = 1'b1; - #(100 * 1000); - click; - #(2 * 1000000); - sw_flag = 1'b0; - dump_fb("fb_06_flag.txt"); + // 4) 蛇形扫完全盘:揭开全部 216 个安全格 + 给 40 颗雷插旗 -> 目标通关 + sweep_board; + #(3 * 1000000); + dump_fb("fb_13_after_sweep.txt"); + count_open; + $display(" [game] 扫完后:已揭开 %0d / 216, 旗=%0d, state=%0d, 丢事件=%0d", + open_n, dut.ram[32'h0428 >> 2], dut.ram[32'h0424 >> 2], dut.ram[32'h0490 >> 2]); - // 6) 往右一格再揭开 - press(4, 200); - click; - #(2 * 1000000); - dump_fb("fb_07_click2.txt"); + // 5) 通关后再点一下 = 返回背景(回到开始界面) + click_t; + #(3 * 1000000); + dump_fb("fb_14_back_to_start.txt"); - $display("[tb_saolei] === done, LED=%04h led_we=%b ===", led, led_we); + $display(" [game] === 结束:state=%0d(1=WIN 2=LOSE)已揭开 %0d/216 旗=%0d 丢事件=%0d ===", + dut.ram[32'h0424 >> 2], open_n, dut.ram[32'h0428 >> 2], dut.ram[32'h0490 >> 2]); $finish; end