上板实测:鼠标已能操作(说明“奇校验 + 帧按位的下标对齐”那个修复就是根因),
但上下是反的、左右正确 —— 这是两套约定的差异,不是解码错:
* PS/2 鼠标原生约定:dx 正 = 右、**dy 正 = 上**
* saolei.c 的约定:正 dx = 列 +1、**正 dy = 行 +1(向下)**,和 cpu21_pointer 的
BTND = +8 一致
所以 cpu21_ps2_mouse 在拼包时把 dy 取负(dx 不动),三种事件源(板载按键 / PS/2 鼠标 /
PS/2 键盘)方向统一。
键盘:另加 W/A/S/D = 方向键(与 tools/play_saolei.tcl 里的别名保持一致),这样用普通
字母键也能测。
自测同步更新(都 ALL PASS):
* tb_ps2_mouse:改发“鼠标向上”的 dy=+8,事件里必须得到 dy=-8;向下 dy=-5 -> +5
* tb_ps2_kbd:新增 W(1D) -> dy=-8 用例
170 lines
5.2 KiB
Verilog
170 lines
5.2 KiB
Verilog
`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(插旗);
|
||
// 发 W(1D)应产生 dy=-8(W/A/S/D 别名);
|
||
// 松开码(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] W 键(1D)期望 dy=-8(W/A/S/D 是方向键别名)");
|
||
exp_dx = 0; exp_dy = -8; exp_btn = 0;
|
||
send_byte(8'h1D);
|
||
#(100_000);
|
||
|
||
$display("[kbd] rx_count=%0d events=%0d", rx_count, n_ev);
|
||
if ((ok == 0) || (n_ev != 5))
|
||
$display("[kbd] === FAIL ===");
|
||
else
|
||
$display("[kbd] === ALL PASS ===");
|
||
$finish;
|
||
end
|
||
|
||
endmodule
|