05Microblaze最小系统搭建及GPIO测试

发布时间 2023-09-11 15:48:55作者: 米联客(milianke)

软件版本:vitis2021.1(vivado2021.1)

操作系统:WIN10 64bit

硬件平台:适用AMD-XILINX A7/K7/Z7/ZU/KU系列FPGA

登录"米联客"SOC|SOC社区-www.uisrc.com视频课程、答疑解惑!

1 概述

前面我们快速学习了FPGA工程的创建,仿真,调试。FPGA的功能非常强大,还可以在FPGA上做一个CPU.我们这节课给读者演示如何在FPGA上部署一个CPU软核,实现一个最小的SOC。

一般如果你是搞FPGA开发的肯定学习过C语言,学习C语言的时候,我们跑的第一个工程大多都是跑一个Hello World!不同于ZYNQ自带了硬核,FPGA嵌入软核CPU,这个软核是由FPGA内部的逻辑资源实现,虽然性能上比不了ZYNQ的硬件,但在一些控制的领域还是能做到游刃有余。同样的,AMD-XILINX也提供了软核的IP,这就是MicroBlaze。

MicroBlaze™ CPU 是嵌入式、可修改预置 32 位 RISC 微处理器配置系列。利用没有成本、基于 Eclipse 的 AMD-XILINX 软件开发套件,系统设计人员可在没有任何 FPGA 经验的情况下,使用所选的评估套件立即启动 MicroBlaze 处理器的开发。MicroBlaze 处理器符合大量不同应用的需求,这些应用包括工业、医疗、汽车、消费类以及通信市场等。

2 系统框图

一个MicroBlaze最小系统主要由四个部分组成:CPU、内存(BRAM或者DDR等)、FLASH和UART,下一个实验我们会在当前的系统加入MIG DDR控制器。接下来分别介绍这四个部分的搭建,最后使之成为一个完整的系统。

3 基于图形化设计SOC系统

3.1 创建vivado工程

1-1:在打开的VIVADO2021.1软件界面,单击Create Project。

1-2:单击NEXT,在弹出的窗口中输入工程名和选择保存路径,然后单击Next。

1-3:选择RTL Project,单击NEXT

1-4:在弹出的窗口中选择与板卡对应的芯片型号,这里的图片仅作参考

1-5:单击Finish完成工程的创建

3.2 CPU配置

MicroBlaze 是基于AMD-XILINX公司FPGA的一款32/64位软核嵌入式处理器,已针对AMD-XILINX FPGA的实现进行了优化。

MicroBlaze内部有32个32/64位通用寄存器和16个32/64位特殊寄存器。为了提高性能,MicroBlaze还具有指令和数据缓存。所有的指令字长都是32位,有3个操作数和2种寻址模式。指令按功能划分有逻辑运算、算术运算、分支、存储器读/写和特殊指令等。指令执行的流水线是并行流水线,它分为3级流水:取指、译码和执行。MicroBlaze可以响应软件和硬件中断,进行异常处理,通过外加控制逻辑,可以扩展外部中断。

下图显示了MicroBlaze内核的功能框图。

针对AMD-XILINX不同系列的芯片,下表中提供了MicroBlaze内核的最大频率。

