小梅哥Xilinx FPGA学习笔记16——FSM(状态机)的学习

news2024/11/28 16:26:37

目录

一、 状态机导读

1.1 理论学习   

1.2 状态机的表示

1.3 状态机编码

1.4 状态机描述方式

二 、实战演练一(来自野火)

2.1 实验目标

2.2 模块框图

2.3 状态转移图绘制

2.4 设计文件

2.5 仿真测试文件

2.6 仿真结果

三、 实战演练二(来自野火)

3.1 实验目标

3.2 模块框图

3.3 状态转移图绘制

3.4 设计文件

3.5 仿真测试文件

3.6 仿真结果

四、 实战演练三(来自小梅哥)

4.1 实验目标

4.2 模块框图

4.3 端口功能描述

4.4 设计文件

4.5 仿真测试文件

4.6 仿真结果

五、 实战演练四(来自小梅哥)

5.1 实验目标

5.2 设计文件

5.2.1 顶层文件

5.2.2 串口接收文件

5.2.3 字符检测模块

5.3 引脚约束文件

5.4 板上验证


一、 状态机导读

       状态机大家一定都听说过,为什么需要状态机呢?通过前面的学习我们都知道FPGA 是并行执行的,如果我们想要处理具有前后顺序的事件该怎么办呢?这时就需要引入状态机了。本文将从原理、实例、应用为大家总结了状态机设计和实现的方法。

1.1 理论学习   

       状态机简写为 FSM Finite State Machine),也称为同步有限状态机,我们一般简称为状态机,之所以说“同步”是因为状态机中所有的状态跳转都是在时钟的作用下进行的,而“有限”则是说状态的个数是有限的。状态机根据影响输出的原因分为两大类,即 Moore 型状态机Mealy 型状态机, Moore 型状态机的输出只和当前状态有关而与输入无关 ;Mealy 型状态机的输出不仅和当前状态有关还和输入有关。

1.2 状态机的表示

        那么状态机该如何表示呢?我们会在一些数据手册中经常看到如下图 所示的图,这种图就是用于表达状态机的状态转移图。有了状态转移图,我们就可以对状态机想要表达的东西一清二楚,包括用代码去实现都会变得很容易,所以说如何根据实际需求设计抽象出符合要求的状态机是非常关键的。

1.3 状态机编码

       状态机可根据控制信号按照预先设定的状态进行状态转移,这就出现了如何对状态进行有效编码的问题。编码方式,最简单的就是直接使用 二进制编码 进行表示,除此之外还有使用 格雷码、独热码 。假设有八个状态从 A H ,利 用不同的编码格式分别如下表所示。

       从上表中可以发现:独热码,每一个状态均使用一个寄存器, 在与状态比较时仅仅需要比较一位, 相比其他译码电路简单;格雷码,所需寄存器数与二 进制码一样,译码复杂,但相邻位只跳动一位,一般用于异步多时钟域多 bit 的转换,如异步 FIFO ;二进制码,最为常见的编码方式,所用寄存器少,译码 较复杂。
       按照 FPGA 厂商给的建议,选择哪一种编码格式是要根据状态机的复杂度、 器件类型以及从非法状态中恢复出来的要求来确定。在使用不同的编码格式生 成出来的 RTL 视图中可以看出二进制比独热码使用更少的寄存器。二进制用 7 个寄存器就可以实现 100 个状态的状态机,但是独热码就需要 100 个寄存器。 但是另一方面,虽然独热码使用更多的寄存器但是其组合逻辑相对简单。一般 CPLD 中,由于提供较多的组合逻辑资源而推荐使用前者,而在 FPGA 中提 供较多的时序逻辑而推荐使用后者。

1.4 状态机描述方式

状态机描述方式,可分为一段式两段式以及三段式
       一段式,整个状态机写到一个 always 模块里面。在该模块中既描述状态转 移,又描述状态的输入和输出。
       两段式,用两个 always 模块来描述状态机。其中一个 always 模块采用同步 时序描述状态转移,另一个模块采用组合逻辑判断状态转移条件(若也使用时序逻辑就称为新二段式),描述状态转 移规律及其输出。
       三段式,在两个 always 模块描述方法基础上,使用三个 always 模块。一个 always 模块采用同步时序描述状态转移,一个 always 采用组合逻辑判断状态转 移条件,描述状态转移规律,另一个 always 模块描述状态输出 ( 可以用组合电路 输出,也可以时序电路输出 )

二 、实战演练一(来自野火)

