Quartus II Nios II : B# ~/ F! O! K
使用Altera Nios II 嵌入式处理器,系统设计者可以通过添加定制指令到Nios II指令集中,来加速处理对时间要求苛刻的软件算法。使用定制指令,用户可以将一个包含多条标准指令的指令序列减少为硬件实现的一条指令。 用户可以在很多的应用中使用这个特性, 例如,优化数字信号处理的软件的内部循环、信息包头的处理和计算密集的应用。Nios II 配置向导提供了图形化的用户界面用来添加多达256的定制指令到Nios II处理器。定制指令逻辑直接连接到Nios II 算术逻辑单元(ALU),如图8-1所示。
7 T b8 u T, ^2 R6 x) S本节包括如下的内容: 8 [3 Q4 `- t8 @' t( K
— Nios II 定制指令的特性。 / m" b( G: O2 Z8 i; F/ @
— 实现定制指令的软硬件要求。
( a& i s0 c5 z1 T — 定制指令的体系结构类型的定义。 8.1.1 定制指令综述 * R* [4 t0 [: Q, d
使用Nios II定制指令,用户可以利用FPGA的灵活性来满足系统性能的需要。定制指令允许用户添加定制功能到Nios II处理器的ALU。
! B7 v# P! f' {% w( ?- L; L Nios II 定制指令是在处理器的数据路径上与ALU紧邻的定制逻辑模块。定制指令提供给用户通过裁剪Nios II处理器内核来满足特定应用需求的能力。用户具有将软件算法转化成定制的硬件逻辑模块来进行加速处理的能力。因为,很容易改变基于FPGA的Nios II处理器的设计,在设计过程中定制指令提供了简单的方法来测试软硬件的权衡。 - X0 y: V; ?3 j% f. }
图8-2是Nios II定制指令的硬件结构图。Nios II定制指令逻辑的基本操作是从dataa和/或datab端口接收输入,在result端口驱动输出,输出是由用户生成的定制指令逻辑产生的。 ; y3 `* Z* W2 ]: {! k4 \
Nios II处理器支持不同的定制指令体系结构类型。图8–2 给出了用于不同的体系结构类型的另外的端口。不是所有端口都是需要的,有些端口只有在用于实现特定的定制指令时才存在。图8–2也显示了一个可选的与外部逻辑的接口。该接口允许用户与Nios II处理器数据路径之外的系统资源相接口。 Nios II定制指令软件接口很简单,而且抽象了定制指令的细节。对于每一条定制指令,Nios II IDE在系统头文件system.h中产生一个宏。用户在C或C++ 应用程序中如同一个函数一样调用宏。用户不需要编写汇编程序来访问定制指令。当然,在Nios II 处理器汇编语言程序中也可以调用定制指令。
! M8 F; a- y, k4 E$ b8.1.2 定制指令体系结构的类型
( U2 P- Y& q% _& k) ]- ~+ h3 a/ } Nios II支持不同的定制指令体系结构来满足不同应用的要求。体系结构从简单的、单时钟周期组合指令结构到扩展的可变长度的、多时钟周期定制指令体系结构。选择的体系结构决定了硬件接口。表8–1给出了定制指令体系结构的类型、应用和硬件接口。 1. 组合逻辑定制指令体系结构
0 C) D, C' |; w8 Q 组合逻辑定制指令体系结构包括一个能在一个时钟周期完成的逻辑模块。图8–3为组合逻辑定制指令体系结构的结构图。 图8–3 组合逻辑定制指令结构图使用了dataa和datab端口作为输入,在result端口驱动输出结果。因为逻辑可以在一个时钟周期内完成,所以不需要控制端口。组合逻辑必需的端口是result端口。dataa和datab端口是可选的。在定制指令需要输入操作数才有这两个端口。如果定制指令只需要一个输入端口,使用dataa。 # A& D0 L1 f4 A9 K C
2. 多时钟周期定制指令体系结构 * S; ]( m( n+ V6 P6 F& G
多时钟周期的定制指令包括一个需要2个或更多时钟周期才能完成操作的逻辑模块。对于多时钟周期定制指令需要控制端口。图8–4显示了多时钟周期定制指令的结构图。 多时钟周期定制指令可以在固定或可变的时钟周期数内完成:
* P6 d8 e3 [) Q, O/ T5 P( Z0 | - 固定长度:在系统生成时用户指定需要的时钟周期数。
- 可变长度:在握手方案中使用start和done端口来决定定制指令何时完成。
4 Q, |. ~& Q5 L; s
在表8–2中,对于多时钟周期的定制指令clk、clk_en和reset端口是必需的,而start、done、dataa、datab和result端口是可选的。只有定制指令的功能需要它们时才存在。 ! Z, |" L+ p% o: J/ |3 d
下面描述多时钟周期定制指令硬件端口的操作细节。图8–5给出了多时钟周期定制指令的时序图。
+ ?% v" o2 x8 c4 g. l* C$ { - 在第一个时钟周期,当ALU发出定制指令,处理器置start端口为高电平——有效,这时dataa和datab 端口具有有效的值,而且在定制指令执行的期间一直保持有效。
固定长度:处理器置start有效,等待一个指定的时钟周期数,然后读result。对于n个周期的操作,定制指令逻辑模块必须在start端口有效之后的第n-1时钟上升沿提供有效的数据。
, B1 f; I, R5 k) q 可变长度:处理器一直等到done端口有效,done端口为高电平有效。处理器在done有效之后的时钟沿读result端口。定制指令模块必须done端口为有效的同一个时钟周期向result端口上提供数据。 7 i( E- S8 z* w) E, ]! `% N
- Nios II系统时钟提供给定制指令模块的clk端口,Nios II系统主reset提供给高电平有效的reset端口。reset端口只有当整个Nios II系统复位才有效。
- 定制指令模块必须将高电平有效的clk_en端口处理成传统的时钟使能信号,当clk_en无效时,忽略clk。
- 定制指令模块的端口中不是表0-2中的定制指令的端口都是和外部逻辑的接口。
- 用户可以进一步优化多时钟周期指令,可以是通过实现扩展的内部寄存器文件定制指令,或者是创建有外部接口的定制指令。
3. 扩展定制指令体系结构
) x7 N. O6 O1 P# A$ s# A G: @5 r 扩展定制指令体系结构允许一个定制指令实现几个不同的操作,扩展的定制指令使用N域来指定逻辑模块执行哪个操作。指令的N域的字宽度可达8比特,使得一个定制指令可以实现多达256不同的操作。
5 W1 o$ Y+ a9 { 图8-6是扩展定制指令的结构图,可以实现位交换、比特交换和半字交换的操作。图8-6 的定制指令对从dataa 端口接收到的数据进行交换操作,它使用2比特宽度的n端口来选择多路复用器的输出,决定提供给result端口哪个输出。n端口的输入直接来自定制指令的N域字。这个例子中的逻辑是非常简单的,用户可以基于N域来实现任何用户想要的功能选择。 扩展定制指令可以是组合指令和多时钟周期指令,要实现扩展指令只要添加一个n端口到用户的定制指令逻辑。n端口的宽度由定制指令逻辑能够执行的操作数目决定。扩展的定制指令占用多个定制指令索引。例如,图8-6中的定制指令占用4个索引,因为n是2个比特的宽度。 因此, 当该指令在Nios II系统中实现之后, Nios II系统还剩下256 - 4 = 252 可用的索引。
% X" ~: W6 c; p U# e9 v n端口的行为同dataa端口类似。当在时钟的上升沿,start为有效时,处理器提供给n端口信号,n端口在定制指令执行的期间保持稳定不变。所有其它的定制指令端口操作保持不变。
9 P' d) F: g: r" E' N4. 内部寄存器文件定制指令体系结构 $ {3 z* Z" q+ X. \( G5 b
Nios II处理器允许定制指令逻辑访问其内部寄存器文件,这提供给用户指定定制指令从Nios II 处理器寄存器文件或是从定制指令本身的寄存器文件读操作数的灵活性。而且,定制指令可以写结果到定制指令的本地寄存器文件而不是Nios II处理器寄存器文件。内部寄存器访问定制指令使用readra、 readrb和writerc来决定I/O访问发生在Nios II处理器文件还是内部寄存器文件。并且,端口a、b和c指定从哪个内部寄存器读数据以及写数据到哪个寄存器。例如,如果readra为有效(即,从内部寄存器读),a提供了内部寄存器文件的索引。 更多的Nios 定制指令实现的信息参考Nios II Processor Reference Handbook中 Instruction Set Reference章节。图8-7显示了一个简单的乘加定制指令逻辑。 当readrb为无效时,定制指令逻辑对dataa和datab相乘,然后将结果存在accumulate(累加)寄存器 。Nios II处理器可以将结果读出。通过将readrb置为有效,处理器可以将累加器中的值读出来,作为乘法器的输入。表8–3列出了内部寄存器文件定制指令的端口。只用当定制指令的功能需要时,才使用这些可选的端口。 readra、readrb、writerc和a、b和c端口的行为同dataa类似。当start端口有效,处理器在时钟的上升沿提供readra、readrb、writerc、a、b和c。所有的端口在定制指令执行的过程保持不变。为了确定如何处理寄存器文件I/O,定制指令逻辑读高电平有效的readra、readrb和 writerc端口。定制指令逻辑使用a、b和c端口作为寄存器文件索引。当readra或者readrb无效时,定制指令逻辑忽略相应的a或b端口。当writerc为无效时,处理器忽略result端口上的值。其它的定制指令端口的操作是相同的。 5 B7 d( D9 D1 |
5. 外部接口定制指令体系结构
# V6 }( Z* S+ x! @1 c$ Q 图8-8显示Nios II定制指令允许用户添加一个同处理器数据路径之外的逻辑进行通信的接口。在系统生成时,任何的不被看作为定制指令端口的接口会出现在SOPC Builder顶层模块中,外部逻辑可以对其访问。因为定制指令逻辑能够访问处理器外部的存储器,这样就可以扩展定制指令逻辑的功能。 图8-8显示的是一个具有外部存储器接口的多时钟周期定制指令。
6 B3 E( [6 S+ q8 p) S, M7 o7 u 定制指令逻辑可以执行不同的任务,例如,存储中间结果,或者读存储器来控制定制指 令操作。可选的外部接口也提供了一个数据流入和流出处理器的专用的接口。例如,定制指 令逻辑能够直接将处理器寄存器文件的数据传递给外部的FIFO存储器缓冲,而不是通过处理器数据总线。
$ [* b1 P+ ]3 |6 v7 T& ^& O6 b0 K$ Z6 Z3 r8.1.3 软件接口 0 r4 a3 `' Q5 x6 F- }( T$ z
Nios II定制指令的软件接口从应用代码抽象了逻辑的实现细节,在编译过程中,Nios IIIDE生成允许应用代码访问定制指令的宏。这一节介绍定制指令软件接口的细节,包括如下的内容:
) F: M8 @0 G, {% G$ \ 1. 定制指令例子
& W) i3 F0 K$ d: R/ t# B# H% a例8-1显示了system.h头文件中的一部分,定义了位交换定制指令的宏。这个例子使用一个32位的输入,只执行一个功能。 - z3 c) c5 ^5 F& G( z/ t" O
例8-1,位交换宏定义
3 G/ ^! s- B9 G/ q3 [0 o#define ALT_CI_BSWAP_N 0x00
% @8 I6 Q" c3 X+ c) ?3 k#define ALT_CI_BSWAP(A) __builtin_custom_ini(ALT_CI_BSWAP_N,(A))
+ O& C' w4 w( z5 nALT_CI_BSWAP_N被定义为0x0,作为定制指令的索引。ALT_CI_BSWAP(A)宏被映射到一个只需要一个参数的gcc内嵌函数。
: w+ n- @' i4 l5 F( I$ ^; D. U7 l# g: W例8-2演示的是在应用代码中使用位交换定制指令。
' A" U3 J9 z; q, Y例8-2,位交换指令的使用
' S D8 N! C; M. Y1. #include "system.h"
1 s/ ]0 B6 Q E. w2.
# D0 t) z* ^0 k3. - I9 ]7 Q2 y' ^
4. int main (void)
' o# k' P# C3 u8 Y n3 W5. { $ H) E$ E" u7 c1 z l5 t& Q
6. int a = 0x12345678;
0 w: ~" M( @7 ]0 i7. int a_swap = 0;
* A! ?1 E7 D. t$ ^8. & f+ q( m+ b$ Y" y4 I* d+ t. x$ l
9. a_swap = ALT_CI_BSWAP(a); 0 J/ U& q% Z( C
10. return 0;
+ ^0 k8 ~% p" m11.} * w% {+ R6 W& i
在例8–2中,system.h 文件被包含,其中有宏的定义。该例声明了两个整数,a和a_swap。 * K; q7 n* `% d3 r2 n8 K# Y
a作为输入传递给位交换定制指令,结果赋给a_swap。例8–2可以体现大部分应用使用定制指令的方法。 Nios II IDE在宏的定义中只使用了C的整数类型。有时,应用需要使用整数之外的其它输入类型,因此,需要传递期望的整数之外的类型返回值。 用户能够为Nios II 定制指令定义定制的宏,允许其它的32位的输入类型同定制指令接口。
8 o" N% o K& T l: g/ _! X2. 内嵌函数和用户定义的宏 ) K8 G9 g$ p; m) Q; W) c
Nios II处理器使用gcc内嵌函数来映射定制指令。通过使用内嵌函数,软件可以使用非整数类型的定制指令。共有52个唯一定义的内嵌函数来提供支持类型的不同组合。内嵌函数名具有下面的格式:
Z; H0 O H1 n4 W9 M; n0 f! z8 u__builtin_custom_<return type>n<parameter types> - D; c: G4 x' m9 b9 T
表8-4为定制指令支持的32位的参数和返回值的类型,和在内嵌函数中使用的缩写。 例8–3 内嵌函数
+ {/ ~% c$ d- R. \' l# W+ yvoid __builtin_custom_nf (int n, float dataa);
* Q. o$ e* N/ K' l) e6 _' yfloat __builtin_custom_fnp (int n, void * dataa);
# Z) J0 W/ u8 P; z7 C4 G I, U' I在例8–3中,_builtin_custom_nf函数需要一个整形(int)和一个浮点型(float)作为输入,不返回结果;相反,_builtin_custom_fnp函数需要一个指针作为输入,返回一个浮点数。 ) ~2 R o0 y2 S
例8–4显示了应用中使用的用户定义的定制指令的宏。
! d( Z* |; W( M7 D6 j例8–4 定制指令宏的使用
/ [% I; P W. a. }5 I) T: [1. /* define void udef_macro1(float data); */
- @. d6 V6 d) u4 C, k: ?2. #define UDEF_MACRO1_N 0x00 9 v6 l+ u/ L ]/ a1 j7 r4 ? w# x: j
3. #define UDEF_MACRO1(A) __builtin_custom_nf(UDEF_MACRO1_N, (A));
) L$ Y3 _- Q( @# k/ }4. /* define float udef_macro2(void *data); */ 4 H; F% l% h6 N Z6 z
5. #define UDEF_MACRO2_N 0x01 2 ?8 C$ u5 e! e& D! x1 U/ V% d
6. #define UDEF_MACRO2(B) __builtin_custom_fnp(UDEF_MACRO2_N, (B)); / l6 n5 @9 \9 e( t% ~# t. k% o8 Y
7. - m6 D, d' [. ]+ v$ y
8. int main (void)
; `2 i8 [: h4 ^5 `: V, O- I5 F; l% O9. { - Z! `1 d' d- D) r8 I6 b' J
10. float a = 1.789; % R& \" _- } @
11. float b = 0.0;
7 W1 W% `& K4 _0 c1 a9 v: I12. float *pt_a = &a; % O |2 ]9 @* p8 h; z
13. 6 S- q+ c- G; _8 t+ n
14. UDEF_MACRO1(a);
- q; D" h! L+ L15. b = UDEF_MACRO2((void *)pt_a);
3 b6 }# r6 R6 y# o1 c2 }16. return 0;
# Y& p* L* j, }$ f- k17. } 在第2到第6行,声明了用户定义的宏,并映射到相应的内嵌函数。宏UDEF_MACRO1需要一个浮点型的输入,不返回任何值。宏UDEF_MACRO2需要一个指针作为输入,返回一个浮点型的值。第14和15行显示这两个用户定义的宏的使用。
' ~% S/ j$ X4 z* @5 g7 ^3 D8.1.4 实现 Nios II 定制指令
7 t) d, I. e5 r/ Z9 a# z, J 本节介绍使用SOPC Builder元件编辑器实现Nios II定制指令的过程。元件编辑器使用户能够创建新的SOPC Builder元件,包括Nios II定制指令。有关SOPC Builder元件编辑器的更多信息,参阅Quartus II Handbook Volume 4:SOPC Builder Component Editor章节。
, I: Z& C, ^7 Z. d, o8 e, y1. 在 SOPC Builder中实现定制指令的硬件实现Nios II定制指令需要下面的步骤:
; r! E( _! ?7 ]$ W6 P, M - 打开Nios II CPU的定制指令设置窗口。
- 添加定制指令的设计文件。
- 配置定制指令的端口。
- 设置元件组的名字。
- 生成SOPC Builder 系统,在Quartus II软件中进行编译。
(一)打开Nios II CPU的定制指令设置窗口 & i. U; X5 M: P5 |1 s" o
1. 打开SOPC Builder系统
7 _" `4 r% e, x! C. i2. 在Altera SOPC Builder System Contents中选择Nios II处理器。
. d' x3 I. V& a- S/ `: I# D" M3. 在Module菜单中点击Edit….,Nios II配置向导窗口出现。
0 U9 |8 ?6 Y" m7 q4. 点击Custom Instructions页,出现如图8-9的窗口。 & q& s7 J9 L! t" g
5. 在8-9的窗口中点击Import…,出现如图8-10的窗口。 (二)添加顶层设计文件
7 a7 w( \' O% |3 E1 ~1. 在图8-10窗口中,点击Add;
1 A- N" P" o: l# E3 n( J' S2. 切换到相应的目录,选择硬件设计的文件,设计文件可以是HDL文件、EDIF文件或BDF 1 Y5 O8 B# C* L/ F9 e8 N% H! k
文件,本例使用的是BDF文件,是实现CRC编码的逻辑,如图8-11所示,点击打开。
% u/ p. D% h) A+ A; ?6 R: a( [3. 设计文件会出现在图8-12所示的窗口中,软件会自动识别处顶层模块,本例显示的是crc。
6 O1 u: k+ v P! n8 |5 P& Q4. 在图8-12中点击read port-list from files,将定制指令端口列出来,如图8-13。 (三)发布定制指令
& ^ ] D* R {% B4 N! l1. 在图 8-13中点击 Next,进入图 8-14 的 Publish页面。 : e- w! X( D& `1 S0 ~0 n$ T# z4 ?
2. 在 Component Name 栏中输入 crc,在 group 栏中输入 custom instruction。 、
( `( P. I5 e# J1 Z3. 点击窗口底部的 Add to library,将定制指令逻辑添加到定制指令库中,如图 8-15 所示。 图8-15 加入到定制指令库中的定制指令
+ s. u3 i0 [, v# E+ Z) S: Q1 @(四)将定制指令加入系统 * y- g% k; [3 T0 ^9 q( G
1. 在图 8-15 的窗口中,选中 crc 定制指令,然后点击 Add,即将其加入到系统中,如图
5 U) ?0 v% ]8 Q6 ~' d# J& ~8-16。也可以在图 8-14 中,点击 Add to System,进行添加。 4 k) H$ T* S2 T
2. 在图 8-16的窗口中点击 Finish,完成定制指令的添加。
0 N( j4 p% B. J' d" G(五) 生成系统
: l: p8 X$ H7 g# ?) n1. 生成 SOPC Builder系统。 7 f/ B4 k. i5 Y2 b4 R; t
2. 在 Quartus II 软件中对整个工程进行编译。 |