RTL状态机低功耗与时序优化 - 第4节

4. 状态机综合优化:资源共享与状态合并

状态机优化,说白了就是两件事:省面积和提速度。我做了十几年RTL设计,见过太多人把状态机写得又大又慢,其实很多地方是可以优化的。

4.1 资源共享:别让硬件闲着

先说说资源共享。你想想看,如果两个状态都要做同样的运算,干嘛不共用一套电路?

核心思想:把多个状态中相同的操作提取出来,共用同一个硬件模块。

举个例子,一个简单的状态机,状态A和状态B都要计算 data_in + 8'h10:

// 优化前:每个状态各自计算
always @(*) begin
  case(state)
    STATE_A: result = data_in + 8'h10;
    STATE_B: result = data_in + 8'h10;
    STATE_C: result = data_in + 8'h20;
    default: result = 8'h00;
  endcase
end

// 优化后:共用加法器
wire [7:0] add_result = data_in + 8'h10;
always @(*) begin
  case(state)
    STATE_A: result = add_result;
    STATE_B: result = add_result;
    STATE_C: result = data_in + 8'h20;
    default: result = 8'h00;
  endcase
end

我在项目中遇到过这样的情况:一个通信协议的状态机,有4个状态都要做CRC校验。一开始每个状态都例化了一个CRC模块,面积直接炸了。后来改成共用,面积省了将近40%。

我的习惯:写状态机之前,先列个表,看看哪些操作是重复的。重复超过2次,就考虑共用。

4.2 状态合并:能省则省

状态合并,就是把功能相似的状态合并成一个。这招对面积优化特别有效。

什么时候可以合并?我总结了几条经验:

  • 输出完全相同:两个状态的所有输出信号都一样
  • 转移条件互补:一个状态的条件是另一个的取反
  • 中间状态可省略:比如等待1个周期和等待2个周期,可以用计数器代替

举个例子,一个简单的序列检测器:

// 优化前:状态太多
parameter IDLE = 3'b000;
parameter DETECT_1 = 3'b001;
parameter DETECT_2 = 3'b010;
parameter DETECT_3 = 3'b011;
parameter DONE = 3'b100;

// 优化后:合并中间状态
parameter IDLE = 2'b00;
parameter DETECT = 2'b01;
parameter DONE = 2'b10;

嗯,这里要注意:合并状态不能影响功能。我曾经为了省面积,把两个时序要求不同的状态合并了,结果时序收敛不了,得不偿失。

避坑指南:我曾经把两个输出相同但转移条件不同的状态合并,结果导致状态机进入了非法状态。后来我养成了一个习惯:合并前先画状态转移图,确认没有歧义。

5. 状态化简算法:隐含表法

状态化简,说白了就是找冗余状态。手工做的话,隐含表法是最实用的。

5.1 隐含表法的基本原理

隐含表法的思路很简单:如果两个状态在所有输入下的输出和下一状态都相同,那它们就是等价的,可以合并。

具体步骤:

  1. 画一个三角形表格,行和列都是状态
  2. 逐对比较状态,标记不等价对
  3. 递归检查隐含条件
  4. 找出所有等价状态对
  5. 合并等价状态

我刚开始学的时候觉得这方法挺麻烦的,但用熟了之后发现,它比凭感觉找冗余要靠谱得多。

5.2 实战案例

假设我们有4个状态:S0、S1、S2、S3。输入是x,输出是y。

当前状态 x=0时下一状态 x=0时输出 x=1时下一状态 x=1时输出
S0 S1 0 S2 0
S1 S0 0 S3 0
S2 S1 0 S2 0
S3 S0 0 S3 0

用隐含表法分析:

  • S0和S2:输出相同,但下一状态不同(S1 vs S1,S2 vs S2)—— 等价?
  • S1和S3:输出相同,下一状态也相同(S0 vs S0,S3 vs S3)—— 等价!

所以S1和S3可以合并。合并后状态数从4个减到3个。

我的经验:隐含表法适合状态数少于20个的情况。状态太多的话,建议用工具自动化简。

6. 状态机综合指导

综合工具怎么处理你的状态机?了解这个,你才能写出对综合友好的代码。

6.1 综合工具的状态编码策略

综合工具通常支持三种编码方式:

编码方式 面积 速度 适用场景
二进制编码 最小 较慢 状态数多,面积敏感
格雷编码 中等 中等 跨时钟域,减少毛刺
独热编码 最大 最快 状态数少,速度敏感

我个人习惯:状态数少于8个用独热码,8到16个用格雷码,超过16个用二进制码。当然,这也不是的,具体要看你的设计目标。

6.2 给综合工具的指导

你可以通过综合指令告诉工具你的偏好:

// 告诉工具用独热码
(* fsm_encoding = "one-hot" *) reg [3:0] state;

// 或者用综合指令
// synopsys fsm_encoding one-hot

注意:综合指令只是建议,不是强制。工具可能会根据你的设计自动选择更优的编码方式。

6.3 综合优化技巧

我总结了几条实用的综合优化技巧:

  • 避免全并行case:用 unique case 或 priority case 告诉工具你的意图
  • 不要用default状态做复位:复位逻辑单独写,别混在状态机里
  • 状态机输出用寄存器:组合逻辑输出容易产生毛刺,建议打一拍
  • 状态转移用非阻塞赋值:这是RTL设计的基本功,别搞错了

嗯,说到这个,我想起一个案例。有个同事写状态机,输出直接用组合逻辑,结果后仿的时候发现毛刺导致功能错误。后来改成寄存器输出,问题就解决了。

核心总结:状态机优化不是一蹴而就的,需要根据具体需求权衡面积和速度。资源共享和状态合并是面积优化的利器,隐含表法是状态化简的实用工具,而了解综合工具的行为能帮你写出更高效的代码。

状态机优化知识体系 资源共享 状态合并 状态化简 提取公共运算 共用硬件模块 减少面积开销 输出相同可合并 转移条件互补 中间状态可省略 隐含表法 等价状态识别 递归检查隐含条件 综合指导:编码策略 + 综合指令 目标:面积更小 · 速度更快 · 功耗更低 二进制编码 格雷编码 独热编码

好了,这一章的内容就到这里。状态机优化是个实践性很强的工作,光看理论是不够的,建议你找个实际项目练练手。下一章我们会聊状态机的时序分析和优化,到时候见。

可可精品资料库