Verilog基础语法——状态机(类型、写法、状态编码方式)

写在前面

  在FPGA设计过程,经常会设计状态机用于控制整个硬件电路的工作进程,也称为有限状态机(Finite State Machine,FSM)。本文将对状态机的类型、写法以及状态编码方式进行介绍。

一、状态机类型

  状态机根据类型可以分为Moore型状态机和Mealy型状态机,其中Moore型状态机的输出只与当前状态有关,如下图所示。其中包含三个部分:(1)用于产生次态的组合逻辑;(2)用于寄存器现态的状态寄存器;(3)用于产生输出的组合逻辑;

在这里插入图片描述

  而Mealy型状态机不仅与当前状态有关,且与当前时刻的输入有关,如下图所示。Mealy型状态机产生次态的组合逻辑和状态寄存器与Moore型状态机是一致的,而不同的是,Mealy型状态机的输出不仅与现态有关,而且与当前时刻的输入也相关

在这里插入图片描述
  进一步地,可以通过状态转换图来详细分析两者之间的不同之处。以”101“的非重叠序列检测为例,Moore型状态机的状态转换图如下图所示。

Tip:非重叠序列检测是指检测到的多组“101”序列之间是非重叠、独立的,比如序列110101进行“101”的非重叠序列检测,输出检测结果为000100。而重叠序列检测值检测到的多组“101”序列之间可以重叠,即第一组“101”和第二组“101”之间可以有重叠部分,第一组“101”序列的最后一个1是第二组“101”序列的第一个1,比如序列110101进行“101”的重叠序列检测,输出检测结果为000101。

在这里插入图片描述

  而Mealy型状态机的状态转换图如下图所示。

在这里插入图片描述
  在这里可以看出,同样情况下Moore型状态机要比Mealy型状态机多一个状态,也就是说在实际设计中Moore型状态机会比Mealy型状态机多一个时钟周期。总结来说,Moore型状态机的相比于Mealy型状态机会慢一拍,但是时序会更好,而Mealy状态机相比于Moore状态机快一拍,速度会更快,但时序会差一些。在设计中一般考虑使用Moore型状态机。

二、状态机写法

  对于状态机的写法,可以分为一段式、两段式和三段式,不同的写法将在下面三个小节介绍。

2.1 一段式

  一段式状态机中只有一个always块,在该always块中同时描述状态寄存、状态转移条件的判断与数据输出。对于大规模电路设计,状态机控制的数据输出有多个,该写法会导致状态机代码复杂且杂乱,后续开发过程难以修改和维护。以”101“序列的Moore型状态机为例,其一段式状态机写法如下:

module check1(
    input    wire    clk       ,
    input    wire    rst_n     ,
    input    wire    din       ,
    output   reg     check_pass
);

localparam S0 = 4'b0001; // 初始化状态
localparam S1 = 4'b0010; // 计数状态
localparam S2 = 4'b0100; // 计算状态
localparam S3 = 4'b1000; // 结束状态

reg             din_r;
reg    [3:0]    state;

// 输入数据打拍
always @(posedge clk) din_r <= din;
 
