522 lines
15 KiB
Verilog
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
|