Verilog编程技巧01——如何编写三段式状态机

前言

Verilog编程技巧系列文章将聚焦于介绍Verilog的各种编程范式或者说技巧,编程技巧和编程规范有部分重合,但并非完全一样。规范更注重编码的格式,像变量命名、缩进、注释风格等,而编程技巧则更偏重更直观易读、更便于维护、综合效果更好的Verilog/SV代码写法,像:如何编写状态机、如何进行参数化设计、如何进行流水线设计等。可以说编程技巧就是一些编程套路,熟练掌握这些技巧可帮助我们更高效的完成FPGA开发工作。

本文是Verilog编程技巧系列的第一篇文章,介绍了如何编写三段式状态机。

在FPGA设计中,三段式状态机因其结构清晰、可靠性高等特点,成为实现状态机的最佳方式。本文主要说明了以下问题:

  1. 如何评价一个状态机写的好不好;
  2. 为什么三段式状态机更好;
  3. 为什么更推荐独热码;
  4. 第三段输出逻辑应该怎么写。

最后,本文分享了Verilog与SV的三段式状态机模板。通过阅读本文,你将能够更深入的理解三段式状态机的写法及其背后的原理。

本文主要参考了Clifford E. Cummings的文章,他是数字电路设计领域的技术先驱,享有一定国际声誉。他的文章可从此网站(界面如下图)下载:http://www.sunburst-design.com/papers/。他的一些关于状态机的文章给本文的一些结论提供了理论和实验支撑。

一. 什么是状态机

状态机是描述系统行为的数学模型。包含状态(系统某时刻状况)、转移(状态间切换)、事件(触发转移因素)和动作(转移时执行的操作),用于分析和设计系统逻辑,在多领域广泛应用。

依据状态数量,状态机可分为两类:

特征有限状态机(FSM)无限状态机(ISM)
全称与简称Finite State Machine,简称FSMInfinite State Machine,简称ISM
状态数量有限无限
转移方式离散、明确条件可能连续(如基于变量变化)
典型应用离散逻辑控制连续系统或复杂动态系统

在HDL编码时,我们构建的总是有限状态机,所以这里我们说的状态机默认是指有限状态机

依据输出与当前状态的关系,状态机也可分为两类:

状态机类型输出决定因素特点描述
Moore型(摩尔型)仅取决于当前状态任意时刻,只要状态确定,输出就确定,输出与输入无关
Mealy型(米利型)取决于当前状态和输入信号输出不仅和当前状态有关,还受当前输入的影响

这两种状态机的命名均来源于人名,看着比较奇怪。

实际应用中,无需刻意去区分状态机属于哪种类型,没有什么意义。

在Verilog/SV编码中,状态机应用极其广泛,它几乎可以描述所有逻辑,也就是说如果你想,所有的模块的内部逻辑都可以写成状态机。所以,研究如何编写更好的状态机很有必要。

二. 一段、二段、三段与四段式状态机

在Verilog/SV中,状态机按功能可以分为三个部分:

  1. 状态转移:负责在不同的状态之间进行切换,它决定了状态机如何从当前状态迁移到下一个状态。
  2. 状态判断:根据当前状态和输入信号来确定下一个状态是什么。
  3. 输出逻辑:根据当前状态机的状态来产生相应的输出信号。

根据这三部分在代码中用几个always块来描述,可将状态机分为以下几种:

2.1 一段式状态机

状态转移、状态判断与输出逻辑全都写在同一个always块中,称为一段式状态机。示例如下:

// 状态定义
localparam S0 = 1'b0, S1 = 1'b1;
reg state;

