
module big_ila_monitor
#(
    parameter BI_DATA_WIDTH = 16, // number of bits to capture
    parameter MEM_WORD_WIDTH = 9, // number of samples to capture = 2^MEM_WORD_WIDTH
    parameter PRE_TRIGGER_SAMPLES = 16 // number of samples saved before the trigger event
)
(
    input   RESET  // active high asynchronous reset

,   input   CAPTURE_CLK
,   input   ARM    // get ILA ready
,   input   TRIG   // start recording samples
,   input   [BI_DATA_WIDTH-1:0] DATA_IN // data to be captured

,   input   DATA_READ_CLK
,   output  CAPTURE_DONE // ready to send data out
,   output  [MEM_WORD_WIDTH-1:0] TRIG_POINT // memory address where trigger occurred
,   output  [BI_DATA_WIDTH-1:0] DATA_OUT // previously captured data sending out
,   input   DATA_READ_ENABLE // acknowledge reading a single data point
);

localparam MEM_WORD_DEPTH = 2**MEM_WORD_WIDTH;

localparam  POST_TRIGGER_SAMPLES = (MEM_WORD_DEPTH - PRE_TRIGGER_SAMPLES);


localparam  BI_STATE_IDLE = 'd0,
            BI_STATE_ARMED = 'd1,
            BI_STATE_TRIGD = 'd2,
            BI_STATE_READOUT = 'd3;

localparam  BI_READOUT_STATE_IDLE = 'd0,
            BI_READOUT_STATE_CAPTURE_DONE = 'd1,
            BI_READOUT_STATE_WAIT_CAPTURE_RELEASE = 'd2;

reg [1:0] bi_state = BI_STATE_IDLE;
reg [1:0] bi_readout_state = BI_READOUT_STATE_IDLE;

reg [MEM_WORD_WIDTH:0] stored_samples_after_trigger;
reg [MEM_WORD_WIDTH:0] sent_samples;

reg [MEM_WORD_WIDTH-1:0] capture_pointer;
reg [MEM_WORD_WIDTH-1:0] readout_pointer;
reg [MEM_WORD_WIDTH-1:0] next_readout_pointer;

reg [MEM_WORD_WIDTH-1:0] trig_point;
reg [MEM_WORD_WIDTH-1:0] trig_point_cdc, trig_point_datareadclk;

reg capture_done;
reg capture_done_cdc, capture_done_datareadclk;

reg readout_done; // sent out entire capture buffer
reg readout_done_cdc, readout_done_captureclk;

reg output_valid;


// clock domain crossing
always @(posedge CAPTURE_CLK or posedge RESET) begin
    if(RESET) begin
        readout_done_cdc <= 1'b0;
        readout_done_captureclk <= 1'b0;
    end
    else begin
        readout_done_cdc <= readout_done;
        readout_done_captureclk <= readout_done_cdc;
    end
end

always @(posedge DATA_READ_CLK or posedge RESET) begin
    if(RESET) begin
        capture_done_cdc <= 1'b0;
        capture_done_datareadclk <= 1'b0;

        trig_point_cdc <= 'd0;
        trig_point_datareadclk <= 'd0;
    end
    else begin
        capture_done_cdc <= capture_done;
        capture_done_datareadclk <= capture_done_cdc;

        trig_point_cdc <= trig_point;
        trig_point_datareadclk <= trig_point_cdc;
    end
end


always @(posedge CAPTURE_CLK or posedge RESET) begin
    if(RESET) begin
        bi_state <= BI_STATE_IDLE;
        capture_done <= 1'b0;
    end else begin
        case(bi_state)
        BI_STATE_IDLE: begin
            if(ARM) begin
                bi_state <= BI_STATE_ARMED;
            end
        end
        BI_STATE_ARMED: begin
            if(~ARM) begin
                bi_state <= BI_STATE_IDLE;
            end else if(TRIG) begin
                bi_state <= BI_STATE_TRIGD;
            end
        end
        BI_STATE_TRIGD: begin
            if(stored_samples_after_trigger >= (POST_TRIGGER_SAMPLES-1)) begin
                bi_state <= BI_STATE_READOUT;
                capture_done <= 1'b1;
            end
        end
        BI_STATE_READOUT: begin
            if(readout_done_captureclk) begin
                bi_state <= BI_STATE_IDLE;
                capture_done <= 1'b0;
            end
        end
        endcase
    end 
