From 0bf2017e9e4f62ac559ec4b0327b6882d6c77eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=B3=E5=85=89=E5=B0=91=E5=B9=B4?= <849317537@qq.com> Date: Mon, 27 May 2024 16:58:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0xdma=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- xdma/axi_bram.sv | 177 ++++++++++++++++++++++++++++++++++++++ xdma/fpga_top.sv | 220 +++++++++++++++++++++++++++++++++++++++++++++++ xdma/xdma_rw.c | 216 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 613 insertions(+) create mode 100644 xdma/axi_bram.sv create mode 100644 xdma/fpga_top.sv create mode 100644 xdma/xdma_rw.c diff --git a/xdma/axi_bram.sv b/xdma/axi_bram.sv new file mode 100644 index 0000000..3645a01 --- /dev/null +++ b/xdma/axi_bram.sv @@ -0,0 +1,177 @@ + +module axi_bram #( + parameter AXI_IDWIDTH = 4, // AXI的id宽度 + parameter AXI_AWIDTH = 64, // AXI的 地址总线宽度 + parameter AXI_DWIDTH = 256, // AXI的数据总线宽度 一次读取的数据位数 + parameter MEM_AWIDTH = 12 // BRAM size = MEM_AWIDTH*C_M_AXI_DATA_WIDTH (bits) = MEM_AWIDTH*C_M_AXI_DATA_WIDTH/8 (bytes) +) ( + input wire rstn, + input wire clk, + // AXI-MM AW interface ---------------------------------------------------- + output wire s_axi_awready, // 从写地址准备好 + input wire s_axi_awvalid, // 主准备好 + input wire [ AXI_AWIDTH-1:0] s_axi_awaddr, + input wire [ 7:0] s_axi_awlen, + input wire [ AXI_IDWIDTH-1:0] s_axi_awid, + // AXI-MM W interface ---------------------------------------------------- + output wire s_axi_wready, + input wire s_axi_wvalid, + input wire s_axi_wlast, + input wire [ AXI_DWIDTH-1:0] s_axi_wdata, + input wire [(AXI_DWIDTH/8)-1:0] s_axi_wstrb, + // AXI-MM B interface ---------------------------------------------------- + input wire s_axi_bready, + output wire s_axi_bvalid, + output wire [ AXI_IDWIDTH-1:0] s_axi_bid, + output wire [ 1:0] s_axi_bresp, + // AXI-MM AR interface ---------------------------------------------------- + output wire s_axi_arready, // 表示S 已经可以接受 读地址信号 + input wire s_axi_arvalid, // 表示M 地址已经准备就绪 + input wire [ AXI_AWIDTH-1:0] s_axi_araddr, // M 发送的地址 + input wire [ 7:0] s_axi_arlen, // 突发长度 + input wire [ AXI_IDWIDTH-1:0] s_axi_arid, + // AXI-MM R interface ---------------------------------------------------- + input wire s_axi_rready, // 只有当M ready的时候, S才可以发数据 + output wire s_axi_rvalid, // S的数据已经准备好, 可以发送到M + output wire s_axi_rlast, + output reg [ AXI_DWIDTH-1:0] s_axi_rdata, + output wire [ AXI_IDWIDTH-1:0] s_axi_rid, + output wire [ 1:0] s_axi_rresp +); + + +function automatic int log2 (input int x); + int xtmp = x, y = 0; + while (xtmp != 0) begin + y ++; + xtmp >>= 1; + end + return (y == 0) ? 0 : (y - 1); +endfunction + + + +// --------------------------------------------------------------------------------------- +// AXI READ state machine +// --------------------------------------------------------------------------------------- + +enum reg [0:0] {R_IDLE, R_BUSY} rstate = R_IDLE; + +reg [AXI_IDWIDTH-1:0] rid = '0; // 从 读地址 传过来的id +reg [ 7:0] rcount = '0; // 从 读地址 传过来的读取次数 +reg [ MEM_AWIDTH-1:0] mem_raddr, mem_raddr_last; // 当前操作的地址, 和上次操作的地址 + +assign s_axi_arready = (rstate == R_IDLE); // 读地址 可以进行 接收 +assign s_axi_rvalid = (rstate == R_BUSY); // 读数据 以准备就绪 +assign s_axi_rlast = (rstate == R_BUSY) && (rcount == 8'd0); // 是否是最后一个数据, 直接连线 +assign s_axi_rid = rid; // 直接连线 +assign s_axi_rresp = '0; + +always @ (posedge clk or negedge rstn) + if (~rstn) begin + rstate <= R_IDLE; + rid <= '0; + rcount <= '0; + end + else begin + case (rstate) + R_IDLE : + if (s_axi_arvalid) begin // M发起 读地址 请求 + rstate <= R_BUSY; // S切换到忙碌 + rid <= s_axi_arid; // 记录本次 M读地址 ID + rcount <= s_axi_arlen; // 记录本次 M的读长度 + end + R_BUSY : + if (s_axi_rready) begin + if (rcount == 8'd0) // 如果是 忙碌状态, 且 是最后一个数据 + rstate <= R_IDLE; // 则切换 S状态 为 空闲, 状态结束 + rcount <= rcount - 8'd1; // 每次忙碌状态 rcount 都会减1 + end + endcase + end + +// 组合逻辑块,根据当前状态计算内存读地址 +always_comb + if (rstate == R_IDLE && s_axi_arvalid) // 如果空闲状态 且 M已经准备好地址 + + mem_raddr = (MEM_AWIDTH)'(s_axi_araddr >> log2(AXI_DWIDTH/8)); // 计算地址, + // 因为每次数据都会 传256位数据, 这里除以8, 得到需要传的次数, 每次1字节传输, 256/8就是32次 + // 看能整除多少次2, 然后 s_axi_araddr 进行右移, 移动到传过来的地址的 AXI_DWIDTH的字节边界 start起始的位置 + + else if (rstate == R_BUSY && s_axi_rready) // 如果繁忙, 且 M 已经准备好 接收 S发来的数据 + mem_raddr = mem_raddr_last + (MEM_AWIDTH)'(1); // 计算下一个内存地址, 加一即可, 这是一个二维数组 + else + mem_raddr = mem_raddr_last; // 保持地址不变 + +// 在每个时钟上升沿,更新上一次的读地址 +always @ (posedge clk) + mem_raddr_last <= mem_raddr; + + + +// --------------------------------------------------------------------------------------- +// AXI WRITE state machine +// --------------------------------------------------------------------------------------- + +enum reg [1:0] {W_IDLE, W_BUSY, W_RESP} wstate = W_IDLE; + +reg [AXI_IDWIDTH-1:0] wid = '0; +reg [ 7:0] wcount = '0; +reg [ MEM_AWIDTH-1:0] mem_waddr = '0; + +assign s_axi_awready = (wstate == W_IDLE); +assign s_axi_wready = (wstate == W_BUSY); +assign s_axi_bvalid = (wstate == W_RESP); +assign s_axi_bid = wid; +assign s_axi_bresp = '0; + +always @ (posedge clk or negedge rstn) + if (~rstn) begin + wstate <= W_IDLE; + wid <= '0; + wcount <= '0; + mem_waddr <= '0; + end else begin + case (wstate) + W_IDLE : + if (s_axi_awvalid) begin + wstate <= W_BUSY; + wid <= s_axi_awid; + wcount <= s_axi_awlen; + mem_waddr <= (MEM_AWIDTH)'(s_axi_awaddr >> log2(AXI_DWIDTH/8)); + end + W_BUSY : + if (s_axi_wvalid) begin + if (wcount == 8'd0 || s_axi_wlast) + wstate <= W_RESP; + wcount <= wcount - 8'd1; + mem_waddr <= mem_waddr + (MEM_AWIDTH)'(1); + end + W_RESP : + if (s_axi_bready) + wstate <= W_IDLE; + default : + wstate <= W_IDLE; + endcase + end + + + +// --------------------------------------------------------------------------------------- +// a block RAM +// --------------------------------------------------------------------------------------- + +reg [AXI_DWIDTH-1:0] mem [ 1< +#include +#include +#include +#include + +#include + + + + +// 函数:从设备读取数据到本地内存(缓冲区) +// 功能:实现设备到主机的数据传输 +// 参数: +// dev_fd : 设备实例句柄 +// addr : 设备中的源地址 +// buffer : 缓冲区基指针 +// size : 数据大小 +// 返回值: +// 成功:0;失败:-1 +int dev_read (int dev_fd, uint64_t addr, void *buffer, uint64_t size) { + if ( addr != lseek(dev_fd, addr, SEEK_SET) ) // 调整文件偏移到指定地址 + return -1; // 偏移失败 + if ( size != read(dev_fd, buffer, size) ) // 从设备读取数据到缓冲区 + return -1; // 读取失败 + return 0; +} + + +// 函数:从本地内存(缓冲区)写数据到设备 +// 功能:实现主机到设备的数据传输 +// 参数: +// dev_fd : 设备实例句柄 +// addr : 设备中的目标地址 +// buffer : 缓冲区基指针 +// size : 数据大小 +// 返回值: +// 成功:0;失败:-1 +int dev_write (int dev_fd, uint64_t addr, void *buffer, uint64_t size) { + if ( addr != lseek(dev_fd, addr, SEEK_SET) ) + return -1; + if ( size != write(dev_fd, buffer, size) ) + return -1; + return 0; +} + + + +// 函数:获取毫秒级别的时间 +// 功能:返回当前时间的毫秒数 +static uint64_t get_millisecond () { + struct timeb tb; + ftime(&tb); + return (uint64_t)tb.millitm + (uint64_t)tb.time * 1000UL; + // tb.time is the number of seconds since 00:00:00 January 1, 1970 UTC time; + // tb.millitm is the number of milliseconds in a second +} + + + +// 函数:解析无符号整数 +// 功能:从字符串中解析出十六进制或十进制的无符号64位整数 +// 参数: +// string : 输入的字符串 +// pvalue : 解析后的值的指针 +// 返回值: +// 成功解析的项数,失败则为0 +int parse_uint (char *string, uint64_t *pvalue) { + if ( string[0] == '0' && string[1] == 'x' ) // 十六进制格式"0xXXXXXXXX" + return sscanf( &(string[2]), "%lx", pvalue); + else // 十进制格式 + return sscanf( string , "%lu", pvalue); +} + + + + +#define DMA_MAX_SIZE 0x10000000UL // DMA操作的最大尺寸定义 256kb + + + +char USAGE[] = + "Usage: \n" + "\n" + " 写入设备(主机到设备):\n" + " %s <文件名> to <设备名> <设备内地址> <大小>\n" + " 示例:\n" + " %s data.bin to /dev/xdma0_h2c_0 0x100000 0x10000\n" + "\n" + " 读取设备(设备到主机):\n" + " %s <文件名> from <设备名> <设备内地址> <大小>\n" + " 示例:\n" + " %s data.bin from /dev/xdma0_c2h_0 0x100000 0x10000\n"; + + + +int main (int argc, char *argv[]) { + int ret = -1; + + uint64_t millisecond; + char usage_string [1024]; + + char *dev_name, *file_name; + char direction; + uint64_t address, size; + + int dev_fd = -1; + FILE *file_p = NULL; + void *buffer = NULL; + + sprintf(usage_string, USAGE, argv[0], argv[0], argv[0], argv[0] ); + + if (argc < 6) { // not enough argument + puts(usage_string); + return -1; + } + + + file_name = argv[1]; + direction = argv[2][0]; + dev_name = argv[3]; + + if ( 0 == parse_uint(argv[4], &address) ) { // parse the address in the device + puts(usage_string); + return -1; + } + + if ( 0 == parse_uint(argv[5], &size ) ) { // parse the size in the device + puts(usage_string); + return -1; + } + + // print information: + if (direction == 't') { // to (write device, host-to-device) + printf("from : %s\n" , file_name); + printf("to : %s addr=0x%lx\n" , dev_name, address); + printf("size : 0x%lx\n\n" , size); + } else if (direction == 'f') { // from (read device, device-to-host) + printf("from : %s addr=0x%lx\n" , dev_name, address); + printf("to : %s\n" , file_name); + printf("size : 0x%lx\n\n" , size); + } else { + puts(usage_string); + return -1; + } + + + if (size > DMA_MAX_SIZE || size == 0) { + printf("*** ERROR: DMA size must larger than 0 and NOT be larger than %lu\n", DMA_MAX_SIZE); + return -1; + } + + + buffer = malloc(size); // allocate local memory (buffer) + if (buffer == NULL) { + printf("*** ERROR: failed to allocate buffer\n"); + goto close_and_clear; + } + + dev_fd = open(dev_name, O_RDWR); // open device + if (dev_fd < 0) { + printf("*** ERROR: failed to open device %s\n", dev_name); + goto close_and_clear; + } + + file_p = fopen(file_name, direction == 't' ? "rb" : "wb"); // open file for read/write + if (file_p == NULL) { + printf("*** ERROR: failed to open file %s\n", file_name); + goto close_and_clear; + } + + millisecond = get_millisecond(); // start time + + if (direction == 't') { + if ( size != fread(buffer, 1, size, file_p) ) { // local file -> local buffer + printf("*** ERROR: failed to read %s\n", file_name); + goto close_and_clear; + } + if ( dev_write(dev_fd, address, buffer, size) ) { // local buffer -> device + printf("*** ERROR: failed to write %s\n", dev_name); + goto close_and_clear; + } + } else { + if ( dev_read(dev_fd, address, buffer, size) ) { // device -> local buffer + printf("*** ERROR: failed to read %s\n", dev_name); + goto close_and_clear; + } + if ( size != fwrite(buffer, 1, size, file_p) ) { // local buffer -> local file + printf("*** ERROR: failed to write %s\n", file_name); + goto close_and_clear; + } + } + + millisecond = get_millisecond() - millisecond; // get time consumption + millisecond = (millisecond > 0) ? millisecond : 1; // avoid divide-by-zero + + printf("time=%lu ms data rate=%.1lf KBps\n", millisecond, (double)size / millisecond ); + + ret = 0; + +close_and_clear: + + if (buffer != NULL) + free(buffer); + + if (dev_fd >= 0) + close(dev_fd); + + if (file_p != NULL) + fclose(file_p); + + return ret; +} + +