FPGA实战篇:Moore/Mealy状态机

什么是状态机?

状态机是根据当前输入信号和自身当前所处状态来改变输出逻辑的一种逻辑系统,目前它也被抽象应用于软件设计当中,本文从硬件设计角度来解释状态机,使用Verilog语言来抽象描述并实现状态机。

状态机类型

状态分为两种类型:
Moore(摩尔)状态机:输出逻辑只与当前状态有关,与输入信号无关
Mealy(米勒)状态机:输出逻辑不仅与当前状态有关,也与输出信号有关
以上两种都为有限状态机,为什么称之为有限状态机?因为它们的状态是固定的,是有限的,例如一个状态机一开始就是256个状态,它的状态不会随着时间而变长或变少不是非固定的。

Moore状态机

Moore状态机的原理在状态机类型处已经简单解释了一遍,可以简单看下它的抽象逻辑图
整个逻辑图分为两大部分:
在这里插入图片描述第一部分用于给状态寄存器一个初始状态初始化电路(这块一般是写在第二部分状态传递组合逻辑里,这里分出来是为了体现它),第二部分则是Moore状态机逻辑流程图
第一部分只有一个逻辑:初始化逻辑,主要作用是在rst电平到来时对状态寄存器进行一个初始赋值,确保每次复位都有一个稳定的状态
第二部分则是根据状态寄存器作为输入去生成新的状态的组合逻辑,生成完成之后将次态传递给状态寄存器变为现态,最后现态在传递给输出组合逻辑来改变输出结果
原理说完了,开始使用Verilog来实现它

TIPS
状态机的实现不一定是死的,可以自由发挥,本文给出的实现只是为了方便大家理解,多数情况下大家只要理解状态机的思想,可以按自己想法实现它,改造它。
如果你是初学者那么可以先不考虑如何更完美的实现它,例如考虑是否会生成锁存器、信号毛刺等现象,优先考虑的是如何实现它,并理解它。

本文以Flow Led电路为例,使用Moore来实现流水灯(总共4个LED灯)控制
首先定义一个基本构建模块,有两个输入一个输出,分别是clk、rst和led

module flow_led(input                 sys_clk,input                 sys_rst_n,output reg [3:0]      led
);
endmodule

按照刚刚的抽象逻辑图我们来一步一步实现moore状态机,首先定义5个状态

parameter LED_LIGHT_0 = 4'b0000,LED_LIGHT_1 = 4'b0001,LED_LIGHT_2 = 4'b0010,LED_LIGHT_3 = 4'b0100,LED_LIGHT_4 = 4'b1000;

这四个状态分别对应哪个LED灯在亮LED_LIGHT_0 代表每个灯都不亮,然后定义次态寄存器和现态寄存器用于保存下一个状态和当前状态:

reg [3:0] next_state;
reg [3:0] now_state;

接着我们就可以编写moore状态机了,接下来第一部分要写的就是状态转移逻辑,如果要实现状态转移那么就需要有转移条件moore状态机的转移条件是对内而非外部输入信号,首先我们需要思考我们要实现什么,我们需要实现的是一个流水灯,为了实现流水灯我们还需要一个计数器,用于实现延迟流水效果,那么计数器就是我们让状态发生改变的因变量,那么我们先定义它:

reg [31:0] 	counter;

我们想要实现流水灯的时间间隔为1秒,可以根据当前时钟频率来计算,我当前实验环境给芯片提供时钟频率的晶振是50Mhz,也就是1秒会跳变50000000次时钟周期,每个周期为1/50000000=0.00000002(20ns),那么我们只需要让counter每个周期递增一次,递增到50000000 - 1次就可以了,这里**-1是因为当前周期也算在内**。
所以生成次态的组合逻辑代码就如下所示:

