NIOS II就是一款CPU,和51、ARM、MIPS、X86的概念是一样的。但是与其他处理器架构相比NIOS II最大的特点是运行在(Intel Altera)FPGA上的软核处理器,说白了就是使用Verilog HDL或者VHDL语言在FPGA内部实现了一个处理器,这是一个庞大的系统,相当于在ARM处理器上编写一个操作系统,所以不是所有人都可以创建一个自己的CPU系统,而且就算自己编写了一个CPU系统,也没有配套的编译器和开发环境的支持,只能使用二进制指令进行编程,这是何等的痛苦。当然了Altera公司早就考虑到这点了,NIOS软核应运而生,NIOS II是在NIOS的进化版本,现在好像都是使用NIOS II系统了。同时Altera公司为NIOS II软核提供了完整的工具链包括软核的定制、丰富的外设IP库、NIOS II软件开发环境、编译器等。
FPGA系统上使用NIOS II处理器之后相当于传统的FPGA+MCU协作,各自发挥各自的优势,FPGA能够实现高效的数据处理,MCU能够处理繁杂的任务。
手头有现成的一块Cyclone II开发板,拿这个板子进行试验。打开Quartus II软件,创建一个工程,顶层模块名叫做HelloWorld,选择好了器件之后可以开始定制NIOS II系统了,这里说的NIOS II系统指的不仅仅是一个NIOS II CPU软核,它包含了CPU、时钟、存储、总线、外设等部分。在Quartus软件的工具栏上打开Qsys工具,如下图:
进入到Qsys界面:
这就是NIOS II系统的定制软件,在右侧的System Contents栏中可以看到已经默认存在一个clk_0模块了,可能就是为了告诉我时钟是必不可少的。左侧是Altera提供的丰富的IP核,种类非常的多,可以构建一个资源丰富的单片机系统。
HelloWorld工程需要时钟、CPU、ROM、RAM、JTAG_UART、SYSID、PIO(后面会有为什么需要这个PIO外设)这些模块。我们可以挨个在左侧的Library中搜索,然后双击之后进行添加和配置。
开发板上的晶振是50MHz的,不使用PLL的情况下输入给系统的时钟就是外部晶振的时钟频率。返回设计界面在Export栏双击clk_in_rst,导出clk_in_rst信号:
添加一个NIOS II系统,
NIOS II一共三款类型,我选择了NIOS II/e,这是最小体积但是性能最差的一款处理器,因为我的开发板是Cyclone II的EC2C5T144C8,内部资源比较有限,如果选择NIOS II/f的话会导致内部资源不够用。下面还有Reset Vector和Exception Vector没有配置,因为现在还配置不了,这些向量是跟程序运行地址相关的,但是还没有添加系统存储器。
搜索on chip memery,然后添加配置:
存储器类型选择ROM,Data Width选择32,Total memery size选择2048,不能太大,器件空间有限。
步骤和上面的ROM定制一样,存储器类型选择RAM,Data Width选择32,Total memery size选择6144,这个值是恰好的值,小了导致NIOS II应用程序无法运行,大了导致RAM使用率超出FPGA内部总RAM,会在编译的Place&Route过程中出错。
这个模块主要是为了下载程序、单步调试和数据打印使用的,使用默认配置就行了。
添加这个的目的我也没仔细研究,好像是Eclipse在连接CPU的时候会进行ID检测。直接添加即可,32 bit System ID就写0x00000000即可。
PIO就是Parallel I/O,相当于51单片机的P0、STM32的GPIOA。PIO模块的具体配置如下图:
添加完所有模块之后还需要进行线路连接,这里有一些准则:
1、clk时钟需要连接到所有模块,因为所有模块都是时序器件,都需要时钟驱动。
2、clk_reset也连接到所有模块,因为如果时钟模块被复位了,其他连接了clk时钟的器件都要复位。注意clk模块的clk_in_reset是外部输入的复位信号,clk_reset信号是clk模块输入给其他器件的复位输出信号。
3、jtag_uart模块的复位输出连接到所有模块,因为调试模块有权利进行系统复位。
4、NIOS II软核的data_master数据总线连接到所有器件,因为所有器件只有被分配到CPU的总线中去之后才能被CPU所调用(器件需要能够被寻址,并能够读取或者写入数据)。
5、NIOS II软核的instruction_master指令总线连接到存储器中去,包括onchip_rom和onchip_ram,也可以是SDRAM控制器等。
根据上面的准则进行连线,最后的连线图如下:
连线完成之后需要设置NIOS II IP核的Reset Vector和Exception Vector,双击nios2_qsys进行配置:
表示复位之后程序从0x00005000地址处的ROM中开始运行。产生中断/异常之后从0x00002020地址处的RAM中运行,这就是中断向量表。
下面开始基地址分配。我们知道NIOS II是32位的内核,寻址空间是4GB,我们定义了那么多器件,还有存储器,存储器的空间各不相同,每个器件挂载在什么地址上面呢?我们可以自己计算然后配置地址,但是Qsys提供了分配工具,点击菜单栏中的【System】→【Assign Base Addresses】,Qsys工具会自动为各个IP核分配基地址。基地址分配完成后,可以看到0Error和0 Warning的提示,如图所示:.
到这里已经配置好一个NIOS II系统了,最后一步就是生成NIOS II系统了。点击右侧菜单栏中的【Generate】栏,进入生成Qsys系统的设置界面:
完成之后如果没有错误就会生成一个icpu.qip模块,在icpu的synthesis文件夹中,这个模块就是我们定制的IP核,在顶层模块中可以添加这个模块。记得要保存这个Qsys系统,下次要修改icpu的时候直接打开修改然后generate即可。
在Qsys界面里面有一个HDL example:
直接复制这个程序到顶层模块中调用即可:
这里我加了一个LED闪烁模块,用来查看系统是不是在运行。然后Pin planner、Programmer......这里不再赘述,常规操作。烧写到FPGA开发板上之后led按照2.5Hz的频率进行闪烁,说明系统已经在运行。
现在已经有一个属于我们自己的CPU系统在FPGA中待着了,可不能要它就这么干耗着,要做点什么不是么?和STM32使用KEIL MDK集成开发环境一样,NIOS II也是使用集成开发环境进行开发的,但是遗憾的是这个开发环境是基于Eclips的,着实是不怎么好用,希望Altera好好优化一下。打开Quartus软件在菜单栏的Tools栏下选择Nios II Software Build Tools for Eclips,打开之后选择一个工作空间,然后File--New--Nios II Application and BSP from Template,使用模板为例创建一个工程:
上面Target hardware information中的SOPC Information File name选择刚才创建NIOS II系统是generate出来的文件,这个文件就是你创建的系统的配置文件。下面的Templates选择Hello World Small,这个程序的占用空间比Hello World工程要小很多。创建完成之后会生成两个项目:
HelloWorld_bsp是开发环境根据SOPC Information File生成的,就是根据我设计的CPU生成的独有的bsp库,开发应用程序的时候可以直接使用。
打开工程HelloWorld的hello_world_small.c文件主函数中只有一个打印字符串的功能。右击两个工程选择Build Project进行编译,如果没有问题就可以将程序下载到系统是实验,右击工程--Run as--Nios II Hardware,首次下载会弹出下面的Run Configuration窗口进行配置。
配置完之后就可以直接在Eclips的工具栏上直接进行运行了:
左边的是Debug,右边的是Run,电机向下箭头选择Debug/Run类型。
遗憾的是下载进去之后没有反应:
Nios II Console窗口中没有任何数据。试了很多次都没有成功。但是偶然我实验了一次Debug:
点击红框中的Resume之后开始运行程序,发现有数据打印出来了,然后将程序修改成循环打印数据,再进行调试:
的确有数据循环打印,但是停止Debug之后又没有数据打印了,难道停止Debug之后CPU停止运行了么?还是说jtag_uart传输有问题呢?这时候我就想使用PIO来控制LED灯来查看程序是否在运行,程序如下:
#include "sys/alt_stdio.h"
#include "altera_avalon_pio_regs.h"
int main()
{
while(1)
{
IOWR_ALTERA_AVALON_PIO_DATA(0x6000,0xFFFF);
alt_putstr("Hello world!\n");
usleep(100000);
IOWR_ALTERA_AVALON_PIO_DATA(0x6000,0x0000);
alt_putstr("Hello world!\n");
usleep(100000);
}
/* Event loop never exits. */
while (1);
return 0;
}
进入Debug模式之后程序可以运行,LED灯闪烁,有数据打印,但是数据打印很顿挫,按道理打印频率是10Hz,不应该顿挫,然后停止Debug,发现LED不闪烁,数据不打印,按下复位按键之后发现LED闪烁了3次,然后就停了,说明程序其实还是运行了,但是到后来卡住了似的。我猜测是卡在了jtag_uart打印的地方,所以把alt_putstr函数都注释了,再次实验发现退出Debug并复位之后LED可以正常闪烁,并且直接下载程序之后LED也能闪烁。那很有可能就是jtag_uart打印出现了问题,jtag_uart模块的打印是使用jtag的几根线传输打印数据的,和真正的UART是不一样的。倒是也可以解决这个问题,直接在我的NIOS II系统中添加一个UART IP核即可。但是这个问题还是要解决的,有明白人希望可以解答我的疑惑。
本文章参考正点原子的新起点FPGA开发指南和新起点Nios II开发指南,非常感谢原子提供的非常丰富的学习资料!