Add lots of comments to FPGA sources
This commit is contained in:
parent
d804f4596e
commit
8f28367b2d
7 changed files with 335 additions and 70 deletions
|
|
@ -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
|
||||
endmodule
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
endmodule
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -11,15 +11,15 @@
|
|||
</db_ref>
|
||||
</db_ref_list>
|
||||
<zoom_setting>
|
||||
<ZoomStartTime time="588433216fs"></ZoomStartTime>
|
||||
<ZoomEndTime time="611044557fs"></ZoomEndTime>
|
||||
<Cursor1Time time="607276000fs"></Cursor1Time>
|
||||
<ZoomStartTime time="0fs"></ZoomStartTime>
|
||||
<ZoomEndTime time="607276001fs"></ZoomEndTime>
|
||||
<Cursor1Time time="50308000fs"></Cursor1Time>
|
||||
</zoom_setting>
|
||||
<column_width_setting>
|
||||
<NameColumnWidth column_width="175"></NameColumnWidth>
|
||||
<ValueColumnWidth column_width="150"></ValueColumnWidth>
|
||||
<ValueColumnWidth column_width="146"></ValueColumnWidth>
|
||||
</column_width_setting>
|
||||
<WVObjectSize size="38" />
|
||||
<WVObjectSize size="39" />
|
||||
<wvobject type="logic" fp_name="/window_matcher_tb/window_matcher_i/clk">
|
||||
<obj_property name="ElementShortName">clk</obj_property>
|
||||
<obj_property name="ObjectShortName">clk</obj_property>
|
||||
|
|
@ -195,4 +195,8 @@
|
|||
<obj_property name="ElementShortName">border_matched</obj_property>
|
||||
<obj_property name="ObjectShortName">border_matched</obj_property>
|
||||
</wvobject>
|
||||
<wvobject type="array" fp_name="/window_matcher_tb/window_matcher_i/matcher_state">
|
||||
<obj_property name="ElementShortName">matcher_state[4:0]</obj_property>
|
||||
<obj_property name="ObjectShortName">matcher_state[4:0]</obj_property>
|
||||
</wvobject>
|
||||
</wave_config>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue