// SPDX-License-Identifier: CERN-OHL-S-2.0
/*

Copyright (c) 2025 FPGA Ninja, LLC

Authors:
- Alex Forencich

*/

`resetall
`timescale 1ns / 1ps
`default_nettype none

/*
 * Corundum-proto descriptor read module
 */
module cndm_proto_desc_rd
(
    input  wire logic         clk,
    input  wire logic         rst,

    /*
     * DMA
     */
    taxi_dma_desc_if.req_src  dma_rd_desc_req,
    taxi_dma_desc_if.sts_snk  dma_rd_desc_sts,
    taxi_dma_ram_if.wr_slv    dma_ram_wr,

    /*
     * Control signals from port-level control registers
     */
    input  wire logic         txq_en,
    input  wire logic [3:0]   txq_size,
    input  wire logic [63:0]  txq_base_addr,
    input  wire logic [15:0]  txq_prod,
    output wire logic [15:0]  txq_cons,
    input  wire logic         rxq_en,
    input  wire logic [3:0]   rxq_size,
    input  wire logic [63:0]  rxq_base_addr,
    input  wire logic [15:0]  rxq_prod,
    output wire logic [15:0]  rxq_cons,

    /*
     * Descriptor request interface
     */
    input  wire logic [1:0]   desc_req,
    taxi_axis_if.src          axis_desc[2]
);

// Control for internal streaming DMA engine
localparam RAM_ADDR_W = 16;

taxi_dma_desc_if #(
    .SRC_ADDR_W(RAM_ADDR_W),
    .SRC_SEL_EN(1'b0),
    .SRC_ASID_EN(1'b0),
    .DST_ADDR_W(RAM_ADDR_W),
    .DST_SEL_EN(1'b0),
    .DST_ASID_EN(1'b0),
    .IMM_EN(1'b0),
    .LEN_W(5),
    .TAG_W(1),
    .ID_EN(0),
    .DEST_EN(1),
    .DEST_W(1),
    .USER_EN(1),
    .USER_W(1)
) dma_desc();

// Descriptor read control state machine
typedef enum logic [1:0] {
    STATE_IDLE,
    STATE_READ_DESC,
    STATE_READ_DATA,
    STATE_TX_DESC
} state_t;

state_t state_reg = STATE_IDLE;

logic [1:0] desc_req_reg = '0;

logic [15:0] txq_cons_ptr_reg = '0;
logic [15:0] rxq_cons_ptr_reg = '0;

assign txq_cons = txq_cons_ptr_reg;
assign rxq_cons = rxq_cons_ptr_reg;

always_ff @(posedge clk) begin
    // Host DMA control descriptor to manage transferring descriptors from host memory
    dma_rd_desc_req.req_src_sel <= '0;
    dma_rd_desc_req.req_src_asid <= '0;
    dma_rd_desc_req.req_dst_sel <= '0;
    dma_rd_desc_req.req_dst_asid <= '0;
    dma_rd_desc_req.req_imm <= '0;
    dma_rd_desc_req.req_imm_en <= '0;
    dma_rd_desc_req.req_len <= 16;
    dma_rd_desc_req.req_tag <= '0;
    dma_rd_desc_req.req_id <= '0;
    dma_rd_desc_req.req_dest <= '0;
    dma_rd_desc_req.req_user <= '0;
    dma_rd_desc_req.req_valid <= dma_rd_desc_req.req_valid && !dma_rd_desc_req.req_ready;

    // Streaming DMA control descriptor to manage reading descriptors
    dma_desc.req_src_sel <= '0;
    dma_desc.req_src_asid <= '0;
    dma_desc.req_dst_addr <= '0;
    dma_desc.req_dst_sel <= '0;
    dma_desc.req_dst_asid <= '0;
    dma_desc.req_imm <= '0;
    dma_desc.req_imm_en <= '0;
    dma_desc.req_len <= 16;
    dma_desc.req_tag <= '0;
    dma_desc.req_id <= '0;
    dma_desc.req_user <= '0;
    dma_desc.req_valid <= dma_desc.req_valid && !dma_desc.req_ready;

    // Latch descriptor request pulses
    desc_req_reg <= desc_req_reg | desc_req;

    // reset pointers when queues are disabled
    if (!txq_en) begin
        txq_cons_ptr_reg <= '0;
    end

    if (!rxq_en) begin
        rxq_cons_ptr_reg <= '0;
    end

    case (state_reg)
        STATE_IDLE: begin
            // idle state - wait for descriptor request
            // favor RX over TX
            if (desc_req_reg[1]) begin
                // compute host address - base address plus producer pointer, modulo queue size
                dma_rd_desc_req.req_src_addr <= rxq_base_addr + 64'(16'(rxq_cons_ptr_reg & ({16{1'b1}} >> (16 - rxq_size))) * 16);
                dma_desc.req_dest <= 1'b1;
                desc_req_reg[1] <= 1'b0;
                if (rxq_cons_ptr_reg == rxq_prod || !rxq_en) begin
                    // queue is empty or disabled, generate invalid descriptor
                    dma_desc.req_user <= 1'b1;
                    dma_desc.req_valid <= 1'b1;
                    state_reg <= STATE_TX_DESC;
                end else begin
                    // increment pointer and start transfer operation
                    dma_desc.req_user <= 1'b0;
                    dma_rd_desc_req.req_valid <= 1'b1;
                    rxq_cons_ptr_reg <= rxq_cons_ptr_reg + 1;
                    state_reg <= STATE_READ_DESC;
                end
            end else if (desc_req_reg[0]) begin
                // compute host address - base address plus producer pointer, modulo queue size
                dma_rd_desc_req.req_src_addr <= txq_base_addr + 64'(16'(txq_cons_ptr_reg & ({16{1'b1}} >> (16 - txq_size))) * 16);
                dma_desc.req_dest <= 1'b0;
                desc_req_reg[0] <= 1'b0;
                if (txq_cons_ptr_reg == txq_prod || !txq_en) begin
                    // queue is empty or disabled, generate invalid descriptor
                    dma_desc.req_user <= 1'b1;
                    dma_desc.req_valid <= 1'b1;
                    state_reg <= STATE_TX_DESC;
                end else begin
                    // increment pointer and start transfer operation
                    dma_desc.req_user <= 1'b0;
                    dma_rd_desc_req.req_valid <= 1'b1;
                    txq_cons_ptr_reg <= txq_cons_ptr_reg + 1;
                    state_reg <= STATE_READ_DESC;
                end
            end
        end
        STATE_READ_DESC: begin
            // read descriptor state - wait for host DMA read, start streaming DMA read
            if (dma_rd_desc_sts.sts_valid) begin
                dma_desc.req_valid <= 1'b1;
                state_reg <= STATE_TX_DESC;
            end
        end
        STATE_TX_DESC: begin
            // transmit descriptor state - wait for streaming DMA read
            if (dma_desc.sts_valid) begin
                state_reg <= STATE_IDLE;
            end
        end
        default: begin
            state_reg <= STATE_IDLE;
        end
    endcase

    if (rst) begin
        state_reg <= STATE_IDLE;
    end
end

// local RAM to store the descriptor temporarily
taxi_dma_ram_if #(
    .SEGS(dma_ram_wr.SEGS),
    .SEG_ADDR_W(dma_ram_wr.SEG_ADDR_W),
    .SEG_DATA_W(dma_ram_wr.SEG_DATA_W),
    .SEG_BE_W(dma_ram_wr.SEG_BE_W)
) dma_ram_rd();

taxi_dma_psdpram #(
    .SIZE(1024),
    .PIPELINE(2)
)
ram_inst (
    .clk(clk),
    .rst(rst),

    /*
     * Write port
     */
    .dma_ram_wr(dma_ram_wr),

    /*
     * Read port
     */
    .dma_ram_rd(dma_ram_rd)
);

// streaming DMA engine to read descriptor from local RAM
taxi_axis_if #(
    .DATA_W(axis_desc[0].DATA_W),
    .KEEP_EN(axis_desc[0].KEEP_EN),
    .KEEP_W(axis_desc[0].KEEP_W),
    .LAST_EN(axis_desc[0].LAST_EN),
    .ID_EN(axis_desc[0].ID_EN),
    .ID_W(axis_desc[0].ID_W),
    .DEST_EN(1),
    .DEST_W(1),
    .USER_EN(axis_desc[0].USER_EN),
    .USER_W(axis_desc[0].USER_W)
) m_axis_rd_data();

taxi_dma_client_axis_source
dma_inst (
    .clk(clk),
    .rst(rst),

    /*
     * DMA descriptor
     */
    .desc_req(dma_desc),
    .desc_sts(dma_desc),

    /*
     * AXI stream read data output
     */
    .m_axis_rd_data(m_axis_rd_data),

    /*
     * RAM interface
     */
    .dma_ram_rd(dma_ram_rd),

    /*
     * Configuration
     */
    .enable(1'b1)
);

// demux module to route descriptor appropriately
taxi_axis_demux #(
    .M_COUNT(2),
    .TDEST_ROUTE(1)
)
demux_inst (
    .clk(clk),
    .rst(rst),

    /*
     * AXI4-Stream input (sink)
     */
    .s_axis(m_axis_rd_data),

    /*
     * AXI4-Stream output (source)
     */
    .m_axis(axis_desc),

    /*
     * Control
     */
    .enable(1'b1),
    .drop(1'b0),
    .select('0)
);

endmodule

`resetall
