/*******************************************************************************
 * Module: transport
 * Date: 2015-07-11  
 * Author: Alexey     
 * Description: sata transport layer implementation
 *
 * Copyright (c) 2015 Elphel, Inc.
 * transport.v is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * transport.v file is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/> .
 *
 * Additional permission under GNU GPL version 3 section 7:
 * If you modify this Program, or any covered work, by linking or combining it
 * with independent modules provided by the FPGA vendor only (this permission
 * does not extend to any 3-rd party modules, "soft cores" or macros) under
 * different license terms solely for the purpose of generating binary "bitstream"
 * files and/or simulating the code, the copyright holders of this Program give
 * you the right to distribute the covered work without those independent modules
 * as long as the source code for them is available from the FPGA vendor free of
 * charge, and there is no dependence on any encrypted modules for simulating of
 * the combined code. This permission applies to you if the distributed code
 * contains all the components and scripts required to completely simulate it
 * with at least one of the Free Software programs.
 *******************************************************************************/
module transport #(
    parameter DATA_BYTE_WIDTH = 4
)
(
    input   wire    clk,
    input   wire    rst,

    // link layer (LL) control

    // issue a frame
    output  wire    frame_req,
    // frame started to be transmitted
    input   wire    frame_ack,
    // frame issue was rejected because of incoming frame with higher priority
    input   wire    frame_rej,
    // LL is not ready to receive a frame request. frame_req shall be low if busy is asserted
    input   wire    frame_busy,
    // frame was transmitted w/o probles and successfully received @ a device side
    input   wire    frame_done_good,
    // frame was transmitted, but device messages of problems with receiving
    input   wire    frame_done_bad,

    // LL reports of an incoming frame transmission. They're always allowed and have the highest priority
    input   wire    incom_start,
    // LL reports of a completion of an incoming frame transmission.
    input   wire    incom_done,
    // LL reports of errors in current FIS
    input   wire    incom_invalidate, // TODO
    // TL analyzes FIS and returnes if FIS makes sense.
    output  wire    incom_ack_good,
    // ... and if it doesn't
    output  wire    incom_ack_bad,

    // transmission interrupts
    // TL demands to brutally cancel current transaction TODO
    output  wire    sync_escape_req,
    // acknowlegement of a successful reception TODO
    input   wire    sync_escape_ack,
    // TL demands to stop current recieving session TODO
    output  wire    incom_stop_req,

    // controls from a command layer (CL)
    // FIS type, ordered by CL
    input   wire    [2:0]   cmd_type,
    // request itself
    input   wire            cmd_val,
    // destination port
    input   wire    [3:0]   cmd_port,
    // if cmd got into processing, busy asserts, when TL is ready to receive a new cmd, busy deasserts
    output  wire            cmd_busy,
    // indicates completion of a request
    output  wire            cmd_done_good,
    // request is completed, but device wasn't able to receive
    output  wire            cmd_done_bad,

    // shadow registers TODO reduce outputs/inputs count. or not
    // actual registers are stored in CL
    input   wire    [31:0]  sh_data_in,
    input   wire    [15:0]  sh_feature_in,
    input   wire    [47:0]  sh_lba_in,
    input   wire    [15:0]  sh_count_in,
    input   wire    [7:0]   sh_command_in,
    input   wire    [7:0]   sh_dev_in,
    input   wire    [7:0]   sh_control_in,
    input   wire            sh_autoact_in,
    input   wire            sh_inter_in,
    input   wire            sh_dir_in,
    input   wire    [63:0]  sh_dma_id_in,
    input   wire    [31:0]  sh_buf_off_in,
    input   wire    [31:0]  sh_dma_cnt_in,
    input   wire            sh_notif_in,
    input   wire    [15:0]  sh_tran_cnt_in,
    input   wire    [3:0]   sh_port_in,
    // TL decodes register writes and sends corresponding issues to CL
    output  wire    [47:0]  sh_lba_out,
    output  wire    [15:0]  sh_count_out,
    output  wire    [7:0]   sh_command_out,
    output  wire    [7:0]   sh_err_out,
    output  wire    [7:0]   sh_status_out,
    output  wire    [7:0]   sh_estatus_out, // E_Status
    output  wire    [7:0]   sh_dev_out,
    output  wire    [3:0]   sh_port_out,
    output  wire            sh_inter_out,
    output  wire            sh_dir_out,
    output  wire    [63:0]  sh_dma_id_out,
    output  wire    [31:0]  sh_dma_off_out,
    output  wire    [31:0]  sh_dma_cnt_out,
    output  wire    [15:0]  sh_tran_cnt_out, // Transfer Count
    output  wire            sh_notif_out,
    output  wire            sh_autoact_out,
    output  wire            sh_lba_val_out,
    output  wire            sh_count_val_out,
    output  wire            sh_command_val_out,
    output  wire            sh_err_val_out,
    output  wire            sh_status_val_out,
    output  wire            sh_estatus_val_out, // E_Status
    output  wire            sh_dev_val_out,
    output  wire            sh_port_val_out,
    output  wire            sh_inter_val_out,
    output  wire            sh_dir_val_out,
    output  wire            sh_dma_id_val_out,
    output  wire            sh_dma_off_val_out,
    output  wire            sh_dma_cnt_val_out,
    output  wire            sh_tran_cnt_val_out, // Transfer Count
    output  wire            sh_notif_val_out,
    output  wire            sh_autoact_val_out,


    // shows if dma activate was received (a pulse)
    output  wire            got_dma_activate,
    output  wire    [3:0]   got_dma_activate_port,
    // if CL made a mistake in controlling data FIS length
    output  wire            data_limit_exceeded,

    // LL data
    // data inputs from LL
    input   wire    [DATA_BYTE_WIDTH*8 - 1:0]   ll_data_in,
    input   wire    [DATA_BYTE_WIDTH/2 - 1:0]   ll_data_mask_in,
    input   wire                                ll_data_val_in,
    input   wire                                ll_data_last_in,
    // transport layer tells if its inner buffer is almost full
    output  wire                                ll_data_busy_out,

    // data outputs to LL
    output  wire    [DATA_BYTE_WIDTH*8 - 1:0]   ll_data_out,
    // not implemented yet TODO
    output  wire    [DATA_BYTE_WIDTH/2 - 1:0]   ll_data_mask_out,
    output  wire                                ll_data_last_out,
    output  wire                                ll_data_val_out,
    input   wire                                ll_data_strobe_in,

    // CL data
    // required content is bypassed from ll, other is trimmed
    // only content of data FIS, starting from 1st dword. Max burst = 2048 dwords
    // data outputs to CL
    output  wire    [DATA_BYTE_WIDTH*8 - 1:0]   cl_data_out,
    output  wire    [DATA_BYTE_WIDTH/2 - 1:0]   cl_data_mask_out,
    output  wire                                cl_data_val_out,
    output  wire                                cl_data_last_out,
    // transport layer tells if its inner buffer is almost full
    input   wire                                cl_data_busy_in,

    // data inputs from CL
    input   wire    [DATA_BYTE_WIDTH*8 - 1:0]   cl_data_in,
    // not implemented yet TODO
    input   wire    [DATA_BYTE_WIDTH/2 - 1:0]   cl_data_mask_in,
    input   wire                                cl_data_last_in,
    input   wire                                cl_data_val_in,
    output  wire                                cl_data_strobe_out,


    // watchdog timers calls. They shall be handled in TL, but for debug purposes are wired to the upper level
    // when eof acknowledgement is not received after sent FIS
    output  wire    watchdog_eof,
    // when too many dwords is in current FIS
    output  wire    watchdog_dwords
);

