目的
使用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.
综上画出状态机如图:
思路完成,根据思路,设计代码如下(采用三段式写法):
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仿真得到波形图如下:
输入的序列为:
0001_0110_1011_0111_0010_1010_1101_0000_1011_1101_1000_0010_1101_1011_0011
对照波形图,逐个查看波形出现的位置,总共应当出现9个高电平,且依次出现在每个1011的最后一位。仔细对比波形,输出正确无误。
功能实现完整。