龙芯杯CPU设计竞赛与ZYNQ设计流程介绍

随着开源的MCU源代码越来越多,也逐渐的影响着嵌入式系统开发的思路,出现了两种以前不常见的设计思路。第一,原本需要购买一颗MCU芯片的设计,现在直接考虑购买一颗带有MCU硬核的FPGA(如ZYNQ系列FPGA)替代,既有MCU的功能,又有接口可编程的能力,更加的灵活;第二种思路是设计中原本需要写复杂控制逻辑的Verilog模块,现在直接考虑替换为一颗或者多颗开源的MCU IP核,反正这些MCU都是验证过没问题的,并且占用资源很小,同时可编程也带来了功能改变的灵活性,何乐而不为呢!

(分两次介绍上述第一种应用的两种不同应用场景实例,带操作系统和不带操作系统来控制LED灯的应用。)

“龙芯杯”竞赛的影响和设计水平不断提升

随着中兴华为事件的不断发酵,集成电路行业恰似长颈鹿的脖子一样被一双无形的手给紧紧的卡住了。之所以称之为长颈鹿的脖子,是因为集成电路行业拥有很长的产业链,而在这个产业链上我们却处处都弱,拿长颈鹿又细又长的脖子来形容再恰当不过了。

国内各种IC设计竞赛、EDA设计竞赛也越来越多,但笔者认为,最具有代表性最能反映一所高校在校学生数字IC设计水平的也就是“龙芯杯”CPU设计竞赛。“龙芯杯”MIPS CPU设计已经举行三届了,参赛的队伍水平也在不断的提高,一个大三本科生设计一颗带有4发射乱序处理、分支预测等功能且能跑Linux操作的CPU核已经不是什么问题,并且FPGA验证的主频已经超过了120MHz,凸显了FPGA时序优化能力的提升。随着RISC-V等开源CPU的不断发展,相信国内在CPU设计能力上的差距正在加速缩小,被ARM和INTEL卡脖子的时代可能快要结束了。

从参赛的规模上来看,也在不断的扩大。今年又新加入了不少高校,像北京邮电大学、河北大学等。龙芯杯竞赛至少反映了这些高校在CPU设计方向的课程设置已经没有问题,设计CPU需要众多课程的支撑,《计算机组成原理》、《编译原理》、《操作系统》及FPGA设计开发等相关课程,相对而言,国内绝大多数高校虽然有相关的课程,可如果找到有能力设计CPU的学生组队参赛的高校却不多。


以下二维码链接是一位清华大学大三本科生写的项目分享,可以反应出当代本科生设计CPU能力的水平。


好了,回归正题,以下介绍ZYNQ系列FPGA如何使用。以下内容作者是殷建飞。

部分硬件设计中需要CPU完成对电路寄存器的配置,为了完成Zedboard对FPGA上部分寄存器的配置功能,可以在PS单元(处理器系统)上运行裸机程序(无操作系统支持)完成和PL单元(FPGA部分)的数据交互功能,此时PS单元更像单片机开发;另一种方法是PS单元运行Linux操作系统,通过驱动程序和应用程序完成对硬件寄存器的读写操作,并且Linux有着完整的网络协议栈支持,后续可拓展性更强,可以更好的发挥ZYNQ这种异构架构芯片的性能。主要分为两部分,分别阐述Zedboard中FPGA和处理器互联总线与硬件设计和Zedboard处理器系统上嵌入式Linux的移植与通过驱动和应用程序简单配置FPGA寄存器的实现。本文主要介绍不带操作系统的情况。

PS与PL接口设计和硬件设计

PS与PL交互可以通过ZYNQ系统内的高速总线来完成,ZYNQ内包含AXI4标准总线、AXI-Lite轻型总线和AXI-Stream流式总线。其中AXI4标准总线支持有地址、猝发和连续的传输,可以用于大容量数据的传输;而AXI-Lite总线则是一个轻量级的地址映射单次传输接口,占用更少的逻辑资源;AXI4-Stream是面向数据流的无地址的传输,更适合承载视频流等流式数据。综上,因为本次实验硬件设计较为简单,数据传输量较小,因此选用AXI轻型总线作为PS单元与PL单元交互接口。

