`timescale 1ns / 1ps // ============================================================================= // tb_ps2_kbd —— 单独验证 PS/2 键盘解码(扫描码集 2) // // 发 → 键(E0 74)应产生 dx=+8;发 ↑(E0 75)应产生 dy=-8; // 发 Enter(5A)应产生 btn=1(开格);发 左Shift(12)应产生 btn=2(插旗); // 松开码(F0 xx)不应产生事件;校验位错的帧应被整帧丢掉。 // // PS/2 键盘发扫描码(集 2):0xF0 前缀 = 下一个码是"松开",0xE0 前缀 = 扩展键, // 方向键是扩展键(↑ = E0 75、↓ = E0 72、← = E0 6B、→ = E0 74), // 所以解码器只对"按下"产生事件。键盘自动重复 = 按住方向键时连续移动。 // // 跑法: // xvlog srcs/rtl/cpu21_ps2_kbd.v srcs/tb/tb_ps2_kbd.v // xelab -top tb_ps2_kbd -snapshot kbdsim // xsim kbdsim -R // ============================================================================= module tb_ps2_kbd; reg clk = 1'b0; reg reset = 1'b1; reg ps2_clk = 1'b1; reg ps2_data = 1'b1; wire ev_valid; wire [7:0] ev_dx, ev_dy; wire [4:0] ev_btn; wire [15:0] rx_count; always #10 clk = ~clk; // 50 MHz cpu21_ps2_kbd dut ( .clk (clk), .reset (reset), .ps2_clk_i (ps2_clk), .ps2_data_i (ps2_data), .ev_valid_o (ev_valid), .ev_dx_o (ev_dx), .ev_dy_o (ev_dy), .ev_btn_o (ev_btn), .rx_count_o (rx_count) ); // 发一个 PS/2 位:先摆数据,稳定后拉低时钟(DUT 在下降沿采样) task send_bit; input b; begin ps2_data = b; #(10_000); ps2_clk = 1'b0; #(20_000); ps2_clk = 1'b1; #(20_000); end endtask // 发一个字节:起始 0 + 8 位数据(LSB first) + 奇校验 + 停止 1 task send_byte; input [7:0] d; integer i; reg p; begin p = ~^d; // 奇校验 send_bit(1'b0); for (i = 0; i < 8; i = i + 1) send_bit(d[i]); send_bit(p); send_bit(1'b1); end endtask // 校验位错的字节(应被整帧丢掉) task send_byte_bad_parity; input [7:0] d; integer i; begin send_bit(1'b0); for (i = 0; i < 8; i = i + 1) send_bit(d[i]); send_bit(^d); send_bit(1'b1); end endtask integer n_ev = 0; integer ok = 1; integer exp_dx, exp_dy, exp_btn; always @(posedge clk) begin if (ev_valid) begin n_ev = n_ev + 1; $display(" [kbd] event #%0d: dx=%0d dy=%0d btn=%b", n_ev, $signed(ev_dx), $signed(ev_dy), ev_btn); if ((exp_dx != 999) && ($signed(ev_dx) != exp_dx)) begin $display(" [kbd] FAIL: 期望 dx=%0d", exp_dx); ok = 0; end if ((exp_dy != 999) && ($signed(ev_dy) != exp_dy)) begin $display(" [kbd] FAIL: 期望 dy=%0d", exp_dy); ok = 0; end if ((exp_btn != 999) && (ev_btn != exp_btn)) begin $display(" [kbd] FAIL: 期望 btn=%b", exp_btn); ok = 0; end end end initial begin $display("[kbd] === PS/2 keyboard decode test ==="); #2000; reset = 1'b0; #2000; // 校验错的字节(0x74)不能被当成"→"键 $display("[kbd] 校验位错的字节应该被丢掉"); exp_dx = 999; exp_dy = 999; exp_btn = 999; send_byte_bad_parity(8'h74); #(100_000); if (n_ev != 0) begin $display(" [kbd] FAIL: 校验错的帧不该产生事件"); ok = 0; end $display("[kbd] 只发松开码 F0 75(不该有事件)"); send_byte(8'hF0); send_byte(8'h75); #(100_000); if (n_ev != 0) begin $display(" [kbd] FAIL: 松开码不该产生事件"); ok = 0; end $display("[kbd] → 键(E0 74)期望 dx=+8"); exp_dx = 8; exp_dy = 0; exp_btn = 0; send_byte(8'hE0); send_byte(8'h74); #(100_000); $display("[kbd] ↑ 键(E0 75)期望 dy=-8"); exp_dx = 0; exp_dy = -8; exp_btn = 0; send_byte(8'hE0); send_byte(8'h75); #(100_000); $display("[kbd] Enter(5A)期望 btn=00001"); exp_dx = 0; exp_dy = 0; exp_btn = 5'b00001; send_byte(8'h5A); #(100_000); $display("[kbd] 左 Shift(12)期望 btn=00010(软件里 = 插旗)"); exp_dx = 0; exp_dy = 0; exp_btn = 5'b00010; send_byte(8'h12); #(100_000); $display("[kbd] rx_count=%0d events=%0d", rx_count, n_ev); if ((ok == 0) || (n_ev != 4)) $display("[kbd] === FAIL ==="); else $display("[kbd] === ALL PASS ==="); $finish; end endmodule