FPGA学习笔记(七)——FSM(Finite State Machine,有限状态机)设计
FPGA设计中,最重要的设计思想就是状态机的设计思想!状态机的本质就是对具有逻辑顺序和时序规律的事件的一种描述方法,它有三个要素:状态、输入、输出:状态也叫做状态变量(比如可以用电机的不同转速作为状态),输出指在某一个状态的特定输出,输入指状态机中进入每个状态的条件。根据状态机的输出是否和输入有关,可分为摩尔(Moore)型状态机和米勒型(Mealy)状态机:摩尔型状态机的输出只取决于当前状态,而米勒型状态机的输出不仅取决于当前状态,还与当前输入有关。通常,我们描述状态机有三种方法:状态转移图、状态转移表、HDL描述,状态转移图直观,设计用,而HDL语言方便描述,实现时用。
那么,如何用HDL描述一个好的状态机呢?主要有以下四点:安全、稳定性高,速度快,面积小,设计清晰;在描述过程中,我们会引用两个新的verilog语法:localparam描述参数(等价于parameter)以及用task/endtask将输出功能块封装,增强代码可读性;描述状态机的关键是要描述清楚状态机的三大要素:如何进行状态转移?每个状态的输出?状态输出是否和输入条件相关?通常有三种写法,下面通过一个实例说明;
实例.检测“Hello”序列状态机
1、功能:在输入一串字符中检测“Hello”序列,检测到后将led状态进行翻转;
2、根据设计的FSM状态转移图(visio绘制):
3、一段式描述法:在一个always块里既描述状态转移,又描述状态的输入和输出;
verilog代码如下:
//检测“Hello”后led状态翻转 module check_hello(
input clk, //50M时钟信号
input rst, //低电平复位
input [:]asci, //字符输入
output reg led //控制led
);
//状态寄存器
reg [:]NS; //nextstate //状态独热编码
localparam
CHECK_H = 'b0_0001,
CHECK_e = 'b0_0010,
CHECK_la = 'b0_0100,
CHECK_lb = 'b0_1000,
CHECK_o = 'b1_0000; //一段式状态机
always@(posedge clk,negedge rst)
if(!rst)begin
NS <= CHECK_H;
led <= 'b1; //led熄灭
end
else begin
case(NS)
CHECK_H:
begin
led <= 'b1;
if(asci == "H")
NS <= CHECK_e;
else
NS <= CHECK_H;
end
CHECK_e:
begin
led <= 'b1;
if(asci == "e")
NS <= CHECK_la;
else
NS <= CHECK_H;
end
CHECK_la:
begin
led <= 'b1;
if(asci == "l")
NS <= CHECK_lb;
else
NS <= CHECK_H;
end
CHECK_lb:
begin
led <= 'b1;
if(asci == "l")
NS <= CHECK_o;
else
NS <= CHECK_H;
end
CHECK_o:
begin
if(asci == "o")
led <= 'b0;
else
led <= 'b1;
NS <= CHECK_H;
end
//排除任意情况,增强FSM安全性
default:
begin
NS <= CHECK_H;
led <= 'b1;
end
endcase
end
endmodule
testbench测试文件如下:
`timescale 1ns/1ps
`define clk_period module check_hello_tb(); reg clk; //50M时钟信号
reg rst; //低电平复位
reg [:]asci; //字符输入
wire led; //控制led //例化测试模块
check_hello check_hello_test(
.clk(clk), //50M时钟信号
.rst(rst), //低电平复位
.asci(asci), //字符输入
.led(led) //控制led
); //产生50M时钟信号
initial clk = ;
always #(`clk_period / )clk <= ~clk; //开始测试
initial begin
rst = ; //系统复位
asci = 'bx;
#(`clk_period * );
rst = ;
#(`clk_period);
asci = "H";
#(`clk_period);
asci = "e";
#(`clk_period);
asci = "l";
#(`clk_period);
asci = "l";
#(`clk_period);
asci = "o";
#(`clk_period);
asci = "";
#(`clk_period);
asci = "e";
#(`clk_period);
asci = "h";
#(`clk_period);
asci = "l";
#(`clk_period);
$stop;
end
endmodule
测试结果如下,可以看到,刚开始输入数据是任意数据,FSM为CHECK_H状态,led输出高电平,保持熄灭;当检测到Hello序列时,led输出低电平,状态翻转;
该测试中隐藏了一个重要的问题,我们在编写testbench的时候,将数据变化与时钟上升沿对齐,但在实际中数据变化会产生滞后,所以这时候我们为了更好地模拟实际情况,编写testbench就有一定的技巧:将输出变化延迟1-2ns,所以我们将testbench中的这行代码进行修改:
initial begin
rst = ; //系统复位
asci = 'bx;
#(`clk_period * );
rst = ;
#(`clk_period); //将这行修改为 #(`clk_period + 1); 将整体数据改变时间较时钟上升沿滞后1ns观察
asci = "H";
然后再运行仿真观察波形,可以看到,这次的波形更好地说明了实际情况,因为我们编写的一段式FSM整体在一个时序逻辑中,所以FSM只在时钟上升沿检测数据变化,也就是说,在第一个时钟内发生的变化,下一个时钟沿才能检测到:
状态转移图如下,可以看到按照预定设计执行:
综合出来的电路图如下,可以看到FSM实现的重点在于状态寄存器,耗费资源很少,由综合报告也可看出:
在一段式描述方法中可以看到,虽然一个alaways块就可以解决问题,但描述不清晰,不利于维护修改,并且不利用附加约束,不利于综合其和布局布线器对设计的优化;
4、两段式描述法:一个always块描述状态转移,另一个always块描述状态判断转移条件
verilog代码如下:
//检测“Hello”后led状态翻转 module check_hello(
input clk, //50M时钟信号
input rst, //低电平复位
input [:]asci,//字符输入
output reg led //控制led
);
//状态寄存器
reg [:]NS; //nextstate
reg [:]CS; //currentstate //状态独热编码
localparam
CHECK_H = 'b0_0001,
CHECK_e = 'b0_0010,
CHECK_la = 'b0_0100,
CHECK_lb = 'b0_1000,
CHECK_o = 'b1_0000; //两段式状态机
//第一个always块描述状态转移
always@(posedge clk,negedge rst)
if(!rst)
CS <= CHECK_H;
else
CS <= NS; //状态转移到下一状态 //第二个always块描述状态输出以及判断状态转移
always@(CS,asci)
case(CS)
CHECK_H:
begin
led = 'b1;
if(asci == "H")
NS <= CHECK_e;
else
NS <= CHECK_H;
end
CHECK_e:
begin
led = 'b1;
if(asci == "e")
NS <= CHECK_la;
else
NS <= CHECK_H;
end
CHECK_la:
begin
led = 'b1;
if(asci == "l")
NS <= CHECK_lb;
else
NS <= CHECK_H;
end
CHECK_lb:
begin
led = 'b1;
if(asci == "l")
NS <= CHECK_o;
else
NS <= CHECK_H;
end
CHECK_o:
begin
if(asci == "o")
led = 'b0;
else
led = 'b1;
NS <= CHECK_H;
end
default:
begin
NS <= CHECK_H;
led = 'b1;
end
endcase
endmodule
用之前修改后的testbench测试文件进行测试,测试结果如下,可以看到,两段式状态机描述结果和之前一段式描述并没有差异:
综合后状态转移图如下,与之前也没有差异:
再查看综合后的电路图,与之前有了较大的差异,可以看到,这次led输出采用组合逻辑输出,之前采用一个带有使能端的D触发器输出:
再来分析一下资源占用情况,在上次设计中,总共占用了14个LE,6个寄存器资源,而这次占用了12个LE,5个寄存器资源,因为两段式描述更清晰,便于软件进行分析优化,所以设计也更加优良:
接下来我们再次人为上演上一个testbench中的错误,将数据改变与时钟上升沿对齐,测试波形如下:
可以看出来,因为我们描述输出采用组合逻辑,所以这样的测试是错误的,不仅NS与CS变化没有同步,而且状态也没有翻转,但在实际中,数据变化与时钟上升沿有可能会同时发生,显然这个设计就会出错。所以为了从根本上避免这种情况,一般在输出部分插入一级额外的时钟信号,用来保证信号稳定性,所以我们引入接下来的三段式FSM描述。
5、三段式描述法:一个always块采用时序逻辑描述状态转移,一个always块采用组合逻辑判断状态转移条件,一个always块采用时序逻辑描述状态输出
verilog代码如下:
//检测“Hello”后led状态翻转 module check_hello(
input clk, //50M时钟信号
input rst, //低电平复位
input [:]asci,//字符输入
output reg led //控制led
);
//状态寄存器
reg [:]NS; //nextstate
reg [:]CS; //currentstate //状态独热编码
localparam
CHECK_H = 'b0_0001,
CHECK_e = 'b0_0010,
CHECK_la = 'b0_0100,
CHECK_lb = 'b0_1000,
CHECK_o = 'b1_0000; //三段式状态机
//第一个always块描述状态转移
always@(posedge clk,negedge rst)
if(!rst)
CS <= CHECK_H;
else
CS <= NS; //状态转移到下一状态 //第二个always块判断状态转移
always@(CS,asci)
case(CS)
CHECK_H:
begin
if(asci == "H")
NS <= CHECK_e;
else
NS <= CHECK_H;
end
CHECK_e:
begin
if(asci == "e")
NS <= CHECK_la;
else
NS <= CHECK_H;
end
CHECK_la:
begin
if(asci == "l")
NS <= CHECK_lb;
else
NS <= CHECK_H;
end
CHECK_lb:
begin
if(asci == "l")
NS <= CHECK_o;
else
NS <= CHECK_H;
end
CHECK_o:
begin
NS <= CHECK_H;
end
default:
begin
NS <= CHECK_H;
end
endcase //第三个always块描述状态输出
always@(posedge clk,negedge rst)
if(!rst)
led <= 'b1; //led熄灭
else begin
case(CS)
CHECK_H:
led <= 'b1;
CHECK_e:
led <= 'b1;
CHECK_la:
led <= 'b1;
CHECK_lb:
led <= 'b1;
CHECK_o:
if(asci == "o")
led <= 'b0;
else
led <= 'b1;
default:
led <= 'b1;
endcase
end
endmodule
testbench依然采用之前修改后的测试文件(数据整体延迟时钟1ns)进行测试,结果如下,测试结果和之前相同:
再次人为进行查错,将数据与时钟对齐进行测试,结果如下:
结果是不是很令人惊喜^_^,可以看到当数据变化与时钟上升沿对齐的时候,结果依然正确,这是因为状态输出不是组合逻辑,而是时序逻辑,在输出前面插入了一级时钟信号就有效的解决了问题,所以一般描述FSM时选用三段式描述法描述;
再来看看状态转移图,与之前两种描述也没有差异:
综合后的电路如下,可以验证之前说的,在输出前插入了一级时钟信号:
再来对比一下资源占用,可以看到三段式描述法中和了前面两种描述方法的优点,总共耗费了12个LE,6个寄存器资源,虽然多了一个寄存器资源,却有效的避免了重大错误,这也暗示了一个数字设计里最重要的思想,中和设计思想,有的时候可以需要性能的提升,但在提升性能的同时也会增加资源占用,设计面积增大,所以两者中和往往是最优秀的设计;
至此,一个完整的示例就设计实现完成了,最后再进行几点补充:
1、因为这个示例中输出只有led,所以我们采用了直接写在FSM描述里面,如果输出较多,可以利用task/endtask将输出进行封装;
2、在整个设计中,不管是任何一种描述方式,只要case,就会写入default选项,这一点也一直在设计过程中没有提到;
default选项是必须要写入的,这样就符合了刚开始提到的状态机评判标准最重要的一点——安全性,因为不管我们采用二进制编码还是one-hot编码,在实际应用中可能会由于其他因素(比如噪声)产生突变,这时候就会进入default选项,然后重新启动状态机,可谓优点多多;
3、这一点也是最重要的一点,状态机设计不是一种具体的事物,比如说verilog语法就是固定的,它更多的是一种对具有逻辑规律和时序逻辑事件的一种描述思想,所以,即使前面提到了一段式,两段式,三段式FSM描述方法,在实际中,如果需要,我们可以分离出来4个always块,5个always块等等,这里的一段式,两段式,三段式反映的只是一种设计思想,希望在以后的数字设计中有更多的体会!
FPGA学习笔记(七)——FSM(Finite State Machine,有限状态机)设计的更多相关文章
- 证明与计算(7): 有限状态机(Finite State Machine)
什么是有限状态机(Finite State Machine)? 什么是确定性有限状态机(deterministic finite automaton, DFA )? 什么是非确定性有限状态机(nond ...
- Finite State Machine
Contents [hide] 1 Description 2 Components 3 C# - FSMSystem.cs 4 Example Description This is a Dete ...
- Finite State Machine 是什么?
状态机(Finite State Machine):状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动 作.完成特定操作的控制中心. 类 ...
- (转)Qt Model/View 学习笔记 (七)——Delegate类
Qt Model/View 学习笔记 (七) Delegate 类 概念 与MVC模式不同,model/view结构没有用于与用户交互的完全独立的组件.一般来讲, view负责把数据展示 给用户,也 ...
- Learning ROS for Robotics Programming Second Edition学习笔记(七) indigo PCL xtion pro live
中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS forRobotics Pro ...
- Typescript 学习笔记七:泛型
中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...
- python3.4学习笔记(七) 学习网站博客推荐
python3.4学习笔记(七) 学习网站博客推荐 深入 Python 3http://sebug.net/paper/books/dive-into-python3/<深入 Python 3& ...
- Go语言学习笔记七: 函数
Go语言学习笔记七: 函数 Go语言有函数还有方法,神奇不.这有点像python了. 函数定义 func function_name( [parameter list] ) [return_types ...
- iOS 学习笔记七 【博爱手把手教你使用2016年gitHub Mac客户端】
iOS 学习笔记七 [博爱手把手教你使用gitHub客户端] 第一步:首先下载git客户端 链接:https://desktop.github.com 第二步:fork 大神的代码[这里以我的代码为例 ...
- 【opencv学习笔记七】访问图像中的像素与图像亮度对比度调整
今天我们来看一下如何访问图像的像素,以及如何改变图像的亮度与对比度. 在之前我们先来看一下图像矩阵数据的排列方式.我们以一个简单的矩阵来说明: 对单通道图像排列如下: 对于双通道图像排列如下: 那么对 ...
随机推荐
- Jmeter4.0版本实现背景色切换
今天下载了Jmeter4.0新版本,看着这高大上的黑曜石般的界面,着实不适应. 尤其是在右击,希望enable和disable一个线程组时候,老眼昏花,不太看得清楚哪一个是灰色的不能点击 花了时间看了 ...
- 2017 Gartner数据科学魔力象限出炉,16位上榜公司花落谁家?
https://www.leiphone.com/news/201703/iZGuGfnER4Sv2zRe.html 2017年Gartner数据科学平台(在2016年被称作“高级分析平台”)的魔力象 ...
- 关于JQuery Class选择器的一点
当某个元素的Class为为两个字符串的时候,那用class选择器的时候就必须把两个字符串都写上否则无效 <div class="cla clb">11111<di ...
- Python实现PPPOE攻击工具
前言 大家可能对PPPOE不是很熟悉,但是肯定对拨号上网非常熟悉,拨号上网就是用的这种通信协议.一般PPPOE认证上网主要用于校园网或者小区网中,拨号界面如下图所示. 但是PPPOE这种通信协议,是有 ...
- LruCache的使用及原理
采用LRU算法实现的话就是将最老的数据删掉.利用LRU缓存,我们能够提高系统的性能. 一,是它本身已经实现了按照访问顺序的存储,也就是说,最近读取的会放在最前面,最不常读取的会放在最后(当然,它也 ...
- WiderG的博客皮肤
我的Skin 还是亮出自己的Blog定制代码吧: 其实也不是完全自己写的(有抄袭),也不大懂这方面的知识,代码冗长,逻辑不清,加载缓慢,见谅喽
- 重温《STL源码剖析》笔记 第三章
源码之前,了无秘密. --侯杰 第三章:迭代器概念与traits编程技法 迭代器是一种smart pointer auto_Ptr 是一个用来包装原生指针(native pointer)的对象,声明狼 ...
- ccos2d-x 学习
渲染驱动方式,事件驱动方式 this->addChild(pSprite, 0); 的第二个参数(int zOrder)表示要添加到this类对象中的顺序.是由里向外的方向.值越大表示越在外面. ...
- 【转】javascript 分号问题
javascript的分号代表语句的结束符,但由于javascript具有分号自动插入规则,所以它是一个十分容易让人模糊的东西,在一般情况下,一个换行就会产生一个分号,但实际情况却不然,也就是说在ja ...
- FastDfs上传图片
1.1. 上传步骤 1.加载配置文件,配置文件中的内容就是tracker服务的地址. 配置文件内容:tracker_server=192.168.25.133:22122 2.创建一个TrackerC ...