reg [7:0]   state;

//TODO
assign sync_escape_req = 1'b0;
assign incom_stop_req = 1'b0;
assign cl_data_strobe_out = 1'b0;

// How much time does device have to response on EOF
parameter [13:0]  WATCHDOG_EOF_LIMIT = 14'd1000;

// must have a local reserve copy of shadow registers in case of 
// a) received FIS with incorrect length (seems like an error, so no registers shall be written)
// b) incoming transmission overrides outcoming, so we have to latch outcoming values in real shadow registers
//    while storing incoming ones in the local copy
reg [47:0]  loc_lba;
reg [15:0]  loc_count;
reg [7:0]   loc_command;
reg [7:0]   loc_err;
reg [7:0]   loc_status;
reg [7:0]   loc_estatus; // E_Status
reg [7:0]   loc_dev;
reg [3:0]   loc_port;
reg         loc_inter;
reg         loc_dir;
reg [63:0]  loc_dma_id;
reg [31:0]  loc_dma_off;
reg [31:0]  loc_dma_cnt;
reg [15:0]  loc_tran_cnt; // Transfer Count
reg         loc_notif;
reg         loc_autoact;


// latching cmd inputs
reg [3:0]   cmd_port_r;
reg [2:0]   cmd_type_r;
always @ (posedge clk)
    cmd_type_r  <= rst ? 3'h0 : cmd_val ? cmd_type : cmd_type_r;
always @ (posedge clk)
    cmd_port_r  <= rst ? 4'h0 : cmd_val ? cmd_port : cmd_port_r;

// incomming command type decode, shows which type of FIS shall be issued
localparam [2:0] CMD_TYPE_REG_DEV     = 3'h0; // Reg H2D, bit C -> 0
localparam [2:0] CMD_TYPE_REG_CMD     = 3'h1; // Reg H2D, bit C -> 1
localparam [2:0] CMD_TYPE_DMA_SETUP   = 3'h2;
localparam [2:0] CMD_TYPE_DATA        = 3'h3;
localparam [2:0] CMD_TYPE_BIST_ACT    = 3'h4;

// current header dword
wire    [31:0]  ll_header_dword;
// current dword shall be header's
wire            ll_header_val;
// if last data dword is header's
wire            ll_header_last;
// incorrect size or unmatched type of a received FIS
reg             bad_fis_received; 
// if a FIS has wrong size, make sure it would stop, universal dword counter
reg     [13:0]  dword_cnt;
// FIS dword size exceeded condition
assign  watchdog_dwords = dword_cnt == 14'd2049;
// ask for a receiving termination in case of errors
reg             incom_stop_req_timeout;

// global TL fsm
/*
    idle -----> outcoming FIS ----> outcoming ----non-data--> fill dwords -------------------------+
      |             |if rej                     |                                                  |
      |             |                           +-----data--> make header --> bypass data from CL -+
      |             V                                                                              |
      +-------> incoming FIS -------detect type---non-data--> parse dwords, write sh regs ---------+ 
                                        |                                                          |
                                        +-------------data--> get header ---> bypass data to CL -----> done
*/

