FPGA_学习_08_有限状态机

news2024/10/7 10:21:47

状态机是FPGA编程必学内容之一,因为状态机在项目用的特别多。

那为什么状态机这么重要呢? 在写这篇blog之前,搜到CSDN一位大佬的博客,有一句话令我醍醐灌顶:

“FPGA不同于CPU的一点特点就是CPU是顺序执行的,而FPGA是同步执行(并行)的。那么FPGA如何处理明显具有时间上先后顺序的事件呢?这个时候我们就需要使用到状态机了。” —— https://blog.csdn.net/wuzhikaidetb/article/details/119421783

以前总是用C语言编程,顺序执行的逻辑已深入人心,而状态机就能够实现顺序执行的逻辑。

状态机的每一个状态代表一个事件,从执行当前事件到执行另一事件我们称之为状态的跳转或状态的转移,状态机通过控制各个状态跳转控制流程,使得整个代码看上去更加清晰易懂,在控制复杂流程的时候,状态机优势明显。

1 状态机分类及我的选择

1.1 根据状态机的输出与输入条件相关与否,可分为Moore和Mealy型。

Mealy 状态机:输出不仅取决于当前状态,还取决于输入状态。

Moore 状态机:组合逻辑的输出只取决于当前状态,而与输入状态无关。

Mealy 状态机比Moore状态机的状态个数要少

Mealy 状态机比Moore状态机的输出要早一个时钟周期

可见,从代码的理解,可读性来讲, Moore的状态要全一点。从程序的运行效率来讲,Mealy的输出要比Moore早一个时钟周期,效率更高。

1.2 根据状态机的代码写法,可分为一段式、两段式、三段式。

一段式状态机:整个状态机写到一个 always模块里面,在该模块中既描述状态转移,又描述状态的输入和输出。

二段式状态机:用两个 always 模块来描述状态机,其中一个 always 模块采用同步时序描述状态转移;另一个模块采用组合逻辑判断状态转移条件,描述状态转移规律以及输出。不同于一段式状态机的是,它需要定义两个状态,现态和次态,然后通过现态和次态的转换来实现时序逻辑。

三段式状态机:在两个 always 模块描述方法基础上,使用三个always 模块,一个always 模块采用同步时序描述状态转移,一个 always 采用组合逻辑判断状态转移条件,描述状态转移规律,另一个 always 模块描述状态输出。

1.3 我的选择

一段式的状态机不好维护,我接手项目看到别人的一段式状态机头都大了,所以我不会选择写一段式。 二段式的状态机有点低不成高不就感觉,除非输出和状态的关系特别简单(比如本文的例子就是,经典的3块钱可乐问题也是),否则不建议使用。因此一般来讲,还是用三段式状态机,好维护。综上,我后续的代码开发主打一手 Moore三段式 状态机, 暂不考虑效率问题,主要还是要让代码写的易读,好维护。

2 样例介绍

开发板上有两个按钮,两个led灯。  一个按钮做复位按钮。 另一个按钮作为控制两个LED灯的输入。 按钮每按下一次,LED的值加一, 00 01 10 11 00 如此循环。按钮按下释放的过程,有5个状态,  空闲(复位状态)→按钮未按下 → 按下的抖动 → 按钮已按下 → 释放抖动 → 按钮未按下。

 

 

在开关按下或者释放的时候,都会发生抖动,比如按钮按下的时候,我们可以对按键处于低电平做一个累加计数。 比如当我一直处于低电平累计计数20ms了,那我认为你已经按键按下了,稳定了。 那在这个还没有累积到20ms的阶段呢,我们认为它还在不稳定的状态、抖动状态,JITIER1。释放按钮同理。

我们的输出是什么?  当我的按钮按下一次, 我的LED灯就会加一。  复位状态下是两个灯都不亮。 按第一次,低位灯亮,高位灯灭。 按第二次,高位灯亮,低位灯灭。 按第三次,高位灯和地位灯都亮,按第四次,两个灯都灭。如此循环。

3 样例代码1

以下是我根据样例的理解,写的相关代码

