ps2_decoder¶
Source: Verilog/Terminals/rtl/ps2_decoder.v
Hierarchy: not instantiated by any of the 9 build tops (elaborated by yosys).
Module hierarchy - All modules

Schematic¶
Drawn from the Verilog: no build top uses this module, so it was elaborated from its own file with no defines and default parameters. Sub-modules are boxes (click the picture to open it full size; there every sub-module box links to its page, and every wire shows its Verilog name).
Ports¶
| Direction | Width | Name | Description |
|---|---|---|---|
| input | 1 |
clk |
|
| input | 1 |
rst_n (active low) |
async reset, active low |
| input | 1 |
code_valid |
strobe, one clock |
| input | [7:0] |
code_data |
the scancode itself |
| input | 1 |
code_release |
this was a key RELEASE (F0 seen) |
| input | 1 |
code_extended |
this had the E0 prefix |
| input | 1 |
layout_no |
|
| output | 1 |
ascii_valid |
one clock per character produced |
| output | [7:0] |
ascii_data |
|
| output | 1 |
shift_active |
|
| output | 1 |
ctrl_active |
|
| output | 1 |
caps_active |
Verilog source¶
Verilog/Terminals/rtl/ps2_decoder.v on GitHub.
Show the Verilog of ps2_decoder (148 lines)
//============================================================================
//! PS/2 scancode stream -> terminal bytes. Modifier state and lookup only.
//!
//! Part of the board-independent terminal core (Verilog/Terminals/).
//!
//! WHY THIS IS A SEPARATE MODULE (28-AUG-2026). Two of our boards produce
//! already-decoded scancodes and one produces a raw serial line:
//!
//! Nexys 4 DDR : the onboard microcontroller presents USB HID as a plain
//! PS/2 clock/data pair, so `ps2_keyboard.v` has to do the
//! 11-bit framing itself and then feeds this module.
//! MiSTer : hps_io hands over `ps2_key = {toggle, pressed, extended,
//! code[7:0]}` - Linux has already done the USB work and the
//! framing. There is no serial line to decode; the glue just
//! turns the toggle into a strobe and drives this module.
//! MEGA65 : the keyboard is a matrix behind a CPLD, different again.
//!
//! Splitting the framing from the decoding means the part with the ACTUAL
//! logic in it - modifiers, caps lock, control characters, the TDV cursor
//! keys - exists once and is shared. A keyboard bug found on one board is
//! then fixed on all of them, which is not true if each board carries its own
//! copy.
//!
//! The scancode table's data is cross-checked against two independent
//! published set-2 references (see ps2_ascii_table.v's header, 30-AUG-2026);
//! this module's LOGIC is tested by ps2_keyboard_tb.v through the serial
//! front end.
//!
//! Written 28-AUG-2026.
//============================================================================
`default_nettype none
module ps2_decoder (
input wire clk,
input wire rst_n, //! async reset, active low
//! One scancode, already framed and stripped of its E0/F0 prefixes.
input wire code_valid, //! strobe, one clock
input wire [7:0] code_data, //! the scancode itself
input wire code_release, //! this was a key RELEASE (F0 seen)
input wire code_extended, //! this had the E0 prefix
//! 0 = US ANSI, 1 = Norwegian. Changes both which key carries a character
//! and which ISO 646 byte it sends - see ps2_ascii_table.v. The SCREEN must
//! be switched with the same bit or a typed AE draws as '['.
input wire layout_no,
output reg ascii_valid, //! one clock per character produced
output reg [7:0] ascii_data,
//! Modifier state, exposed because a board may want to light a lamp.
output wire shift_active,
output wire ctrl_active,
output wire caps_active
);
localparam [7:0] SC_LSHIFT = 8'h12;
localparam [7:0] SC_RSHIFT = 8'h59;
localparam [7:0] SC_CTRL = 8'h14;
localparam [7:0] SC_CAPS = 8'h58;
reg s_lshift, s_rshift, s_ctrl, s_caps;
assign shift_active = s_lshift | s_rshift;
assign ctrl_active = s_ctrl;
assign caps_active = s_caps;
wire s_shifted = s_lshift | s_rshift;
wire [7:0] s_ascii_unshifted;
wire [7:0] s_ascii_shifted;
wire [7:0] s_ascii_extended;
ps2_ascii_table TABLE (
.code (code_data),
.layout_no(layout_no),
.unshifted(s_ascii_unshifted),
.shifted (s_ascii_shifted),
.extended (s_ascii_extended)
);
//! Caps lock affects letters only - not digits, not punctuation. Getting
//! this wrong turns SHIFT+2 into a different character with caps on, which
//! is the classic caps-lock bug.
wire s_is_letter = (s_ascii_unshifted >= "a") && (s_ascii_unshifted <= "z");
wire s_use_shifted = s_shifted ^ (s_caps && s_is_letter);
wire [7:0] s_ascii_plain = s_use_shifted ? s_ascii_shifted : s_ascii_unshifted;
//! Control characters: CTRL + @A-Z[\]^_ gives 0x00-0x1F. Applied to the
//! UNSHIFTED letter so ctrl-C and ctrl-shift-C are the same, as on a real
//! terminal.
wire [7:0] s_ascii_ctrl = {3'b000, s_ascii_unshifted[4:0]};
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
s_lshift <= 1'b0;
s_rshift <= 1'b0;
s_ctrl <= 1'b0;
s_caps <= 1'b0;
ascii_valid <= 1'b0;
ascii_data <= 8'h00;
end else begin
ascii_valid <= 1'b0;
if (code_valid) begin
case (code_data)
SC_LSHIFT: s_lshift <= !code_release;
SC_RSHIFT: s_rshift <= !code_release;
SC_CTRL: s_ctrl <= !code_release;
//! Caps lock toggles on PRESS only - toggling on the release too
//! would cancel every press.
SC_CAPS: if (!code_release) s_caps <= !s_caps;
default: begin
// A character comes out on PRESS only.
//
// Extended (E0-prefixed) keys go through their own table: on a
// TDV the arrows and HOME are bare C0 bytes, not escape
// sequences, so they need no sequencer - just a different lookup.
// An extended key with no TDV equivalent sends nothing,
// deliberately (no VT100 fallback).
if (!code_release) begin
if (code_extended) begin
if (s_ascii_extended != 8'h00) begin
ascii_valid <= 1'b1;
ascii_data <= s_ascii_extended;
end
end else if (s_ascii_plain != 8'h00) begin
ascii_valid <= 1'b1;
// A sequence MARKER (bit 7) is never ctrl-masked: there is
// no Ctrl-F1 or Ctrl-arrow encoding on a VT100, so the
// modifier is simply ignored and the base sequence goes out
// (SPEC-vt100-keys.md, "Modifiers").
ascii_data <= (s_ctrl && !s_ascii_plain[7]) ? s_ascii_ctrl
: s_ascii_plain;
end
end
end
endcase
end
end
end
endmodule
`default_nettype wire