From 8f28367b2d28b3cdeab584772853fe7b07af95ed Mon Sep 17 00:00:00 2001 From: jaseg Date: Fri, 17 Dec 2021 15:47:22 +0100 Subject: [PATCH] Add lots of comments to FPGA sources --- demo/fpga/src/edge_cleaner.v | 7 +- demo/fpga/src/spi_regfile.v | 5 + demo/fpga/src/term_emu.v | 131 ++++++++++++++++++++--- demo/fpga/src/term_renderer.v | 80 ++++++++++---- demo/fpga/src/top.v | 30 +++++- demo/fpga/src/window_matcher.v | 138 ++++++++++++++++++++----- demo/fpga/window_matcher_tb_behav.wcfg | 14 ++- 7 files changed, 335 insertions(+), 70 deletions(-) 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] +