// 状态转移、判断与输出
always @(posedge clk) begin
  if (~rstn) {state, out} <= {S0, 1'b0};
  else
    case (state)
      S0: if (in) state <= S1; else state <= S0; out <= 1'b0;
      S1: if (in) {state, out} <= {S0, 1'b1}; else state <= S0;
      default: {state, out} <= {S0, 1'b0};
    endcase
end

出于排版的考虑,去掉了非必要的begin-end,尽量缩短了代码行数,下同。

2.2 二段式状态机

状态转移写在一个时序always块中状态判断与输出写在一个组合always块中,称为二段式状态机。示例如下:

// 状态定义
localparam S0 = 1'b0, S1 = 1'b1;
reg state, next;

// 第一段:时序逻辑,状态转移
always @(posedge clk) begin
  if (~rstn) state <= S0;
  else state <= next;
end

// 第二段:组合逻辑,次态和输出逻辑
always @(*) begin
  case (state)
    S0: if (in) {next, out} = {S1, 1'b0}; else {next, out} = {S0, 1'b0};
    S1: if (in) {next, out} = {S0, 1'b1}; else {next, out} = {S0, 1'b0};
    default: {next, out} = {S0, 1'b0};
  endcase
end

2.3 三段式状态机

状态转移写在一个时序always块中状态判断写在一个组合always块中输出写在一个时序或组合always块中,称为三段式状态机。示例如下:

// 状态定义
localparam S0 = 1'b0, S1 = 1'b1;
reg state, next;

// 第一段:时序逻辑,状态转移
always @(posedge clk) begin
  if (~rstn) state <= S0;
  else state <= next;
end

// 第二段:组合逻辑,计算次态
always @(*) begin
  case (state)
    S0: next = in ? S1 : S0;
    S1: next = in ? S0 : S0;
    default: next = S0;
  endcase
end

// 第三段:时序逻辑,输出逻辑
always @(posedge clk) begin
  if (~rstn) out_a <= 1'b0;
  else out_a <= (state == S1 && in) ? 1'b1 : 1'b0;
end

// 第三段:组合逻辑,输出逻辑
always @(*) begin
  out_b = (state == S1 && in) ? 1'b1 : 1'b0;
end

对于第三段,因为时序逻辑的输出相对组合逻辑会慢一个时钟周期,如果对输出延时有极高的要求,则可以考虑用组合逻辑。但通常还是建议用时序逻辑,时序逻辑的输出更加稳定(无竞争和冒险),也更便于整体的时序分析和约束。

2.4 四段式状态机

四段式状态机是将三段式状态机的输出写成两个always块,一个固定为组合always块,写输出使能逻辑;另一个固定为时序always块,写输出逻辑。示例如下:

// 前面与三段式相同,仅第三段输出逻辑并分为两段
// 组合逻辑,输出使能
always @(*) begin
  out_en = (state == S1 && in);
end

// 时序逻辑,输出逻辑
always @(posedge clk) begin
  if (~rstn)
    out <= 1'b0;
  else if (out_en) 
    out <= 1'b1;
  else 
    out <= 1'b0;
end

从实现的功能上来说,三段式和四段式完全相同,但在设计理念灵活性可维护性等方面,两者存在明显区别:

对比维度三段式状态机四段式状态机
核心思想输出与状态/输入直接耦合,无中间控制信号。引入输出使能信号(out_en),分离输出条件与赋值逻辑。
代码结构输出逻辑与状态转移混合在时序逻辑中。输出使能(组合逻辑)与输出赋值(时序逻辑)分离,模块化更清晰。
输出条件修改需直接修改时序逻辑中的条件判断,可能影响其他逻辑。只需修改组合逻辑中的out_en,输出赋值逻辑保持不变。
扩展性新增输出条件需修改时序逻辑,可能导致冗余。新增条件只需扩展out_en逻辑,不影响输出赋值部分。
适用场景简单场景(如固定序列检测、单一输出控制)。复杂场景(如多条件输出、动态使能控制)。

总的来说,四段式状态机在负责场景有一些代码维护上的优势。

根据2019 CummingsSNUG2019SV_FSM 状态机设计.pdf中的内容,四段式状态机在复杂逻辑的综合中会有一定优势,但三段式代码更加简洁,更符合直观逻辑,所以在FPGA开发中,作者推荐总是优先使用三段式,仅在复杂场景开发的最后,将三段式改为四段式,以提升综合效果。

综合效果:指的是综合工具对不同代码写法的优化支持程度。现代综合工具对四段式会有更好的优化效果,使得四段式在资源效率和时序性能方面通常优于三段式,但具体效果还需实测。

综上,针对FPGA开发,本文仅讨论三段式状态机,如需针对复杂场景进行优化,各位同学可根据需要将三段式改为四段式,并实际比较综合后的资源效率和时序性能确定最终方案。

三. 什么样的状态机是好的状态机?

换句话说,评价一个状态机好坏的标准是什么?一般来说,可以从以下4个维度进行评价:

  1. 可维护性:代码易于修改、扩展,适应需求变更。

  2. 可读性:代码结构清晰、逻辑紧凑,避免冗余,便于阅读理解。

  3. 可调试性:支持高效的仿真验证,便于在仿真或实测时快速定位问题。

  4. 综合效果:对综合工具友好,代码在转化为硬件时的面积、速度与功耗等性能指标优异。

上述四种状态机从这四个维度进行评价,表格如下:

指标一段式二段式三段式四段式
可维护性★★★★★★★★★
可读性★★★★★★★★
可调试性★★★★★★★★
综合效果★★★★★★★★★

说明:

  1. 一颗星(★)表示“差”,两颗星(★★)表示“良”,三颗星(★★★)表示“优” 。
  2. 四段式的综合效果在非常复杂场景可能略好于三段式,但绝大多数场景下,三段式的综合效果同样优异,两者区别不大,所以两者都标注为三颗星。

对于一个功能需求来说,最重要的两件事是开发效率模块性能。可维护性、可读性和可调试性影响开发效率,综合效果则影响模块性能。

综上,我们可以得出结论:

  1. 永远不应该用一段式状态机:一段式状态机逻辑不清,导致代码难以维护、调试和扩展,极易成为"屎山代码"的一部分。其唯一优势(综合效果高)完全无法抵消后期维护的灾难性代价。即使在非常简单的场景中,也不应该使用。
  2. 不推荐用二段式状态机:二段式虽然分离了部分逻辑,但输出仍依赖组合逻辑,存在 输出毛刺风险时序收敛困难,且可调试性和综合效果低于三段式,没有独特的应用场景优势。
  3. 不推荐直接用四段式状态机:四段式虽然逻辑分层更彻底,但会导致 代码冗余开发效率下降。它仅适用于极少数复杂场景(例如多路独立输出需严格时序控制),对大多数设计属于"过度设计"。
  4. 仅推荐使用三段式状态机:三段式是经过验证的最佳实践,没有短板。

四. 状态编码的类型和优缺点

状态编码指的是状态机的不同状态用怎样的二进制去表示,状态编码一般有以下几种类型:

  1. 二进制码(Binary):用二进制自然序列表示状态,N个状态需满足 2^N ≥ S(S为状态总数)。8个状态编码为 000, 001, 010, 011, 100, 101, 110, 111
  2. 格雷码(Gray Code):相邻状态仅有一位不同,形成循环单步跳变。3位格雷码为 000, 001, 011, 010, 110, 111, 101, 100
  3. 独热码(One-Hot):每个状态独占一个比特位,总位数等于状态数(N = S)。3个状态编码为 100, 010, 001
  4. 零空闲独热码(Zero-Idle One-Hot):独热码基础上引入全零( 000...)作为空闲状态,其他状态为单一高位。5个状态编码为 0000(空闲态), 1000, 0100, 0010, 0001
  5. 独冷码(One-Cold):与独热码相反,每个状态由唯一一个低电平位(0)表示,其他位为1。3个状态编码为 011, 101, 110
  6. 约翰逊码(Johnson Code):也称为扭环码(Twisted Ring Code),是一种特殊的环形移位码,相邻状态通过左移或右移,然后补移出位的反码生成,最终形成一个环。3位右移约翰逊码为 000, 100, 110, 111, 011, 001(循环)。
  7. 混合编码(Hybrid Encoding):高位独热码 + 低位二进制码。状态数>20时,平衡资源与性能。

在FPGA设计中,零空闲独热码、独冷码与独热码的资源消耗和实现逻辑高度相似,而约翰逊码和混合编码的实际应用场景较为局限。因此,工程中的核心选择通常集中在以下三种编码:二进制码、格雷码和独热码。这三种编码方式的优缺点对比如下表:

指标二进制码(Binary)格雷码(Gray Code)独热码(One-Hot)
时序逻辑资源消耗
(触发器,FF)
★★★★☆
极低:仅需 ⌈log2(N)⌉ 个触发器(N为状态数)
★★★☆☆
:触发器数与二进制相同,但需额外触发器存储转换码(可选)
★★☆☆☆
:需 N 个触发器,但FPGA中触发器资源充足
组合逻辑资源消耗
(LUT)
★★☆☆☆
中高:状态跳变需复杂组合逻辑(如状态译码器)
★★★☆☆
:需异或门实现码值转换
★★★★☆
极低:状态跳变仅需单比特操作
时序性能★★☆☆☆
:多比特翻转限制频率
★★★☆☆
中高:单比特跳变,但转换逻辑增加延迟
★★★★★
最优:单比特跳变,路径最短,适合高频
功耗★★☆☆☆
:多比特翻转动态功耗大
★★★★☆
:单比特翻转降低功耗
★★★☆☆
:静态功耗略高(触发器多),动态功耗低
容错性★☆☆☆☆
:非法状态多,需额外检测
★★★☆☆
:非法状态较少
★★★★☆
:非法状态极易检测(非单比特为1)
可读性★★★☆☆
:自然顺序易理解,跳变逻辑复杂
★★☆☆☆
中低:编码非自然排列,需查表辅助
★★★★★
极优:每个状态对应唯一比特,仿真直观
推荐优先级★★☆☆☆
- 资源敏感型设计
- 简单状态机
★★★☆☆
- 低功耗/跨时钟域
- 异步FIFO
★★★★★
- FPGA高频设计
- 状态数≤16

星级说明:

  • ★★★★★: 强烈推荐(显著优势,无替代方案)
  • ★★★★☆: 推荐(优势明显,适用场景明确)
  • ★★★☆☆: 一般推荐(需权衡利弊)
  • ★★☆☆☆: 不推荐(仅限特定场景)
  • ★☆☆☆☆: 避免使用(缺陷明显)

由此可以得出结论

  1. 二进制码(★★☆☆☆)
    • 仅限极简设计(如ASIC或低成本FPGA),需承担时序和功耗风险。
    • 添加输出寄存器过滤毛刺。
  2. 格雷码(★★★☆☆)
    • 低功耗/跨时钟域专用,如异步FIFO指针同步,但需手动添加转换逻辑。
    • 避免在复杂状态机中滥用。
  3. 独热码(★★★★★)
    • FPGA设计首选,因LUT资源占用低、时序性能最优、容错性高,完美适配FPGA架构。
    • 仅需注意状态数≤16(超过时可混合编码)。

所以,在FPGA中进行状态机设计时,总是应该使用独热码

五. 第三段输出应该用组合逻辑还是时序逻辑?

指标组合逻辑输出时序逻辑输出
优点

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/85953.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

豆包和deepseek 元宝 百度ai区别是什么

豆包、DeepSeek、元宝和百度 AI 有以下区别&#xff1a; 开发公司 豆包5&#xff1a;由字节跳动公司基于云雀模型开发。DeepSeek4&#xff1a;是深度求索打造的开源多模态大模型。元宝1&#xff1a;是腾讯混元模型的落地产品&#xff0c;整合了 DeepSeek - R1 与混元模型。百…

网页端 js 读取发票里的二维码信息(图片和PDF格式)

起因 为了实现在报销流程中&#xff0c;发票不能重用的限制&#xff0c;发票上传后&#xff0c;希望能读出发票号&#xff0c;并记录发票号已用&#xff0c;下次不再可用于报销。 基于上面的需求&#xff0c;研究了OCR 的方式和读PDF的方式&#xff0c;实际是可行的&#xff…

读文献先读图:GO弦图怎么看?

GO弦图&#xff08;Gene Ontology Chord Diagram&#xff09;是一种用于展示基因功能富集结果的可视化工具&#xff0c;通过弦状连接可以更直观的展示基因与GO term&#xff08;如生物过程、分子功能等&#xff09;之间的关联。 GO弦图解读 ①内圈连线表示基因和生物过程之间的…

pandas随笔

主要操作两个对象&#xff1a;一维带标签数组 和 二维表格DataFrame 一维带标签数组Series pd.Series([1, 3, 5, np.nan, 6, 8]) &#xff0c;结果如下&#xff1a; 可指定索引&#xff0c;pd.Series([1, 3, 5], index[a, b, c]) 二维表格DataFrame 创建时需要指定列名&a…

java教程笔记(十一)-泛型

Java 泛型&#xff08;Generics&#xff09;是 Java 5 引入的重要特性之一&#xff0c;它允许在定义类、接口和方法时使用类型参数。泛型的核心思想是将类型由具体的数据类型推迟到使用时再确定&#xff0c;从而提升代码的复用性和类型安全性。 1.泛型的基本概念 1. 什么是泛…

力扣刷题(第四十九天)

灵感来源 - 保持更新&#xff0c;努力学习 - python脚本学习 反转链表 解题思路 迭代法&#xff1a;通过遍历链表&#xff0c;逐个改变节点的指针方向。具体步骤如下&#xff1a; 使用三个指针&#xff1a;prev&#xff08;初始为None&#xff09;、curr&#xff08;初始为…

设置应用程序图标

(1)找一张图片 (2)然后转ico图片 在线生成透明ICO图标——ICO图标制作 验证16x16就可以 降低exe大小 (3) 在xxx.pro修改 添加 &#xff08;4&#xff09; 删除 build 和 xxxpro_user文件 (5)编译project 和运行xx.exe (6)右键 设置快捷方式

免费wordpress模板下载

西瓜红色的免费wordpress模板&#xff0c;简洁实用又容易上手&#xff0c;适合新手使用。 下载 https://www.waimaoyes.com/moban/2231.html

【React】React 18 并发特性

React 18 引入了 并发特性&#xff08;Concurrent Features&#xff09;&#xff0c;这是一次对 React 渲染机制的重大升级&#xff0c;让 React 更加智能、响应更流畅、资源更节省。 我们来详细讲解一下它的原理、特性、API 以及实际应用。 &#x1f9e0; 一、什么是并发特性…

FFMPEG 提取视频中指定起始时间及结束时间的视频,给出ffmpeg 命令

以下是提取视频中指定起始时间及结束时间的 ffmpeg 命令示例: bash 复制 ffmpeg -i input.mp4 -ss 00:01:30.00 -to 00:05:00.00 -c copy output.mp4 其中,-i input.mp4 是指定要处理的输入视频文件为 “input.mp4”。 -ss 00:01:30.00 表示指定视频的起始时间为 1 分 30 …

mybatis的if判断==‘1‘不生效,改成‘1‘.toString()才生效的原因

mybatis的xml文件中的if判断‘1’不生效&#xff0c;改成’1’.toString()才生效 Mapper接口传入的参数 List<Table> queryList(Param("state") String state);xml内容 <where><if test"state ! null and state 1">AND EXISTS(select…

AI 模型分类全解:特性与选择指南

人工智能&#xff08;AI&#xff09;技术正以前所未有的速度改变着我们的生活和工作方式。AI 模型作为实现人工智能的核心组件&#xff0c;种类繁多&#xff0c;功能各异。从简单的线性回归模型到复杂的深度学习网络&#xff0c;从文本生成到图像识别&#xff0c;AI 模型的应用…

01-python爬虫-第一个爬虫程序

开始学习 python 爬虫 第一个获取使用最多的网站-百度 源代码 并将源代码保存到文件中 from urllib.request import urlopenurl https://www.baidu.com resp urlopen(url)with open(baidu.html, w, encodingutf-8) as f:f.write(resp.read().decode(utf-8))知识点&#xf…

四六级监考《培训学习》+《培训考试》

1 线上注册 &#xff08;网址&#xff1a; https://passport.neea.edu.cn 2 登录培训平台参加线上必修课程学习和考核 &#xff08;平台网址&#xff1a; https://kwstudy.neea.edu.cn 注意选择学员入口&#xff09; 3 考试要求&#xff1a;考试成绩须达应到80分以上&#xf…

回顾Java与数据库的30年历程

当 Java 1.0 于 1996 年推出时&#xff0c;语言和互联网都与今天大不相同。当时&#xff0c;网络主要是静态的&#xff0c;而 Java 承诺通过注入交互式游戏和动画来为网络注入活力&#xff0c;这一承诺极具前景。根据 1995 年写给《连线》杂志的 David Banks 的说法&#xff0c…

simulink有无现成模块可以实现将三个分开的输入合并为一个[1*3]的行向量输出?

提问 simulink有无现成模块可以实现将三个分开的输入合并为一个[1*3]的行向量输出&#xff1f; 回答 Simulink 本身没有一个单独的模块能够直接将三个分开的输入合并成一个 [13] 行向量输出&#xff0c;但是可以通过 组合模块实现你要的效果。 ✅ 推荐方式&#xff1a;Mux …

代码训练LeetCode(24)数组乘积

代码训练(24)LeetCode之数组乘积 Author: Once Day Date: 2025年6月5日 漫漫长路&#xff0c;才刚刚开始… 全系列文章可参考专栏: 十年代码训练_Once-Day的博客-CSDN博客 参考文章: 238. 除自身以外数组的乘积 - 力扣&#xff08;LeetCode&#xff09;力扣 (LeetCode) 全…

NLP学习路线图(十七):主题模型(LDA)

在浩瀚的文本海洋中航行&#xff0c;人类大脑天然具备发现主题的能力——翻阅几份报纸&#xff0c;我们迅速辨别出"政治"、"体育"、"科技"等板块&#xff1b;浏览社交媒体&#xff0c;我们下意识区分出美食分享、旅行见闻或科技测评。但机器如何…

vue对axios的封装和使用

在 Vue 项目中&#xff0c;使用 axios 进行 HTTP 请求是非常常见的做法。为了提高代码的可维护性、统一错误处理和请求拦截/响应拦截逻辑&#xff0c;对axios进行封装使用。 一、基础封装&#xff08;适用于 Vue 2 / Vue 3&#xff09; 1. 安装 axios npm install axios2. 创…

HTML实现端午节主题网站:龙舟争渡,凭吊祭江诵君赋。

名人说:龙舟争渡,助威呐喊,凭吊祭江诵君赋。——苏轼《六幺令天中节》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、项目概览:传统与现代的技术碰撞1. 核心特性一览2. 网站结构设计二、技术亮点深度解析1. 响应式布局的精妙设计2. CSS动画系统的…