{#- PCILeech FIFO Module Template -#}
{# validator: ignore-unsafe-default data_width #}
{% from "_helpers.j2" import get_vendor_id, get_device_id %}
{{ header | safe }}

`default_nettype none

module pcileech_fifo #(
    parameter DEVICE_ID = 16'h{{ get_device_id(device_config, device, config_space, device_id_int) }},
    parameter VENDOR_ID = 16'h{{ get_vendor_id(device_config, device, config_space, vendor_id_int) }},
    parameter FIFO_DEPTH = {{ fifo_depth | default(512) }},
    // data_width: Use context-provided value or sensible module default (128-bit for Gen3+ performance)
    parameter DATA_WIDTH = {{ data_width | default(128) }}
) (
    input  wire         clk,
    input  wire         rst,
    
    // TLP RX interface
    input  wire  [DATA_WIDTH-1:0] rx_tlp_data,
    input  wire         rx_tlp_valid,
    input  wire         rx_tlp_sop,
    input  wire         rx_tlp_eop,
    output logic        rx_tlp_ready,
    
    // TLP TX interface
    output logic [DATA_WIDTH-1:0] tx_tlp_data,
    output logic        tx_tlp_valid,
    output logic        tx_tlp_sop,
    output logic        tx_tlp_eop,
    input  wire         tx_tlp_ready,
    
    // Configuration interface
    input  wire  [31:0] cfg_addr,
    input  wire  [31:0] cfg_wdata,
    input  wire         cfg_wr_en,
    input  wire         cfg_rd_en,
    output logic [31:0] cfg_rdata,
    output logic        cfg_ready,
    
    // Status and control
    output logic [31:0] status_reg,
    output logic        fifo_full,
    output logic        fifo_empty,
    output logic [15:0] fifo_count
);

    // Single-clock FIFO; no clock-domain crossing in this template.

    // Configuration registers
    logic [255:0] rw;
    
    // FIFO signals
    logic [DATA_WIDTH-1:0] fifo_din, fifo_dout;
    logic                  fifo_wr_en, fifo_rd_en;
    logic                  fifo_almost_full, fifo_almost_empty;
    
    // Configuration initialization is handled in reset logic below
    
    // --------------------------------------------------------------------
    // Single-clock FIFO implementation with N+1-bit pointers
    // --------------------------------------------------------------------
    // No CDC path; both write and read sides use 'clk'.

    // Pointer width and indexing
    localparam int LOG2_DEPTH = (FIFO_DEPTH <= 2)    ? 1 :
                                (FIFO_DEPTH <= 4)    ? 2 :
                                (FIFO_DEPTH <= 8)    ? 3 :
                                (FIFO_DEPTH <= 16)   ? 4 :
                                (FIFO_DEPTH <= 32)   ? 5 :
                                (FIFO_DEPTH <= 64)   ? 6 :
                                (FIFO_DEPTH <= 128)  ? 7 :
                                (FIFO_DEPTH <= 256)  ? 8 :
                                (FIFO_DEPTH <= 512)  ? 9 :
                                (FIFO_DEPTH <= 1024) ? 10 : 11;

    logic [LOG2_DEPTH:0] wr_ptr, rd_ptr; // extra MSB for full/empty disambiguation
    wire  [LOG2_DEPTH-1:0] wr_idx = wr_ptr[LOG2_DEPTH-1:0];
    wire  [LOG2_DEPTH-1:0] rd_idx = rd_ptr[LOG2_DEPTH-1:0];

    // Data and sideband flag memories (flags: {SOP,EOP})
    logic [DATA_WIDTH-1:0] fifo_mem   [0:FIFO_DEPTH-1];
    logic [1:0]            flag_mem   [0:FIFO_DEPTH-1];

    // Write path
    always_ff @(posedge clk) begin
        if (rst) begin
            wr_ptr <= '0;
        end else begin
            if (fifo_wr_en && !fifo_full) begin
                fifo_mem[wr_idx] <= fifo_din;
                flag_mem[wr_idx] <= {rx_tlp_sop, rx_tlp_eop};
                wr_ptr <= wr_ptr + 1'b1;
            end
        end
    end

    // Read path (pointer advance in handshake block below)
    assign fifo_dout = fifo_mem[rd_idx];
    wire [1:0] head_flags = flag_mem[rd_idx];

    // Status and thresholds
    wire [LOG2_DEPTH:0] fifo_level = wr_ptr - rd_ptr;
    assign fifo_empty = (wr_ptr == rd_ptr);
    assign fifo_full  = ((wr_idx == rd_idx) && (wr_ptr[LOG2_DEPTH] != rd_ptr[LOG2_DEPTH]));
    assign fifo_count = fifo_level[15:0];
    assign fifo_almost_full  = (fifo_level >= (FIFO_DEPTH - 16));
    assign fifo_almost_empty = (fifo_level <= 16);
    
    // PCIe interface hookups and registered outputs (single-clock)
    assign fifo_din  = rx_tlp_data;
    assign fifo_wr_en = rx_tlp_valid && rx_tlp_ready;
    assign fifo_rd_en = tx_tlp_ready && !fifo_empty;

    // rx_tlp_ready registered
    always_ff @(posedge clk) begin
        if (rst) begin
            rx_tlp_ready <= 1'b0;
        end else begin
            rx_tlp_ready <= ~fifo_almost_full;
        end
    end

    // tx_tlp_* registered
    always_ff @(posedge clk) begin
        if (rst) begin
            tx_tlp_data  <= '0;
            tx_tlp_valid <= 1'b0;
            tx_tlp_sop   <= 1'b0;
            tx_tlp_eop   <= 1'b0;
        end else begin
            // Present head-of-line data every cycle while not empty
            tx_tlp_valid <= !fifo_empty;
            if (!fifo_empty) begin
                tx_tlp_data <= fifo_dout;
                {tx_tlp_sop, tx_tlp_eop} <= head_flags;
            end
            // Pop on handshake
            if (tx_tlp_ready && !fifo_empty) begin
                rd_ptr <= rd_ptr + 1'b1;
            end
        end
    end
    
    // Flow control and framing now handled in registered blocks above
    
    // Configuration register access
    always_ff @(posedge clk) begin
        if (rst) begin
            cfg_rdata <= 32'h0;
            cfg_ready <= 1'b0;
            
            // Initialize all registers
            rw <= 256'h0;
            
            {% if enable_custom_config %}
            // CRITICAL: Enable custom configuration space
            // Change from 1'b1 to 1'b0 to enable custom configuration space
            rw[203] <= 1'b0;  // CFGTLP ZERO DATA (0 = CUSTOM CONFIGURATION SPACE ENABLED)
            {% else %}
            // Use standard configuration space
            rw[203] <= 1'b1;  // CFGTLP ZERO DATA (1 = STANDARD CONFIGURATION SPACE)
            {% endif %}
            
            // Device-specific configuration
            {% if device_specific_config %}
            {% for config_bit, value in device_specific_config.items() %}
            rw[{{ config_bit }}] <= 1'b{{ value }};  // {{ config_bit }} configuration
            {% endfor %}
            {% endif %}
            
            // PCILeech specific settings
            rw[0] <= 1'b1;    // Enable PCILeech functionality
            rw[1] <= 1'b1;    // Enable DMA operations
            rw[2] <= 1'b{{ '1' if enable_scatter_gather else '0' }};    // Scatter-gather support
            rw[3] <= 1'b{{ '1' if enable_interrupt else '0' }};    // Interrupt support
            
            // CFGTLP control bits
            rw[20] <= 1'b0;   // cfg_a7[0] - Configuration address bit 7, bit 0
            rw[21] <= 1'b0;   // cfg_a7[1] - Configuration address bit 7, bit 1
            rw[206] <= 1'b1;  // CFGTLP PCIE WRITE ENABLE (1 = Enable PCIe writes for CFGTLP)
        end else begin
            cfg_ready <= cfg_rd_en || cfg_wr_en;
            
            if (cfg_wr_en) begin
                case (cfg_addr[7:0])
                    8'h00: rw[31:0] <= cfg_wdata;
                    8'h04: rw[63:32] <= cfg_wdata;
                    8'h08: rw[95:64] <= cfg_wdata;
                    8'h0C: rw[127:96] <= cfg_wdata;
                    8'h10: rw[159:128] <= cfg_wdata;
                    8'h14: rw[191:160] <= cfg_wdata;
                    8'h18: rw[223:192] <= cfg_wdata;
                    8'h1C: rw[255:224] <= cfg_wdata;
                    default: ; // Ignore writes to undefined addresses
                endcase
            end
            
            if (cfg_rd_en) begin
                case (cfg_addr[7:0])
                    8'h00: cfg_rdata <= rw[31:0];
                    8'h04: cfg_rdata <= rw[63:32];
                    8'h08: cfg_rdata <= rw[95:64];
                    8'h0C: cfg_rdata <= rw[127:96];
                    8'h10: cfg_rdata <= rw[159:128];
                    8'h14: cfg_rdata <= rw[191:160];
                    8'h18: cfg_rdata <= rw[223:192];
                    8'h1C: cfg_rdata <= rw[255:224];
                    8'h20: cfg_rdata <= {16'h0, fifo_count};  // FIFO status
                    8'h24: cfg_rdata <= {30'h0, fifo_full, fifo_empty};  // FIFO flags
                    default: cfg_rdata <= 32'h0;
                endcase
            end
        end
    end
    
    // Status register output
    assign status_reg = {14'h0, fifo_full, fifo_empty, DEVICE_ID};
    
    {% if enable_performance_counters %}
    // Performance counters
    logic [31:0] rx_packet_count, tx_packet_count;
    logic [31:0] rx_byte_count, tx_byte_count;
    
    always_ff @(posedge clk) begin
        if (rst) begin
            rx_packet_count <= 32'h0;
            tx_packet_count <= 32'h0;
            rx_byte_count <= 32'h0;
            tx_byte_count <= 32'h0;
        end else begin
            if (rx_tlp_valid && rx_tlp_ready && rx_tlp_eop) begin
                rx_packet_count <= rx_packet_count + 1;
            end
            if (rx_tlp_valid && rx_tlp_ready) begin
                rx_byte_count <= rx_byte_count + (DATA_WIDTH / 8);
            end
            if (tx_tlp_valid && tx_tlp_ready && tx_tlp_eop) begin
                tx_packet_count <= tx_packet_count + 1;
            end
            if (tx_tlp_valid && tx_tlp_ready) begin
                tx_byte_count <= tx_byte_count + (DATA_WIDTH / 8);
            end
        end
    end
    {% endif %}
    
    {% if enable_error_detection %}
    // Error detection and reporting
    logic overflow_error, underflow_error;
    logic [31:0] error_count;

    always_ff @(posedge clk) begin
        if (rst) begin
            error_count <= 32'h0;
            overflow_error <= 1'b0;
            underflow_error <= 1'b0;
        end else begin
            // Detect FIFO overflow
            if (fifo_wr_en && fifo_full) begin
                overflow_error <= 1'b1;
                error_count <= error_count + 1;
            end
            
            // Detect FIFO underflow
            if (fifo_rd_en && fifo_empty) begin
                underflow_error <= 1'b1;
                error_count <= error_count + 1;
            end
        end
    end
    {% endif %}

endmodule

`default_nettype wire