基于Verilog HDL的状态机描述方法

简介: ⭐本专栏针对FPGA进行入门学习,从数电中常见的逻辑代数讲起,结合Verilog HDL语言学习与仿真,主要对组合逻辑电路与时序逻辑电路进行分析与设计,对状态机FSM进行剖析与建模。
🔥文章和代码已归档至【Github仓库: hardware-tutorial】,需要的朋友们自取。或者关注公众号【AIShareLab】,回复 FPGA 也可获取。

状态图的建立过程

设计一个序列检测器电路。功能是检测出串行输入数据Sin中的4位二进制序列0101(自左至右输入),当检测到该序列时,输出Out=1;没有检测到该序列时,输出Out=0。(注意考虑序列重叠的可能性,如010101,相当于出现两个0101序列)。

解:首先,确定采用米利型状态机设计该电路。因为该电路在连续收到信号0101时,输出为1,其他情况下输出为0,所以采用米利型状态机。

其次,确定状态机的状态图。根据设计要求,该电路至少应有四个状态,分别用S1、S2、S3、S4表示。若假设电路的初始状态用S0表示,则可用五个状态来描述该电路。根据分析,可以画出图(a)所示的原始状态图。

观察该图可以看出,S2、S4为等价状态,可用S2代替S4,于是得到简化状态图。

然后,根据上面的状态图给出该状态机的输出逻辑。该状态机只有一个输出变量Out,其输出逻辑非常简单,直接标注在状态图中了。若输出变量较多,则可以列出输出逻辑真值表。

最后,就可以使用硬件描述语言对状态图进行描述了。

状态图描述方法

利用Verilog HDL语言描述状态图主要包含四部分内容:

  1. 利用参数定义语句parameter描述状态机中各个状态的名称,并指定状态编码。例如,对序列检测器的状态分配可以使用最简单的自然二进制码,其描述如下:

    parameter  S0=2'b00, S1=2'b01, S2 = 2'b10, S3 = 2'b11;

    或者,

    parameter [1:0] S0=2'b00, S1=2'b01, S2 = 2'b10, S3 = 2'b11;
  2. 用时序的always 块描述状态触发器实现的状态存储。
  3. 使用敏感表和case语句(也可以采用if-else等价语句)描述的状态转换逻辑。
  4. 描述状态机的输出逻辑。

描述状态图的方法多种多样,下面介绍几种:

单个always块描述状态机的方法(尽量避免)

用一个always块对该例的状态机进行描述,其代码如下:

module Detector1 ( Sin, CP, nCR, Out) ;
    input Sin, CP, nCR;    //声明输入变量
    output Out ;                //声明输出变量
    reg Out; 
    reg [1:0] state;  
// 声明两个状态触发器变量state[1]和state[0],记忆电路现态
//The state labels and their assignments
parameter [1:0] S0=2'b00, S1=2'b01, S2 = 2'b10, S3 = 2'b11;
always @(posedge CP or negedge nCR)   
begin
if (~nCR)
state <= S0;  //在nCR跳变为0时,异步清零
else
           case(state) 
      S0: begin Out =1’b0; state <= (Sin==1)? S0 : S1; end
      S1: begin Out = 1’b0; state <= (Sin==1)? S2 : S1; end
      S2: begin Out = 1’b0; state <= (Sin==1)? S0 : S3; end      
      S3: if (Sin==1)  begin Out =1’b1; state <=  S2; end
            else               
           begin Out =1’b0; state <= S1; end                   
    endcase
end
endmodule

严格地说,对序列检测器电路用单个always块的描述方法所描述的逻辑存在着一个隐含的错误,即输出信号Out的描述。

case语句中对输出向量的赋值应是下一个状态输出,这点易出错;状态向量与输出向量都由寄存器实现,面积大,不能实现异步米勒状态机。因此,单个always块描述状态机的写法仅仅适用于穆尔型状态机。单个always块写法的电路结构框图可以用下图进行概括。

两个always块描述状态机的方法(推荐写法!)

