3.1.1 指令总线主端口.......................................................................................................2 3.1.2 数据总线主端口.......................................................................................................3 3.1.3 高速缓存...................................................................................................................3 3.1.4 移位单元...................................................................................................................4 3.1.5 乘法支持...................................................................................................................4 3.1.6 中断支持...................................................................................................................4 3.1.7 NIOS片上调试模块(OCI模式)..........................................................................5 3.2 NIOS CPU编程模型.........................................................................................................5
3.2.1 寄存器.......................................................................................................................6 3.2.2 存储器结构.............................................................................................................10 3.2.3 寻址方式.................................................................................................................15 3.2.4 程序流程控制.........................................................................................................17 3.2.5 异常事件.................................................................................................................18 3.2.6 流水线.....................................................................................................................22 3.3 Avalon总线......................................................................................................................24
3.3.1 概述.........................................................................................................................24 3.3.2 术语和概念.............................................................................................................25 3.3.2 Avalon总线传输...................................................................................................28 3.3.3 Avalon从端口传输...............................................................................................30 3.3.4 Avalon主端口传输..................................................................................................34 3.3.5高级Avalon总线传输.............................................................................................36 3.3.6 片外设备与Avalon总线的接口............................................................................37 3.3.7 Avalon总线地址对齐方式......................................................................................41 3.3.8 连接到外部设备...................................................................................................43 3.4 外设的组织与使用..........................................................................................................43
3.4.1 SOPC Builder与PTF文件.....................................................................................43 3.4.2 定时器.....................................................................................................................45 3.4.3 并行输入输出口.....................................................................................................48 3.4.4 异步收发器(UART)...........................................................................................51 3.4.5 DMA控制器.........................................................................................................58 3.4.6 串行外围设备接口(SPI)....................................................................................63
第3章 NIOS CPU及其外设
本章是Nios CPU的基础,讲介绍Nios CPU的结构和编成模型、Avalon总线以及外
部存储器和外设。
3.1 NIOS CPU结构
Nios 3.0版本的CPU是一个具有5级流水线、指令总线和数据总线分开(Harvard结
构)的RISC微处理器。指令和数据的控制端口都作为Avalon总线主端口(Bus-Master)。用户可以把Nios的Avalon总线主端口和任何从端口(Slaves,如内存和外设)通过Avalon总线互联起来,SOPC Builder会根据需求自动加入仲裁器。Nios CPU是可配置、可优化的软核,可以和用户逻辑一起植入一个Altera PLD器件之中,其的结构如图3.1所示:
图3.1 Nios CPU结构图
3.1.1 指令总线主端口
Nios指令总线主端口(Instruction Bus-Master)是16位宽的端口,支持延时操作。此
主端口仅仅是负责从存储器中读取指令的通道,不支持任何写操作。因为主端口支持延时操作,所以能够适合于各种不同速度的存储器。指令主端口可以在上一条指令返回之前,发出新的取指请求。Nios CPU采用“假设无分支(branch-not-taken)”的预测方法来
生成预取指的地址。由于支持具有操作延迟的存储器,所以使得在使用慢速存储器时,对CPU的影响达到最小,并能在整体上提高系统的最高频率。只有分支预测失败时,才会有比较大的延迟。当访问慢速存储器的时,用户还可以选用片内缓存的机制来提高读取指令的平均速度。
由SOPC Builder自动产生的Avalon总线,具有动态总线宽度对齐逻辑的功能。因此,在Nios指令总线主端口上可以连接8、16和32位宽的存储器,以满足不同应用场合的需要。
3.1.2 数据总线主端口
对于32位的体系结构,Nios数据主端口(Data Bus-Master)宽度是32位;对于16位的体系结构,则宽度为16位。数据主端口有以下三个用途:
当CPU执行一个数据加载指令(LD,LDP,LDS)时,从存储器中读取数据。 当CPU执行一个数据存储指令(ST,STP,STS,ST8s,ST8d,ST16s,ST16d,
STS8s,STS16s)时,向存储器写入数据。
当CPU执行TRAP指令或处理内外部异常时,从中断向量表中取出中断向量。 Nios数据总线主端口不支持延迟操作(因为预测地址没有意义)。因此,数据主端口把来自从端口的延迟看作是等待周期。当Nios数据总线主端口被连接到具有零等待周期的存储器上时,数据加载和数据存储操作都能在单个时钟周期内完成。在数据总线主端口和指令总线主端口共享的从端口上,必须使得数据主端口的优先级别最高,才能获得最高的工作性能。
3.1.3 高速缓存
Nios CPU中的指令、数据主端口各包含一个可选的高速缓存机制。通过SOPC Builder中的Nios处理器配置向导,用户可以通过适当的配置,来选择是否使用指令和数据的缓存功能。缓存位于芯片内部,大小可配置。 指令缓存和数据缓存都采用最简单的直接映像方式。在系统运行时,用户可以对Nios CPU内的控制寄存器进行设置,来决定用指令、数据缓存功能是否起有效。 在启用缓存功能情况下,Nios CPU在执行程序时,如果缓存中具有下一条要执行的指令或者具有当指令所使用的数据,那么Nios CPU就可以直接使用,从而省去从外部存贮器中获取指令或数据的时间,我们把这种情况简称为缓存命中。当缓存有效时,缓存命中就会使得存储器的加载操作在单个时钟周期内完成。而缓存不命中时,就会引起额外的延迟。当禁止缓存时(暂时以软件方式禁止缓存功能),访问存储器时就会引起额外的延时。但当重新启用缓存时,存储器的存储操作将导致一个或两个额外的延迟周期。(使用缓存的存储器,写操作都将导致一个或两个额外的延迟周期。) 值得注意的是,高速缓存仅仅在32位Nios CPU系统中才能实现。另外,Nios缓存单元也仅仅在Stratix,Straix GX和Cyclone系列芯片中才能配置。
3.1.4 移位单元
Nios处理器使用固定的不可配置的桶形移位逻辑来执行所有的移位指令(ASR,ASRI,ASL,ASLI,LSL,LSLI,RLC和RRC),并在两个时钟周期内完成(与移位的位数无关)。在做控制和数字信号处理时,移位单元对所需的移位操作提供了必要方便的支持。
3.1.5 乘法支持
Nios处理器有三种不同的方法来实现整数乘法:
1. MUL指令
32位的Nios CPU可以选择配置成16x16 –> 32整数硬件乘法器,MUL指令可以在3个或更短的时钟周期内计算出一个32位的结果。MUL选项选中后,MUL指令将利用由SDK自动产生的C语言运行支持库(C-runtime库)来实现乘法运算。值得注意:16位的Nios指令系统不支持此选项。
2. MSTEP指令
32位的Nios CPU可以配置成执行单步16x16的乘法运算器,硬件乘法器将在2个时钟周期内完成部分(其余部分在软件辅助下完成)乘法运算。MSTEP选项选中后,SDK生成的C语言运行支持库就会支持相应的乘法操作,它通过连续的MSTEP实现乘法操作,例如用16个连续的MSTEP指令实现16x16 –> 32乘法。由于使用MSTEP所占用的CPU硬件资源还不到5%,很多情况下利用如此小的硬件开销,换得了对硬件乘法的支持,无疑对于软件设计带来很大的方便,所以系统将MSTETP作为默认选项。同样值得注意:16位的Nios指令系统不支持此选项。
3. 软件乘法器
当禁止MSTEP和MUL两种选项时,如果需要完成乘法运算,则由SDK中的C语言运行支持库,利用移位和加法指令来实现整数乘法运算。软件乘法器虽然占用了很少的CPU硬件逻辑,但是比起以硬件实现的乘法运算,其执行速度要慢很多。
3.1.6 中断支持
Nios CPU允许用户取消对TRAP指令、硬中断或内部异常的事件处理,这种选择仅仅只适用于非常简单的系统。在这种配置下,Nios处理器将具有如下特征:
不包括irq输入引脚
无异常处理(TRAP指令未定义)
在执行SAVE和RESTORE指令使寄存器文件上溢/下溢时,不产生异常中断。
仅当有如下要求时才能取消陷阱指令、硬中断或内部异常: 需要最小化的Nios CPU核
确定应用程序不会产生寄存器窗口溢出异常,即子程序的调用深度小于寄
存器窗口数
系统没有任何硬件中断源 汇编代码不包含陷阱指令
注意:Nios的SDK在编译过的代码中不会产生陷阱指令。缺省情况下,中断支持功能开启。
3.1.7 NIOS片上调试模块(OCI模式)
Nios CPU有个可选的片上JTAG调试模块,通过标准的JTAG接口,实现与CPU的通信。此模块是由First Silicon Solutions (FS2) 公司开发的IP核,被称为Nios OCI(On-Chip Instrumentation)调试模块。
在调试应用程序时,Nios OIC调试模块支持用户设置硬件断点,还可以实现软件跟踪。所跟踪数据,保存在片上的存储器或者外部的系统分析器中(如FS2公司的ISA-NIOS in-system analyzer)。该模块还可以向寄存器和存储器读写数据,允许在程序运行期间向存储器下载软件程序和检查寄存器。Nios OCI调试模块可控制CPU的执行,无论何时,用户都可以对正在执行的程序进行控制。也就是说,这种控制作用的优先级别最高。
除了调试功能外,在与主设备通信时,Nios OCI也能被用作标准的输入输出。该功能与调试功能是可同时使用,且互不干扰,但数据的传输速度比起利用串口作为输入输出时要慢一些。
3.2 NIOS CPU编程模型
Nios CPU是单指令流的RISC处理器,大多数指令都是在一个单周期内完成的。Nios
中规定:字节为8位,半字为16位,字为32位。Nios处理器系列包括32位和16位两种结构体系,见表3.1。
表3.1 Nios CPU体系结构
Nios CPU 规格 32位Nios CPU 数据总线宽度(位) ALU宽度(位) 内部寄存器宽度(位) 地址总线宽度(位) 指令宽度(位)
16位Nios CPU
32 16 32 16 32 16 32 16 32 16 Nios工具包,包括GNUPro编译器和Cygnus的调试器,这些都是开放的、符合工业
标准的开发套件。GNUPro工具包中,包含一个C/C++编译器、宏汇编器、连接器、调试器、二进制工具和库等。
Nios指令系统支持C/C++编译,它包括一个标准的算术逻辑操作集,支持位操作、字节提取、数据移动、控制流修改以及条件执行指令。条件执行指令可以有效的替代短
的分支程序。
Nios CPU采用16位指令(指令总线主端口数据宽度为16位)系统时,其指令集有以下特点:
1. 拥有较大的寄存器文件,并以寄存器窗口的形式组织管理 Nios CPU包括最多512个内部通用寄存器。 2. 简单完整的指令集
32位和16位的Nios CPU都使用16位宽度的指令,这样,不仅减小代码的大小,而且也减少了指令存储器的数据位数。
3. 强大的寻址模式
Nios指令集中包含加载(Load)和存储(Store)指令,可使用编译器来加快对结构和本地变量(栈)的访问速度。
4. 良好的可扩展性
用户可以直接将自己的逻辑单元(作为用户定制指令)加入Nios算术逻辑单元ALU中。在软件开发包(SDK)中,系统会相应生成访问该定制指令的宏(用C或汇编编写)。
3.2.1 寄存器
Nios CPU内部包括的寄存器有:一个通用寄存器文件,多个内部控制寄存器,一个程序计数器PC,以及用于前缀指令的K寄存器。
1. 通用寄存器
在32位Nios CPU中,通用寄存器的宽度为32位。寄存器文件的大小是可配置的,根据需要可分别配制为128、256或512个寄存器。软件通过滑动窗口(每个滑动窗口为32个寄存器)来访问寄存器,滑动窗口以16个寄存器长为滑动单位,可以遍历整个寄存器文件并可对其子集进行访问。
寄存器窗口分为四个部分,如表3.2所示。最低8个寄存器(%r0~%r7)是全局(global)寄存器,也被称作%g0~%g7。全局寄存器并不跟随窗口位置的移动而变化,而是一直作为%g0~%g7被访问。寄存器文件上面的24个寄存器(%r8~%r31)可以通过当前的窗口访问。每当一个寄存器窗口向下移动16个寄存器时(比如执行SAVE指令时),输出寄存器(Out Registers)就变成了新窗口位置上的输入寄存器(In Registers),使得新滑动窗口上的局部寄存器和输入寄存器不可访问。见表3.3。
表3.2 寄存器组
输入(In)寄存器 %r24~%r31或%i0~%i7 局部(Local)寄存器 %r16~%r23或%L0~%L7 输出(Out)寄存器 %r8~%r15或%o0~%o7 全局(Global)寄存器 %r0~%r7或%g0~%g7
表3.3 寄存器编程模型
2. K寄存器
K寄存器是一个11位的前缀寄存器,除了PFX或PFXIO指令之外,它可以被其余所有指令设置为0。对于PFX或PFXIO指令,K寄存器的值是由IMM11指令域所决定的。K寄存器所包含的非0值只是为紧跟在PFX或PFXIO后的所指令所用的。
执行PFX或PFXIO指令时,不会响应任何中断(关闭一个周期),因此PFX(或PFXIO)和其后的指令构成原子操作。
K寄存器不能被软件直接访问,而只能被间接访问。比如,一个MOVI指令可以把
K寄存器的11位传输到目标寄存器的第15..5位。如果前一条指令是带有非0变量的PFX指令时,则对K寄存器的读操作就只能产生一个非0的结果。
3. %r0(%g0)寄存器
这个寄存器是用来存放以下指令的自变量或结果用的:STS16S,STS8S,ST16S ST8S,ST16D,ST8D,FILL16,FILL8和USR1-USR4。
4. 计数器PC
程序计数器(PC)的值是下条指令的地址。Nios CPU的所有指令都必须是以半字排列的,因此程序计数器PC的最低位总是为0。每次读取指令之后,程序计数器PC值就自动增加,以指向下一条指令,除非程序计数器PC被其他指令赋值。BR、BSR、CALL、JMP、LERT、RET和TRET指令可以直接修改程序计数器PC的地址值。
5. 控制寄存器
32位Nios处理器含有5个已定义的控制寄存器,它们与多用途寄存器相互独立,只有RDCTL和WRCTL可以对这些控制寄存器进行读写操作(即%ctl0与%g0是无关的)。
(1) STATUS寄存器(%ctl0)
数据缓存使能(Data Cache Enable,DC) DC是数据缓存使能位。当DC=0时,不允许数据缓存,下载指令(LD,LDP和LDS)像没有缓存时一样操作。当DC=1时,下载指令首先检查数据缓存是否包含寻址值。如果缓存包含寻址值(“hits”),CPU使用来自缓存器的值,而不访问内存或外设。如果缓存不包含寻址值(“misses”),则CPU要访问存贮器或外设。假如使用了数据缓存器,即使是使用了低速的存储器或外设,也能在整体上提高系统的性能。
当重启CPU时,数据缓存功能被取消,DC被置为0。如果要使用数据缓存功能,就必须在使用之前对它初始化,即将DC被置为1。
指令缓存使能(Instruction Cache Enable,IC)
IC是指令使能缓存位。当IC=0时,不允许指令缓存,CPU取指令时,像没有缓存器一样的去操作。当IC=1时,CPU取指令时,首先检查指令缓存器中是否包含欲寻址的指令。如果缓存中包含欲寻址的指令(“hits”),则使用来自缓存器的指令,而不再访问存贮器。如果缓存中没有欲寻址指令(“misses”),CPU就会自动的从存贮器中读取指令。同样,假如使用了指令缓存器,即使是使用了低速的存储器,也能在整体上提高系统的性能。
当重启CPU时,默认为不使用指令缓存功能,即IC置为0。如果要使用指令缓存功能,就必须在使用之前对它初始化,即将IC置为1。
中断使能(Interrupt Enable,IE)
IE是中断使能位。当IE=1时,允许处理外部中断和内部异常的事件;当IE=0时,不允许处理外部和内部异常的事件,而无论中断使能位的状态如何,软件TRAP指令都能正常执行。设计者可以直接设置IE而不影响STATUS的其它位,利用SET_IE(%ctl9)和CLR_IE(%ctl8)就可以分别对IE置1和置0。当CPU重启后,IE的默认值为0,即
禁止一切外部中断和内部异常的事件。
中断优先级(Interrupt Priority,IPRI)
IPRI包含当前运行的中断优先级。当发生异常时,IPRI域被设置为异常码。当外部中断发生时,IPRI被直接依照6位的硬件中断优先级设置。对于TRAP指令,IPRI域直接从该指令的IMM6域取值。对于内部异常,则按照预定义的6位异常码设置IPRI域。
假如中断码大于或等于IPRI的值或是IE=0,就不会被响应硬件中断的请求。比如,假定IPRI=3,若中断优先级为0、1、2,则响应中断;若中断优先级为3~63,则无效。TRAP指令是无条件执行的。CPU重启时,IPRI=63,默认为最低优先级。
当前窗口指针(Current Window Pointer,CWP)
CWP指向滑窗的底部,CWP每加1,则滑窗上移16个寄存器;CWP每减1,则滑窗下移16个寄存器。执行SAVE指令时,CWP减1;执行RESTORE指令时,CWP加1。用户只有通过像滑窗管理工具这样的专用系统软件,才可以通过WRCTL指令对CWP写入数值。当CPU重启时,CWP的默认值为HI_LIMIT,即最大值。在256个寄存器的组中,共有16个窗,重启后WVALID(%ctl2)置为0x01C1(因为LO_LIMIT=1,HI_LIMIT=14)。在128个寄存器的组中,HI_LIMIT=6,在512个寄存器的组中,HI_LIMIT=30,LO_LIMIT都为1。
条件码标志(Condition Code Flags)
状态寄存器STATUS最低4位是条件码标志,有些指令可以改变条件码标志。参见表3.4。
表3.4 条件码标志
标志
位
结果
N 3 结果的符号,即最高位
V 2 算术溢出,即结果的符号位与按照无限精度精度计算时的符号不一致 Z 1 结果为零
C 0 加法的进位,减法的借位
(2) ISTATUS寄存器(%ctl1)
ISTATUS是STATUS的映像寄存器。当处理异常事件时,STATUS的内容被复制到ISTATUS中加以保护。在异常服务程序返回时,再将ISTATUS(发生异常前的内容)复制给STATUS寄存器。TRET指令可自动将ISTATUS的内容复制回STATUS中。
在处理异常事件时,所有的中断被禁止(即IE=0),在重新使能(开放)中断前,异常处理程序必须保护ISTATUS的内容。当CPU重启后,缺省情况下ISTATUS=0。
(3) WVALID寄存器(%ctl2) WVALID包括有两个值:HI_LIMIT和LO_LIMIT。当执行SAVE使CWP从LO_LIMIT变成LO_LIMIT-1时,寄存器窗口产生下溢异常(异常#1);当执行RESTORE使CWP从HI_LIMIT变成HI_LIMIT+1时,寄存器窗口上溢异常(异常#2)。WVALID寄存器是可以配置的,有只读和可读写两种配置方式。当CPU重启时,默认情况下,LO_LIMIT被置为1,HI_LIMIT被置为最大有效窗口指针((寄存器组总尺寸/16)- 2)。
(4) ICACHE寄存器(%ctl5)
ICACHE是指令缓存的行失效寄存器。把一个地址写给ICACHE,就可以使得与该地址对应的缓存行无效。在启用指令缓存机制之前,必须用ICACHE初始化指令缓存。如果对被缓冲的存储器执行了写操作,就必须用ICACHE来通知Nios CPU的指令缓存。
ICACHE是只写的控制寄存器。读ICACHE寄存器(通过执行PFX 5;RDCTL)是没有意义的。
在写一个ICACHE寄存器前,必须首先取消指令缓存功能(STATUS的IC位为0)。 (5) CPU_ID寄存器(%ctl6)
CPU_ID寄存器包含有一个16位的常量,用来标识Nios 处理器的版本。每个发行版本都有唯一的CPU_ID。在发行处理器版本附带的readme文件里,可以找到CPU_ID的值。对于32位的Nios CPU,第15位总是1,第14位到第12位是主版本号,其余位的数字串对于每一个主版本是唯一的。第3到第0位若是0x8,表明是该CPU是OpenCore Plus的评估版,而若是其他任一数值则表明是一个完全功能的处理器。
DCACHE寄存器(%ctl7)
DCACHE是数据缓存的行失效寄存器。通过写一个地址到DCACHE可以使得包含该地址的缓存行无效。在使能数据缓存之前,必须用DCACHE初始化数据缓存。也可以用DCACHE通知Nios CPU的数据缓存,另一主设备已经写数据到缓存。
DCACHE是只写的控制寄存器。读DCACHE(通过执行PFX 7;RDCTL)将是没有意义的。
在写一个DCACHE之前,必须首先取消指令缓存功能(STATUS的DC位为0)。如果要使得大量指令缓存行无效,只要把相应的存储区声明为易失即可。C编译器nios-elf-gcc使用PFXIO指令访问易失存储区的数据,这样既跳过缓存又避免使用了缓存行失效的操作。
CLR_IE寄存器(%ctl8)
任何对CLR_IE 的WRCTL操作将清零IE位。不管WRCTL为何值,对于CLR_IE的RDCTL读操作将返回一个不确定的值,因此读RDCTL是没有意义的。
SET_IE寄存器(%ctl8)
任何对SET_IE 的WRCTL操作将置IE位为1。不管SET_IE为何值,对SET_IE的RDCTL读操作将返回一个不确定的值,因此读RDCTL是没有意义的。
3.2.2 存储器结构
Nios处理器的数据访问采用小端对齐方式(little-endian)。使用数据存储器时,必须是连续的字对齐方式。如果存储器的数据宽度小于字宽,则数据总线将会采用动态地址对齐方式(dynamic-bus sizing)来模拟Nios CPU的全宽数据。如果外设的寄存器宽度小于字宽,则在高位添加0来补齐字宽。表3.5和表3.6给出了32位Nios CPU寄存器字宽的例子。
表3.5 典型的32位Nios CPU在地址0x0100的程序/数据储存器
表3.6 地址0x0100的N位宽的外设(32位Nios CPU)
1. 读存储器(或外设)
Nios CPU只能以对齐的方式访问存贮器(或外设),也就是说,只能从一个以4为整数倍的地址处读取一个字。Nios CPU在读取指令时,总是把指令地址的最低两位视为0。如果有必要从字中提取特定字节或半字,那么,Nios CPU指令集中提供了专用的指令以满足这一需要。
最简单的读存贮器指令是LD,举个典型的例子“LD %g3,[%o4]”。第一个操作数%g3是目标寄存器,第二个操作数%o4间接寻址寄存器,其中内容是一个待读取数据的地址,这个地址是按字对齐的,意味着最低两位被视为0。
然而,实际应用中经常需要读取不足32位的数据,针对这种情况,Nios CPU提供了专门的指令,用来从字中提取特殊字节和半字。EXT8D用来提取一个字节,EXT16D用来提取一个半字。典型例子如:EXT8D %g3,%o4。指令中,利用寄存器%o4的最低2位来决定,从第一个操作数中提取哪一个字节,并且用那个字节替代第1个寄存器的全部内容。这个例子图示如下:
图3.2 EXT8D指令
下面的汇编代码显示了,即使在字节不是字对齐的情况下,如何从存储器中读取一
个字节。
例3.1 由存储器中读取单一字节
2. 写存储器(或外设)
Nios CPU可以对存储器(或外设)写入字节、半字或字。字可以用一个指令写入任何以4为整数倍的地址单元,半字可以用两个指令写入任何偶地址起始的内存单元,字节可以用两个指令写入任何地址的内存单元。
一个寄存器的最低字节只能写入以4为整数倍的地址单元;次低字节只能写入4的倍数加1的地址单元,其余以此类推。
Nios CPU也可以写寄存器的低半字到以4为整数倍的地址单元,写寄存器的高半字到以4为整数倍的地址加2的地址单元。
使用ST指令,就可以把任何寄存器的内容写到一个字对齐的存贮器单元;ST8D和ST16D指令分别通过寄存器%r0写字节和半字到对应的内存单元。
实际应用中经常有必要写一个特定的字节或半字到一个特定的存贮器中,而它在源寄存器中的位置可能与待写入的地址奇偶性不符合。FILL8和FILL16可以分别取出寄存器最低字节或半字,并且把它分别复制到%r0中的各个字节或半字中。例子如下: 例3.2 向地址中写入一个字节-地址未按字对齐
3. 缓存操作
通过选择,可以决定Nios CPU是否具有指令缓存和数据缓存的功能。数据缓存将极大的影响Nios CPU执行指令的速度。数据缓存的作用是存储最近访问过的字,CPU尽量使用缓存里的数据,而不进行外部存储器操作。Nios CPU使用直接映射方式的缓存操作,也就是说,数据地址的最低位被用来选定直接访问缓存的行(如图3.3)。在直接映射缓存方式中,不同地址但相差缓存容量倍数的数据字将被存在同一行,字的地址的高位作为标签,与字的数据及一个有效位存储在一起。
图3.3 用字地址的低位访问缓存行
注(1):数据缓存行的总数等于数据缓存的容量除以4。指令缓存行的总数等于指令缓存的容量除以2。
当执行LD,LDP或LDS这类装载指令时,Nios CPU先把装载地址的高位与选定的缓存行的标签作比较,假如两者匹配并且缓存行的数据有效,处理器则用缓存里的数据而不进行读存贮器的操作,从而提高Nios CPU的处理速度。当缓存中存有需要的数据时,称作“hit(命中)”,否则,称为“miss(不命中)”。
write-through是一种最简单的写缓存处理策略,Nios CPU就是使用这种策略。这意味着,数据不仅写入缓存也同时写入存储器。写入数据的缓存行地址,由指令中的数据地址的低位来确定,因此随后从同一个地址装载数据将会是“hit”。除了把数据写到缓存之外,写操作还把地址高位写入数据标签,并使得有效位置位。
在缓存为“miss”的情况下,处理器执行一个读周期时,就会把读取的数据在写入目的寄存器的同时也写入数据缓存中。这样,下一条从相同存贮器地址读取数据的指令将会成为“hit”状态。 (1) 初始化缓存
在使用缓存之前,必须初始化缓存,并且使得缓存使能。初始化数据缓存的方法是通过给DCACHE寄存器写一系列地址。初始化的作用是清除所有缓存行的有效位,以防止未初始化的标签数据产生错误的“hit”标志。写DCACHE时,STATUS中的数据缓存使能位(DC)必须是0。当DC=1时,写DCACHE会产生一个不确定结果。
例3.3中,使用了缓存控制寄存器和状态寄存器来初始化指令缓存(初始化数据缓存与之相似)。此处使用的是C和汇编语言混合编程的方法,通过宏定义nm_icache_enable和nm_icache_disable,利用RDCTL读取状态寄存器,对其进行修改(例如其中的一个位),然后用WRCTL写回。 例3.3 初始化缓存(“\\”表示此行未写完,与下一行是同一行。#define语句要写成一行)
#define np_nios_icache_bit 0x00010000 // bit in control register 0 #define np_nios_dcache_bit 0x00020000 // bit in control register 0 #define np_nios_icache_reg 5 // register to invalidate a line of icache #define np_nios_dcache_reg 7 // register to invalidate a line of dcache #define nm_icache_invalidate_line(byte_address) \\ asm(\"pfx 5 \\n\ wrctl %0\" : : \"r\" (byte_address)); #define nm_icache_enable() \\ { \\
int status; \\
asm(\"rdctl %0\" : \"=r\" (status)); \\ status |= np_nios_icache_bit; \\
asm(\"wrctl %0 \\n\ nop\" : : \"r\" (status)); \\ }
#define nm_icache_disable() \\ { \\
int status; \\
asm(\"rdctl %0\" : \"=r\" (status)); \\ status &= ~np_nios_icache_bit; \\
asm(\"wrctl %0 \\n\ nop\" : : \"r\" (status)); \\ }
void nr_icache_init(void) {
int i;
nm_icache_disable();
for(i = 0; i < nasys_icache_size; i+= nasys_icache_line_size)
}
nm_icache_invalidate_line(i);
nm_icache_enable();
(2) 绕过高速缓存读取外设
重复的访问同一存贮器单元将会导致缓存状态为“hit”,但是对于外设,是不希望因此而阻止对外设的访问。比如,重复从UART中读取数据,尽管数据的地址(固定的端口地址)不变,但返回的数据每次可能都不一样。如果允许数据缓存,CPU在进行第一次读操作后,将不再直接读取外设寄存器,而使用缓存中的数据。因此,对外设端口(Nios CPU将其视为一个存贮器单元)的读操作需要被特殊标识。
Nios 提供了一个很有用的指令PFXIO,用它可以与其它指令组合而构成指令对,使用指令对,就可以用来禁止对数据的缓存操作。紧跟在PFXIO指令之后的LD或LDP指令,即使在缓存使能的情况下,也将是绕过缓存而直接从存储器(或外设)读取数据。反之,对于LD或LDP指令,如果它们之前没有PFXIO,当缓存使能时(DC=1),则从缓存读取数据。
所有的LDS指令在数据缓存使能时,总是直接读取存储器(或外设)中的数据。注意,不能PFXIO/LDS指令序列,否则将产生不确定结果。
对于那些需要绕过缓存直接读取的数据端口或外设端口的特殊操作,在变量声明时必须使用类型限定词volatile,这样Nios C编译器将自动插入PFXIO指令。使用类型限定词volatile声明的寄存器、变量或者缓冲器,都不会使用数据缓存。
3.2.3 寻址方式
Nios CPU指令系统有五种寻址方式: 5/16位立即寻址; 全宽寄存器间接寻址; 部分宽寄存器间接寻址;
带偏移的全宽寄存器间接寻址; 带偏移的部分宽寄存器间接寻址。 1. 5/16位立即寻址
很多算术和逻辑指令,把5位立即数作为一个操作数。例如ADDI指令有2个操作数:一个是寄存器和一个5位宽度的立即数。5位宽度的立即数可表示一个0到31的常数,如果要指定一个6到16位宽度的常数值(一个32到65535的数),就需要PFX指令来设置11位的K寄存器。K寄存器的值和5位宽度的立即数连接起来表示一个16位的常量。PFX指令必须紧放在它所将修饰的指令前。为了支持拆分一个16位宽度的立即数常量为1个11位宽度的PFX值和一个5位宽度的立即数,汇编语言提供了一个%hi()和一个%lo()操作。%hi(x)提取x的高11位(5..15位),%lo(x)提取x的低5位(0..4位)。
例3.4举例说明带有和不带有PFX的ADDI指令的使用情况。
; Assume %g3 contains the value 0x00000041 ADDI %g3,5 ; Add 5 to %g3
; so %g3 now contains 0x00000046
PFX %hi(0x1234) ; Load K with upper 11 bits of 0x1234 ADDI %g3,%lo(0x1234) ; Add low 5 bits of 0x1234 to %g3 ; so the K register contained 0x091 ; and the immediate operand of the ADDI ; instruction contained 0x14, which ; concatenated together make 0x00001234 ; Now %g3 contains 0x0000127A
除了算术运算和逻辑运算指令,还有一些指令,它们使用各种不同长度的立即数,而不被K寄存器修改。为了明确起见,表3.7列出了全部使用5/16位立即数的指令。
表3.7 使用5/16位立即数的指令
ADDI AND(1) ANDN(1) ASRI CMPI LSLI LSRI MOVI MOVHI OR(1) SUBI XOR(1)
注:(1) AND,ANDN,OR,XOR只能用PFX的16位立即数。这些指令之前若没有PFX指令,则使用两个寄存器操作数,采用寄存器间接寻址。
2. 全宽(full width)寄存器间接寻址
LD和ST指令可以将某个存贮器单元的数据字装载到寄存器,也可以将某个寄存器中的数据字写入到存贮器单元,而用另一个寄存器来指定这个存贮器单元的地址。参见表3.8。这个地址要向下排列与字地址对齐,K寄存器中的数值作为字对齐的地址寄存器的一个带符号的偏移量,偏移范围是- 4096 ~ 4092个字节,有效地址按下式计算:K(带符号)×4+(地址寄存器值&0xFFFFFFFC)。如果Nios 处理器包含一个数据缓存,读外设需要用带前缀PFXIO的LD指令。
表3.8 寄存器间接寻址
Instruction
Address Register
Data Register
LD Any Any ST Any Any
3. 部分宽度(partial width)的寄存器间接寻址
Nios CPU指令系统没有可以读取部分字数据的32位指令。要读取部分字数据,可以使用字数据寄存器间接读指令和提取指令(EXT8D,EXT8S,EXT16D,EXT16S)的联合来实现。
某些指令可以写部分字数据,这些指令分为静态变量和动态变量两种情况。源寄存器和存贮器数据字的内部位置,由一个地址寄存器的低位来决定。比如静态变量时,源寄存器和存贮器数据字的内部位置,由指令的1或2位立即操作数来决定。就象全宽寄
存器间接寻址一样,K寄存器作为字对齐的地址寄存器的一个有符号偏移量。
部分宽寄存器间接寻址均用%r0作为源操作数,如表3.9所示。
表3.9 半宽寄存器间接寻址指令
Instruction Address Register Data Register Byte/Half-word Selection ST8S Any ST16S Any ST8D ST16D
Any Any
%r0 %r0 %r0 %r0
Immediate Immediate Low bits of address register Low bits of address register
4. 全宽度带偏移量的寄存器间接寻址
LDP、LDS、STP、STS可以从存贮器中读取字数据到寄存器或从寄存器将字数据写入到存贮器中,其方法是使用一个寄存器来指定基地址,指令中的立即数则表示从基地址处开始的字偏移量。这些指令不像LD、ST可以用任一寄存器来指定存贮器地址,它们只能用某些专用寄存器作为基地址寄存器。LDP、STP只能用%L0,%L1,%L2,%L3作为基地址寄存器,参见表3.10;LDS,STS只能用堆栈指针%sp(相当于%o6)作为基地址寄存器。
表3.10 带有偏移的全宽寄存器间接寻址指令
Instruction LDP
Address Register %L0, %L1, %L2, %L3
Data RegisterAny
Offset Range without PFX or PFXIO 0..124 bytes
LDS %sp STP
%L0, %L1, %L2, %L3
Any 0..1020 bytes Any
0..124 bytes
STS %sp Any 0..1020 bytes
5. 部分宽度带偏移量的寄存器间接寻址
Nios指令集没有可以直接从存贮器中读部分字数据的指令。为了读部分字数据,可以联合使用字数据的变址(带偏移量)寄存器间接寻址指令和一个提取指令EXT8D,EXT8S,EXT16D或EXT16S。使用STS8S和STS16S指令,可以通过立即数来指定相对于堆栈指针的一个字节、或者半字的偏移量,写入源寄存器%r0中相应的部分字数据。这些指令都只能用堆栈指针%sp(相当于%o6)作为地址寄存器,并且只能用寄存器%r0(相当于%g0,在汇编指令中必须作为%r0被访问)作为数据寄存器。这些指令与FILL8或FILL16联合使用很方便。参见表3.11。
表3.11 带有偏移的部分宽寄存器间接寻址指令
Instruction
Address Register
Data Register%r0 %r0
Byte/Half-word SelectionImmediate Immediate
Index Range
0..1023 bytes 0..511 half-words
STS8S %sp STS16S %sp 3.2.4 程序流程控制
NIOS CPU通过以下几种类型指令进行程序流程控制:
相对转移(relative-branch)指令(BR和BSR); 绝对转移(absolute-jump)指令(JMP和CALL); 陷阱(trap)指令(TRET和TRAP);
条件执行(conditional)指令(SKP、SKP0、SKP1、SKPRZ和SKPRNZ)。 1. 相对转移指令
NIOS指令集中有两条相对转移指令,BR和BSR。转移的目标地址是由当前PC值(BR指令自身的地址)和IMM11指令域的值计算而来的。BSR除了返回地址保存在%o7之外,其他与BR相同。BR和BSR都是无条件转移指令。若要实现条件转移,可在BR或BSR前添加SKP类型指令(即条件执行指令)。
BR和BSR都有转移延迟槽,紧跟在BR或BSR的指令。也就是说转移延迟槽其实就是一条指令,转移延迟槽指令将在实现转移之前被执行。注意:相对转移指令的范围是从当前地址向前转移2048指令,或向后转移2046个字节。
2. 绝对转移指令
Nios指令集中有两条绝对跳转指令:JMP和CALL。绝对跳转指令的转移目标地址是由通用寄存器提供的,这些寄存器的内容左移1位并传送到PC。CALL除了返回地址保存在%o7之外,其他的与JMP相同。JMP和CALL都是无条件转移指令。
JMP和CALL指令都具有转移延迟槽,与前述的BR和BSR的情形相似。伪指令LRET,是汇编中“JMP%o7”的别名,可以方便的用于子程序返回。
3. 陷阱指令
Nios指令集中有两条陷阱指令:TRAP和TRET。与JMP、CALL指令不同,TRAP和TRET都没有转移延迟槽。紧跟在TRAP后的指令在异常处理返回前不会执行;而紧跟在TRET后的指令完全不被执行。
4. 条件执行指令
Nios CPU指令集中,条件执行指令有5条,它们是:SKPS,SKP0,SKP1,SKPRZ和SKPRNZ。每条指令都有一个对应的汇编伪指令别名(IFS,IF0,IF1,IFRZ,IFRNZ)。对于条件执行指令,CPU都会根据状态寄存器的情况,来决定程序的执行走向。5条SKP类型的指令的操作基本相同,只是实现转移的条件不同。条件指令执行时,不管检查条件的结果如何,CPU都将从存贮器中取得下一条指令,只是根据检查条件的结果来决定这条指定是执行还是取消。
虽然SKPx(IFx)类型指令常常用来使得相对和绝对转移指令被有条件的执行(请不要与条件汇编相混淆),但实际上,它们也可以用于任何指令。在使PFX或PFXIO条件执行时情况比较特殊,紧跟在后面的两条指令将执行或取消。PFX或PFXIO构成的指令对,将被视作一个单元来处理。
3.2.5 异常事件
本节介绍以下内容: 异常事件向量表;
外部硬件中断、内部异常事件、寄存器窗溢出(上溢和下溢)、陷阱(TRAP)
指令等异常情况的处理;
直接软件异常(TRAP)以及处理异常事件的步骤。 1. 异常事件处理概述
Nios处理器可以处理多达64个异常事件。用户可以通过设置STATUS寄存器中的IE控制位来使能或取消所有异常,也可以通过设置优先级别来有选择性的使能异常中断。Nios处理器有三种中断源:外部硬件中断,内部异常,软件TRAP指令。
Nios异常处理机制可以准确的处理所有的内部异常事件,可以完好的保存和恢复现场,就象程序从未被中断过一样。使用SAVE或RESTORE指令而造成寄存器窗口向上或向下溢出,也将会以产生产生内部异常的方式请求CPU处理。
异常处理子程序总是在一个新开的滑窗内执行,中断响应很快。中断处理程序会自动保护现场(即被中断的程序正在使用的寄存器中的内容)。
Nios处理器有一个非屏蔽的异常中断源,优先级别为0,专用于OCI调试模块。Nios OCI调试模块是一个由FS2公司设计的IP核,作为一个FS2 OCI模块来运行,直接与CPU内部信号相连。信号触发后,非屏蔽异常使得程序中断执行,而不考虑IE或IPRI的状态如何。不可屏蔽的异常中断是为调试功能保留的,用户程序不能访问非屏蔽中断。非屏蔽中断服务程序执行后,CPU总是返回到中断前的状态。
2. 异常向量表(Exception Vector Table)
异常向量表中含有64个异常处理的入口地址,每个入口地址占用4个字节。异常向量表的基地址是可以配置的。Nios CPU的中断优先级别为1~63,假设处理的异常码为n,当有中断申请并得到响应时,CPU在异常向量表第n个位置取得入口,并将其乘以2后送给程序计数器PC,从而进入中断服务子程序。非屏蔽中断0与其它中断不同,它不依赖于向量表的入口,向量表的第0个入口是无用的。
异常向量表可放在RAM或ROM中,取决于目标系统的硬件存储的分配情况。值得注意的是ROM中的异常向量表是不能在线初始化的。
3. 外部硬件中断源(External Hardware Interrupt Sources)
一个外部中断源对应一个硬件中断请求,可以通过驱动中断输入管脚来申请中断,同时需要在中断码输入端发送一个6位的中断码。在典型的系统中,Nios CPU的中断码和中断请求由自动生成的Avalon总线内部互连逻辑提供。因此,系统外设一般只有一个中断请求输出端,而由自动生成的总线逻辑将其转换成1位中断请求输入以及相应的6位的中断码。如果IE被使能且请求中断的优先级高于IPRI中设置值,则CPU响应中断,并将按照中断码处理相应的中断服务。
Nios 的中断请求输入是电平触发型的。CPU在每个时钟的上升沿对中断请求和中断码的进行采样。请求中断的外部中断源应当保持中断输出请求信号一直有效,直到软件响应中断(比如向中断外设内部的某个寄存器写入0)为止。如果中断源在中断响应之前取消请求,则中断请求将是无效的。
4. 内部异常发生源(Internal Exception Sources)
Nios有两个内部异常发生源:寄存器窗口向上溢出和向下溢出。Nios处理器可以对
所有的内部异常进行相应的处理。在任何一种异常发生时,异常处理程序都能够处理异常事件,并使程序继续执行。
(1) 寄存器窗口下溢(Register Window Underflow)
寄存器窗口向下溢出的异常码是1。当系统已经在使用最低有效的滑窗(CWP=LO_LIMIT)时,如果再执行SAVE指令就会产生一个下溢出异常。如果执行SAVE指令,就会把CWP移到LO_LIMIT下面,就会产生寄存器窗口下溢事件。产生寄存器向下溢出异常事件后,CPU将在SAVE后面的指令执行之前,转去执行异常处理子程序。
当执行一条SAVE指令而造成一个寄存器窗口下溢异常时,CWP在执行溢出异常服务子程序之前会减1(当CWP=LO_LOMIT-1时)。当禁止中断(IE=0)或当前IPRI值小于等于1时,CPU不处理向下溢出异常。
下溢异常处理子程序如何动作依赖于系统的要求。对于运行代码庞大且复杂的系统,向下(或向上)溢出异常处理程序将使用一个虚拟寄存器文件来扩展物理寄存器的限制。当下溢发生时,下溢处理程序将把当前全部寄存器的内容保存到内存,并使CWP回到HI_LIMIT位置,从而有新的空间继续开放滑窗。另外,许多嵌入式系统可能希望更为准确的控制寄存器的使用和子程序调用深度,如果遇到下溢出异常时,系统将打印出出错的信息,并退出程序,这样处理将是一种很好的选择。
程序员决定寄存器窗口下溢异常处理的操作。Nios软件开发套件(SDK)会自动安装一个寄存器向下溢出异常处理的默认程序,它用堆栈来存储虚拟寄存器文件。
寄存器窗口下溢异常只能由SAVE指令产生。通过一个WRCTL指令直接写CWP为小于LO_LIMITD的值不能产生滑窗下溢异常。当CWP已经小于LO_LIMIT时,也不能产生滑窗下溢异常事件。
(2) 寄存器窗口上溢(Register Window Overflow)
滑窗向上溢出的异常码是2。当最高有效的滑窗(CWP=HI_LIMIT)已经在使用时,执行RESTORE指令将产生一个上溢异常事件。产生滑窗上溢异常后,CPU将在执行RESTORE后面的指令之前转去执行异常处理子程序。
上溢出异常处理子程序的操作依赖于系统的要求。当系统运行庞大且复杂的代码时,上溢异常处理程序将执行一个虚拟寄存器文件来扩展物理寄存器的限制。当上溢发生时,CPU的上溢处理程序从堆栈中重载物理寄存器的所有内容,并使CWP回到LO_LIMIT位置。另外,对许多嵌入式系统可能希望精确控制寄存器使用和子程序的调用深度,如果遇到上溢出异常时,系统打印错误信息并退出程序,这样处理将是一种很好的选择。
程序员可根据具体的情况,决定滑窗上溢异常处理的具体操作。Nios软件开发套件(SDK)会自动安装一个寄存器向下溢出异常处理的默认程序,它用堆栈来存储虚拟寄存器文件。
只有RESTORE指令可以产生寄存器窗口上溢异常。通过一个WRCTL指令直接向CWP写入一个大于HI_LIMTD的值,不能产生上溢异常。而且,当CWP已经大于HI_LIMIT时,RESTORE指令也不能产生异常。
5. 直接软件异常(Direct Software Exceptions-TRAP指令)
程序可以通过执行TRAP指令,直接要求进行一段异常处理,指令的IMM6域提供
异常码。不管IE或IPRI的状态如何,TRAP指令总是被执行的。TRAP指令没有延迟槽,在控制命令跳转到指定的异常处理程序之前,紧跟在TRAP后的指令是不执行的。紧跟在TRAP指令的地址保存在%o7里,因此在异常处理进程结束时使用TRET指令可以使程序恢复到原处继续执行。
6. 异常处理流程 异常处理流程:
STATUS的内容被复制到ISTATUS;
CWP减1,异常处理子程序打开一个新滑窗(这个动作不适于向下溢出异常事
件,因为那种情况下CWP已经被产生异常事件的SAVE指令减1了); IE被置为0,禁止中断;
IPRI被置为6位的异常码(优先级);
将中断返回地址(发生中断时未执行的指令地址)传送到%o7内; 从异常处理向量表中取出异常处理程序的入口地址,并且写入PC; 在异常处理完成后,利用TRET指令便可以返回到原来的程序处。 (1) 使用寄存器窗口
所有的异常处理都在一个新开启的窗口里进行,这样处理的好处是异常处理的复杂度和反应时间大为减少,因为不需要为了保护现场而耗费时间。异常处理程序可以自由地使用新开的窗口里的%o0~%o5和%L0~%L7寄存器。异常处理程序不需要在入口处执行SAVE指令。
因为异常处理程序总是在一个新的异常处理窗口中执行,因此程序要始终为异常处理保留一个寄存器窗口。可以设置LO_LIMIT大于0以保证至少一个滑窗是有效的。例如,当程序在用完最后一个寄存器窗口时执行SAVE指令,这时将产生一个寄存器窗口下溢异常。这个下溢异常处理程序将使用由程序保留的这个寄存器窗口。
假如Nios CPU含有Nios OCI调试模块,LO_LIMIT的值默认为2。其它情况,LO_LIMIT的默认值为1。使用Nios OCI调试模块,需要设置LO_LIMIT=2,因为非屏蔽异常处理程序的执行需要使用一个寄存器窗口。例如,某个程序某个外设在CWP=2时申请中断。于是产生寄存器窗口下溢异常,CWP减为1,程序转而执行下溢异常处理程序。如果在下溢异常处理程序中,程序再次被非屏蔽异常中断,则非屏蔽异常处理程序将使用最后一个寄存器窗口。
(2) 状态保存寄存器ISTATUS 当一个异常发生时,被中断程序的STATUS寄存器的内容被复制到ISTATUS寄存器。然后STATUS被修改(IE=0,IPRI被设置,CWP减1)。而原来的STATUS内容保存在ISTATUS中,不会受到影响。当异常处理程序结束时,原来的STATUS又通过TRET指令把ISTATUS的内容重载回来。
CPU在异常处理程序入口之前自动禁止中断,因此ISTATUS是不会被一个后续中断(或异常)所覆盖的。对于中断嵌套的程序,要在重新使能中断前特别注意保护ISTATUS寄存器,并在中断禁止后恢复ISTATUS寄存器的内容。
当非屏蔽中断(TRAP0)被触发时,STATUS和ISTATUS被同时保存。保存ISTATUS
是因为非屏蔽中断可以中断一个正在执行的异常处理程序。非屏蔽中断服务程序结束后,CPU返回到异常处理前的状态,STATUS和ISTATUS又被恢复。
7. 返回地址
当异常发生时,被中断的程序临时被挂起,将要执行的下一条指令将作为中断服务程序的返回地址。返回地址在程序转去执行中断处理程序之前被保存在新开的寄存器窗口的%o7中。%o7的值是返回地址右移一位的数值,以便中断处理程序执行TRET时能够顺利返回到被中断程序。
8. 简单异常处理与复杂异常处理
Nios处理器的结构体系支持高效的简单异常处理,由硬件自身完成保护现场和恢复现场的工作。而对于复杂异常处理(比如中断嵌套),则要求有其它的保护措施。 (2) 简单异常处理
简单异常处理具有以下特征: 不能重新使能的中断;
不能使用SAVE或RESTORE(包括直接使用这两条指令,或者调用包含这两条
指令的子程序);
不能使用任何TRAP指令(或调用含有TRAP指令的子程序); 不能改变寄存器%g..%g7,或%i0..%i7的内容。
任何异常处理程序,只要符合以上规则,就不需要对ISTATUS保护,也不需要对保存在%o7中的返回地址进行特殊保护。一个简单异常处理程序不需要考虑CWP以及寄存器窗口的管理。
(3) 复杂异常处理程序
任何不满足简单异常处理原则的程序,都称为复杂异常处理程序。异常处理支持中断嵌套以及代码复杂的程序。
一个复杂异常处理程序应该有以下几个附加的要求:
必须在重新使能中断前,保存ISTATUS的内容(比如将ISTATUS的内容保存在
堆栈里);
必须在重新使能中断前,检查CWP,确保CWP是大于等于LO_LIMIT。如果
CWP 中断返回前,必须恢复ISTATUS的内容。如果已经对寄存器窗口做了特别的移 位,那么还要对CWP进行调整。 中断返回前,必须恢复被中断程序的寄存器窗口文件数据。 3.2.6 流水线 Nios CPU是采用流水线技术的RISC结构,如图3.4所示。除了分支程序延迟槽(branch delay slots)和CWP被WRCTL指令改变的情况之外,流水线的实现对软件来说是透明的。 图3.4 Nios CPU方框图 1. 直接CWP操作 每个修改STATUS寄存器(%dtl0)的WRCTL指令后面必须跟一个NOP指令。 2. 分支程序延迟槽 分支程序延迟槽定义为紧跟在BR、BSR、CALL和JMP指令后的指令。分支延迟槽指令将在程序转移之前执行,然后程序才转移目标程序处执行程序。表3.12说明了BR指令的分支延迟槽。 表3.12 BR分支延迟槽例子 在分支指令(b)被取出,指令(c)在控制指令转向分支程序前执行。上面的指令片段的执行顺序是(a)(b)(c)和(e)。指令(c)是指令(b)的分支延迟槽。指令(d)不执行。除了下面列出的指令,大部分指令都可以作为分支槽指令: BR IFRZ SKP0 TRAP BSR IFS SKP1 PFXIO CALL IFRNZ SKPRNZ JMP IF1 LRET SKPRZ RET IF0 PFX SKPS TRET 3.3 Avalon总线 3.3.1 概述 Avalon总线是一种相对简单的总线结构,主要用于连接片内处理器与外设,以构成可编程单芯片系统(SOPC)。Avalon总线描述了主从构件间的端口连接关系,以及构件间通信的时序关系。 对于Avalon总线设计的基本目标是: 简单:提供简单的、易于理解的协议。 优化总线资源利用率:节约了可编程逻辑器件的逻辑单元。 同步操作:易于与片上的其它用户逻辑集成,避免了复杂时序分析。 基本的Avalon总线传输,就是在主从外设之间传输一个字节、半字或一个字(8,16,32位)。当此次传输结束后,总线就可以在下个时钟周期进行下一次的传输,传输的主从外设可与上一次相同也可不同。Avalon总线也支持高级的特性,例如:支持需要延迟的外设,流(streaming)设备以及多主控制器操作。在单个总线传输过程中,这些高级传输模式允许外设之间传输多个数据单元。 Avalon总线提供多个主设备的操作。这种设计为构建SOPC系统提供了极大的灵活性,并且能适应高速外设。例如:一个主外设可以在进行DMA传输时,来自外设数据通过数据通道传输给存储器而不许要处理器的干预。Avalon的主从外设的交互是基于从端口仲裁技术的。当多个主外设同时访问同一个从外设时,由从端口仲裁裁定那个主外设将获得访问权。从端口仲裁有如下两个优点: 仲裁机制的细节被封装在Avalon总线内部。主、从设备的接口具有一致性,与 总线上的设备数目无关。 多个主外设可以同时在总线上进行传输,只是不能在同一个总线周期中访问同 一个从外设。 Avalon总线是为SOPC环境而设计的,互联逻辑由PLD内部的逻辑单元构成。Avalon总线具有以下基本特点: 外设接口的时钟与Avalon时钟是同步的。因此不需要复杂的异步握手/应答机制。 采用标准的同步时序分析技术就可以测出Avalon总线和整个系统的性能。 所有的信号都是高电平或低电平有效,有利于总线的切换(turn-around)。Avalon 总线内部的多路复用器决定驱动所对应的设备。外设在未选中的情况下,也不需要将输出置为高租态。 地址、数据和控制信号使用独立的专用端口,简化了外设的设计。外设不需要 地址译码和判断总线周期,在未选中时,也不需要禁止输出。 Avalon总线还包括许多其它特性和约定,以支持由SOPC Builder自动生成的系统、总线和外设,包括: 最大4GB的地址空间:存储器和外设可以映像到32位地址空间中的任意位置。 内置地址译码:Avalon总线自动产生所有外设的片选信号,极大地简化了基于 Avalon总线的设计工作。 多主设备总线结构:Avalon总线上可以包括多个主外设,并自动生成仲裁逻辑。 采用向导帮助用户配置系统:SOPC Builder提供图形化的向导,帮助用户进行 总线配置(添加外设、指定主/从关系、定义地址映像等)。编译器将根据用户在向导中输入的参数自动生成Avalon总线结构的总线配置文件。 动态地址对齐:如果参与传输的双方总线宽度不一致,Avalon总线自动处理数 据传输的细节,使得不同数据总线宽度的外设能够方便连接。 3.3.2 术语和概念 与SOPC设计相关的许多术语是全新的,有别于传统片外总线结构,所以有必要在此处集中作一介绍。值得说明的是,人们常所说的外设,指的是传统外设(例如UART、PIO、定时器),其特征是,计算机系统通过特定的端口与这些外设相连接,并实现特定的功能;而Avalon总线模块是把与它连接的所有模块都视为外设(如Nios CPU、存储器、DMA控制器、存储器控制器、以太网控制器等)。两者有别,希望读者注意这个问题。以下的术语和概念将在后文中被多次用到,它们构成了Avalon总线规范的概念框架: 1. 总线周期(Bus Cycle) 总线周期是总线传输中的基本时间单元,定义为Avalon总线时钟的两个连续的上升沿之间的时间。总线信号的时序都是以总线周期为参考基准的。 2. 总线传输(Bus Transfer) Avalon的一次总线传输定义为对数据的一次读或写操作,可能需要若干总线周期。Avalon总线支持字节(8位)、半字(16位)、字(32位)传输。 3. 流传输(Steaming Transfer) 流传输在流主外设和流从外设之间建立一个开放的通道以实现连续的数据传输。在进行数据传输时,只要数据有效就可以在主从设备间传输,而此期间,主外设并不需要不断查询从外设的寄存器状态。流传输使主从设备的数据吞吐量达到最大化,又避免外设发生数据上溢或者下溢的溢出现象。 4. 延迟读传输(Read Transfer with Latency) 延迟读传输对于那些在第一次访问时需要几个周期而接下来可以连续读取的外设。延迟传输允许主外设发出一个读请求后,转而进行其它不相关的工作,而在几个周期后继续接受数据。这对于指令获取操作和DMA传输非常有利,因为这两种操作经常访问连续的地址。有了延迟读传输这一特性,CPU或者DMA主设备就可以预取数据,并保持同步内存有效,从而降低访问的平均延迟时间。 5. Avalon总线模块(Avalon Bus Module) Avalon总线模块是外设组件之间通讯的主要通道。它由各类控制、数据和地址信号以及仲裁逻辑组成,使用它就可以将系统模块的外设连接起来。Avalon总线是可配置的总线结构,可以满足与用户外设互连的各种需要。 Avalon总线由SOPC Builder自动生成的,对于用户来讲,省去了总线和外设互连的一大部分繁琐工作。设计者通常可以把Avalon总线模块看作是连接外设的主要干线,如图3.5所示。 图3.5 Avalon总线框图 Avalon总线为外设提供以下服务: 数据通道复用(利用数据选择器); 地址译码(译码逻辑产生片选信号); 生成等待周期(加入等待周期以适应具有特殊等待要求的外设); 动态地址对齐(隐藏宽度不同的外设之间的细节); 中断优先级分配; 延迟传输功能; 流传输模式。 6. Avalon外设(Avalon Peripherals) Avalon总线的外设就是一个逻辑设备,它可以在片内也可以在片外,具有特定的功能,并能够与系统的其它组件间通讯。外设是模块化的系统组件,可以在设计时根据系统的需求添加或者删除。 Avalon外设包括存储器和处理器,还包括传统的外设,例如UART、PIO、定时器和 总线桥。用户逻辑也是Avalon诸多外设之一,只要这种外设能够按照要求提供地址、数据以及控制信号即可。连接到Avalon总线上的标准外设,都有相应地址范围或端口地址,还可以由用户对它们重新定义。 Avalon外设分为主外设和从外设。主外设是可以启动总线传输的外设,它至少包含一个连接到Avalon总线模块的主端口。它也可以包含一个从端口来接受由其它主外设对其初始化,并由其它主外设启动总线传输。从外设(如内存或UART)通常只包含从端口,用于与总线模块互连。 在SOPC环境中,区分以下两类外设十分重要。 系统模块内部的外设 如果一个外设可以在SOPC Builder的库中找到,或者用户指定了用户自定义外设的设计文件的位置,SOPC Builder便会自动找到该外设并将其连接到Avalon总线模块上,即系统模块内部模块。 系统模块外部的外设 有时将Avalon总线外设放在系统模块的外部,可能出于以下几种原因:1)外设存在于PLD芯片的外部;2)外设需要通过一些时序转换逻辑连接Avalon总线模块;3)尚未完全实现(由于资源限制,或者出于别的考虑)的外设的模块。 7. 主端口(Master Port) 主端口是主外设上用于在Avalon总线上初始化传输的连接端口。主端口直接连接到Avalon总线模块上。实际上,一个主外设可能有几个主端口和从端口。主从端口之间的依赖关系取决于外设的设计。然而,这些端口上的单个总线传输必须遵守规范要求。 8. 从端口(Slave Port) 从端口是外设中用来接受来自另一个外设主端口的Avalon总线传输的连接端口。从端口直接连接到Avalon总线模块。主外设可能也有一个从端口,来接受来自其它主设备的总线传输。 9. 主从端口对(Master-Slave Pair) 通过Avalon总线模块连接的一个主端口和一个从端口称之为主从端口对。结构上,主从端口对通过Avalon总线连接到相应的端口上。这样,主端口就可以通过Avalon总线模块来实现有效的控制和数据的传输。 10. PTF文件与SOPC Builder参数和选项(PTF File & Builder Parameters & Switches) Avalon总线和外设的配置可以在SOPC Builder的GUI界面中设定的。在GUI界面中,用户可以指定各种参数和选项,并按照这些参数和选项生成一个PTF系统文件。PTF文件是一个文本文件,它完整地定义了以下内容: 定义Avalon总线模块的结构与功能的参数 定义每个外设的结构与功能的参数 每个外设的主、从角色 每个外设提供的端口信号(例如控制信号、地址信号、数据信号) 每个可被多个主端口访问的从端口的仲裁机制 在系统生成时,PTF文件被传递给HDL生成器用来创建系统模块的寄存器传输级 (RTL)的行为描述。 3.3.2 Avalon总线传输 Avalon总线的规范,定义了主从端口之间传输数据所需的信号和时序。Avalon总线与外设接口的信号组成因传输类型的不同而有所差异。首先,主从传输因主从端口的定义不同而具有不同的接口属性。此外,每个端口的传输类型和信号线数目也不相同,它们取决于PTF文件中的定义。 Avalon总线规范提供了各种选项来剪裁总线的信号和时序,以满足不同类型外设的需求。基本的Avalon总线传输一次只能在主从端口对之间传输一个数据单元。总线传输可以调整等待时间以适应于慢速的外设,也具有流传输和多主传输方式来满足高速外设的需求,还可以是由几种外设传输类型的组合。Avalon从传输的所有信号时序都是源于从端口的基本读写传输。同样,主端口的基本读写传输也是主端口传输的基础。 1. 主端口与从端口的接口 当我们讨论Avalon总线传输时,应该注意,讨论的是总线的哪一边,是主端口还是从端口接口。 从端口的有效信号总是主外设发起总线传输的结果。但是连接到从端口的输入信号不是直接来自主端口。Avalon总线模块会对信号进行裁剪定制(例如插入等待周期,仲裁多主竞争),以满足从外设的需要。 因此,Avalon总线传输分为主传输和从传输两种类型。大多数的设计者可能只对从传输感兴趣,因为他们设计的外设绝大多数都是从外设。这样,设计者只需要考虑Avalon总线模块和定制外设之间的信号连接。而主传输的细节只有当设计者需要设计一个主外设时才需要了解。 2. Avalon总线时序 Avalon总线是一个同步的总线接口,由Avalon总线时钟驱动,所有总线传输的信号都与Avalon总线时钟同步。所有总线传输总是在时钟上升沿到达时稳定,在有效捕获到数据后的下一个时钟的上升沿终止。 同步总线接口并不意味着所有Avalon总线信号都具有锁存功能。特别是Avalon总线的片选信号是由与Avalon总线时钟同步的寄存器的输出组合而成的。由于Avalon总线的信号在稳定前可能会变化多次,所以与其相连接的外设就不能是边沿信号触发的端子。Avalon总线外设必须只对在时钟上升沿稳定的信号响应,而输出信号也在时钟上升沿稳定。 Avalon总线也可以连接异步外设,如片外异步存储器,但是有几点需要考虑清楚。由于Avalon总线模块是同步的,所有Avalon信号最多只能以与总线周期相等的时间间隔为基本时间单位来调整。而且,如果异步外设的输出直接连接到Avalon总线,设计者要确保输出信号要在总线时钟上升沿前稳定。 Avalon总线规范没有说明信号在两个时钟边沿之间是如何跳变得。对信号的锁定是由Avalon总线时钟确定的,而且信号要在时钟边沿前稳定。因此,规范中的Avalon总线 时序图忽略了精确的时间信息。时钟上升沿之间的信号跳变的实际情况取决于系统所选的PLD器件的性能。同样,Avalon总线没有固有的最高性能,系统模块在特定的器件上综合布线后,设计者必须进行标准的时序分析来确定Avalon总线的最高性能。 3. Avalon总线信号 因为Avalon总线是由HDL文件综合生成的片上总线结构,所以要特别注意Avalon总线与Avalon外设之间的互连。这与片外被动总线差别很大,片外总线是所有外设共享的一组确定的金属线。而对于Avalon总线,SOPC Builder必须知道外设是通过那个端口连接到Avalon总线模块上的。而且,它还要知道每个端口的名字和角色。而端口的名字和角色都在系统的PTF文件中定义。 Avalon总线规范不要求Avalon外设必须包含哪些端口。它只定义了外设可以包含的各种信号类型(地址、数据、时钟等)。通过为外设的每个端口分配一个有效的Avalon总线信号类型来确定外设的角色。用户也可以自己定义端口,但是SOPC Builder将不把该端口连接到Avalon总线上。通常,Avalon信号类型分为主端口信号和从端口信号。因此,外设使用的信号类型首先应由其端口的主从角色来决定。每个主从端口至少包括一个信号类型。每个主、从端口使用的信号类型,取决于外设的设计本身。例如,一个只有输出的PIO从外设只需定义用于写传输(输出方向)的信号,而不需要定义用于读传输的信号。 Avalon总线规范也没有规定Avalon外设端口的命名规则。每个端口的角色需要明确的说明,但是端口的名字需要由外设而定。端口可以依据它的信号类型来命名,也可以遵循系统的命名规范。这里,我们将信号类型作为端口名,而实际的命名可能与此不同。 表3.13列举了部分Avalon从端口可用的信号类型。信号的方向是站在从外设的角度上来定义的。 信号类型 宽度 表3.13 部分Avalon从端口信号 方向 必须 说明 系统模块和Avalon总线模块的全局时钟信 clk 1 in no 号。所有总线传输都同步clk。只有异步从端 口才能省略clk Address 1-32 In no 来自Avalon总线模块的地址信号 Read 1 in no 从端口的读请求信号 readdata 1-32 out no 读传输中输出到Avalon总线的数据线 write 1 in no 从端口的写请求信号 writedata 1-32 in no 写传输中来自Avalon总线模块的数据线 irq 1 out no 中断请求 4. 并发多主端口的Avalon总线需考虑的事项 Avalon总线支持多主端口互连,而不需要额外的信号线。当多个主外设同时访问同一个从外设时,由Avalon总线模块中的从端口仲裁来仲裁冲突。当多个主外设试图同时 访问同一个从外设时,Avalon总线模块内部的从端口仲裁逻辑会被用来解决冲突。仲裁机制对于Avalon总线的外设是完全透明的。因此,对于主从端口来说,无论是否仲裁,总线传输的协议是一致的。从端口不会知道有多个主端口试图同时访问,同样,处于被迫等待的主端口也不会意识到另一个竞争获胜的主端口是谁,而只是等待,一直持续到等待信号取消。隐藏仲裁细节可以大大简化外设的设计工作的复杂程度。值得指出的是,任何Avalon外设都可以用于单主和多主结构。 3.3.3 Avalon从端口传输 本节从一个抽象的、系统级的角度来说明主外设与从外设之间的数据交换过程。从外设的角度来看,数据传输发生在外设的从端口和Avalon总线模块之间。本节讨论的焦点仅限于Avalon总线模块和从端口之间的接口问题。 1. 从传输的Avalon总线信号 表3.14列举了外设的从端口与Avalon总线接口之间的信号类型。信号的方向是站在从端口的角度来定义的。外设需要的信号由外设的设计和PTF文件中的信号定义决定的,不需要包含全部的信号类型。 表3.14 Avalon从端口信号 信号类型 宽度 方向 必须 说明 系统模块和Avalon总线模块的全局时钟信号。所有总 Clk 1 in no 线传输都同步clk。只有异步从端口才能省略clk Reset 1 in no 全局复位信号 chipselect 1 in yes 从端口的片选信号 Address 1-32 in no 来自Avalon总线模块的地址信号 begintransfer 在每个新的Avalon总线传输的第一个总线周期期间 1 in no 有效 字节使能信号,在访问宽度超过8位的存储器时选择特 bytenable 0,2,4 in no 定的字节段 Read 1 in no 从端口的读请求信号 readdata 1-32 out no 读传输中输出到Avalon总线的数据线 Write 1 in no 从端口的写请求信号 writedata 1-32 in no 写传输中来自Avalon总线模块的数据线 readdatavalid 1 out no 读取数据有效信号,仅用于具有可变读延迟的从端口 等待请求信号,当从端口不能立即响应时暂停Avalonwaitrequest 1 out no 总线模块 readyfordata 1 out no 流传输模块信号。表示流模式从端口可以接收数据 dataavailable 1 out no 流传输模块信号。表示流模式从端口拥有有效数据 endofpacket 1 out no 流传输模式信号。用于向主端口报告“包结束”状态 Irq 1 out no 中断请求 复位请求信号,该信号使得一个外设可以复位整个系统resetrequest 1 out no 模块 在信号名称后添加“_n”表示低点平有效,否则都是高电平有效的。如:read信号为高电平有效, read_n为低电平有效。 2. 从端口读传输 (1)基本的从端口读传输 基本的从端口读传输是指没有等待状态的传输,它是所有Avalon总线从端口读传输的基础。其他的读传输模式都是在基本传输基础上的扩展,并对基本读传输的时序做了一定的修改。基本从端口读传输由Avalon总线发起,并从从端口向Avalon总线模块传输一个数据单元,数据的宽度等于外设数据端口的宽度。 图3.6显示了这一过程。总线传输开始于一个时钟上升沿,并在下一个时钟上升沿结束,不插入等待周期。由于传输在一个时钟周期内完成,目标外设必须能够迅速的以异步方式向Avalon总线模块输出相应地址中的内容。 在clk的第一个上升沿,Avalon总线向目标外设传递address、byteenable_n和read_n信号。Avalon总线模块内部对address进行译码,产生片选并驱动从端口的chipselect信号。一旦chipselect信号有效,从端口在数据有效时,就会立即驱动readdata输出。最后,Avalon总线模块在下一个时钟上升沿捕获readdata。 图3.6 基本从端口读传输 无等待周期的基本读传输,只适用于完全异步的外设。目标外设必须在外设被选中或地址变化时立即向Avalon总线提供数据。为了正确传输,readdata的输出在第二个时钟上升沿必须有效且稳定。锁存输入或输出端口的同步外设不能使用无等待周期的基本从端口读传输。大部分片上设备都采用同步接口,因此至少需要一个时钟周期来捕获数 据。在读传输中,需要至少一个等待周期,除非该外设是具有延迟的。 (2)有固定等待状态的从端口读传输 具有固定等待周期的从端口读传输与基本读传输的信号相同,只是信号的时序不同。该传输模式适合于不能在一个周期内提供数据的慢速外设。 图3.7显示了具有一个等待周期的从端口读时序。Avalon总线在第一个总线周期生成address、byteenable_n、read_n和chipselect信号。由于存在等待周期,外设不需要在第一个总线周期提供数据信号。第一个总线周期是第一个(也是唯一一个)等待周期。从端口可以在任何时刻捕获地址和控制信号。片上的同步外设通常在第二个总线周期开始时的clk上升沿捕获地址和控制信号。在第二个周期期间,目标外设向Avalon总线提供readdata。在第三个时钟上升沿,Avalon捕获readdata并结束总线传输。 图3.7 具有一个固定等待周期的从端口读传输 (3)由外设控制等待状态的从端口读传输 外设控制等待周期的传输允许目标硬件延迟任意个总线周期。采用这种传输方式,外设可以在不同的时间向Avalon总线提供数据。 图3.8显示了这个情况,外设控制等待周期的读传输模式使用了waitrequest信号,它是从端口的一个输出信号。当从端口的read_n信号有效后,从端口若要延长读传输,它必须在第一个总线周期内返回waitrequest(置1使之有效),Avalon总线模块发现waitrequest有效,便会处于暂停(stall)状态,直到waitrequst失效后的下一个clk上升沿才捕获readdata。 Avalon总线模块没有超时机制来限制从端口延迟总线的最长时间。当Avalon总线被暂停时,同时系统中还将有一个等待返回数据的主设备被暂停。这样,从端口可以永远的挂起主端口。因此,设计者外设时必须确保从外设不会生成不确定的waitrequest信号。 对于使用了外设控制等待周期的端口,该端口的其他传输模式可能会受到限制。比如无法再使用建立和保持时间。大多数情况下,产生waitrequest信号的外设是片上的外设,因此不需要考虑建立和保持时间。 图3.8 具有外设控制等待周期的从端口读传输 (4) 有建立时间的从端口读传输 按照PTF文件中的说明,Avalon总线能够自动的调整建立时间以满足需求。主外设初始读传输时不需要考虑从外设的建立和保持时间要求。有建立时间的端口的读传输信号线与基本读传输相同,只是信号的时序有差异。 这种传输方式通常用于一些片外外设,这些外设需要address和chipselect信号,他们必须在读使能信号产生前保持稳定一段时间。一个建立时间N意味着address和byteenable_n和chipselect信号产生后,还需要N个总线周期才能生成read_n信号。要注意,chipselect不受建立时间的影响。如果外设的read_n和chipselect都需要建立时间,设计者必须自己为接口添加相应的逻辑。 在这种方式下,进行一次总线传输所需的总线周期个数取决于建立和等待状态的周期个数。例如,一个外设的参数是Setup_Time=2,Read_Wait_States=3,这样一次传输就需要6个总线周期:2个总线周期的建立时间,3个总线周期的等待周期,1个总线周期来捕获数据。图3.9显示了具有一个总线周期的建立时间和一个固定等待周期的从端口读传输时的情况。 如果某个外设同时具有读和写传输并且指定了建立时间,那么读和写传输就具有相同的建立时间。如果外设使用了外设控制等待周期,将无法再使用建立时间。 图3.9具有一个总线周期的建立时间和一个固定等待周期的从端口读传输 3. 从端口写传输 从端口写传输与从端口读传输是类似的,这里不再赘述。需要注意的是,读传输数据线是readdata,写传输数据线是writedata,两数据线是相互独立的,这与传统总线不同。 3.3.4 Avalon主端口传输 本节讨论主端口和Avalon总线之间的总线传输问题。从抽象的系统级的角度来看,主端口传输的数据交换发生在主、从外设之间。然而,从主外设的角度来看,数据传输发生在主端口与Avalon总线模块之间。如果主外设访问的地址不在Avalon总线已定义的外设地址范围内,便会产生未知结果。从端口的存在并不影响主端口与Avalon总线之间的接口。原因是Avalon总线模块而不是主端口对相应的从端口启动和终止从传输。因此,下面主要讨论是主端口和Avalon总线之间的接口问题。 Avalon从端口的传输模式较多,而主端口传输模式则相对较少也较简单。下面的讨论中假定Avalon主外设是同步片上模块。 主传输有一条原则:生成所有的启动总线传输信号,然后等待直到Avalon总线撤消了waitrequest信号。有了这条原则,在熟悉基本的从端口读写传输的基础上,就不难理解主端口传输的接口了。 1. 主传输的Avalon总线信号 表3.15列举了外设主端口与Avalon总线模块接口的所有信号,并给出了简要的描述。外设提供的信号取决于外设的设计和外设的PTF文件中所描述的端口特征,而不需要包含全部的信号。 表3.15 Avalon主端口信号 信号类型 宽度 方向 必须 说明 系统模块和Avalon总线模块的全局时钟信号。 clk 1 in yes 所有总线传输都同步clk。 reset 1 in no 全局复位信号 Address 1-32 out yes 输出到Avalon总线模块的地址信号 字节使能信号,在访问宽度超过8位的存储器时bytenable 0,2,4 out no 选择特定的字节段 read 1 out no 主端口发出的读请求信号 readdata 8,16,32 in no 读传输中来自Avalon总线的数据线 write 1 out no 主端口发出的写请求信号 writedata 8,16,32 out no 写传输中输出到Avalon总线模块的数据线 waitrequest 1 in yes 迫使主端口等待 irq 1 in no 从外设发出的中断请求 irqnumber 6 in no 发出中断的从端口的中断优先级 流传输模式信号。用于从端口向主端口报告“包endofpacket 1 in no 结束”状态 readdatavalid 仅用于延迟读传输模式。指示从端口在 1 in no readdata信号上提供了有效的数据 延迟读传输模式信号。主端口通过设置该信号能 flush 1 out no 够清除所有挂起的延迟读传输 2. Avalon总线上基本的主端口读传输 在基本的主端口读传输模式的情况下,主端口在时钟的上升沿产生有效的地址和读请求信号,来启动总线传输。理想情况下,读取数据在下一个时钟周期的上升沿之前返回,这样只需要一个总线周期就可以结束读传输。如果数据在下一个时钟上升沿前还未就绪,这时Avalon总线就会产生一个等待请求信号来暂停主端口的读传输,直到从被寻址的从端口获得数据为止。值得注意,基本的主端口读传输不存在延迟问题。 主端口读传输开始于clk上升沿,在第一个clk上升沿之后,主端口立即使address和read_n有效。如果Avalon总线模块不能在第一个总线周期提供readdata,它会在下一个clk上升沿之前使得waitrequest有效。如果主端口在clk上升沿时发现waitrequest有效,它将会等待。主端口必须在waitrequest撤销后的下一个上升沿之前保持所有数据的稳定。在waitrequest失效后,主端口在下一个clk上升沿捕获readdata,并使address和read_n失效。总线可以在下一个总线周期立即启动另一次总线传输。见图3.10。 图3.10 Avalon总线上基本的主端口读传输 3. Avalon总线上基本的主端口写传输 主端口写传输与从端口读传输是类似的,这里不再赘述。 3.3.5高级Avalon总线传输 本小节介绍高级Avalon总线传输模式,包括带延迟的总线传输、流传输,还要介绍Avalon总线控制信号。我们只对这几种传输模式做简要的介绍,读者如果想了解详细内容,可参阅相关文档。 1. 带延迟的Avalon总线传输 有一些同步外设,在第一次访问时需要几个周期的延迟,但在随后的每个周期都可以返回数据。对于这类外设,带延迟的Avalon总线传输模式就能够有效的提高数据传输的速度。这种传输模式只用于读传输,而写传输则没有带延迟模式,原因是读传输不需要从端口的应答信号。在带延迟的Avalon总线传输模式中,允许主外设先发送一个读请求,在进行一段时间的其他不相关的工作后,再接收数据。这一处理过程通常称作“posted reads”。在不相关的工作中,即使第一次传输的数据没有返回,也可以进行其他的读传输。这一特性模式最适合于CPU获取指令传输和DMA读传输。显然,正是这一特性,使得CPU或DMA主外设可以预取数据,然后保持同步内存的有效工作,从而降低平均访问时间。 2. 流传输 流传输模式就是在流主外设和流从外设之间开设一个通道来进行连续的数据传输。有了这样的传输通道,就可以在主从端口对之间实现数据的高速传输,而不需要主外设不断查询从外设的状态寄存器来判断从外设是否可接收或发送数据。流传输模式是主从对之间的吞吐量达到最大,且避免了从外设端数据的上溢和下溢问题。 流传输模式中,从外设只需要为主外设提供简单的流控制信号,而由Avalon总线模块自动传输数据。流传输模式省略了主外设查询从外设状态寄存器的繁琐操作,这样就降低了设计主外设的复杂程度,因为它只有简单的智能功能。例如DMA控制器,它只需要简单的流控制信号和计数器实现从外设与寻址递增的存储器之间的数据传输。 3.Avalon总线控制信号 Avalon总线模块提供一些控制信号来实现一定的系统级的功能。 (1)中断请求信号 大多数的微处理器需要中断产生逻辑和优先级分配逻辑。系统模块为连接到Avalon总线上的外设和处理器提供了这样的服务。 当外设希望产生中断时,每个从端口都可用irq输出信号来申请中断服务。Avalon总线规范没有规定中断请求生成的原因和时间。中断信号的时序与总线传输无关,它可以在任意时刻发出中断请求。大部分情况下,从外设需要保持中断请求信号有效,一直到主端口将中断请求复位为止。设计者必须事先为每个从端口所使用的irq分配预定的优先级别,有关系统中的优先级别的分配情况,可以在系统的PTF文件中清楚的看到。irq的值越低,中断优先级越高,irq0就具有最高的中断优先级。 主端口一般有两组数据信号来处理中断请求:irq和irqnumber。系统中所有从端口的中断请求输出信号通过或逻辑后连接到主端口,这样任一从端口都可以产生中断。Avalon总线的内部逻辑向6位irqnumber总线提供优先级最高的中断的译码值。如果有多个主外设使用irq和irqnumber信号,每个主端口将获得相同的值。Avalon总线规范没有规定主外设应该在何时和如何相应中断。多数情况下,主外设需要响应中断并向发出中断请求的从外设发出中断源复位信号。 (2)复位控制逻辑 系统只有一个复位信号输入端,通过用户逻辑的扩展,就可以用多个复位信号的组合对系统模块和外设实现复位。这个全局复位信号与系统中其他复位逻辑一起,为需要复位的Avalon外设分配复位信号。每个Avalon外设将根据设计时的要求来响应(或忽略)复位的请求。 系统中可能存在三种情况导致系统复位: PLD重新配置:PLD完成配置后,Avalon总线检测状态并生成至少一个时钟周 期的复位信号; 外部输入全局复位信号; Avalon从外设发出复位请求信号。 通常,外设在复位信号产生后的几个时钟周期内将进入复位状态。复位信号的时序与总线传输无关,它可以在任何时刻产生。 从端口可以通过复位请求信号来强迫整个系统复位。中断请求信号对于看门狗之类的外设非常有用。要注意,复位请求信号与IRQ不同,复位请求信号将导致系统中所有使用复位信号的外设立即复位,而他们正在进行工作都将伍条件的被终止。 (3)启动传输信号 从端口的begintransfer输入信号提供一个易于理解的指示,表明启动一个新的Avalon从端口传输。按照Avalon总线规范规定,外设必须按照一定的次序来接收Avalon总线信号。对于直接连接到Avalon接口上的逻辑,判断从传输的开始是比较困难的,因为address、read enable、write enable和chipselect信号不一定在传输开始时就变化。因此Avalon总线在从传输的第一个总线周期产生begintransfer信号。 Begintransfer信号在模拟Avalon传输时是一个有用的调试信号,不紧如此,它还简化了外设的设计难度。 3.3.6 片外设备与Avalon总线的接口 Avalon三态接口用于将片外设备通过PLD的I/O引脚直接连接到Avalon总线模块上。PTF参数Bus_Type=“avlon_tristate”用来指定一个使用Avalon三态接口的片外外设。大多数系统需要与一些片外存储器接口,片外存储器通常共享PCB上的地址和数据总线。因此具有双向并可三态的数据接口是非常必要的,它允许多个总线器件分时驱动数据总线。Avalon三态接口规定了一个可以连接简单的片外从设备(如flash、SRAM、SSRAM等)的接口。但是,一些Avalon总线的传输模式是片外设备无法使用的。 Avalon三态接口只能用于连接片外从外设。片外从外设可以使用外设控制等待状态传输,也可以使用固定的建立、保持时间和等待状态。Avalon三态接口不支持片外主外设。设计者可以在片上设计用户逻辑实现Avalon总线与片外主外设的桥接。 1. 从端口传输的Avalon三态信号 表3.16列出了片外从设备与Avalon总线模块接口的信号类型。信号方向是以从设备的角度描述的。并不是所有的外设都需要全部包括这些信号类型,这取决于外设的设计参数以及外设的PTF文件中对端口的定义。 表3.16 片外从设备与Avalon总线模块接口的信号类型 Avalon三态接口使用的是双向端口data,而不是独立的readdata和writedata。当Avalon总线驱动数据端口进行写传输时,从外设捕获数据。当进行读传输时,从外设驱动数据端口,Avalon总线捕获数据。数据端口是双向的,从外设(Avalon总线也是)只能在特定的时间驱动数据端口。 Avalon三态接口中引入了共享端口的概念。共享端口用来减少Avalon总线和外部设备之间的连接信号线的数目。在PTF文件中,用Is_Shared来声明共享端口。共享端口可以连接到多个片外从设备。数据端口总是共享的,地址、读写信号线也可用来共享。如果几个外设使用同一类型的端口,那么这个端口可以声明为共享的。Avalon三态从设备必须根据chipselect和outputenable信号在特定的时间内做出响应。 使用Avalon三态接口,必须使用chipselect信号选中外设。片外外设只有在chipselect有效时才能进行数据传输。Chipselect不可能是共享信号,一个chipselect信号驱动一个特定的片外外设。 Avalon三态接口还引入一个新的信号类型outputenable来进行从端口读传输。为避免数据总线冲突,片外从外设只能在outputenable有效时驱动数据总线。Outputenable主要用于带延迟的片外存储器外设(比如SSRAM,它只能在读传输启动了几个时钟周期后才能驱动数据总线)。 2. 不带延迟的Avalon三态从端口读传输 在与异步片外存储器外设(SRAM和flash)的数据传输中,不带延迟的Avalon三态从端口读传输是最常用的模式。基本的Avalon三态从端口读传输与基本的Avalon从端口读传输(不是三态)基本一致,唯一的区别是数据信号双向的。片外从外设只有在outputenable有效时,才能驱动数据总线。不带延迟的Avalon三态从端口读传输支持固定建立时间和等待状态(包括外设控制等待状态),时序与非三态传输相同。 大多数情况下是直接将chipselect_n信号连接到外部存储器的片选端,将read_n信号连接到输出允许端。Outputenable_n可以用来驱动外设的输出允许端,因为对于无延迟的从端口传输,outputenable_n信号与read_n是相同的。 一些存储器器件还有一个复用的R/Wn(读为高,写为低)管脚。Avalon三态write_n信号可连接到R/Wn管脚,因为write_n只在写传输时有效,其他时候为无效状态。图3.11给出一个有固定建立时间和固定等待状态的Avalon三态从端口读传输。 图 3.11有固定建立时间和固定等待状态的Avalon三态从端口读传输 带有固定延迟的Avalon三态从端口读传输 带有固定延迟的Avalon三态从端口传输,常常用来连接片外同步存储器设备,如SSRAM和ZBT SRAM等等。注意:片外设备没有变化的延迟时间支持。 带有固定延迟的Avalon三态从端口读传输与带有固定延迟的Avalon从端口读传输基本一致,唯一区别是双向数据端口。从外设只能在outputenable有效时才能驱动数据总线,而其他时间,数据必须是三态。因为有效数据要在几个周期的延迟后再能返回,outputenable信号要在地址信号稳定后有效。这种方式支持固定的建立、保持时间和等待状态,时序与非三态的情况一样。 因为这种传输方式在平时的设计中使用较少,我们不再详细介绍,有兴趣者可以参阅相关文档。 4. Avalon三态从端口写传输 Avalon三态从端口写传输模式用于Avalon总线与片外可写存储器件的连接,如SRAM、SSRAM和flash。基本的Avalon三态从端口写传输与非三态方式大致相同,唯一区别是用双向数据端口取代了writedata输入端口。支持固定的建立、保持时间和等待状态,也支持外设的等待状态控制,时序与非三态模式相同。和非三态写传输模式一样,三态写传输也不具有延迟模式。 即使某个从外设不进行传输,数据端口也可以被其他不相关外设驱动。因此,片外设备只能在片选信号有效时才能捕获数据,而且设备在写传输过程中不能驱动数据信号线。 设计者可以直接将Avalon的write_n信号连接到设备的写使能管脚。一些同步存储器件,有一个复用的R/Wn管脚,write_n也可以与它直接连接。此外,一些同步存储器在写传输时使用字节使能信号(BWn)来指定写那个字节。Avalon端口writebyteenable信号是write和byteenable的逻辑与,可以直接连接到BWn管脚。 Avalon三态接口不支持延迟写传输。但是,Avalon三态从端口写传输仍然能成功的向片外同步存储器件(如SSRAM和ZBT RAM)写入数据。例如,可以利用保持状态使数据总线在write信号撤销后仍然保持有效几个周期。Avalon总线等待每一个正在进行的带延迟的读传输完成后,才可以开始新的写传输。这样就防止了延迟读与写之间可能存在的数据总线冲突。值得说明的是,在这种模式时,很有可能会使Avalon总线接口在执行读写操作时的传输速率下降。尽管如此,连续的高速读和高速写模式还是支持的。 图3.12给出一个有固定建立时间和保持时间的Avalon三态从端口写传输的例子。其中,address和bidirectional data信号是共享的。Write_n,chipselect_n以及outputenable_n低有效,可以与绝大多数外部存储器件相匹配。Outputenalble_n和clk只是个参考。 3. 图3.12 有固定建立时间和保持时间的Avalon三态从端口写传输 3.3.7 Avalon总线地址对齐方式 1. 地址对齐概述 Avalon总线可以连接不同数据宽度的主从外设,它们可以不匹配。例如,32位主端口可以访问8位从端口,16位主端口也可以访问32位从端口。如果系统中存在数据宽度不匹配的主从对,那么就需要使用地址对齐的方式来处理。这种情况几乎存在于所有的微处理器系统,而不仅仅是Avalon总线。 讨论数据宽度不同的主从之间的传输,首先要弄清那个外设的数据端口更宽,也就是数据位数多。为了讨论方便,我们用宽端口、窄端口来命名两种端口。 当一个宽的主端口访问窄的主端口,将产生如下问题:当宽的主端口从从端口读取数据时,如何处理最高位(MSBs)的问题,Avalon总线提供了两种解决途径: 静态地址对齐方式 当一个主端口的传输只对应一个从端口的传输时,就可以使用静态地址对齐方式。例如,一个32位主端口读一个16位从端口时,Avalon总线返回一个32位的数据,但是只有低16位是有效的,高位数据为零或不确定。静态地址对齐方式适合很多的嵌入式系 统应用。但是,设计者必须知道相关从端口的数据宽度和寻址机制,这就增加了主外设设计的复杂性。 动态地址对齐方式 使用动态地址对齐方式,宽的主端口从窄的从端口读一次数据,从端口与Avalon总线之间进行几次数据传输。例如,当一个32位的主端口读8位的从端口,Avalon总线返回一个32位字,数据字中包含4个从端口的有效字节数据。动态地址对齐方式抽象了从端口的物理细节,使主外设每次传输都觉得从外设与自己的数据宽度一样。动态地址对齐方式简化了主端口的设计过程。 一般存储器外设使用动态地址对齐方式,其他外设使用静态地址对齐方式。 当然,还有窄的主端口连接到宽的从端口情况。这种情况下,Avalon总线自动从从端口取32位数据,并提取相应的半字给16位的主端口。这些处理逻辑集成在Avalon总线中,以实现16位主端口到宽的从端口的连接。 2. 选择Avalon外设的地址对齐参数 地址对齐的参数是在系统的PTF文件中定义的。每个连接到Avalon总线的从端口都分配好了地址对齐方式(静态和动态)。地址对齐方式不能应用于主端口,主端口只接收与主端口等宽的数据单元;从端口的地址对齐方式决定了主端口以何种方式来处理数据。 对于指令存储器或数据存储器,都应该使用动态地址对齐的访问形式。从系统级角度看,动态总线宽度有以下几个优点: 32位和16位处理器都可以可以使用价格便宜的8位或16位的数据存储器和程 序存储器。如果没有动态地址对齐的访问形式,处理器将无法执行比指令宽度窄的存储器中的代码。 存储器的物理宽度对于软件是透明的。 节省程序指令,但不并降低程序的执行速度。因为不需要进行读和移位之类的 处理来组成宽的数据单元。 静态地址对齐方式适合于其它的所有外设。而动态地址对齐方式不适合那些由映射到存储器空间的寄存器控制的外设,这些外设直接受对特定寄存器读和写操作的影响。处理器通常一次只能访问外设的一个寄存器。通过读写一个特定的寄存器来完成控制,但不会影响其他寄存器原有的内容。 有时候,外设同时含有控制寄存器和存储器空间。这种情况下,有两种解决方案来分配相应的地址对齐方式。第一,一般这种外设是为某个特殊的嵌入式系统而设计的。设计者可以使外设与主端口的数据宽度匹配。如果从端口的数据宽度与主端口相同或者更宽,那么地址对齐方式就不是问题了。第二,如果外设已经设计好了,无法再修改使之与主端口匹配,那么就需要一些接口逻辑来合并两个Avalon从接口。一个从端口使用静态地址对齐方式来寻址外设寄存器,另一个从端口使用动态地址对齐方式来寻址内存空间。 3.3.8 连接到外部设备 对于只使用系统模块内部的外设的系统来说,设计者不需要考虑Avalon外设与Avalon总线连接的细节。但是,大多数系统都需要与片外存储器连接,系统设计者就必须将系统模块外的设备(包括片外设备)连接到Avalon总线上去。而且,多数系统将Avalon信号通过一个三态桥连接到片外设备,这样多个片外设备可以共享地址和数据总线。这种连接方法与传统的在PCB上布线的总线方式类似,但并不一定能够清楚的看出地址总线与外设地址管脚之间的连接关系。尤其是,当共享总线的外设有不同的数据宽度并使用不同的数据对齐方式时,情况将变得更加复杂。 下面的讨论中,A[0]代表从外设地址总线的最低位。每个从外设的A[0]都不是一定要连接到Avalon地址端口的最低位。连接方式取决于PTF文件中定义的从外设的地址对齐方式。请注意,存储器外设总是使用动态地址对齐方式,且Avalon地址端口是可以按字节寻址的。表3.16列举了连接片外设备的A[0]信号线到Avalon地址端口的情况。 表3.16 连接片外设备的A[0]信号线到Avalon地址端口的情况 当将数据宽度窄的从外设连接到宽的Avalon数据端口时,从外设的数据总线最低位应连接到Avalon数据端口的最低位。 3.4 外设的组织与使用 3.4.1 SOPC Builder与PTF文件 1. SOPC Builder的组成 从一个仅只进行基本功能开发的用户角度来看,SOPC Buider是一个能够生成硬件子系统的集成化工具。SOPC Builder包括两个主要的组件: 图形用户接口(GUI) 系统生成器 图形用户接口提供了图形化管理IP模块,配置系统,报告错误等功能。用户对系统设置的改动都存储在系统的PTF文件里。SOPC Builder的图形用户接口,实际上就是一个系统PTF文件的编辑器。 用户点击Generate按钮,就可以生PTF文件。通常,这是SOPC Builer图形用户接口的最后一步。系统生成器虽然由图形用户接口启动,但是它是一个完全独立的程序。系统生成器负责创建所有SOPC Builder的输出文件。组件关系和各自的作用,如图3.13所示。 图3.13 SOPC Builer组件关系和各自的作用 系统生成程序的执行需要从PTF文件读取系统配置信息和参数。PTF文件是图形用 户接口和系统生成程序之间唯一的交互渠道。PTF文件中的内容,一部分用于图形用户接口(显示IP模块的信息),一部分用于系统生成程序(总线时序信息)生成系统互连逻辑。其他PTF文件的数据同时用于图形用户接口和系统生成程序。 2. PTF文件的两种类型 SOPC Builder使用两种类型的PTF文件: 系统PTF文件 系统PTF文件存储着系统的设计信息。在系统设计创建时,SOPC Builder都会自动地创建一个相应的PTF文件。PTF文件总是反映用户当前系统的完整信息,因此,一旦用户因某种需要更改了设计方案,系统都会及时地更新PTF文件。 Class.ptf文件 Class.ptf描述SOPC Builder库组件,每个库组件对应一个相应的Class.ptf文件。如 果希望为SOPC Builder库创建自己的IP模块,就需要创建一个Class.ptf文件。 如果只是从事简单的系统设计,那么不需要创建自己的库组件,也就不需要了解PTF的详细内容,也不需要了解SOPC Builder的复杂流程。但是,当从事复杂的系统设计时,就需要了解这些内容了(具体内容可以参阅相关的技术文档)。 3.4.2 定时器 NIOS 定时器模块是NIOS开发工具包中的一个库组件。定时器可用于周期脉冲发生器或看门狗定时器。 4. 功能描述 NIOS计时器模块是一个32位的内部定时器。用户可通过控制一组寄存器中相应的位来选择定时器的工作模式,决定是否申请中断等;通过相应的寄存器设置定时器时间初值;在定时器工作的任意时刻,还可以读取定时器的当前值。 用户通过如下方式控制定时器: 通过periodl 和periodh寄存器设置定时器初值; 通过设置控制寄存器的start位和stop位,来启动和停止内部计时器; 通过设置控制寄存器的中断使能(ito)位来开启或禁止中断; 通过设置控制寄存器的连续运行位,来设定运行模式(连续计时或单次运行)。 定时器的初值由两个16位寄存器periodl 和periodh构成。这样的直接好处,就是即适合于16 位的CPU,也适合于32位的CPU使用。对于前者,自然不成问题。对于后者,即32位NIOS CPU是通过对2个独立的写操作访问16位寄存器(periodl和periodh)来设定一个32递减计数初值。 无论何时,用户都可以读取定时器的当前值。对snapl或snaph寄存器进行写操作,系统就将当前定时器的值复制到snap寄存器中。然后,用户通过读取snap寄存器就可获得当前定时器的值。定时器由系统主时钟驱动,也就是说,定时器、Nios CPU、外设使用的是同一个时钟源。 定时器还可以配置为看门狗定时器。看门狗定时器的输出reset_output可以作为系统复位逻辑的输入信号。 5. 定时器的寄存器 表3.16列举并描述了timer寄存器。 表3.16 定时器寄存器 状态寄存器 to 位:内部计时器为0时,to 位置为1。该位被超时事件置1后,只能通过软件清零。软件通过向状态寄存器执行写操作来清零to 位。 run位: 内部计时器运行时,run 位为1,否则为0。软件通过写控制寄存器的start或stop位来启动和终止内部计时器。对状态寄存器的写操作不改变run 位的值。 控制寄存器 ito 位 :如果ito 位为1,当状态寄存器的to 位为1(定时器溢出)时,计时器会发出一个中断请求。如果ito 位 为0,将不会产生中断。 cont 位:当内部计时器为0时,不论cont 位为何值,它都重新载入preiodl 和preiodh寄存器中的32位值。如果cont 位为1,定时器将连续计数,只有写stop位才会停止计时。如果cont 位为0,重新载入初值后,定时器停止计数。 start 位:程序通过向start 位写1来启动计时器。Strat 位是一个事件位——当对它 执行写操作时,计时器开始计时。而start 位对定时器的操作没有后续的影响,向start位写入0是无效的。如果定时器已经被暂停,向start位写入1将使定时器从当前值继续计数。 stop 位:对stop位写1可使定时器停止计时。Stop 位是一个事件位——当对它执行写操作时,计时器停止计时。存储在stip位的值对内部计时器不会产生后续影响。对stop位写0是无效的。如果PTF参数always_run设为1时,对stop 位的写操作则不会对内部定时器起任何作用。注意:如果同时对start 位和stop位写1,则结果不定的,应当避免这样的情况。 Periodl和Periodh寄存器 Periodl和Periodh分别存储32位计数初值的低16位和高16位。定时器的实际周期(即溢出速率,如果允许申请中断,则也可视为中断速率)比periodl和periodh寄存器中的初值大1,因为定时器认为0(0x00000000)也需要一个时钟周期。 内部定时器在下列情况下装载periodl和periodh中的32位的原始值: ——对periodl或periodh执行写操作 ——内部计时器值达到0。 当程序对periodl和periodh进行写操作时,定时器将自动停止计时。 注意:当PTF参数fixed_period为1时,写periodl或periodh寄存器会以原来的初值重启定时器计数;当PTF参数always_run为1,写periodl或periodh寄存器不会停止内部定时器。(always_run为1时,定时器的停止不受程序控制)。 Snapl和snaph寄存器 snaph和Snapl寄存器分别存储32位定时器瞬时值的高16位和低16位。通过对snaph或snapl的写操作,可以使snaph和snapl装载定时器当前值。然后,读取snaph和snapl就可以得到定时器当前值。无论定时器运行与否,对snap寄存器的写操作都可以获得定时器的快照,而对定时器的运行没有影响。 6. 看门狗定时器操作 通过设置PTF参数可以将定时器配置成一个看门狗。 Reset_output=“1”; Always_run=“1”; Fixed_period=“1”; 看门狗定时器在系统复位时处于休眠状态(不工作)。程序可以对控制寄存器的start位写入1来启动定时器。看门狗定时器一旦启动,就永不会停止。当计时器为0,它将会产生一个复位信号。 如果系统使用看门狗定时器,reset_out输出将导致系统复位。可以通过写periodl或periodh寄存器来重启内部定时器(写入数据忽略不计)。只有不断的重新启动看门狗计时器,才能防止系统复位重启。如果由于其它原因,导致程序跑飞而不能的重新启动看门狗计时器,系统就会被看门狗产生的复位信号强制复位。 7. 数据结构和示例程序 int nr_timer_milliseconds(void) 如果系统定义了一个或多个定时器,就可以使用nr_timer_milliseconds函数。该函数在头文件nios.h中声明。 Nr_timer_milliseconds程序需要一个名为timer1的定时器,此定时器的基地址由nr_timer1定义,并有na_timer1_irq定义中断码。子程序第一次调用时,它将为Timer1安装一个中断服务程序,并返回0。后面再调用该子程序,程序返回距离第一次调用的时间(以ms为单位)。 3.4.3 并行输入输出口 Nios并行输入/输出(PIO)模块作为SOPC Builder库中一个组件,包含在Nios的开发套件当中。它是1到32位的并行I/O口(包括输入、输出、边沿捕获),具有许多配置选项,用户可以利用它在Nios开发板上定义设备逻辑,接口信号等。 8. 功能描述 并行输入/输出(PIO)模块可以看成是软件和用户逻辑之间的映像接口。它有两个主要的用途: 为软件和片上用户逻辑提供接口 为软件和片外用户逻辑提供接口 图3.13列举了三种不同的PIO配置方式: 图3.13 三种不同的PIO配置方式 9. PIO寄存器 表3.17列举并描述了PIO寄存器。 表3.17 PIO寄存器 数据寄存器(data) 对于仅只作为输入的PIO(当PTF参数has_out和has_tri都置零)端口来说,写数据寄存器(data register)是无效的;同样,对于仅只作为输出的PIO(当has_in和has_tri都置零)端口来说,从数据寄存器中读取数据是没有无效的。 方向寄存器(direction) 当PTF参数has_tri置1时,PIO的输入输出口线在三态控制下共用一个管脚。方向寄存器则控制每个PIO位的数据方向。当方向寄存器置1,输出数据;方向寄存器置0时,输入数据。在系统复位时,所有方向寄存器位都置0,即缺省为输入方式。当PTF参数has_tri为0时,PIO模块不包含该寄存器。 中断掩码寄存器(interruptmask) 中断标志寄存器中的某一位置1时,与其相应的PIO端口允许中断。PTF参数 irq_type决定PIO的如何产生中断。PTF中irq_type为NONE时,该寄存器不存在,因为没有中断需要使能。 在系统复位时,中断掩码寄存器的所有位都为0,所有端口都禁止中断。 边沿捕获寄存器(edgecapture) PTF参数edge_type可设置成RISING, FALLING, 或ANY,当PIO输入端口检测到有边沿变化时,边沿捕获寄存器会把相应的位置1。edge_type设置成NONE,边沿捕获寄存器不存在。 对边沿捕获寄存器执行写操作将清除数据寄存器所有位。 10. 数据结构及示例程序 typedef volatile struct { int np_piodata; // read/write, up to 32 bits int np_piodirection; // write/readable, up to 32 bits, //1->output bit int np_piointerruptmask; // write/readable, up to 32 bits, // 1->enable interrupt int np_pioedgecapture; // read, up to 32 bits, // cleared by any write } np_pio; 3.4.4 异步收发器(UART) Nios UART模块是包含在Nios开发工具包中的一个SOPC Builder库组件。UART模块与通用串口兼容,用户可以根据需要设置通讯模式,如波特率、奇偶校验、停止位、数据位和其他可选择的控制信号等。在SOPC Builder中,用户可把UART定义为设备逻辑,并为其定义接口信号。SOPC Builder会生成UART模块的verilog HDL或VHDL源码和软件子程序,方便了系统的集成。 1.功能描述 Nios UART是在Altera器件内部可以实现简易的RS-232异步发送和接收逻辑设备。UART通过两个外部管脚(RxD和TxD)发送接收串行数据。通过对5个内存映射的16位寄存器的操作,实现对UART控制和通信。 为了满足RS-232电平信号的桂发要求,在TXD/RXD I/O管脚和对应的RS-232外部连接之间,需要一个电平转换芯片。输入电压变化范围和输出电压值取决于Altera设备的I/O管脚的配置。UART传输数据的起始位为逻辑0,终止位为逻辑1。数据的传输由clk同步时钟来同步。 UART可以与DMA控制器一起使用,允许数据在UART和存储器间进行流数据传输。例如,在一个高速系统仿真过程中,如果利用UART传输仿真时的各种数据,其仿真速度是很难令人接受的,因为UART即使使用115200bps波特率,也不过相当于每秒大约传输11500个字节。如果使用一个小波特率分频器来运行UART,就能够以系统时钟的一半速度来实现串行的数据传输。在这种模式下,每两个时钟周期就可以传疏1bit,几乎每10个时钟周期就可以传输一个字节。 此外,还可以定制数据流来实现UART的数据传输,由于该方法比较复杂,此处不再详述,有兴趣的读者可以参考相关资料。 发送器: UART发送器包括一个7,8,9位的txdata保持寄存器和一个7,8,9位传输移位寄存器(数据的位数由PTF配置文件中的data_位s参数决定)。txtdata寄存器对用户来讲是可操作的,可以直接对其执行写操作。在串行移位的传输空闲时,发送移位寄存器自动从txdata寄存器上装载。发送移位寄存器直接与TxD管脚,低位先传输。 这两个寄存器提供双重缓冲:用户可以在先前写入的数据移出的过程中,向txdata寄存器写入新的数值。用户可以通过读取状态寄存器的ready(trdy)位,发送移位寄存器的empty(tmt)位和发送溢出(toe)位来了解数据的传输情况。根据RS-232规范和PTF的配置,发送设备自动在txd数据流中插入正确的开始、停止和奇偶校验位。 接收器: UART接收器包括一个7,8,9位的接收器移位寄存器和一个7,8,9位rxdata保持寄存器,用户只要直接对rxtdata保持寄存器执行读操作就可以了。每完成一次数据的接收,Rxdata保持寄存器都自动从接收移位寄存器上装载,也就是说,移位接收的过程 是自动的,接收移位寄存器装配好一个数据后,就会自动的保存到Rxdata保持寄存器中。 这两个寄存器提供双重缓冲。Rxdata寄存器能够在移位寄存器接收数据的同时保持一个先前接收的数据。用户可以通过读取状态寄存器的rrdy位,接收溢出位(roe),中断检测位(brk),奇偶校验错误位(pe),帧错误位(fe)来了解数据传输的状态。根据RS-232的规范和UART配置,接收器自动监测串口RxD中开始、停止和奇偶校验位。接收器检查接收的数据中的四个异常的情况,并设置相应的状态寄存器(fe,pe,roe,brk)。 波特率发生器: UART内部波特率发生器的时钟与Avalon Bus系统使用的是同一个时钟源,所不同的是内部波特率发生器的时钟由系统时钟分频而产生的。当PTF中fixrd_baud参数设为0时,UART的波特率可由主机通过分频寄存器设定。当fixed_baud设为1时,UART使用一个固定波特率。 中断输出: UART产生一个IRQ输出信号,作为它的Avalon bus接口的一部分。当一个或多个内部状况寄存器置1且控制寄存器中相应的中断位被置1的时候,UART发出中断请求。复位时,所有的中断使能位都置为0。 每一个可能的中断情况都在状态寄存器有一相关位,在控制寄存器中有一相关的中断使能位。当任何一个中断情况发生时,相关的状态位被设为1,且直到软件清除状态寄存器才会置0。软件向状态寄存器写任意置(值会被忽略)来清零状态寄存器。当任何一个状态位置1,且相应的中断位为1时,就会输出IRQ信号。 2.UART寄存器 表3.17列出了UART的相关寄存器及属性 表3. 17 UART寄存器及属性 rxdata 寄存器 用户可以从rxdata寄存器中读取接收到的数据。当数据移位寄存器接收到一个完整地的数据后,状态寄存器的rrdy位就会被置1,并且刚刚接收到的数据被转移到接收数据保存寄存器rxdata中。当从rxdata寄存器读出一个数据时,状态寄存器的rrdy位即被清空。如果在rrdy位被置1时(即当用户没有取走先前数据时)数据传送到rxdata寄存器,就会发生接收溢出错误并且状态寄存器的roe位置1。新数据会一直向rxdata寄存器传送,不管软件是否取走先前数据。 注意:向rxdata寄存器写数据无效。 txdata 寄存器 用户把要发送的数据直接写入到发送保持寄存器txdata。直到发送器准备就绪时,数据才能被写入txdata寄存器,此时状态寄存器的trdy位指示传送状态。如果当trdy为0时数据被写入txdata寄存器,结果不确定。用户把数据写入txdata寄存器时,trdy位都会被置0。当数据从txdata寄存器转移到发送移位寄存器时trdy位被置1,这表明txdata寄存器为空。 例如,假设UART外设空闲并且用户将数据写入txdata寄存器。在闲置阶段,trdy位置0,但是在数据转移到发送移位寄存器后,trdy位立即置1。此时,用户就可以把另一个数据字节写入txdata寄存器,且trdy位再次被置0。此时,先前的数据字节仍然处于通过串行TxD引脚传送的过程中。因此,trdy位一直保持为0直到处理周期结束。当该周期结束时,第二个数据字节被转移到传送移位寄存器。 注意:从txdata寄存器读数据将获得一个不确定的结果。 状态寄存器 状态寄存器是由反映UART内部特定状态的位组成的。无论何时,用户都可以访问状态寄存器,并且这样做并不会改变任何位的值。每一位都与控制寄存器中相应的中断使能位相关。如果在一状态位等于1的同时与此状态位相关的中断位也等于1,则发送一个中断请求。 当用户对状态寄存器执行了写操作,大多数状态位就会被置为0。状态寄存器的各个位的含义,如表3.18所示。 pe 位:当接收的奇偶校验位出现一个意外的逻辑电平时,奇偶校验错误将会发生,pe 位会被置1。当PTF参数parity被设为“N”时,没有奇偶校验功能,且pe 位总为0。pe 位需要软件来清零。 表3.18 状态寄存器 fe 位:当接收器检测到一个错误的停止位时,帧错误将会发生,并且fe 位会被置为1。fe 位需要软件来清零。 当fe 或pe 位被置1时,从rxdata寄存器中读取数据是没有意义的。 brk 位:当RxD管脚连续保持低电平超过一帧数据发送时间时,接收器检测到中止发生,且brk位被置1。Brk位需要软件来清零。 roe 位:当用户未读取先前帧数据时新的数据就传输到rxdata保持寄存器中,就会产生接收器溢出错误。在这种情况下,roe 位被置为1,rxtada寄存器之前的内容会被新的数据所覆盖。roe 位需要软件来清零。 toe 位:当trdy为0且用户将一帧新的数据写到txdata保持寄存器中时,toe 位置为1。toe 需要软件来清零。 tmt 位:tmt 位表明发送移位寄存器的当前状态。在发送移位寄存器正在向TxD管教移送数据时,tmt置为0。当发送移位寄存器空闲的时候,tmt 位置1。用户可以通过检查tmt 位来判断传输是否完成。对状态寄存器执行写操作tmt不会改变。 trdy 位:trby 位表明txdata保持寄存器的当前状态。当txdata保持寄存器是空时,trdy为1。当txdata寄存器中的值还没有传输到发送移位寄存器中时,trdy为0。用户必须等待trdy等于1时才可以向txdata寄存器写入新的数据。对状态寄存器执行写操作trdy不会改变。 rrdy 位:rrdy 位表明rxdata保持寄存器的当前状态。当rxdata保持寄存器是空时,rrdy为0。当一帧新的数据传输到rxdata寄存器中时,rrdy 位置1。当用户对rxdata寄存器执行了读操作后,Rrdy 位就置0。用户必须等候rrdy等于1时才可以从rxdata读取数据。对状态寄存器执行写操作rrdy不会改变。 e 位:异常状态位e 的值是toe,roe,brk,fe和pe 位执行简单的OR逻辑得到的结果。e位表示异常状态发生。e位和相应的中断使能位ie可以禁止/开启所有错误状态的中断。对状态寄存器执行写操作会使得E 位被置为0。 dcts 位:当PTF文件中的参数use_cts_rts等于1时,状态寄存器才会含有dcts 位。检测到同步采样cts_n输入管脚有电平转换,该位就会被置1。cts_n输入上的升和降的传输都可以设置这个点。需要用户写状态寄存器才能使得dcts 位清零。如果控制寄存器中的idcts中断使能位为1且dcts为1时,UART会产生一个中断。当PTF文件中的参数use_cts_rts等于0时,dcts 位总是为0。 cts 位:当PTF文件中的参数use_cts_rts等于1时,状态寄存器包括只读cts 位。这个位反映了cts输入信号的瞬间、同步采样的逻辑状态。UART硬件有一个负逻辑输入位cts_n。因此,当一个低电平加到cts_n输入管脚时,cts等于1。当一个高电平加到cts_n输入管脚时,cts等于0。当PTF文件中的参数use_cts_rts等于0时,cts 位总是为0。cts_n输入对UART的发送或是接收数据没有任何影响。cts_n输入只影响cts 和dcts 位的状态。当控制寄存器的idcts被使能时可以产生的一个中断请求。 eop 位:当PTF文件中的参数use_eop_register等于1时,状态寄存器包含eop 位。当用户向发送器写一个EOP字或从接收器上读一个EOP字时,eop位被设置为1。特别的是,当软件向txdata寄存器写数据,且写到txdata上的数据值同edhofpactet寄存器当前值是一样的时候,eop 位置1。当用户从rxdata寄存器读数据,且读取的数据值同edhofpactet寄存器当前值是一样的时候,eop 位也设为1。Eop也需要软件清零(向状态 寄存器执行写操作)。当PTF文件中的参数use_eop_register等于0时,eop 位总为0。 控制寄存器 控制寄存器中只使用了13位,其余未用定义,如表3.19所示。在控制寄存器中为状态寄存器中的位都提供相应的中断使能位,用户可以在任何时候读取控制寄存器中的值。 表3.19 控制寄存器及说明 用户可以通过控制寄存器位来判断中断请求所发生的原因。 状态寄存器中的每一位都在控制寄存器的同一位置上有相对应的中断位。例如,pe 位是状态寄存器中的位0,相对应的ipe 位是控制寄存器中的位0。当状态位和它相对应的中断位全都等于1时,会产生一个中断的请求。 trbk 位:在软件的控制下,传输中断位允许软件在UART的txd位上传输一个中断字。当trdk 位等于1时,txd位置0。即使txd管脚有其他电平信号时,txd总为0。trbk 位可以中断任何正在进行的传输,因此,在适当的间断过程后,用户必须将trbk 位再次设置为0。 rts 位:当PTF文件中的参数use_cts_rts等于1时,控制寄存器包括rts 位。这个可读、可写的位直接和rts_n输出位相连。用户可以在任何时候写rts位。rts的取值对于UART的发送和接收均没有任何影响。rts 位的唯一目的就是决定rts_n输出位的取值。当rts为1时,一个低电平就会输出到rts_n上。当rts为0时,一个高电平会被输出到rts_n上。当参数use_cts_rts等于0时,rts 位总为0,写rts对其他没有任何影响。 分频寄存器 只有当PTF文件中的参数fixed_baud置0时,分频寄存器才能被执行。当fixed_baud被设为1时,分频寄存器不存在,在分频寄存器上执行写操作没有任何作用,此时读分频寄存器,结果是不确定的。 当fixed_baud被设为0时,分频寄存器的内容被用来产生UART的波特率时钟。UART 最终的波特率可以由公式来计算: band rate =clock_freq/ ( divisor+1) 用户可以在任何时候读取分频寄存器中的取值。 edofpacket 寄存器 edofpacket 寄存器缺省值为0。 3. UART的数据结构 下面是UART的数据结构: 4.UART的子程序 表3.20列举了Nios库中可用的软件子程序。这些功能在头文件nios_h中表明。 表3.20 Nios库中可用的软件子程序及说明 int nr_uart_rxchar ( np_uart *uartBase ) 这个子程序从UART的外围设备中读取一个字符,外设的地址可以通过uartbase指针传递。如果没有字符,nr_uart_rxchar返回值为-1。如果uartbase为0, nr_uart_rxchar从UART的nasys_printf_uart(nios_.h)地址读取一个字符。uartbase参数是一个指向UART 外围设备的指针。下面为nr_uart_rxchar子程序的示例。 #include \"nios.h\" void main(void) { int c; printf(\"Please enter a character:\\n\"); while((c = nr_uart_rxchar(nasys_printf_UART)) == -1); // wait for valid input printf(\"Your character is:\%c\\n\} int nr_uart_txchar ( int c , np_uart *uartBase) 这个子程序向UART的外围设备发送一个单一的字符c,外围设备的地址可以通过uartbase传递。如果uartBase为0,nr_uart_txchar从UART的nasys_printf_uart(nios_.h上确定的)位置上发送一个字符。其中c 为待发送的字符,uartBase为指向UART外设的指针。 下面为nr_uart_rxchar子程序的示例。 #include \"nios.h\" #define kLineWidth 77 #define kLineCount 100 void SendLots(void) { char c; int i,j; int mix; printf(\"\\n\\nPress character, or printf(\"%c\\n\\n\Don't show unprintables if(c < 32) c = '.'; mix = c==' '; for(i = 0; i < kLineCount; i++) { for(j = 0; j < kLineWidth; j++) { if(mix) { c++; if(c >= 127) c = 33; } nr_uart_txchar(c,nasys_printf_UART); // send character to UART } nr_uart_txcr(); // send carriage return and new line } printf(\"\\n\\n\"); } int nr_uart_txcr(void),这个子程序向地址为nasys_printf_uart的UART发送一个回车换行符到UART。 int nr_uart_txhex(int x),这个子程序向nasys_printf_uart地址的UART发送一个16进制的整数x。16位的NIOS CPU的数值范围为0000到FFFF,32位NIOS CPU的区间为00000000到FFFFFFFF。 int nr_uart_txhex16(short x),这个子程序向nasys_printf_uart地址的UART发送一个16进制的整数x。范围为0000到FFFF。 int nr_uart_txhex32(long x),这个子程序向nasys_printf_uart地址的UART的发送一个32进制的整数值x。数值范围为00000000到FFFFFFFF。这个子程序不能适用于16位NIOS CPU。 int nr_uart_txstring(char *s),这个子程序向nasys_printf_uart地址的UART的发送空白终止符。 3.4.5 DMA控制器 NIOS DMA模块是包含在NIOS开发工具包中的一个Altera SOPC Buidler库组件。通过DMA模块,就可以直接在外设和存储器之间快速的传递大批数据,而不需要CPU的干预。 11. 功能描述: DMA外设可用于存储器之间、存储器与外围设备之间以及外设之间的DMA的数据传输。DMA外围设备可用来与straming-capable外设相连接,允许在没有CPU干预的情况下,完成固定长度或可变长度的数据传输。 DMA外围设备有两个Avalon主端口(一个读端口,一个写端口)和一个从端口来控制DMA,如图3.13所示。DMA通过检查从端口的连接来决定自己的配置,如果有必要,用户也可以修改默认值。 典型的DMA传递过程如下: 通过写控制端口设置DMA传输数据。 启动DMA的外设,然后DMA外设在没有CPU的干预下传输数据。 DMA的读传输主端口从目标地址(内存或外设)读取数据,而写传输主端口向 目的地址(内存或外设)写数据。读端口和写端口之间可能需要FIFO进行数据 缓冲。 当指定的字节数传输完成或者传输了一个包结束(EOP)标志,DMA将结束传 输。DMA的外设可以在传输结束时发出中断请求,以便CPU决定下一步的工作任务。 在传输的过程中,或在传输结束以后,程序可以通过检查DMA的状态寄存器, 来判断传输是在进行之中或者已经结束。 图3.13 具有主从接口的DMA外设 12. 寄存器 DMA寄存器如表3.21: 表3.21 DMA寄存器 状态寄存器(status) 状态寄存器反应DMA内部的各种状态,各个位的定义及说明如表3.22所示。可以随意读取状态寄存器而不改变寄存器的值。 表3.22 状态寄存器各个位的定义及说明 done位:当检测到包末尾标志或指定字节数传输完成时,done位置1。对状态寄存器的写操作将使该位清零,同时清除中断请求。 busy位:busy位为1表示DMA传输正在进行中。 reop位:在读数据过程中检测到包末尾标志而结束DMA传输时,reop位置1。 weop位:在写数据过程中检测到包末尾标志而结束DMA传输时,weop位置1。 len位:当指定字节数传输完而结束DMA传输时,len位置1。 读地址寄存器(readaddress) 读地址寄存器中存放的是DMA待传输数据的源地址,读地址寄存器的宽度由系统生成时的设置决定,它的宽度应该足够寻址所有读端口所对应的从外设。 写地址寄存器(writeaddress) 写地址寄存器中存放的是DMA传输时待写入数据的目的地址,写地址寄存器的宽度由系统生成时的设置决定,它的宽度应该足够寻址所有写端口所对应的从外设。 长度寄存器(len) 长度寄存器中存放的是读写端口之间需要传输的字节数。长度寄存器的宽度由系统 生成时的设置决定,它的宽度应该满足读写端口对应的所有外设的需求。 DMA每完成一次写传输,长度寄存器的值相应地减一。当寄存器的值减小到0时,如果控制寄存器的leen位被使能,状态寄存器的len位将被置1。长度寄存器减小到0时就不再减小。 注意:长度寄存器的值是按照字节数来计算的。对于字传输,它必须是4的倍数;对于半字的传输,它必须是2的倍数。 控制寄存器(control) 控制寄存器中的各个位决定DMA的工作方式,各位的说明如表3.23所示。用户可以任意读取控制寄存器的值来了解DMA的工作方式,而不会对操作产生任何影响。 表3.23 控制寄存器中的各个位定义及说明 byte、hw和word位:传输的数据宽度由byte、hw和word位决定,选择那种宽度就将相应位置1。在DMA工作方式时,源设备和目的设备的数据宽度可以相同,也可以不相同。无论是源设备还是目的设备,传输数据的宽度取决于数据宽度最窄的一个。例如,读取一个16位flash,写入32位的片上内存的DMA传输是半字传输。相应的设置为:byte=0,hw=1,word=0。 Go位: 当go位置1时,启动DMA开始传输。例如,当长度寄存器不为0时,go位的值由0变为1,将启动DMA传输。 I_en位:当i_en位为1时,在状态寄存器的done位变为1时将会产生DMA中断请求。当I_en位为0时,IRQ输出总是0。对状态寄存器的写操作将清除done位,同时也清除中断请求。 Reen位:当reen位置为1时,从外设可以通过生成包末尾标志来结束DMA传输。 Ween位:当Ween位置为1时,从外设可以通过生成包末尾标志来结束DMA传输。 Leen位:当Leen位置为1时,DMA在长度寄存器为0时结束传输。若Leen位为0,长度寄存器到达0时,则传输不会停止。也就是说,如果Leen位为0,控制器将不按照预设的传输数据量而终止传输,这种方式,适合于传输数据量大小不确定的场合。 Rcon位:rcon位控制读地址递增的方式。 Wcon位:wcon位控制写地址递增的方式。 3. 地址递增 一次DMA传输是一系列对存储器或外设的读(或写)访问。例如一个典型的存储器访问,读数据的地址每次访问递增1、2或4个字节,由传输的数据宽度决定。而典型的外设(比如UART)却有固定的寄存器寻址。DMA工作(读或写)时地址递增的规则如下: 如果控制寄存器rcon位置1,读地址递增值为0。 否则,读写访问地址按照控制寄存器里设置的传输数据宽度递增。 4. 中断 当状态寄存器的done位和控制寄存器的I_en位同时为1时,DMA外设输出中断请求。一个典型的DMA中断处理程序首先要读取状态寄存器的len、reop和weop位来判断中断原因。然后,写状态寄存器清除中断,并做相应的处理,再进行下一次DMA传输。 5. 软件数据结构 typedef volatile struct { int_np_dmastatus; //状态寄存器 int_np_damreadaddress; //源数据的地址 int_np_dmawriteaddress; //数据的目的地址 int_np_dmalength; //传输字节数 int_np_dmareserved1; //备用 int_np_dmareserved2; //备用 int_np_dmacontrol; //控制寄存器 int_np_dmareserved3; //备用 } np_dma; 3.4.6 串行外围设备接口(SPI) 串行外围接口模块(SPI)是Altera SOPC Builder中的库组件,它包含在 Nios开发套件中。SPI模块是一种广泛应用于嵌入式系统中的工业标准通信接口。很多半导体生产商销售多种使用SPI接口的传感、变换和控制设备。SOPC Builder可以对SPI模块进行逻辑及接口信号的定义。SOPC Builder会自动生成SPI模块的Verilog HDL或VHDL源代码,以及相应的软件接口子程序。 Nios串行外围接口模块(SPI)是三线制的,既可作为主设备又可作为从设备。通过SPI总线,允许与一个或多个外部设备实现通信。通过5个内存映射的16位寄存器来控制外围设备,并实现与外围设备通信。当SPI被配置为主设备时,它可与多达16个从设备连接。 1. 功能描述 SPI外围设备对传统协议进行了扩展,允许一个以上的外部SPI外设共享同一miso、mosi和sclk信号。每一个SPI外设至少有四个接口信号miso、mosi、sclk和ss_n。主设备包含1-16个ss_n信号,分别与从设备的ss_n连接。各个信号的功能取决于SPI外设是作为主设备还是作为从设备运行。如表3.24所示。 表3.24 SPI接口描述 miso(master in,slave out)引脚作用是传输从设备到主设备的同步数据。sclk引脚由主设备驱动,来同步所有的数据传送。每一个SPI从设备都有一个ss_n引脚,低电平有效。从属设备只有在ss_n为低电平时才响应传输操作。 一般情况下,当SPI外设未被选中时,就把它们的miso输出引脚置为高阻抗状态。而Altera提供的SPI从外设在未被选中时,在miso输出管脚被驱动为一个不确定的值。如果一个SPI的从设备连接到一个片外(off-chip)SPI主设备上,片选端可以用于控制Nios系统模块外面的三态管脚。可以已看出,当多个从设备共享同一SPI总线时这样的设计是很有必要的。 SPI外围设备能够与DMA外围设备一起实现SPI和存储器之间的数据流传输。 SPI外围设备包括下列用户组件: 存储器映射的寄存器空间(软件接口) SPI总线接口引脚(对其它SPI设备的硬件接口) 寄存器控制位决定了软件何时能够访问寄存器。所有数据都要经由SPI总线接口引脚miso、mosi、 sckl和 ss_n来传输。 13. 主设备方式运行 在设计时,如果在PTF中将SPI外设配置为主设备,它将作为主设备运行。总体而言,SPI协议不支持多主系统。主设备负责启动所有的数据传输,且每一次传输都看作为既是一次接收又是一次发送操作。在每个有效的时钟沿,主设备通过mosi引脚向从设备传送一新的数据位,同时从设备通过miso引脚向主设备传送一新的数据位。 SPI传送逻辑由一个n位的(这里n是一个从1到16的值)txdata传送保持寄存器和一个n位的传送移位寄存器(数据位数由PTF中的databits位决定),用户通过把一待传送数据值写入txdata寄存器开始一次SPI传输。传送移位寄存器直接连到mosi数据管脚。数据位传输的优先顺序(最低有效位优先或者最高有效位优先)取决于PTF中lsbfirst位的赋值。当传送移位寄存器传输完一帧数据时,传送移位寄存器自动从txdata寄存器加载数据。 注意:传送移位寄存器和txdata寄存器在数据传送过程中提供双重缓冲;即当先前写入的字符正被移出移位寄存器时,用户还可以向txdata保持寄存器写入新值。 SPI外围接收逻辑由一个n位(这里n是一个从1到16的值)txdata的接收保持寄存器和一个n位的接收移位寄存器(数据位数由PTF中的databits位决定)。接收数据保持寄存器可以直接由用户读取。接收移位寄存器直接与miso数据引脚相连。每当数据被完全接收时rxdata保持寄存器自动从接收移位寄存器下载数据。 注意:接收移位寄存器和rxdata寄存器在数据接收过程中提供双重缓冲;即当随后的数据字节正被移入接收移位寄存器时rxdata保持寄存器能够保留先前接收到的数据字节。 通过读状态寄存器中的trdy, tmt和toe位,软件可以监视主设备写操作的状态。同样,软件通过读状态寄存器中的roe和rrdy位能够监视从设备读操作的状态。 14. 从设备方式运行 SPI从设备除了不能起动数据传输外,它和主设备运行方式基本相同。在一次传输开始前,从设备查询它的ss_n引脚,等待此引脚变为低电平时,该从设备被SPI主设备所选中,SPI从设备就会立即开始把传送移位寄存器的内容传到miso引脚。从设备也同时开始读接收移位寄存器,一次读写处理同时被执行。 15. 寄存器 SPI寄存器如表3.25所示: 表3.25 SPI寄存器 注释: 1) 对状态寄存器的写操作清空roe, toe 和e 位。 2)仅对SPI主设备而言 rxdata寄存器 用户从rxdata寄存器中读取接收的数据。 当新的数据由miso输入端被完全接收后,状态寄存器的rrdy位就会置1并且数据被转移到rxdata寄存器。当软件从rxdata寄存器将数据读出时,状态寄存器的rrdy位即被清0。如果当rrdy位为1时(即软件没有取走先前数据时)传送数据到rxdata寄存器,就会发生接收溢出错误且状态寄存器的roe位置1。新数据会一直向rxdata寄存器传送,不管软件是否取走先前数据。 注意:向rxdata寄存器写数据无效。 txdata 寄存器 用户欲发送的数据将被直接写入到txdata寄存器中。当状态寄存器的trdy位为就绪时,数据才能被写入 txdata寄存器。如果当trdy为0时数据被写入txdata寄存器,就会出现一个toe位错误并且txdata寄存器中的内容成为不确定的值。用户把数据写入txdata寄存器时,trdy位就会被置0。当数据从txdata寄存器转移到发送移位寄存器时,trdy位被置1,以表明txdata寄存器为空。 例如,假设SPI外围设备空闲并且用户把欲发送的数据写入txdata寄存器。在闲置阶段,trdy位置0,但是在数据转移到发送移位寄存器后,trdy位立即置1。此时,用户就可以把另一个数据写入txdata寄存器,且trdy位再一次被置0。先前的数据仍然处于通过串行mosi引脚传送的过程中。因此,trdy位一直保持为0直到处理周期结束。当该周期结束时,第二个数据字节被转移到传送移位寄存器且trdy位又被置1。 状态寄存器 状态寄存器由6位组成,如表3.26所示。无论何时,用户访问状态寄存器,且不会改变任何位的值。状态寄存器中的每一位都与控制寄存器中相应的中断使能位相关联。如果某一状态位及相应的中断使能位同时等于1则会产生中断请求信号。 表3.26 状态寄存器 roe位:如果rxdata寄存器为满时有接收到新的数据,roe位将被置位1(即当rrdy位置1 时)。假如发生了接收溢出错误(ROE),新数据就覆盖了旧的数据。当用户对状态寄存器执行写操作时roe位被置0。 toe位:在发送寄存器为满时(即当trdy位置0时)有数据写入txdata,则toe位被置1。如果发生发送溢出错误(TOE),新数据将被忽略。当用户对状态寄存器执行写操作时,toe位置0。 tmt位:正在移出数据时,tmt位被置0,当移位寄存器为空时其被置1。 trdy位:当txdata寄存器为空时trdy位置1。 rrdy位:当rxdata寄存器满时rrdy位置1。 e位:当toe或roe置位时e位被置1。当用户对状态寄存器执行写操作时e位置0。 控制寄存器 控制寄存器是由6位组成,如表3.27所示。控制寄存器中的每一位都能够使能与状态寄存器中相应的一种中断。用户可以在任意时刻读取控制寄存器中的值。 表3.27控制寄存器 通过对控制寄存器的赋值(iroe, itoe, itrdy, irrdy和ie),用户就可以设定SPI发送中断请求的条件。状态寄存器中的每一位在控制寄存器中相应的位置都有一中断使能位。 例如,状态寄存器7号位是rrdy, 则控制寄存器7号位是irrdy(中断使能,接收器准备就绪)。对于状态寄存器的每一位,如果状态位和它相应的中断使能位都被置1,就会产生一次中断请求。 控制寄存器的10号位sso不是一个中断使能位。它可以强制ss_n在任何时刻都有效。sso可用于在SPI链接上传送或接收任意大小的数据。下面给出了通过一个8位SPI主机传24位数据的例子。 sso控制寄存器代码示例: //Force SS-n active: na_spi_0 -> np_spicontrol |= np_spicontrol_sso_mask; for (i=0;i<3;++i) { //Transmit a byte: while (!(na_spi_0 -> np_spistatus & np_spistatus_trdy_mask)); na_spi_0 -> np_spirxdata = data[i]; //Read and throw away the received data: while (!(na_spi_0 -> np_spistatus & np_spistatus_rrdy_mask)); na_spi_0 -> np_spirxdata; } //wait until the last byte is transmited: while (!(na_spi_0 -> np_spistatus & np_spistatus_tmt_mask)); //Release SS-n: na_spi_0 -> np_spicontrol &=~np_spicontrol_sso_mask; 从设备选择寄存器(slaveselect regester): 从设备选择寄存器用来设置SPI总线主设备驱动从设备的选择掩码。从设备选择寄存器只有SPI外设被配置为主外设时才存在。该寄存器可通过设置相应的位来寻址16个从外设。比如,选择从外设0,则从设备选择寄存器中的0号位须置1。 通过设置选择位,SPI主外设能够同时连接多个从外设。例如,若要选择从机设备1,5和6,选择寄存器位1,5,6须置为1。 注意:当同时选择多个从机设备时要慎重考虑。在miso引脚上可能会产生冲突。 复位操作将使得0号外设被选中,并清除从外设选择寄存器的其他位。因此,在设备复位后,从外设0 会自动被选中。 SPI数据结构 Typedef volatile struct { int np_spirxdata; int np_spitxdata; int np_spistatus; int np_spicontrol; //Read-only,1-16bit //Write-only,1-16bit //Read-only,9-bit //Read/Write,9-bit int np_spireserved; int np_spislaveselect; } np_spi; //Reserved //Read/Write,1-16bit,master only SPI子程序 表3.28列出了可用SPI软件子程序。这些函数声明包含在文件nios.h中。 表3.28可用的SPI程序及说明 int nr_spi_rxchar ( np_spi *pSPI) 这个子程序从SPI外设读一字符,该外围设备的地址被传送给指针pSPI。pSPI参数是指向SPI外围设备。 int nr_spi_txchar( int i, np_spi *pSPI) 这个子程序把一个字符i,送到pSPI指针指向的SPI外设。参数I 为待发送的字符,pSPI为指向外设的指针。 因篇幅问题不能全部显示,请点此查看更多更全内容