`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
);

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;

// ===== Write enable control. =====
// Usually follows the input DE signal, but after the first 
// reset we wait for a VS high edge to ensure that we start
// at the beginning of a frame

reg cam1_saw_vsync;
reg cam1_vs_r;
always @(posedge CAM1_CLK or posedge ARESET) begin
    if(ARESET) begin
        cam1_saw_vsync <= 1'b0;
        cam1_vs_r <= 1'b0;
    end else begin
        cam1_vs_r <= CAM1_VS;
        if(CAM1_VS && ~(cam1_vs_r)) begin // rising edge
            cam1_saw_vsync <= 1'b1;
        end
    end
end

assign cam1_write_enable = (CAM1_DE && cam1_saw_vsync);

reg cam2_saw_vsync;
reg cam2_vs_r;
always @(posedge CAM2_CLK or posedge ARESET) begin
    if(ARESET) begin
        cam2_saw_vsync <= 1'b0;
        cam2_vs_r <= 1'b0;
    end else begin
        cam2_vs_r <= CAM2_VS;
        if(CAM2_VS && ~(cam2_vs_r)) begin // rising edge
            cam2_saw_vsync <= 1'b1;
        end
    end
end

assign cam2_write_enable = (CAM2_DE && cam2_saw_vsync);

// ===== 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 ARESET) begin
    if(ARESET) 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 ARESET) begin
    if(ARESET) 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(ARESET),
    .RdReset(ARESET),

    .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(ARESET),
    .RdReset(ARESET),

    .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(ARESET), //input WrReset
    .RdReset(ARESET), //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(ARESET), //input WrReset
    .RdReset(ARESET), //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
);







endmodule