用两个always块对该例的状态机进行描述,其代码如下:

module  Detector2 ( Sin, CP, nCR, Out) ;
input Sin, CP, nCR;    //定义输入变量
output Out ;                //定义输出变量
reg Out; 
reg [1:0] Current_state, Next_state;
parameter [1:0] S0=2'b00, S1=2'b01, S2 = 2'b10, S3 = 2'b11;
//状态转换,时序逻辑
  always @(posedge CP or negedge nCR ) 
begin
if (~nCR)
    Current_state <= S0;   //异步清零
else
     Current_state <= Next_state; 
     //在CP上升沿触发器状态翻转
   end

//下一状态产生和输出信号,组合逻辑
always @( Current_state or Sin) 
   begin    
     Next_state =2’bxx;                                                                                                                                                                                                                      
     Out=1’b 0;
   case(Current_state )
     S0: begin Out =1’b0; Next_state = (Sin==1)? S0 : S1; end
     S1: begin Out =1’b0; Next_state = (Sin==1)? S2 : S1; end
     S2: begin Out =1’b0; Next_state = (Sin==1)? S0 : S3; end     
     S3: if (Sin==1)
            begin Out =1’b1; Next_state = S2; end
        else
            begin Out =1’b0; Next_state = S1; end      
    endcase
  end    

endmodule

用两个always块描述状态机的写法是值得推荐的方法之一,两个always块写法的电路结构框图可以用下图进行概括。

两个always块写法的电路结构框图概括。

第一个always模块采用同步时序逻辑方式描述状态转移(中间方框); 第二个always模块采用组合逻辑方式描述状态转移规律(第一个方框)和描述电路的输出信号(第三个方框)。

使用三个always块分别描述

即第一个always模块采用同步时序逻辑方式描述状态转移(中间方框); 第二个always模块采用组合逻辑方式描述状态转移规律(第一个方框); 第三个always模块描述电路的输出信号,在时序允许的情况下,通常让输出信号经过一个寄存器再输出,保证输出信号中没有毛刺。

用三个always块对该例的状态机进行描述,其代码如下:

module  Detector3 ( Sin, CP, nCR, Out) ;
    input Sin, CP, nCR;    //定义输入变量
    output Out ;                //定义输出变量
    reg Out; 
    reg [1:0] Current_state, Next_state;
     parameter [1:0] S0=2'b00, S1=2'b01, S2 = 2'b10, S3 = 2'b11;
//状态转换,时序逻辑
always @(posedge CP or negedge nCR )
  begin
     if (~nCR)
       Current_state <= S0;                 //异步清零
     else
       Current_state <=  Next_state; //在CP上升沿触发器状态翻转
  end 
 //下一状态产生,组合逻辑
always @( Current_state or Sin) 
  begin    
       Next_state =2’bxx;                                                                                                                                                                                                                      
       case(Current_state )
         S0: begin Next_state = (Sin==1)? S0 : S1; end
         S1: begin Next_state = (Sin==1)? S2 : S1; end
         S2: begin Next_state = (Sin==1)? S0 : S3; end     
         S3: if (Sin==1)
                begin Next_state = S2; end
                    else
            begin Next_state = S1; end      
       endcase
  end    
 /* 输出逻辑: 让输出信号经过一个寄存器再输出,可以消除Out信号中的毛刺,时序逻辑*/
always @ (posedge CP or negedge nCR )
    begin
    if (~nCR)    Out <= 1’b 0;
              else 
                      begin 
                   case(Current_state )
               S0, S1, S2:          Out <= 1’b0;     
                              S3:               if (Sin==1)  
                                                 Out <= 1’b1; 
                              else             
                                                           Out <= 1’b0; 
                         endcase
                    end    
    end    
endmodule

三种描述方法比较

1-always 2-always 3-always
结构化设计
代码编写/理解 不宜,理解难
输出信号 寄存器输出 组合逻辑输出 寄存器输出
不产生毛刺 产生毛刺 不产生毛刺
面积消耗 最小
时序约束 不利 有利 有利
可靠性、可维护性 较高 最高
后端物理设计 不利 有利 有利