(1)自定义IP封装

VIVADO中提供了多种AXI总线接口的IP核,例如DMA IP中使用AXI-lite用来完成CPU配置DMA引擎的寄存器,而AXI标准总线用来完成DMA引擎面向内存地址映射的搬运数据。但是官方的IP核应用不够灵活,这里我们可以封装自己的IP并在其中加入需要的逻辑处理。

打开任意一个VIVADO工程,选择Tools中的Creatand Package New IP选项,点击next前进。


选择建立一个AXI4的外设选项


进行IP命名和版本的选择,另外需要选定工程路径,这是稍后建 立IP时VIVADO会新建立的工程路径。


在接口选项中,选择一个AXI-lite的Slave接口,数据位宽为 32位,寄存器数量为4,这里我们不需要太多的寄存器。当然在面对更复杂的应用时,可以添加更多的AXI总线通道。


选择Edit IP,此时VIVADO会打开一个新的工程。当然,也可以选择将IP添加到库,然后在工程中搜索添加该IP核,然后重新选中该IP选择Edit in IP Packager,VIVADO同样会打开一个新工程修改之前的IP。


在上述新工程中,可以看到工程的组织目录led_v1_1_S00_AXI才是我们创建的总线接口模块,而led_v1顶层模块只是添加了对总线接口的例化而已。那么VIVADO建立的IP为什么要新封装一层例化呢?我们用另一个多个AXI通道的工程来看一下,当IP需要多个AXI通道时,我们发现顶层例化了多个AXI总线通道,我们可以方便的设计和添加.v文件来完成多个数据通道之间的数据传输和总线控制。


(2)AXI-Lite协议分析

下面我们对AXI-Lite总线协议进行简单分析,AXI总线共5个数据通道,分别是写地址通道、写数据通道、写响应通道和读地址通道、读数据通道。AXI采用握手信号机制,当发送数据一端发送valid信号,而接受信号一端发送ready信号,当valid和ready同时有效时数据完成一次传输。

首先来看写数据接口,写数据需要写数据地址通道先发送地址,然后写数据通道发送数据,slave端接受完成后通过写响应通道告诉master端。


上述代码完成了slave端写地址通道的响应,slave端在上电复位后保持awreday信号为低电平,当master发出awvalid信号时,slave端将写地址通道上的地址数据存入寄存器,并将awready信号拉高一个时钟周期,完成一次写地址通道的传输过程。

对于写数据通道而言,其数据传输过程和写地址通道的传输过程很类似,slave端上电复位后将wready信号拉低,在master将wvalid信号拉高后,slave端将数据存入寄存器同时将wready信号拉高一个周期,完成一次写数据通道传输过程。

写操作由于是从master传输数据到slave,因此需要slave返回一个正确的响应信号给master来告诉master我已经正确收到数据,这就是写响应通道。由于写响应通道数据是从slave端向master端,因此由master端发出ready信号,slave端上电复位后拉低valid信号,在检测到master端的ready信号后将valid信号和bresp回应信号一同发送到总线上完成写响应传输过程。一个完整的写交易过程完成。


通过上述代码,读者可能会困惑写地址通道和写数据通道为什么绑定到一起了呢?难道AXI-Lite总线的写地址通道和写数据通道数据传输必须同时发生吗?我们参考了XILINX的xapp1168中关于AXI总线设计的工程,发现在它的master端设计中,写地址通道和写数据通道的传输是由同一个信号触发的。也就是说master端总是先准备好写地址通道和写数据通道的数据后,然后同时传输要写入的地址和要写入的数据。因此,在slave端,保持相同的设计就可以获得最优的性能。这也是上述代码中为何写地址通道获取数据时也要求写数据通道(wvalid)信号也有效。

