5.6 软件工程——运行调试
在EDS中,执行菜单栏Run→Run Configurations命令。如图5-49所示,确认选中当前工程的Nios II Hardware。
单击选中“Target Connection”选项卡,如图5-50所示,可以多次单击“Refresh Connections”直到窗口右上角没有红色的Error提醒,并且窗口右下角的Run按钮是可点击状态,单击Run按钮运行软件。
当然了,在运行软件之前先要将Quartus II编译生成的vip.sof文件下载的FPGA中。
5.7 板级调试
①打开“http://www.hzcourse.com/resource/readBook?path=/openresources/teach_ebook/uncompressed/15714/OEBPS/Text/...\prj\vip_ex3”文件夹下的工程。
②使用Programmer将“http://www.hzcourse.com/resource/readBook?path=/openresources/teach_ebook/uncompressed/15714/OEBPS/Text/...\prj\vip_ex3\output_files”文件夹下的vip.sof文件下载到VIP核心板中,此时VIP板上的指示灯D1还处于熄灭状态。
③执行菜单栏的Run→Run Configurations命令。如图5-51所示,确认选中当前工程的Nios II Hardware。
图5-51 运行配置页面
④单击选中“Target Connection”选项卡,如图5-52所示,可以多次单击“Refresh Connections”直到窗口右上角没有红色的Error提醒,并且窗口右下角的Run按钮是可点击状态,单击Run按钮运行软件。
⑤片刻后,我们可以看到Nios II Console窗口中打印出我们在软件上预设的字符,如图5-53所示。并且VIP板上的指示灯D1也开始欢乐地闪烁了。
第6章 工程实例4——NAND Flash读写测试
本章导读
本章的工程实例是在上一个工程基础上,软硬协同设计一个NAND Flash控制器,实现对NAND Flash的读写。本实例较前面的工程要复杂很多,尤其是这个NAND Flash控制器在硬件逻辑层面实现数据读写,要封装为一个Qsys的外设组件,而在软件层面主要实现读写指令的控制。不过请读者放心,我们做了非常详细的讲解,意图帮助读者“啃”下这块硬骨头。
6.1 功能概述
本实例在工程实例3的基础上,添加了一个自定义的NAND Flash控制器组件,如图6-1所示,这个组件也是挂在Qsys系统的Avalon-MM总线上。NAND Flash复杂的底层驱动时序都由这个控制器内部产生,无需NIOS II处理器直接参与。NIOS II处理器只需要通过Avalon-MM总线对相关寄存器进行读写就可以实现NAND Flash的读写。
在搭建的这个Qsys平台上,我们需要运行一个简单的NAND Flash擦除、写入和读出的操作。软件流程如图6-2所示,在外设初始化后,通过JTAG UART打印一串初始化信息;接着依次对NAND Flash的第1023块(Block)存储区执行擦除、写入操作,同时打印所有写入数据和读出数据进行比较;完成以上操作后,LED闪烁。
6.2 IP核配置——自定义Qsys组件
1.自定义组件概述
我们在Qsys平台上所构建的系统中,NIOS II处理器是一个Master(主机),这个Master可以连接很多Slaver(从机),Altera的Qsys中本身就能够提供一些常用的Slaver,如前面我们已经使用过的GPIO、JTAG UART、Timer等;而很多应用中,仅仅使用这些Slaver还不能满足我们的需求,我们常常需要定制一些特殊的外设作为Slaver挂在总线上,供NIOS II进行读写控制。
如图6-3所示,用户自定义逻辑(Component Logic)可以就着Avalon-MM或Avalon-ST接口与NIOS II处理器之间进行通信,从而达到自定义逻辑与处理器访问的无缝连接。这个自定义组件既可以与FPGA外部的芯片进行连接(一般是使用FPGA的IO口),也可以与NIOS II作为主机的总线连接,如最常见的Avalon-MM总线或Avalon-ST总线。
也许不少读者对于这张示意图还有些不解,不要紧,在正式实践之前,我们会先把理论知识学扎实。我们会花一些时间来弄懂或者说回顾一下总线的概念,以及我们的Qsys系统架构中到底会使用到一些什么样的总线。也就是说,大家在动手实践前要弄明白自定义组件是挂靠在怎样的一条总线上,在底层逻辑设计时又要以怎样一种握手机制来设计这条总线的从机。
2.总线的概念
关于总线,比较官方的说法是:总线(Bus)是计算机各种功能部件之间传送信息的公共通信干线。说到计算机,大家不要下意识地就以为只是特指每天我们都要面对的电脑。比较高端的计算机,火箭用的叫箭载计算机,卫星上用的叫星载计算机;稍微逊色一点的,工业上用的叫工控机,我们家里用的叫PC机、笔记本;而嵌入式用的计算机更是数不胜数了,PDD、GPS和手
机,MP3、MP4和电纸书,电梯、冰箱和微波炉的电子控制部分,细细找找,发现生活中凡是涉及电的还真没有一样离得开“微电”的控制。提到控制,那么肯定或大或小有个CPU,一旦和CPU搭上边好歹也要算个小型计算机了。计算机不仅有CPU,还需要有各种外设配合CPU与外界通信,那么CPU与外设之间的通信靠什么,总线?是的,虽然不是每一个系统中都要有总线,但凡大一点的系统都会有总线,因为总线能够很好地衔接管理各个外设与CPU之间的通信,能够简化硬件电路设计和系统结构。所以,说了这么多,你该领悟到总线很重要了吧。
光说重要你肯定感觉不过瘾,要打破沙锅问到底,总线到底如何工作?如何连接CPU和外设呢?
广义上来说,任何衔接多个外设甚至是多个相同外设的一组信号都可以称为“总线”,比如CAN总线、USB总线、I2C总线等。但在这里所论述的总线,主要是针对CPU与外设之间的总线。在嵌入式系统应用中,也许大家都接触过的有Intel总线和Motorola总线。这两类总线最初应用于Intel和Motorola两家公司生产的处理器,最为典型的是早期的PC系列处理器,如Intel的8086和Motorola的MC6800,以至于后来的很多单片机乃至外设芯片的并口通信都能够兼容这两种模式的总线。为了更好地理解总线的概念,不妨让我们看看这两种总线的工作机制。如图6-4和图6-5所示,是一颗支持与单片机的Intel和Motorola接口连接的芯片的读写时序图。习惯上,大家一谈到8080总线就认定是Intel总线,6800总线也就是Motorola总线。从这两个时序图上,我们看到的区别恐怕主要是读写选通的区别。Intel总线分别使用读选通信号RD#和写选通信号WR#两个信号的低电平状态来表示当前处于读或写选通状态。而MOTOROLA总线则只使用一条RW#信号来表示当前的读写状态,当总线选通期间,若RW#为高电平则表示读状态,低电平则为写状态。除此以外,规范的Intel总线和Motorola总线在地址和数据总线的使用上也是有所区别的。
另外,我们需要从这两个时序图中看到一个基本的“总线雏形”。也就是说,一条规范的总线,无外乎控制信号(有时候我们也习惯地称之为控制总线,但是此总线非彼总线,大家注意区分)、地址信号、数据信号这三类信号组成。如Intel总线的控制信号主要有片选信号CS#、写选通信号WR#、读选通RD#和地址/数据选择信号RS,它们的功能就是用于指示当前的总线处于怎样的状态——是正在读取数据、正在写入数据、正在写入地址还是闲置中。它们的地址信号和数据信号是复用(很多总线的地址和数据信号不是复用的,如Avalon总线就是分离的)的,即时序图中的DB[15:0]。在一次读写操作中,如果RS为高电平,则表示当前操作为命令的读或写;如果RS低高电平,则表示当前操作为数据的读或写。
Intel总线和Motorola总线在嵌入式系统中仍大行其道,甚至随处可见,不信你可以随便下载个芯片的数据手册看看。对我们的学习而言,认识一条总线的目的不仅仅在于领会总线本身的工作机制,尤其是要成为一名FPGA工程师,我们常常要和底层的硬件打交道,因此,我们学习的最终目的是学以致用。在笔者所著的《深入浅出玩转FPGA》笔记8的“单向控制信号检测”小节里就讲述了一种用FPGA逻辑来设计Intel/Motorola总线的从机。
其实上面提到的Intel总线和Motorola总线是CPU和外设数据交互的一种方式,在硬件工程师的板级设计中看得到固定信号连接的通信方式。而在我们后面将要重点讨论的这种CPU与外设的互联总线却是硬件工程师在PCB板上看不到的。也就是说,我们这里的外设不在“外”而在“内”,这些外设是集成在CPU内部的,而CPU内部的总线互联架构是板级设计的工程师们无法直观看到的。但是作为Qsys系统架构的工程师,我们就必须深入地研究这些互联总线的工作机理。毕竟,完全DIY出来的Qsys硬件系统中所有的组件都是我们根据系统需求精挑细选出来的,那么它们之间的衔接和数据交换也必须是我们能够去“设计”的一部分。
先不讨论在Altera的Qsys平台主流的Avalon总线,我们可以去研究一下ARM7系统中常见的内部系统总线是什么样的。如图6-6所示,这是NXP公司推出的一款内嵌ARM7TDMI-S内核的控制器内部功能框图。
在图6-6所示框图里,我们不是很关心它都集成了哪些外设,而是要看看它的处理器内核与其他外设以及外设与外设之间是如何互联的。简单地看,从内核引出的两条总线分别是Local bus和Advanced high-performance bus(AHB),Local bus即本地总线上挂靠的都是一些实时性要求最高、数据吞吐量最大的“外设”。毫无疑问,在一个系统中,这种实时性最高、数据吞吐量最大的“外设”非存储器莫属,因为程序运行、数据变量读写都要频繁地访问ROM或RAM。除此之外,我们也看到有一个高速GPIO模块也“运行”在这条“高速公路”上,该款芯片特别地增强了GPIO的性能,那一定是为满足一些特定的应用需求而设计的。除了存储器,其实这条高速总线是可以挂靠任何符合总线通信规范的外设组件的,但是一旦这条“高速公路”的“车”多了,就不可避免地发生拥堵,那么所谓“高速”就名不副实了。
那怎么办,系统还有一大堆外设需要挂靠呢。不是还有一条AHB总线吗,这也是一条高性能总线,虽然可能和本地总线比还是要差点。这条总线上挂着一个中断向量控制器(VECTORED INTERRUPT CONTROLLER)和一个桥(AHB TO APB BRIDGE)。无疑的,系统的中断需要第一时间得到响应,因此它也就被挂在离处理器内核最近的总线上。最后来看那个AHB2APB桥,我们
知道一般的嵌入式外设速度都不高,因此在这个系统内部,就把所有余下的外设组件都挂在了一条叫APB的总线上,而这条APB总线最终也是需要挂在与CPU直接相连的AHB总线上。那么,AHB2APB桥所做的就是连接APB总线和AHB总线,并且它还要像CPU本身一样作为APB总线的主机,来统管各个外设。
费了这么多篇幅,好歹我们讲到CPU内部的片上总线了。除了ARM7上流行的AMBA总线外,还有Silicore的WISHBONE总线(很多开源设计都采用此总线)、IBM的CoreConnect总线,然后就是Altera的Avalon总线。不同总线各有特点,适用范围也有不同。Avalon总线就是Altera公司主推的应用于其软核处理器NIOS II上的总线,主要包括Avalon-MM总线和Avalon-ST总线,下面我们就去认识它们。
基于NIOS II处理器的片内系统互连主要靠的就是Avalon-MM总线和Avalon-ST总线。如图6-7所示,一个典型的NIOS II系统,NIOS II处理器和各外设之间通过Avalon-MM总线进行交互,而外设之间的点到点数据传输则可以通过Avalon-ST总线来完成。Avalon-MM(Avalon Memory Mapped Interface)总线是一种基于地址读写的主从互连的机制。Avalon-ST(Avalon Streaming Interface)总线主要应用于单向数据流传输,可以完成点到点的大数据量吞吐。另外也略带提一下,在Avalon总线规范里,还有Avalon-MM Tristate和Conduit接口。前者主要是Avalon-MM的一个扩展,也可以理解为它是Avalon-MM的一个“集线器”,对一个Avalon-MM从机进行复用,这样做的好处是减少从机接口的数量,用一套总线信号就可以挂接多个从机。比如在接口信号的数量紧张时,尤其是连接到FPGA外部引脚上的多个存储器(如图6-7中的SRAM和FLASH),此时就可以复用到一个Avalon-MM Tristate接口上。而Conduit接口则是AvalonMM从机引出的可以连接到FPGA其他逻辑模块或是FPGA外部引脚上的信号接口。
3.Avalon-MM总线
话说Avalon-MM总线就好像一条康庄大道,而各个主机或是从机都是通过一条专用通道连接到这条主干线上。主机有访问各个从机的主动权,一个系统中的主机可以不止一个,CPU可以是主机,DMA也可以是。当CPU通过康庄大道访问从机A的时候,DMA也可以通过这条康庄大道访问从机A以外的其他从机,二者互不冲突。但是,如果CPU访问从机A,DMA也试图访问从机A,那么我们就发现最终通往从机A的那条“专用通道”就要抗议了,“一山难容二虎”啊。此时我们就必须考虑加入一些仲裁逻辑,可以让某个主机优先访问,也可以遵循“先来后到”的准则。
回到概念上来,Avalon-MM总线所针对的是主从连接、可以用地址进行访问的通信。对于很多嵌入式的软件工程师,他们潜意识里已经把这些复杂外设的驱动控制理解为对datasheet里那些大大小小的寄存器所对应的地址进行读读写写了。问题也的确这么简单,而Avalon-MM接口就是顺着大家的这种惯常思维(毕竟这已成为了一种标准了)应运而生。其实简单的Avalon-MM接口时序和前面介绍的Intel接口或是Motorola接口很是有几分相似。所以,大家也不用太恐惧,面对Avalon-MM我们有信心,不仅是弄懂它,更是要玩转它。
因为在实例就是要来做一个与NIOS II连接的Avalon-MM从机组件,所以我们会在实践中更深入地接触Avalon-MM这位漂亮的“美眉”(MM)。这里为了让大家对Avalon-MM总线的通信方式有一些直观的认识,就列举一个最简单的Avalon-MM主从工作机制进行讨论,也就是之前的例程我们使用GPIO配置为Output外设时的写操作。
就拿工程实例3的PIO_LED外设来看,我们可以先回顾一下当初定义这个外设的功能。无非就是一个1位输出的GPIO外设,当我们软件编程时向其对应地址写入数据值就可以控制LED引脚的输出电平状态。其硬件连接关系大体如图6-8所示。
那么我们可能还会更关心这个硬件连接的具体实现细节,也就是Avalon-MM总结的控制时序。如图6-9所示,打开工程管理窗口中的pio_led模块代码,这个代码是根据我们在Qsys中配置的PIO_LED外设自动生成的。
PIO_LED的从机接口逻辑代码如下。
module vip_qsys_pio_led (
// inputs:
address,
chipselect,
clk,
reset_n,
write_n,
writedata,
// outputs:
out_port,
readdata
);
output out_port;
output [ 31: 0] readdata;
input [ 1: 0] address;
input chipselect;
input clk;
input reset_n;
input write_n;
input [ 31: 0] writedata;
wire clk_en;
reg data_out;
wire out_port;
wire read_mux_out;
wire [ 31: 0] readdata;
assign clk_en = 1;
//s1, which is an e_avalon_slave
assign read_mux_out = {1 {(address == 0)}} & data_out;
always @(posedge clk or negedge reset_n)
begin
if (reset_n == 0)
data_out <= 0;
else if (chipselect && ~write_n && (address == 0))
data_out <= writedata;
end
assign readdata = {32'b0 | read_mux_out};
assign out_port = data_out;
endmodule
如图6-10所示,从工程的RTL视图里也可以看到PIO_LED外设的接口信号,与代码里的接口是一致的。
这些接口中,作为inputs的信号中,时钟信号clk、复位信号reset_n是由系统提供的。其他几个信号如地址信号address、片选信号chipselect、写选通信号write_n和写入数据writedata都是Avalon总线上的常用信号。简单来看,使用这几个信号组成的Avalon总线接口写时序如图6-11所示。
写选通信号和片选信号的有效时钟周期数也都是可变的,甚至地址和数据的有效时钟宽度也都是可调整的。由此足见Avalon接口的灵活性,尤其在开发者自己设计Avalon从机作为Qsys上的一个自定义组件时,我们更是可以根据从机的实际状况将各个接口信号的时序关系调整到一个最优的状态。
回过头来,再看前面给出的从机底层代码,用文字描述可能会显得比读代码更枯燥,因为波形都给出来了,有点基础的朋友恐怕都更愿意去对照着波形解读代码。这里的接口代码只是一种最简洁的写法,里面有一些技巧,我们可以模仿,但是不要把思路限制住,我们要活学活用,如此你才会逐渐发觉——设计原来如此简单。