`timescale 1ns / 1ps

module key_fsm(
        input   wire            clk     ,       // 时钟
        input   wire            rst_n   ,       // 复位信号
        input   wire            key_in  ,       // 按键输入
        output  wire    [1:0]   led             // LED灯输出    
);

//==================================================================
//                        Parameter define
//==================================================================

localparam      IDLE            = 5'b00001;
localparam      BUTTON_UP       = 5'b00010;
localparam      JUTTER1         = 5'b00100;
localparam      BUTTON_DOWN     = 5'b01000;
localparam      JUTTER2         = 5'b10000;
localparam      CNT_MAX         = 1_000_000 - 1;        // 20ms


//==================================================================
//                        Internal Signals
//==================================================================

reg     [4:0]   key_state       ;       // 按钮状态
reg     [4:0]   next_state      ;       // 下一个状态

reg             key_d           ;       // 按钮输入延迟,打一拍 消抖
reg             key_dd          ;       // 按钮输入延迟,打两拍 消抖
reg     [31:0]  cnt_jutter1     ;       // 抖动1计数
reg     [31:0]  cnt_jutter2     ;       // 抖动2计数
reg             key_flag        ;       // 按钮按下标志
reg     [1:0]   led_r           ;       // LED灯输出       

always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                key_state       <= IDLE;            
        end
        else begin
                key_state       <= next_state;
        end
end

always @(*) begin
        case(key_state)
                IDLE:begin
                        if(rst_n == 1'b0) begin
                                next_state <= IDLE;
                        end
                        else begin
                                next_state <= BUTTON_UP;
                        end
                end

                BUTTON_UP:begin
                        if (key_dd == 1'b0) begin
                                next_state <= JUTTER1;
                        end
                        else begin
                                next_state <= BUTTON_UP;
                        end
                end

                JUTTER1:begin
                        if (key_dd == 1'b1) begin
                                next_state <= BUTTON_UP;
                        end
                        else if (cnt_jutter1 == CNT_MAX) begin
                                next_state <= BUTTON_DOWN;
                        end
                        else begin
                                next_state <= JUTTER1;
                        end
                
                end

                BUTTON_DOWN:begin
                        if (key_dd == 1'b1) begin
                                next_state <= JUTTER2; 
                        end
                        else begin
                                next_state <= BUTTON_DOWN;
                        end
                end

                JUTTER2:begin
                        if (key_dd == 1'b0) begin
                                next_state <= BUTTON_DOWN;
                        end
                        else if (cnt_jutter2 == CNT_MAX) begin
                                next_state <= BUTTON_UP;
                        end
                        else begin
                                next_state <= JUTTER2;
                        end
                end


                default:begin                   // 避免锁存器
                        next_state <= IDLE;
                end
        endcase
end


//----------------------------- key_d -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                key_d   <= 1'b1;        
        end
        else begin
                key_d   <= key_in;    
        end
end

//----------------------------- key_dd -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                key_dd  <= 1'b1;         
        end
        else begin
                key_dd <= key_d;
        end
end

//----------------------------- cnt_jutter1 -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                  cnt_jutter1 <= 'd0;  
        end
        else if (key_state == JUTTER1 && key_dd == 1'b0) begin
                if (cnt_jutter1 == CNT_MAX) begin
                        cnt_jutter1 <= 'd0;
                end
                else begin
                        cnt_jutter1 <= cnt_jutter1 + 1'b1;
                end
        end
        else begin
                cnt_jutter1 <= 'd0;
        end
end

//----------------------------- cnt_jutter2 -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                cnt_jutter2 <= 'd0;        
        end
        else if (key_state == JUTTER2 && key_dd == 1'b1) begin
                if(cnt_jutter2 == CNT_MAX) begin
                        cnt_jutter2 <= 'd0;
                end
                else begin
                        cnt_jutter2 <= cnt_jutter2 + 1'b1;
                end

        end
        else begin
                cnt_jutter2 <= 'd0;
        end
end

//----------------------------- key_flag -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                key_flag        <= 1'b0;                    
        end
        else if(cnt_jutter1 == CNT_MAX && key_state == JUTTER1) begin
                key_flag        <= 1'b1;
        end
        else begin
                key_flag        <= 1'b0;
        end
end

//----------------------------- led_r -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                led_r <= 2'b00;        
        end
        else if (key_flag == 1'b1) begin
                led_r <= led_r + 1'b1;
        end
        else begin
                led_r <= led_r;
        end
end

assign  led = ~led_r;

// 这个状态机, 没有第三段,输出只和当前的状态有关,和当前的输入无关。

endmodule

写完运行也成功了,但是却更迷茫了,这个功能用状态机来实现, 输出仅仅和其中一个状态有关联没有专门的always模块来描述各种状态系统的输出,这使得我心里那一关过不去, 真的,有这个必要吗? 这一定是我的打开方式不对。

4 样例代码2

苦思冥想后,我决定重新定义这个功能模块状态机的状态。 IDLE(VALUE0) → VALUE1 → VALUE2 → VALUE3。 每个状态都对应一种输出。 复位和默认状态为IDLE,当按键按下一次,触发状态向下一个状态转移。如果没有按下,则保持当前状态。

代码如下:

`timescale 1ns / 1ps


