Files
risc-v-cpu/pipelined-cpu/srcs/rtl/cpu21_ps2_mouse.v
T
Alpha 4e3525ffe2 fix(saolei): 修复被文本工具改坏的 RTL —— 这是综合失败的直接原因
## 现象(Vivado 2020.2 综合日志)

`synth_design -top cpu21_board_top -part xc7a100tcsg324-1` 报 13 个
`ERROR: [Synth 8-2715] syntax error`,指向:
- `cpu21_pointer.v:106`、`cpu21_board_top.v:308`、`cpu21_vga.v:242`
  —— `non-printable character with the hex value '0xe4'`(UTF-8 中文字的首字节)
- `cpu21_board_top.v:139` —— `syntax error near "="`
- `cpu21_ps2_mouse.v:45 / 47 / 145` —— `0xe3`、`syntax error near "ta_s1"`
- `cpu21_board_rom.vh:3...` —— 一连串 `syntax error near "="`(前一处错误的级联)

## 根因

**不是 RTL 设计问题,是工作区文件被改坏**(编辑器/格式化类的文本改动把 README 的
片段插进了 Verilog 文件里):

1. `cpu21_pointer.v`、`cpu21_vga.v`、`cpu21_ps2_mouse.v` 文件末尾被追加了一行
   来自 README 的 `Tcl 一键重建`(于是合成器在 `endmodule` 之后遇到中文字节);
2. `cpu21_board_top.v` 里被插入了同样的残行,**顶掉了 VGA 例化的 `.vga_hs` 那一行**;
3. `cpu21_board_top.v` 的 ROM 段被吞掉一整块:
   `reg [31:0] rom [...]` / `integer i;` / `initial begin` 全部消失,
   只剩下 `for (i = 0; ...) rom[i] = ...;` —— 这也正是 `cpu21_board_rom.vh`
   里 `syntax error near "="` 的由来(`initial` 块没了,`rom[i]=...` 成了非法语句);
4. `cpu21_ps2_mouse.v` 的 `reg ps2_clk_s1, ps2_clk_s2, ps2_data_s1, ps2_data_s2;`
   声明被拼进了 `assign ps2_data_oe_o = 1'b0` 里(变成
   `assign ps2_data_oe_o = 1'b02_data_s1, ps2_data_s2;`),注释残片
   `。本模块只接收,两条线都不驱动。` 夹在中间。

## 修复

- 删除 3 处 README 残行,恢复 `cpu21_board_top.v` 的 `.vga_hs` 端口连接;
- 补齐 `cpu21_board_top.v` ROM 段的 `reg/integer/initial begin`;
- 恢复 `cpu21_ps2_mouse.v` 被拼掉的 `reg` 声明,并整理开漏赋值的注释;
- 全量扫描 `srcs/**` 确认:无残留 README 文本碎片、`module/endmodule` 与
  `` `ifdef/`endif `` 配对正常、四个受损文件的关键结构与例化(ROM/RAM/CPU/VGA/
  数码管/按键、16 个字形表、PS2 状态机、按键去抖)均完整。

## 状态

- 本次只改 RTL 文本,未改任何逻辑/端口/地址。
- 本机没有 Vivado / xsim / iverilog,**无法在本地复验语法**,需要重新跑一次综合确认。
2026-09-18 15:58:13 +08:00

