diff --git a/demo/fpga/src/edge_cleaner.v b/demo/fpga/src/edge_cleaner.v
index c543618..8bd23ee 100644
--- a/demo/fpga/src/edge_cleaner.v
+++ b/demo/fpga/src/edge_cleaner.v
@@ -1,3 +1,8 @@
+
+/* module for cleaning up signal edges of off-chip SPI interface.
+*
+* This works by toggling "out" only after a sequence of 8 consecutive ones or zeros on "in" at system clock (100 MHz).
+*/
module edge_cleaner(
input clk,
input in,
@@ -16,4 +21,4 @@ always @(posedge clk) begin
end
end
-endmodule
\ No newline at end of file
+endmodule
diff --git a/demo/fpga/src/spi_regfile.v b/demo/fpga/src/spi_regfile.v
index 0fbc2f6..de5b185 100644
--- a/demo/fpga/src/spi_regfile.v
+++ b/demo/fpga/src/spi_regfile.v
@@ -1,14 +1,18 @@
`timescale 1ns / 1ps
+/* SPI register file for getting/setting config and data registers through SPI */
module spi_regfile(
input clk, rst,
+ /* externally-facing SPI interface */
input sck, sdi, ncs,
output reg sdo,
+ /* Data I/O for transferring stream payload data (e.g. encrypted window content or terminal data) */
input [7:0] spi_data_in,
output reg [7:0] spi_data_out,
+ /* Register-mapped values */
input [7:0] spi_status_word,
output reg [7:0] spi_cmd_word,
output reg spi_cmd_begin,
@@ -27,6 +31,7 @@ reg load_data;
assign rxbuf_dbg = rxbuf;
+/* Clean up signal edges */
wire sck_clean, sdi_clean, ncs_clean;
edge_cleaner sck_cleaner (.clk(clk), .in(sck), .out(sck_clean));
edge_cleaner sdi_cleaner (.clk(clk), .in(sdi), .out(sdi_clean));
diff --git a/demo/fpga/src/term_emu.v b/demo/fpga/src/term_emu.v
index 1b820da..8c3bdc3 100644
--- a/demo/fpga/src/term_emu.v
+++ b/demo/fpga/src/term_emu.v
@@ -1,5 +1,19 @@
`timescale 1ns / 1ps
+/* Terminal emulator
+ *
+ * This module receives a stream of ASCII bytes containing both printable ASCII escape characters and VT-100 sequences.
+ * It processes all escape sequences and renders the resulting glyphs into a fixed-size glyph buffer. For each (x, y)
+ * screen cell index this glyph buffer contains the glyph in that cell as well as styling information for this glyph
+ * such as color or underline. The glyph buffer is accessed in a random-access pattern since the input data might
+ * contain cursor movement escape sequences. Also, not every input byte corresponds to one output glyph since escape
+ * sequences only change the term_emu's internal state, but do not cause any direct output.
+ *
+ * term_emu handles newlines to keep track of the current cursor position. Additionally, newlines are forwarded to the
+ * output glyph buffer. The semantic here is that when rendering a pixel line, the terminal renderer "skips" all entries
+ * of the glyph buffer after it has seen the first newline "glyph" in that row of glyphs.
+ *
+ */
module term_emu(
input clk, rst,
@@ -12,40 +26,89 @@ module term_emu(
output reg [19:0] glyph_buffer_w_data
);
+/* Fixed size of (external) glyph memory. Can be passed in from the outside, too, in case a different size is used.
+ * This has to be known for the glyph buffer to prevent out-of-bounds accesses. */
parameter GLYPHMEM_W = 256; /* glyphs */
parameter GLYPHMEM_H = 128; /* glyphs */
-localparam ST_PARSE_TEXT = 9'b000000001,
- ST_PARSE_ESC = 9'b000000010,
- ST_PARSE_CSI = 9'b000000100,
- ST_PARSE_SEP = 9'b000100000,
- ST_PARSE_INVAL = 9'b010000000;
+/* Escape sequence parser states
+ *
+ * Remember: An escape sequence usually looks like this: "\033[91;4m"
+ *
+ * "\033" is the ASCII char 0o33 (or 0x1b hex), the "escape" char.
+ * "\033[" is the two-char sequence called "control sequence introducer" (CSI)
+ * "91" is an SGR parameter setting the foreground color to bright red
+ * ";" separates two parameters
+ * "4" is an SGR parameter enabling underline
+ * "m" says that the preceding parameters are part of an "set graphic rendition" (SGR) command. Other letters mean other
+ * commands, which assign different meanings to the preceding numbers (e.g. for cursor movement).
+ *
+ * Read: this is kind of equivalent to setting:
+ *
+ * terminal state = csi_m(91, 4)
+ *
+ */
+localparam ST_PARSE_TEXT = 9'b000000001, /* Regular text, convert input character to glyph */
+ ST_PARSE_ESC = 9'b000000010, /* Found \e a.k.a. \033 a.k.a. \x1b escape char*/
+ ST_PARSE_CSI = 9'b000000100, /* Found CSI (control sequence introducer), i.e. "\e["
+ ST_PARSE_SEP = 9'b000100000, /* Found separator between control sequence "parameters" (i.e. numbers). */
+ ST_PARSE_INVAL = 9'b010000000; /* Escape sequence parsing error. Disables processing until next [a-zA-Z]
+ found. All escape sequences end with a letter and cannot contain any
+ letter inside their parameters. */
wire [15:0] glyph_buffer_w_addr_comp = (GLYPHMEM_W*glyph_y) + glyph_x;
-reg [8:0] parser_state;
-reg [3:0] cur_fg;
-reg [3:0] cur_bg;
+/* current graphical state */
+reg [3:0] cur_fg; /* foreground color from 4-bit palette */
+reg [3:0] cur_bg; /* background color from 4-bit palette */
reg cur_bold;
reg cur_underline;
+/* cursor x/y position */
+reg [9:0] glyph_x;
+reg [9:0] glyph_y;
+
+/* escape sequence parser state */
+reg [8:0] parser_state;
+
+/* Buffer for received CSI parameters, i.e. the numbers between "\033[" and the trailing letter "m".
+ * Numbers are saved in BCD format since in many cases we are not interested in their numerical value anyway.
+ * We set a fixed maximum size here since most escape codes (for cursor movement and such) only need one or two anyway,
+ * and the SGR escape sequence (to set colors, underline etc.) will never need more than this unless you do something
+ * dumb. */
localparam NUM_BUF_SZ = 8;
+/* format: {abc def ghi} with each letter being one BCD digit, and each group corresponding to one parameter number.
+ * e.g. receiving \033[91;4m would lead to num_buf = {000 000 000 091 004}. 0xf is used to mark empty digits. */
reg [NUM_BUF_SZ*12-1:0] num_buf;
wire [11:0] num_buf_last;
assign num_buf_last = num_buf[NUM_BUF_SZ*12-1:NUM_BUF_SZ*12-1-11];
+
+/* Register for the "action" letter at the end of the escape code, after the parameter list. e.g. 'm' for SGR. */
reg [7:0] parser_action;
+/* Did we get a valid escape sequence? */
reg parser_valid;
+/* Index of the entry in num_buf (above) that we are currently processing */
reg [4:0] num_buf_idx;
-reg [9:0] glyph_x;
-reg [9:0] glyph_y;
+/* Counters for cursor movement. These counters are in BCD because that seemed simpler than converting decimal to
+ * binary.*/
reg [11:0] csi_act_ctr;
reg cursor_movement_x_pos;
reg cursor_movement_x_neg;
reg cursor_movement_y_pos;
reg cursor_movement_y_neg;
+/* Did we get an unsupported/invalid action letter in parser_action? */
reg esc_inval;
+/* Are we parsing the first digit of a parameter number now? */
reg num_start;
+/* The parser/renderer logic works in two stages.
+ *
+ * => First, when an input byte is available, receive it and update the parser state.
+ * => Second, when the parser state indicates that an escape sequence was received, process it.
+ * This processing can take several clock cycles. E.g. cursor movement is currently handled by simply incrementing
+ * until the given offset has been reached. This was done to avoid decimal/binary conversion and because this parser
+ * runs at a *much* higher clock than the input data is arriving at anyway.
+ */
always @(posedge clk) begin
in_byte_ack <= 0;
glyph_buffer_w_valid <= 0;
@@ -74,13 +137,17 @@ always @(posedge clk) begin
in_byte_ack <= 1;
case (parser_state)
+ /* We're currently parsing normal text. */
(ST_PARSE_TEXT): begin
if (in_byte == 8'h1b) begin /* \e */
+ /* Start of escape sequence */
parser_state <= ST_PARSE_ESC;
end else if (in_byte == 8'h0a) begin /* \n */
+ /* newline. Forward newline character to glyph buffer, update cursor. */
glyph_x <= 0;
glyph_buffer_w_valid <= 1;
+ /* Send "zero" graphic state to glyph buffer for newline char */
glyph_buffer_w_data <= {1'b0, 1'b0, 2'b00, cur_bg, 4'b0000, in_byte};
glyph_buffer_w_addr <= glyph_buffer_w_addr_comp;
@@ -89,6 +156,8 @@ always @(posedge clk) begin
end
end else begin
+ /* Normal character. Just forward to glyph buffer, update cursor.
+ * NOTE: we do not wrap lines. */
glyph_buffer_w_valid <= 1;
glyph_buffer_w_data <= {cur_underline, cur_bold, 2'b00, cur_bg, cur_fg, in_byte};
glyph_buffer_w_addr <= glyph_buffer_w_addr_comp;
@@ -98,6 +167,7 @@ always @(posedge clk) begin
end
end
+ /* We received the escape char "\e" last cycle, now we should get "[" to form an CSI. */
(ST_PARSE_ESC): begin
if (in_byte == 8'h5b) begin /* [ */
parser_state <= ST_PARSE_CSI;
@@ -105,28 +175,40 @@ always @(posedge clk) begin
num_start <= 1;
end else begin
+ /* Bad/unsupported escape sequence, ignore everything from now on until next letter [a-zA-Z]. */
parser_state <= ST_PARSE_INVAL;
end
end
- (ST_PARSE_CSI), (ST_PARSE_SEP): begin
+ /* We are parsing a parameter number, e.g. after an "\e[" in the last two cycles, or a parameter separator ";".
+ * Note that this state repeats for each digit of the number. Since we read the number into num_buf as bcd, we
+ * just shift it in from the right. num_start is used to re-set num_buf on the first digit of a new parameter
+ * (e.g. after a ";"). */
+ (ST_PARSE_CSI), (ST_PARSE_SEP): begin
if (in_byte >= 8'h30 && in_byte <= 8'h39) begin /* 0-9 */
+ /* Valid decimal digit received */
if (num_start) begin
num_start <= 0;
num_buf <= {num_buf[NUM_BUF_SZ*12-1:12], 8'hff, ~in_byte[3:0]};
end else begin
num_buf <= {num_buf[NUM_BUF_SZ*12-1:12], num_buf[7:0], ~in_byte[3:0]};
end
+ /* Continue in same state! */
parser_state = ST_PARSE_CSI;
end else if (in_byte == 8'h3b) begin /* ; */
- num_buf <= {num_buf[NUM_BUF_SZ*12-1-12:0], 12'hfff};
+ /* Separator received */
+ num_buf <= {num_buf[NUM_BUF_SZ*12-1-12:0], 12'hfff}; /* shift num_buf by one parameter size */
parser_state <= ST_PARSE_SEP;
num_start <= 1;
end else if (in_byte >= 8'h40 && in_byte <= 8'h7e) begin
+ /* action character (e.g. "m" for SGR) received => end of escape sequence. */
+ /* store action for processing */
parser_action <= in_byte;
+ /* kick off processing */
parser_valid <= 1;
+ /* reset processing logic */
num_buf_idx <= 0;
esc_inval <= 0;
csi_act_ctr <= 0;
@@ -134,22 +216,32 @@ always @(posedge clk) begin
cursor_movement_x_neg <= 0;
cursor_movement_y_pos <= 0;
cursor_movement_y_neg <= 0;
+ /* return parser to normal text state. Processing will have finished by the time the next character
+ * arrives. */
parser_state <= ST_PARSE_TEXT;
end else begin
+ /* Bad char received as part of escape sequence. Ignore everything until next letter [a-zA-Z] */
parser_state <= ST_PARSE_INVAL;
end
end
+ /* Bad escape sequence */
(ST_PARSE_INVAL): begin
if (in_byte >= 8'h40 && in_byte <= 8'h7e) begin
+ /* we got a letter or one of a few special chars. This ends any escape sequence, even those not handled
+ * by this parser. Thus, now we can safely return to regular text processing. */
parser_state <= ST_PARSE_TEXT;
end
end
endcase
end else if (parser_valid) begin
+ /* Process a sequence parsed above. */
+
if (cursor_movement_x_pos || cursor_movement_x_neg || cursor_movement_y_pos || cursor_movement_y_neg) begin
+ /* Cursor movement block. This is enabled from one of the branches below when processing cursor movements.
+ */
csi_act_ctr <= bcd12_inc(csi_act_ctr);
if (csi_act_ctr != ~num_buf_last) begin
@@ -248,6 +340,7 @@ always @(posedge clk) begin
cur_underline <= 0;
end
+ /* dark foreground colors */
(12'h030): cur_fg <= 0;
(12'h031): cur_fg <= 1;
(12'h032): cur_fg <= 2;
@@ -256,9 +349,10 @@ always @(posedge clk) begin
(12'h035): cur_fg <= 5;
(12'h036): cur_fg <= 6;
(12'h037): cur_fg <= 7;
- (12'h038): esc_inval <= 1;
+ (12'h038): esc_inval <= 1; /* we do not handle 256-color-palette xterm colors yet. Ignore. */
(12'h039): cur_fg <= 7;
+ /* dark background colors */
(12'h040): cur_bg <= 0;
(12'h041): cur_bg <= 1;
(12'h042): cur_bg <= 2;
@@ -267,9 +361,10 @@ always @(posedge clk) begin
(12'h045): cur_bg <= 5;
(12'h046): cur_bg <= 6;
(12'h047): cur_bg <= 7;
- (12'h048): esc_inval <= 1;
+ (12'h048): esc_inval <= 1; /* we do not handle 256-color-palette xterm colors yet. Ignore. */
(12'h049): cur_bg <= 0;
+ /* bright foreground colors */
(12'h090): cur_fg <= 8;
(12'h091): cur_fg <= 9;
(12'h092): cur_fg <= 10;
@@ -279,6 +374,7 @@ always @(posedge clk) begin
(12'h096): cur_fg <= 14;
(12'h097): cur_fg <= 15;
+ /* bright background colors */
(12'h100): cur_bg <= 8;
(12'h101): cur_bg <= 9;
(12'h102): cur_bg <= 10;
@@ -287,18 +383,21 @@ always @(posedge clk) begin
(12'h105): cur_bg <= 13;
(12'h106): cur_bg <= 14;
(12'h107): cur_bg <= 15;
- /* (12'hfff): ignore! */
+ /* (12'hfff): empty parameter -> ignore! */
endcase
+ /* Shift in next parameter to process */
num_buf_idx <= num_buf_idx + 1;
num_buf <= {num_buf[NUM_BUF_SZ*12-1-12:0], 12'h000};
end else begin
+ /* unsupported action letter, simply quit processing to ignore sequence. */
parser_valid <= 0;
end
end
end
+/* 3-digit BCD up-counter for cursor movements */
function [11:0] bcd12_inc;
input [11:0] in;
begin
@@ -321,4 +420,4 @@ begin
end
endfunction
-endmodule
\ No newline at end of file
+endmodule
diff --git a/demo/fpga/src/term_renderer.v b/demo/fpga/src/term_renderer.v
index 4a62825..715ebee 100644
--- a/demo/fpga/src/term_renderer.v
+++ b/demo/fpga/src/term_renderer.v
@@ -1,5 +1,21 @@
`timescale 1ns / 1ps
+/* Terminal glyph renderer
+ *
+ * This module reads glyph data from a glyph memory, and renders it into pixel data. The current glyph memory read
+ * location is adressed by this module through glyphmem_r_addr. The module's rendering logic is synchronized against
+ * a set of external blank/sync signals (in_blank/[hv]sync). This module introduces one clock cycle processing delay,
+ * which is why it outputs its own, delayed sync signals (out_[hv]sync) along with its output data.
+ *
+ * The glyph memory is 20 bit wide and, for each cell of the screen, besides the glyph for that cell contains complete
+ * styling information for that cell. The glyph memory's data format is as follows:
+ *
+ * { underline, bold, unused[1:0], bg_color[3:0], fg_color[3:0], glyph_index[7:0] }
+ *
+ * Colors are from a 16-color palette hard-coded below.
+ *
+ * This module supports pixel-accurate scrolling in y-position through the scroll_y input.
+ */
module term_renderer(
input rst, clk,
@@ -16,6 +32,7 @@ output [7:0] out_green,
output [7:0] out_blue
);
+/* Glyph memory size. Can be passed in from outside when instantiating for different-size memories. */
parameter GLYPHMEM_W = 256; /* glyphs */
parameter GLYPHMEM_H = 128; /* glyphs */
@@ -48,33 +65,36 @@ begin
/* Peppermint color palette: https://noahfrederick.com/log/lion-terminal-theme-peppermint */
case (index)
default: color_palette = 24'h353535;
- (1): color_palette = 24'he64569;
- (2): color_palette = 24'h89d287;
- (3): color_palette = 24'hdab752;
- (4): color_palette = 24'h439ecf;
- (5): color_palette = 24'hd961dc;
- (6): color_palette = 24'h64aaaf;
- (7): color_palette = 24'hb3b3b3;
- (8): color_palette = 24'h535353;
- (9): color_palette = 24'he4859a;
- (10): color_palette = 24'ha2cca1;
- (11): color_palette = 24'he1e387;
- (12): color_palette = 24'h6fbbe2;
- (13): color_palette = 24'he586e7;
- (14): color_palette = 24'h96dcda;
- (15): color_palette = 24'hdedede;
+ (1): color_palette = 24'he64569;
+ (2): color_palette = 24'h89d287;
+ (3): color_palette = 24'hdab752;
+ (4): color_palette = 24'h439ecf;
+ (5): color_palette = 24'hd961dc;
+ (6): color_palette = 24'h64aaaf;
+ (7): color_palette = 24'hb3b3b3;
+ (8): color_palette = 24'h535353;
+ (9): color_palette = 24'he4859a;
+ (10): color_palette = 24'ha2cca1;
+ (11): color_palette = 24'he1e387;
+ (12): color_palette = 24'h6fbbe2;
+ (13): color_palette = 24'he586e7;
+ (14): color_palette = 24'h96dcda;
+ (15): color_palette = 24'hdedede;
endcase
end
endfunction
-/* Glyph x/y synchronization logic */
+/* Glyph x/y synchronization logic
+ * We count both the current glyph x/y index we are rendering, as well as the pixel x/y index within that glyph that we
+ * are rendering.
+ */
reg [11:0] glyph_x;
reg [11:0] glyph_y;
-reg [FONT_GLYPH_W-1:0] glyph_sreg_out;
reg [5:0] px_x;
reg [5:0] px_y;
reg in_hsync_last, in_vsync_last, in_blank_last;
+/* glyph style */
wire [7:0] gm_data_glyph = glyphmem_data[7:0];
wire [11:0] gm_data_style = glyphmem_data[19:8];
reg [11:0] glyphmem_style_reg;
@@ -85,11 +105,18 @@ wire gm_data_bold = gm_data_style[10];
wire gm_data_underline = glyphmem_style_reg[11] && !newline_found;
reg newline_found;
+/* add one clock cycle of processing delay to sync signals. */
assign out_vsync = in_vsync_last;
assign out_hsync = in_hsync_last;
+/* Fetch glyph/style data for current x/y position */
assign glyphmem_r_addr = (GLYPHMEM_W*glyph_y) + glyph_x;
+/* Render current pixel within current glyph. */
+/* Shift register for current row of current glyph */
+reg [FONT_GLYPH_W-1:0] glyph_sreg_out;
+/* render underline and current pixel */
wire px_data = glyph_sreg_out[FONT_GLYPH_W-1] || (gm_data_underline && px_y == FONT_GLYPH_H-2);
+/* map fg/bg color */
assign {out_red, out_green, out_blue} = color_palette(newline_found ? last_bgcolor : (px_data ? gm_data_fgcolor : gm_data_bgcolor));
/* Core logic */
@@ -112,6 +139,7 @@ always @(posedge clk) begin
in_blank_last <= in_blank;
if (!in_blank_last && in_blank) begin
+ /* End of scanline. Reset x position counters */
glyph_x <= 0;
px_x <= 0;
newline_found <= 0;
@@ -126,41 +154,55 @@ always @(posedge clk) begin
end
if (!in_blank) begin
+ /* We are inside the active portion of the scanline. */
if (px_x != FONT_GLYPH_W-1) begin
+ /* we are in the middle of a glyph */
px_x <= px_x + 1;
end else begin
+ /* we have reached the border between glyphs */
px_x <= 0;
end
if (px_x == 0) begin
+ /* We are on the leftmost pixel of a glyph. Fetch glyph pixel data from memory. Switch between
+ * bold/normal fonts as indicated by glyph style information. */
if (newline_found) begin
+ /* ignore glyphs following a newline character. */
glyph_sreg_out <= 0;
+
end else if (gm_data_bold) begin
glyph_sreg_out <= glyph_table_bold[gm_data_glyph*FONT_GLYPH_H + px_y];
+
end else begin
glyph_sreg_out <= glyph_table_default[gm_data_glyph*FONT_GLYPH_H + px_y];
end
+ /* Register style. This is necessary since we perform the actual mapping to color values and adding of
+ * an underline above this process purely combinatorially. */
glyphmem_style_reg <= gm_data_style;
+ /* advance glyph index now to give font ROM a few cycles to fetch the next glyph's pixel data. */
glyph_x <= glyph_x + 1;
if (!newline_found && gm_data_glyph == 8'h0a) begin /* Newline character */
newline_found <= 1;
- last_bgcolor <= gm_data_bgcolor;
+ last_bgcolor <= gm_data_bgcolor; /* fill background color until screen edge after newline character */
end
end else begin
+ /* we are in the middle of a glyph, just shift through this glyph's pixel data. */
glyph_sreg_out <= {glyph_sreg_out[FONT_GLYPH_W-2:0], 1'b0};
end
end else if (!in_hsync_last) begin
+ /* We have reached the end of the frame. Reset glyph pixel data and cached style for next frame. */
glyph_sreg_out <= 0;
glyphmem_style_reg <= 0;
end
if (in_vsync_last && !in_vsync) begin
- glyph_y <= scroll_y / FONT_GLYPH_H;
+ /* we have reached the end of the scan line. Update y position. */
+ glyph_y <= scroll_y / FONT_GLYPH_H; /* TODO handle arbitrary glyph heights without variable div/mod */
px_y <= scroll_y % FONT_GLYPH_H;
end
end
diff --git a/demo/fpga/src/top.v b/demo/fpga/src/top.v
index 2f2b1ff..38937e8 100644
--- a/demo/fpga/src/top.v
+++ b/demo/fpga/src/top.v
@@ -1,23 +1,31 @@
+/* Pixel processing top-level module.
+ *
+ * This gets instantiated from hdmi_design.vhd */
module proc_top(
- input clk,
+ input clk, /* System clock input, 100 MHz crystal */
+ /* SPI interface to control microcontroller (STM32F4) */
input sck, sdi, ncs,
output sdo,
+ /* HDMI input RGB data */
input in_blank, in_hsync, in_vsync,
input [7:0] in_red,
input [7:0] in_green,
input [7:0] in_blue,
input is_interlaced, is_second_field,
+ /* HDMI output RGB data */
output out_blank, out_hsync, out_vsync,
output [7:0] out_red,
output [7:0] out_green,
output [7:0] out_blue,
+ /* Debug LEDs */
output [5:0] debug,
+ /* Debug switches */
input [7:0] switches
);
@@ -122,10 +130,14 @@ end
/* END DEBUG END */
/* ================= */
+/* Size of glyph memory (emulated terminal window size) */
parameter GLYPHMEM_W = 256; /* glyphs */
parameter GLYPHMEM_H = 128; /* glyphs */
+
+/* Size of buffer for receiving encrypted payloads */
parameter PAYLOAD_BUF_SIZE = 16384;
+/* Reset pulse stretching logic */
reg rst = 0;
reg [3:0] rst_cnt = 0;
@@ -141,6 +153,7 @@ always @(posedge clk) begin
end
end
+/* Idle detection logic */
reg input_idle = 0;
reg [19:0] idle_cnt = 0;
always @(posedge clk) begin
@@ -264,6 +277,7 @@ end
wire [7:0] rxbuf_dbg;
+/* SPI control interface register access logic */
spi_regfile spi_regfile_dut (
.clk(clk), .rst(rst),
@@ -282,6 +296,9 @@ spi_regfile spi_regfile_dut (
.rxbuf_dbg(rxbuf_dbg)
);
+/* Terminal emulator
+ * Converts ASCII with embedded VT-100 escape sequences (color / styling) into glyph data (glyph index plus per-glyph
+ * color/style information). */
term_emu #(
.GLYPHMEM_W(GLYPHMEM_W),
.GLYPHMEM_H(GLYPHMEM_H)
@@ -297,6 +314,8 @@ term_emu #(
.glyph_buffer_w_data(glyph_buffer_w_data)
);
+/* Terminal renderer
+ * Takes glyph data plus per-glyph color/style information and outputs pixel data synchronized with input sync signals */
term_renderer #(
.GLYPHMEM_W(GLYPHMEM_W),
.GLYPHMEM_H(GLYPHMEM_H)
@@ -329,6 +348,15 @@ wire [11:0] scan_x_dbg;
wire [11:0] scan_y_dbg;
wire [8:0] match_dbg;
+/* Main content window matching logic. This:
+ *
+ * => Finds the marker pattern in input data stream (win_locked)
+ * => Extracts its position and size (win_w/win_h)
+ * => Generates sync signals for just the window (win_blank, etc.)
+ * => Outputs original window pixels for decryption (out_data_en/valid)
+ * => Replaces window content with winow overlay from terminal renderer (win_red/g/b)
+ * => Outputs composited pixel data stream (out_blank/[hv]sync/red/green/blue)
+ */
window_matcher window_matcher_i (
.clk(clk),
.rst(rst),
diff --git a/demo/fpga/src/window_matcher.v b/demo/fpga/src/window_matcher.v
index 6770a99..5135062 100644
--- a/demo/fpga/src/window_matcher.v
+++ b/demo/fpga/src/window_matcher.v
@@ -1,5 +1,51 @@
`timescale 1ns / 1ps
+/* Window matching & pixel processing unit.
+ *
+ * This accepts an incoming pixel data stream from the HDMI input. It detects window markers, extracts encrypted window
+ * data, and overlays decrypted window data with the input pixel data to generate an output pixel data stream.
+ *
+ * The input frame format looks like this:
+ *
+ * (0, 0)
+ * +----------------------------------------------------------+
+ * | |
+ * | +---------------------------------+ |
+ * | | black border | |
+ * | | +---+-----+-----------------+ | |
+ * | | |mk | hdr | encrypted data | | |
+ * | | |mk | hdr | encrypted data | | |
+ * | | |mk | hdr | encrypted data | | |
+ * | | +---+-----+-----------------+ | |
+ * | | | |
+ * | +---------------------------------+ |
+ * | |
+ * +----------------------------------------------------------+
+ *
+ * [mk] = window marker, fixed pattern that starts each window pixel line
+ * [hdr] = window header, occurs on the left of every window pixel line
+ * [encrypted data] = encrypted payload data, is extracte by this module
+ * [black border] = separates window content from outside screen content.
+ * This is only really relevant on the right side to detect window width.
+ *
+ * The output frame format looks like this:
+ *
+ * (0, 0)
+ * +----------------------------------------------------------+
+ * | |
+ * | +---------------------------------+ |
+ * | | black border | |
+ * | | +----+----------------------+ | |
+ * | | | | | |
+ * | | | terminal content | | |
+ * | | | | | |
+ * | | +---------------------------+ | |
+ * | | | |
+ * | +---------------------------------+ |
+ * | |
+ * +----------------------------------------------------------+
+ *
+ */
module window_matcher(
/* Pixel clock and synchronous, active-high reset */
input clk, rst,
@@ -33,6 +79,7 @@ module window_matcher(
output reg out_data_en,
output reg out_data_valid,
+ /* debug signals */
output [7:0] debug,
output [7:0] match_dbg,
output [11:0] scan_x_dbg,
@@ -47,7 +94,8 @@ module window_matcher(
assign debug = {in_pxd_match_dbg, win_blank, win_hactive, win_locked, 1'b0};
- /* Pattern matching */
+ /* Window marker pattern. This is a sequence of (rgb) pixel values that does not usually occur in normal screen
+ * content. */
localparam [23:0] MARKER_0 = 24'h001020;
localparam [23:0] MARKER_1 = 24'h304050;
localparam [23:0] MARKER_2 = 24'h607080;
@@ -57,8 +105,9 @@ module window_matcher(
localparam [23:0] MARKER_6 = 24'h504030;
localparam [23:0] MARKER_7 = 24'h201000;
- /* In captures, we rarely observe that a channel value like 8'h50 gets changed to 8'h4f. I have no idea why this is, and pröperly debugging it is a major To-Do. For now, however, we simply dump the four LSBs and adjust the upper nibble to round.
- */
+ /* In captures, we rarely observe that a channel value like 8'h50 gets changed to 8'h4f. I have no idea why this is,
+ * and pröperly debugging it is a major To-Do. For now, however, we simply dump the four LSBs and adjust the upper
+ * nibble to round. */
function [7:0] unfuck;
input [7:0] in_ch;
begin
@@ -77,6 +126,8 @@ module window_matcher(
wire [7:0] in_blue_unfucked = unfuck(in_blue);
wire [23:0] in_pxd_unfucked = {in_red_unfucked, in_green_unfucked, in_blue_unfucked};
+ /* Pattern matching logic. What we want to match is the exact pixel sequence [MARKER_0] ... [MARKER_7]. */
+ /* First, we match each incoming pixel against all possible marker pixels */
wire [7:0] in_pxd_match = {
in_pxd_unfucked == MARKER_7,
in_pxd_unfucked == MARKER_6,
@@ -102,6 +153,7 @@ module window_matcher(
end
end
+ /* Second, we look for a row of 8 (N(MARKER_*)) ones in the resulting matches. */
reg [7:0] in_pxd_match_sr [6:0];
wire in_pxd_pattern_match =
in_pxd_match_sr[6][0]
@@ -134,7 +186,10 @@ module window_matcher(
end
end
- /* Pixel scan state machine */
+ /* Pixel scan state machine
+ *
+ * This logic keeps track of our x/y position in the input data stream.
+ */
reg [11:0] scan_x;
reg [11:0] scan_y;
assign scan_x_dbg = scan_x;
@@ -204,7 +259,14 @@ module window_matcher(
end
end
- /* Window matching state machine */
+ /* Window matching state machine.
+ *
+ * When the window pattern is detected, this state machine keeps track of where we are inside the window, and when
+ * we reach the end of the window. It also measures window x/y/w/h and generates the corresponding output signals.
+ *
+ * We determine window scroll position from an "y" index in each window header. The first "y" value we see in the
+ * input data stream is the current scroll position.
+ */
reg [11:0] win_x_int;
reg [11:0] win_y_int;
reg [11:0] win_w_int;
@@ -217,12 +279,12 @@ module window_matcher(
assign win_w_dbg = win_w_int;
assign win_h_dbg = win_h_int;
- localparam ST_MAT_WAITING = 5'b00000,
- ST_MAT_RX0 = 5'b00001,
- ST_MAT_RX1 = 5'b00010,
- ST_MAT_RX2 = 5'b00100,
- ST_MAT_RX3 = 5'b01000,
- ST_MAT_DATA = 5'b10000;
+ localparam ST_MAT_WAITING = 5'b00000, /* waiting for marker match inside current line */
+ ST_MAT_RX0 = 5'b00001, /* receiving first header data field (encrypted payload SOF marker) */
+ ST_MAT_RX1 = 5'b00010, /* 2nd header field (line y/scroll position) */
+ ST_MAT_RX2 = 5'b00100, /* 3rd header field (window width) */
+ ST_MAT_RX3 = 5'b01000, /* 4th header field (window height) */
+ ST_MAT_DATA = 5'b10000; /* we are inside the window's encrypted payload area */
reg [4:0] matcher_state;
reg matched;
@@ -315,7 +377,11 @@ module window_matcher(
end
end
- /* Border match locking process */
+ /* Border match locking process
+ *
+ * Here, we lock the window dimensions matched from the border's dimensions at the end of each frame. These values
+ * are used for window h/v-sync generation below for the terminal glyph renderer.
+ */
reg [11:0] border_w;
reg [11:0] border_h;
reg [11:0] border_right_reg;
@@ -350,7 +416,12 @@ module window_matcher(
end
end
- /* Match locking process */
+ /* Match locking process
+ *
+ * This generates the "locked" output signals at the end of the frame. These signals tell the control
+ * microcontroller that a window is currently visible on screen and that the received encrypted data buffer is
+ * filled with fresh data.
+ */
reg [11:0] win_x;
reg [11:0] win_y;
always @(posedge clk) begin
@@ -391,7 +462,12 @@ module window_matcher(
end
- /* Window H/VSYNC outputs */
+ /* Window H/VSYNC outputs
+ *
+ * This generates the window h/v-sync signals for the terminal glyph renderer. We generate these from the window
+ * dimensions we observed during the last frame since we don't know yet position and size of the window in the
+ * current frame.
+ */
reg win_hactive;
always @(posedge clk) begin
@@ -425,7 +501,13 @@ module window_matcher(
end
end
- /* Border matching */
+ /* Border matching
+ *
+ * Here, we match the window border to generate the window h/v-sync outputs. The values matched here are locked at
+ * the end of frame in a process above.
+ *
+ * NOTE: The border is several pixels in width, so the top/bottom border frame lines can be matched multiple times.
+ */
localparam BORDER_COLOR = 24'h000000,
BACKGROUND_COLOR = 24'hd020a0,
FONT_FG_COLOR = 24'he0e010;
@@ -435,19 +517,19 @@ module window_matcher(
reg [11:0] border_right;
reg [11:0] border_bottom;
- localparam BSTATE_WAITING = 15'b000000000000001, /* 0001 */
- BSTATE_FIRST_LINE = 15'b000000000000010, /* 0002 */
- BSTATE_FIRST_LINE_RIGHT = 15'b000000000000100, /* 0004 */
- BSTATE_FIRST_LINE_DONE = 15'b000000000001000, /* 0008 */
- BSTATE_WIN_LINE_WAIT = 15'b000000000010000, /* 0010 */
- BSTATE_WIN_LINE_WIN = 15'b000000000100000, /* 0020 */
- BSTATE_WIN_LINE_BUSY = 15'b000000001000000, /* 0040 */
- BSTATE_WIN_LINE_DONE = 15'b000000010000000, /* 0080 */
- BSTATE_LINE_WAIT = 15'b000000100000000, /* 0100 */
- BSTATE_LINE_BUSY = 15'b000001000000000, /* 0200 */
- BSTATE_LINE_DONE = 15'b000010000000000, /* 0400 */
- BSTATE_DONE = 15'b000100000000000, /* 0800 */
- BSTATE_INVALID = 15'b100000000000000; /* 4000 */
+ localparam BSTATE_WAITING = 15'b000000000000001, /* 0001 */ /* We are before the window */
+ BSTATE_FIRST_LINE = 15'b000000000000010, /* 0002 */ /* We are inside the top frame +-------+ portion of the border */
+ BSTATE_FIRST_LINE_RIGHT = 15'b000000000000100, /* 0004 */ /* We are on the right of the top frame portion */
+ BSTATE_FIRST_LINE_DONE = 15'b000000000001000, /* 0008 */ /* We are in the blanking interval after a line containing a top border frame line */
+ BSTATE_WIN_LINE_WAIT = 15'b000000000010000, /* 0010 */ /* We are waiting for either another left/right pair of border lines around the content ( | .... | ) or the bottom of the border. */
+ BSTATE_WIN_LINE_WIN = 15'b000000000100000, /* 0020 */ /* We are inside the left/right border */
+ BSTATE_WIN_LINE_BUSY = 15'b000000001000000, /* 0040 */ /* We are inside the window area, inside the border */
+ BSTATE_WIN_LINE_DONE = 15'b000000010000000, /* 0080 */ /* We are to the right of a border left/right pair */
+ BSTATE_LINE_WAIT = 15'b000000100000000, /* 0100 */ /* We are waiting for the border's bottom +-------+ edge */
+ BSTATE_LINE_BUSY = 15'b000001000000000, /* 0200 */ /* We are inside the border's bottom edge */
+ BSTATE_LINE_DONE = 15'b000010000000000, /* 0400 */ /* We are to the right of the borders bottom edge */
+ BSTATE_DONE = 15'b000100000000000, /* 0800 */ /* We are below the border */
+ BSTATE_INVALID = 15'b100000000000000; /* 4000 */ /* Something went wrong, the border does not look correct (e.g. does not align with window because the window moved between frames). */
reg [14:0] bstate;
always @(posedge clk) begin
diff --git a/demo/fpga/window_matcher_tb_behav.wcfg b/demo/fpga/window_matcher_tb_behav.wcfg
index 5b3a7f3..fc91d79 100644
--- a/demo/fpga/window_matcher_tb_behav.wcfg
+++ b/demo/fpga/window_matcher_tb_behav.wcfg
@@ -11,15 +11,15 @@
-
-
-
+
+
+
-
+
-
+
clk
clk
@@ -195,4 +195,8 @@
border_matched
border_matched
+
+ matcher_state[4:0]
+ matcher_state[4:0]
+