Verilog使用有限状态机实现对特定序列的识别

目的

使用mealy状态机完成对一输入序列的检测,当输入序列中有1011的特征时,输出一个时钟周期的高电平。举例如下:
如果输入的序列为:

0001_0110_1011_0111_0010_1010_1101_0000_1011_1101_1000_0010_1101_1011_0011...

则输出的序列为:

0000_0010_0001_0010_0000_0000_0100_0000_0001_0000_1000_0000_0100_1001_0000...

实现

1 detect_serial_fsm.v
        根据设计需求,需要识别序列中出现的1011这一特定序列,使用米利状态机来描述过程。那么可以设定:
        当输入为1,从闲置态到达状态1;若输入为0则停留在闲置态。
        在状态1若输入0,到达状态2;若为1则停留在状态1。
        在状态2若输入为1,进入状态3;否则进入闲置态。
        在状态3若输入为1进入状态4;否则返回状态2.
        在状态4若输入为1,返回状态1,否则回到状态2,只有状态4产生输出1,且两种情况都输出1.
        以上结果是在实验了几次最初画的状态图发现问题之后做出的。主要是考虑了返回时到达的状态。接下来主要说明判断识别返回的分支,由于判断的序列是1011,
        来到状态1时,已经输过了1。此时若为1则出现11的序列,对比1011,只能重新从1开始识别,故回到状态1已经输过1的状态。
        来到状态2时,已经输过10.此时若为0,则出现100序列,对比1011,只能从闲置态重新开始。
        来到状态3时,已经输过101。若此时输入为0,则为1010,对比1011,只能从状态2(已经输过10)重新开始.
        来到状态4时,已经输过1011。若此时输入1,则为10111,返回到状态1;若输入为0,已有10110对比1011回到状态2.
        综上画出状态机如图:
fsm1.2
思路完成,根据思路,设计代码如下(采用三段式写法):

module detect_serial_fsm(
    input               clk_i,
    input               rst_i,
    input               signal_i,
    output  reg         detected_o,
    output  [4:0]       curent_state_o //为观察方便,引出当前状态用以判断

);
parameter length = 5; //更方便地更改状态长度

parameter [length-1 : 0]                         //one-hot code
                S_IDLE   = 5'b00001,
                S_State1 = 5'b00010,
                S_State2 = 5'b00100,
                S_State3 = 5'b01000,
                S_State4 = 5'b10000;

reg [length-1 : 0] c_state;
reg [length-1 : 0] n_state;
//为观察方便,引出当前状态用以判断
assign curent_state_o = c_state;
//三段式状态机
always @(posedge clk_i or negedge rst_i) begin
    if (!rst_i) begin
        c_state <= S_IDLE; // reset低电平复位        
    end
    else begin
        c_state <= n_state; //next state logic
    end
end

always @(*) begin   //state register
    case(c_state)
        S_IDLE   : 
          if (signal_i)
            n_state = S_State1;
          else 
            n_state = S_IDLE;
        S_State1 :
          if (signal_i)
            n_state = S_State1;
          else 
            n_state = S_State2;
        S_State2 :
          if (signal_i)
            n_state = S_State3;
          else  
            n_state = S_IDLE;
        S_State3 :
          if (signal_i)
            n_state = S_State4;
          else 
            n_state = S_State2;
        S_State4 :
          if (signal_i)
            n_state = S_State1;
          else 
            n_state = S_State2; 
        default :
            n_state = S_IDLE;
    endcase 
end

always @ (*) begin   //output logic
    case(c_state)
        S_IDLE   : detected_o = 1'b0;
        S_State1 : detected_o = 1'b0;
        S_State2 : detected_o = 1'b0;
        S_State3 : detected_o = 1'b0;
        S_State4 : detected_o = 1'b1;
        default  : detected_o = 1'b0;
    endcase
end

endmodule

2 tb_detect_serial_fsm.v
        产生激励来验证序列判断功能,为方便对比,准备产生助教给出示例序列的激励,则可以直接用助教示例的输出对比自己的输出来检验成果。为了方便地产生序列,同样使用task来简化操作,同时为了方便观察,希望能使task产生的序列与task输入的参数形式相同(即输入参数为1011产生的序列即为1011),使用位操作的方法,将输入二进制制参数逐位操作。同时序列变化要略提前于时钟上升沿以保证数据处理的正确(前几次因为在时钟上升沿之后产生数据导致结果与预期不符),设计代码如下:

`timescale 1ns / 1ps
module tb_detect_serial_fsm(
    output  reg            clk_o,
    output  reg            rst_o,
    output  reg            signal_o,
    input                  detected_i
);

initial begin
    clk_o = 1;
    rst_o = 0;
    #10
    rst_o = 1;
    #9                 //时钟周期为10,将输入序列推后9
    sequence_o(4'b0001); //1st 标记序列方便检查
    sequence_o(4'b0110); //2nd
    sequence_o(4'b1011); //3rd
    sequence_o(4'b0111); //4th
    sequence_o(4'b0010); //5th
    sequence_o(4'b1010); //6th
    sequence_o(4'b1101); //7th
    sequence_o(4'b0000); //8th
    sequence_o(4'b1011); //9th
    sequence_o(4'b1101); //10th
    sequence_o(4'b1000); //11th
    sequence_o(4'b0010); //12th
    sequence_o(4'b1101); //13th
    sequence_o(4'b1011); //14th
    sequence_o(4'b0011); //15th
end

always begin
    #5 clk_o = ~clk_o;
end

task sequence_o(
    input [3:0] fourbits
);
  begin
    #10 signal_o = fourbits[3];
    #10 signal_o = fourbits[2];
    #10 signal_o = fourbits[1];
    #10 signal_o = fourbits[0];
  end
endtask

endmodule

3 tb_top
tb_top只需要连接好两个模块。由于自己增加了当前状态输出,相应的增加一条线。
代码如下:

module tb_top;
wire  clk, rst, signal, detected;
wire  [4:0]  curent_state;

tb_detect_serial_fsm inst_tb_dtect_serial_fsm(
    .clk_o(clk),
    .rst_o(rst),
    .signal_o(signal),
    .detected_i(detected)
);

detect_serial_fsm inst_detect_serial_fsm(
    .clk_i(clk),
    .rst_i(rst),
    .signal_i(signal),
    .detected_o(detected),
    .curent_state_o(curent_state)
);

endmodule

使用modelsim仿真得到波形图如下:
fsm1.3

输入的序列为:
0001_0110_1011_0111_0010_1010_1101_0000_1011_1101_1000_0010_1101_1011_0011

        对照波形图,逐个查看波形出现的位置,总共应当出现9个高电平,且依次出现在每个1011的最后一位。仔细对比波形,输出正确无误。

        功能实现完整。

猜你喜欢

转载自blog.csdn.net/Stynis/article/details/80556211