Practice
Workshop PS2 Keyboard
Marlow Toolroom still runs its CNC console from an old PS/2 keyboard with a space bar worn smooth. Operators use it to send jog commands while setting up each job.
The keyboard sends each bit on two slow wires and supplies its own clock.
One winter morning, a damaged frame passed through the console reader without a check. The bad scan code was read as "jog left, fast."
The steel table struck its end stop, and coolant sloshed from the tray before the operator could react.
Your task: build a PS/2 receiver that checks each start bit, parity bit, and stop bit. Quietly discard broken or incomplete frames. Only a complete, correct frame may update the saved scan code.
Interface
| Port | Direction | Type | Description |
|---|---|---|---|
clk_i | in | 1 bit | Console system clock, much faster than the keyboard clock |
rst_i | in | 1 bit | Synchronous reset (active high) |
ps2_clk_i | in | 1 bit | Keyboard clock line |
ps2_data_i | in | 1 bit | Keyboard data line |
scan_o | out | 8-bit vector | Last good scan code, held |
valid_o | out | 1 bit | One-clock pulse for each good frame |
parity_err_o | out | 1 bit | One-clock pulse on a parity failure |
frame_err_o | out | 1 bit | One-clock pulse on a bad start, bad stop, or mid-frame timeout |
Behavior
- Each PS/2 input passes through its own two-flop synchronizer before any receiver logic examines it
- Data is captured on each detected falling edge of the synchronized
ps2_clk_i - Each frame contains exactly 11 captured bits
- The first bit is the start bit, and it must be 0
- A bad start bit makes
frame_err_opulse on the clock that detects its falling edge - A bad start bit is not treated as a frame, and the receiver remains idle
- The next eight bits form the scan code, least significant bit first
- The tenth bit is the odd-parity bit
- The eight data bits and parity bit together must contain an odd number of ones
- The eleventh bit is the stop bit, and it must be 1
- For a good frame,
scan_oupdates one clock after the stop bit's captured edge valid_opulses on that same update clock- For a completed frame with bad parity,
parity_err_opulses instead, andscan_odoes not change - A bad stop bit makes
frame_err_opulse, andscan_odoes not change - If 128 system clocks pass mid-frame without the next captured falling edge,
frame_err_opulses - A timeout discards the partial frame and returns the receiver to idle
valid_o,parity_err_o, andframe_err_oare one-clock pulses and never overlapscan_oholds its last good value through every failed or incomplete framerst_ihigh at a rising edge clears all outputs, clearsscan_oto zero, and returns the receiver to idle- All outputs are registers
What the bench checks
- The testbench drives keyboard lines with irregular but legal timing and checks every output on every clock against a model with both synchronizer chains
- It sends good frames whose codes reveal the least significant bit first order
- It completes a frame with bad parity and checks a
parity_err_opulse withscan_ountouched - It sends a bad stop bit and a bad start bit and checks the right
frame_err_opulse for each - It silences the keyboard mid-frame and checks one
frame_err_opulse after 128 quiet clocks, with the partial frame discarded - It checks that
scan_oholds its last good value through every failure and updates only onvalid_o - It resets while idle and mid-frame and checks cleared outputs and an idle receiver
Constraints
TIMING: receiver behavior is synchronous to
clk_iafter two-flop input synchronization. Keyboard edges arrive far slower thanclk_iin this bench. All outputs are registered.
SCOPE: the two-flop chain is the required discipline for a foreign clock line. An RTL run does not prove metastability safety.
Do not add ports.
Click Run to execute your code. Output will appear here.