2.1 实验目标

       我们都在大街见过自动售卖饮料的可乐机,殊不知整个可乐机系统的售卖过程就可以很好的用状态机来实现,为了学习我们先来实现一个简单的可乐机系统。可乐机每次只能投入 1 枚 1 元硬币,且每瓶可乐卖 3 元钱,即投入 3 个硬币就可以让可乐机出可乐,如果投币不够 3 元想放弃投币需要按复位键,否则之前投入的钱不能退回。

2.2 模块框图

       首先必不可少的是时钟和复位信号;其次是投币 1 元的输入信号,我们取名为 pi_money ;可乐机输出我们购买的可乐,取名为 po_cola 。根据上面的分析设计出的 框图如下图 所示。
端口列表与功能总结如下面表格 所示。

2.3 状态转移图绘制

       斜杠左边表达的是状态的输入,斜杠右边表达的是状态的输出,结构非常的简单,各状态之间的功能、跳转的条件、输入输出都能够在状态转移图中非常清楚的表达出来。
1 、输入: 投入 1 元硬币;
2 、输出: 出可乐、不出可乐;
3 、状态: 可乐机中有 0 元、可乐机中有 1 元、可乐机中有 2 元、可乐机中有 3 元。

2.4 设计文件

module simple_fsm(
    input sys_clk,
    input sys_rst_n,
    input pi_money,
    output reg po_cola
    );
    
    parameter IDLE = 0;//记住独热码,二进制码,格雷码的区别。
    parameter ONE = 1;
    parameter TWO = 2;
    
    reg [1:0]state;
    
    always@(posedge sys_clk or negedge sys_rst_n)
        if(!sys_rst_n)begin
            state <= IDLE; 
            po_cola <= 0;
        end
        else begin    
            case(state)    
                IDLE: begin
                    if(pi_money)begin
                        state <= ONE;
                        po_cola <= 0; //可以使用两个时序逻辑分别描述状态转换和输出。
                    end
                    else
                        state <= IDLE;                      
                end
                ONE:
                    if(pi_money)begin
                        state <= TWO;
                        po_cola <= 0; 
                    end
                    else
                        state <= ONE; 
                TWO:
                    if(pi_money)begin
                        state <= IDLE;
                        po_cola <= 1; 
                    end
                    else
                        state <= TWO;        
                default:begin state <= IDLE;po_cola <= 0;end
            endcase
        end   
endmodule

2.5 仿真测试文件