module key_fsm(
        input   wire            clk     ,       // 时钟
        input   wire            rst_n   ,       // 复位信号
        input   wire            key_in  ,       // 按键输入
        output  wire    [1:0]   led             // LED输出
);


//==================================================================
//                        Parameter define
//==================================================================

localparam IDLE         = 4'b0001;
localparam VALUE1       = 4'b0010;
localparam VALUE2       = 4'b0100;
localparam VALUE3       = 4'b1000;
localparam CNT_MAX      = 1000_000 - 1; // 20ms


//==================================================================
//                        Internal Signals
//==================================================================

reg     [3:0]   state           ;
reg     [3:0]   next_state      ;
reg     [31:0]  cnt             ;
reg             key_d           ;
reg             key_dd          ;
reg             flag            ;
reg             key_flag        ;
reg     [1:0]   led_r           ;

assign led = ~led_r ;

//----------------------------- FSM_Part1 -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                state   <= IDLE;            
        end
        else begin
                state   <= next_state;
        end
end

//----------------------------- FSM_Part2 -----------------------------
always @(*) begin
        case(state)
                IDLE:begin
                        if (key_flag == 1'b1) begin
                                next_state <= VALUE1;
                        end
                        else begin
                                next_state <= IDLE;
                        end
                end

                VALUE1:begin
                        if (key_flag == 1'b1) begin
                                next_state <= VALUE2;
                        end
                        else begin
                                next_state <= VALUE1;
                        end
                end

                VALUE2:begin
                        if (key_flag == 1'b1) begin
                                next_state <= VALUE3;
                        end
                        else begin
                                next_state <= VALUE2;
                        end
                end

                VALUE3:begin
                        if (key_flag == 1'b1) begin
                                next_state <= IDLE;
                        end
                        else begin
                                next_state <= VALUE3;
                        end
                end

                default:begin                   // 避免锁存器
                        next_state <= IDLE;
                end
        endcase
end

//----------------------------- FSM_Part3 -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                led_r <= 2'b00;
        end
        else begin
                case(state)
                        IDLE:begin
                                led_r   <= 2'b00;
                        end
                        VALUE1:begin
                                led_r   <= 2'b01;
                        end
                        VALUE2:begin
                                led_r   <= 2'b10;
                        end
                        VALUE3:begin
                                led_r   <= 2'b11;
                        end
                        default:begin
                                led_r   <= 2'b00;
                        end
                endcase
        end
end

//----------------------------- key_d -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                key_d   <= 1'b1;        
        end
        else begin
                key_d   <= key_in;    
        end
end

//----------------------------- key_dd -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                key_dd  <= 1'b1;         
        end
        else begin
                key_dd  <= key_d;
        end
end

//----------------------------- cnt -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                cnt     <=  'd0;             
        end
        else if( key_dd == 1'b0) begin
                if (cnt == CNT_MAX) begin
                        cnt     <= 'd0;
                end
                else begin
                        cnt     <= cnt + 1'b1;
                end
        end
        else begin
                cnt     <= 'd0;
        end        
end

//----------------------------- flag -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                flag    <= 1'b0;     
        end
        else if(cnt == CNT_MAX) begin
                flag    <= 1'b1;
        end
        else if(key_dd == 1'b1) begin
                flag    <= 1'b0;
        end
        else begin
                flag    <= flag;
        end
end

//----------------------------- key_flag -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                key_flag <= 1'b0;         
        end
        else if (cnt == CNT_MAX && flag == 1'b0) begin
                key_flag <= 1'b1;
        end
        else begin
                key_flag <= 1'b0;
        end
end

endmodule

可以看到,修改后,代码的逻辑变得更加清晰, 三段式的状态机,三个部分都有。  剩余的所有代码,我都是为了去得到状态机中,用于转移状态的key_flag变量的计算。

例子其实很简单,同样的功能,我的上一篇文章:FPGA_学习_07_按键消抖也实现了,代码量更少,更清爽。 本文主要是为了去探讨三段式状态机。

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

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

相关文章

操作系统页面置换算法详解(OPT算法、FIFO算法、LRU算法解释+缺页数计算)

名人说&#xff1a;一花独放不是春&#xff0c;百花齐放花满园。——《增广贤文》 作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 〇、页面置换算法是什么&#xff1f;一、OPT算法1、算法介绍2、举例二、FIFO算法1、…

【嵌入式模型转换】算能盒子SE5 芯片板子BM1684

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言1.环境准备2. 开发机安装环境 前言 官网文档指引&#xff1a;https://doc.sophgo.com/sdk-docs/v23.03.01/docs_latest_release/docs/SophonSDK_doc/zh/html/c…

ffmpeg capture decklink

确保decklink设备已经接入 lspci | grep Blackm02:00.0 Multimedia video controller: Blackmagic Design DeckLink SDI Micro 确保decklink驱动已经正确安装 lsmod | grep blackmagicblackmagic_io 2068480 2 确保ffmpeg已经启用decklink&#xff0c;如何配置之前的博文已经…

高磁场型霍尔效应测试系统

高磁场型霍尔效应测试系统由&#xff1a;电磁铁、电磁铁电源、高精度恒流源高精度电压表、高斯计、霍尔效应样品支架、标准样品、系统软件。用于测量半导体材料的载流子浓度、迁移率、电阻率、霍尔系数等重要参数&#xff0c;而这些参数是了解半导体材料电学特性必须预先掌控的…

conda下jupyter lab 多kernel

1.安装ipykernel pip install ipykernel2.手动添加 --kernel python -m ipykernel install --name tensorflow --display-name "tensroflow"3. 查看创建的kernel jupyter kernelspec list4. 修改配置文件kernel.json&#xff0c;使得已创建的kernel匹配 5. 修改配…

【Axure教程】根据标签数自动调整尺寸的多选下拉列表

​多选下拉列表常用于需要用户从一组选项中选择多个选项的情况。它提供了一个下拉菜单&#xff0c;用户可以点击展开并选择他们感兴趣的多个选项。多选下拉列表可以用于展示可选标签&#xff0c;并允许用户选择多个标签。例如&#xff0c;在一个博客发布界面上&#xff0c;可以…

【Flutter】如何在 Flutter 中获取设备 ID

文章目录 一、 前言二、 设备 ID 的重要性1. 什么是设备 ID2. 设备 ID 的作用 三、 在 Flutter 中获取设备 ID1. 需要的工具和库2. 简单代码示例3. 完整可以运行的代码 四、 注意事项1. 权限问题2. 设备兼容性问题 五、 总结 一、 前言 在移动应用开发中&#xff0c;有时我们需…

飞书小程序开发教程——获取app_access_token

前言 最近在玩飞书小程序&#xff0c;真是受够了某信的小程序&#xff0c;一坨坨的限制加恶心人的手段。体验了飞书加飞书文档的功能真的还是飞书好用&#xff0c;又可以定制机器人啥的真方便。 但是飞书除了开发者文档真没啥资料&#xff0c;估计没啥人写吧…本专栏后续就慢慢…

DVPP媒体数据处理视频编码问题案例

DVPP&#xff08;Digital Vision Pre-Processing&#xff09;是昇腾AI处理器内置的图像处理单元&#xff0c;通过AscendCL媒体数据处理接口提供强大的媒体处理硬加速能力&#xff0c;主要功能包括图像编解码、视频编解码、图像抠图缩放等。 本期就分享几个关于DVPP视频编码问题…

未磁科技全球首台64通道无液氦心磁图仪及首个培训基地落户北京安贞医院

【全球首台64通道无液氦心磁图仪在北京安贞医院举行开机仪式】 近日&#xff0c;在北京安贞医院举行了未磁科技全球首台64通道无液氦心磁图仪开机仪式&#xff0c;中国医学装备协会赵自林理事长、北京安贞医院纪智礼书记、张宏家院长、宋现涛教授&#xff0c;以及未磁科技蔡宾…

【离散化+枚举】代码源每日一题div2 全部相等

全部相等 - 题目 - Daimayuan Online Judge 题意&#xff1a; 思路&#xff1a; 首先&#xff0c;子序列最长&#xff0c;想到了DP&#xff0c;但是子序列DP&#xff0c;D不出来&#xff0c;所以要放弃想法 不难发现&#xff0c;这道题和顺序没有关系&#xff0c;所以可以说…

Vision Pro,难成初代iPhone

“一个全新的增强显示平台&#xff0c;一个革命性的新产品”&#xff0c;这是苹果CEO库克给Apple Vision Pro的评价。 当苹果拿出Vision Pro时&#xff0c;想必整个科技行业都感到震惊。这不仅是因为苹果时隔十六年后&#xff0c;再次在WWDC带来“革命性产品”&#xff0c;更是…

Ai 赋能|这 6 款免安装的工具,功能超神~

今天小呆给小伙伴们分享几款小巧好用的工具&#xff0c;功能非常实用&#xff0c;而且全都免费&#xff0c;值得大家使用&#xff01; 1、Pix Fix 修复画质损坏的图像 Pix Fix 是一个免费的线上图片工具&#xff0c;可以对过度压缩造成损毁的图片以算法为基础进行噪点的修复&…

【枚举+数学】代码源每日一题div2 数学

数学 - 题目 - Daimayuan Online Judge 题意&#xff1a; 思路&#xff1a; 首先把条件理一下 gcd(s1,s2)K 按照惯用套路 设 s1k1*K s2k2*K 那么就是(k1k2)*Ksum 所以K是sum的一个因子 因此只需要枚举sum的因子即可 但是注意数据范围&#xff1a;1e9 说明不能直接枚…

MySQL数据库加密

有时候为防止别人攻击数据库&#xff0c;直接读取修改数据&#xff0c;我们会对数据库数据进行加密。 一&#xff1a;AES_ENCRYPT(需要加密的数据,密钥) AES_DECRYPT(需要解密的数据,刚才的密钥) 如&#xff1a;update area set jiamiAES_ENCRYPT(test数据加密,miyao) where …

Python基础知识掌握之 字符串格式化

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 为了确保字符串按预期显示&#xff0c;我们可以使用 format() 方法对结果进行格式化。 字符串 format() format() 方法允许您格式化字符串的选定部分。 有时文本的一部分是你无法控制的&#xff0c;也许它们来自数据…

分享几个免费设计生成工具 (第二期)

今天来给大家分享几个国外免费的设计生成工具 A. Tools https://www.a.tools/ 一个免费实用的在线工具箱&#xff0c;提供了 85 个特色工具&#xff0c;包含图形图像、日期和时间、文本编辑器、解密和加密、编程与设计、图表生成器等等&#xff0c;还有热门的 AI聊天提示生成器…

freemarker学习记录

freemarker 介绍 FreeMarker 是一款模板引擎&#xff1a; 即一种基于模板和要改变的数据&#xff0c; 并用来生成输出文本(HTML网页&#xff0c;电子邮件&#xff0c;配置文件&#xff0c;源代码等)的通用工具。 它不是面向最终用户的&#xff0c;而是一个Java类库&#xff0c…

electron 生命周期

生命周期事件&#xff1a; reday: app初始化完成dom-ready:一个窗口的文本加载完成&#xff0c;可以进行dom操作did-finish-load: 导航完成时触发,选项卡旋转器停止旋转时&#xff08;发生在dom-ready后&#xff09;比如点击一个选项卡后&#xff0c;某一个选项下的dom加载完成…

基于zustand维护的一套跨框架(react/vue)跨应用的状态管理及共享方案

zustand-pub 只要从事前端开发&#xff0c;不论是小程序还是web&#xff0c;都绕不开状态管理。 众所周知&#xff0c; zustand 是一套轻量、便捷、可拓展的状态管理方案&#xff0c;不论国内 or 国外&#xff0c;都备受喜爱&#xff0c;star 数已接近 3W。 而 zustand-pub 则…