状态机性能考察(代码编写)
2012-10-18
标签: 状态机

• 状态机代码编写

在大型系统中编码风格会极大的影响状态机的性能。状态机的代码编写方式一般是采用所谓的双always块风格。其一是时序逻辑,在时钟沿到来时将状态向量赋值为下一状态。其二是纯组合逻辑,使用case或if/else语句枚举当前状态和输入可能的组合,并指定下一状态和输出。

但是这并不总是最佳的选择,对于不同的状态机应当具体情况具体分析。下面结合一个例子来说明。实现的功能是“10”序列检测器,相继采样到1,0则输出一个周期的正脉冲,否则一直输出0。该状态机有1个输入:i,1个输出:o。使用Synplify pro 7.6综合,器件选择EPF10K10ATC144-1。

先考虑mealy机。状态图如下:

双always块风格的编码如下:

module test_machine2 (clk, rst_n, i, o);

input clk;

input rst_n;

input i;

output o;

reg o;

reg state;

reg nstate;

parameter S0=1'b0;

parameter S1=1'b1;

always @(posedge clk or negedge rst_n)

if(~rst_n)

state<=S0;

else

state<=nstate;

always @(state or i)

case(state)

S0:

begin

if(i)

nstate<=S1;

else

nstate<=S0;

o<=1'b0;

end

S1:

begin

if(i)

begin

nstate<=S1; o<=1'b0;

end

else

begin

nstate<=S0; o<=1'b1;

end

end

default:

begin

nstate<=S0; o<=1'b0;

end

endcase

endmodule

综合结果:

Total LUTs:2

Register bits:1

Worst Slack:995.595

Estimated Frequency:227.0MHz

前仿真波形:

仿真结果并不理想,因为输出未经同步,其输出时间随输入随时变化。加上输出同步后的代码如下:

reg no;

将组合always块中对o的赋值都替换成对no进行,然后加入:

always @(posedge clk or negedge rst_n)

if(~rst_n)

o<=1'b0;

else

o<=no;

综合后的结果:

Total LUTs:2

Register bits:2

Worst Slack:994.900

Estimated Frequency:196.1MHz

前仿真波形:

而同样功能单always块编码风格的代码如下:

module test_machine (clk, rst_n, i, o);

input clk;

input rst_n;

input i;

output o;

reg o;

reg state;

parameter S0=1'b0;

parameter S1=1'b1;

always @(posedge clk or negedge rst_n)

if(~rst_n)

begin

state<=S0;

o<=1'b0;

end

else case(state)

S0:

begin

if(i)

state<=S1;

else

state<=S0;

o<=1'b0;

end

S1:

begin

if(i)

begin

state<=S1; o<=1'b0;

end

else

begin

state<=S0; o<=1'b1;

end

end

default:

begin

state<=S0; o<=1'b0;

end

endcase

endmodule

其综合结果(系统参数和RTL网表)和仿真波形与增加了输出同步后的双always块编码风格完全相同。

在这个例子中,单always块风格的状态机变量更少,由于将组合逻辑与时序逻辑划分的工作交给综合工具来完成,因此代码在功能上比较简洁清晰。而性能等同于双always块风格的状态机。(当然,在这种小case中也看不出性能能有什么差别来,因此这个结论并不能推广。)

单always还有一个好处,它在仿真效率上略胜一筹。在仿真过程中,单always块状态机仅仅在时钟变化时才需要计算下一状态和输出。而相应的双always块状态机由于有always组合逻辑块,每当输入变化时都要计算下一状态和输出。

那么双always块状态机的优势体现在哪里呢。在大规模状态机中我也作了实验。对1553B总线协议芯片BC模式主状态机,采用两种编码风格综合结果如下(综合工具采用synplify pro 7.2,器件选用EPF10K100ARC240-1,当时没有记录Worst Slack):

双always

单always

Total LUTs

713 of 4992 (14%)

607 of 4992 (12%)

Register bits

98 (47 using enable)