通过上述设计,通过写数据通道和写地址通道获取到了CPU想要写入的地址和数据,下一步就要将数据写入对应寄存器。由于我们选择了4个寄存器,在代码中已经由VIVADO为我们自动生成了4个slave寄存器。4个寄存器共需要2位地址线来进行寻址,由于slave是挂在ARM 处理器外部的,当处理器调用函数写外设的寄存器,如果不做偏移,那么数据就会写入寄存器0,因此地址axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB]决定了将数据写入哪个寄存器。ADDR_LSB是由数据位宽决定的,DDR数据位宽为字节,所以地址线最低位对应8位数据,对于32位位宽的数据,我们必须进行32位对齐,所以地址偏移从32/4+1位开始。OPT_MEM_ADDR_BITS则决定了偏移范围有多大,两位的位宽就足以寻址4个寄存器,一次本工程中OPT_MEM_ADDR_BITS是1,地址位宽是[1:0]共二位,如果8个寄存器就需要三位,那么OPT_MEM_ADDR_BITS就需要定义为2。

下面的代码实现了根据偏移地址将数据写入对应寄存器的操作。

假设slave基地址0x43c00000,当我们调用Xil_Out32(0x43C00000,Value)写的就是slv_reg0的值,此时axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB]即axi_awaddr[3:2]为2’b00,如果地址偏移4位,Xil_Out32(0x43C00000+4,Value),此时写入slv_reg1寄存器,同时axi_awaddr[3:2]为2’b01.


上面分析了写通道的数据传输,接下来分析读数据通道

读地址通道和读数据通道的传输不可能同时进行,master端通过握手信号将读地址写入slave端,slave准备数据然后通过握手信号返回数据给master。

Slave上电复位后将读地址通道arready信号拉低,master需要读数据时将地址发送到读地址通道的araddr信号线上,同时拉高arvalid信号,发起一次读地址发送,slave端检测到arvalid信号有效,锁存araddr数据,并拉高arready信号完成读地址接受过程。slave根据ardddr的偏移获取对应寄存器的数据,然后在读数据通道上主动发起一次数据传输,即将数据置入rdata并拉高读地址通道的rvalid信号,等待master接受数据并返回rready信号有效后完成读数据传输。

因为Zedboard开发板有8位led灯,所以我们设计一个8位宽的wire型线连接到寄存器0的低8位即可。


需要注意在顶层文件中添加该端口。

至此,我们自定义的IP就设计完成了,我们重新封装该IP核。

首先点击各项的蓝色感叹号,vivado会自动更改IP的一些信息。全部消失后就可以进行IP封装。


选择重新封装选项进行封装,封装完成后VIVADO工程会自动关闭。


将封装好的IP添加到库目录,在任意VIVADO工程中选择IP Catalog选项,选中USER Repository选项,右键选中Add Repositor,选中我们之前建立IP时选择的目录。添加完成。


(3)硬件工程设计

本次实验基于VIVADO2017.4版本进行硬件设计,建立VIVADO工程并选用Zedboard开发板(这样后面的部分接口可以使用VIVADO默认配置)


新建block design,添加zynq processing_system,然后点击Run BlockAutomation进行硬件板卡信息填充。运行完成后可以看到ZYNQ系统以及配置好了DDR内存和串口设置。


如果你使用其他板卡,那么要根据外设的位置和内存型号进行设置,这里进行简单介绍。

PS-PL配置选项用于打开PS单元和PL单元之前的互联总线,PS单元中面向PL单元的有4个GP接口。也就是两个AXI-Lite主端口和两个AXI-Lite从端口,以及4个高速的HP接口。由于我们需要CPU配置FPGA,因此启用一个GP主端口。


配置IO选项用于关闭和打开ZYNQ的外设部分的配置,由于ZYNQ可灵活配置,因此需要选择和板卡相对应的外设位置,选择Zedboard的好处在于可以利用VIVADO中预设的板卡信息进行自动设置。