`timescale 1ns / 1ps
module simple_fsm_test();

reg sys_clk ;
reg sys_rst_n;
reg pi_money;
wire po_cola;

simple_fsm simple_fsm_inst(
    .sys_clk(sys_clk),
    .sys_rst_n(sys_rst_n),
    .pi_money(pi_money),
    .po_cola(po_cola)
    );
    
    initial   sys_clk = 1;
    always #10  sys_clk = ~sys_clk;


    initial 
    begin
    sys_rst_n = 0;
    pi_money = 0;
    #201;
    sys_rst_n = 1;
    #20000;
    pi_money = 1;
    #20;
    pi_money = 0;
    #20000;
    pi_money = 1;
    #20;
    pi_money = 0;
    #20000;
    pi_money = 1;
    #20;
    pi_money = 0;
    #20000;
    $stop;
    end
endmodule

2.6 仿真结果

三、 实战演练二(来自野火)

3.1 实验目标

      上一部分中的可乐机比较简单,只能投 1 元的硬币,但是我们生活中还有 0.5 元的硬币,所以我们在本章中将可乐机设计的稍微复杂一些,做成既可以投 1 元的硬币也可以投0.5 元的硬币,然后我们把可乐的定价改为 2.5 元一瓶。我们增加了可乐机的复杂度而引入了新的问题: 可乐定价为 2.5 元一瓶,可投入 0.5 元、1 元硬币,投币不够 2.5 元需要按复位键退回钱款,投币超过 2.5 元需找零。

3.2 模块框图

首先时钟和复位信号依然是必不可少的输入信号;输入信号还有投币,除了可以投 1 元外,还可以投 0.5元,所以我们将投币 1 元的输入信号取名为 pi_money_one ,将投币 0.5 元的输入信号取名
pi_money_half;可乐机的输出除了可乐还可能会有找零(找零的结果只有一种即找回0.5元)我们将可乐机输出购买可乐的信号取名为 po_cola,找零的信号取名为po_money。根据上面的分析设计出的框图如下图所示。

端口列表与功能总结如下面表格所示。

3.3 状态转移图绘制

1 、输入: 投入 0.5 元硬币、投入 1 元硬币;
2 、输出: 不出可乐 / 不找零、出可乐 / 不找零、出可乐 / 找零;
3 、状态: 可乐机中有 0 元、可乐机中有 0.5 元、可乐机中有 1 元、可乐机中有 1.5 元、可
乐机中有 2 元、可乐机中有 2.5 元、可乐机中有 3 元。

3.4 设计文件

module hard_fsm(
    input sys_clk,
    input sys_rst_n,
    input pi_money_half,
    input pi_money_one,
    output reg po_cola,
    output reg po_money
    );
    
    parameter IDLE  = 0,//使用二进制码
               HALF = 1,
               ONE = 2,
               ONE_HALF = 3,
               TWO = 4,
               TWO_HALF = 5;
               
    reg [2:0]state;
    
    always@(posedge sys_clk or negedge sys_rst_n)
        if(!sys_rst_n)begin
            state <= IDLE;    
    end
        else begin 
            case(state)
                IDLE:if(pi_money_half)begin
                    state <= HALF;
                end
                    else if(pi_money_one)begin
                    state <= ONE;
                end
                    else begin
                    state <= IDLE;
                end
                HALF:if(pi_money_half)begin
                    state <= ONE;
                end
                    else if(pi_money_one)begin
                    state <= ONE_HALF;
                end
                    else begin
                    state <= HALF;
                end
                ONE:if(pi_money_half)begin
                    state <= ONE_HALF;
                end
                    else if(pi_money_one)begin
                    state <= TWO;
                end
                    else begin
                    state <= ONE;
                end
                ONE_HALF:if(pi_money_half)begin
                    state <= TWO;
                end
                    else if(pi_money_one)begin
                    state <= IDLE;
                end
                    else begin
                    state <= ONE_HALF;
                end   
                TWO:if(pi_money_half || pi_money_one)begin
                    state <= IDLE;
                end
                    else begin
                        state <= TWO;
                end    
                default:state <= IDLE;
            endcase
        end
        
       always@(posedge sys_clk or negedge sys_rst_n)//使用两段式状态机写法
           if(!sys_rst_n)
               po_cola <= 0;  
           else if(((state == TWO) && (pi_money_half == 1))
               ||((state ==ONE_HALF) &&(pi_money_one == 1))
               ||((state ==TWO) &&(pi_money_one == 1)))
               po_cola <= 1;
           else
               po_cola <= 0;
                   
        always@(posedge sys_clk or negedge sys_rst_n)
           if(!sys_rst_n)
               po_money <= 0;      
           else if((state == TWO) && (pi_money_one == 1))
               po_money <= 1;    
           else
               po_money <= 0;
endmodule

3.5 仿真测试文件

`timescale 1ns/1ns
module hard_fsm_tb();

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//

//reg define
 reg sys_clk;
 reg sys_rst_n;
 reg pi_money_one;
 reg pi_money_half;
 reg random_data_gen;
 
 //wire define
 wire po_cola;
 wire po_money;
 
 //********************************************************************//
 //***************************** Main Code ****************************//
 //********************************************************************//
 
 //初始化系统时钟、全局复位
 initial begin
 sys_clk = 1'b1;
 sys_rst_n <= 1'b0;
 #20
 sys_rst_n <= 1'b1;
 end
 
 //sys_clk:模拟系统时钟,每 10ns 电平翻转一次,周期为 20ns,频率为 50MHz
 always #10 sys_clk = ~sys_clk;
 
 //random_data_gen:产生非负随机数 0、1
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
    random_data_gen <= 1'b0;
 else
    random_data_gen <= {$random} % 2;
 
 //pi_money_one:模拟投入 1 元的情况
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            pi_money_one <= 1'b0;

        else
            pi_money_one <= random_data_gen;

//pi_money_half:模拟投入 0.5 元的情况
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            pi_money_half <= 1'b0;
        else
//取反是因为一次只能投一个币,即 pi_money_one 和 pi_money_half 不能同时为 1
            pi_money_half <= ~random_data_gen;

//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//

//------------------------complex_fsm_inst------------------------
    hard_fsm hard_fsm_inst(
        .sys_clk (sys_clk ), //input sys_clk
        .sys_rst_n (sys_rst_n ), //input sys_rst_n
        .pi_money_one (pi_money_one ), //input pi_money_one
        .pi_money_half (pi_money_half ), //input pi_money_half

        .po_cola (po_cola ), //output po_money
        .po_money (po_money ) //output po_cola
); 

endmodule

3.6 仿真结果

四、 实战演练三(来自小梅哥)

4.1 实验目标

实现字符串检测状态机: 为了实现对字符串“ hello ”的检测,首先画出其状态转移图,如下图所示。

