// Copyright © 2019-2023
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

`include "VX_platform.vh"

`TRACING_OFF
module VX_scope_tap #(     
    parameter SCOPE_ID  = 0,    // scope identifier
    parameter SCOPE_IDW = 8,    // scope identifier width
    parameter TRIGGERW  = 0,    // trigger signals width
    parameter PROBEW    = 0,    // probe signal width
    parameter SIZE      = 256,  // trace buffer size
    parameter IDLE_CTRW = 16    // idle time between triggers counter width
) ( 
    input wire clk,
    input wire reset,
    input wire start,
    input wire stop,
    input wire [TRIGGERW-1:0] triggers,
    input wire [PROBEW-1:0] probes,
    input wire bus_in,
    output wire bus_out    
);
    localparam TX_DATAW     = 64;
    localparam TX_DATA_BITS = `LOG2UP(TX_DATAW);
    localparam DATAW        = PROBEW + TRIGGERW;    
    localparam DATA_BITS    = `LOG2UP(DATAW);
    localparam ADDRW        = `CLOG2(SIZE);
    localparam TRIGGER_ENABLE = (TRIGGERW != 0);
    localparam MAX_IDLE_CTR = (2 ** IDLE_CTRW) - 1;    
    
    localparam CTRL_STATE_IDLE = 2'd0;
    localparam CTRL_STATE_RECV = 2'd1;
    localparam CTRL_STATE_CMD  = 2'd2;
    localparam CTRL_STATE_SEND = 2'd3;
    localparam CTRL_STATE_BITS = 2;

    localparam TAP_STATE_IDLE  = 2'd0;
    localparam TAP_STATE_WAIT  = 2'd1;
    localparam TAP_STATE_RUN   = 2'd2;
    localparam TAP_STATE_BITS  = 2;

    localparam CMD_GET_WIDTH = 3'd0;
    localparam CMD_GET_COUNT = 3'd1;
    localparam CMD_GET_START = 3'd2;
    localparam CMD_GET_DATA  = 3'd3;
    localparam CMD_SET_START = 3'd4;
    localparam CMD_SET_STOP  = 3'd5;
    localparam CMD_TYPE_BITS = 3;

    localparam GET_TYPE_WIDTH = 2'd0;
    localparam GET_TYPE_COUNT = 2'd1;
    localparam GET_TYPE_START = 2'd2;
    localparam GET_TYPE_DATA  = 2'd3;
    localparam GET_TYPE_BITS  = 2;

    `NO_RW_RAM_CHECK reg [DATAW-1:0] data_store [SIZE-1:0];
    `NO_RW_RAM_CHECK reg [IDLE_CTRW-1:0] delta_store [SIZE-1:0];

    reg [TRIGGERW-1:0] prev_triggers;
    reg [IDLE_CTRW-1:0] delta;
    reg [63:0] timestamp, start_time;

    reg [ADDRW-1:0] waddr, waddr_end;

    reg cmd_start, delta_flush;

    reg [63:0] start_delay, delay_cntr;

    reg [TAP_STATE_BITS-1:0] tap_state;
    reg [CTRL_STATE_BITS-1:0] ctrl_state;
    reg [GET_TYPE_BITS-1:0] get_type;
        
    reg [TX_DATA_BITS-1:0] ser_tx_ctr;
    reg [DATA_BITS-1:0] read_offset;
    reg [ADDRW-1:0] raddr;
    reg read_data;

    //
    // trace capture
    //

    wire [ADDRW-1:0] raddr_n = raddr + 1;

    wire [ADDRW:0] count = (ADDRW+1)'(waddr) + 1;

    always @(posedge clk) begin
        if (reset) begin
            tap_state       <= TAP_STATE_IDLE;
            raddr           <= '0;
            waddr           <= '0;
            delta           <= '0;
            prev_triggers   <= '0;
            read_offset     <= '0;
            read_data       <= 0;
            timestamp       <= '0;
        end else begin
            timestamp <= timestamp + 1;

            case (tap_state)
            TAP_STATE_IDLE: begin
                if (start || cmd_start) begin                    
                    delta       <= '0;
                    delta_flush <= 1;            
                    if (0 == start_delay) begin
                        tap_state  <= TAP_STATE_RUN;
                        start_time <= timestamp;
                    `ifdef DBG_TRACE_SCOPE
                        `TRACE(2, ("%d: *** scope #%0d: recording start - time=%0d\n", $time, SCOPE_ID, timestamp));
                    `endif
                    end else begin
                        tap_state <= TAP_STATE_WAIT;
                        delay_cntr <= start_delay;                    
                    `ifdef DBG_TRACE_SCOPE
                        `TRACE(2, ("%d: *** scope #%0d: delayed start - time=%0d\n", $time, SCOPE_ID, start_delay));
                    `endif
                    end
                end
            end
            TAP_STATE_WAIT: begin
                delay_cntr <= delay_cntr - 1;
                if (1 == delay_cntr) begin
                    tap_state  <= TAP_STATE_RUN;
                    start_time <= timestamp;
                `ifdef DBG_TRACE_SCOPE
                    `TRACE(2, ("%d: *** scope #%0d: recording start - time=%0d\n", $time, SCOPE_ID, timestamp));
                `endif
                end
            end
            TAP_STATE_RUN: begin
                if (TRIGGER_ENABLE != 0) begin
                    if (delta_flush || (triggers != prev_triggers)) begin                        
                        data_store[waddr]  <= {probes, triggers};
                        delta_store[waddr] <= delta;
                        waddr       <= waddr + 1;
                        delta       <= '0;
                        delta_flush <= 0;
                    end else begin
                        delta       <= delta + 1;
                        delta_flush <= (delta == (MAX_IDLE_CTR-1));
                    end
                    prev_triggers <= triggers;
                end else begin                    
                    data_store[waddr]  <= {probes, triggers};
                    delta_store[waddr] <= '0;
                    waddr <= waddr + 1;
                end
                if (stop || (waddr >= waddr_end)) begin
                    waddr <= waddr;
                `ifdef DBG_TRACE_SCOPE
                    `TRACE(2, ("%d: *** scope #%0d: recording stop - waddr=(%0d, %0d)\n", $time, SCOPE_ID, waddr, waddr_end));
                `endif
                    tap_state <= TAP_STATE_IDLE;                    
                end
            end
            default:;
            endcase
            
            if (ctrl_state == CTRL_STATE_SEND 
             && get_type == GET_TYPE_DATA
             && ser_tx_ctr == 0)  begin
                if (~read_data) begin
                    read_data <= 1;
                end else begin
                    if (DATAW > TX_DATAW) begin
                    `IGNORE_WARNINGS_BEGIN                        
                        if (read_offset < DATA_BITS'(DATAW-TX_DATAW)) begin
                            read_offset <= read_offset + DATA_BITS'(TX_DATAW);
                        end else begin
                            raddr       <= raddr_n;                            
                            read_data   <= 0;
                            read_offset <= '0;
                        end
                    `IGNORE_WARNINGS_END
                    end else begin
                        raddr <= raddr_n;
                        read_data <= 0;
                    end                    
                    if (raddr_n == waddr) begin
                        raddr <= 0;
                    end
                end
            end
        end
    end

    //
    // command controller
    //
    
    reg bus_out_r;
    
    reg [TX_DATAW-1:0] ser_buf_in;
    wire [TX_DATAW-1:0] ser_buf_in_n = {ser_buf_in[TX_DATAW-2:0], bus_in};
    `UNUSED_VAR (ser_buf_in)

    wire [CMD_TYPE_BITS-1:0] cmd_type = ser_buf_in[CMD_TYPE_BITS-1:0];
    wire [SCOPE_IDW-1:0] cmd_scope_id = ser_buf_in_n[CMD_TYPE_BITS +: SCOPE_IDW];
    wire [TX_DATAW-CMD_TYPE_BITS-SCOPE_IDW-1:0] cmd_data = ser_buf_in[TX_DATAW-1:CMD_TYPE_BITS+SCOPE_IDW];

    wire [TX_DATAW-1:0] data_chunk = TX_DATAW'(DATAW'(data_store[raddr] >> read_offset));
    wire [TX_DATAW-1:0] get_data = read_data ? data_chunk : TX_DATAW'(delta_store[raddr]);
    
    always @(posedge clk) begin
        if (reset) begin
            ctrl_state  <= CTRL_STATE_IDLE;
            cmd_start   <= 0;
            start_delay <= '0;
            waddr_end   <= ADDRW'(SIZE-1);               
            bus_out_r   <= 0; 
        end else begin
            bus_out_r   <= 0;             
            cmd_start   <= 0;

            case (ctrl_state)
            CTRL_STATE_IDLE: begin
                if (bus_in) begin
                    ctrl_state <= CTRL_STATE_RECV;
                end
                ser_tx_ctr <= TX_DATA_BITS'(TX_DATAW-1);
            end
            CTRL_STATE_RECV: begin
                ser_tx_ctr <= ser_tx_ctr - 1;
                ser_buf_in <= ser_buf_in_n;
                if (ser_tx_ctr == 0) begin
                    ctrl_state <= (cmd_scope_id == SCOPE_ID) ? CTRL_STATE_CMD : CTRL_STATE_IDLE;
                end
            end
            CTRL_STATE_CMD: begin                
                ctrl_state <= CTRL_STATE_IDLE;
                case (cmd_type) 
                CMD_SET_START: begin
                    start_delay <= 64'(cmd_data);
                    cmd_start   <= 1;
                end
                CMD_SET_STOP: begin
                    waddr_end <= ADDRW'(cmd_data);
                end
                CMD_GET_WIDTH,
                CMD_GET_START,
                CMD_GET_COUNT,
                CMD_GET_DATA: begin 
                    ctrl_state <= CTRL_STATE_SEND;                        
                    get_type   <= GET_TYPE_BITS'(cmd_type);
                    ser_tx_ctr <= TX_DATA_BITS'(TX_DATAW-1);
                    bus_out_r  <= 1;
                end
                default:;
                endcase            
            `ifdef DBG_TRACE_SCOPE
                `TRACE(2, ("%d: *** scope #%0d: CMD: type=%0d\n", $time, SCOPE_ID, cmd_type));
            `endif
            end
            CTRL_STATE_SEND: begin
                ser_tx_ctr <= ser_tx_ctr - 1;
                case (get_type)
                GET_TYPE_WIDTH: begin
                    bus_out_r <= 1'(DATAW >> ser_tx_ctr);
                `ifdef DBG_TRACE_SCOPE
                    if (ser_tx_ctr == 0) begin
                        `TRACE(2, ("%d: *** scope #%0d: SEND width=%0d\n", $time, SCOPE_ID, DATAW));
                    end     
                `endif                    
                end
                GET_TYPE_COUNT: begin
                    bus_out_r <= 1'(count >> ser_tx_ctr);
                `ifdef DBG_TRACE_SCOPE
                    if (ser_tx_ctr == 0) begin
                        `TRACE(2, ("%d: *** scope #%0d: SEND count=%0d\n", $time, SCOPE_ID, count));
                    end     
                `endif                    
                end
                GET_TYPE_START: begin
                    bus_out_r <= 1'(start_time >> ser_tx_ctr);                    
                `ifdef DBG_TRACE_SCOPE
                    if (ser_tx_ctr == 0) begin
                        `TRACE(2, ("%d: *** scope #%0d: SEND start=%0d\n", $time, SCOPE_ID, start_time));
                    end     
                `endif                    
                end
                GET_TYPE_DATA: begin
                    bus_out_r <= 1'(get_data >> ser_tx_ctr);
                `ifdef DBG_TRACE_SCOPE
                    if (ser_tx_ctr == 0) begin
                        `TRACE(2, ("%d: *** scope #%0d: SEND data=%0d\n", $time, SCOPE_ID, get_data));
                    end     
                `endif                    
                end
                default:;
                endcase
                if (ser_tx_ctr == 0) begin
                    ctrl_state <= CTRL_STATE_IDLE;
                end      
            end
            default:;
            endcase
        end        
    end

    assign bus_out = bus_out_r;

endmodule
`TRACING_ON
