
前几篇文章主要集中在 Zynq SoC 的处理系统 (PS) 包括:
使用 MIO 和 EMIO
Zynq SoC 的中断结构
Zynq 私有定时器和看门狗
Zynq SoC 三重定时器计数器 (TTC)
但从设计的角度来看,Zynq SoC 真正令人兴奋的是创造一个使用 Zynq 可编程逻辑 (PL) 应用程序。使用 PL 将任务从 PS 加载到 PL 为其他任务回收处理器带宽,加速任务。此外,PS 端可以控制 PL 在经典电影系统应用中执行的操作端。使用 Zynq SoC 的 PL 提高系统性能,降低功耗,为实时事件提供可预测的延迟。
简介
Zynq PS 和 PL 连接以下接口:
两个 32 位主 AXI 端口(PS 主)
两个 32 位从 AXI 端口(PL 主)
四个 32/64 位从机高性能端口(PL 主机)
1 个 64 位于加速器一致性端口 (ACP)(PL 主控)
从 PS 到 PL 的四个时钟
PS 到 PL 中断
PL 到 PS 中断
DMA 外设请求接口
以下是不同接口点的框图:
ARM 的 AXI 为提供高带宽和低延迟提供突发协议。每个 AXI 端口包含独立的读写通道。使用要求低的接口 AXI 协议的一个版本是 AXI4-Lite,它是一种更简单的协议,可用于寄存器式控制/状态接口。例如,Zynq XADC 使用 AXI4-Lite 接口连接到 Zynq PS。有关 AXI 有关协议的更多信息,请访问:
http://www.arm.com/products/system-ip/amba/amba-open-specifications.php
Zynq SoC 支持三种不同的 AXI 它们可以用来连接传输类型PS到设备的PL端:
AXI4 Burst transfers
AXI4-Lite for simple control interfaces
AXI4-Streaming for unidirectional data transfers
下表定义了每个接口的理论带宽:
必须使用 Zynq SoC 的 DMA 控制器可以达到上表中列出的最大速度。当 PS 是主机时,DMA 减少了控制器 Zynq SoC 的 ARM Cortex-A9 MPCore 处理器负载。在不使用 DMA 从 PS 到 PL 端的最大传输速率为 25Mbytes/sec。
总之,在 PS 和 PL 之间使用了惊人的东西 14.4Gbytes/sec(115.2Gbits/sec)理论带宽!
创建AXI外设
本节将使用 AXI 接口在 Zynq SoC 的可编程逻辑结构中创建外设。
第一步
第一步是打开 Vivado 设计并从工具选项中选择创建和包装 IP”选项-create and package IP。
这将打开允许创建的对话框 AXI4 外设。对话框的第一个实际页面提供了创建新的选项 IP 或将当前设计或目录转换为当前设计或目录 IP 模块。
选择创新 AXI4 外设 - Create new AXI4 peripheral选项指向预定义 IP 位置 Vivado 主页上的管理 IP 创造新的部分 IP 位置。
然后,对话框允许输入新外围设备的库、名称、描述和公司 URL。这个非常简单的例子(我稍后会扩展)。
下面的对话框是一个强大的对话框,我们可以定义我们想要指定的 AXI4 接口类型:
主或从
接口类型 – Lite、Streaming 或 Burst
总线宽度 32 或 64 位
内存大小
寄存器数量
这个初始示例非常简单,这样我就可以演示创建外设所需的过程 Vivado 实现它,然后导出它 SDK。出于这个原因,我寄存器 AXI4-L ite 接口,然后我们可以使用软件找到它。这些寄存器可用于控制设计的可编程逻辑。
最后创建外围设备-create peripheral对话框允许为新外围设备生成驱动程序文件选择一个选项。因为它会使外设和 SDK 使用更简单。
一旦“Create Peripheral关闭向导,可以打开创建 VHDL 在文件中添加自定义硬件设计 PL 中执行想要的功能。我只使用我们创建的四个寄存器,所以我不能编辑文件。创建外围设备后,我们希望在 Vivado 在设计中连接和使用它。这很简单。打开系统框图,选择从左菜单中添加系统框图 IP 选项。在这个菜单中创建的外围设备应该能够找到。外围设备可按字母顺序列出。
第二步
将此 IP 将模块拖入设计中,然后连接到 AXI GP 总线,其中 Vivado 提供运行连接自动化工具。
该工具将产生我们可以实施的设计。
外设的地址范围可以通过单击地址编辑器选项卡来修改。请注意,4k 对我们来说,地址空间是允许的最小地址空间 4 寄存器示例太慷慨了。幸运的是,Zynq SoC 中的 ARM Cortex-A9 MPCore 处理器有大量的地址空间。
一旦 Vivado 连接和地址空间地址空间分配,如下图所示,我们可以设计并导出它 SDK。然后我们就可以开始使用我们的外围设备了。
请注意,可以检查实施报告,以确保包含已创建的外围设备。
验证
上面我们已经产生了一个AXI外设,下一步SDK验证外设的正确性。
使用 Vivado 创建 AXI4 外设并生成BIN文件。
在创建设计的硬件组件后,我们现在需要将其导出给我们 SDK 在设计中,我们可以编写软件来驱动它。第一步是在 Vivado 打开当前项目,编译生成BIN文件,然后将硬件导出到 SDK。(如果试图导出硬件,SDK 在使用中,将收到警告。)若不导出硬件 SDK,则下次打开 SDK 硬件定义和板级支撑包需要更新,否则不能使用。还需要更新设计中定义的存储库,包括包含外围设备的存储库 IP 存储库。
打开 xparameters.h 文件(在 BSP 包含用于查看文件中的文件 AXI4 外设地址空间:
下一步是打开 System.MSS 要使用的文件应自定义和定制 BSP驱动程序是在外设创建过程中生成的,而不是通用驱动程序。
重建项目可以确保驱动程序文件被加载 BSP 这是一个非常有用的步骤,因为这些文件还包含一个简单的自检程序,可以用来测试外设的软件接口是否正确,然后开始使用它进行更先进的操作。使用这个测试程序也表明我们已经在那里了 Vivado 硬件实例化正确。
在 BSP 下 libsrc 中,会看到很多新的 AXI4 外设文件。这些文件允许使用本地外围设备(如 XADC 和 GPIO)我们以前在其他文章中使用同样的读写设备。
对于这个简单的例子,文件 myip.h 三个函数可以用来驱动新外设。
MYIP_mReadReg(BaseAddress, RegOffset)
MYIP_mWriteReg(BaseAddress, RegOffset, Data)
XStatus MYIP_Reg_SelfTest( void * baseaddr_p);
读写功能除自检功能外,还映射到通用函数 Xil_In32 和 Xil_Out32,它们在 Xil_io.h 定义。然而,由于搜索地址的外设非常清晰,使用创建的函数可以使代码更可读。
对于这个例子,我们在外设中只有四个寄存器,所以我们只使用自检,它将写入并读取所有寄存器,并报告通过或失败。这个测试让我们相信,一旦我们在外围模块中定义了它们,我们就可以继续使用更先进的功能,我们已经获得了正确的硬件和软件环境。在下一篇文章中,我们将研究如何使用它 HDL 从处理系统中卸载功能,提高系统性能,将代码添加到外设中。
利用XADC进行复杂运算
假设我们想要 Zynq 实施更复杂的计算,如工业控制系统。通常,通过多个模拟输入( ADC),铂电阻温度计由热敏电阻、热电偶、压力传感器 (PRT) 等待传感器驱动。
很多时候,来自这些传感器的数据需要从未来传递函数 ADC 将原始数据值转换为可用于进一步处理的数据。一个很好的例子是 Zynq XADC,它在 XADCPS.h 它包含许多函数/宏,用于原始 XADC 值转换为电压或温度。但而,这些转换非常简单。如果是这样的话。计算变得越复杂,则需要 Zynq 处理时间就越多。如果使用 Zynq SoC 的可编程逻辑 (PL) 端来执行这些计算,则可以大大加快计算速度。附带的好处是,处理器还可以腾出时间来执行其他软件任务,因此可以通过使用 PL 进行计算来提高处理带宽。
我们可以使用以millibars为单位的大气压力转换为以米(meters)为单位的高度的示例来演示这种转换。下面的传递函数给出了压力在 0 到 10 millibars之间的海拔高度:
使用 Zynq SoC 的处理系统 (PS) Zynq 实现这个传递函数非常简单,使用下面的代码行,其中“结果-result”是一个浮点数;a、b 和 c 是上述传递函数中定义的常数;i 是输入值
result = ((float)a*(i*i)) + ((float)b*(i)) + (float)c;
对于这个例子,我将使用嵌套在“for”循环中的代码来模拟上面输入值中的步骤。代码通过 STDOUT 输出结果。因为我要计算执行这个计算所需的时间,我将使用私有计时器来确定这个时间,如下:
for(i=0.0; i<10.0; i = i +0.1 ){
XScuTimer_LoadTimer(&Timer, TIMER_LOAD_VALUE);
timer_start = XScuTimer_GetCounterValue(&Timer);
XScuTimer_Start(&Timer);
result = ((float)a*(i*i)) + ((float)b*(i)) + (float)c;
XScuTimer_Stop(&Timer);
timer_value = XScuTimer_GetCounterValue(&Timer);
printf("%f,%f,%lu,%lu, \n\r",i,result,timer_start, timer_value);
}
虽然此代码可能无法提供最准确的时序参考,但足以证明我们研究的原理。在Zynq板上运行上述代码,在终端窗口中获得了以下结果。注意:
对此输出进行一些简单的分析表明,计算结果平均需要 25 个 CPU_3x2x 时钟周期。。使用 666MHz 处理器时钟,此计算需要 76 ns。我相信很多人会看到ADC输出不是浮点数而是一个定点数。使用整数数学重写函数代码导致时钟周期的平均数非常相似。但是我认为对于这个例子,浮点数会更容易使用,并且不需要解释定点数系统背后的原理。
在确定了 Zynq 的 PS 端执行中等复杂度传递函数需要多长时间的基准之后,我们下一次可以看看当我们将相同的函数转移到设备的 PL 端时,我们能以多快的速度计算这个函数。
定点数工作原理
上一节我们使用PS计算了一个公式,接下来我们将使用PL端加速这一公式计算,但是PL端的特点是只能进行定点计算,所以这一小节我们将说明一下定点数工作原理。
在数字系统中有两种表示数字的方法:定点或浮点。定点表示将小数点保持在固定位置,这就大大简化了算术运算。如图所示,定点数由称为整数和小数部分的两部分组成:数字的整数部分在隐含小数点的左侧,小数部分在右侧。
上述定点数能够使用二进制补码表示表示介于 0.0 和 255.9906375 之间的无符号数或介于 –128.9906375 和 127.9906375 之间的有符号数。
浮点数分为指数和尾数两部分。浮点表示允许小数点根据值的大小在数字内浮动。定点表示的主要缺点是要表示更大的数字或使用小数获得更准确的结果,需要更多的位。虽然 FPGA 可以同时支持定点数和浮点数,但大多数应用程序都采用定点数系统,因为它们比浮点数系统更易于实现。
在设计中,我们可以选择使用无符号或有符号数字。通常,选择受到正在实施的算法的限制。无符号数可以表示 0 到 2n – 1 的范围,并且始终表示正数。有符号数使用补码数系统来表示正数和负数。二进制补码系统允许通过简单地将两个数字相加来从另一个数字中减去一个数字。补码数可以表示的范围是:- (2n-1) ~ + (2n-1 – 1)
表示定点数内整数位和小数位之间分割的正常方式是 x,y,其中 x 表示整数位的数量,y 表示小数位的数量。例如 8,8 代表 8 个整数位和 8 个小数位,而 16,0 代表 16 个整数和 0 个小数位。
在许多情况下,整数和小数位数将在设计时确定,这个时间通常在算法从浮点转换之后。由于 FPGA 的灵活性,我们可以表示任意位长的定点数;我们不仅限于 32、64 甚至 128 位寄存器。FPGA 对 15 位、37 位或 1024 位寄存器同样适用。
所需整数位的数量取决于该数字需要存储的最大整数值。小数位数取决于最终结果的所需精度。要确定所需的整数位数,我们可以使用以下等式:
例如,表示 0.0 到 423.0 之间的值所需的整数位数是:
我们需要 9 个整数位,允许表示 0 到 511 的范围.
两个定点操作数的小数点必须对齐才能加、减或除这两个数字。也就是说,一个 x,8 数字只能添加到、减去或除以同样在 x,8 表示形式中的数字。要对不同 x,y 格式的数字执行算术运算,我们必须首先对齐小数点。请注意,对齐除法的小数点并不是绝对必要的。但是,实现定点除法需要仔细考虑,以确保在这种情况下得到正确的结果。
同样,两个定点数相乘时,小数点也不需要对齐。例如,将两个定点数相乘,格式为 14,2 和 10,6,结果为 24,8(格式为 24 个整数位和 8 个小数位)。对于除以固定常数,我们当然可以通过计算常数的倒数然后将该常数结果用作乘数来简化设计。
PL加速PS端计算
上一节简单说了PL端实现定点计算的一些基础知识,接下来就是专注于在系统中实现PL端加速的工作。
在我们开始切割代码之前,我们需要确定我们将在这个特定实现中使用的比例因子(小数点的位置)。在此示例中,输入信号的范围在 0 到 10 之间,因此我们可以将 4 个十进制位和 12 个小数位打包成一个 16 位输入向量。
上面的公式就是我们要实现的,它具有三个常数 A、B 和 C:A = -0.0088 B = 1.7673 C =131.29。我们需要在实现中处理(缩放)这些常数。在 FPGA 中这样做的好处在于,我们可以对每个常数进行不同的缩放以优化性能,如下表所示:
当我们实现上述等式时,我们需要考虑合成向量的扩展,对于术语 Ax^2和 Bx 定义如下:
要使用常数 C 执行最终加法,我们需要对齐小数点。因此,我们需要将结果和 Ax^2和 Bx 除以 2 的幂,以将小数点与 C 对齐。result也将被格式化为这个值,即 8,8。
计算完上述内容后,我们就准备好在前几节创建的 Vivado 外设工程中实施设计。
第一个实现步骤是在 Vivado 中打开框图视图,右键单击IP,然后选择“Edit in IP Packager”。一旦 IP Packager 在顶层文件中打开,我们就可以轻松实现一个简单的过程,在多个时钟周期内执行计算。(本示例中为五个时钟,尽管可以进一步优化。)
现在我们可以在将更新的硬件导出到 SDK 之前,在 Vivado 中重新打包和重建项目(记得更新版本号)。
在 SDK 中,我们可以使用与以前相同的方法,除了现在使用定点数字系统而不是前面示例中使用的浮点系统:
for(i=0; i<2560; i = i+25 ){
XScuTimer_LoadTimer(&Timer, TIMER_LOAD_VALUE);
timer_start = XScuTimer_GetCounterValue(&Timer);
XScuTimer_Start(&Timer);
ADAMS_PERIHPERAL_mWriteReg(Adam_Low, 4, i);
result = ADAMS_PERIHPERAL_mReadReg(Adam_Low, 12);
XScuTimer_Stop(&Timer);
timer_value = XScuTimer_GetCounterValue(&Timer);
printf("%d,%lu,%lu,%lu, \n\r",i,result,timer_start, timer_value);
}
当上面的代码在ZYNQ板上运行时,我们在串口上看到以下结果输出:
33610 的结果等于 131.289 除以 2^8 时,这是正确的并且符合浮点计算。尽管数值结果相同,但最大的区别在于执行计算所需的时间。虽然外围设计的实际计算只需要 5 个时钟,但生成结果需要 140 个时钟或 420ns,而在 Zynq SoC 的 PS 侧使用 ARM Cortex-A9 处理器则需要 25 个 CPU 时钟。
为什么会出现差异?PL端不应该更快吗?主要原因时外围 I/O 时间开销。在使用 PL 端时,我们必须考虑 AXI 总线上的总线延迟和 AXI 总线频率,在此应用中为 142.8MHz(请求为 150MHz)。AXI 总线开销导致计算时间长于预期。然而,一切都没有错。错的是我做错了方向:因为这种 I/O 开销时间,将任务转移到 Zynq SoC 的 PL 并不是以这种方式使用的。
那么如果我们要采取更合理的方法,需要怎么做?DMA
下一篇文章我们将使用DMA来搬运数据,看下结果是不是我们要的~~
本文部分源文件:
https://gitee.com/openfpga/zynq-chronicles/blob/master/VHDL_part24.vhd
ZYNQ从放弃到入门(一)MIO
ZYNQ从放弃到入门(二)-PS端 GPIO
ZYNQ从放弃到入门(三)- 中断(一)
ZYNQ从放弃到入门(四)- 中断(二)
ZYNQ从放弃到入门(五)- 专用定时器
ZYNQ从放弃到入门(六)- 专用看门狗
ZYNQ从放弃到入门(七)-三重定时器计数器 (TTC)