146 lines
6.2 KiB
Verilog
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// cpu21_ps2_mouse —— 可选的真鼠标输入:PS/2 鼠标(Digilent Pmod PS2 插在 JA 上)。
//
// JA[1] = PS/2 DATA,JA[2] = PS/2 CLK(两条线都需要上拉,见 XDC 里的 PULLUP)。
//
// 注意:本模块没有在真实硬件上验证过(作者手头没有 PS/2 鼠标),如果你不用真
// 鼠标,请让顶层用 cpu21_pointer(5 向按键)作为事件源即可,C 程序完全不用改。
// 板级顶层 cpu21_board_top 用参数 USE_PS2_MOUSE 选择这两者。
//
// 实现内容:
// 1) 接收:在 ps2_clk 的下降沿采样 ps2_data,收满 11 位(起始 0 + 8 数据
// LSB 先 + 奇校验 + 停止 1)后校验并拼成 3 字节一包;
// 2) 解析:byte0 = 标志(bit0 左键、bit1 右键、bit3 固定 1),byte1 = dx,
// byte2 = dy,bit7 为符号位;
// 3) 上报:当 dx/dy 非 0 或按键状态变化时产生一个事件(单位与
// cpu21_pointer 相同,即原始计数 = 1/8 格);
// 4) 本模块只做"接收",不实现主机→鼠标的命令发送(0xF4 使能上报)。多数
// PS/2 鼠标上电后就会主动上报,这类鼠标可以直接用;如果你的鼠标上电毫无
// 反应,说明它需要主机先发 0xF4 —— 那部分需要自行补上,或者直接用默认的
// 5 向按键(cpu21_pointer),C 程序完全不用改。
// -----------------------------------------------------------------------------
module cpu21_ps2_mouse (
input wire clk,
input wire reset,
// PS/2 双向线(开漏)
input wire ps2_clk_i,
output wire ps2_clk_oe_o,
input wire ps2_data_i,
output wire ps2_data_oe_o,
// 事件上报
output reg ev_valid_o,
output reg [7:0] ev_dx_o,
output reg [7:0] ev_dy_o,
output reg [4:0] ev_btn_o,
// 调试:收到的字节数(可选观察)
output reg [15:0] rx_count_o
);
// 开漏:oe = 1 时把线拉低,否则高阻(外部上拉)。本模块只接收,两条线都不驱动。
assign ps2_clk_oe_o = 1'b0;
assign ps2_data_oe_o = 1'b0;
// 两级同步
reg ps2_clk_s1, ps2_clk_s2, ps2_data_s1, ps2_data_s2;
always @(posedge clk or posedge reset) begin
if (reset) begin
ps2_clk_s1 <= 1'b1;
ps2_clk_s2 <= 1'b1;
ps2_data_s1 <= 1'b1;
ps2_data_s2 <= 1'b1;
end
else begin
ps2_clk_s1 <= ps2_clk_i;
ps2_clk_s2 <= ps2_clk_s1;
ps2_data_s1 <= ps2_data_i;
ps2_data_s2 <= ps2_data_s1;
end
end
wire clk_fall = ~ps2_clk_s1 & ps2_clk_s2; // 同步后的下降沿
wire clk_rise = ps2_clk_s1 & ~ps2_clk_s2;
// ---------------------------------------------------------------------------
// 接收移位:11 位一帧,bit0 = 起始位,bit1..8 = 数据,bit9 = 校验,bit10 = 停止
// ---------------------------------------------------------------------------
reg [3:0] bit_cnt_q;
reg [10:0] frame_q;
reg frame_ok_q;
reg [23:0] pkt_q; // 3 字节包
reg [1:0] pkt_cnt_q;
reg [7:0] byte_cnt_q; // 一包内的字节序号(0..2)
reg [7:0] dx_q, dy_q;
reg [4:0] btn_q; // {4'b0, middle, right, left} -> 只取低 3 位使用
// 校验:11 位中 1 的个数应为奇数(奇校验)
function automatic odd_parity_ok(input [10:0] f);
odd_parity_ok = ^{f[9:1]}; // data + parity 共 9 位,1 的个数应为偶数
endfunction
always @(posedge clk or posedge reset) begin
if (reset) begin
bit_cnt_q <= 4'd0;
frame_q <= 11'd0;
frame_ok_q <= 1'b0;
byte_cnt_q <= 8'd0;
pkt_q <= 24'd0;
rx_count_o <= 16'd0;
ev_valid_o <= 1'b0;
end
else begin
ev_valid_o <= 1'b0;
if (clk_fall) begin
if (bit_cnt_q == 4'd0 && ps2_data_s2 == 1'b0) begin
// 起始位有效,开始收
frame_q <= {ps2_data_s2, frame_q[10:1]};
bit_cnt_q <= 4'd1;
end
else if (bit_cnt_q != 4'd0) begin
frame_q <= {ps2_data_s2, frame_q[10:1]};
if (bit_cnt_q == 4'd10) begin
bit_cnt_q <= 4'd0;
// f = {停止, 校验, 8 数据, 起始},这里只关心数据与校验
if (odd_parity_ok(frame_q) && (frame_q[10] == 1'b1)) begin
rx_count_o <= rx_count_o + 16'd1;
case (byte_cnt_q)
8'd0: begin
// 标志字节:bit3 必须为 1(否则是鼠标发来的其他响应,丢弃)
if (frame_q[8:1][3] == 1'b1) begin
pkt_q[7:0] <= frame_q[8:1];
byte_cnt_q <= 8'd1;
end
end
8'd1: begin
pkt_q[15:8] <= frame_q[8:1]; // dx
byte_cnt_q <= 8'd2;
end
8'd2: begin
byte_cnt_q <= 8'd0;
ev_dx_o <= frame_q[8:1]; // dy
ev_dy_o <= pkt_q[15:8]; // 注意:byte1 是 dx
ev_btn_o <= {2'b0, pkt_q[2], pkt_q[1]}; // {middle, right, left}
ev_valid_o <= 1'b1;
end
default:
byte_cnt_q <= 8'd0;
endcase
end
end
else begin
bit_cnt_q <= bit_cnt_q + 4'd1;
end
end
end
end
end
endmodule
`default_nettype wire
Tcl 一键重建