always @(*) begincase (now_state) beginLED_LIGHT_0:beginif (counter >= 32'd49999999)next_state = LED_LIGHT_1;elsenext_state = LED_LIGHT_0;endendcasecounter = counter + 1;
end

上面的代码逻辑是组合逻辑,当now_state、counter发生改变时它才会触发,它的作用就是根据当前状态来生产新的状态,生产新的状态的因变量就是counter,当counter计数到一秒时切换置下一个状态,例如当前状态是LED_LIGHT_0那么下一个状态就是LED_LIGHT_1,这里为了防止产生锁存器所以加了一个else,让它保持不变。
简单来说就是当达到一秒时根据当前状态来确定下一次应该是什么状态,这里可以看到是依据内部变量counter来判断的,这里简单提一句,其实mealy只需要将counter改为input的寄存器就可以了
以上是一个基础示例,它会先根据当前状态然后进行切换,如果当前状态为LED_LIGHT_0就代表当前没有一个灯是亮的,就判断counter是否到达一秒钟了,如果到了则将next_state设置为LED_LIGHT_1,该下一个灯亮了,然后将counter置零从新技术,否则让next_state继续保持LED_LIGHT_0,这里的else是为了防止verilog综合生成锁存器电路,也可以不写。
在最后递增counter
然后按照上面的思路,我们依次把其它状态补充齐全:

always @(*) begincase (now_state)LED_LIGHT_0:beginif (counter >= 32'd49999999)next_state = LED_LIGHT_1;elsenext_state = LED_LIGHT_0;endLED_LIGHT_1:beginif (counter >= 32'd49999999)next_state = LED_LIGHT_2;elsenext_state = LED_LIGHT_1;endLED_LIGHT_2:beginif (counter >= 32'd49999999)next_state = LED_LIGHT_3;elsenext_state = LED_LIGHT_2;endLED_LIGHT_3:beginif (counter >= 32'd49999999)next_state = LED_LIGHT_4;elsenext_state = LED_LIGHT_3;endLED_LIGHT_4:beginif (counter >= 32'd49999999)next_state = LED_LIGHT_1;elsenext_state = LED_LIGHT_4;enddefault:next_state = LED_LIGHT_0;endcase
end

注意在LED_LIGHT_4时我们就不需要让它回归初始状态了,让它从第一个LED开始继续循环,这里counter在别的逻辑里会进行处理,最后的default是在其它情况下例如电路出现其它以外时让它置初始状态,灯灭。
状态生成组合逻辑到此就写完了,接下来就是去编写传递组合逻辑,这里我进行了一些考虑,决定将它写成时序逻辑,而非组合逻辑,目的是为了包含初始逻辑与递增因变量counter

always @(posedge sys_clk or negedge sys_rst_n) beginif (!sys_rst_n) beginnow_state <= LED_LIGHT_0;counter   <= 32'b0;endelse if (now_state != next_state) beginnow_state <= next_state;counter <= 32'b0;endelsecounter <= counter + 1;
end

上面的代码逻辑包含了初始逻辑,在会对now_state和counter进行初始化,然后会判断当前状态是否不等于下一个状态,这里就是检查状态是否发生了改变,如果发生了改变那么意味着要从LED0切换到LED1或从LED2切换到LED3,如果发生了改变那么就将状态进行转移并将计数器置0如果状态相同代表当前处于LED0或LED1下,那么就对counter进行递增,保持1秒后切换到下一个LED
最后就是输出逻辑了,输出逻辑就比较简单,只需要根据当前状态来设置哪个LED灯亮即可:

always @(*) begincase (now_state)LED_LIGHT_0: led = 4'b0000;LED_LIGHT_1: led = 4'b0001;LED_LIGHT_2: led = 4'b0010;LED_LIGHT_3: led = 4'b0100;LED_LIGHT_4: led = 4'b1000;default: 	 led = 4'b0000;endcase
end

代码编写完了, 接下来仿真试试,这里我使用的是modesimtestbench代码如下:

`timescale 1 ns/ 1 psmodule flow_led_test_bench();reg sys_clk;
reg sys_rst_n;                                         
wire [3:0]  led;   flow_led i1 (.led(led),.sys_clk(sys_clk),.sys_rst_n(sys_rst_n)
);initial                                                
begin   
sys_clk <=1'b0;
sys_rst_n <= 1'b0;
#100 sys_rst_n <= 1'b1;  
endalways #10 sys_clk=~sys_clk;endmodule

仿真时序图
在这里插入图片描述
通过下面的步长可以看到每秒next_statenow_state都在发生变化,按代码预期的在切换状态。
在这里插入图片描述
RTL View
在这里插入图片描述
moore状态机verilog完整代码

module flow_led(input 				sys_clk,input 				sys_rst_n,output reg [3:0] 	led
);parameter LED_LIGHT_0 = 4'b0000,LED_LIGHT_1 = 4'b0001,LED_LIGHT_2 = 4'b0010,LED_LIGHT_3 = 4'b0100,LED_LIGHT_4 = 4'b1000;reg [3:0] 	next_state;
reg [3:0] 	now_state;
reg [31:0] 	counter;always @(*) begincase (now_state)LED_LIGHT_0:beginif (counter >= 32'd49999999)next_state = LED_LIGHT_1;elsenext_state = LED_LIGHT_0;endLED_LIGHT_1:beginif (counter >= 32'd49999999)next_state = LED_LIGHT_2;elsenext_state = LED_LIGHT_1;endLED_LIGHT_2:beginif (counter >= 32'd49999999)next_state = LED_LIGHT_3;elsenext_state = LED_LIGHT_2;endLED_LIGHT_3:beginif (counter >= 32'd49999999)next_state = LED_LIGHT_4;elsenext_state = LED_LIGHT_3;endLED_LIGHT_4:beginif (counter >= 32'd49999999)next_state = LED_LIGHT_1;elsenext_state = LED_LIGHT_4;enddefault:next_state = LED_LIGHT_0;endcase
endalways @(posedge sys_clk or negedge sys_rst_n) beginif (!sys_rst_n) beginnow_state <= LED_LIGHT_0;counter   <= 32'b0;endelse if (now_state != next_state) beginnow_state <= next_state;counter <= 32'b0;endelsecounter <= counter + 1;
endalways @(*) begincase (now_state)LED_LIGHT_0: led = 4'b0000;LED_LIGHT_1: led = 4'b0001;LED_LIGHT_2: led = 4'b0010;LED_LIGHT_3: led = 4'b0100;LED_LIGHT_4: led = 4'b1000;default: 	 led = 4'b0000;endcase
endendmodule

写完了moore状态机接下来来实现mealy状态机,mealy刚刚在写moore时也说过,只需要将counter换成input寄存器就可以了,虽然代码上改动不大但是对于电路实现来说其实还是有一点差距的,那么这里我们来改变一下想要实现的功能,接下来实现一个用按键作为输入信号来实现控制led灯的工作模式,首先声明一下需求:

  1. 输入有4个key
  2. 每个key对应一个led工作状态
  3. key1对应led灯1亮
  4. key2对应led灯2亮
  5. key3对应led灯3亮
  6. key4对应led灯4亮
  7. 所有效果仅在key按下有效
  8. key按下为低电平

理解了需求那么我们开始编码,首先定义输入部分:

module flow_led(input 				sys_clk,input 				sys_rst_n,input  reg  [3:0]   key,output reg  [3:0] 	led
);

输入分别是时钟信号复位信号按键信号输出led灯
然后我们生成不同的状态,对应不同按键按下的情况:

parameter STATE_DEFAULT        = 4'b0000,STATE_LED_LIGHT_1    = 4'b0001,STATE_LED_LIGHT_2    = 4'b0010,STATE_LED_LIGHT_3    = 4'b0100,STATE_LED_LIGHT_4    = 4'b1000;

STATE_DEFAULT:默认状态,没有任何按键按下
STATE_LED_LIGHT_1:按键1按下,LED1亮
STATE_LED_LIGHT_2:按键2按下,LED2亮
STATE_LED_LIGHT_3:按键3按下,LED3亮
STATE_LED_LIGHT_4:按键4按下,LED4亮
在定义状态存储的中间寄存器与当前时刻状态的寄存器

reg [3:0] 	next_state;
reg [3:0] 	now_state;

然后接着我们修改状态生成的组合逻辑:

always @(*) begincase (now_state)STATE_DEFAULT:beginif (key == 4'b1110)next_state = STATE_LED_LIGHT_1;else if (key == 4'b1101)next_state = STATE_LED_LIGHT_2;else if (key == 4'b1011)next_state = STATE_LED_LIGHT_3;else if (key == 4'b0111)next_state = STATE_LED_LIGHT_4;elsenext_state = STATE_DEFAULT;     endendcase
end

上面的代码将其它状态改掉了,只保留一个STATE_DEFAULT,也就是按键没有按下的状态,只有在按键没有按下时我们才去对按键进行检测,当这个时刻有按键按下时,根据按下的按键来使能不同的状态。
状态生成组合逻辑写好了接下来就是写状态转移逻辑了:

always @(posedge sys_clk or negedge sys_rst_n) beginif (!sys_rst_n) beginnow_state <= STATE_DEFAULT;endelse if (now_state != next_state) beginnow_state <= next_state;endelse if (key ==4'b1111)now_state <= STATE_DEFAULT;elsenow_state <= now_state;
end

初始化逻辑依旧在状态转移这里做,上面的逻辑就是判断当前状态是否等于下一个状态,用来判断是否生成新的状态,如果生成了新的状态那么将新状态进行转移,如果当前处于新状态则判断按键是否松开了,如果松开将状态置为defulat这里就实现了按键的按下与松开的逻辑把控,最后的else是为了防止生成锁存器,只要key!=4’b1111就意味着当前有个按键被按下了,那么当前状态一定被赋予对应的条件状态了,那么接下来就是输出组合逻辑了。

always @(*) begincase (now_state)STATE_DEFAULT:          led = 4'b0000;STATE_LED_LIGHT_1:      led = 4'b0001;STATE_LED_LIGHT_2:      led = 4'b0010;STATE_LED_LIGHT_3:      led = 4'b0100;STATE_LED_LIGHT_4:      led = 4'b1000;default: 	            led = 4'b0000;endcase
end

输出组合逻辑只需要根据当前状态来改变灯的点亮就可以了。
接下来就进行仿真测试,testbench代码如下:

`timescale 1 ns/ 1 psmodule flow_led_test_bench();reg sys_clk;
reg sys_rst_n;
reg  [3:0]  key;                                         
wire [3:0]  led;   flow_led i1 (.led(led),.sys_clk(sys_clk),.sys_rst_n(sys_rst_n),.key(key)
);initial                                                
begin   
sys_clk <=1'b0;
sys_rst_n <= 1'b0;
key <= 4'b1111;
#100 sys_rst_n <= 1'b1;#1000000000 key=4'b1110;
#1000000000 key=4'b1111;#1000000000 key=4'b1101;
#1000000000 key=4'b1111;#1000000000 key=4'b1011;
#1000000000 key=4'b1111;#1000000000 key=4'b0111;
#1000000000 key=4'b1111;endalways #10 sys_clk=~sys_clk;endmodule

在init里使用延迟来模拟按键的按下与松开。
仿真结果如下
在这里插入图片描述
可以看到时序图随着按键每秒按下/松开,state都在发生变化,led也在发生变化。
完整verilog代码如下

module flow_led(input 				sys_clk,input 				sys_rst_n,input  wire [3:0]   key,output reg  [3:0] 	led
);parameter STATE_DEFAULT        = 4'b0000,STATE_LED_LIGHT_1    = 4'b0001,STATE_LED_LIGHT_2    = 4'b0010,STATE_LED_LIGHT_3    = 4'b0100,STATE_LED_LIGHT_4    = 4'b1000;reg [3:0] 	next_state;
reg [3:0] 	now_state;always @(*) begincase (now_state)STATE_DEFAULT:beginif (key == 4'b1110)next_state = STATE_LED_LIGHT_1;else if (key == 4'b1101)next_state = STATE_LED_LIGHT_2;else if (key == 4'b1011)next_state = STATE_LED_LIGHT_3;else if (key == 4'b0111)next_state = STATE_LED_LIGHT_4;elsenext_state = STATE_DEFAULT;     endendcase
endalways @(posedge sys_clk or negedge sys_rst_n) beginif (!sys_rst_n) beginnow_state <= STATE_DEFAULT;endelse if (now_state != next_state) beginnow_state <= next_state;endelse if (key ==4'b1111)now_state <= STATE_DEFAULT;elsenow_state <= now_state;
endalways @(*) begincase (now_state)STATE_DEFAULT:          led = 4'b0000;STATE_LED_LIGHT_1:      led = 4'b0001;STATE_LED_LIGHT_2:      led = 4'b0010;STATE_LED_LIGHT_3:      led = 4'b0100;STATE_LED_LIGHT_4:      led = 4'b1000;default: 	            led = 4'b0000;endcase
endendmodule

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

influxdb与LSM-TREE

一、什么是LSM-TREE 在一些写多读少的场景&#xff0c;为了加快写磁盘的速度&#xff0c;提出使用日志文件追加顺序写&#xff0c;加快写的速度&#xff0c;减少随机读写。但是日志文件只能遍历查询。不支持随机查询&#xff0c;提出使用LSM-TREE。除了利用磁盘顺序写之外&…

Mac保护电池健康,延长电池使用寿命的好方法

使用Mac的过程中&#xff0c;如何延长电池的使用寿命是大家非常关心的问题&#xff0c;而养成一个良好的充电习惯能够有效的延长电池的使用寿命 避免过度充电和过度放电能够有效的保护电池&#xff0c;因此长时间的充电与长时间放点都不可取&#xff0c;但是在日常的使用过程中…

AutosarMCAL开发——基于EB ResourceM模块

目录 一、ResourceM模块的作用以及原理1.ResourceM模块的作用2.单核系统运行原理a.上电复位b.启动代码执行c.应用程序加载d.应用程序执行 3.代码执行过程4.内存分配a.地址空间划分b.具体地址分配c.示例说明 4.多核系统运行原理a.MCU架构 二、EB配置介绍三、总结 一、ResourceM模…

【LeetCode】返回链表的中间结点、删除链表的倒数第 N 个结点

主页&#xff1a;HABUO&#x1f341;主页&#xff1a;HABUO &#x1f31c;钱塘江上潮信来&#xff0c;今日方知我是我&#x1f31b; 1.返回链表的中间结点 题目&#xff1a;给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。如果有两个中间结点&#xff0…

Netty篇(学习前言)

目录 一、为什么使用Netty 1. Netty编程相比NIO编程的优势 2. Netty 相比其它网络应用框架的优势 二、让我们走进Netty 1. 简介 2. 设计目标 3. 主要特点 4. Netty的作者 5. Netty 的地位 6. Netty 的优势 五、Netty版本说明 六、Netty架构设计 1. 线程模型基本介绍…

Ceph 学习指南 集群部署【 cephadm 】

文章目录 引言初识 Server SANServer SAN 和传统存储对比 Ceph 概述Ceph 的架构设计Ceph 的特点Ceph 块存储Ceph 文件系统Ceph 对象存储Ceph 介绍 Ceph 集群部署配置 aliyun 源配置时间同步配置 hosts 文件安装 docker配置免密登录ceph 集群部署ceph1 配置安装 python3安装 cep…

Linux篇(常见入门命令)

目录 一、开启终端 二、Linux命令格式 1. 什么是Linux 的命令&#xff1f; 三、Linux下的命令补全 四、切换用户 五、uname&#xff1a;查看操作系统信息 六、ls&#xff1a;查看目录下文件 1. 用法一 2. 用法二 3. 用法三 七、pwd&#xff1a;显示当前路径 八、cd&…

全面解析:网络协议及其应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 # 全面解析&#xff1a;网络协议及其应用 文章目录 网络协议概述定义发展历程主要优势 主要网络协议应用层协议传输层协议网络层…

02- 模块化编程-006 ADC0808数码显示对比

1、ADC0808 芯片介绍 ADC0808是一款集成的CMOS设备&#xff0c;包含8位模拟至数字转换器、8通道多路复用器和与微处理器兼容的控制逻辑。8位A/D转换器采用逐次逼近作为转换技术。转换器特点包括高阻抗斩波稳定比较器、256R电压分压器、模拟开关树和逐次逼近寄存器。8通道多路复…

【自动化测试】APP UI 自动化(安卓)-本地环境搭建

一、软件准备及版本介绍 软件版本JAVA-SDK1.8.0_181 python 3.10.10 Android SDK Tools 下最新版本即可&#xff0c;无特殊要求 PyCharm 2023.3.5&#xff08;下最新版本即可&#xff0c;无特殊要求&#xff09; 二、安装步骤及环境变量配置 2.1 Java安装及配置 1&am…

leetcode912.排序数组的题解

题目描述&#xff1a; 题目要求在不使用任何内置函数的情况下解决问题&#xff0c;时间复杂度为 O(nlog(n))。 笔者使用了快速排序&#xff0c;但是直接使用最原始的快速排序&#xff0c;有些特殊的测试用例会超时。 1&#xff09;如果数组本身基本有序&#xff0c;则使用原始…

安装Blender并使用

前言 该系列记录了如何用Blenderpro来构建自己的场景数据集&#xff0c;从环境搭建到后期构建数据集的整个流程 本文章是第一部分&#xff0c;BlenderPrc2的安装以及环境配置 部分参考https://blog.csdn.net/weixin_49521551/article/details/121573334 官方文档https://dlr…

json-server的使用(根据json数据一键生成接口)

一.使用目的 在前端开发初期&#xff0c;后端 API 可能还未完成&#xff0c;json-server 可以快速创建模拟的 RESTful API&#xff0c;帮助前端开发者进行开发和测试。 二.安装 npm install json-server //局部安装npm i json-server -g //全局安装 三.使用教程 1.准备一…

MySQL详细安装教程

一、从MySQL官网安装 可以翻译成中文看起来就舒服多了 下载并打开安装包&#xff0c;能看到版本是8.0.36&#xff0c;双击运行或者右键选择打开&#xff0c;打开后是一个安装向导&#xff0c;这个安装向导会先帮我们安装一个 mysql-installer 的程序&#xff0c;再通过该程序安…

qt QErrorMessage详解

1、概述 QErrorMessage是Qt框架中用于显示错误消息的一个对话框类。它提供了一个简单的模态对话框&#xff0c;用于向用户显示错误或警告消息。QErrorMessage通常用于应用程序中&#xff0c;当需要向用户报告错误但不希望中断当前操作时。它提供了一个标准的错误消息界面&…

Vue3安装、创建到使用

vue安装 npm install vuenext # 全局安装 vue-cli npm install -g vue/cli #更新插件 项目中运行 vue upgrade --nextvue create 命令 vue create [options] <app-name> options 选项可以是&#xff1a; -p, --preset <presetName>&#xff1a; 忽略提示符并使用已…

Linux 下执行定时任务之 Systemd Timers

不知道 ECS 因为什么缘故&#xff0c;上面安装的 MySQL 服务老是不定期挂掉&#xff0c;本来想通过 Linux 得 Cron 配置个半小时的定时检测任务&#xff0c;结果一直没有执行&#xff0c;因此又尝试使用了 Systemd Timers 进行了重新配置&#xff0c;简要做个记录。 Systemd Ti…

计算机网络:网络层 —— IP 多播技术

文章目录 基本概念IP多播地址和多播组 IP多播的类型硬件多播将IPv4多播地址映射为多播MAC地址 基本概念 多播&#xff08;Multicast&#xff0c;也称为组播&#xff09;是一种实现“一对多”通信的技术&#xff0c;允许一台或多台主机&#xff08;多播源&#xff09;发送单一数…

OuteTTS:基于纯语言建模的开源文本到语音合成项目,支持语音克隆等多种语音合成任务

❤️ 如果你也关注大模型与 AI 的发展现状&#xff0c;且对大模型应用开发非常感兴趣&#xff0c;我会快速跟你分享最新的感兴趣的 AI 应用和热点信息&#xff0c;也会不定期分享自己的想法和开源实例&#xff0c;欢迎关注我哦&#xff01; &#x1f966; 微信公众号&#xff…

C语言 | Leetcode C语言题解之第540题有序数组中的单一元素

题目&#xff1a; 题解&#xff1a; int singleNonDuplicate(int* nums, int numsSize) {int low 0, high numsSize - 1;while (low < high) {int mid (high - low) / 2 low;mid - mid & 1;if (nums[mid] nums[mid 1]) {low mid 2;} else {high mid;}}return …