localparam  STATE_IDLE           = 8'h0;
localparam  STATE_INCOMING       = 8'h1;
localparam  STATE_OUTCOMING      = 8'h2;
localparam  STATE_IN_DATA        = 8'h10; // Data FIS from device
localparam  STATE_IN_REG_1       = 8'h20; // Register FIS Device to Host: 1st dword
localparam  STATE_IN_REG_2       = 8'h21; // Register FIS Device to Host: 2nd dword
localparam  STATE_IN_REG_3       = 8'h22; // Register FIS Device to Host: 3rd dword
localparam  STATE_IN_REG_4       = 8'h23; // Register FIS Device to Host: 4th dword
localparam  STATE_IN_REG_ERR     = 8'h24; // Register FIS Device to Host: Error happened
localparam  STATE_IN_DMAA_ERR    = 8'h30; // DMA Activate: Error Happened
localparam  STATE_IN_DMAS_1      = 8'h40; // DMA Setup FIS device to host: 1st dword
localparam  STATE_IN_DMAS_2      = 8'h41; // DMA Setup FIS device to host: 2nd dword
localparam  STATE_IN_DMAS_3      = 8'h42; // DMA Setup FIS device to host: 3rd dword
localparam  STATE_IN_DMAS_4      = 8'h43; // DMA Setup FIS device to host: 4th dword
localparam  STATE_IN_DMAS_5      = 8'h44; // DMA Setup FIS device to host: 5th dword
localparam  STATE_IN_DMAS_6      = 8'h45; // DMA Setup FIS device to host: 6th dword
localparam  STATE_IN_DMAS_ERR    = 8'h46; // DMA Setup FIS device to host: Error happened
localparam  STATE_IN_BIST_1      = 8'h50; // BIST Activate FIS Device to Host: 1st dword
localparam  STATE_IN_BIST_2      = 8'h51; // BIST Activate FIS Device to Host: 2nd dword
localparam  STATE_IN_BIST_ERR    = 8'h52; // BIST Activate FIS Device to Host: Error happened
localparam  STATE_IN_PIOS_1      = 8'h60; // PIO Setup FIS: 1st dword
localparam  STATE_IN_PIOS_2      = 8'h61; // PIO Setup FIS: 2nd dword
localparam  STATE_IN_PIOS_3      = 8'h62; // PIO Setup FIS: 3rd dword
localparam  STATE_IN_PIOS_4      = 8'h63; // PIO Setup FIS: 4th dword
localparam  STATE_IN_PIOS_ERR    = 8'h64; // PIO Setup FIS: Error happened
localparam  STATE_IN_SDB_1       = 8'h70; // Set Device Bits FIS: 1st dword
localparam  STATE_IN_SDB_ERR     = 8'h70; // Set Device Bits FIS: Error happened
localparam  STATE_OUT_DATA_H     = 8'h80; // Data FIS from host: header
localparam  STATE_OUT_DATA_D     = 8'h81; // Data FIS from host: payload
localparam  STATE_OUT_REG        = 8'h90; // Register FIS Host to Device
localparam  STATE_OUT_DMAS       = 8'ha0; // DMA Setup FIS Host to Device
localparam  STATE_OUT_BIST       = 8'hb0; // BIST Activate FIS Host to Device
localparam  STATE_OUT_WAIT_RESP  = 8'hc0; // 
localparam  STATE_IN_UNRECOG     = 8'hf0; // Unrecognized FIS from Device