参考文献:

  1. Verilog HDL与FPGA数字系统设计,罗杰,机械工业出版社,2015年04月
  2. Verilog HDL与CPLD/FPGA项目开发教程(第2版), 聂章龙, 机械工业出版社, 2015年12月
  3. Verilog HDL数字设计与综合(第2版), Samir Palnitkar著,夏宇闻等译, 电子工业出版社, 2015年08月
  4. Verilog HDL入门(第3版), J. BHASKER 著 夏宇闻甘伟 译, 北京航空航天大学出版社, 2019年03月

欢迎关注公众号【AIShareLab】,一起交流更多相关知识,前沿算法,Paper解读,项目源码,面经总结。

目录
相关文章
|
缓存 Java 数据库
SpringBoot 接口:响应时间优化9个技巧!
今天聊聊 SpringBoot接口:响应时间优化的9个技巧。在实际开发中,提升接口响应速度是一件挺重要的事,特别是在面临大量用户请求的时候。好了,咱们直接切入正题。
970 0
|
Python
python numpy安装
一、python下的numpy安装方法 第一步:安装python,这里不做介绍。 第二步:打开cmd看python是否安装成功。 第三步:输入 python -m pip install -U pip 安装pip文件,pip文件一般在python安装包的script目录下。
6158 0
|
算法 调度
操作系统中几种最常见的调度算法(适用于软件设计师考试与期末考试复习)
先进先出置换算法我们可以理解为排队准则(谁先来,谁就先运行)先来先到原则。该算法总是淘汰最先进入主页的页面,即选择在主存中驻留时间最久的页面淘汰。该算法简单,只要把一个进程调入主存的页面,然后按照先后链接一个队列,并设置一个指针即可。它是一个最直观、性能最差的算法,会有 Belady 现象(是指一个进程未分配它所要求的全部页面,有时就.....
1939 1
操作系统中几种最常见的调度算法(适用于软件设计师考试与期末考试复习)
|
消息中间件 存储 Cloud Native
云消息队列 Kafka 版 V3 系列荣获信通院“云原生技术创新标杆案例”
2024 年 12 月 24 日,由中国信息通信研究院(以下简称“中国信通院”)主办的“2025 中国信通院深度观察报告会:算力互联网分论坛”,在北京隆重召开。本次论坛以“算力互联网 新质生产力”为主题,全面展示中国信通院在算力互联网产业领域的研究、实践与业界共识,与产业先行者共同探索算力互联网产业未来发展的方向。会议公布了“2024 年度云原生与应用现代化标杆案例”评选结果,“云消息队列 Kafka 版 V3 系列”荣获“云原生技术创新标杆案例”。
402 91
|
10月前
|
消息中间件 关系型数据库 Kafka
阿里云基于 Flink CDC 的现代数据栈云上实践
阿里云基于 Flink CDC 的现代数据栈云上实践
195 1
|
前端开发 JavaScript 测试技术
React 中集成 Chart.js 图表库
本文介绍了如何在 React 项目中集成 Chart.js 创建动态图表,涵盖基础概念、安装步骤、代码示例及常见问题解决方法,帮助开发者轻松实现数据可视化。
405 11
|
Java Maven
Maven 依赖管理
Maven 一个核心的特性就是依赖管理。当我们处理多模块的项目(包含成百上千个模块或者子项目),模块间的依赖关系就变得非常复杂,管理也变得很困难。针对此种情形,Maven 提供了一种高度控制的方法。
441 5
|
机器学习/深度学习 Python
LightGBM高级教程:高级特征工程
LightGBM高级教程:高级特征工程【2月更文挑战第8天】
1170 2
|
人工智能 云栖大会
AI Infra 核心技术专题 | 2024 云栖大会预热
AI Infra 核心技术专题 | 2024 云栖大会
java实现(燃油车车牌生成)
java实现(燃油车车牌生成)