module pcie_iop2_msg_arbiter #(
    parameter E0_ADDR = 20'h0,
    parameter E0_MASK = 20'h0,
    parameter E1_ADDR = 20'h0,
    parameter E1_MASK = 20'h0,
    parameter E2_ADDR = 20'h0,
    parameter E2_MASK = 20'h0,
    parameter E3_ADDR = 20'h0,
    parameter E3_MASK = 20'h0
) (
    //Clocks and resets
    input           clk,
    input           reset,

    input [63:0]    regi_tdata,
    input           regi_tvalid,
    output          regi_tready,
    output [63:0]   rego_tdata,
    output          rego_tvalid,    
    input           rego_tready,

    output [63:0]   e0_regi_tdata,
    output          e0_regi_tvalid,
    input           e0_regi_tready,
    input [63:0]    e0_rego_tdata,
    input           e0_rego_tvalid,    
    output          e0_rego_tready,
    
    output [63:0]   e1_regi_tdata,
    output          e1_regi_tvalid,
    input           e1_regi_tready,
    input [63:0]    e1_rego_tdata,
    input           e1_rego_tvalid,    
    output          e1_rego_tready,

    output [63:0]   e2_regi_tdata,
    output          e2_regi_tvalid,
    input           e2_regi_tready,
    input [63:0]    e2_rego_tdata,
    input           e2_rego_tvalid,    
    output          e2_rego_tready,

    output [63:0]   e3_regi_tdata,
    output          e3_regi_tvalid,
    input           e3_regi_tready,
    input [63:0]    e3_rego_tdata,
    input           e3_rego_tvalid,    
    output          e3_rego_tready
);

    //*******************************************************************************
    // PCIe output message arbiter
    //
    axi_mux4 #(.PRIO(0), .WIDTH(64), .BUFFER(0)) rego_arbiter_mux (
        .clk(clk), .reset(reset), .clear(1'b0),
        .i0_tdata(e0_rego_tdata), .i0_tlast(e0_rego_tvalid), .i0_tvalid(e0_rego_tvalid), .i0_tready(e0_rego_tready),
        .i1_tdata(e1_rego_tdata), .i1_tlast(e1_rego_tvalid), .i1_tvalid(e1_rego_tvalid), .i1_tready(e1_rego_tready),
        .i2_tdata(e2_rego_tdata), .i2_tlast(e2_rego_tvalid), .i2_tvalid(e2_rego_tvalid), .i2_tready(e2_rego_tready),
        .i3_tdata(e3_rego_tdata), .i3_tlast(e3_rego_tvalid), .i3_tvalid(e3_rego_tvalid), .i3_tready(e3_rego_tready),
        .o_tdata(rego_tdata), .o_tlast(), .o_tvalid(rego_tvalid), .o_tready(rego_tready)
    );
    //
    //*******************************************************************************

    //*******************************************************************************
    // PCIe input message arbiter
    //
    wire [63:0]     regi_msg;
    wire            regi_rc;
    wire [19:0]     regi_addr;
    wire            e0_rego_rd, e1_rego_rd, e2_rego_rd, e3_rego_rd;

    ioport2_msg_decode e0_rego_decoder (.message(e0_rego_tdata), .rd_request(e0_rego_rd));
    ioport2_msg_decode e1_rego_decoder (.message(e1_rego_tdata), .rd_request(e1_rego_rd));
    ioport2_msg_decode e2_rego_decoder (.message(e2_rego_tdata), .rd_request(e2_rego_rd));
    ioport2_msg_decode e3_rego_decoder (.message(e3_rego_tdata), .rd_request(e3_rego_rd));

    localparam DEST_E0   = 2'd0;
    localparam DEST_E1   = 2'd1;
    localparam DEST_E2   = 2'd2;
    localparam DEST_E3   = 2'd3;

    reg  [1:0]  regi_resp_dest;
    wire [1:0]  regi_req_dest, regi_dest;
    
    assign regi_req_dest = 
        ((regi_addr & E0_MASK) == E0_ADDR) ? DEST_E0 : (
        ((regi_addr & E1_MASK) == E1_ADDR) ? DEST_E1 : (
        ((regi_addr & E2_MASK) == E2_ADDR) ? DEST_E2 : (
        ((regi_addr & E3_MASK) == E3_ADDR) ? DEST_E3 : (
        DEST_E0))));
    
    //A response must be routed to the port with the last read request 
    always @(posedge clk) begin
        if (reset)
            regi_resp_dest <= DEST_E0;  //Default 0
        else if (e0_rego_tvalid & e0_rego_tready & e0_rego_rd)
            regi_resp_dest <= DEST_E0;
        else if (e1_rego_tvalid & e1_rego_tready & e1_rego_rd)
            regi_resp_dest <= DEST_E1;
        else if (e2_rego_tvalid & e2_rego_tready & e2_rego_rd)
            regi_resp_dest <= DEST_E2;
        else if (e3_rego_tvalid & e3_rego_tready & e3_rego_rd)
            regi_resp_dest <= DEST_E3;
    end

    ioport2_msg_decode regi_decoder (
        .message(regi_msg), .rd_response(regi_rc), .address(regi_addr));

    //If request, get destination from msg. 
    //If response, get destination from last read location. 
    assign regi_dest = regi_rc ? regi_resp_dest : regi_req_dest;

    axi_demux4 #(.ACTIVE_CHAN(4'b1111), .WIDTH(64), .BUFFER(0)) regi_arbiter_demux (
        .clk(clk), .reset(reset), .clear(1'b0),
        .header(regi_msg), .dest(regi_dest),
        .i_tdata(regi_tdata), .i_tlast(regi_tvalid), .i_tvalid(regi_tvalid), .i_tready(regi_tready),
        .o0_tdata(e0_regi_tdata), .o0_tlast(), .o0_tvalid(e0_regi_tvalid), .o0_tready(e0_regi_tready),
        .o1_tdata(e1_regi_tdata), .o1_tlast(), .o1_tvalid(e1_regi_tvalid), .o1_tready(e1_regi_tready),
        .o2_tdata(e2_regi_tdata), .o2_tlast(), .o2_tvalid(e2_regi_tvalid), .o2_tready(e2_regi_tready),
        .o3_tdata(e3_regi_tdata), .o3_tlast(), .o3_tvalid(e3_regi_tvalid), .o3_tready(e3_regi_tready)
    );
    //
    //*******************************************************************************

endmodule