{#- PCILeech FIFO Module Template -#}
{% from "_helpers.j2" import get_vendor_id, get_device_id %}
{{ header | safe }}

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) }},
    parameter DATA_WIDTH = {{ data_width | default(128) }}
) (
    input  logic        clk,
    input  logic        rst,
    
    // PCIe TLP interface
    input  logic        pcie_clk,
    input  logic        pcie_rst_n,
    
    // TLP RX interface
    input  logic [DATA_WIDTH-1:0] rx_tlp_data,
    input  logic        rx_tlp_valid,
    input  logic        rx_tlp_sop,
    input  logic        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  logic        tx_tlp_ready,
    
    // Configuration interface
    input  logic [31:0] cfg_addr,
    input  logic [31:0] cfg_wdata,
    input  logic        cfg_wr_en,
    input  logic        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
);

    // 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;
    
    // Clock domain crossing signals
    logic [DATA_WIDTH-1:0] rx_data_sync, tx_data_sync;
    logic rx_valid_sync, tx_valid_sync;
    
    // Configuration initialization is handled in reset logic below
    
    // FIFO instantiation
    {% if fifo_type == 'block_ram' %}
    // Block RAM FIFO for high performance
    fifo_generator_v13_2_5 #(
        .C_FAMILY("{{ fpga_family | default('artix7') }}"),
        .C_INTERFACE_TYPE(0),
        .C_MEMORY_TYPE(2),  // Block RAM
        .C_DATA_WIDTH(DATA_WIDTH),
        .C_FIFO_DEPTH(FIFO_DEPTH),
        .C_PROG_FULL_THRESH_ASSERT_VAL(FIFO_DEPTH - 16),
        .C_PROG_EMPTY_THRESH_ASSERT_VAL(16)
    ) u_fifo (
        .clk(clk),
        .srst(rst),
        .din(fifo_din),
        .wr_en(fifo_wr_en),
        .rd_en(fifo_rd_en),
        .dout(fifo_dout),
        .full(fifo_full),
        .empty(fifo_empty),
        .prog_full(fifo_almost_full),
        .prog_empty(fifo_almost_empty),
        .data_count(fifo_count)
    );
    {% else %}
    // Distributed RAM FIFO for lower latency
    logic [{{ ((fifo_depth | default(512)) | log2) | int }}:0] wr_ptr, rd_ptr;
    logic [DATA_WIDTH-1:0] fifo_mem [0:FIFO_DEPTH-1];
    
    always @(posedge clk) begin
        if (rst) begin
            wr_ptr <= 0;
            rd_ptr <= 0;
        end else begin
            if (fifo_wr_en && !fifo_full) begin
                fifo_mem[wr_ptr[{{ ((fifo_depth | default(512)) | log2) | int - 1 }}:0]] <= fifo_din;
                wr_ptr <= wr_ptr + 1;
            end
            if (fifo_rd_en && !fifo_empty) begin
                rd_ptr <= rd_ptr + 1;
            end
        end
    end
    
    assign fifo_dout = fifo_mem[rd_ptr[{{ ((fifo_depth | default(512)) | log2) | int - 1 }}:0]];
    assign fifo_empty = (wr_ptr == rd_ptr);
    assign fifo_full = ((wr_ptr + 1) == rd_ptr);
    assign fifo_count = wr_ptr - rd_ptr;
    assign fifo_almost_full = (fifo_count >= (FIFO_DEPTH - 16));
    assign fifo_almost_empty = (fifo_count <= 16);
    {% endif %}
    
    // Clock domain crossing for PCIe interface
    {% if enable_clock_crossing %}
    // Synchronize RX data from PCIe clock domain
    always @(posedge clk) begin
        if (rst) begin
            rx_data_sync <= 0;
            rx_valid_sync <= 1'b0;
        end else begin
            rx_data_sync <= rx_tlp_data;
            rx_valid_sync <= rx_tlp_valid;
        end
    end
    
    // Synchronize TX data to PCIe clock domain
    always @(posedge pcie_clk) begin
        if (!pcie_rst_n) begin
            tx_data_sync <= 0;
            tx_valid_sync <= 1'b0;
        end else begin
            tx_data_sync <= fifo_dout;
            tx_valid_sync <= fifo_rd_en;
        end
    end
    
    assign fifo_din = rx_data_sync;
    assign fifo_wr_en = rx_valid_sync && rx_tlp_ready;
    assign fifo_rd_en = tx_tlp_ready && !fifo_empty;
    assign tx_tlp_data = tx_data_sync;
    assign tx_tlp_valid = tx_valid_sync;
    {% else %}
    // Direct connection (same clock domain)
    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;
    assign tx_tlp_data = fifo_dout;
    assign tx_tlp_valid = !fifo_empty;
    {% endif %}
    
    // Flow control
    assign rx_tlp_ready = !fifo_almost_full;
    
    // TLP framing (simplified)
    assign tx_tlp_sop = tx_tlp_valid;  // Simplified: each transfer is a packet
    assign tx_tlp_eop = tx_tlp_valid;
    
    // Configuration register access
    always @(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 = {
        {16'h0, DEVICE_ID},
        {14'h0, fifo_full, fifo_empty}
    };
    
    {% 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 @(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 parity_error, overflow_error, underflow_error;
    logic [31:0] error_count;
    
    always @(posedge clk) begin
        if (rst) begin
            error_count <= 32'h0;
            parity_error <= 1'b0;
            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
            
            // Simple parity check on data
            if (fifo_wr_en) begin
                parity_error <= ^fifo_din;  // Odd parity check
            end
        end
    end
    {% endif %}

endmodule