由上图可以看出,如果在状态不符合转换条件,那么状态机要么回到初态检测 h 出现,要么回到状态 e (不满足向后转移条件,但是输入字符为‘ h ’), 且每一个状态都有特定的方向,其输出仅由当前状态决定,因此这是一个摩尔型状态机。

4.2 模块框图

4.3 端口功能描述

4.4 设计文件

module hello(
    input sys_clk,
    input sys_rst_n,
    input [7:0]data_in,//输入数据
    input data_in_valid,//有效数据输入
    output reg check_ok//检测出来一个hello就出现一个高脉冲
);
    
    //定义5个状态
    localparam CHECK_h = 0,
               CHECK_e = 1,
               CHECK_l1 = 2,
               CHECK_l2 = 3, 
               CHECK_o = 4;
    reg [2:0]state;
    
//我使用的是新二段式状态机,小梅哥用的是一段式状态机。
 always@(posedge sys_clk or posedge sys_rst_n)   
    if(!sys_rst_n)   
        state <= CHECK_h;    
    else begin
        case(state)
            CHECK_h:begin
                if(data_in_valid && data_in == "h")
                    state <= CHECK_e;
                else 
                    state <= CHECK_h;                 
            end    
            CHECK_e:begin
                if(data_in_valid && data_in == "e")//千万注意if还有else if的顺序问题,不然可能会造成检测不成功的现象出现。比如出现hhello就无法检测出来。
                    state <= CHECK_l1;
                else if(data_in_valid && data_in == "h")
                    state <= CHECK_e; 
                else if(data_in_valid)
                    state <= CHECK_h;   
                else
                    state <= CHECK_e;      
            end
            CHECK_l1:
                if(data_in_valid && data_in == "l")
                    state <= CHECK_l2;
                else if(data_in_valid && data_in == "h")
                    state <= CHECK_e; 
                else if(data_in_valid)
                    state <= CHECK_h; 
                else
                    state <= CHECK_l1;   
            CHECK_l2:
                if(data_in_valid && data_in == "l")
                    state <= CHECK_o;
                else if(data_in_valid && data_in == "h")
                    state <= CHECK_e; 
                else if(data_in_valid)
                    state <= CHECK_h; 
                else
                    state <= CHECK_l2;  
            CHECK_o:
                if(data_in_valid && data_in == "o")
                    state <= CHECK_h;//检测完毕回到起始状态
                else if(data_in_valid && data_in == "h")
                    state <= CHECK_e; 
                else if(data_in_valid)
                    state <= CHECK_h;              
                else
                    state <= CHECK_o;  
        endcase
    end   
  //输出单独控制逻辑  
always@(posedge sys_clk or posedge sys_rst_n)   
    if(!sys_rst_n)   
        check_ok <= 0; 
    else if((state == CHECK_o) && data_in_valid && (data_in == "o"))
        check_ok <= 1;   
    else if(state == CHECK_h)
        check_ok <= 0;  
    else
        check_ok <= check_ok;     
endmodule

4.5 仿真测试文件

`timescale 1ns / 1ps
`define CLK_PERIOD 20
module hello_tb();

reg sys_clk;
reg sys_rst_n;
reg data_valid;
reg [7:0]data_in;
wire check_ok;

hello hello(
    .sys_clk(sys_clk),
    .sys_rst_n(sys_rst_n),
    .data_in(data_in),
    .data_in_valid(data_valid),
    .check_ok(check_ok)
    );

initial sys_clk = 1;
always#(`CLK_PERIOD/2) sys_clk = ~sys_clk;

        initial 
            begin
            sys_rst_n = 0;
            data_valid = 0;
            data_in = 0;
            #(`CLK_PERIOD*20);
            sys_rst_n = 1;
            #(`CLK_PERIOD*20 + 1);
        repeat(2)
            begin
            gen_char("I");
            #(`CLK_PERIOD);
            gen_char("A");
            #(`CLK_PERIOD);
            gen_char("h");
            #(`CLK_PERIOD);
            gen_char("e");
            #(`CLK_PERIOD);
            gen_char("l");
            #(`CLK_PERIOD);
            gen_char("l");
            #(`CLK_PERIOD); 
            gen_char("h");
            #(`CLK_PERIOD);
            gen_char("h");
            #(`CLK_PERIOD);
            gen_char("e");
            #(`CLK_PERIOD);
            gen_char("l");
            #(`CLK_PERIOD);
            gen_char("l");
            #(`CLK_PERIOD);
            gen_char("o");
            #(`CLK_PERIOD);
            gen_char("e");
            #(`CLK_PERIOD);
            gen_char("h");
            #(`CLK_PERIOD);
            gen_char("h");
            #(`CLK_PERIOD);
            gen_char("o");
            #(`CLK_PERIOD); 
            end
            #200;
            $stop;
            end
        task gen_char;
        input [7:0]char; 
        begin
            data_in = char;
            data_valid = 1'b1;
            #(`CLK_PERIOD);
            data_valid = 1'b0;
        end
        endtask
