4 示例主机应用程序
4.1 概述
有四个或五个简单的 C 程序演示了如何访问 Xillybus 的设备文件。这些程序可在包含 Xillybus / XillyUSB 主机驱动程序的压 缩文件中找到(可从网站下载)。请参阅“demoapps”目录,其中包含以下文件:
-
Makefile——此文件包含“make”工具用于编译程序的规则。
-
streamread.c——从文件读取数据,并将数据发送到标准输出。
-
streamwrite.c——从标准输入读取数据,并将数据发送到文件。
-
memread.c——执行 seek 操作后读取数据。演示如何访问 FPGA 中的存储器接口(memory interface)。
-
memwrite.c——执行 seek 操作后写入数据。演示如何访问 FPGA 中的存储器接口。
这些程序的目的是展示正确的编码风格。它们也可以作为编写您自己程序的基础。然而,这些程序均不适用于实际应用,特 别是因为它们在高数据速率下表现不佳。请参见第 5 章以获取实现高带宽性能的指 南。
这些程序非常简单,仅演示了在 Linux 计算机上访问文件的标准方法。这些方法在 Xillybus Linux 主机应用程序编程指南中有详细讨论。因此,此处不对这些程序进行详细解释。
请注意,这些程序使用了底层 API,例如 open()、read() 和 write()。避免使用更广为人知的 API(如 fopen()、fread()、fwrite() 等),因为这些 API 依赖于 C 运行时库维护的数据缓冲区。这些数据缓冲区可能引起混淆,尤其是因为与 FPGA 的通信常常因运行时库而延迟。
下载 PCIe 驱动程序的用户会在“demoapps”目录中找到第五个程序: fifo.c。该程序演示了用户空间 RAM FIFO 的实现。此 程序很少有用,因为设备文件的 RAM 缓冲区可以配置得足够大,以适用于几乎所有场景。因此,fifo.c 仅在高数据速率且 RAM 缓冲区需要非 常大(即数 GB)时才有用。
该程序未随 XillyUSB 的驱动程序一起提供,因为可能需用 fifo.c 的数据速率在 XillyUSB 上无法达到。
4.2 编辑与编译
如果您熟悉 Linux 上程序的编译,可以跳到下一节: Xillybus 示例程序的编译可通过“make”按常规方式进行。
首先,切换到 C 文件所在的目录:
$ cd demoapps
要编译所有五个程序,只需在 shell 提示符下输入“make”。预期输出如下:
$ make gcc -g -Wall -O3 memwrite.c -o memwrite gcc -g -Wall -O3 memread.c -o memread gcc -g -Wall -O3 streamread.c -o streamread gcc -g -Wall -O3 streamwrite.c -o streamwrite gcc -g -Wall -O3 -pthread fifo.c -o fifo
以“gcc”开头的五行是“make”请求使用的编译器命令。这些命令可用于单独编译程序,但没必要这样做,只需使用“make”即 可。
在某些系统上,如果未安装 POSIX 线程库(例如某些 Cygwin 安装),则第五个编译(fifo.c)可能失败。如果您不打算使用 fifo.c,可以忽略此错误。
“make”工具仅编译必要的内容。如果只修改了一个文件,“make”将仅请求编译该文件。因此,正常的工作方式是编辑您想要 编辑的文件,然后使用“make”重新编译,不会发生不必要的编译。
使用“make clean”可以删除先前编译生成的可执行文件。
如前所述,Makefile 包含编译规则。该文件的语法并不简单,但幸运的是,通常只需借助常识即可对该文件进行修改。
Makefile 与它所在的同一目录中的文件相关联。因此,您可以复制整个目录,然后在该副本中的文件上工作。这两个目录副本不会相互干 扰。
也可以添加一个 C 文件并轻松修改 Makefile,以便“make”也能编译这个新文件。例如,假设将 memwrite.c 复制到一个新文 件,命名为 mygames.c。可以通过 GUI 界面或命令行完成:
$ cp memwrite.c mygames.c
下一步是编辑 Makefile。有许多文本编辑器,并且运行它们的方式也多种多样。在大多数系统上,可以通过在 shell 提示符 下输入“gedit”或“xed”来启动 GUI 编辑器。不过,在计算机桌面的菜单中更容易找到 GUI 文本编辑器。还有许多可以在终端窗口内工作的文本 编辑器,例如 vim、emacs、nano 和 pico。
使用哪个编辑器取决于个人品味和经验。例如,可以使用以下命令开始编辑 Makefile:
$ xed Makefile &
命令末尾的“&”告诉 shell 不要等待程序结束: 下一个 shell 提示符会立即出现。这适用于启动 GUI 应用程 序等。
需要在 Makefile 中修改的行是:
APPLICATIONS=memwrite memread streamread streamwrite
将该行修改为:
APPLICATIONS=memwrite memread streamread streamwrite mygames
下次输入“make”时,就会编译 mygames.c。
4.3 运行程序
第 3.3 节 中展示的简单回环示例可以用两个示例程序来完成。
假设“demoapps”已是当前目录,并且已经用“make”完成了编译。
在第一个终端中输入:
$ ./streamread /dev/xillybus_read_8
这是从设备文件读取的程序。
请注意,命令以“./”开头: 必须明确指明可执行文件所在的目录。在此示例中,使用“./”来表示当前目录。
然后,在第二个终端窗口中:
$ ./streamwrite /dev/xillybus_write_8
这与使用“cat”的示例类似。区别在于,“streamwrite”在将数据发送到设备文件之前不会等待 ENTER 键。相反,此程序尝试 对每个字符分别操作。为此,该程序使用了一个名为 config_console() 的函数。该函数仅用于实现对键盘输入的即时响应,与 Xillybus 无 关。
以上示例针对的是 PCIe / AXI 的 Xillybus。对于 XillyUSB,设备文件的名称前缀略有不同。例如,使用 xillyusb_00_read_8 而不是 xillybus_read_8。
重要
的:
streamread 和 streamwrite 执行的 I/O 操作效率较低: 为了使这些程序更简单,I/O 缓冲区的大小仅为 128 字节。当需要
高数据速率时,应使用更大的缓冲区。请参见第 5.3 节。
4.4 存储器接口
memread 和 memwrite 程序更有趣,因为它们演示了如何访问 FPGA 上的存储器。这是通过对设备文件调用 lseek() 函数来实现的。在 Xillybus Linux 主机应用程序编程指南 中有一节解释了与 Xillybus 设备文件相关的此 API。
请注意,在 demo bundle 中,只有 xillybus_mem_8 允许 seek 操作。该设备文件也是唯一一个可以同时以读写方式打开的 文件。
在写入存储器之前,可以使用 hexdump 工具观察当前状态:
$ hexdump -C -v -n 32 /dev/xillybus_mem_8 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000020
此输出是存储器阵列中的前 32 个字节: hexdump 打开了 /dev/xillybus_mem_8 并从该设备文件读取了 32 个字节。当打开 一个允许 lseek() 的文件时,初始位置始终为零。因此,输出包含存储器阵列中从位置 0 到位置 31 的数据。
您的输出可能不同: 此输出反映的是 FPGA 的 RAM,其中可能包含其他值。特别是,这些值可能因之前对 RAM 的实验而不为零。
关于 hexdump 标志的说明: 上面显示的输出格式是由“-C”和“-v”选项产生的。“-n 32”表示仅显示前 32 个字节。 存储器阵列只有 32 个字节长,因此读取更多字节是毫无意义的。
memwrite 可用于更改阵列中的值。例如,可以使用以下命令将地址 3 处的值更改为 170(十六进制格式为 0xaa):
$ ./memwrite /dev/xillybus_mem_8 3 170
为了验证命令是否生效,可以重复上面的 hexdump 命令:
$ hexdump -C -v -n 32 /dev/xillybus_mem_8 00000000 00 00 00 aa 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000020
显然,命令生效了。
在 memwrite.c 中,关键部分是“lseek(fd, address, SEEK_SET)”这一行。该函数调用改变了设备文件的位置,从而改变了 FPGA 内部被访问的阵列元素的地址。随后的读操作或写操作从此位置开始。每次这样的访问都会根据传输的字节数递增位置。
允许 seek 操作的设备文件对于轻松向 FPGA 发送配置命令也很有用。如前所述,当打开一个允许 lseek() 的文件时,初始位 置始终为零。这也适用于如下示例中的命令:
$ echo -n 010111 > /dev/xillybus_mem_8
注意“echo”命令中的“-n”部分。这可以防止“echo”在其输出末尾添加换行符。
此命令将“0”的 ASCII 码(值 0x30)写入地址零。同样,将值 0x31 写入地址 1,依此类推。因此,这个简单的“echo”命令可 以用于一次性设置多个寄存器的值。
这是一种便捷的方法,因为 FPGA 上的实现很简单。例如,假设“echo”命令只打算使用字符“0”和“1”。因此,只有 bit 0 是重 要的。以下是一个示例寄存器,它从“echo”命令的第三个字节获取其值:
reg my_register;
always @(posedge bus_clk)
if (user_w_mem_8_wren && (user_mem_8_addr == 2))
my_register <= user_w_mem_8_data[0];
这种方法特别适用于在开发 FPGA 逻辑时进行测试。