需要注意的是,这部分I/O口为ZYNQ的MIO,而PL部分的为EMIO。

另外,想要成功启动linux至少需要一个TTC外设。


时钟配置选项用于管理PS单元和PL单元的时钟,包括内存频率和CPU主频信息,I/O口频率设置。另外PS部分可以向PL部分提供最多4个时钟,这里仅开启一个100M的时钟。


DDR配置选项选择正确的内存型号即可,这里仍然使用Zedboard的默认配置即可。较为重要的是下面的中断选项,可以选择打开PS和PL之间的中断开关,因为本次工程暂时没有用到,所以不再赘述。

点击OK保存对处理器系统的配置,继续添加IP,搜索我们之前定制的IP,发现led V1版本的自定义IP核,点击添加。


添加完成后将led端口引出,可以使用快捷键Ctrl+T,或者使用右键菜单也可以完成。我们将剩余的AXI标准端口使用VIVADO的自动连线功能完成连接。点击Run Connection Automation,勾选所有IP,完成后发现VIVADO为我们自动添加了一个AXI交换矩阵和复位模块。


顶层模块如图,保存。

我们在工程管理中选中创建的Block Design,选中生成HDL


VIVADO会将处理器系统和IP部件生成Verilog文件。在工程中添加约束文件,加入对LED[7:0]的管教约束。

set_property PACKAGE_PIN T22 [get_ports {LED[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[0]}]
set_property PACKAGE_PIN T21 [get_ports {LED[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[1]}]
set_property PACKAGE_PIN U22 [get_ports {LED[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[2]}]
set_property PACKAGE_PIN U21 [get_ports {LED[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[3]}]
set_property PACKAGE_PIN V22 [get_ports {LED[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[5]}]
set_property PACKAGE_PIN W22 [get_ports {LED[5]}]
set_property PACKAGE_PIN U19 [get_ports {LED[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[7]}]
set_property PACKAGE_PIN U14 [get_ports {LED[7]}]

然后就可以生成比特流了。

硬件设计部分的工作在这里就完成了,我们需要记录外设在ARM处理器系统上挂载的地址,这样才能使用软件写入和读取其数据。在VIVAOD中Address Edit选项卡中可以编辑外设地址,这里我们保持默认,并记录。


在完成硬件设计部分后,我们可以简单的运行一个裸机程序来检验一下我们的映射关系是否正确。首先在VIVADO中将生成的比特流导出到SDK。


选择File,Export->ExportHardware,而后选择File->Lunch SDK,选择本地路径(这样会包含硬件比特流)。

(4)裸机程序验证

在SDK下,已经包含对应工程的硬件平台和板级支持包,点击File -> New -> Application project建立一个新的SDK工程。


建立工程名称,选择裸机,C语言开发,班级支持包默认。Next,选择HelloWord模板。我们在main函数中做简单修改:

#include <stdio.h>
#include "xparameters.h"
#include "sleep.h"
#include "xil_io.h"

int main()
{
    int i = 1;
    while(1)
    {
        for(i=0;i < 8;i++)
        {
            Xil_Out32(0x43c00000, 1<<i);
            sleep(1);
        }
    }
    return 0;
}

通过上述程序,我们讲一个8位循环的数据写入寄存器0中,由于寄存0的低8位连接到了LED灯,因此会有流水灯的效果。

函数 Xil_Out32(0x43c00000,1<<i);将数据写入到物理地址为0x43c00000的地方去,而这里刚好就是我们挂载AXI-Lite slave外设的物理地址。

在软件工程上右键选择Run As -> runconfigurations


选择对应的比特流,选中烧写FPGA和复位系统,点击run,可以看到烧写完成后开发板成功点亮流水灯。


全文完。

本文转自:网络交换FPGA,转载此文目的在于传递更多信息,版权归原作者所有。
*本文由网络交换FPGA授权转发,如需转载请联系作者本人

最新文章

最新文章