endmodule

4.6 仿真结果

五、 实战演练四(来自小梅哥)

5.1 实验目标

       通过上面字符串检测状态机的学习,结合前面章节学习的串口接收模块可以设计一个串口接收字符串检测系统。整个系统的结构框图如下图所示。系 统功能为 FPGA 通过串口接收模块接收 PC 通过串口发送的字符串数据流,然后通过字符串检测模块对字符串进行检测,每次完整接收到“ hello ”字符串就让 LED 灯翻转。

5.2 设计文件

5.2.1 顶层文件

module fsm_hello_test(
    input sys_clk,
    input sys_rst_n,
    input uart_rxd,
    output reg Led
);

    wire [7:0]data_in;
    wire data_in_valid;
    wire check_ok;
     
    zdyz_rs232_rx #(
        .CLK_FREQ (5000_0000),//波特率设置
        .UART_BPS (115200)
    )
    zdyz_rs232_rx(
    .sys_clk(sys_clk) , //系统时钟
    .sys_rst_n(sys_rst_n) , //系统复位,低有效
    
    .uart_rxd(uart_rxd) , //UART 接收端口
    .uart_rx_done(data_in_valid), //UART 接收完成信号,接收完成后就代表数据有效
    .uart_rx_data(data_in) //UART 接收到的数据送给字符检测模块作为输入
);

hello hello(
    .sys_clk(sys_clk),
    .sys_rst_n(sys_rst_n),
    .data_in(data_in),
    .data_in_valid(data_in_valid),
    .check_ok(check_ok)
);

//每检测一次,LED就要翻转一次
always@(posedge sys_clk or posedge sys_rst_n)
    if(!sys_rst_n)
        Led <= 0;
    else if(check_ok)
        Led <= ~Led;
    else 
        Led <= Led;
        
endmodule

5.2.2 串口接收文件

(个人觉得正点原子的串口接收模块比小梅哥的简单易懂,实用)


module zdyz_rs232_rx(
    input sys_clk , //系统时钟
    input sys_rst_n , //系统复位,低有效
    
    input uart_rxd , //UART 接收端口
    output reg uart_rx_done, //UART 接收完成信号
    output reg [7:0] uart_rx_data //UART 接收到的数据
);

parameter CLK_FREQ = 5000_0000; //系统时钟频率
parameter UART_BPS = 115200 ; //串口波特率
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数 BPS_CNT 次

reg uart_rxd_d0;
reg uart_rxd_d1;
reg rx_flag ; //接收过程标志信号
reg [3:0] rx_cnt ; //接收数据位计数器    
reg [15:0] baud_cnt ; //波特率计数器(位宽为16,防止溢出)
reg [7:0 ] rx_data_t ; //接收数据寄存器

wire start_flag;//开始接收的标志,下降沿到来。
    
//打两拍:波特率时钟和系统时钟不同步,为异步信号,所以要进行打拍处理,防止产生亚稳态  
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        uart_rxd_d0 <= 1'b0;
        uart_rxd_d1 <= 1'b0;
    end
    else begin
        uart_rxd_d0 <= uart_rxd;
        uart_rxd_d1 <= uart_rxd_d0;
    end
end  
    
assign start_flag = (uart_rxd_d0 == 0)&&(uart_rxd_d1 == 1);//下降沿到来的表示方法
// rx_flag接收信号的拉高与拉低   
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        rx_flag <= 1'b0;
    else if(start_flag) //检测到起始位
        rx_flag <= 1'b1; //接收过程中,标志信号 rx_flag 拉高
    //在停止位一半的时候,即接收过程结束,标志信号 rx_flag 拉低
    else if((rx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1))//rx_flag 要提前拉低,防止其影响下一帧数据的接收
        rx_flag <= 1'b0;
    else
        rx_flag <= rx_flag;
    end 
//波特率的计数器计数逻辑 
always @(posedge sys_clk or negedge sys_rst_n)begin 
    if(!sys_rst_n) 
        baud_cnt <= 0;      
    else if(rx_flag)begin
        if(baud_cnt == BAUD_CNT_MAX - 1)
            baud_cnt <= 0;
        else
            baud_cnt <= baud_cnt + 1;       
    end
    else
        baud_cnt <= 0;