//-------- 一段式状态机 --------//
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        state <= S0;
        check_pass <= 1'b0;
    end
    else begin
        case(state)
            S0 :
                begin
                    if(din_r == 1'b1) begin
                        state <= S1;
                        check_pass <= 1'b0;
                    end
                    else begin
                        state <= S0;
                        check_pass <= 1'b0;
                    end
                end

            S1 : 
                begin
                    if(din_r == 1'b0) begin
                        state <= S2;
                        check_pass <= 1'b0;
                    end
                    else begin
                        state <= S1;
                        check_pass <= 1'b0;
                    end
                end
            
            S2 : 
                begin
                    if(din_r == 1'b1) begin
                        state <= S3;
                        check_pass <= 1'b0;
                    end
                    else begin
                        state <= S2;
                        check_pass <= 1'b0;
                    end
                end
            
            S3 : 
                begin
                    if(din_r == 1'b1) begin
                        state <= S1;
                        check_pass <= 1'b1;
                    end
                    else begin
                        state <= S0;
                        check_pass <= 1'b1;
                    end
                end

            default: 
                begin
                    state <= S0;
                    check_pass <= 1'b0;
                end

        endcase
    end 
end

endmodule

2.2 两段式

  一段式状态机写法中代码杂乱,不利于后续修改和维护,两段式状态机则克服了该缺点,将三个部分(状态转移寄存器、状态转移条件判断、输出)分成三个always块进行描述,同时输出使用组合逻辑进行赋值,而组合逻辑赋值会导致毛刺等现象,增加电路的不稳定性。以”101“序列的Moore型状态机为例,其两段式状态机写法如下:

module check2(
    input    wire    clk       ,
    input    wire    rst_n     ,
    input    wire    din       ,
    output   reg     check_pass
);

localparam S0 = 4'b0001; // 初始化状态
localparam S1 = 4'b0010; // 计数状态
localparam S2 = 4'b0100; // 计算状态
localparam S3 = 4'b1000; // 结束状态

reg             din_r;
reg    [3:0]    curr_state;
reg    [3:0]    next_state;

// 输入数据打拍
always @(posedge clk) din_r <= din;

//-------- 两段式状态机 --------//
// 第一段(时序逻辑,用于描述状态寄存器)
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        curr_state <= S0; //初始化状态
    else
        curr_state <= next_state;
end

// 第二段(组合逻辑,用于状态转移条件判断与输出)
always @(*) begin
    next_state = S0;
    case(curr_state)
        S0 :
            begin
                if(din_r == 1'b1) begin
                    next_state = S1;
                    check_pass = 1'b0;
                end
                else begin
                    next_state = S0;
                    check_pass = 1'b0;
                end
            end
            
        S1 :
            begin
                if(din_r == 1'b0) begin
                    next_state = S2;
                    check_pass = 1'b0;
                end
                else begin
                    next_state = S1;
                    check_pass = 1'b0;
                end
            end

        S2 :
            begin
                if(din_r == 1'b1) begin
                    next_state = S3;
                    check_pass = 1'b0;
                end
                else begin
                    next_state = S2;
                    check_pass = 1'b0;
                end
            end

        S3 :
            begin
                check_pass = 1'b1;
                if(din_r == 1'b1) begin
                    next_state = S1;
                end
                else begin
                    next_state = S0;
                end
            end

        default:
            begin
                next_state = S0;
                check_pass = 1'b0;
            end
    endcase
end

endmodule

  有一点值得注意的是,在两段式状态机中数据输出check_pass与次态next_state采用组合逻辑进行控制,一定要补全if-else中else情况下的数值,否则在电路综合时会产生Latch。

2.3 三段式

  三段式状态机在两段式状态机的基础上,将控制输出数据赋值的组合逻辑改成时序逻辑,消除了组合逻辑输出中存在毛刺

module check3(
    input    wire    clk       ,
    input    wire    rst_n     ,
    input    wire    din       ,
    output   reg     check_pass
);

localparam S0 = 4'b0001; // 初始化状态
localparam S1 = 4'b0010; // 计数状态
localparam S2 = 4'b0100; // 计算状态
localparam S3 = 4'b1000; // 结束状态

reg             din_r;
reg    [3:0]    curr_state;
reg    [3:0]    next_state;

// 输入数据打拍
always @(posedge clk) din_r <= din;

//-------- 三段式状态机 --------//
// 第一段(时序逻辑,用于描述状态寄存器)
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        curr_state <= S0; //初始化状态
    else
        curr_state <= next_state;
end

// 第二段(组合逻辑,用于状态转移条件判断)
always @(*) begin
    next_state = S0;
    case(curr_state)
        S0 :
            begin
                if(din_r == 1'b1)
                    next_state = S1;
                else
                    next_state = S0;
            end
            
        S1 :
            begin
                if(din_r == 1'b0)
                    next_state = S2;
                else
                    next_state = S1;
            end

        S2 :
            begin
                if(din_r == 1'b1)
                    next_state = S3;
                else
                    next_state = S2;
            end

        S3 :
            begin
                if(din_r == 1'b1)
                    next_state = S1;
                else
                    next_state = S0;
            end

        default:next_state = S0;
    endcase
end

// 第三段(时序逻辑,用于描述输出)
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        check_pass <= 1'b0;
    else
        case(curr_state)
            S0 : check_pass <= 1'b0;
            S1 : check_pass <= 1'b0;
            S2 : check_pass <= 1'b0;
            S3 : check_pass <= 1'b1;
            default: check_pass <= 1'b0;
        endcase
end

endmodule

Tip:这里需要注意的是,两段式状态机于三段式状态机的区别之处并不是状态机有几个always块,而是输出由组合逻辑还是时序逻辑控制。两段式状态机的输出由组合逻辑控制,容易产生毛刺,三段式状态机的输出由时序逻辑控制,避免了两段式状态机输出存在毛刺的问题。

  总结来说,一段式、两段式、三段式状态机的特点与优缺点如下表所示:

一段式写法两段式写法三段式写法
特点一个always块(时序逻辑),同时描述状态寄存器、状态转移条件判断与数据输出两个always块,第一个always块(时序逻辑)描述状态寄存器,第二个always块(组合逻辑)描述状态转移条件判断与数据输出三个always块,第一个always块(时序逻辑)描述状态寄存器,第二个always块(组合逻辑)描述状态转移条件判断,第三个always块(时序逻辑)描述数据输出
优点结构简单,易于实现代码清晰,易于修改和调试便于修改和维护,输出输出不存在毛刺
缺点不利于修改和维护数据输出存在毛刺

问题1:一段式、两段式和三段式状态机写法不同,在RTL电路与时序上具体差异是什么样的?

(1)RTL电路
  下图分别为一段式、两段式和三段式状态机的RTL电路,由于一段式状态机中输出check_pass均由时序逻辑控制,区别在于将写在一个always块中的三个部分(状态转移寄存器、状态转移条件判断、输出)分成三个always块进行描述,所以两者的RTL电路一致。而两段式状态机中输出check_pass采用组合逻辑进行控制,所以与两段式、三段式状态机的RTL电路是不一致的。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
(2)时序波形

  为了对比三种不同写法状态机的时序差异,编写测试文件TestBench如下:

`timescale 1ns/1ns
module tb_check();
reg clk,rst_n,din;
wire check_pass1,check_pass2,check_pass3;

initial begin
    clk = 1'b1;
    rst_n = 1'b0;
    din = 1'b0;

    #100
    rst_n = 1'b1;

    #20
    din = 1'b1;
    #20
    din = 1'b1;
    #20
    din = 1'b0;
    #20
    din = 1'b1;
    #20
    din = 1'b1;
    #20
    din = 1'b0;
    #20
    din = 1'b1;
end

always #10 clk = ~clk;

check1 check1_inst(
    .clk       (clk        ),
    .rst_n     (rst_n      ),
    .din       (din        ),
    .check_pass(check_pass1)
);

check2 check2_inst(
    .clk       (clk        ),
    .rst_n     (rst_n      ),
    .din       (din        ),
    .check_pass(check_pass2)
);

check3 check3_inst(
    .clk       (clk        ),
    .rst_n     (rst_n      ),
    .din       (din        ),
    .check_pass(check_pass3)
);

endmodule

  仿真波形如下图所示,有图可知,两段式状态机输出要比一段式状态机和三段式状态机快一拍

在这里插入图片描述

三、状态机状态编码方式

  常用的状态机状态编码方式有二进制码(Binary)、格雷码(Gray)、独热码(One-Hot)等,不同的编码方式适用于不同的应用场景。以8个状态的状态机为例,介绍不同编码方式的优缺点,如下表所示。

二进制码(Binary)格雷码(Gray)独热码(One-Hot)
8个状态3’b000
3’b001
3’b010
3’b011
3’b100
3’b101
3’b110
3’b111
3’b000
3’b001
3’b011
3’b010
3’b110
3’b111
3’b101
3’b100
8’b00000001
8’b00000010
8’b00000100
8’b00001000
8’b00010000
8’b00100000
8’b01000000
8’b10000000
优点使用寄存器较少,编码方式简单使用寄存器较少,且出现毛刺的可能性低编码方式简单,容易增加状态
缺点每次状态变化涉及多个比特位反转,容易出现毛刺,且消耗较多LUT编码方式复杂,状态跳转需要较多组合逻辑使用寄存器多
适用场景小型设计(状态数小于4)大型状态机(状态个数大于24)状态数在4~24之间,适用于判断条件复杂但是状态少的情况下

  对于上述表述,在学习的过程中仍然存在下面几个问题:

问题1:如果状态机的状态个数不为 2 n 2^n 2n,或者状态机的状态跳转不仅仅是按照相邻的状态跳转(如下图红色箭头),那么格雷码相对于二进制码是否存在优势?

在这里插入图片描述

回答这个问题没有比较明确的答案,后续更新… …

问题2:独热码的状态不是用了更多的位宽,又是如何做到节省资源的?

回答:比如一个8个状态的状态机,独热码编码方式需要8位宽,在使用到状态时,无需将8位数值进行比较,只需比较单个位宽即可,如下:

assign dout = curr_state[0] ? 1'b1 : 1'b0;
assign check_pass = curr_state[3] ? 1'b1 : 1'b0;

  而如果使用二进制编码或者格雷编码,需要3位宽表示8个状态,在使用到状态时,需要将3位数值全部进行比较,如下:

assign dout = (curr_state == S0) ? 1'b1 : 1'b0;
assign check_pass = (curr_state == S3) ? 1'b1 : 1'b0;

写在后面

  在本文中我们学习了FPGA设计中状态机的一些基础知识,包括状态机类型、状态机的写法与状态机的编码方式。其中,状态机的类型包括Moore型和Mealy型两种,Moore型状态机输出仅与当前状态有关,与当前输入无关,Mealy型状态机输出不仅与当前状态有关,而且与当前输入有关。状态机的写法包括一段式、两段式和三段式写法,三种状态机中,一段式状态机不符合时序逻辑和组合逻辑分开描述的代码风格,代码冗长,不利于修改和维护;两段式状态机中,输出一般使用组合逻辑描述,而组合逻辑容易产生毛刺;三段式状态机既解决了一段式状态机中代码冗长与代码风格混乱、不利于修改维护的问题,也解决了两段式状态机中组合逻辑输出产生毛刺的问题,是目前设计中常用的状态机设计方法。状态机常用的编码方式包括二进制码格雷码独热码三种,三种不同编码方式适用于不同的场景下,在实际设计过程中应根据状态的个数与设计的复杂度等多方面综合考虑。


🧐:以上为个人学习笔记,如有疑问,欢迎评论区交流探讨 !!!

  • 26
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: FPGA是一种可编程逻辑器件,可以通过编程实现各种功能。而Verilog是一种硬件描述语言,用于描述FPGA中的电路。Verilog基础语法包括模块定义、端口定义、信号定义、赋值语句、条件语句、循环语句等。模块定义是Verilog中最基本的语法,用于定义一个电路模块。端口定义用于定义模块的输入输出端口。信号定义用于定义模块内部的信号。赋值语句用于给信号赋值。条件语句用于根据条件执行不同的代码。循环语句用于重复执行一段代码。掌握Verilog基础语法是学习FPGA编程的基础。 ### 回答2: FPGA是一种可编程逻辑器件,它可以通过硬件描述语言来编写自定义的逻辑电路来实现各种功能。其中,Verilog是一种常用的硬件描述语言,有助于实现FPGA的功能。 Verilog语言主要由模块、端口、信号、语句和运算符五个基本部分组成。模块是Verilog中的最基本的语法单元,类似于传统程序语言中的函数,模块之间可以通过实例化进行连接。端口则是与外部世界进行通信的接口,可以分为输入端口(input)和输出端口(output)两种类型。信号是Verilog中表示数字信号的基本单元,可以是位向量、整数或实数。语句则是Verilog中描述操作和行为的语言,常用的语句包括赋值语句、分支语句和循环语句。运算符则是Verilog中用于进行操作的符号,包括算数运算符、逻辑运算符和位运算符。 在Verilog语言中,还有一些常用的结构体和命令可以帮助我们更方便地实现FPGA的功能。其中,常用的结构体包括always语句、case语句和module归档,常用的命令包括initial语句、wire语句和reg语句。always语句可以在指定的触发条件下执行某一段代码,case语句可以根据不同的条件执行不同的代码段,module归档则可以将多个模块合并为一个模块。initial语句可用于在仿真开始前初始化某些信号,wire语句则用于声明并连接信号,reg语句则用于声明并存储信号。 总体而言,了解FPGA和Verilog基础语法可以帮助我们更好地理解FPGA技术的应用和实现。然而,理论知识只有结合实际操作和实验才能更好地掌握。所以,我们还需要结合实际项目来进行练习和实践,从而更好地掌握FPGA和Verilog基础语法。 ### 回答3: FPGA(可编程门阵列)是一种可编程逻辑器件,可以用来创建定制的数字电路。Verilog是一种硬件描述语言,用于描述数字电路的结构和行为。 Verilog基础语法有以下几个部分: 1.模块定义:Verilog代码以模块的形式进行组织,每个模块都有一个名称和端口列表。模块定义以module关键字开始,以endmodule关键字结束。 2.端口声明:模块的端口是输入和输出连接到其他模块或FPGA芯片的引脚。端口可以是输入(input)、输出(output)或双向(inout)。端口声明在模块定义中。 3.信号声明:信号是描述数字电路中状态的变量。可以是单个位(wire)或多位(reg),在模块中声明。 4.赋值语句:用来为信号赋值,包括非阻塞赋值(<=)、阻塞赋值(=)和连续赋值(assign)。 5.条件分支语句:if, else if和else语句是用来控制程序流程,实现条件判断。 6.循环语句:Verilog支持for、while、do while和forever等类型的循环语句,可以在程序中实现迭代操作。 7.模块实例化:用来将其他模块作为子模块嵌入到当前模块中,从而实现复杂的数字电路。 除上述基本语法外,Verilog还有其他常用语法,如always块、initial块和function定义等。需要深入了解和使用Verilog,可以参考相关资料和教程。掌握了Verilog语法,可以使用FPGA搭建各种个性化的数字电路,用于嵌入式系统、数字信号处理、计算网络等各种应用领域。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值