1、序列检测器?
序列检测器指的就是将一个指定的序列(以‘10010’为例)从数字码流中识别出来,是一个经典的数字电路实例。通常的序列检测方法有2种:有限状态机法(FSM);移位寄存器法。
序列检测还分为重复检测和非重复检测。假设在输入码流“10010010”中进行重复检测,则会在检测到第一个“10010”和第二个“10010”后分别拉高输出表示成功检测;若非重复检测则只在检测到第一个“10010”拉高输出。
接下来就几种情况分别进行说明并提供代码、仿真及仿真结果说明。
2、有限状态机法
FSM(有限状态机)又可分为两种,根据状态机的输出是否与输入条件相关,可将状态机分为两大类,即摩尔(Moore)型状态机和米勒(Mealy) 型状态机。可参考
FPGA状态机(一段式、二段式、三段式)、摩尔型(Moore)和米勒型(Mealy)
-
Moore 状态机:组合逻辑的输出只取决于当前状态,而与输入状态无关。
-
Mealy 状态机:输出不仅取决于当前状态,还取决于输入状态。
2.1、
Moore 状态机
先将状态转移图贴出:
- IDLE:初始状态,对输入的码流进行检测,若为1则跳转到状态A,则为0保留在该状态
- A: 对输入的码流进行检测,若为0则跳转到状态B(10),则为1保留在该状态
- B: 对输入的码流进行检测,若为0则跳转到状态C(100),则为1则跳转到状态A(101)
- C: 对输入的码流进行检测,若为1则跳转到状态D(1001),则为0则跳转到状态IDLE(1000)
- D: 对输入的码流进行检测,若为0则跳转到状态E(10010),则为1则跳转到状态A(10011)
-
E: 此时已经成功检测到了序列”10010“,可以拉高输出。
- 重复检测:然后对输入的码流进行检测,若为0则跳转到状态C(100_100,后面的100可视为新的一轮检测),则为1则跳转到状态A(10010_1)
- 非重复检测:然后对输入的码流进行检测,若为0则跳转到状态IDLE(10010_0,后面的100不可视为新的一轮检测),则为1则跳转到状态A(10010_1)
2.1.1、Verilog代码
根据状态转移图可编写Verilog代码如下:需要注意的是,将是否进行重复检测设为了参数REPEAT,1–重复检测;0–非重复检测,提供程序复用性。
//检测序列“10010”,使用三段式Moore型状态机
module seqdet_moore
#(
parameter REPEAT = 1'b1 //1--重复检测;0--非重复检测
)
(
input x , //输入
input clk , //时钟
input rst_n , //复位信号,低电平有效
output reg z //输出采用时序逻辑,避免毛刺
);
//reg define
reg [2:0] cur_state , //现态
next_state ; //状态
//parameter define
localparam IDLE = 3'd0,
A = 3'd1,
B = 3'd2,
C = 3'd3,
D = 3'd4,
E = 3'd5;
//三段式状态机第1段
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cur_state <= IDLE;
else
cur_state <= next_state;
end
//三段式状态机第2段
always@(*)begin
next_state = IDLE;
case(cur_state)
IDLE: next_state = x ? A : IDLE;
A : next_state = x ? A : B;
B : next_state = x ? A : C;
C : next_state = x ? D : IDLE;
D : next_state = x ? A : E;
E : next_state = REPEAT ? (x ? A : C) : (x ? A : IDLE);
default:next_state = IDLE;
endcase
end
//三段式状态机第3段
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
z <= 1'b0;
else
case(cur_state)
E : z <= 1'b1;
default : z <= 1'b0;
endcase
end
endmodule
2.1.2、testbench
在测试代码我们分别例化两个检测模块,一个用来检测重复输出,另一个用来检测非重复输出。
//检测序列“10010”,使用三段式Moore型状态机
`timescale 1ns/1ns
module tb_seqdet_moore;
reg x ;
reg clk ;
reg rst_n ;
wire z1 ; //重复检测输出结果
wire z0 ; //非重复检测输出结果
parameter REPEAT1 = 1'b1; //重复检测
parameter REPEAT0 = 1'b0; //非重复检
initial begin
clk = 1'b1;
rst_n <= 1'b0;
x <= 1'b0;
#30 rst_n<=1; //拉高复位信号
forever #20 x <= ({$random} % 2); //生成1bit的随机数
end
always #10 clk <= ~clk; //时钟信号50M
//例化序列检测器模块--重复检测
seqdet_moore
#(
.REPEAT (REPEAT1 )
)
seqdet_moore_isnt1(
.x (x ),
.clk (clk ),
.rst_n (rst_n ),
.z (z1 )
);
//例化序列检测器模块--非重复检测
seqdet_moore
#(
.REPEAT (REPEAT0 )
)
seqdet_moore_isnt0(
.x (x ),
.clk (clk ),
.rst_n (rst_n ),
.z (z0 )
);
endmodule
2.1.3、仿真结果
仿真结果如下:
可以看到重复检测的结果比非重复检测的结果还是多一些的,接下来选取几个典型的局部:
上图中,成功检测到序列”10010“,因为后续输入不满足条件,所以不存在重复检测。检测到序列 ”10010“后系统进入状态E,在状态E拉高输出表示一次成功的检测,由于输出采用时序逻辑,输出会延迟一个时钟周期,所以总体看来,Moore状态机的输出要落后序列 ”10010“1个时钟周期。
重复检测的结果如下:
上图中,输入码流为”10010_010“,在重复检测模式中,后面的10010又组成了一次成功的序列输入,所以一共输出两次成功检测标志;而非重复检测标志则只输出一次成功检测标志。
2.2、
Mealy 状态机
Mealy 状态机的输出不仅取决于当前状态,还取决于输入状态。所以在检测到序列”1001“后,若下一个输入为0则会拉高输出;若下一个输入为1则无输出。据此可以画出状态转移图如下:
- IDLE:初始状态,对输入的码流进行检测,若为1则跳转到状态A,则为0保留在该状态
- A: 对输入的码流进行检测,若为0则跳转到状态B(10),则为1保留在该状态
- B: 对输入的码流进行检测,若为0则跳转到状态C(100),则为1则跳转到状态A(101)
- C: 对输入的码流进行检测,若为1则跳转到状态D(1001),则为0则跳转到状态IDLE(1000)
-
D: 对输入的码流进行检测,则为1则跳转到状态A(10011);若为0则成功检测到了序列”10010“,可以拉高输出。
- 重复检测:则跳转到状态E(100_10,后面的10可视为新的一轮检测)
- 非重复检测:则跳转到状态IDLE(10010_,开始新的一轮检测)
2.2.1、Verilog代码
根据状态转移图可编写Verilog代码如下:需要注意的是,将是否进行重复检测设为了参数REPEAT,1–重复检测;0–非重复检测,提供程序复用性。
//检测序列“10010”,重复检测,使用三段式Mealy型状态机
module seqdet_mealy
#(
parameter REPEAT = 1'b1 //1--重复检测;0--非重复检测
)
(
input x , //输入
input clk , //时钟
input rst_n , //复位信号,低电平有效
output reg z //输出采用时序逻辑,避免毛刺
);
//reg define
reg [2:0] cur_state , //现态
next_state ; //状态
//parameter define
localparam IDLE= 3'd0,
A = 3'd1,
B = 3'd2,
C = 3'd3,
D = 3'd4;
//三段式状态机第1段
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cur_state <= IDLE;
else
cur_state <= next_state;
end
//三段式状态机第2段
always@(*)begin
next_state = IDLE;
case(cur_state)
IDLE: next_state = x ? A : IDLE;
A : next_state = x ? A : B;
B : next_state = x ? A : C;
C : next_state = x ? D : IDLE;
D : next_state = REPEAT ? (x ? A : B) : (x ? A : IDLE);
default:next_state = IDLE;
endcase
end
//三段式状态机第3段
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
z <= 1'b0;
else begin
case(cur_state)
D: begin
if(x == 1'b0) //10010
z <= 1'b1;
else //10011
z <= 1'b0;
end
default : z <= 1'b0;
endcase
end
end
endmodule
2.2.2、testbench
在测试代码我们分别例化两个检测模块,一个用来检测重复输出,另一个用来检测非重复输出。
//检测序列“10010”,使用三段式MEALY型状态机
`timescale 1ns/1ns
module tb_seqdet_mealy;
reg x ;
reg clk ;
reg rst_n ;
wire z1 ; //重复检测输出结果
wire z0 ; //非重复检测输出结果
parameter REPEAT1 = 1'b1; //重复检测
parameter REPEAT0 = 1'b0; //非重复检测
initial begin
clk = 1'b1;
rst_n <= 1'b0;
x <= 1'b0;
#30 rst_n<=1; //拉高复位信号
forever #20 x <= ({$random} % 2); //生成1bit的随机数
end
always #10 clk <= ~clk; //时钟信号50M
//例化序列检测器模块--重复检测
seqdet_mealy
#(
.REPEAT (REPEAT1 )
)
seqdet_mealy_isnt1(
.x (x ),
.clk (clk ),
.rst_n (rst_n ),
.z (z1 )
);
//例化序列检测器模块--非重复检测
seqdet_mealy
#(
.REPEAT (REPEAT0 )
)
seqdet_mealy_isnt0(
.x (x ),
.clk (clk ),
.rst_n (rst_n ),
.z (z0 )
);
endmodule
2.2.3、仿真结果
仿真结果如下:
可以看到重复检测的结果比非重复检测的结果还是多一些的,接下来选取几个典型的局部图:
在检测到后1001后,接下来的输入为0构成10010所以立即输出成功检测标志。
重复检测的结果如下:
上图中,输入码流为”10010_010“,在重复检测模式中,后面的10010又组成了一次成功的序列输入,所以一共输出两次成功检测标志;而非重复检测标志则只输出一次成功检测标志。
3、移位寄存器法
3.1、说明
移位寄存器法比较简单粗暴,直接使用一个5位的寄存器寄存每一个码流,并在下一个时钟周期对其移位,再与需要的结果(10010)进行对比,根据对比结果输出检测结果。
非重复检测:最少都是码流”10010——10010“,那么两次成功输出会间隔最少5个时钟。所以我们可以设计一个计数器,当成功检测时将其清零,其他时候递加1。
实际上重复检测的时候直接输出即可,而非重复检测则需要根据计数器的值进行判断。每次成功输出时需要判断此时计数器的值,若非重复检测则计数器的值应大于等于4(0-4共5个时钟)。
3.2、Verilog代码
将是否进行重复检测设为了参数REPEAT,1–重复检测;0–非重复检测,编写Verilog代码如下:
//检测序列“10010”,使用移位寄存器法
module seqdet_reg
#(
parameter REPEAT = 1'b1 //1--重复检测;0--非重复检测
)
(
input x , //输入
input clk , //时钟
input rst_n , //复位信号,低电平有效
output reg z //输出采用时序逻辑,避免毛刺
);
reg [4:0] z_reg; //移位寄存器
reg [9:0] cnt; //计数器,这里位宽设置大点,留点余量1024
reg first_flag; //第一次检测标志,拉高则表示已经检测到了第一个“10010”
//将输入向左移位寄存
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
z_reg <= 5'd0;
else
z_reg <= {z_reg[3:0],x}; //向左移位
end
//对比结果进行输出
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
z <= 1'b0;
first_flag <= 1'b0;
cnt <= 10'd0;
end
else if(REPEAT)begin //重复检测到序列
first_flag <= 1'b0;
cnt <= 10'd0;
if(z_reg == 5'b10010) //捕捉到10010
z <= 1'b1;
else
z <= 1'b0;
end
else begin //非重复检测到序列
if(z_reg == 5'b10010)begin //捕捉到10010
if(!first_flag)begin //是第一个“10010”
z <= 1'b1;
first_flag <= 1'b1; //拉高标志信号
cnt <= 10'd0; //计数器清零
end
else begin //不是第一个“10010”
if(cnt >= 'd4)begin //间隔5个时钟以上(0-4)
z <= 1'b1;
cnt <= 10'd0; //计数器清零
first_flag <= first_flag;
end
else begin
z <= 1'b0;
cnt <= cnt + 1; //计数器累加1
first_flag <= first_flag;
end
end
end
else begin //没有捕捉到10010
z <= 1'b0;
cnt <= cnt + 1; //计数器累加1
first_flag <= first_flag;
end
end
end
endmodule
3.3、 testbench
在测试代码我们分别例化两个检测模块,一个用来检测重复输出,另一个用来检测非重复输出。基本和FSM的tb文件一致。
3.4、仿真结果
仿真结果见下图:
方框内都是进行了重复检测的部分,由于重复检测的逻辑比较简单,所以我们只截取重复部分的非重复检测的典型图:
4、其他
- 想要整个工程的朋友可以在评论区留下邮箱
- 原创不易,能否给个三连~_~!
版本信息
文件:V1.0
编号:4
Vivado:无
Modelsim:Modelsim SE-64 10.4
Quartus II:无