//Credit to the Implementation of HDMI Spec v1.4a by Sameer Puri https://github.com/sameer
`default_nettype none
/*
Expected Result:
A new colour should wipe across the screen over a couple of seconds. This should happen repeatedly. 
*/
module dvi_pattern
(
    input wire clk, 
    input logic key_i,
    output wire tmds_clk_n,
    output wire tmds_clk_p,
    output wire [2:0] tmds_d_n,
    output wire [2:0] tmds_d_p, 
    output wire div_led
);

wire key;
assign key = key_i ^ `INV_BTN;
wire clk_pixel, clk_pixel_x5, pll_lock, reset;
logic [2:0] tmds;
logic tmds_clock;

reg sys_resetn = 0;
reg [3:0] reset_cnt = 15;      // reset for 15 cycles
always @(posedge clk) begin
    reset_cnt <= reset_cnt == 0 ? 0 : reset_cnt - 1;
    if (reset_cnt == 0)
        sys_resetn <= key;
end
assign reset = ~sys_resetn; 

PLL480 pll_dvi (
    .clkin(clk),
    .clkout(clk_pixel_x5),
    .lock(pll_lock)
  );


CLKDIV #(.DIV_MODE("5")) clk_div (
    .CLKOUT(clk_pixel),
    .HCLKIN(clk_pixel_x5),
    .RESETN(pll_lock)
);


reg [23:0] counter; 
always @(posedge clk_pixel)
    counter<=counter+1'b1;

assign div_led = counter[23]; 

localparam BIT_WIDTH = 10; 
localparam BIT_HEIGHT = 10; 
localparam START_X = 0; 
localparam START_Y = 0; 
localparam int NUM_CHANNELS = 3;
logic hsync;
logic vsync;

logic [BIT_WIDTH-1:0] cx = START_X; 
logic [BIT_HEIGHT-1:0] cy = START_Y; 


localparam real VIDEO_RATE = 25.2E6; 

localparam [BIT_WIDTH-1:0] FRAME_WIDTH = 800;
localparam [BIT_HEIGHT-1:0] FRAME_HEIGHT = 525;
localparam [BIT_WIDTH-1:0] SCREEN_WIDTH = 640;
localparam [BIT_HEIGHT-1:0] SCREEN_HEIGHT = 480;
localparam [BIT_WIDTH-1:0] HSYNC_PULSE_START = 16;
localparam [BIT_WIDTH-1:0] HSYNC_PULSE_SIZE = 96;
localparam [BIT_HEIGHT-1:0] VSYNC_PULSE_START = 10;
localparam [BIT_HEIGHT-1:0] VSYNC_PULSE_SIZE = 2;
localparam INVERT = 1;


