// ============================================================================ // snix_axil_cdma_mux.sv // Multi-Channel AXI-Lite MUX Wrapper for snix_axi_mm2mm engine // // Register Map (per channel, offset 0x40): // 0x00: READ_ADDR (Source Address) // 0x04: WRITE_ADDR (Destination Address) // 0x08: LENGTH (Transfer length in bytes) // 0x0C: TAG (User tag, purely for software tracking) // 0x10: CTRL (Write 1 to bit 0 to trigger) // [5:3] = AXI AxSIZE (0=1B, 1=2B, 2=4B, 3=8B...) // [13:6] = AXI AxLEN (0=1 beat, 15=16 beats...) // 0x14: STATUS (Bit 0: Busy RO, Bit 1: Done W1C) // ============================================================================ `timescale 1ns / 1ps module snix_axil_cdma_mux #( parameter int ADDR_WIDTH = 32, parameter int DATA_WIDTH = 32, // Matches engine default parameter int AXIL_ADDR_WIDTH = 32, parameter int AXIL_DATA_WIDTH = 32, parameter int ID_WIDTH = 4, parameter int USER_WIDTH = 1, parameter int PORTS = 8, parameter int FIFO_DEPTH = 16 ) ( input logic clk, input logic rst_n, // ========================================== // AXI-Lite Slave Interface (CPU CSR Access) // ========================================== input logic [AXIL_ADDR_WIDTH-1:0] s_axil_awaddr, input logic s_axil_awvalid, output logic s_axil_awready, input logic [AXIL_DATA_WIDTH-1:0] s_axil_wdata, input logic [AXIL_DATA_WIDTH/8-1:0] s_axil_wstrb, input logic s_axil_wvalid, output logic s_axil_wready, output logic [1:0] s_axil_bresp, output logic s_axil_bvalid, input logic s_axil_bready, input logic [AXIL_ADDR_WIDTH-1:0] s_axil_araddr, input logic s_axil_arvalid, output logic s_axil_arready, output logic [AXIL_DATA_WIDTH-1:0] s_axil_rdata, output logic [1:0] s_axil_rresp, output logic s_axil_rvalid, input logic s_axil_rready, // ========================================== // AXI4 Master Interface (To Crossbar/Memory) // ========================================== output logic [ID_WIDTH-1:0] mm2mm_awid, output logic [ADDR_WIDTH-1:0] mm2mm_awaddr, output logic [7:0] mm2mm_awlen, output logic [2:0] mm2mm_awsize, output logic [1:0] mm2mm_awburst, output logic mm2mm_awlock, output logic [3:0] mm2mm_awcache, output logic [2:0] mm2mm_awprot, output logic [3:0] mm2mm_awqos, output logic [USER_WIDTH-1:0] mm2mm_awuser, output logic mm2mm_awvalid, input logic mm2mm_awready, output logic [DATA_WIDTH-1:0] mm2mm_wdata, output logic [DATA_WIDTH/8-1:0] mm2mm_wstrb, output logic mm2mm_wlast, output logic [USER_WIDTH-1:0] mm2mm_wuser, output logic mm2mm_wvalid, input logic mm2mm_wready, input logic [ID_WIDTH-1:0] mm2mm_bid, input logic [1:0] mm2mm_bresp, input logic [USER_WIDTH-1:0] mm2mm_buser, input logic mm2mm_bvalid, output logic mm2mm_bready, output logic [ID_WIDTH-1:0] mm2mm_arid, output logic [ADDR_WIDTH-1:0] mm2mm_araddr, output logic [7:0] mm2mm_arlen, output logic [2:0] mm2mm_arsize, output logic [1:0] mm2mm_arburst, output logic mm2mm_arlock, output logic [3:0] mm2mm_arcache, output logic [2:0] mm2mm_arprot, output logic [3:0] mm2mm_arqos, output logic [USER_WIDTH-1:0] mm2mm_aruser, output logic mm2mm_arvalid, input logic mm2mm_arready, input logic [ID_WIDTH-1:0] mm2mm_rid, input logic [DATA_WIDTH-1:0] mm2mm_rdata, input logic [1:0] mm2mm_rresp, input logic mm2mm_rlast, input logic [USER_WIDTH-1:0] mm2mm_ruser, input logic mm2mm_rvalid, output logic mm2mm_rready, // ========================================== // Local endpoint interface // ========================================== output logic ep_wr_valid, output logic [3:0] ep_wr_id, output logic [3:0] ep_wr_bank, output logic [7:0] ep_wr_word_addr, output logic [31:0] ep_wr_data, input logic ep_wr_ready, output logic ep_rd_valid, output logic [3:0] ep_rd_id, output logic [3:0] ep_rd_bank, output logic [7:0] ep_rd_word_addr, input logic [31:0] ep_rd_data, input logic ep_rd_ready, // ========================================== // Generic external endpoint requester // ========================================== input logic ext_req_valid, output logic ext_req_ready, input logic [1:0] ext_req_mode, input logic [ADDR_WIDTH-1:0] ext_req_src, input logic [ADDR_WIDTH-1:0] ext_req_dst, input logic [31:0] ext_req_bytes, input logic [3:0] ext_req_ep_id, input logic [3:0] ext_req_ep_bank, input logic [7:0] ext_req_ep_word_off, output logic ext_req_done, // Global Interrupt (OR'd from all channels) output logic dma_finish ); // ========================================== // Local Parameters & Utilities // ========================================== localparam int CH_BITS = $clog2(PORTS); localparam logic [1:0] MODE_MEM2MEM = 2'd0; localparam logic [1:0] MODE_MEM2EP = 2'd1; localparam logic [1:0] MODE_EP2MEM = 2'd2; // Function to safely apply WSTRB to 32-bit registers function automatic logic [31:0] apply_wstrb( input logic [31:0] old_val, input logic [31:0] new_val, input logic [3:0] wstrb ); logic [31:0] res; res[7:0] = wstrb[0] ? new_val[7:0] : old_val[7:0]; res[15:8] = wstrb[1] ? new_val[15:8] : old_val[15:8]; res[23:16] = wstrb[2] ? new_val[23:16] : old_val[23:16]; res[31:24] = wstrb[3] ? new_val[31:24] : old_val[31:24]; return res; endfunction // ========================================== // Internal Registers (Per Channel) // ========================================== logic [ADDR_WIDTH-1:0] ch_src_addr [PORTS]; logic [ADDR_WIDTH-1:0] ch_dst_addr [PORTS]; logic [31:0] ch_len [PORTS]; logic [31:0] ch_tag [PORTS]; logic [31:0] ch_ctrl [PORTS]; logic [31:0] ch_mode [PORTS]; logic [31:0] ch_ep_cfg [PORTS]; logic [PORTS-1:0] ch_req; // Pending requests (Busy) logic [PORTS-1:0] ch_done; // Completion flags logic [PORTS-1:0] arb_set_done; // From Arbiter to CSR // ========================================== // Address Decoding (0x40 offset per channel) // ========================================== wire [CH_BITS-1:0] wr_ch = s_axil_awaddr[6 +: CH_BITS]; wire [5:0] wr_reg = s_axil_awaddr[5:0]; wire [CH_BITS-1:0] rd_ch = s_axil_araddr[6 +: CH_BITS]; wire [5:0] rd_reg = s_axil_araddr[5:0]; // ========================================== // AXI-Lite Slave Logic (Robust Backpressure) // ========================================== assign s_axil_bresp = 2'b00; assign s_axil_rresp = 2'b00; // Write Path Handshake always_ff @(posedge clk) begin if (!rst_n) begin s_axil_awready <= 1'b0; s_axil_wready <= 1'b0; end else begin if (s_axil_awvalid && s_axil_wvalid && !s_axil_awready && (!s_axil_bvalid || s_axil_bready)) begin s_axil_awready <= 1'b1; s_axil_wready <= 1'b1; end else begin s_axil_awready <= 1'b0; s_axil_wready <= 1'b0; end end end wire do_write = s_axil_awready && s_axil_awvalid && s_axil_wready && s_axil_wvalid; always_ff @(posedge clk) begin if (!rst_n) begin s_axil_bvalid <= 1'b0; end else begin if (do_write) begin s_axil_bvalid <= 1'b1; end else if (s_axil_bready && s_axil_bvalid) begin s_axil_bvalid <= 1'b0; end end end // Read Path Handshake always_ff @(posedge clk) begin if (!rst_n) begin s_axil_arready <= 1'b0; end else begin if (s_axil_arvalid && !s_axil_arready && (!s_axil_rvalid || s_axil_rready)) begin s_axil_arready <= 1'b1; end else begin s_axil_arready <= 1'b0; end end end wire do_read = s_axil_arready && s_axil_arvalid; always_ff @(posedge clk) begin if (!rst_n) begin s_axil_rvalid <= 1'b0; s_axil_rdata <= '0; end else begin if (do_read) begin s_axil_rvalid <= 1'b1; if (rd_ch < PORTS) begin case (rd_reg) 6'h00: s_axil_rdata <= ch_src_addr[rd_ch]; 6'h04: s_axil_rdata <= ch_dst_addr[rd_ch]; 6'h08: s_axil_rdata <= ch_len[rd_ch]; 6'h0C: s_axil_rdata <= ch_tag[rd_ch]; 6'h10: s_axil_rdata <= ch_ctrl[rd_ch]; 6'h14: s_axil_rdata <= {30'd0, ch_done[rd_ch], ch_req[rd_ch]}; 6'h18: s_axil_rdata <= ch_mode[rd_ch]; 6'h1C: s_axil_rdata <= ch_ep_cfg[rd_ch]; default: s_axil_rdata <= 32'd0; endcase end else begin s_axil_rdata <= 32'd0; // Out of bounds end end else if (s_axil_rready && s_axil_rvalid) begin s_axil_rvalid <= 1'b0; end end end // ========================================== // Register File Write Logic // ========================================== assign dma_finish = |ch_done; always_ff @(posedge clk) begin if (!rst_n) begin ch_req <= '0; ch_done <= '0; for (int i=0; i= PORTS) ? (check_ch_ext - PORTS) : check_ch_ext[CH_BITS-1:0]; if (ch_req[check_ch] && !arb_set_done[check_ch]) begin cur_ch <= check_ch; rr_ptr <= (check_ch == (PORTS - 1)) ? '0 : (check_ch + 1); state <= START; break; end end end START: begin engine_start <= 1'b1; state <= RUN; end RUN: begin if (engine_done) begin arb_set_done[cur_ch] <= 1'b1; state <= IDLE; end // Optional: Add a watchdog timeout counter here if dealing with untrusted PCIe/AXI endpoints end endcase end end // ========================================== // Instantiate The Original Core Engine // ========================================== snix_axi_mm2mm #( .ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(DATA_WIDTH), .ID_WIDTH (ID_WIDTH), .USER_WIDTH(USER_WIDTH), .FIFO_DEPTH(FIFO_DEPTH) ) u_core_engine ( .clk (clk), .rst_n (rst_n), .ctrl_start (mm2mm_start), .ctrl_stop (1'b0), // Tied off; can be wired if global abort is needed .ctrl_src_addr (engine_src), .ctrl_dst_addr (engine_dst), .ctrl_len (engine_len), .ctrl_size (engine_size), .ctrl_transfer_len (engine_bytes), .ctrl_done (mm2mm_done), // AXI4 Port Connections .mm2mm_awid (core_awid), .mm2mm_awaddr (core_awaddr), .mm2mm_awlen (core_awlen), .mm2mm_awsize (core_awsize), .mm2mm_awburst(core_awburst),.mm2mm_awlock (core_awlock), .mm2mm_awcache(core_awcache),.mm2mm_awprot (core_awprot), .mm2mm_awqos (core_awqos), .mm2mm_awuser (core_awuser), .mm2mm_awvalid(core_awvalid),.mm2mm_awready(core_awready), .mm2mm_wdata (core_wdata), .mm2mm_wstrb (core_wstrb), .mm2mm_wlast (core_wlast), .mm2mm_wuser (core_wuser), .mm2mm_wvalid (core_wvalid), .mm2mm_wready (core_wready), .mm2mm_bid (mm2mm_bid), .mm2mm_bresp (mm2mm_bresp), .mm2mm_buser (mm2mm_buser), .mm2mm_bvalid (mm2mm_bvalid), .mm2mm_bready (core_bready), .mm2mm_arid (core_arid), .mm2mm_araddr (core_araddr), .mm2mm_arlen (core_arlen), .mm2mm_arsize (core_arsize), .mm2mm_arburst(core_arburst),.mm2mm_arlock (core_arlock), .mm2mm_arcache(core_arcache),.mm2mm_arprot (core_arprot), .mm2mm_arqos (core_arqos), .mm2mm_aruser (core_aruser), .mm2mm_arvalid(core_arvalid),.mm2mm_arready(core_arready), .mm2mm_rid (mm2mm_rid), .mm2mm_rdata (mm2mm_rdata), .mm2mm_rresp (mm2mm_rresp), .mm2mm_rlast (mm2mm_rlast), .mm2mm_ruser (mm2mm_ruser), .mm2mm_rvalid (mm2mm_rvalid), .mm2mm_rready (core_rready) ); // ========================================== // Generic external endpoint requester // ========================================== assign ext_req_ready = !ext_active && (state == IDLE); always_ff @(posedge clk) begin if (!rst_n) begin ext_active <= 1'b0; ext_start_pulse <= 1'b0; ext_req_done <= 1'b0; ext_mode_r <= MODE_MEM2EP; ext_src_r <= '0; ext_dst_r <= '0; ext_bytes_r <= '0; ext_ep_id_r <= '0; ext_ep_bank_r <= '0; ext_ep_word_off_r <= '0; end else begin ext_start_pulse <= 1'b0; ext_req_done <= 1'b0; if (ext_req_valid && ext_req_ready) begin ext_active <= 1'b1; ext_start_pulse <= 1'b1; ext_mode_r <= ext_req_mode; ext_src_r <= ext_req_src; ext_dst_r <= ext_req_dst; ext_bytes_r <= ext_req_bytes; ext_ep_id_r <= ext_req_ep_id; ext_ep_bank_r <= ext_req_ep_bank; ext_ep_word_off_r <= ext_req_ep_word_off; end else if (ext_active && ep_done) begin ext_active <= 1'b0; ext_req_done <= 1'b1; end end end // ========================================== // Endpoint transfer engine (32-bit only in this SoC integration) // ========================================== typedef enum logic [2:0] {EP_IDLE, EP_AR, EP_READ, EP_AW, EP_WRITE, EP_BRESP, EP_DONE} ep_state_t; ep_state_t ep_state; logic [31:0] ep_axi_addr; logic [31:0] ep_bytes_left; logic [7:0] ep_word_addr_r; wire [7:0] ep_beats_total = (engine_bytes[31:2] == 30'd0) ? 8'd1 : engine_bytes[9:2]; wire [7:0] ep_axi_len = ep_beats_total - 8'd1; wire ep_mode_mem2ep = (engine_mode == MODE_MEM2EP); wire ep_mode_ep2mem = (engine_mode == MODE_EP2MEM); always_ff @(posedge clk) begin if (!rst_n) begin ep_state <= EP_IDLE; ep_done <= 1'b0; ep_axi_addr <= '0; ep_bytes_left <= '0; ep_word_addr_r <= '0; end else begin ep_done <= 1'b0; case (ep_state) EP_IDLE: begin if (ep_start) begin ep_axi_addr <= ep_mode_mem2ep ? engine_src : engine_dst; ep_bytes_left <= engine_bytes; ep_word_addr_r <= engine_ep_word_off; ep_state <= ep_mode_mem2ep ? EP_AR : EP_AW; end end EP_AR: begin if (ep_arvalid && ep_arready) ep_state <= EP_READ; end EP_READ: begin if (mm2mm_rvalid && ep_rready) begin ep_word_addr_r <= ep_word_addr_r + 8'd1; ep_bytes_left <= (ep_bytes_left > 32'd4) ? (ep_bytes_left - 32'd4) : 32'd0; if (mm2mm_rlast) ep_state <= EP_DONE; end end EP_AW: begin if (ep_awvalid && ep_awready) ep_state <= EP_WRITE; end EP_WRITE: begin if (ep_wvalid && ep_wready) begin ep_word_addr_r <= ep_word_addr_r + 8'd1; ep_bytes_left <= (ep_bytes_left > 32'd4) ? (ep_bytes_left - 32'd4) : 32'd0; if (ep_wlast) ep_state <= EP_BRESP; end end EP_BRESP: begin if (mm2mm_bvalid && ep_bready) ep_state <= EP_DONE; end EP_DONE: begin ep_done <= 1'b1; ep_state <= EP_IDLE; end default: ep_state <= EP_IDLE; endcase end end assign ep_arid = '0; assign ep_araddr = ep_axi_addr; assign ep_arlen = ep_axi_len; assign ep_arsize = 3'd2; assign ep_arburst = 2'b01; assign ep_arlock = 1'b0; assign ep_arcache = 4'b0; assign ep_arprot = 3'b0; assign ep_arqos = 4'b0; assign ep_aruser = '0; assign ep_arvalid = (ep_state == EP_AR); assign ep_rready = (ep_state == EP_READ) && ep_wr_ready; assign ep_wr_valid = (ep_state == EP_READ) && mm2mm_rvalid && ep_mode_mem2ep; assign ep_wr_id = engine_ep_id; assign ep_wr_bank = engine_ep_bank; assign ep_wr_word_addr = ep_word_addr_r; assign ep_wr_data = mm2mm_rdata[31:0]; assign ep_awid = '0; assign ep_awaddr = ep_axi_addr; assign ep_awlen = ep_axi_len; assign ep_awsize = 3'd2; assign ep_awburst = 2'b01; assign ep_awlock = 1'b0; assign ep_awcache = 4'b0; assign ep_awprot = 3'b0; assign ep_awqos = 4'b0; assign ep_awuser = '0; assign ep_awvalid = (ep_state == EP_AW); assign ep_wdata = ep_rd_data; assign ep_wstrb = {{(DATA_WIDTH/8){1'b1}}}; assign ep_wlast = (ep_bytes_left <= 32'd4) && ep_wvalid; assign ep_wuser = '0; assign ep_wvalid = (ep_state == EP_WRITE) && ep_mode_ep2mem && ep_rd_ready; assign ep_bready = (ep_state == EP_BRESP); assign ep_rd_valid = (ep_state == EP_WRITE) && ep_mode_ep2mem && ep_wready; assign ep_rd_id = engine_ep_id; assign ep_rd_bank = engine_ep_bank; assign ep_rd_word_addr = ep_word_addr_r; endmodule