`timescale 1ns / 1ps // ============================================================================= // tb_ps2_mouse —— 单独验证 PS/2 鼠标接收逻辑(不跑 CPU,几秒出结果) // // 模拟 PS/2 鼠标发 3 字节包: // byte0 = 标志字节(bit3 必须为 1 才是有效的鼠标数据;bit0/1/2 = 左/右/中键) // byte1 = dx(有符号) byte2 = dy(有符号) // 检查 cpu21_ps2_mouse 是否给出正确的 ev_valid / ev_dx / ev_dy / ev_btn。 // // PS/2 时序:DUT 在 ps2_clk 的**下降沿**采样 ps2_data,所以这里先把数据摆好, // 再拉低时钟。一帧 11 位 = 起始 0 + 8 位数据(LSB 先) + **奇校验** + 停止 1。 // 奇校验 = 校验位让 data + parity 共 9 位里 1 的个数为奇数(真设备就是这么发的)。 // // 方向约定:PS/2 的 **dx 正 = 右、dy 正 = 上**(真鼠标往上推,byte2 是正数); // 而软件(saolei.c / cpu21_pointer)里 **dy 正 = 向下(行号增加)**, // 所以 cpu21_ps2_mouse 上报前把 dy 取负 —— 本测试就是盯这个约定: // 发“向上”的 dy,事件里必须得到负的 dy。 // // 跑法(Vivado 的 xvlog/xelab/xsim 或其它仿真器都行): // xvlog srcs/rtl/cpu21_ps2_mouse.v srcs/tb/tb_ps2_mouse.v // xelab -top tb_ps2_mouse -snapshot ps2sim // xsim ps2sim -R // ============================================================================= module tb_ps2_mouse; 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; wire [7:0] ev_dy; wire [4:0] ev_btn; wire [15:0] rx_count; always #10 clk = ~clk; // 50 MHz,和板上 CPU 主频一致 cpu21_ps2_mouse dut ( .clk (clk), .reset (reset), .ps2_clk_i (ps2_clk), .ps2_clk_oe_o (), .ps2_data_i (ps2_data), .ps2_data_oe_o(), .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; // 奇校验:data + parity 里 1 的个数为奇数 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; always @(posedge clk) begin if (ev_valid) begin n_ev = n_ev + 1; $display(" [ps2] event #%0d: dx=%0d dy=%0d btn=%b", n_ev, $signed(ev_dx), $signed(ev_dy), ev_btn); if (n_ev == 1) begin if (($signed(ev_dx) != 8) || ($signed(ev_dy) != -8) || (ev_btn != 5'b00000)) begin $display(" [ps2] FAIL: 第 1 包应为 dx=+8 dy=-8 btn=00000(鼠标向上 -> 事件 dy 为负)"); ok = 0; end end if (n_ev == 2) begin if (($signed(ev_dx) != -3) || ($signed(ev_dy) != 5) || (ev_btn[0] != 1'b1)) begin $display(" [ps2] FAIL: 第 2 包应为 dx=-3 dy=+5 btn=xxxx1(鼠标向下 -> 事件 dy 为正)"); ok = 0; end end end end integer rx_after_bad; initial begin $display("[ps2] === PS/2 mouse receiver test ==="); #2000; reset = 1'b0; #2000; $display("[ps2] 校验位错的字节应该被丢掉"); send_byte_bad_parity(8'h08); #(100_000); if (rx_count != 16'd0) begin $display(" [ps2] FAIL: 校验错的帧不该被计数(rx_count=%0d)", rx_count); ok = 0; end rx_after_bad = rx_count; $display("[ps2] packet 1: flag=0x08(no button) dx=+8 dy=+8(鼠标向右上移)"); send_byte(8'h08); send_byte(8'h08); send_byte(8'h08); #(100_000); $display("[ps2] packet 2: flag=0x09(left) dx=-3 dy=-5(向左下移)"); send_byte(8'h09); send_byte(8'hFD); send_byte(8'hFB); #(100_000); $display("[ps2] rx_count=%0d events=%0d", rx_count, n_ev); if ((rx_count != rx_after_bad + 16'd6) || (n_ev != 2) || (ok == 0)) $display("[ps2] === FAIL ==="); else $display("[ps2] === ALL PASS ==="); $finish; end endmodule