97 (45 using enable)

Estimated Frequency

75.9MHz

52.2MHz

在这个例子中,双always块状态机占用的LUT更多,但是可达频率更高。随着芯片技术的进步,大规模芯片越来越便宜,我们平时更注意的是频率的提高而不是资源的节省。双always块无疑是面积换速度的好方法。

由于以前综合技术的滞后,很多经典文章都推荐双always块编码风格,手动划分组合逻辑与时序逻辑以免综合出垃圾逻辑。而随着技术的进步,将这样的工作交给机器去完成反而可能节约资源,特别是对于新手而言手动划分并不一定可靠时。

双always并不是唯一的选择,对于

1 初学者,为了避免手动划分的失误(很多初学者在预期的组合逻辑中往往综合出锁存器,或者出现例子中输出未同步的现象)

2 可用资源比较苛刻的系统,为了节省资源

3 大型的不追求速度的复杂系统,为了功能级描述的清晰

可以考虑使用单always块风格编写状态机。

以上是对于Mealy机的讨论,下面讨论Moore机。

实现同样的功能,“10”序列检测器,的Moore机状态图如下:

由于Moore机的输出只与当前状态有关,因此实现同样功能的Moore机状态数往往多于Mealy机。

由于输出仅从当前状态组合产生,而下一状态从当前状态和输入共同组合产生。为了清晰,将这两个组合逻辑分开放在不同的always块中。多always块风格的状态机代码如下:

module test_machine4 (clk, rst_n, i, o);

input clk;

input rst_n;

input i;

output o;

reg o;

reg [1:0] state;

reg [1:0] nstate;

parameter S0=2'b00;

parameter S1=2'b01;

parameter S2=2'b10;

always @(posedge clk or negedge rst_n)

if(~rst_n)

state<=S0;

else

state<=nstate;

always @(state)

if(state==S2)

o<=1'b1;

else

o<=1'b0;

always @(state or i)

case(state)

S0:

begin

if(i)

nstate<=S1;

else

nstate<=S0;

end

S1:

begin

if(i)

nstate<=S1;

else

nstate<=S2;

end

S2:

begin

if(i)

nstate<=S1;

else

nstate<=S0;

end

default:

begin

nstate<=S0;

end

endcase

endmodule

单always块风格的状态机编码如下:

module test_machine3 (clk, rst_n, i, o);

input clk;

input rst_n;

input i;

output o;

reg o;

reg [1:0] state;

parameter S0=2'b00;

parameter S1=2'b01;

parameter S2=2'b10;

always @(posedge clk or negedge rst_n)

if(~rst_n)

begin

state<=S0;

o<=1'b0;

end

else case(state)

S0:

begin

if(i)

begin

state<=S1;

o<=1'b0;

end

else

begin

state<=S0;

o<=1'b0;

end

end

S1: // 注意,当前状态(S1)下可以有不同的输出,因为

begin

if(i)

begin

state<=S1;

o<=1'b0; //输出取决于下一状态,而非当前状态

end

else

begin

state<=S2;

o<=1'b1; //输出取决于下一状态,而非当前状态

end

end

S2:

begin

if(i)

begin

state<=S1;

o<=1'b0;

end

else

begin

state<=S0;

o<=1'b0;

end

end

default:

begin

state<=S0;

o<=1'b0;

end

endcase

endmodule

这两者综合后的参数相同:

Total LUTs:2

Register bits:2

Worst Slack:994.900

Estimated Frequency:196.1MHz

其仿真波形也与前面的Mealy机仿真波形一致。

有趣的是两者的RTL图看起来形式不同,实际上对应的门级网表是一样的:

多always块状态机RTL:

单always块状态机RTL:

下面对两者进行比较。