MicroBlaze官方提供了三种预设配置: Microcontroller(运行裸机应用程序的简单微控制器)、FreeRTOS(一种具有高速缓存和存储器保护单元接口的实时系统处理器、Application(运行Linux的内存管理单元的应用处理器)。下表显示了这三种配置在官方XC7A200T芯片设备上的性能和利用率

创建一个空的FPGA工程

 

创建Block Design

BD(block design)命名为system

添加IP

双击MicroBlaze图标,对其进行配置

预设配置:选择current setting。

处理器配置:默认32-bit处理器

Select implemention to optimize(优化设置):

area(面积优先)。如果选了这个,implementation就会优化面积,尤其是减少流水线数量,从5条减少到3条。(推荐:建议在资源比较紧张的架构,如Artix-7,使能这个选项。然而,如果对性能有敏感的要求,就不要选这个选项,因为一些指令需要额外的时钟周期去执行。另外,对于MMU, Branch Target Cache, Instruction Cache Streams, Instruction Cache Victims, Data Cache Victims, ACE是不能进行面积优化的。)

Enable MicroBlaze Debug Module Interface(调试接口)

使能调试功能。用AMD-XILINX Microprocessor Debugger来下载、调试程序。(推荐:除非面积资源奇缺,否则不要禁止这个功能。)

Use Instruction and Data Caches(指令和数据cache,当使用外置DDR的时候使用):

当执行放在LMB之外的程序的时候,可以使用指令缓存来改善性能。指令缓存有如下特点:当使用外部存储时,激活这个选项可以明显地改善性能,即使这个缓存很小。

勾选Use Instruction and Data Caches

Enable Exceptions(异常处理使能)

当使用一个支持异常的操作系统时,需要激活这个选项。或者在一个单独的程序中添加异常回调函数。

Use Memory Management(内存管理)

当使用一个支持虚拟内存保护的操作系统时(如Linux),需要激活。(当你使能面积优化或者堆栈保护功能时,内存管理单元是不可见的,自动禁止)

 

Enable Discrete Ports:使能软核上的独立端口。

 

单击NEXT进入下一页配置:

这一页是关于移位器、浮点单元、整形乘法器,整形除法器、模式比较器、其他机器状态寄存器指令、加载/存储和交换指令、额外的流指令、容错功能。

使能桶型移位器(Enable Barrel Shifter):使能软核中的筒形移位器硬件。激活这个参数,就可以使用如下指令(bsrl,bsra,...)使能这个可以提高应用的性能,但是会增大软核的尺寸。如果激活,编译器会自动使用筒形移位器指令。

 

使能浮点单元(Enable Floating Point Unit):使能一个单精度浮点单元(FPU)。使用FPU可以明显地提高应用的单精度浮点性能,同时也会增大软核的尺寸。

 

使能整形乘法器(Enable Integer Multiplier):使能一个整形乘法器硬件。若激活,则可以在给MUL32赋值时,使用mul和muli指令。当给MUL64赋值时,使用mulh,mulhu,mulhsu指令。这个参数可以设置为NONE,可以把MUL或者DSP48释放,用作其他用途。这样做对软核的面积影响很小。当使用这个选项,编译器自动使用mul指令。

 

使能整形除法器(Enable Integer Divider):使能一个整形除法器硬件。若激活,可以使用idiv,iduvu指令。使能这个选项可以提高应用中的除法性能,但是增大了软核的尺寸。当使用这个选项,编译器自动使用idiv指令。

 

使能额外机器状态寄存器指令(Enable Additional Machine Status Register Instructions):若激活,则可以读写MSR,使用msrset和msrclr命令。可以提高访问MSR的性能。

 

使能模式比较器(Enable Pattern Comparator):如激活,则可以使用模式匹配指令pcmpbf,pcmpeq,pcmpne。模式匹配字节查找指令(pattern comparator byte find, pcmpbf)返回找到的第一个字节的位置,提高字符串和模式匹配操作的效率。若使能,SDK库会自动使用这个指令。pcmfeq和pcmpne指令根据两个字是否相同,返回1或者0。这些指令会提高setting flags的效率,编译器会自动使用它们。激活这个选项还可以count leading zeros指令,clz。clz指令能提高优先级编码的效率。

 

使能保留的加载/保存和交换指令(Enable Reserved Load/Store and Swap Instructions):lbur,lhur,lwr,sbr,shr,swr,swapb,和swaph。这些指令能够以相对的字节序来读写数据,交换指令能交换字节或者半个字长。当用little-endian的MicroBlaze访问big-endian的网络时,可以提高效率。

 

使能额外的流命令(Enable Additional Stream Instructions):当使用AXI4-Stream链接时,提供额外的功能。这包括动态访问指令GETD和PUTD,这两个指令用寄存器来选择接口。(重要:一定要激活流异常功能,才能使用这些指令,而且知道选择一个流链接)

单击Next进入下一页配置:使能cache修改如下

单击Next进入下一页配置:

这一页是对microblaze的调试接口进行配置,选择默认的BASIC配置

单击Next进入下一页配置:

该页面主要用于设置总线接口,设置本地内存总线接口、AXI总线接口、Stream总线接口

单击OK 完成配置

 

产生使用VIVADO BD的自动化功能

设置如下

自动化连线后

3.3 添加AXI UARTLite IP

我们还要添加一个UART用于串口打印,因为不需要复杂功能,我们只要添加一个基本的UART IP

设置UART核,修改波特率为115200。

通过连线的方式,连接到AXI interconnect,增加AXI interconnect的接口

连接AXI接口到UART的AXI接口

连接中断

连接时钟

连接复位

3.4 CLK_WIZ时钟设置

修改clk_wiz时钟输入,这里注意每个板子的输入可能不一样,有单端,有差分,有25M 50M 100M 200M,请大家核对原理图

取消时钟复位

把时钟输入,和UART端口引出

相同方法,引出时钟

修改时钟名,让时钟名符合使用习惯

3.5 添加AXI-Interconnect IP

添加1个axi-interconnect IP,互联到这两个接口

双击该IP

修改设置如下

3.6 添加MIG IP核心(添加DDR配置)

AMD-XILINX DDR通过MIG控制,添加MIG IP核

双击IP配置

MIG核设置

 

导入DDR的IO约束,这个文件也是我们从已经做好的工程导出的,直接导入可以提高效率,不同的板子文件名可能不一样注意下。

 

校对

既然可以导入,那么也可以通过Save Pin Out 导出设置,这里就不再演示

 

MIG核配置完成后

3.7 完成信号连线设计

连线的过程不再重复描述,根据我们最终的截图,完成IP之间的连线,并且引出MIG DDR接口

因为只用到1个中断,修改concat IP,改为1

3.8 添加AXI QUAD SPI IP核

接下来,添加一个axi_quad_spi核

双击设置如下

增加axi-interconnect

使用相同的方法,完成axi-interconnect IP的连线

3.9 完成基于IP图形化的最小系统设计

完成后的最小系统如下包含了CPU、中断、调试模块、UART、QSPI、DDR:

3.10 BD(block design)层次化功能

我们可以利用BD的层次化功能,把目前的最小系统层次化为1个最小系统,按下ctrl选中所有IP

然后右击,创建层次化

我们可以取一个名字,这里我们输入MinSoc

现在看起很不舒服

继续右击,选中Regenerate Layout

现在看起来非常清爽了,端口只包含了时钟输入、串口、FLASH、DDR

3.11 添加AXI_GPIO

 

AXI-GPIO的设置如下,输出输出各设置2bit,支持中断,本实验不讲解中断后面再做中断实验

将接口连接到位,初学者可能不知道如何从我们外部连接到Minsoc,我们只需要右键接口,然后右键make Connection随后选择需要连接的接口即可自动完成连线。

连线完成

 

3.12 地址空间的分配

PS(microblaze cpu 或者ARM 简称PS)通过访问地址空间可以实现寄存器,或者内存的访问。

检查地址空间的分配, BRAM的地址空间已经分配,AXI_INTC IP地址也已经分配,UART和SPI的的地址没有分配。

选中assign ALL

 

分配完成后

3.13 VIVADO自动校对功能

利用VIVADO自动化校验功能

现在图形化的设计已经完成

3.14自动产生调用BD代码的接口代码

BD图形化设计本质还是FPGA代码,是一种图形化形象编程的方式。

检查自动产生的顶层文件,可以看到顶层文件调用了BD模块

3.15绑定FPGA pin脚

绑定FPGA管脚前文已经有详细的介绍,此处不再重复赘述,xdc文件存放路径为uisrc/04_pin

3.16 编译FPGA工程

 

4 编译完成后,导出硬件

米联客会新建3个文件路径

到此,完成了SOC FPGA部分的设计工作

5 导出导入BD的tcl脚本方法

最小系统搭建完成之后,在之后的设计中可能会再次使用到这一部分的设计,为了避免重复的设计,浪费开发时间,笔者这里介绍一种高效的工程管理方式——将BD文件导出为tcl脚本。导出tcl脚本之后,在下次设计中,我们就只需要运行tcl脚本即可完成此部分电路的配置,能有效的节省开发时间。

5.1 导出BD为tcl

首先确保Block design处于被打开的状态,在TCL控制台中输入如下命令可将BD文件导出为tcl脚本。

5.2 导入BD tcl

新建一个vivado工程

自定义IP(如果用户用到自定义IP需要设置,没用到不需要设置)路径设置,如果用到必须设置,否则导入tcl会找不到自定义IP.

本实验没有用到自定义IP

导入TCL到新的vivado工程

运行完source指令之后,系统会自动生成我们之前搭建好的BD文件,之后我们就可以使用这个文件来搭建最小系统。

6 搭建Vitis-sdk工程

创建soc_base sdk platform和APP工程。

6.1 创建SDK Platform工程

启动Vitis-Sdk

设置好路径

米联客资料中的路径规范如下图:

soc_prj里面是基于SOC的硬件工程源码

soc_hw里面是xsa格式文件,soc_prj编译会导出system_wrapper.xsa到这个文件

soc_sdk里面是裸机的sdk工程,sdk工程创建依赖soc_hw中的system_wrapper.xsa

单击Create Platform Project 创建基于开发平台的工程

添加之前创建的system_wrapper.xsa文件

 

6.2 创建hello_world APP工程

自动产生一个helloworld传输输出程序

右击工程编译

6.3 创建axi_gpio_test APP工程

"axi_gpio_test "APP工程代码,我们直接源码,用户直接新建一个空的工程,然后将源码直接复制粘贴即可,我们对于空工程的新建不做过多的讲解。

新建完成后工程如下图所示:

7 程序分析

hello_word APP就是简单通过调用FPGA工程师中的uart-lite IP串口输出一些字符信息,程序代码简单不做分析。

7.1 axi_gpio_test.c测试程序

#include "sys_xintr.h"

#include "axi_gpio_intr.h"

#include "sleep.h"

 

extern XGpio Gpio; //定义GPIO实列

extern XIntc Intc; //定义系统中断实列

 

extern volatile u32 gpio_intr_flag; //中断标志

extern volatile u32 gpio_val; //保存GPIO按键输入读取值

 

void init_intr_sys(void)

{

 

    XInit_Intr_System(&Intc , 0);//初始化系统中断

    Gpiopl_init(&Gpio, AXI_GPIO_DEV_ID); //初始化GPIO

    Gpiopl_Setup_Intr_System(&Intc, &Gpio, GPIO_INTR_ID); //初始化GPIO中断

    XSetup_Intr_Exception(&Intc); //设置中断异常

}

 

 

int main(void)

{

    init_intr_sys();//初始化系统中断

    while(1)

    {

        if(gpio_intr_flag)

        {

            gpio_intr_flag = 0;

            XGpio_DiscreteWrite(&Gpio,2,gpio_val);

            printf("SW=%d int\n\r", gpio_val);

        }

    }

    return XST_SUCCESS;

}

7.2 axi_gpio_intr.c程序

#include "axi_gpio_intr.h"

 

volatile u32 gpio_intr_flag;

volatile u32 gpio_val;

 

//中断回调函数

void GpioplIntrHandler(void *Callback)

{

    XGpio *GpioPtr = (XGpio *)Callback;

    u32 IrqStatus;

 

    IrqStatus = XGpio_InterruptGetStatus(GpioPtr); //获取中断状态

 

    if((IrqStatus & 0x01) == 0x01)

    {

        gpio_val = XGpio_DiscreteRead(GpioPtr, 1); //GPIO中断需要读按键状态知道哪一个按键产生了中断

        gpio_intr_flag = 1; //设置中断标志

    }

 

    XGpio_InterruptClear(GpioPtr, IrqStatus); //清除中断

 

}

 

//设置中断

void Gpiopl_Setup_Intr_System(XIntc *GicInstancePtr, XGpio *InstancePtr, u16 IntrId)

{

XIntc_Connect(GicInstancePtr, IntrId, //设置中断回调函数

(Xil_ExceptionHandler)GpioplIntrHandler,

(void *)InstancePtr);

 

XIntc_Enable(GicInstancePtr, IntrId);//使能中断号指定的中断

 

    XGpio_InterruptEnable(InstancePtr, 0x01);//使能中断

 

    XGpio_InterruptGlobalEnable(InstancePtr); //使能全局中断

 

}

 

int Gpiopl_init(XGpio *InstancePtr, u32 DeviceId)

{

 

    int Status;

    /* Initialize AXI GPIO */

    Status = XGpio_Initialize(InstancePtr, DeviceId);

    if (Status != XST_SUCCESS) {

        xil_printf("AXI GPIO config failed!\r\n");

        return XST_FAILURE;

    }

 

    XGpio_SetDataDirection(InstancePtr, 1, 0x3);//设置GPIO通道1用于输入

 

    XGpio_SetDataDirection(InstancePtr, 2, 0x0); //设置GPIO通道2用于输出

 

    XGpio_SetDataDirection (&Gpio, 1, 0x3); //输入设置上拉

 

    XGpio_SetDataDirection (&Gpio, 2, 0x0); //输出设置输出0

 

return 1;

}

在初始化函数中,设置channel1 的2个IO为输入,channel2 的2个IO为输出

在Gpiopl_Setup_Intr_System(XIntc *GicInstancePtr, XGpio *InstancePtr, u16 IntrId)函数中主要是对,AXI-GPIO的全局中断使能寄存器GIER和IPIER中断使能寄存器进行设置

当GPIO的电平状态发生改变就会触发中断,回调函数中,首先读取AXI-GPIO的状态寄存器,然后判断是否是channel1产生的中断。之后读取GPIO的按键输入状态,设置中断触发变量为1。最后清除中断。

8 实验演示

8.1 硬件准备

 

8.2 helloworld实验结果

为了观察实现结果,需要打开串口

首选选中Debug,出来Vitis Serial Terminal,串口终端

设置正确的串口号

单击OK 打开终端

选中Design切换界面

右击选中调试

双击System Project Debug

选中SystemDebug_hellowrld_sytem 单击debug

输入结果如下,最小系统运行正常

8.3 axi_gpio_test实验结果

除了通过串口观察按键中断的结果,而且可以通过LED观察。每次按下按键产生一个中断,每次松开按键也会产生一个中断。总之,只要按键的状态发生改变就会产生中断。

9本章小结

本章节我们学会了搭建Microblaze最小系统,并且该系统可以在多种场合重复使用,并且利用我们搭建的最小系统完成一个简单的AXI_GPIO的使用。