end 
//位计数实现逻辑
always @(posedge sys_clk or negedge sys_rst_n)begin 
    if(!sys_rst_n)
        rx_cnt <= 0;   
    else if(rx_flag)begin
        if(baud_cnt == BAUD_CNT_MAX - 1)
            rx_cnt <= rx_cnt + 1;        
        else
            rx_cnt <= rx_cnt;
    end
    else
        rx_cnt <= 0;//其他情况下都为0,所以不用担心计数超过9,且其计数也不会超过9,当rx_flag为0时就不计数了
end
//根据 rx_cnt 来寄存 rxd 端口的数据
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        rx_data_t <= 0;    
    else if(rx_flag)begin   //系统处于接收过程时
        if(baud_cnt == BAUD_CNT_MAX/2 - 1)begin//判断 baud_cnt 是否计数到数据位的中间 
            case(rx_cnt)
                1:rx_data_t[0] <= uart_rxd_d1; //寄存数据的最低位
                2:rx_data_t[1] <= uart_rxd_d1;
                3:rx_data_t[2] <= uart_rxd_d1;
                4:rx_data_t[3] <= uart_rxd_d1;
                5:rx_data_t[4] <= uart_rxd_d1;
                6:rx_data_t[5] <= uart_rxd_d1;
                7:rx_data_t[6] <= uart_rxd_d1;
                8:rx_data_t[7] <= uart_rxd_d1;//寄存数据的高低位
                default:rx_data_t <= rx_data_t; 
            endcase
        end
        else 
            rx_data_t <= rx_data_t;
    end
    else
        rx_data_t <= 0; 
end
//给接收完成信号和接收到的数据赋值    
always @(posedge sys_clk or negedge sys_rst_n)begin 
    if(!sys_rst_n)begin    
        uart_rx_done <= 0;   
        uart_rx_data <= 0;
    end
    //当接收数据计数器计数到停止位,且 baud_cnt 计数到停止位的中间时
    else if((rx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1))begin
        uart_rx_done <= 1; //拉高接收完成信号
        uart_rx_data <= rx_data_t;//并对 UART 接收到的数据进行赋值
    end    
    else begin
        uart_rx_done <= 0; 
        uart_rx_data <= uart_rx_data;     
    end
end    
endmodule

5.2.3 字符检测模块

module hello(
    input sys_clk,
    input sys_rst_n,
    input [7:0]data_in,//输入数据
    input data_in_valid,//有效数据输入
    output reg check_ok//检测出来一个hello就出现一个高脉冲
);
    
    //定义5个状态
    localparam CHECK_h = 0,
               CHECK_e = 1,
               CHECK_l1 = 2,
               CHECK_l2 = 3, 
               CHECK_o = 4;
    reg [2:0]state;
    
//我使用的是新二段式状态机,小梅哥用的是一段式状态机。
 always@(posedge sys_clk or posedge sys_rst_n)   
    if(!sys_rst_n)   
        state <= CHECK_h;    
    else begin
        case(state)
            CHECK_h:begin
                if(data_in_valid && data_in == "h")
                    state <= CHECK_e;
                else 
                    state <= CHECK_h;                 
            end    
            CHECK_e:begin
                if(data_in_valid && data_in == "e")//千万注意if还有else if的顺序问题,不然可能会造成检测不成功的现象出现。比如出现hhello就无法检测出来。
                    state <= CHECK_l1;
                else if(data_in_valid && data_in == "h")
                    state <= CHECK_e; 
                else if(data_in_valid)
                    state <= CHECK_h;   
                else
                    state <= CHECK_e;      
            end
            CHECK_l1:
                if(data_in_valid && data_in == "l")
                    state <= CHECK_l2;
                else if(data_in_valid && data_in == "h")
                    state <= CHECK_e; 
                else if(data_in_valid)
                    state <= CHECK_h; 
                else
                    state <= CHECK_l1;   
            CHECK_l2:
                if(data_in_valid && data_in == "l")
                    state <= CHECK_o;
                else if(data_in_valid && data_in == "h")
                    state <= CHECK_e; 
                else if(data_in_valid)
                    state <= CHECK_h; 
                else
                    state <= CHECK_l2;  
            CHECK_o:
                if(data_in_valid && data_in == "o")
                    state <= CHECK_h;//检测完毕回到起始状态
                else if(data_in_valid && data_in == "h")
                    state <= CHECK_e; 
                else if(data_in_valid)
                    state <= CHECK_h;              
                else
                    state <= CHECK_o;  
        endcase
    end   
  //输出单独控制逻辑  
