摘 要:设计了一款面向嵌入式控制领域的16位堆栈处理器,该处理器包含两个堆栈:执行数学表达式的数据堆栈和支持子程序调用的返回堆栈,其指令集含35条堆栈指令.详细给出了该堆栈处理器的体系结构及设计方法;不仅采用简单有效的指令编码方式缩小了代码体积,同时给出了单周期 *** 作多个堆栈元素的解决方法.该处理器采用FPGA实现,在XC5VLX110T芯片上的运行时钟频率最高达到146.7MHz。最后给出了设计的软件仿真与硬件综合结果。
1 引言Forth是由Charles H.Moore在1960年代发明的一种基于堆栈、可扩展、具有简单哲学思想的计算机编程语言[1],特别适合于软件代码在千行数量级的中规模嵌入式系统中应用,并且已经被国外广泛应用于天文、军事、航空航天、工业自动化、图形、仪器仪表等领域。
Forth语言既可以被看作汇编语言又可以被看作高级语言,它与传统语言最大的区别在于它是基于堆栈的和可扩展性。Forth语言本质上定义了一种双堆栈体系结构。这种体系结构的主要思想是基于两个不同的堆栈,一个是用来执行数学表达式的数据堆栈,另一个是用来支持子程序调用的,即保存子程序返回地址的返回堆栈,指令的所有 *** 作都是针对这两个堆栈的一个或者几个栈顶元素[2]。
Forth语言定义的这种双堆栈体系结构清晰明了,复杂度低,并且其主要面向的是嵌入式控制领域。在这种背景下,结合FPGA的灵活性,本文设计并实现了一种基于FPGA 的16位堆栈处理器,相比基于RISC体系结构的嵌入式处理器,具有以下几点优势:
① 很大程度上避免了处理器进行上下文切换带来的开销,因为处理器的运行不依赖于大量的通用寄存器;
② 处理器的寻址方式非常简单,几乎所有指令都是0 *** 作数指令.这样不仅系统复杂度显著降低,速度得到提升,代码体积也大大减小;
③ 处理器在运行具有深度嵌套特征的程序时有更加明显的优势,因为具有专门的硬件堆栈来执行子程序调用与返回。
2 设计与实现
2.1 堆栈处理器体系结构概览
处理器的结构如图1所示,为了能够清晰地展示其结构,部分信号并未显示或直接连接.处理器包含的主要模块见表1。
需要说明的是,表中的数据堆栈由栈顶寄存器T、次栈顶寄存器N1、第三栈顶寄存器N2以及深度为32的堆栈存储器构成;返回堆栈由栈顶寄存器R以及深度为32的堆栈存储器构成。
2.2 指令集
2.2.1 指令集设计
处理器实现了四种类型共35条指令,具体如下表2所示。
表中大部分指令遵从了Forth语言的原语命名规则与功能.比如,其中的‘>r’表示将数据堆栈栈顶寄存器T的内容d出到返回堆栈栈顶寄存器R,‘r>’则表示相反的功能.另外,‘@’表示从存储器读数据到数据堆栈,而‘!’表示将数据堆栈的内容存储到存储器中.下面分别简述各种类型指令的功能。
① 堆栈 *** 作:主要是对数据堆栈或者返回堆栈的一个或者多个栈顶元素的 *** 作.例如,swap指令交换T与N1的内容;one和zero指令分别将T的所有bit位置1和0;drop指令d出T中内容并抛弃.所有的堆栈 *** 作类型指令都在一个周期内完成。
② 数学运算:数学运算指令的 *** 作数均为T和N1中内容.逻辑运算指令包括or、xor和and;移位指令包括asr、lsr和lsl;1plus和1min分别将数据堆栈栈顶元素加1和减1.所有数学运算类型指令都在一个周期内完成。
为了节省硬件资源,堆栈处理器没有实现硬件支持的乘法器[3]和除法器[4],因此没有直接的乘法和除法指令,取而代之的是mpp和shld指令.这两条指令分别完成了乘法器和除法器在一个时钟周期中完成的 *** 作.mpp指令执行时需要同时 *** 作寄存器T、N1和N2.N1与N2中分别存放初始的乘数与被乘数,T的初始值为0;最终乘积结果的高低16位分别存放于T与N1.mpp指令的执行分成两步.第一步判断N1最低位是否为1:如果为1,将N2与T中内容相加;否则不做 *** 作.第二步将T做高位、N1做低位,对其内容进行逻辑右移.这样连续执行完16条mpp指令后,T与N1中存放的分别为乘积的高低16位.这16条mpp指令再配合其他几条指令便可以完成正常的乘法 *** 作,完整的乘法 *** 作耗费19个时钟周期.除法的实现与乘法类似,完整的除法 *** 作耗费21个时钟周期.下面给出了乘法 *** 作的程序示例,其详细解释将在3.1节的仿真分析中给出:
litc 0 ∥将0压入堆栈
mpp ∥第1条mpp指令
…
mpp ∥第16条mpp指令
rot ∥乘法结束后,清理掉
drop ∥位于N2的被乘数
③ 访存:堆栈处理器支持按字节和按字两种访存方式.指令集中的litc、c@和c!指令为字节访存指令;而lit、@和!为按字访存指令.这里简要说明按字访存指令的功能,按字节访存与之类似.lit将一个字常量压入数据堆栈;@将T的内容d出送入地址寄存器A,按A中地址访存取得数据后压入数据堆栈;!指令将T中的内容d出送入地址寄存器A,再将当前栈顶T的数据d出存入A 中地址.除litc指令的其他指令需要两个周期执行,litc指令在设计时被优化为不需要访存,因此只需要一个周期执行。
④ 程序转移:call为子程序调用指令,它将当前PC寄存器值压入返回堆栈,同时将子程序地址送入PC寄存器;ret为子程序返回指令,它将返回堆栈的栈顶值d出,送入PC寄存器;jmp为无条件跳转指令;jz和jc为有条件跳转指令,跳转条件分别是数据堆栈栈顶T的内容是否为0和ALU进位标志位是否为0;drjne将返回堆栈栈顶R的内容减1,然后判断R中内容是否为0,如果不为0则跳转.除call与ret以外的其他指令都需要先访存取得16位跳转地址,然后压入堆栈,因此需要两个周期执行;ret与call指令只需要一个周期执行。
2.2.2 指令集编码优化
存储器是嵌入式控制系统中的一种稀缺资源,因此在设计堆栈处理器时,采用了一种简单但是有效的编码方式进行指令编码,不仅大大减小了代码体积,还在一定程度上可以降低系统的复杂度,提高系统效率。
在堆栈处理器上执行的程序一般都通过大量子程序调用进行模块化设计以减小代码尺寸,这样才能充分利用硬件支持的返回堆栈.因此,在典型的堆栈处理器程序中25%的时间花费在子程序调用上[5],这就要求在实现堆栈处理器时必须高效地实现call指令.一方面,如果call指令占用的bit位较少,便能减小代码体积;另外一方面,如果call指令的执行能在一个周期完成,将大大加快程序的执行速度.因此指令集采用了两种不同的编码方式实现。
对于除call以外的其他指令,每条指令占8位且最高位均为0.处理器每次访存取出的16位数据中,高低8位各为一条指令.采用这种设计方案后,一方面,每次访存可以取出两条指令.这在处理器和存储器之间形成了一个处理器速度两倍于存储器速度的缓冲,可以在一定程度上避免Cache的引入,降低嵌入式系统的复杂度;另一方面,每条指令占用8位而不是16位可以大大减小代码体积。
all指令采用了另外一种实现方式,如果访存取出的16位数据中最高位为1,那么这16位不再被解释为两条指令,最高位被解释为call指令,剩下的15位经逻辑左移1位后作为子程序地址.相对于采用与其他类型指令相同的8位实现方式,这种实现方式使得call指令仅占1位,可以进一步减小代码体积。
另外,call指令执行时的子程序地址不需要通过访存取得,因此其执行将只耗费一个周期.当然,这样的方案会让call指令只能调用位于偶地址的子程序,但是好的设计来源于适当的折中,这种损失相对于获得的效率和性能提升是值得的。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)