从输出与当前状态对应关系的描述上说,多always块代码的描述更清晰更接近于状态图,而单always块代码则看起来难以理解。这是因为多always块状态机是组合输出,当前状态(state)变化立刻引起输出变化,这一点与状态图相符。而单always块是寄存器输出,当前状态(state)变化后,要等到下一次时钟到来检测到此变化才能引起输出的变化,这导致了输出滞后了一个时钟周期。因此,为了将输出提前,在单always块的Moore机中输出是取决于下一状态而不是当前状态!在例子中,每次状态转移到S2时(state<=S2),必然伴随着输出1(o<=1'b1)。而转移到其他状态时总是输出0。

多always块状态机往往是组合输出。(在这个例子中,由于输出只有1位,并且刚好等于一个状态位,因此在例子中的多always块状态机的输出刚好是直接从寄存器输出。)为了将该输出同步,需要再增加一级寄存器:

reg no;

将组合always块中对o的赋值都替换成对no进行,然后加入:

always @(posedge clk or negedge rst_n)

if(~rst_n)

o<=1'b0;

else

o<=no;

这样不但导致综合输出的逻辑变大,并且输出滞后了一个时钟,如图:

作为总结,对于Moore机,单always块风格代码与状态图的对应较差,难以理解,但能够产生寄存器输出。多always块风格代码清晰易懂,与状态图的对应较好,但通过组合逻辑输出,若再加寄存器同步则会导致输出滞后一个周期。

另一个有趣的现象:Active HDL 6.2根据状态图自动生成的状态机综合出的门级网表(带输出同步),与多always块综合出的门级网表(带输出同步)对比如下(上面是自动生成,下面是手动代码):

• 器件结构

器件结构对于逻辑实现的影响不言而喻。器件结构不但会影响所采用的综合技术,而且也与状态机的状态编码技术互相作用。

早期的器件由二极管、晶体管和简单的门构成。当时使用“状态编码邻接规则”来指导状态的编码和逻辑的化简。具体来说,是用卡诺图表示下一状态和输出函数,通过合并卡诺图中的“1”使得描述FSM组合逻辑部分的乘机项之和表达式中的乘机项最大,而数量最少。(当年我们作数字逻辑试验就干这个,先用卡诺图化简,然后用74系列搭逻辑)。随后技术发生了变化,而上面的原理依然是各种新算法的基础。

PLA出现时期先后有很多逻辑化简和状态编码方法。例如KISS、MAXAD、NOVA等。这些都是基于符号最小化的方法。它们假定逻辑功能采用两层PLA实现,并在这一假定下试图生成最小势的符号覆盖。

多层逻辑器件的出现,使得上面这些方法难以达到优化的效果。由于增加了搜索空间,同时难以构造评价标准,使得符号最小化在多层逻辑领域难以实现。著名的算法例如MUSTANG,它试图使得输出函数表达式中的公共项的数量和大小最大,通过对选定的状态对进行邻接编码分配来完成。

而查找表器件进一步增加了多层逻辑的挑战。在查找表FPGA中,无法使用项和文字的数量来评估功能实现的成本。例如,一个5输入LUT实现一个5输入与门(1个项,5个文字)与实现一个5输入异或门(16个项,80个文字)需要同要的开销。面向FPGA的综合中的特殊工作主要是对输出函数进行并行分解和顺序分解。并行分解是将一个多输出函数分解为多个模块,每个某块可用给定的逻辑块(如4输入1输出LUT)来实现,使得逻辑块的数量最少,逻辑块之间的互联最少。顺序分解是增加特定的过滤模块,将多输入函数的携带信息较少的部分输入进行预处理,产生精简的输出,作为函数逻辑功能的输入。主要算法有基于信息关系与度量的算法、基于语义分析的算法等。

可见,不同的器件类型需要不同的综合方法与之相适应。即使是同一种类的器件,内部结构不同也有不同的综合过程(看那些第三方的综合工具也真够累的),实现性能也有差别。

可能会用到的工具/仪表
本站简介 | 意见建议 | 免责声明 | 版权声明 | 联系我们
CopyRight@2024-2039 嵌入式资源网
蜀ICP备2021025729号