//==============================================================================
// MSI-X Table and PBA Implementation
// Generated by PCILeech Firmware Generator
//
// This module implements the MSI-X table and Pending Bit Array (PBA) in BRAM
// with support for byte-enable granularity writes and interrupt delivery.
//
// Features:
// - Parameterized MSI-X table size
// - Support for byte-enable granularity writes
// - Interrupt delivery logic with masking support
// - PBA functionality for tracking pending interrupts
//==============================================================================

module msix_table #(
    parameter NUM_MSIX = {{ NUM_MSIX }},                // Number of MSI-X table entries
    parameter MSIX_TABLE_BIR = {{ MSIX_TABLE_BIR }},          // BAR indicator for MSI-X table
    parameter MSIX_TABLE_OFFSET = {{ MSIX_TABLE_OFFSET }},       // Offset of MSI-X table in the BAR
    parameter MSIX_PBA_BIR = {{ MSIX_PBA_BIR }},            // BAR indicator for MSI-X PBA
    parameter MSIX_PBA_OFFSET = {{ MSIX_PBA_OFFSET }}          // Offset of MSI-X PBA in the BAR
) (
    // Clock and reset
    input  logic        clk,
    input  logic        reset_n,
    
    // BAR access interface
    input  logic [31:0] bar_addr,
    input  logic [2:0]  bar_index,
    input  logic [31:0] bar_wr_data,
    input  logic        bar_wr_en,
    input  logic [3:0]  bar_wr_be,
    input  logic        bar_rd_en,
    output logic [31:0] bar_rd_data,
    output logic        bar_access_match,
    
    // MSI-X control interface
    input  logic        msix_enable,       // MSI-X function enable
    input  logic        msix_function_mask, // MSI-X function mask
    
    // Interrupt interface
    output logic        msix_interrupt,    // MSI-X interrupt request
    output logic [10:0] msix_vector,       // MSI-X vector number
    input  logic        msix_interrupt_ack  // Acknowledge from PCIe core
);

    // Calculate PBA size in 32-bit words
    localparam PBA_SIZE = ({{ NUM_MSIX }} + 31) / 32;
    
    // MSI-X Table storage (4 DWORDs per entry)
    (* ram_style="block" *) logic [31:0] msix_table_mem[0:{{ NUM_MSIX }}*4-1];
    
    // MSI-X PBA storage
    logic [31:0] msix_pba_mem[0:PBA_SIZE-1];
    
    // Internal signals
    logic is_table_access;
    logic is_pba_access;
    logic [31:0] table_addr;
    logic [31:0] pba_addr;
    logic [10:0] current_vector;
    logic vector_masked;
    logic [31:0] control_dword;
    logic interrupt_pending;
    
    // Determine if access is to MSI-X table or PBA
    assign is_table_access = (bar_index == {{ MSIX_TABLE_BIR }}) && 
                            (bar_addr >= {{ MSIX_TABLE_OFFSET }}) && 
                            (bar_addr < ({{ MSIX_TABLE_OFFSET }} + {{ NUM_MSIX }} * 16));
                            
    assign is_pba_access = (bar_index == {{ MSIX_PBA_BIR }}) && 
                          (bar_addr >= {{ MSIX_PBA_OFFSET }}) && 
                          (bar_addr < ({{ MSIX_PBA_OFFSET }} + PBA_SIZE * 4));
    
    // Calculate table and PBA addresses
    assign table_addr = (bar_addr - {{ MSIX_TABLE_OFFSET }}) >> 2;  // Convert to DWORD index
    assign pba_addr = (bar_addr - {{ MSIX_PBA_OFFSET }}) >> 2;      // Convert to DWORD index
    
    // Signal if this module should handle the access
    assign bar_access_match = is_table_access || is_pba_access;
    
    // Synchronous read logic for better timing
    logic [31:0] bar_rd_data_reg;
    
    always_ff @(posedge clk) begin
        if (!reset_n) begin
            bar_rd_data_reg <= 32'h0;
        end else if (bar_rd_en) begin
            if (is_table_access) begin
                bar_rd_data_reg <= msix_table_mem[table_addr];
            end else if (is_pba_access) begin
                bar_rd_data_reg <= msix_pba_mem[pba_addr];
            end else begin
                bar_rd_data_reg <= 32'h0;
            end
        end
    end
    
    assign bar_rd_data = bar_rd_data_reg;
    
    // Write logic with byte enables
    always_ff @(posedge clk) begin
        if (!reset_n) begin
            // Reset MSI-X table and PBA
{% if RESET_CLEAR %}
            for (int i = 0; i < {{ NUM_MSIX }} * 4; i++) begin
                msix_table_mem[i] <= 32'h0;
            end
            
            for (int i = 0; i < PBA_SIZE; i++) begin
                msix_pba_mem[i] <= 32'h0;
            end
{% endif %}
        end else begin
            if (bar_wr_en) begin
                if (is_table_access) begin
                    // Write to MSI-X table with byte enables
{% if USE_BYTE_ENABLES %}
                    if (bar_wr_be[0]) msix_table_mem[table_addr][7:0] <= bar_wr_data[7:0];
                    if (bar_wr_be[1]) msix_table_mem[table_addr][15:8] <= bar_wr_data[15:8];
                    if (bar_wr_be[2]) msix_table_mem[table_addr][23:16] <= bar_wr_data[23:16];
                    if (bar_wr_be[3]) msix_table_mem[table_addr][31:24] <= bar_wr_data[31:24];
{% else %}
                    msix_table_mem[table_addr] <= bar_wr_data;
{% endif %}
{% if WRITE_PBA_ALLOWED %}
                end else if (is_pba_access) begin
                    // PBA is typically read-only, but implement write for completeness
                    // In a real device, software shouldn't write to PBA
{% if USE_BYTE_ENABLES %}
                    if (bar_wr_be[0]) msix_pba_mem[pba_addr][7:0] <= bar_wr_data[7:0];
                    if (bar_wr_be[1]) msix_pba_mem[pba_addr][15:8] <= bar_wr_data[15:8];
                    if (bar_wr_be[2]) msix_pba_mem[pba_addr][23:16] <= bar_wr_data[23:16];
                    if (bar_wr_be[3]) msix_pba_mem[pba_addr][31:24] <= bar_wr_data[31:24];
{% else %}
                    msix_pba_mem[pba_addr] <= bar_wr_data;
{% endif %}
{% else %}
                // end else if (is_pba_access) begin
                //     // PBA is read-only - writes are ignored
{% endif %}
                end
            end
            
            // Clear pending bit when interrupt is acknowledged
            if (msix_interrupt_ack && interrupt_pending) begin
                msix_pba_mem[current_vector >> 5] <= msix_pba_mem[current_vector >> 5] & ~(1 << (current_vector & 5'h1F));
            end
        end
    end
    
    // Interrupt delivery state machine
    typedef enum logic [1:0] {
        IDLE,
        PENDING,
        DELIVERING,
        WAITING_ACK
    } intr_state_t;
    
    intr_state_t intr_state = IDLE;
    logic [10:0] pending_vector;
    
    // Interrupt delivery logic
    always_ff @(posedge clk) begin
        if (!reset_n) begin
            intr_state <= IDLE;
            msix_interrupt <= 1'b0;
            msix_vector <= 11'h0;
            pending_vector <= 11'h0;
            interrupt_pending <= 1'b0;
        end else begin
            case (intr_state)
                IDLE: begin
                    msix_interrupt <= 1'b0;
                    interrupt_pending <= 1'b0;
                    
                    // Check for pending interrupts
                    for (int i = 0; i < {{ NUM_MSIX }}; i++) begin
                        // Check if this vector has a pending bit set
                        if (msix_pba_mem[i >> 5] & (1 << (i & 5'h1F))) begin
                            pending_vector <= i[10:0];
                            intr_state <= PENDING;
                            break;
                        end
                    end
                end
                
                PENDING: begin
                    // Get control DWORD (third DWORD in the entry)
                    control_dword = msix_table_mem[pending_vector * 4 + 3];
                    
                    // Check if vector is masked
                    vector_masked = control_dword[0];
                    
                    if (msix_enable && !msix_function_mask && !vector_masked) begin
                        // Vector is enabled and not masked - deliver interrupt
                        msix_vector <= pending_vector;
                        msix_interrupt <= 1'b1;
                        current_vector <= pending_vector;
                        interrupt_pending <= 1'b1;
                        intr_state <= WAITING_ACK;
                    end else begin
                        // Vector is masked - keep pending bit set and check next vector
                        intr_state <= IDLE;
                    end
                end
                
                WAITING_ACK: begin
                    if (msix_interrupt_ack) begin
                        msix_interrupt <= 1'b0;
                        intr_state <= IDLE;
                    end
                end
                
                default: intr_state <= IDLE;
            endcase
        end
    end
    
    // Function to trigger an MSI-X interrupt
    // This would be called by other modules to trigger an interrupt
    function void trigger_interrupt(input logic [10:0] vector);
        if (vector < {{ NUM_MSIX }}) begin
            // Set the pending bit
            msix_pba_mem[vector >> 5] = msix_pba_mem[vector >> 5] | (1 << (vector & 5'h1F));
        end
    endfunction

{% if INIT_TABLE %}
    // NOTE: For synthesis, memory initialization should be done in reset logic
    // The following is for simulation only and may cause synthesis issues:
    // initial begin
    //     $readmemh("msix_table_init.hex", msix_table_mem);
    // end
    
    // For synthesis, add initialization in the reset section of the always_ff block above
    // For example, in the reset section at line ~105:
    // for (int i = 0; i < {{ NUM_MSIX }} * 4; i++) begin
    //     msix_table_mem[i] <= 32'h0;
    // end
{% endif %}

{% if INIT_PBA %}
    // NOTE: For synthesis, memory initialization should be done in reset logic
    // The following is for simulation only and may cause synthesis issues:
    // initial begin
    //     $readmemh("msix_pba_init.hex", msix_pba_mem);
    // end
    
    // For synthesis, add initialization in the reset section of the always_ff block above
    // For example, in the reset section at line ~109:
    // for (int i = 0; i < PBA_SIZE; i++) begin
    //     msix_pba_mem[i] <= 32'h0;
    // end
{% endif %}

endmodule