tachibana/demo/fpga/src/window_matcher.v
2021-07-20 15:59:50 +02:00

522 lines
15 KiB
Verilog

`timescale 1ns / 1ps
module window_matcher(
/* Pixel clock and synchronous, active-high reset */
input clk, rst,
input bypass,
/* Input pixel bus */
input in_blank, in_hsync, in_vsync,
input [7:0] in_red, [7:0] in_green, [7:0] in_blue,
/* Output pixel bus */
output out_blank,
output out_hsync, out_vsync,
output [7:0] out_red, [7:0] out_green, [7:0] out_blue,
/* Overlay data IO */
output reg win_blank,
output [11:0] win_x_dbg,
output [11:0] win_y_dbg,
output [11:0] win_w_dbg,
output [11:0] win_h_dbg,
output reg [11:0] win_w,
output reg [11:0] win_h,
output reg win_locked,
input [7:0] win_red, [7:0] win_green, [7:0] win_blue,
/* Extracted data output */
output reg out_data_en,
output reg out_data_valid,
output [7:0] debug,
output [7:0] match_dbg,
output [11:0] scan_x_dbg,
output [11:0] scan_y_dbg
);
/* Pixel data pipeline */
wire [23:0] in_pxd = {in_red, in_green, in_blue};
wire [23:0] win_pxd = {win_red, win_green, win_blue};
wire [23:0] out_pxd;
assign {out_red, out_green, out_blue} = out_pxd;
assign debug = {in_pxd_match_dbg, win_blank, win_hactive, win_locked, 1'b0};
/* Pattern matching */
localparam [23:0] MARKER_0 = 24'h001020;
localparam [23:0] MARKER_1 = 24'h304050;
localparam [23:0] MARKER_2 = 24'h607080;
localparam [23:0] MARKER_3 = 24'h90a0b0;
localparam [23:0] MARKER_4 = 24'hc0d0e0;
localparam [23:0] MARKER_5 = 24'hf04020;
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.
*/
function [7:0] unfuck;
input [7:0] in_ch;
begin
unfuck[3:0] = 4'h0;
if (in_ch[3:0] < 8) begin
unfuck[7:4] = in_ch[7:4];
end else begin
unfuck[7:4] = in_ch[7:4] + 1;
end
end
endfunction
wire [7:0] in_red_unfucked = unfuck(in_red);
wire [7:0] in_green_unfucked = unfuck(in_green);
wire [7:0] in_blue_unfucked = unfuck(in_blue);
wire [23:0] in_pxd_unfucked = {in_red_unfucked, in_green_unfucked, in_blue_unfucked};
wire [7:0] in_pxd_match = {
in_pxd_unfucked == MARKER_7,
in_pxd_unfucked == MARKER_6,
in_pxd_unfucked == MARKER_5,
in_pxd_unfucked == MARKER_4,
in_pxd_unfucked == MARKER_3,
in_pxd_unfucked == MARKER_2,
in_pxd_unfucked == MARKER_1,
in_pxd_unfucked == MARKER_0
};
assign match_dbg = in_pxd_match;
reg [3:0] in_pxd_match_dbg;
always @(posedge clk) begin
if (rst) begin
in_pxd_match_dbg <= 0;
end else begin
if (!in_blank) begin
in_pxd_match_dbg <= in_pxd_match_dbg | in_pxd_match[7:4];
end else begin
in_pxd_match_dbg <= 0;
end
end
end
reg [7:0] in_pxd_match_sr [6:0];
wire in_pxd_pattern_match =
in_pxd_match_sr[6][0]
&& in_pxd_match_sr[5][1]
&& in_pxd_match_sr[4][2]
&& in_pxd_match_sr[3][3]
&& in_pxd_match_sr[2][4]
&& in_pxd_match_sr[1][5]
&& in_pxd_match_sr[0][6]
&& in_pxd_match[7]
&& !in_blank;
always @(posedge clk) begin
if (rst == 1) begin
in_pxd_match_sr[0] <= 0;
in_pxd_match_sr[1] <= 0;
in_pxd_match_sr[2] <= 0;
in_pxd_match_sr[3] <= 0;
in_pxd_match_sr[4] <= 0;
in_pxd_match_sr[5] <= 0;
in_pxd_match_sr[6] <= 0;
end else begin
in_pxd_match_sr[0] <= in_pxd_match;
in_pxd_match_sr[1] <= in_pxd_match_sr[0];
in_pxd_match_sr[2] <= in_pxd_match_sr[1];
in_pxd_match_sr[3] <= in_pxd_match_sr[2];
in_pxd_match_sr[4] <= in_pxd_match_sr[3];
in_pxd_match_sr[5] <= in_pxd_match_sr[4];
in_pxd_match_sr[6] <= in_pxd_match_sr[5];
end
end
/* Pixel scan state machine */
reg [11:0] scan_x;
reg [11:0] scan_y;
assign scan_x_dbg = scan_x;
assign scan_y_dbg = scan_y;
reg [11:0] scan_x_reg [7:0];
reg in_hsync_reg;
reg in_vsync_reg;
reg in_blank_reg;
reg [23:0] in_pxd_reg;
assign out_hsync = in_hsync_reg;
assign out_vsync = in_vsync_reg;
assign out_blank = in_blank_reg;
always @(posedge clk) begin
if (rst == 1) begin
scan_x <= 0;
scan_x_reg[0] <= 0;
scan_x_reg[1] <= 0;
scan_x_reg[2] <= 0;
scan_x_reg[3] <= 0;
scan_x_reg[4] <= 0;
scan_x_reg[5] <= 0;
scan_x_reg[6] <= 0;
scan_x_reg[7] <= 0;
scan_y <= 0;
in_hsync_reg <= 0;
in_vsync_reg <= 0;
in_blank_reg <= 0;
in_pxd_reg <= 0;
end else begin
in_hsync_reg <= in_hsync;
in_vsync_reg <= in_vsync;
in_blank_reg <= in_blank;
in_pxd_reg <= in_pxd;
scan_x_reg[0] <= scan_x;
scan_x_reg[1] <= scan_x_reg[0];
scan_x_reg[2] <= scan_x_reg[1];
scan_x_reg[3] <= scan_x_reg[2];
scan_x_reg[4] <= scan_x_reg[3];
scan_x_reg[5] <= scan_x_reg[4];
scan_x_reg[6] <= scan_x_reg[5];
scan_x_reg[7] <= scan_x_reg[6];
if (!in_blank) begin
scan_x <= scan_x + 1;
end
if (!in_blank_reg && in_blank) begin
scan_y <= scan_y + 1;
scan_x_reg[0] <= 0;
scan_x_reg[1] <= 0;
scan_x_reg[2] <= 0;
scan_x_reg[3] <= 0;
scan_x_reg[4] <= 0;
scan_x_reg[5] <= 0;
scan_x_reg[6] <= 0;
scan_x_reg[7] <= 0;
scan_x <= 0;
end
if (in_vsync_reg && !in_vsync) begin
scan_y <= 0;
end
end
end
/* Window matching state machine */
reg [11:0] win_x_int;
reg [11:0] win_y_int;
reg [11:0] win_w_int;
reg [11:0] win_h_int;
assign win_x_dbg = win_x_int;
assign win_y_dbg = win_y_int;
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;
reg [4:0] matcher_state;
wire matched = matcher_state[4];
reg [11:0] dval_x_reg;
reg [11:0] dval_y_reg;
always @(posedge clk) begin
if (rst == 1) begin
out_data_valid <= 0;
out_data_en <= 0;
end
if (rst || in_vsync) begin
matcher_state <= ST_MAT_WAITING;
win_x_int <= 0;
win_y_int <= 0;
win_w_int <= 0;
win_h_int <= 0;
dval_x_reg <= 0;
dval_y_reg <= 0;
end
if (!rst) begin
if (in_blank) begin
/* Reset state if the header is only partially contained in this frame */
if (matcher_state != ST_MAT_DATA) begin
matcher_state <= ST_MAT_WAITING;
end
end else begin
case (matcher_state)
ST_MAT_WAITING: begin
if (in_pxd_pattern_match) begin
matcher_state <= ST_MAT_RX0;
win_x_int <= scan_x_reg[7];
win_y_int <= scan_y;
end
end
ST_MAT_RX0: begin
matcher_state <= ST_MAT_RX1;
end
ST_MAT_RX1: begin
matcher_state <= ST_MAT_RX2;
end
ST_MAT_RX2: begin
matcher_state <= ST_MAT_RX3;
win_w_int <= in_pxd;
end
ST_MAT_RX3: begin
matcher_state <= ST_MAT_DATA;
win_h_int <= in_pxd;
out_data_valid <= 1;
out_data_en <= 1;
dval_x_reg <= 13;
dval_y_reg <= 0;
end
endcase
end
if (matcher_state == ST_MAT_DATA) begin
/* blank */
if (scan_x == win_x_int && out_data_en) begin
out_data_valid <= 1;
dval_x_reg <= 1;
end else if (out_data_en) begin
dval_x_reg <= dval_x_reg + 1;
end
if (dval_x_reg == win_w_int || in_blank) begin
out_data_valid <= 0;
end
if (!in_blank_reg && in_blank) begin
dval_y_reg <= dval_y_reg + 1;
end
if (dval_y_reg == win_h_int) begin
out_data_en <= 0;
end
end
end
end
/* Match locking process */
reg [11:0] win_x;
reg [11:0] win_y;
always @(posedge clk) begin
if (rst) begin
win_locked <= 0;
win_w <= 0;
win_h <= 0;
win_x <= 0;
win_y <= 0;
end else begin
if (in_vsync_reg == 0 && in_vsync == 1) begin
win_locked <= matched;
if (matched) begin
win_w <= win_w_int;
win_h <= win_h_int;
win_x <= win_x_int;
win_y <= win_y_int;
end else begin
win_w <= 0;
win_h <= 0;
win_x <= 0;
win_y <= 0;
end
end
end
end
/* Window H/VSYNC outputs */
reg [11:0] win_blank_ctr;
reg [11:0] win_hsync_ctr;
reg win_hactive;
always @(posedge clk) begin
if (rst) begin
win_blank <= 1;
win_hactive <= 0;
win_blank_ctr <= 0;
win_hsync_ctr <= 0;
end else begin
if (win_locked) begin
/* hsync */
if (scan_x == win_x && win_hactive) begin
win_blank <= 0;
win_blank_ctr <= 1;
end
if (win_blank == 0) begin
win_blank_ctr <= win_blank_ctr + 1;
end
if (win_blank_ctr == win_w || in_blank) begin
win_blank <= 1;
win_blank_ctr <= 0;
end
if (win_hactive && in_blank_reg && !in_blank) begin
win_hsync_ctr <= win_hsync_ctr + 1;
end
if (scan_y == win_y) begin
win_hactive <= 1;
end
if (win_hsync_ctr == win_h && !in_blank_reg && in_blank) begin
win_hactive <= 0;
end
if (in_vsync_reg == 1 && in_vsync == 0) begin
win_hsync_ctr <= 0;
win_hactive <= 0;
end
end
end
end
/* Border matching */
localparam BORDER_COLOR = 24'h162329,
BACKGROUND_COLOR = 24'hd61ca1,
FONT_FG_COLOR = 24'he1e31b;
wire is_border_px = (in_pxd_unfucked & 24'hf0f0f0) == (BORDER_COLOR & 24'hf0f0f0);
wire is_bg_px = (in_pxd_unfucked & 24'hf0f0f0) == (BACKGROUND_COLOR & 24'hf0f0f0) ||
(in_pxd_unfucked & 24'hf0f0f0) == (FONT_FG_COLOR & 24'hf0f0f0);
reg [11:0] border_right;
reg [11:0] border_bottom;
reg row_valid_border_h, row_valid_border_v;
reg valid_border;
wire [11:0] border_top = win_y - 2;
wire [11:0] border_left = win_x - 2;
localparam BSTATE_WAITING = 15'b000000000000001,
BSTATE_TOP1 = 15'b000000000000010,
BSTATE_TOP1_RIGHT = 15'b000000000000100,
BSTATE_TOP2_LEFT = 15'b000000000001000,
BSTATE_TOP2 = 15'b000000000010000,
BSTATE_TOP2_RIGHT = 15'b000000000100000,
BSTATE_MID_LEFT = 15'b000000001000000,
BSTATE_MID_L1 = 15'b000000010000000,
BSTATE_MID_L2 = 15'b000000010000000,
BSTATE_MID_CENTER = 15'b000000010000000,
BSTATE_MID_R1 = 15'b000000010000000,
BSTATE_MID_R2 = 15'b000000010000000,
BSTATE_MID_RIGHT = 15'b000000100000000,
BSTATE_BOT1_LEFT = 15'b000001000000000,
BSTATE_BOT1 = 15'b000010000000000,
BSTATE_BOT1_RIGHT = 15'b000100000000000,
BSTATE_BOT2 = 15'b001000000000000,
BSTATE_FINISHED = 15'b010000000000000;
BSTATE_INVALID = 15'b100000000000000;
wire [14:0] bstate;
always @(posedge clk) begin
if (rst || in_vsync || !win_locked || win_x < 2 || win_y < 2) begin
row_valid_border_h <= 0;
row_valid_border_v <= 0;
valid_border <= 0;
border_right <= 0;
border_bottom <= 0;
bstate <= BSTATE_WAITING;
end else if (!in_blank) begin
case (bstate)
(BSTATE_WAITING): begin
if (scan_x == border_left && scan_y == border_top) begin /* top left pixel of border */
bstate <= is_border_px ? BSTATE_TOP1 : BSTATE_INVALID;
end
end
(BSTATE_TOP1): begin
if (!is_border_px) begin
border_right <= scan_x;
bstate <= BSTATE_TOP1_RIGHT;
end
end
(BSTATE_TOP2_LEFT): begin
if (scan_x == border_left) begin
bstate <= is_border_px ? BSTATE_TOP2 : BSTATE_INVALID;
end
end
(BSTATE_TOP2): begin
if (scan_x == border_right) begin
bstate <= BSTATE_TOP2_RIGHT;
end else begin
bstate <= is_border_px ? BSTATE_TOP2 : BSTATE_INVALID;
end
end
(BSTATE_MID_LEFT): begin
if (scan_x == border_left) begin
bstate <= is_border_px ? BSTATE_MID_L1 : BSTATE_INVALID;
end
end
(BSTATE_MID_L1): begin
bstate <= is_border_px ? BSTATE_MID_L2 : BSTATE_INVALID;
end
(BSTATE_MID_L2): begin
if (is_border_px) begin
bstate <= BSTATE_BOT1;
end else if (is_bg_px) begin
bstate <= BSTATE_MID_C;
end else begin
bstate <= BSTATE_INVALID;
end
end
(BSTATE_MID_C): begin
if (is_border_px) begin
bstate <= BSTATE_MID_R1;
end else if (is_bg_px) begin
bstate <= BSTATE_MID_C;
end else begin
bstate <= BSTATE_INVALID;
end
end
(BSTATE_MID_R1): begin
bstate <= is_border_px ? BSTATE_MID_R2 : BSTATE_INVALID;
end
(BSTATE_MID_R2): begin
bstate <= (scan_x == border_right) ? BSTATE_MID_RIGHT : BSTATE_INVALID;
end
(BSTATE_BOT1): begin
if (scan_x == border_right) begin
bstate <= BSTATE_BOT1_RIGHT;
end else begin
bstate <= is_border_px ? BSTATE_BOT1 : BSTATE_INVALID;
end
end
(BSTATE_BOT2): begin
if (scan_x == border_right) begin
bstate <= BSTATE_BOT1_RIGHT;
end else begin
bstate <= is_border_px ? BSTATE_BOT1 : BSTATE_INVALID;
end
end
(BSTATE_FINISHED): begin
end
(BSTATE_INVALID): begin
/* do nothing, reset on next vsync. */
end
endcase
end else begin /* blank */
case (bstate)
(BSTATE_TOP1_RIGHT): bstate <= BSTATE_TOP2_LEFT;
(BSTATE_TOP2_RIGHT): bstate <= BSTATE_MID_LEFT;
(BSTATE_MID_RIGHT): bstate <= BSTATE_MID_LEFT;
(BSTATE_BOT1_RIGHT): bstate <= BSTATE_BOT2_LEFT;
endcase
end
end
/* Payload extractor */
reg [23:0] in_pxd_last;
always @(posedge clk) in_pxd_last <= rst ? 0 : in_pxd;
/* Compositor */
assign out_pxd = (!win_blank && !bypass) ? win_pxd : in_pxd_reg;
endmodule