{#- MSI-X Table and PBA Implementation Template -#}
{{ header | safe }}

`default_nettype none

module msix_table #(
    parameter NUM_MSIX = {{ msix_config.num_vectors | default(16) }},                // Number of MSI-X table entries
    parameter MSIX_TABLE_BIR = {{ msix_config.table_bir | default(0) }},          // BAR indicator for MSI-X table
    parameter MSIX_TABLE_OFFSET = {{ msix_config.table_offset | default(0x1000) }},       // Offset of MSI-X table in the BAR
    parameter MSIX_PBA_BIR = {{ msix_config.pba_bir | default(0) }},            // BAR indicator for MSI-X PBA
    parameter MSIX_PBA_OFFSET = {{ msix_config.pba_offset | default(0x2000) }}          // 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 = ({{ msix_config.num_vectors | default(16) }} + 31) / 32;
    
    // MSI-X Table storage (4 DWORDs per entry)
    // Staging vs Active memories to ensure atomic entry updates
    (* ram_style="block" *) logic [31:0] msix_table_staging[0:{{ msix_config.num_vectors | default(16) }}*4-1];
    (* ram_style="block" *) logic [31:0] msix_table_active[0:{{ msix_config.num_vectors | default(16) }}*4-1];
    // Per-entry valid bitmask for staged DWORDs; when committed, bits clear
    logic [3:0] msix_stage_valid[0:{{ msix_config.num_vectors | default(16) }}-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;
    // Indices into table structures
    logic [$clog2(NUM_MSIX)-1:0] entry_index;   // which MSI-X entry (0..NUM_MSIX-1)
    logic [1:0]                  dword_index;   // which DWORD within entry (0..3)
    
    // Determine if access is to MSI-X table or PBA
    assign is_table_access = (bar_index == {{ msix_config.table_bir | default(0) }}) &&
                             (bar_addr >= {{ msix_config.table_offset | default(0x1000) }}) &&
                             (bar_addr < ({{ msix_config.table_offset | default(0x1000) }} + {{ msix_config.num_vectors | default(16) }} * 16));
                             
    assign is_pba_access = (bar_index == {{ msix_config.pba_bir | default(0) }}) &&
                           (bar_addr >= {{ msix_config.pba_offset | default(0x2000) }}) &&
                           (bar_addr < ({{ msix_config.pba_offset | default(0x2000) }} + PBA_SIZE * 4));
    
    // Calculate table and PBA addresses
    assign table_addr = (bar_addr - {{ msix_config.table_offset | default(0x1000) }}) >> 2;  // Convert to DWORD index
    assign pba_addr = (bar_addr - {{ msix_config.pba_offset | default(0x2000) }}) >> 2;      // Convert to DWORD index
    // Derive entry and dword indices from table_addr (DWORD addressing)
    assign entry_index = (table_addr >> 2);   // 4 DWORDs per entry
    assign dword_index = table_addr[1:0];
    
    // 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
                // Reads reflect most recent writes (staging view)
                bar_rd_data_reg <= msix_table_staging[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 msix_config.reset_clear | default(true) %}
            for (int i = 0; i < {{ msix_config.num_vectors | default(16) }} * 4; i++) begin
                msix_table_staging[i] <= 32'h0;
                msix_table_active[i]  <= 32'h0;
            end
            for (int v = 0; v < {{ msix_config.num_vectors | default(16) }}; v++) begin
                msix_stage_valid[v] <= 4'b0000;
            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 STAGING memory with byte enables
                    // Track DWORD-valid bits per entry for atomic commit
{% if msix_config.use_byte_enables | default(true) %}
                    if (bar_wr_be[0]) msix_table_staging[table_addr][7:0]   <= bar_wr_data[7:0];
                    if (bar_wr_be[1]) msix_table_staging[table_addr][15:8]  <= bar_wr_data[15:8];
                    if (bar_wr_be[2]) msix_table_staging[table_addr][23:16] <= bar_wr_data[23:16];
                    if (bar_wr_be[3]) msix_table_staging[table_addr][31:24] <= bar_wr_data[31:24];
{% else %}
                    msix_table_staging[table_addr] <= bar_wr_data;
{% endif %}
                    // Mark this DWORD staged
                    msix_stage_valid[entry_index][dword_index] <= 1'b1;

                    // Commit policy:
                    // - Commit when control DWORD (index 3) is written
                    // - Or when all 4 DWORDs have been staged
                    if (dword_index == 2'd3 || &msix_stage_valid[entry_index]) begin
                        // Copy 4 DWORDs atomically into ACTIVE table
                        for (int k = 0; k < 4; k++) begin
                            msix_table_active[(entry_index << 2) + k] <=
                                msix_table_staging[(entry_index << 2) + k];
                        end
                        // Clear valid mask after commit
                        msix_stage_valid[entry_index] <= 4'b0000;
                    end
{% if msix_config.write_pba_allowed | default(false) %}
                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 msix_config.use_byte_enables | default(true) %}
                    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 < {{ msix_config.num_vectors | default(16) }}; 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_active[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 < {{ msix_config.num_vectors | default(16) }}) begin
            // Set the pending bit
            msix_pba_mem[vector >> 5] = msix_pba_mem[vector >> 5] | (1 << (vector & 5'h1F));
        end
    endfunction

{% if msix_config.init_table | default(false) %}
    // 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 < {{ msix_config.num_vectors | default(16) }} * 4; i++) begin
    //     msix_table_mem[i] <= 32'h0;
    // end
{% endif %}

{% if msix_config.init_pba | default(false) %}
    // 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

`default_nettype wire