always@(posedge sys_clk or posedge sys_rst_n)   
    if(!sys_rst_n)   
        check_ok <= 0; 
    else if((state == CHECK_o) && data_in_valid && (data_in == "o"))
        check_ok <= 1;   
    else if(state == CHECK_h)
        check_ok <= 0;  
    else
        check_ok <= check_ok;     
endmodule

5.3 引脚约束文件

set_property PACKAGE_PIN U18 [get_ports sys_clk]
set_property IOSTANDARD LVCMOS33 [get_ports sys_clk]
set_property IOSTANDARD LVCMOS33 [get_ports Led]
set_property IOSTANDARD LVCMOS33 [get_ports sys_rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports uart_rxd]
set_property PACKAGE_PIN F20 [get_ports sys_rst_n]
set_property PACKAGE_PIN K16 [get_ports uart_rxd]
set_property PACKAGE_PIN G17 [get_ports Led]

5.4 板上验证

本文所有程序均经过板上验证过,均正常,放心使用参考。

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

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

相关文章

时序预测 | Matlab实现SSA-CNN-GRU麻雀算法优化卷积门控循环单元时间序列预测

时序预测 | Matlab实现SSA-CNN-GRU麻雀算法优化卷积门控循环单元时间序列预测 目录 时序预测 | Matlab实现SSA-CNN-GRU麻雀算法优化卷积门控循环单元时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab实现SSA-CNN-GRU麻雀算法优化卷积门控循环单元时间序…

分布式事务之最终一致性

分布式事务之最终一致性 参考链接分布式事务基础理论概述案例解决方案:RocketMQ可靠消息注意事项&#xff1a;代码实现 参考链接 原文链接&#xff1a;https://blog.csdn.net/jikeyeka/article/details/126296938 分布式事务基础理论 基于上述的CAP和BASE理论,一般情况下会保…

Grafana Loki 组件介绍

Loki 日志系统由以下3个部分组成&#xff1a; Loki是主服务器&#xff0c;负责存储日志和处理查询。Promtail是专为loki定制的代理&#xff0c;负责收集日志并将其发送给 loki 。Grafana用于 UI展示。 Distributor Distributor 是客户端连接的组件&#xff0c;用于收集日志…

小米SU7汽车发布会; 齐碳科技C+轮融资;网易 1 月 3 日发布子曰教育大模型;百度文心一言用户数已突破 1 亿

投融资 • 3200 家 VC 投资的创业公司破产&#xff0c;那个投 PLG 的 VC 宣布暂停投资了• 云天励飞参与 AI 技术与解决方案提供商智慧互通 Pre-IPO 轮融资• 百度投资 AIGC 公司必优科技• MicroLED量测公司点莘技术获数千万级融资• 智慧互通获AI上市公司云天励飞Pre-IPO轮战…

10. Opencv检测并截取图中二维码

1. 说明 在二维码扫描功能开发中,使用相机扫描图片时,往往图片中的信息比较多样,可能会造成二维码检测失败的问题。一种提高检测精度的方式就是把二维码在图片中单独抠出来,去除其它冗余信息,然后再去识别这张提取出来的二维码。本篇博客记录采用的一种实现二维码位置检测…

OSPF被动接口配置-新版(14)

目录 整体拓扑 操作步骤 1.基本配置 1.1 配置R1的IP 1.2 配置R2的IP 1.4 配置R4的IP 1.5 配置R5的IP 1.6 配置PC-1的IP地址 1.7 配置PC-2的IP地址 1.8 配置PC-3的IP地址 1.9 配置PC-4的IP地址 1.10 检测R1与PC3连通性 1.11 检测R2与PC4连通性 1.12 检测R4与PC1连…

docker小白第九天

docker小白第九天 安装redis集群 cluster(集群)模式-docker版本&#xff0c;哈希槽分区进行亿级数据存储。如果1~2亿条数据需要缓存&#xff0c;请问如何设计这个存储案例。单机存储是不可能的&#xff0c;需要分布式存储&#xff0c;如果使用redis又该如何部署。 哈希取余分…

第十二章 Sleuth分布式请求链路跟踪

Sleuth分布式请求链路跟踪 gitee:springcloud_study: springcloud&#xff1a;服务集群、注册中心、配置中心&#xff08;热更新&#xff09;、服务网关&#xff08;校验、路由、负载均衡&#xff09;、分布式缓存、分布式搜索、消息队列&#xff08;异步通信&#xff09;、数…

nginx报错upstream sent invalid header

nginx报错upstream sent invalid header 1.报错背景 最近由于nginx 1.20的某个漏洞需要升级到nginx1.25的版本。在测试环境升级完nginx后&#xff0c;发现应用直接报错502 bad gateway了。 然后查看nginx的errlog&#xff0c;发现&#xff1a; upstream sent invalid head…

