//==============================================================================
//! @file fpga_device.v
//==============================================================================
//------------------------------------------------------------------------------
// --
// CERN - FPGA Version Device --
// --
//------------------------------------------------------------------------------
//
// unit name:  fpga_device
//
//! @brief FPGA Version Device
//
//! @author alen.arias.vazquez@cern.ch
//
//! @date 06/05/2022
//
//! @details
//
//! @URL: https://ohwr.org/project/fpga-dev-id/wikis
//
//------------------------------------------------------------------------------

module fpga_device # (
        parameter g_VENDOR_ID                   = 32'h0,
        parameter g_DEVICE_ID                   = 32'h0,
        parameter g_VERSION                     = 32'h0,
        parameter g_BYTE_ORD_MARK               = 32'h0,
        parameter g_SOURCE_ID                   = 128'h0,
        parameter g_CAP_MASK                    = 32'h0,
        parameter g_VENDOR_UUID                 = 128'h0,
        parameter g_CORE_ID                     = 32'h0,
        parameter g_NAME                        = 64'h0,
        parameter g_BUILD_DATE                  = 32'd0,
        parameter g_HASH                        = 160'h0,
        parameter g_TAG                         = 64'h0,
        parameter g_S_AXI_DATA_WIDTH            = 32,
        parameter g_S_AXI_ADDR_WIDTH            = 8,
        parameter g_SIM_DNA_VALUE               = 96'hAABBCCDDEEFF112233445566
    )
    (
        // Clock && RESET
        input                                   S_AXI_ARESETN,
        input                                   S_AXI_ACLK,
        //! Slave Interface Write Address Ports
        input   [g_S_AXI_ADDR_WIDTH-1:0]        S_AXI_AWADDR,
        input   [2:0]                           S_AXI_AWPROT,
        input                                   S_AXI_AWVALID,
        output                                  S_AXI_AWREADY,
        //! Slave Interface Write Data Ports
        input   [g_S_AXI_DATA_WIDTH-1:0]        S_AXI_WDATA,
        input   [(g_S_AXI_DATA_WIDTH/8)-1:0]    S_AXI_WSTRB,
        input                                   S_AXI_WVALID,
        output                                  S_AXI_WREADY,
        //! Slave Interface Write Response Ports
        output  [1:0]                           S_AXI_BRESP,
        output                                  S_AXI_BVALID,
        input                                   S_AXI_BREADY,
        //! Slave Interface Read Address Ports
        input   [g_S_AXI_ADDR_WIDTH-1:0]        S_AXI_ARADDR,
        input   [2:0]                           S_AXI_ARPROT,
        input                                   S_AXI_ARVALID,
        output                                  S_AXI_ARREADY,
        //! Slave Interface Read Data Ports
        output  [g_S_AXI_DATA_WIDTH-1:0]        S_AXI_RDATA,
        output  [1:0]                           S_AXI_RRESP,
        output                                  S_AXI_RVALID,
        input                                   S_AXI_RREADY
    );

    //! Unused lines WRITE
    assign S_AXI_AWREADY = 1'b0;
    assign S_AXI_WREADY = 1'b0;
    assign S_AXI_BRESP = 2'b0;
    assign S_AXI_BVALID = 1'b0;

    //! Unused lines READ
    assign S_AXI_RRESP = 2'b00;

    //! Constants
    localparam c_ZEROS              = {g_S_AXI_DATA_WIDTH{1'b0}};
    localparam c_LSB_ADDR           = (g_S_AXI_DATA_WIDTH / 32) + 1;
    localparam c_ADDR_VENDOR_ID     = 'h00;
    localparam c_ADDR_DEVICE_ID     = 'h01;
    localparam c_ADDR_VERSION       = 'h02;
    localparam c_ADDR_BYTE_ORD_MARK = 'h03;
    localparam c_ADDR_SOURCE_ID_0   = 'h04;
    localparam c_ADDR_SOURCE_ID_1   = 'h05;
    localparam c_ADDR_SOURCE_ID_2   = 'h06;
    localparam c_ADDR_SOURCE_ID_3   = 'h07;
    localparam c_ADDR_CAP_MASK      = 'h08;
    localparam c_ADDR_VENDOR_UUID_0 = 'h0C;
    localparam c_ADDR_VENDOR_UUID_1 = 'h0D;
    localparam c_ADDR_VENDOR_UUID_2 = 'h0E;
    localparam c_ADDR_VENDOR_UUID_3 = 'h0F;
    localparam c_ADDR_CORE_ID       = 'h10;
    localparam c_ADDR_NAME_LSB      = 'h11;
    localparam c_ADDR_NAME_MSB      = 'h12;
    localparam c_ADDR_BUILD_T       = 'h13;
    localparam c_ADDR_HASH_0        = 'h14;
    localparam c_ADDR_HASH_1        = 'h15;
    localparam c_ADDR_HASH_2        = 'h16;
    localparam c_ADDR_HASH_3        = 'h17;
    localparam c_ADDR_HASH_4        = 'h18;
    localparam c_ADDR_DNA_0         = 'h19;
    localparam c_ADDR_DNA_1         = 'h1A;
    localparam c_ADDR_DNA_2         = 'h1B;
    localparam c_ADDR_TAG_LSB       = 'h1C;
    localparam c_ADDR_TAG_MSB       = 'h1D;

    //! Axi lite auxiliary regs
    reg [g_S_AXI_ADDR_WIDTH-1:0]    s_araddr;
    reg                             s_arready;
    reg                             s_rvalid;

    //! User lines
    reg     [g_S_AXI_DATA_WIDTH-1:0]               s_RDATA;
    wire    [g_S_AXI_ADDR_WIDTH-c_LSB_ADDR-1:0]    s_RADDR;
    wire                                           s_REN;

    //! DNA
    wire    [95:0] s_dna;

    //--------------------------------------------------------------------------
    //! ARREADY LOGIC & ARADDR Latch
    always @(posedge S_AXI_ACLK, negedge S_AXI_ARESETN)
    begin : p_arready
        if (! S_AXI_ARESETN) begin
            s_araddr  <= 'h0;
            s_arready <= 1'b0;
        end
        else begin
            if (!s_arready && S_AXI_ARVALID) begin
                s_araddr  <= S_AXI_ARADDR;
                s_arready <= 1'b1;
            end
            else begin
                s_araddr <= s_araddr;
                s_arready <= 1'b0;
            end
        end
    end : p_arready

    assign S_AXI_ARREADY = s_arready;

    //! RVALID
    always @(posedge S_AXI_ACLK, negedge S_AXI_ARESETN)
    begin : p_rvalid
        if (! S_AXI_ARESETN)
            s_rvalid <= 1'b0;
        else begin
            if (~(s_rvalid) & s_arready & S_AXI_ARVALID)
                s_rvalid <= 1'b1;
            else if (s_rvalid & S_AXI_RREADY)
                s_rvalid <= 1'b0;
        end
    end : p_rvalid

    //! Read assignment
    assign s_RADDR = s_araddr[g_S_AXI_ADDR_WIDTH-1:c_LSB_ADDR];
    assign s_REN = ~(s_rvalid) & s_arready & S_AXI_ARVALID;

    //! Register Access
    always @(posedge S_AXI_ACLK, negedge S_AXI_ARESETN)
    begin : p_read
        if (! S_AXI_ARESETN)
            s_RDATA <= 'h0;
        else begin
            if (s_REN) begin
                case(s_RADDR)
                    c_ADDR_VENDOR_ID        : s_RDATA <= g_VENDOR_ID;
                    c_ADDR_DEVICE_ID        : s_RDATA <= g_DEVICE_ID;
                    c_ADDR_VERSION          : s_RDATA <= g_VERSION;
                    c_ADDR_BYTE_ORD_MARK    : s_RDATA <= g_BYTE_ORD_MARK;
                    c_ADDR_SOURCE_ID_0      : s_RDATA <= g_SOURCE_ID[31:0];
                    c_ADDR_SOURCE_ID_1      : s_RDATA <= g_SOURCE_ID[63:32];
                    c_ADDR_SOURCE_ID_2      : s_RDATA <= g_SOURCE_ID[95:64];
                    c_ADDR_SOURCE_ID_3      : s_RDATA <= g_SOURCE_ID[127:96];
                    c_ADDR_CAP_MASK         : s_RDATA <= g_CAP_MASK;
                    c_ADDR_VENDOR_UUID_0    : s_RDATA <= s_dna[31:0];
                    c_ADDR_VENDOR_UUID_1    : s_RDATA <= {g_VENDOR_UUID[63:48],s_dna[47:32]};
                    c_ADDR_VENDOR_UUID_2    : s_RDATA <= g_VENDOR_UUID[95:64];
                    c_ADDR_VENDOR_UUID_3    : s_RDATA <= g_VENDOR_UUID[127:96];
                    c_ADDR_CORE_ID          : s_RDATA <= g_CORE_ID;
                    c_ADDR_NAME_LSB         : s_RDATA <= g_NAME[31:0];
                    c_ADDR_NAME_MSB         : s_RDATA <= g_NAME[63:32];
                    c_ADDR_BUILD_T          : s_RDATA <= g_BUILD_DATE;
                    c_ADDR_HASH_0           : s_RDATA <= g_HASH[31:0];
                    c_ADDR_HASH_1           : s_RDATA <= g_HASH[63:32];
                    c_ADDR_HASH_2           : s_RDATA <= g_HASH[95:64];
                    c_ADDR_HASH_3           : s_RDATA <= g_HASH[127:96];
                    c_ADDR_HASH_4           : s_RDATA <= g_HASH[159:128];
                    c_ADDR_DNA_0            : s_RDATA <= s_dna[31:0];
                    c_ADDR_DNA_1            : s_RDATA <= s_dna[63:32];
                    c_ADDR_DNA_2            : s_RDATA <= s_dna[95:64];
                    c_ADDR_TAG_LSB          : s_RDATA <= g_TAG[31:0];
                    c_ADDR_TAG_MSB          : s_RDATA <= g_TAG[63:32];
                    default                 : s_RDATA <= c_ZEROS;
                endcase
            end
        end
    end : p_read

    //! Assign outputs
    assign S_AXI_RDATA = s_RDATA;
    assign S_AXI_RVALID = s_rvalid;

    //--------------------------------------------------------------------------
    dna_reader # (
        .g_SIM_DNA_VALUE    (g_SIM_DNA_VALUE)
    ) i_dna_reader (
        .clk_i              (S_AXI_ACLK),
        .rst_n_i            (S_AXI_ARESETN),
        .dna_rdy_o          (),
        .dna_o              (s_dna)
    );

endmodule
//------------------------------------------------------------------------------
//! end module
//------------------------------------------------------------------------------
