`timescale 1ns/1ns

module line_combiner #(
    parameter LINE_WIDTH = 400
)
(
    input       ARESET,

    input       CAM1_CLK,
    input [7:0] CAM1_DATA,
    input       CAM1_DE,
    input       CAM1_VS,

    input       CAM2_CLK,
    input [7:0] CAM2_DATA,
    input       CAM2_DE,
    input       CAM2_VS,

    input           COMBINED_CLK,
    output [7:0]    COMBINED_DATA,
    output          COMBINED_DE,
    output          COMBINED_VS


,   output          CAM1_EMPTY
,   output          CAM2_EMPTY
,   output          CAM1_ACTIVATED
,   output          CAM2_ACTIVATED
,   output          COMBINED_OUTPUTTING
);

localparam TOTAL_LINE_WIDTH = 2*LINE_WIDTH;

// Pushes camera data into two FIFOs.
// Once a line's worth of data is in both FIFOs,
// pumps out both lines, CAM1 followed by CAM2.
// In this way, this module combines two cameras
// in a side-by-side manner.

// Input vertical sync is used for the first
// alignment of the FIFO. No data is inserted after
// reset until a rising edge of vertical sync
// Afterwards, the input vertical syncs are used
// to tell the controller that VS needs to go high
// before the next combined line output

// Output vertical sync is generated by the controller,
// goes high after both input VS are high and one line
// of data is ready, goes low after both input VS are low
// and we finish outputting the last line

wire            cam1_write_enable;
wire            cam2_write_enable;
wire            cam1_read_enable;
wire            cam2_read_enable;
reg             cam_read_sel; // 0: cam1, 1: cam2
wire            cam1_empty;
wire            cam2_empty;
wire            cam1_full;
wire            cam2_full;
wire [7:0]      cam1_read_data;
wire [7:0]      cam2_read_data;
wire [10:0]     cam1_read_count;
wire [10:0]     cam2_read_count;

wire            cam1_vs_rising;
wire            cam1_vs_falling;
wire            cam2_vs_rising;
wire            cam2_vs_falling;
reg             cam1_vs_r = 1'b0; // single cycle delayed Vsync
reg             cam2_vs_r = 1'b0;

reg  [1:0]      cam1_valid_frame_count;
reg  [1:0]      cam2_valid_frame_count;
reg             cam1_in_vactive;
reg             cam2_in_vactive;
reg             cam1_activated;
reg             cam2_activated;

reg             reset;
reg             reset_1cdc;
reg             reset_1clk;
reg             reset_2cdc;
reg             reset_2clk;

reg [1:0]       cam1_full_cdc;
reg [1:0]       cam2_full_cdc;


// Reset on whenever either FIFO is full
always @(posedge COMBINED_CLK or posedge ARESET) begin
    if(ARESET) begin
        reset = 1'b1;
    end
    else begin
        cam1_full_cdc <= {cam1_full_cdc[0], cam1_full};
        cam2_full_cdc <= {cam2_full_cdc[0], cam2_full};
        if(cam1_full_cdc[1] || cam2_full_cdc[1]) begin
            reset = 1'b1;
        end
        else begin
            reset = 1'b0;

        end
    end
end

always @(posedge CAM1_CLK or posedge reset) begin
    if(reset) begin    
        reset_1cdc <= 1'b1;
        reset_1clk <= 1'b1;
    end else begin
        reset_1cdc <= reset;
        reset_1clk <= reset_1cdc;
    end
end

always @(posedge CAM2_CLK or posedge reset) begin
    if(reset) begin    
        reset_2cdc <= 1'b1;
        reset_2clk <= 1'b1;
    end else begin
        reset_2cdc <= reset;
        reset_2clk <= reset_2cdc;
    end
end

// ===== Write enable control. =====
// Usually follows the input DE signal, but after the first 
// reset we wait for 4 complete frames (VS rising and falling edges)
// to ensure we start at the beginning of a frame

always @(posedge CAM1_CLK) begin
    cam1_vs_r <= CAM1_VS; // no reset for these!
    // otherwise, it could immediately act as a VSync rising edge
    // if reset is released while VSync is high
end

assign cam1_vs_rising = (CAM1_VS && ~(cam1_vs_r));
assign cam1_vs_falling = ((~CAM1_VS) && cam1_vs_r);

always @(posedge CAM1_CLK or posedge reset_1clk) begin
    if(reset_1clk) begin
        cam1_activated <= 1'b0;
        cam1_valid_frame_count <= 2'd0;
        cam1_in_vactive <= 1'b0;
    end else begin
        if(!cam1_activated) begin
            if(!cam1_in_vactive) begin
                // in vblanking, waiting for vsync rising edge
                if(cam1_vs_rising) begin
                    cam1_in_vactive <= 1'b1;
                end
            end
            else begin
                // in vactive, waiting for vsync falling edge
                if(cam1_vs_falling) begin
                    cam1_in_vactive <= 1'b0;
                    if(cam1_valid_frame_count == 2'd3) begin
                        // already got our 4 frames
                        cam1_activated <= 1'b1;
                    end
                    else begin
                        cam1_valid_frame_count <= cam1_valid_frame_count + 2'd1;
                    end
                end
            end
        end
    end
end

assign cam1_write_enable = (CAM1_DE && cam1_activated);


always @(posedge CAM2_CLK) begin
    cam2_vs_r <= CAM2_VS;
end

assign cam2_vs_rising = (CAM2_VS && ~(cam2_vs_r));
assign cam2_vs_falling = ((~CAM2_VS) && cam2_vs_r);

always @(posedge CAM2_CLK or posedge reset_2clk) begin
    if(reset_2clk) begin
        cam2_activated <= 1'b0;
        cam2_valid_frame_count <= 2'd0;
        cam2_in_vactive <= 1'b0;
    end else begin
        if(!cam2_activated) begin
            if(!cam2_in_vactive) begin
                // in vblanking, waiting for vsync rising edge
                if(cam2_vs_rising) begin
                    cam2_in_vactive <= 1'b1;
                end
            end
            else begin
                // in vactive, waiting for vsync falling edge
                if(cam2_vs_falling) begin
                    cam2_in_vactive <= 1'b0;
                    if(cam2_valid_frame_count == 2'd3) begin
                        // already got our 4 frames
                        cam2_activated <= 1'b1;
                    end
                    else begin
                        cam2_valid_frame_count <= cam2_valid_frame_count + 2'd1;
                    end
                end
            end
        end
    end
end

assign cam2_write_enable = (CAM2_DE && cam2_activated);

// ===== read control =====
// after both FIFOs have at least one line's worth of data, 
// trigger a read out of one line from each 
reg outputting;
reg [9:0] output_count;
always @(posedge COMBINED_CLK or posedge reset) begin
    if(reset) begin
        outputting <= 1'b0;
        output_count <= 'd0;
        cam_read_sel <= 1'b0;
    end else begin
        if (!outputting) begin
            if ((cam1_read_count >= LINE_WIDTH) && (cam2_read_count >= LINE_WIDTH)) begin
                outputting <= 1'b1;
                output_count <= 'd0;
                cam_read_sel <= 1'b0;
            end
        end else begin
            output_count <= output_count + 'd1;
            if (output_count < (LINE_WIDTH-1)) begin
                cam_read_sel <= 1'b0;
            end else if (output_count < (TOTAL_LINE_WIDTH-1)) begin
                cam_read_sel <= 1'b1;
            end else begin
                outputting <= 1'b0;
            end
        end
            
    end
end

assign cam1_read_enable = (outputting && (cam_read_sel == 1'b0));
assign cam2_read_enable = (outputting && (cam_read_sel == 1'b1));

// ===== Output data enable =====
// The data is output one clock cycle after it is requested
// So we can use the data read enable with one clock delay
// as our data out enable
reg cam1_read_en_r;
reg cam2_read_en_r;
always @(posedge COMBINED_CLK) begin
    cam1_read_en_r <= cam1_read_enable;
    cam2_read_en_r <= cam2_read_enable;
end

assign COMBINED_DE = (cam1_read_en_r || cam2_read_en_r);

// ===== Output data selection =====
// The data out is chosen between the two fifos
// Remember that the data is output one cycle after read enable is high
// We can use one of the two read enables (registered) as they are never 
// going to both be high at the same time
assign COMBINED_DATA = cam2_read_en_r ? cam2_read_data : cam1_read_data;

// ===== Output vertical sync =====
// If output VS is low, wait for both cam streams incoming VS to go high
// and change output VS to high.
// If output VS is high, wait for both cam streams VS to go low, and
// for all data to be finished outputting from the FIFOs then set output
// VS low after a short delay.
reg [3:0] vs_cycle_wait;
reg [1:0] cam1_vs_cc_r;
reg [1:0] cam2_vs_cc_r;
reg vs_out;
always @(posedge COMBINED_CLK or posedge reset) begin
    if(reset) begin
        vs_cycle_wait <= 'd0;
        vs_out <= 1'b0;
        cam1_vs_cc_r <= 2'b00;
        cam2_vs_cc_r <= 2'b00;
    end else begin
        // input vertical sync clock domain crossing
        cam1_vs_cc_r <= {cam1_vs_cc_r[0], CAM1_VS};
        cam2_vs_cc_r <= {cam2_vs_cc_r[0], CAM2_VS};
        // output vertical sync control
        if(!vs_out) begin
            if(cam1_vs_cc_r[1] && cam2_vs_cc_r[1]) begin
                vs_out <= 1'b1;
            end
        end else begin
            if((!cam1_vs_cc_r[1]) && (!cam2_vs_cc_r[1]) && (!COMBINED_DE)) begin
                if(vs_cycle_wait == 'd15) begin
                    vs_cycle_wait <= 'd0;
                    vs_out <= 1'b0;
                end else begin
                    vs_cycle_wait <= vs_cycle_wait + 'd1;
                end
            end
        end
    end 
end

assign COMBINED_VS = vs_out;

/*
asynchronous_fifo #(.DEPTH(1024), .DATA_WIDTH(8), .COUNT_WIDTH(10))
u_fifo_cam1(
    .WrReset(reset),
    .RdReset(reset),

    .WrClk(CAM1_CLK),
    .WrEn(cam1_write_enable),
    .Data(CAM1_DATA),
    .Full(cam1_full),

    .RdClk(COMBINED_CLK),
    .RdEn(cam1_read_enable),
    .Q(cam1_read_data),
    .Empty(cam1_empty),

    .Rnum(cam1_read_count)
);

asynchronous_fifo #(.DEPTH(1024), .DATA_WIDTH(8), .COUNT_WIDTH(10))
u_fifo_cam2(
    .WrReset(reset),
    .RdReset(reset),

    .WrClk(CAM2_CLK),
    .WrEn(cam2_write_enable),
    .Data(CAM2_DATA),
    .Full(cam2_full),

    .RdClk(COMBINED_CLK),
    .RdEn(cam2_read_enable),
    .Q(cam2_read_data),
    .Empty(cam2_empty),

    .Rnum(cam2_read_count)
);
*/

fifo_top u_fifo_cam1(
    .WrReset(reset_1clk || cam1_vs_rising), //input WrReset
    .RdReset(reset || cam1_vs_rising), //input RdReset

    .WrClk(CAM1_CLK), //input WrClk
    .WrEn(cam1_write_enable), //input WrEn
    .Data(CAM1_DATA), //input [7:0] Data
    .Full(cam1_full), //output Full

    .RdClk(COMBINED_CLK), //input RdClk
    .RdEn(cam1_read_enable), //input RdEn
    .Q(cam1_read_data), //output [7:0] Q
    .Empty(cam1_empty), //output Empty
    .Rnum(cam1_read_count) //output [10:0] Rnum
);

fifo_top u_fifo_cam2(
    .WrReset(reset_2clk || cam2_vs_rising), //input WrReset
    .RdReset(reset || cam2_vs_rising), //input RdReset

    .WrClk(CAM2_CLK), //input WrClk
    .WrEn(cam2_write_enable), //input WrEn
    .Data(CAM2_DATA), //input [7:0] Data
    .Full(cam2_full), //output Full

    .RdClk(COMBINED_CLK), //input RdClk
    .RdEn(cam2_read_enable), //input RdEn
    .Q(cam2_read_data), //output [7:0] Q
    .Empty(cam2_empty), //output Empty
    .Rnum(cam2_read_count) //output [10:0] Rnum
);


assign CAM1_EMPTY = cam1_empty;
assign CAM2_EMPTY = cam2_empty;
assign CAM1_ACTIVATED = cam1_activated;
assign CAM2_ACTIVATED = cam2_activated;
assign COMBINED_OUTPUTTING = outputting;

endmodule