always @ (posedge clk)
    if (rst)
    begin
        state                   <= STATE_IDLE;
        dword_cnt               <= 14'h0;
        incom_stop_req_timeout  <= 1'b0;
        bad_fis_received        <= 1'b0;
        loc_lba                 <= 48'h0;
        loc_count               <= 32'h0;
        loc_command             <= 8'h0;
        loc_err                 <= 8'h0;
        loc_status              <= 8'h0;
        loc_estatus             <= 8'h0;
        loc_dev                 <= 8'h0;
        loc_port                <= 4'h0;
        loc_inter               <= 1'h0;
        loc_dir                 <= 1'h0;
        loc_dma_id              <= 64'h0;
        loc_dma_off             <= 32'h0;
        loc_dma_cnt             <= 32'h0;
        loc_tran_cnt            <= 16'h0;
        loc_notif               <= 1'h0;
        loc_autoact             <= 1'h0;
    end
    else
        case (state)
            STATE_IDLE:
            begin
                dword_cnt               <= 14'h0;
                incom_stop_req_timeout  <= 1'b0;
                bad_fis_received        <= 1'b0;
                if (frame_req)
                    state   <= STATE_OUTCOMING;
                else
                if (incom_start | frame_req)
                    state   <= STATE_INCOMING;
                else
                    state   <= STATE_IDLE;

                loc_lba      <= sh_lba_in;
                loc_count    <= sh_count_in;
                loc_command  <= sh_command_in;
                loc_err      <= 8'h0;
                loc_status   <= 8'h0;
                loc_estatus  <= 8'h0;
                loc_dev      <= 8'h0;
                loc_port     <= sh_port_in;
                loc_inter    <= sh_inter_in;
                loc_dir      <= sh_dir_in;
                loc_dma_id   <= sh_dma_id_in;
                loc_dma_off  <= sh_buf_off_in;
                loc_dma_cnt  <= sh_dma_cnt_in;
                loc_tran_cnt <= sh_tran_cnt_in;
                loc_notif    <= sh_notif_in;
                loc_autoact  <= sh_autoact_in;
            end

            STATE_INCOMING:
            // enter state when we're starting to get a FIS, leave after 1st dword is received
            begin
                if (ll_data_val_in)
                // if 0-th dword came
                    case (ll_data_in[7:0])
                    // act depending on packet type

                        8'h34:
                        // register 
                        begin
                            if (~ll_data_last_in)
                            begin
                                loc_port    <= ll_data_in[11:8];
                                loc_inter   <= ll_data_in[14];
                                loc_status  <= ll_data_in[23:16];
                                loc_err     <= ll_data_in[31:24];
                                state       <= STATE_IN_REG_1;
                            end
                            else
                            // an error state, too little dwords transfered
                            begin
                                bad_fis_received    <= 1'b1;
                                state               <= STATE_IDLE;
                            end
                        end

                        8'h39:
                        // DMA Activate
                        begin
                            if (~ll_data_last_in)
                            begin
                                state       <= STATE_IN_DMAA_ERR;
                                dword_cnt   <= 14'h1;
                            end
                            else
                            begin
                                // got_dma_activate - wire assigment
                                state       <= STATE_IDLE;
                            end
                        end

                        8'h41:
                        // DMA Setup
                        begin
                            if (~ll_data_last_in)
                            begin
                                loc_port    <= ll_data_in[11:8];
                                loc_dir     <= ll_data_in[13];
                                loc_inter   <= ll_data_in[14];
                                loc_autoact <= ll_data_in[15];
                                state       <= STATE_IN_DMAS_1;
                            end
                            else
                            // an error state, too little dwords transfered
                            begin
                                bad_fis_received    <= 1'b1;
                                state               <= STATE_IDLE;
                            end
                        end

                        8'h46:
                        // Data FIS
                        begin
                            if (~ll_data_last_in)
                            begin
                                loc_port    <= ll_data_in[11:8];
                                dword_cnt   <= 14'h1;
                                state       <= STATE_IN_DATA;
                            end
                            else
                            // an error state, too little dwords transfered
                            begin
                                bad_fis_received    <= 1'b1;
                                state               <= STATE_IDLE;
                            end
                        end

                        8'h58:
                        // BIST
                        begin
                            // for now skips payload, just controls length TODO
                            state   <= STATE_IN_BIST_1;
                        end

                        8'h5f:
                        // PIO setup
                        begin
                            if (~ll_data_last_in)
                            begin
                                loc_port        <= ll_data_in[11:8];
                                loc_dir         <= ll_data_in[13];
                                loc_inter       <= ll_data_in[14];
                                loc_status      <= ll_data_in[23:16];
                                loc_err         <= ll_data_in[31:24];
                                state           <= STATE_IN_PIOS_1;
                            end
                            else
                            // an error state, too little dwords transfered
                            begin
                                bad_fis_received    <= 1'b1;
                                state               <= STATE_IDLE;
                            end
                        end

                        8'ha1:
                        // Set Device Bits
                        begin
                            if (~ll_data_last_in)
                            begin
                                loc_inter       <= ll_data_in[14];
                                loc_notif       <= ll_data_in[15];
                                loc_status[2:0] <= ll_data_in[19:17];
                                loc_status[6:4] <= ll_data_in[23:21];
                                loc_err         <= ll_data_in[31:24];
                                state           <= STATE_IN_SDB_1;
                            end
                            else
                            // an error state, too little dwords transfered
                            begin
                                bad_fis_received    <= 1'b1;
                                state               <= STATE_IDLE;
                            end
                        end

                        default:
                        // no known FIS type matched
                        begin
                            dword_cnt   <= 14'h0;
                            state       <= STATE_IN_UNRECOG;
                        end
                    endcase
            end

            STATE_OUTCOMING:
            // enter state when we're issuing a FIS, leave when got an ack from ll (FIS started to transmit)
            // or if FIS won't start because of incoming transmission. In such case outcoming request parameter shall be latched TODO or not?
            begin
                dword_cnt   <= 14'h0;
                state       <= frame_rej ? STATE_INCOMING :
                               frame_ack & cmd_type_r == CMD_TYPE_REG_DEV   ? STATE_OUT_REG    :
                               frame_ack & cmd_type_r == CMD_TYPE_REG_CMD   ? STATE_OUT_REG    :
                               frame_ack & cmd_type_r == CMD_TYPE_DMA_SETUP ? STATE_OUT_DMAS   :
                               frame_ack & cmd_type_r == CMD_TYPE_DATA      ? STATE_OUT_DATA_H :
                               frame_ack & cmd_type_r == CMD_TYPE_BIST_ACT  ? STATE_OUT_BIST   :
                                                                              STATE_OUTCOMING;
            end

            STATE_IN_DATA:
            // receiving data from Data FIS, bypass it into buffer at upper level
            begin
                if (incom_done)
                // EOF received, CRC good
                begin
                    state   <= STATE_IDLE;
                end
                else 
                if (ll_data_val_in)
                begin
                    if (dword_cnt == 14'd2049)
                    // if too much data for a data FIS TODO handle this exception properly
                        state       <= STATE_IDLE; 
                    else
                    // continuing receiving data
                    begin
                        dword_cnt   <= dword_cnt + 1'b1;
                        state       <= STATE_IN_DATA;
                    end
                end
            end

            STATE_IN_REG_1:
            // receiving register FIS, dword by dword
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    // incorrect frame size
                    begin
                        bad_fis_received <= 1'b1;
                        state            <= STATE_IDLE;
                    end
                    else
                    // going to the next dword, parse current one: {Device, LBA High, LBA Mid, LBA Low}
                    begin
                        loc_lba[7:0]    <= ll_data_in[7:0];
                        loc_lba[23:16]  <= ll_data_in[15:8];
                        loc_lba[39:32]  <= ll_data_in[23:16];
                        loc_dev[7:0]    <= ll_data_in[31:24];
                        state           <= STATE_IN_REG_2;
                    end
            end

            STATE_IN_REG_2:
            // receiving register FIS, dword by dword
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    // incorrect frame size
                    begin
                        bad_fis_received <= 1'b1;
                        state            <= STATE_IDLE;
                    end
                    else
                    // going to the next dword, parse current one: {Reserved, LBA High (exp), LBA Mid (exp), LBA Low (exp)}
                    begin
                        loc_lba[15:8]   <= ll_data_in[7:0];
                        loc_lba[31:24]  <= ll_data_in[15:8];
                        loc_lba[47:40]  <= ll_data_in[23:16];
                        state           <= STATE_IN_REG_3;
                    end
            end

            STATE_IN_REG_3:
            // receiving register FIS, dword by dword
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    // incorrect frame size
                    begin
                        bad_fis_received <= 1'b1;
                        state            <= STATE_IDLE;
                    end
                    else
                    // going to the next dword, parse current one: {Reserved, Reserved, Sector Count (exp), Sector Count}
                    begin
                        loc_count[15:0] <= ll_data_in[15:0];
                        state           <= STATE_IN_REG_4;
                    end
            end

            STATE_IN_REG_4:
            // receiving register FIS, dword by dword
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    // correct frame size, finishing
                    begin
                        state            <= STATE_IDLE;
                    end
                    else
                    // incorrect frame size
                    begin
                        state           <= STATE_IN_REG_ERR;
                        dword_cnt       <= 14'h4;
                    end
            end

            STATE_IN_REG_ERR:
            // FIS was started as REG, but for some reason it has a size more than needed
            // just wait until it's over and assert an error
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    begin
                        bad_fis_received <= 1'b1;
                        state            <= STATE_IDLE;
                    end
                    else
                    begin
                        if (watchdog_dwords)
                        // if for some reason FIS continue transferring for too long, terminate it
                        begin
                            state                   <= STATE_IDLE;
                            incom_stop_req_timeout  <= 1'b1;
                        end
                        else
                            dword_cnt <= dword_cnt + 1'b1;
                    end
            end

            STATE_IN_DMAA_ERR:
            // FIS was started as DMA Activate, but for some reason it has a size more than needed
            // just wait until it's over and assert an error
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    begin
                        bad_fis_received <= 1'b1;
                        state            <= STATE_IDLE;
                    end
                    else
                    begin
                        if (watchdog_dwords)
                        // if for some reason FIS continue transferring for too long, terminate it
                        begin
                            state                   <= STATE_IDLE;
                            incom_stop_req_timeout  <= 1'b1;
                        end
                        else
                            dword_cnt <= dword_cnt + 1'b1;
                    end
            end

            STATE_IN_DMAS_1:
            // receiving DMA Setup FIS, dword by dword
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    // incorrect frame size
                    begin
                        bad_fis_received <= 1'b1;
                        state            <= STATE_IDLE;
                    end
                    else
                    // going to the next dword, parse current one: DMA Buffer Id Low
                    begin
                        loc_dma_id[31:0]    <= ll_data_in[31:0];
                        state               <= STATE_IN_DMAS_2;
                    end
            end

            STATE_IN_DMAS_2:
            // receiving DMA Setup FIS, dword by dword
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    // incorrect frame size
                    begin
                        bad_fis_received <= 1'b1;
                        state            <= STATE_IDLE;
                    end
                    else
                    // going to the next dword, parse current one: DMA Buffer Id High
                    begin
                        loc_dma_id[63:32]   <= ll_data_in[31:0];
                        state               <= STATE_IN_DMAS_3;
                    end
            end

            STATE_IN_DMAS_3:
            // receiving DMA Setup FIS, dword by dword
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    // incorrect frame size
                    begin
                        bad_fis_received <= 1'b1;
                        state            <= STATE_IDLE;
                    end
                    else
                    // going to the next dword, parse current one: Reserved
                    begin
                        state           <= STATE_IN_DMAS_4;
                    end
            end

            STATE_IN_DMAS_4:
            // receiving DMA Setup FIS, dword by dword
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    // incorrect frame size
                    begin
                        bad_fis_received <= 1'b1;
                        state            <= STATE_IDLE;
                    end
                    else
                    // going to the next dword, parse current one: DMA Buffer Offset
                    begin
                        loc_dma_off[31:0]   <= ll_data_in[31:0];
                        state               <= STATE_IN_DMAS_5;
                    end
            end

            STATE_IN_DMAS_5:
            // receiving DMA Setup FIS, dword by dword
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    // incorrect frame size
                    begin
                        bad_fis_received <= 1'b1;
                        state            <= STATE_IDLE;
                    end
                    else
                    // going to the next dword, parse current one: DMA Transfer Count
                    begin
                        loc_dma_cnt[31:0]   <= ll_data_in[31:0];
                        state               <= STATE_IN_DMAS_6;
                    end
            end

            STATE_IN_DMAS_6:
            // receiving DMA Setup FIS, dword by dword
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    // correct frame size, finishing, current dword: Reserved
                    begin
                        state            <= STATE_IDLE;
                    end
                    else
                    // incorrect frame size
                    begin
                        state           <= STATE_IN_DMAS_ERR;
                        dword_cnt       <= 14'h6;
                    end
            end

            STATE_IN_DMAS_ERR:
            // FIS was started as DMA Setup, but for some reason it has a size more than needed
            // just wait until it's over and assert an error
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    begin
                        bad_fis_received <= 1'b1;
                        state            <= STATE_IDLE;
                    end
                    else
                    begin
                        if (watchdog_dwords)
                        // if for some reason FIS continue transferring for too long, terminate it
                        begin
                            state                   <= STATE_IDLE;
                            incom_stop_req_timeout  <= 1'b1;
                        end
                        else
                            dword_cnt <= dword_cnt + 1'b1;
                    end
            end

            STATE_IN_BIST_1:
            // receiving BIST FIS, dword by dword
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    // incorrect frame size
                    begin
                        bad_fis_received <= 1'b1;
                        state            <= STATE_IDLE;
                    end
                    else
                    // going to the next dword, parse current one: TODO
                    begin
                        state               <= STATE_IN_BIST_2;
                    end
            end

            STATE_IN_BIST_2:
            // receiving BIST FIS, dword by dword
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    // correct frame size, finishing, current dword: Reserved
                    begin
                        state            <= STATE_IDLE;
                    end
                    else
                    // incorrect frame size
                    begin
                        state           <= STATE_IN_BIST_ERR;
                        dword_cnt       <= 14'h2;
                    end
            end

            STATE_IN_BIST_ERR:
            // FIS was started as BIST Activate, but for some reason it has a size more than needed
            // just wait until it's over and assert an error
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    begin
                        bad_fis_received <= 1'b1;
                        state            <= STATE_IDLE;
                    end
                    else
                    begin
                        if (watchdog_dwords)
                        // if for some reason FIS continue transferring for too long, terminate it
                        begin
                            state                   <= STATE_IDLE;
                            incom_stop_req_timeout  <= 1'b1;
                        end
                        else
                            dword_cnt <= dword_cnt + 1'b1;
                    end
            end

            STATE_IN_PIOS_1:
            // receiving PIO Setup FIS, dword by dword
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    // incorrect frame size
                    begin
                        bad_fis_received <= 1'b1;
                        state            <= STATE_IDLE;
                    end
                    else
                    // going to the next dword, parse current one: {Device, LBA High, LBA Mid, LBA Low}
                    begin
                        loc_lba[7:0]    <= ll_data_in[7:0];  
                        loc_lba[23:16]  <= ll_data_in[15:8];
                        loc_lba[39:32]  <= ll_data_in[23:16];
                        loc_dev         <= ll_data_in[31:24];
                        state           <= STATE_IN_PIOS_2;
                    end
            end

            STATE_IN_PIOS_2:
            // receiving PIO Setup FIS, dword by dword
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    // incorrect frame size
                    begin
                        bad_fis_received <= 1'b1;
                        state            <= STATE_IDLE;
                    end
                    else
                    // going to the next dword, parse current one: {Reserved, LBA High (exp), LBA Mid (exp), LBA Low (exp)}
                    begin
                        loc_lba[15:8]   <= ll_data_in[7:0];
                        loc_lba[31:24]  <= ll_data_in[15:8];
                        loc_lba[47:40]  <= ll_data_in[23:16];
                        state           <= STATE_IN_PIOS_3;
                    end
            end

            STATE_IN_PIOS_3:
            // receiving PIOS FIS, dword by dword
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    // incorrect frame size
                    begin
                        bad_fis_received <= 1'b1;
                        state            <= STATE_IDLE;
                    end
                    else
                    // going to the next dword, parse current one: {E_Status, Reserved, Sector Count (exp), Sector Count}
                    begin
                        loc_count[15:0] <= ll_data_in[15:0];
                        loc_estatus     <= ll_data_in[31:24];
                        state           <= STATE_IN_PIOS_4;
                    end
            end

            STATE_IN_PIOS_4:
            // receiving PIO Setup FIS, dword by dword
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    // correct frame size, finishing, current dword: {Reserved, Transfer Count}
                    begin
                        loc_tran_cnt    <= ll_data_in[15:0];
                        state           <= STATE_IDLE;
                    end
                    else
                    // incorrect frame size
                    begin
                        state           <= STATE_IN_BIST_ERR;
                        dword_cnt       <= 14'h4;
                    end
            end

            STATE_IN_PIOS_ERR:
            // FIS was started as PIO Setup Activate, but for some reason it has a size more than needed
            // just wait until it's over and assert an error
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    begin
                        bad_fis_received <= 1'b1;
                        state            <= STATE_IDLE;
                    end
                    else
                    begin
                        if (watchdog_dwords)
                        // if for some reason FIS continue transferring for too long, terminate it
                        begin
                            state                   <= STATE_IDLE;
                            incom_stop_req_timeout  <= 1'b1;
                        end
                        else
                            dword_cnt <= dword_cnt + 1'b1;
                    end
            end

            STATE_IN_SDB_1:
            // receiving Set Device Bits FIS, dword by dword
            begin
                if (ll_data_val_in)
                    if (ll_data_last_in)
                    // correct frame size, finishing, current dword: Reserved
                    begin
                        state           <= STATE_IDLE;
                    end
                    else
                    // incorrect frame size
                    begin
                        state           <= STATE_IN_SDB_ERR;
                        dword_cnt       <= 14'h1;
                    end
            end

            STATE_IN_SDB_ERR:
            // FIS was started as Set Device Bits FIS, but for some reason it has a size more than needed
            // just wait until it's over and assert an error
            begin
                if (ll_data_val_in)
                     if (ll_data_last_in)
                     begin
                         bad_fis_received <= 1'b1;
                         state            <= STATE_IDLE;
                     end
                     else
                     begin
                         if (watchdog_dwords)
                         // if for some reason FIS continue transferring for too long, terminate it
                         begin
                             state                   <= STATE_IDLE;
                             incom_stop_req_timeout  <= 1'b1;
                         end
                         else
                             dword_cnt <= dword_cnt + 1'b1;
                     end
            end

            STATE_OUT_DATA_H:
            // Send data FIS header
            begin
                if (ll_data_strobe_in)
                begin
                    state       <= STATE_OUT_DATA_D;
                    dword_cnt   <= 14'h1;
                end
            end

            STATE_OUT_DATA_D:
            // Send data FIS data payload
            begin
                if (ll_data_strobe_in)
                begin
                    if (cl_data_last_in)
                    begin
                    // All data is transmitted
                        dword_cnt   <= 14'h0;
                        state       <= STATE_OUT_WAIT_RESP;
                    end
                    else
                    if (dword_cnt == 2048)
                    // data_limit_exceed - wire assigned
                        state       <= STATE_IDLE;
                    else
                    begin
                        state       <= STATE_OUT_DATA_D;
                        dword_cnt   <= dword_cnt + 1'b1;
                    end
                end
            end

            STATE_OUT_REG:
            // Register Host 2 Device FIS
            begin
                if (ll_data_strobe_in)
                    // 5 header dwords, then wait for a reception on a device side
                    if (dword_cnt[2:0] == 3'h4)
                    begin
                        dword_cnt   <= 14'h0;
                        state       <= STATE_OUT_WAIT_RESP;
                    end
                    else
                    begin
                        state       <= STATE_OUT_REG;
                        dword_cnt   <= dword_cnt + 1'b1;
                    end
            end

            STATE_OUT_DMAS:
            // DMA Setup outcoming FIS
            begin
                if (ll_data_strobe_in)
                    // 7 header dwords, then wait for a reception on a device side
                    if (dword_cnt[2:0] == 3'h6)
                    begin
                        dword_cnt   <= 14'h0;
                        state       <= STATE_OUT_WAIT_RESP;
                    end
                    else
                    begin
                        state       <= STATE_OUT_DMAS;
                        dword_cnt   <= dword_cnt + 1'b1;
                    end
            end

            STATE_OUT_BIST:
            begin
                if (ll_data_strobe_in)
                    // 3 header dwords, then wait for a reception on a device side
                    if (dword_cnt[2:0] == 3'h2)
                    begin
                        dword_cnt   <= 14'h0;
                        state       <= STATE_OUT_WAIT_RESP;
                    end
                    else
                    begin
                        state       <= STATE_OUT_BIST;
                        dword_cnt   <= dword_cnt + 1'b1;
                    end
            end

            STATE_OUT_WAIT_RESP:
            begin
                if (frame_done_good)
                    // cmd_done_good wire assigned
                    state       <= STATE_IDLE;
                else 
                if (frame_done_bad)
                    // cmd_done_bad wire assigned
                    state       <= STATE_IDLE;
                else
                if (dword_cnt == WATCHDOG_EOF_LIMIT)
                // in here dword_cnt works as a watchdog timer
                begin
                    state       <= STATE_IDLE; 
                    // watchdog_eof wire assigned
                    // for now while debugging let it be indicated on higher level TODO Choose exception. May be send incom stop req. 
                    //      Be aware of no response for that. In such case go for rst for ll. Or better make link_reset -> 1. And dont forget for oob
                end
                else
                begin
                    dword_cnt   <= dword_cnt + 1'b1;
                    state       <= STATE_OUT_WAIT_RESP;
                end
            end

            STATE_IN_UNRECOG:
            begin
                if (incom_done | incom_invalidate)
                // transmission complete
                // incom_ack_bad wire assigned
                    state   <= STATE_IDLE;
                else
                if (watchdog_dwords)
                begin
                    state                   <= STATE_IDLE;
                    incom_stop_req_timeout  <= 1'b1;
                end
                else
                begin
                    dword_cnt   <= dword_cnt + 1'b1;
                    state       <= STATE_IN_UNRECOG;
                end
                    
            end

            default:
            begin
            end
        endcase

// buys circuit
assign  cmd_busy = |state | frame_busy;

// respond if received FIS had any meaning in terms of TL
// actual response shall come next tick after done signal to fit LL fsm
reg incom_done_r;
reg incom_done_bad_r;
always @ (posedge clk)
    incom_done_bad_r <= incom_done & state == STATE_IN_UNRECOG;
always @ (posedge clk)
    incom_done_r     <= incom_done;

assign  incom_ack_bad   = incom_done_bad_r | bad_fis_received;
assign  incom_ack_good  = incom_done_r & ~incom_ack_bad;

// after a device says it received the FIS, reveal the error code
assign  cmd_done_good = state == STATE_OUT_WAIT_RESP & frame_done_good;
assign  cmd_done_bad  = state == STATE_OUT_WAIT_RESP & frame_done_bad;

// Reg H2D FIS header
wire    [31:0] header_regfis;
assign  header_regfis   = dword_cnt[2:0] == 3'h0 ?  {sh_feature_in[7:0], sh_command_in, cmd_type_r == CMD_TYPE_REG_CMD, 3'h0, cmd_port_r, 8'h27} : //  features command C R R R PMPort FISType
                          dword_cnt[2:0] == 3'h1 ?  {sh_dev_in, sh_lba_in[39:32], sh_lba_in[23:16], sh_lba_in[7:0]} : // Device LBAHigh LBAMid LBALow
                          dword_cnt[2:0] == 3'h2 ?  {sh_feature_in[15:8], sh_lba_in[47:40], sh_lba_in[31:24], sh_lba_in[15:8]} : // Features (exp) LBAHigh (exp) LBAMid (exp) LBALow (exp)
                          dword_cnt[2:0] == 3'h3 ?  {sh_control_in[7:0], 8'h00, sh_count_in[15:0]} : // Control Reserved SectorCount (exp) SectorCount
                       /*dword_cnt[2:0] == 3'h4 ?*/ {32'h0000}; // Reserved
// DMA Setup FIS header
wire    [31:0] header_dmas;
assign  header_dmas     = dword_cnt[3:0] == 4'h0 ?  {8'h0, 8'h0, sh_autoact_in, sh_inter_in, sh_dir_in, 1'b0, cmd_port_r, 8'h41} : // Reserved, Reserved, A I D R PMPort, FIS Type
                          dword_cnt[3:0] == 4'h1 ?  {sh_dma_id_in[31:0]} : // DMA Buffer Identifier Low
                          dword_cnt[3:0] == 4'h2 ?  {sh_dma_id_in[63:32]} : // DMA Buffer Identifier High
                          dword_cnt[3:0] == 4'h4 ?  {sh_buf_off_in[31:0]} : // DMA Buffer Offset
                          dword_cnt[3:0] == 4'h5 ?  {sh_dma_cnt_in[31:0]} : // DMA Transfer Count
                                  /* 4'h3 | 4'h6 */ {32'h0000}; // Reserved
// BIST Activate FIS header
wire    [31:0] header_bist; // TODO
assign  header_bist     = dword_cnt[2:0] == 3'h0 ?  {8'h00, 8'h00, 4'h0, cmd_port_r, 8'h58} : // Reserved, T A S L F P R V, R R R R PMPort, FIS Type
                          dword_cnt[2:0] == 3'h1 ?  {32'h00000000} : // Data1
                          dword_cnt[2:0] == 3'h2 ?  {32'h00000000} : // Data2
                                                    {32'h00000000};
// Data FIS header
wire    [31:0] header_data;
assign  header_data     = {8'h00, 8'h00, 4'h0, cmd_port_r, 8'h46}; // Reserved, Reserved, R R R R PMPort, FIS Type


assign  ll_header_val   = state == STATE_OUT_REG | state == STATE_OUT_DMAS | state == STATE_OUT_BIST | state == STATE_OUT_DATA_H; 
assign  ll_header_last  = state == STATE_OUT_REG    & dword_cnt[2:0] == 3'h4 |
                          state == STATE_OUT_DMAS   & dword_cnt[2:0] == 3'h6 |
                          state == STATE_OUT_BIST   & dword_cnt[2:0] == 3'h2;
assign  ll_header_dword = {32{state == STATE_OUT_REG}}      & header_regfis | 
                          {32{state == STATE_OUT_DMAS}}     & header_dmas   | 
                          {32{state == STATE_OUT_BIST}}     & header_bist   |
                          {32{state == STATE_OUT_DATA_H}}   & header_data;

// bypass data from ll to cl if it's data stage in data FIS
assign  cl_data_val_out     = ll_data_val_in & state == STATE_IN_DATA;
assign  cl_data_last_out    = ll_data_val_in & ll_data_last_in & state == STATE_IN_DATA;
assign  cl_data_mask_out    = ll_data_mask_in;
assign  cl_data_out         = ll_data_in & {32{cl_data_val_out}};
assign  ll_data_busy_out    = cl_data_busy_in;

// set data to ll: bypass payload from cl or headers constructed in here
assign  ll_data_val_out     = ll_header_val | cl_data_val_in;
assign  ll_data_last_out    = ll_header_last & ll_header_val | cl_data_last_in & ~ll_header_val; 
assign  ll_data_out         = ll_header_dword & {32{ll_header_val}} | cl_data_in & {32{~ll_header_val}};
assign  ll_data_mask_out    = {2{ll_header_val}} | cl_data_mask_in & {2{~ll_header_val}};

// limit was 2048 words + 1 headers
assign  data_limit_exceeded = dword_cnt == 14'd2048 & ~cl_data_last_in;

// check if no data was obtained from buffer by ll when we're waiting for a response
wire    chk_strobe_while_waitresp;
assign  chk_strobe_while_waitresp = state == STATE_OUT_WAIT_RESP & ll_data_strobe_in;

// issue a FIS 
assign  frame_req = cmd_val & state == STATE_IDLE & ~frame_busy;

// update shadow registers as soon as transaction finishes TODO invalidate in case of errors
// TODO update only corresponding fields, which was updated during the transmission
assign  sh_lba_out      = loc_lba;
assign  sh_count_out    = loc_count;
assign  sh_command_out  = loc_command;
assign  sh_err_out      = loc_err;
assign  sh_status_out   = loc_status;
assign  sh_estatus_out  = loc_estatus;
assign  sh_dev_out      = loc_dev;
assign  sh_port_out     = loc_port;
assign  sh_inter_out    = loc_inter;
assign  sh_dir_out      = loc_dir;
assign  sh_dma_id_out   = loc_dma_id;
assign  sh_dma_off_out  = loc_dma_off;
assign  sh_dma_cnt_out  = loc_dma_cnt;
assign  sh_tran_cnt_out = loc_tran_cnt;
assign  sh_notif_out    = loc_notif;
assign  sh_autoact_out  = loc_autoact;

assign  sh_lba_val_out      = ll_data_last_in;
assign  sh_count_val_out    = ll_data_last_in;
assign  sh_command_val_out  = ll_data_last_in;
assign  sh_err_val_out      = ll_data_last_in;
assign  sh_status_val_out   = ll_data_last_in;
assign  sh_estatus_val_out  = ll_data_last_in;
assign  sh_dev_val_out      = ll_data_last_in;
assign  sh_port_val_out     = ll_data_last_in;
assign  sh_inter_val_out    = ll_data_last_in;
assign  sh_dir_val_out      = ll_data_last_in;
assign  sh_dma_id_val_out   = ll_data_last_in;
assign  sh_dma_off_val_out  = ll_data_last_in;
assign  sh_dma_cnt_val_out  = ll_data_last_in;
assign  sh_tran_cnt_val_out = ll_data_last_in;
assign  sh_notif_val_out    = ll_data_last_in;
assign  sh_autoact_val_out  = ll_data_last_in;

// dma activate is received when its type met and no errors occurs
assign  got_dma_activate        = state == STATE_INCOMING & cl_data_last_in & ll_data_val_in & ll_data_in[7:0] == 8'h39;
assign  got_dma_activate_port   = {4{got_dma_activate}} & ll_data_in[11:8];

`ifdef CHECKERS_ENABLED
always @ (posedge clk)
    if (~rst)
    if (chk_strobe_while_waitresp)
    begin
        $display("ERROR in %m: retrieving data while being in a STATE_OUT_WAIT_RESP state");
        $finish;
    end
`endif

// eof response watchdog
assign  watchdog_eof = dword_cnt == WATCHDOG_EOF_LIMIT & state == STATE_OUT_WAIT_RESP;
`ifdef CHECKERS_ENABLED
always @ (posedge clk)
    if (~rst)
    if (watchdog_eof)
    begin
        $display("WARNING in %m: watchdog_eof asserted");
        $stop;
    end
`endif
`ifdef CHECKERS_ENABLED
always @ (posedge clk)
    if (~rst)
    if (watchdog_dwords)
    begin
        $display("ERROR in %m: state %h - current FIS contains more than 2048 dwords", state);
        $finish;
    end
`endif
wire chk_inc_dword_limit_exceeded;
assign  chk_inc_dword_limit_exceeded = state == STATE_IN_DATA & dword_cnt == 14'd2049;
`ifdef CHECKERS_ENABLED
always @ (posedge clk)
    if (~rst)
    if (chk_inc_dword_limit_exceeded)
    begin
        $display("ERROR in %m: received more than 2048 words in one FIS");
        $finish;
    end
`endif

endmodule