通过分析SOC 参数的基本属性,利用邻域搜索算法针对功耗进行自动参数优化,得到性能和功耗的最优解。 该方法可以加快IP 设计和SOC 集成进程,减轻设计强度和减少设计错误,大大缩短了优化周期。 该方法已成功应用于一款RISC 处理器和基于它的SOC开发。
引言
片上系统( system on chip ,SOC) 已经成为21世纪全球瞩目的关键核心技术。 SOC 具有垂直整合的特性,并注重创新和创意,产品非常个人化、应用差异化、样式多元化。 SOC 应用需求的广泛性,以及大部分SOC 应用功能单一、性质确定的特点,决定了SOC 目前大多是针对专门的应用领域进行专门设计来满足高性能、低成本和低功耗的要求。 目前SOC 的发展趋势是: 体系结构需要在新技术与产品、市场和应用需求之间取得平衡;设计方法趋向于走专用、定制和自动化的道路。
一方面由于SOC 的专用化设计要求,另一方面由于IP 提供商的支持, SOC 的集成设计方法正向参数化设计发展。 SOC 的参数可能影响系统功耗、性能和面积,每个参数的选择范围是一个有限集合,因此一个SOC 设计可以理解为一组SOC 参数的选择。 在系统结构形成后,SOC 设计问题就转变为参数配置问题。 随着参数的增多,手工对代码进行修改是不可接受的,这不仅效率低,而且会增加错误率。本文对原有的硬件描述语言进行扩展,并建立了参数自动配置环境,通过分析SOC 参数属性,利用邻域搜索算法针对功耗进行自动参数优化。
参数自动配置设计方法
采用传统的硬件描述语言(HDL) ,参数自动配置的难点在于:首先模块本身的描述随参数配置的改变而变化; 其次当模块被更上层的父模块例化(instance) 时,其接口逻辑在不同参数配置下也可能不同。 在进行参数化设计时,只能采用硬件描述语言的宏定义,这种方法不仅不够灵活,而且代码复杂,冗余很多。
参数自动配置环境由一组运行脚本组成,主要通过两个工具Eperl 和Vperl 来进行参数配置,实现了代码的即配置即生成。 Eperl 的作用是定制内部的逻辑,Vperl 的作用是生成模块间的连接关系。 基于Verilog 硬件描述语言的参数自动配置过程如下。
1) 参数首先被送到3.evp 文件,这是同时包括Eperl 语法和Vperl 语法的文件,因为Eperl 和Vperl 电路的语法非常简洁,所以设计师不仅不用担心会在这里陷入困境,反而会大大减少设计强度。
2) 3.evp 文件经过Eperl 处理后生成3.vp 文件,这是只包含Vperl 语法的文件,这时所有的参数都被解析,模块内参数配置都已经完成了。
3) 3.vp 经过Vperl 处理后, 最后生成3.v 文件, 即电路的Verilog 描述。 因此在参数化设计中,与传统设计不同,设计师的描述文件是3.evp 文件,而Verilog 代码只是作为设计的中间代码出现。 参数自动配置环境建立了一系列脚本程序来自动解析相关文件的相互关系,并生成整个系统的硬件描述语言描述。
Eperl 最初用作HTML 页面生成,适合于静态文本相当多,但是又有一部分代码需要动态生成的场合。 因为大多数硬件描述是静态代码,只有一小部分需要可配置,所以Eperl 非常适用于电路的硬件描述。 Eperl 结合了Perl 的解释功能,相当于在原文本结构中插入了一段内嵌代码。 这段内嵌代码最终可以生成想要配置的硬件结构。 Eperl 利用Print 结构来传递所有的内嵌代码。 Eperl 所做的相当于程序员写一个用于生成代码的Perl 脚本。 下面所示程序段是从dma _fifo.evp 文件中提取的一部分代码。
∥Synchronous FIFO.fifo_depth x fifo_width bit words.
&ModuleBeg ;
&Ports ;
&Regs ;
&Wires ;
< : $width_msb=$fifo_width21 ;
$depth_msb=$fifo_depth21 ;
$ptr_width=log ( $fifo_depth)/log (2) ;
$ptr_msb=$ptr_msb21 ;
: >
&Force ("mem","fifomem", < :=$width_msb : >,0,
< :=$depth_msb : >,0) ;
……
∥Update FIFO memory.
always @(posedge clk) begin
if ( rstp== 1′b0 & & writep== 1′b1 & &
fullp== 1′b0)
fifomem [ head ] <=din [ < :=$width_msb :
> :0 ] ;
end
∥Update the head register.
always @(posedge clk) begin
if ( rstp== 1’b1)
head [<:=$ptr_msb:>:0]<=<:=$ptr_width:>′b0 ;
else
if (writep== 1′b1 & & fullp== 1′b0)
head [ < :=$pt r_msb : > :0 ] < =
head [ < :=$pt r_msb : > :0 ] + 1 ;
end
..
在< :…… : > 中的就是Eperl 代码,以& 开始的是Vperl 代码, 如&ModuleBeg 表示模块的开始。 在FIFO 中,可配置的参数主要是2 个: FIFO 深度和FIFO 宽度,所以dma_fif.evp 文件对外只有2 个参数可调, 而其他的比如定义信号所需要的FIFO 寄存器的最高位width_msb 、head 指针的宽度ptr_width 等都可以用Eperl 脚本生成。 这与宏定义相比体现了Eperl 的灵活性。再比如可以通用寄存器组gpr.evp 文件利用for 循环来例化每一个寄存器,更充分地体现了使用Eperl 的灵活性。 for 循环的代码如下所示。
for($i = 0;$i<$reg _num;$i++) {
print"&Instance (\"iu_register\",\"iu_register_$i\ " );\n" ;
}
: >
……
3.evp 格式的文件经过Eperl 解析后生成
3.vp格式的文件。 dma _fifo.evp 经过Eperl 解析后生成的3.vp 文件如下所示。
∥Synchronous FIFO.fifo_depth x fifo _width bit words。
&ModuleBeg ;
&Ports ;
&Regs ;
&Wires ;
&Force ("mem" ,"fifomem" ,15 ,0 ,3 ,0) ;
……
∥Update FIFO memory。
always @(posedge clk) begin
if (rstp== 1′b0 & & writep==1′b1 & &fullp==1′b0)
fifomem [head]<= din [15 :0] ;
end
∥Update the head register。
always @(posedge clk) begin
if (rstp = = 1′b1)
head [1 :0 ] < = 2′b0 ;
else
if (writep = = 1′b1 & & fullp = = 1′b0)
head [1 :0 ] < = head [1 :0 ] + 1 ;
end
……
当然IP 模块的不同配置会造成SOC 系统信号的不同。 如向系统中添加通用异步收发器(UART)模块,SOC 系统就要增加输入输出端口,并且要增加很多内部的连线,比如把IP 总线引入UART.Vperl 程序可以解决这个问题。
Vperl 的工作原理是通过分析模块内使用的信号的属性来确定模块的信号定义。 HDL 具有一定的语法结构,比如Verilog 有2 种信号类型,always 块中的<= 操作符左边一定是reg 类型信号,由此取reg 类型的补集就是wire 类型,除非显式地通过&Force()声明为其他类型,如在dma_fifo.vp 中的第6 行代码,声明为二维reg 数组类型。 分析模块也可以确定模块的输入输出,如果一个信号在模块中自始至终没有被赋值过,那么这个信号必然是input信号;同理,如果一个信号被赋值但从来没被使用过,就是output 信号; 如果既被赋值过,又被使用过,那么这个信号有很大可能是这个模块的内部信号,不是模块的端口,除非显式地通过&Force ( ) 声明为inout类型。 对于模块中例化的子模块(在3.vp 中用&Instance() 语句声明) ,Vperl 程序首先分析模块间的从属关系,并先处理最底层的子模块代码,在自动分析子模块接口的输入输出属性后,缺省地把子模块的输入输出信号作为上层模块的接入信号,并在上层模块中自动进行连接,当然Vperl 也提供了&Connect () 语句来更改子模块接入上层模块的信号名。 所以用Vperl 的格式书写的文件3.vp不需要在文件头部定义信号名。 如下所示是dma_fifo.vp 文件的示例代码。
∥Synchronous FIFO.fifo _depth x fifo _width bit words。
module dma _fifo (clk,rstp,din,writep,readp,dout ,emptyp,fullp);
input clk;
input rstp;
input [15:0] din;
input readp;
input writep;
output [15:0]dout;
output emptyp;
output fullp;
reg emptyp;
reg fullp;
reg [15:0]dout;
reg [ (DEPTH-1):0 ] tail;
reg [ (DEPTH-1):0 ] head;
reg [ (DEPTH-1):0 ] count;
reg [15:0 ] fifomem [0:MAX_COUNT];
……
∥Update FIFO memory。
always @(posedge clk) begin
if ( rstp==1′b0 && writep==1′b1 &&fullp==1′b0)
fifomem [head]<= din [15:0];
end
∥Update the head register。
always @(posedge clk) begin
if ( rstp==1′b1)
head[1:0 ]<=2′b0;
else
if (writep==1′b1 & & fullp==1′b0)
head[1:0 ]<=head[1:0 ] + 1;
end
……
在上述程序段中, din [15:0 ] 对应参数fifo_width=16 的情况, 如果在dma_top 模块中用Vperl 语法例化了dma_fifo ( &Instance(dmafifo);) ,那么Vperl把din[15:0]连接进dma_top ,并进而生成dma_top.v 代码,即dma_top.v 的信号定义。
Vperl 实际上是接管了模块间的信号连接工作,使得设计师不用担心参数改变对模块间关系的影响。 另外,Vperl 的另一项优点就是省去了每个HDL 文件中繁琐的信号定义,避免因为信号定义引起的语法错误(比如信号忘记定义、信号名打错等) ,大大加快源代码的编写速度。 当然,使用Vperl 需要遵循一定的语法。
在参数自动配置机制中,结合Eperl 和Vperl这两种工具就可以由选定的参数组合自动地配置硬件结构。 对于软件而言,由C 语言编写的应用程序不需要修改,但是编译器需要针对不同的执行单元的配置增加减少指令并进行优化。
SOC 参数优化
一个SOC 中包括了P1 , P2 , P3 , ..., Pn 等参数。每个参数的值都可以从一个有限集合中选取。 为了选择最优的参数值,也就是为了达到最优的系统配置,使功耗、性能和面积达到合理的目标,需要进行参数优化。 一种方法是运行一次仿真后,记录下指令序列(trace) ,将不同的参数配置结合指令序列通过纯软件的行为分析器进行分析。 这种方法的优势是速度快。 但是在不同的结构参数下,指令序列差别很大,特别是当指令集都发生改变时,结果的可信度大大降低;而且行为分析器对一些动态参数,比如动态电压,对功耗的影响无法估计,所以可能不得不进行多次仿真来获得较准确的功耗估计。 由于仿真时间一般较长,参数的优化算法对设计周期的影响很大。
参数的属性
在SOC 设计中的定制概念不再是晶体管级的定制,而是体现在模块级层面上,具体表现为系统的结构参数。 SOC 的系统参数具有一些属性,如大多数系统参数是相互影响的,这些相关性必须在参数配置时予以考虑。 相关性可以区分为数值相关和性能相关。 参数自身也有单调性和层次性。
数值相关
两参数的数值相关意味着一个参数的选择限制了另一个参数的选择范围。 例如对于容量限制为64 ×8 bit s 的直接存储器存取(DMA) 缓冲区,可以缓冲64 个8 位数据,但是当用于缓冲32 位数据时,只能放16 个数据。 在这里数据个数和数据格式是数值相关的。 再如可配置处理器的乘法器(MUL) 单元一般不会和乘累加器(MAC)单元一起出现,因为两者的功能是重叠的。 在进行参数选择时,利用数值相关性可以去除一些不可能的参数值,缩小参数的选择范围。
性能相关
两参数的性能相关意味着一个参数的改变会影响另一个参数的最优选择。性能相关是有方向性的,如果参数B 与A 性能相关, 则参数A 是B 的主动参数,参数B 是A 的随动参数。 在参数选择时,可以先确定A 再确定B 。如寄存器的个数相关与MUL 单元相关意味着MUL 单元是主动参数,而寄存器个数是随动参数。 两个参数也可能互相相关,也就是说参数A 和B 之间形成环路。 这时两个参数必须同时调整才能得到最优的方案。 例如高速缓存的组参数和每个缓存块的容量参数的性能是相关的。 在进行参数选择时,性能不相关的参数可以独立选取最优解。
单调性
很多参数对某一优化目标而言是单调函数。 例如决定UART 缓冲区大小的参数,对功耗这一优化目标,一般是缓冲区越大功耗越大。 参数的方向性也可以缩小参数的选择范围。
层次性
模块参数只影响模块自身,比如Cache 容量、Cache 行大小和关联组个数。 多个模块参数可以形成一个系统参数,比如从系统角度来看,Cache 失配率就是一个系统参数。 利用参数的层次性可以进行局部优化。
邻域搜索算法
参数的性能相关性是参数优化的主要难题。 首先需要一种数据结构来表征性能相关性。 图1 较好地表示了参数的性能相关性。 其中,参数用节点表示,而节点与节点间的连线表示两参数的性能相关。节点和节点之间的连线是有方向性的,从A 到B 的边表示参数B 与A 的性能相关。
图1 功耗性能相关图
随着SOC集成度的进一步提高,参数个数增多,导致设计选择空间急剧扩大,完全地对每一个参数组合进行评价是不可能的。 由于每次计算代价函数需要很长的时间,一些进化优化算法,如遗传算法不再适用。 邻域搜索算法是一种解决优化问题的方法。 一般来说,邻域搜索算法只能找到局部最优解。但是与大多数优化问题不同,SOC 的参数具有一定的方向性,系统设计师的经验可以帮助设计师找到一种较优化的参数组合,SOC 参数优化的主要目的是对系统性能进行微调,因此邻域搜索算法可以满足SOC 参数优化的要求。
以一个数据采集系统为例来验证领域搜索算法的有效性,数据采集系统的参数如表1 所示。 在这个应用中,系统从UART 接收数据,利用DMA 运送到内存,软件进行32 点FIR 滤波处理。 UART 的接收速率是10kB/s ,参数的性能相关图如图1 所示。
首先建立功耗相关图。在建图过程中,可以利用参数的数值相关进行参数归并,比如MUL的选用和MAC的选用可以归并为一个参数;以及区分参数的层次性,比如ICache 的失配参数N 由参数A 、B 、C 的一个子图组成。 接着设立代价函数f ( K) =系统功耗, K 是一个参数组合。 选择初始解Kbest.f best=f(Kbest ) 。 然后进行参数优化,参数优化的算法步骤如下。
1) 分析功耗相关图的拓扑顺序,包括较低层次的子图。 一般可以采用邻接表作为实现上述算法的数据结构。 首先从图中选一个入度为0 的节点并将其输出,然后从图中删掉此节点及其所有的边。 反复执行这两步,直至剩下的图中再也没有入度为0 的节点。 剩下的就是单个节点或环路。 分析拓扑顺序是为了确定参数优化的顺序。 拓扑序列的第一个节点就是第一个被优化的参数。
表1 数据采集系统参数表
2) 邻域映射定义为取当前优化节点的最近可选值。 仿真得到功耗f ( K) 。 如果f ( K) < f best ,则Kbest = K , f best = f ( K) ;否则退回原来的值。 重复步骤2) ,直到最近的可选值都被仿真过。
3) 选择下一个优化的节点,一般选择拓扑序列的下一个参数,但是在环路中,如果一个参数发生改变,则需要重新遍历一遍环路,这时应取环路上的节点。 如果该节点包含较低层次的子图,则选取子图中的节点。 重复步骤2) 。
实验结果
在进行优化实验前, 需要建立IP的功耗模型。 门级电路的功耗估计已经比较成熟了。 因为门电路的结构比较简单,可以比较容易地测量静态功耗,并根据标准单元的输入建立动态功耗的查询表。 但是门电路的功耗分析可能需要较大的运算量,而且门电路方法一般用于设计的后期,这时软硬件划分、硬件微结构设计、寄存器传输级(RTL)设计、综合都已经完成了。 这种方法无法对系统设计提供帮助或参考,这对系统设计师来说是很大的担心,也可能对设计周期产生影响。 当前功耗建模的方向主要是在RTL 或行为级领域。功耗常常与状态相关。 对于状态机电路来说,由于硬件模块一般有几个状态,在不同状态下,每个时钟周期上消耗的能量是不同的,可以利用IP 的状态机建模。 状态功耗模型并不一定和IP 核的电路状态机完全一致,它还可以考虑电路中的一些组合逻辑输出和关键输入信号如门控时钟信号等,只要这些信号的组合可以表示一个特殊的功耗状态即可。本文采用常用的状态功耗建模方法,利用TSMC0。18 工艺的门电路仿真数据对Cache 、GPR、各运算单元、DMA 和UART 进行建模。
在软硬件仿真平台上,对这个数据采集系统进行仿真。 由于应用程序较短以及采样速率不是很高,在实验中,共经过了28 个参数组合的仿真,得到的优化解如表2 所示。 如果对整个设计空间进行仿真则需要1.92×107 次仿真。
表2 对参数进行邻域搜索优化的结果
结语
原有的硬件描述语言难以适应参数化设计的要求,Eperl 和Vperl 提供了硬件描述语言的扩展。 利用参数自动配置环境嵌入Eperl 和Vperl ,可以生成对应特定参数配置的SOC 硬件语言描述。 参数优化建立在参数自动配置环境的基础上。 在分析SOC参数属性基础上提出的领域搜索算法可以大大减少SOC优化周期。 该设计环境已应用于一款嵌入式RISC处理器CK520和基于它的SOC开发,并取得了满意的效果。