end

always @(posedge CAPTURE_CLK or posedge RESET) begin
    if(RESET) begin
        capture_pointer <= 'd0;
        stored_samples_after_trigger <= 'd0;
        trig_point <= 'd0;
    end else begin
        case(bi_state)
        BI_STATE_IDLE: begin
            capture_pointer <= 'd0;
            stored_samples_after_trigger <= 'd0;
            trig_point <= 'd0;
        end
        BI_STATE_ARMED: begin
            capture_pointer <= capture_pointer + 'd1;
            stored_samples_after_trigger <= 'd0;
            if(TRIG) begin
                // next state will be BI_STATE_TRIGD
                // so we can now save the trigger point
                trig_point <= capture_pointer + 'd1;
            end
        end
        BI_STATE_TRIGD: begin
            capture_pointer <= capture_pointer + 'd1;
            stored_samples_after_trigger <= stored_samples_after_trigger + 'd1;
        end
        BI_STATE_READOUT: begin
            capture_pointer <= 'd0;
            stored_samples_after_trigger <= 'd0;
        end
        endcase
    end
end    

always @(posedge DATA_READ_CLK or posedge RESET) begin
    if(RESET) begin
        readout_done <= 1'b0;
        readout_pointer <= 'd0;
        output_valid <= 1'b0;
        bi_readout_state <= BI_READOUT_STATE_IDLE;
    end
    else begin
        case(bi_readout_state)
        BI_READOUT_STATE_IDLE: begin
            if(capture_done_datareadclk) begin
                bi_readout_state <= BI_READOUT_STATE_CAPTURE_DONE;
                readout_pointer <= 'd0;
                output_valid <= 1'b1;
            end
        end
        BI_READOUT_STATE_CAPTURE_DONE: begin
            if(DATA_READ_ENABLE) begin
                readout_pointer <= next_readout_pointer;
                if(readout_pointer == (MEM_WORD_DEPTH-1)) begin
                    output_valid <= 1'b0;
                    readout_done <= 1'b1;
                    bi_readout_state <= BI_READOUT_STATE_WAIT_CAPTURE_RELEASE;
                end
            end
        end
        BI_READOUT_STATE_WAIT_CAPTURE_RELEASE: begin
            if(~capture_done_datareadclk) begin
                readout_done <= 1'b0;
                bi_readout_state <= BI_READOUT_STATE_IDLE;
            end
        end
        default: begin
            readout_done <= 1'b0;
            readout_pointer <= 'd0;
            output_valid <= 1'b0;
            bi_readout_state <= BI_READOUT_STATE_IDLE;
        end
        endcase
    end
end

always @(*) begin
    if(output_valid && DATA_READ_ENABLE) begin
        next_readout_pointer <= readout_pointer + 'd1;
    end else begin
        next_readout_pointer <= readout_pointer;
    end
end

// ===========  MEMORY  =============
reg [BI_DATA_WIDTH-1:0] capture_memory [0:MEM_WORD_DEPTH-1];
wire write_enable = (bi_state == BI_STATE_ARMED || bi_state == BI_STATE_TRIGD);
reg [BI_DATA_WIDTH-1:0] capture_data_out;
always @(posedge CAPTURE_CLK) begin
    if(write_enable) begin
        capture_memory[capture_pointer] <= DATA_IN;
    end
end
always @(posedge DATA_READ_CLK) begin
    capture_data_out <= capture_memory[next_readout_pointer];
end

assign CAPTURE_DONE = output_valid;
assign TRIG_POINT = trig_point_datareadclk;
assign DATA_OUT = capture_data_out;


endmodule