语言模型:从n-gram到神经网络的演进

目录 1 前言2 语言模型的两个任务2.1 自然语言理解2.2 自然语言生成 3 n-gram模型4 神经网络语言模型5 结语 1 前言 语言模型是自然语言处理领域中的关键技术之一&#xff0c;它致力于理解和生成人类语言。从最初的n-gram模型到如今基于神经网络的深度学习模型&#xff0c;语言…

LMX2571 芯片配置Verliog SPI驱动

前言 本实验使用ZYNQ的PL(FPGA)对LMX2571芯片进行配置&#xff0c;以下连接为相关的原理和软件使用资料。 TICS Pro 配置时钟芯片 文献阅读–Σ-Δ 小数频率合成器原理 LMX2571芯片数据手册 一、LMX2571配置时序分析 1.1 写时序 LMX2571使用24位寄存器进行编程。一个24位移位…

Codeforces Round 918 (Div. 4)(AK)

A、模拟 B、模拟 C、模拟 D、模拟 E、思维&#xff0c;前缀和 F、思维、逆序对 G、最短路 A - Odd One Out 题意&#xff1a;给定三个数字&#xff0c;有两个相同&#xff0c;输出那个不同的数字。 直接傻瓜写法 void solve() {int a , b , c;cin >> a >>…

机器学习 -- 数据预处理

系列文章目录 未完待续…… 目录 系列文章目录 前言 一、数值分析简介 二、内容 前言 tips&#xff1a;这里只是总结&#xff0c;不是教程哈。 以下内容仅为暂定&#xff0c;因为我还没找到一个好的&#xff0c;让小白&#xff08;我自己&#xff09;也能容易理解&#x…

Java线上问题排查思路

1、Java 服务常见问题 Java 服务的线上问题从系统表象来看大致可分成两大类: 系统环境异常、业务服务异常。 系统环境异常&#xff1a;主要从CPU、内存、磁盘、网络四个方面考虑。比如&#xff1a;CPU 占用率过高、CPU 上下文切换频率次数较高、系统可用内存长期处于较低值、…

工业产线看板的智能化应用

在数字化浪潮兴起之前&#xff0c;许多制造企业主要依赖手工生产和传统的生产管理方法&#xff0c;生产数据的收集和分析主要依赖于人工&#xff0c;导致信息传递滞后、生产过程不透明&#xff0c;难以及时调整生产计划。在传统的生产环境中&#xff0c;生产过程的各个环节缺乏…

留言板(Mybatis连接数据库版)

目录 1.添加Mybatis和SQL的依赖 2.建立数据库和需要的表 3.对应表中的字段&#xff0c;补充Java对象 4.对代码进行逻辑分层 5.后端逻辑代码 之前的项目实例【基于Spring MVC的前后端交互案例及应用分层的实现】https://blog.csdn.net/weixin_67793092/article/details/134…

K8S结合Prometheus构建监控系统

一、Prometheus简介 Prometheus 是一个开源的系统监控和警报工具&#xff0c;用于收集、存储和查询时间序列数据。它专注于监控应用程序和基础设施的性能和状态&#xff0c;并提供丰富的查询语言和灵活的告警机制1、Prometheus基本介绍 数据模型&#xff1a;Prometheus 使用时…

Spring Boot笔记1

1. SpringBoot简介 1.1. 原有Spring优缺点分析 1.1.1. Spring的优点分析 Spring是Java企业版&#xff08;Java Enterprise Edition&#xff0c;javeEE&#xff09;的轻量级代替品。无需开发重量级的Enterprise JavaBean&#xff08;EJB&#xff09;&#xff0c;Spring为企业…

20231227在Firefly的AIO-3399J开发板的Android11的挖掘机的DTS配置单后摄像头ov13850

20231227在Firefly的AIO-3399J开发板的Android11的挖掘机的DTS配置单后摄像头ov13850 2023/12/27 18:40 1、简略步骤&#xff1a; rootrootrootroot-X99-Turbo:~/3TB$ cat Android11.0.tar.bz2.a* > Android11.0.tar.bz2 rootrootrootroot-X99-Turbo:~/3TB$ tar jxvf Androi…

阿里云30个公共云地域、89个可用区、5个金融云和政务云地域

阿里云基础设施目前已面向全球四大洲&#xff0c;公共云地域开服运营30个公共云地域、89个可用区&#xff0c;此外还拥有5个金融云、政务云地域&#xff0c;并且致力于持续的新地域规划和建设&#xff0c;从而更好的满足用户多样化的业务和场景需求。伴随着基础设施的加速投入和…