wire next = screen_partition == (SCREEN_WIDTH -1'B1);
reg [BIT_WIDTH-1: 0] screen_partition;

reg [23:0] old_color, new_color;
wire [23:0] rgb; 
wire [7:0] r_lfsr, g_lfsr, b_lfsr;

// LFSR8 #(.seed(20)) red_lsfr(.reset(reset), .next(next), .clk(clk_pixel), .out_val(r_lfsr));
// LFSR8 #(.seed(81)) green_lsfr(.reset(reset), .next(next), .clk(clk_pixel), .out_val(g_lfsr));
// LFSR8 #(.seed(130)) blue_lsfr(.reset(reset), .next(next), .clk(clk_pixel), .out_val(b_lfsr));

LFSR24 #(.seed(24'hFA311)) rgb_lfsr(.reset(reset), .next(next), .clk(clk_pixel), .out_val({r_lfsr, g_lfsr, b_lfsr})); 


always @(posedge clk_pixel) begin   
    if (next) begin
        screen_partition <= 0;
        old_color <= new_color;
        new_color <= {r_lfsr, g_lfsr, b_lfsr}; 
    end
    else if ((cx == SCREEN_WIDTH-1'b1) && (cy == SCREEN_HEIGHT-1'b1)) begin
        screen_partition <= screen_partition + 1'b1;
    end 
end 

assign rgb = (cx < screen_partition)? new_color: old_color;

always_comb begin
    hsync <= INVERT ^ (cx >= SCREEN_WIDTH + HSYNC_PULSE_START && cx < SCREEN_WIDTH + HSYNC_PULSE_START + HSYNC_PULSE_SIZE);
    // vsync pulses should begin and end at the start of hsync, so special
    // handling is required for the lines on which vsync starts and ends
    if (cy == SCREEN_HEIGHT + VSYNC_PULSE_START)
        vsync <= INVERT ^ (cx >= SCREEN_WIDTH + HSYNC_PULSE_START);
    else if (cy == SCREEN_HEIGHT + VSYNC_PULSE_START + VSYNC_PULSE_SIZE)
        vsync <= INVERT ^ (cx < SCREEN_WIDTH + HSYNC_PULSE_START);
    else
        vsync <= INVERT ^ (cy >= SCREEN_HEIGHT + VSYNC_PULSE_START && cy < SCREEN_HEIGHT + VSYNC_PULSE_START + VSYNC_PULSE_SIZE);
end


// Wrap-around pixel position counters indicating the pixel to be generated by the user in THIS clock and sent out in the NEXT clock.
always_ff @(posedge clk_pixel)
begin
    if (reset)
    begin
        cx <= BIT_WIDTH'(START_X);
        cy <= BIT_HEIGHT'(START_Y);
    end
    else
    begin
        cx <= cx == FRAME_WIDTH-1'b1 ? BIT_WIDTH'(0) : cx + 1'b1;
        cy <= cx == FRAME_WIDTH-1'b1 ? cy == FRAME_HEIGHT-1'b1 ? BIT_HEIGHT'(0) : cy + 1'b1 : cy;
    end
end

// See Section 5.2
logic video_data_period = 0;
always_ff @(posedge clk_pixel)
begin
    if (reset)
        video_data_period <= 0;
    else
        video_data_period <= cx < SCREEN_WIDTH && cy < SCREEN_HEIGHT;
end

logic [2:0] mode = 3'd1;
logic [23:0] video_data = 24'd0;
logic [5:0] control_data = 6'd0;
logic [11:0] data_island_data = 12'd0;


always_ff @(posedge clk_pixel)
begin
    if (reset)
    begin
        mode <= 3'd0;
        video_data <= 24'd0;
        control_data <= 6'd0;
    end
    else
    begin
        mode <= video_data_period ? 3'd1 : 3'd0;
        video_data <= rgb;
        control_data <= {4'b0000, {vsync, hsync}}; // ctrl3, ctrl2, ctrl1, ctrl0, vsync, hsync
    end
end


// All logic below relates to the production and output of the 10-bit TMDS code.
logic [9:0] tmds_internal [NUM_CHANNELS-1:0] /* verilator public_flat */ ;
genvar i;
generate
    // TMDS code production.
    for (i = 0; i < NUM_CHANNELS; i++)
    begin: tmds_gen
        tmds_channel #(.CN(i)) tmds_channel (
            .clk_pixel(clk_pixel), 
            .video_data(video_data[i*8+7:i*8]), 
            .data_island_data(0), 
            .control_data(control_data[i*2+1:i*2]), 
            .mode(mode), 
            .tmds(tmds_internal[i])
        );
    end
endgenerate


OSER10 gwSer0( 
    .Q( tmds[ 0 ] ),
    .D0( tmds_internal[ 0 ][ 0 ] ),
    .D1( tmds_internal[ 0 ][ 1 ] ),
    .D2( tmds_internal[ 0 ][ 2 ] ),
    .D3( tmds_internal[ 0 ][ 3 ] ),
    .D4( tmds_internal[ 0 ][ 4 ] ),
    .D5( tmds_internal[ 0 ][ 5 ] ),
    .D6( tmds_internal[ 0 ][ 6 ] ),
    .D7( tmds_internal[ 0 ][ 7 ] ),
    .D8( tmds_internal[ 0 ][ 8 ] ),
    .D9( tmds_internal[ 0 ][ 9 ] ),
    .PCLK( clk_pixel ),
    .FCLK( clk_pixel_x5 ),
    .RESET( reset ) );

OSER10 gwSer1( 
    .Q( tmds[ 1 ] ),
    .D0( tmds_internal[ 1 ][ 0 ] ),
    .D1( tmds_internal[ 1 ][ 1 ] ),
    .D2( tmds_internal[ 1 ][ 2 ] ),
    .D3( tmds_internal[ 1 ][ 3 ] ),
    .D4( tmds_internal[ 1 ][ 4 ] ),
    .D5( tmds_internal[ 1 ][ 5 ] ),
    .D6( tmds_internal[ 1 ][ 6 ] ),
    .D7( tmds_internal[ 1 ][ 7 ] ),
    .D8( tmds_internal[ 1 ][ 8 ] ),
    .D9( tmds_internal[ 1 ][ 9 ] ),
    .PCLK( clk_pixel ),
    .FCLK( clk_pixel_x5 ),
    .RESET( reset ) );

OSER10 gwSer2( 
    .Q( tmds[ 2 ] ),
    .D0( tmds_internal[ 2 ][ 0 ] ),
    .D1( tmds_internal[ 2 ][ 1 ] ),
    .D2( tmds_internal[ 2 ][ 2 ] ),
    .D3( tmds_internal[ 2 ][ 3 ] ),
    .D4( tmds_internal[ 2 ][ 4 ] ),
    .D5( tmds_internal[ 2 ][ 5 ] ),
    .D6( tmds_internal[ 2 ][ 6 ] ),
    .D7( tmds_internal[ 2 ][ 7 ] ),
    .D8( tmds_internal[ 2 ][ 8 ] ),
    .D9( tmds_internal[ 2 ][ 9 ] ),
    .PCLK( clk_pixel ),
    .FCLK( clk_pixel_x5 ),
    .RESET( reset ) );
    
assign tmds_clock = clk_pixel;

generate
    if (`IS_ELVDS)
        ELVDS_OBUF tmds_bufds [3:0] (
            .I({clk_pixel, tmds}),
            .O({tmds_clk_p, tmds_d_p}),
            .OB({tmds_clk_n, tmds_d_n})
        );
    else
        TLVDS_OBUF tmds_bufds [3:0] (
            .I({clk_pixel, tmds}),
            .O({tmds_clk_p, tmds_d_p}),
            .OB({tmds_clk_n, tmds_d_n})
        );
endgenerate
endmodule


// 8-bit LFSR
module LFSR8 #(
    parameter seed = 10
) (
    input wire reset, 
    input wire clk,
    input wire next, 
    output reg [7:0] out_val
);


always @(posedge clk) begin
    if (reset)
        out_val <= seed; 
    else if (next)
        out_val <= {out_val[6:0], ((out_val[7] ^~ out_val[5]) ^~ out_val[4]) ^~ out_val[3]}; 
end
endmodule

// 24-bit LFSR
module LFSR24 #(
    parameter seed = 10
) (
    input wire reset, 
    input wire clk,
    input wire next, 
    output reg [23:0] out_val
);

always @(posedge clk) begin
    if (reset)
        out_val <= seed; 
    else if (next)
        out_val <= {out_val[22:0], ((out_val[23] ^~ out_val[22]) ^~ out_val[21]) ^~ out_val[16]}; 
end
endmodule