本章目录:
0. 前言
序列检测是面试中的一个高频考点,必须要掌握。要求如下:
- 对与序列检测的原理能说清。
- 要会手撕代码(有两种方法能实现:状态机 和 移位寄存器),至少得掌握状态机的方法吧!!!
1. 什么是序列检测
对于一个输入为x,输出为y的系统,我们想要检测某一个序列是否是我们想要的序列,如果是,输出y为1(或0);若不是,输出y为0(或1)。
下边举个例子:
比如我们想要检测的序列为 | 1001 |
---|---|
输入 x | 001001 |
输出 y | 000001 |
这就叫做序列检测!!!
1.1 什么是可重叠序列检测?什么是不可重叠序列检测?
1.1.1 可重叠序列检测
直接举例说明即可===>
比如我们想要检测的序列为 | 1001 |
---|---|
输入 x | 001001101001001 |
输出 y | 000001000001001 |
此时我们的输出最后一位是1,因为输入序列的倒数第四位是上一个已经检测出来序列的末尾,也就是下一个序列的开头用的是上一个序列的结尾,这就是可重叠的。
1.1.2 不可重叠序列检测
同样举例来说明===>
比如我们想要检测的序列为 | 1001 |
---|---|
输入 x | 001001101001001 |
输出 y | 000001000001000 |
此时我们的输出最后一位是0,因为输入序列的倒数第三位是一个新序列的开头,也就是下一个序列的开头用的不是上一个序列的结尾,这就是不可重叠的。
2. 序列检测的方法
序列检测目前我知道的有2种,状态图法 和 移位寄存器法。
下边我以实现检测序列为1001的可重叠检测 为例。
2.1 状态图法
上图是原始状态转移图,具体步骤如下:
首先说明一下,图中的ABCD表示的是状态,线条上边表示的是:输入/输出。
- 先对序列的第一位取反,表示一直等待有效状态来临。在图中序列第一位是1,取反为0,在A出A入的部分体现;如果此时等到的是序列第一位,表示可以进入下一个状态,图中A出B入的部分。每个状态都有两种可能,此时,A状态完成。
- 来到B状态,此时B的状态为1。如果此时来一个0,此时为10,刚好是我们序列1001想要的,那直接进入下一个状态;如果此时来的是1,此时为11,很遗憾,不是我们想要的,那只能报废这个1之前的那个1了,重新回到1状态,即回到B本身。
- 来到C状态,此时C的状态为10。如果此时来了0,此时为100,刚好是序列1001想要的,直接进入下一个状态;如果来的是1,此时为101,哎,不想要,咋办?作废1前边的10,回到状态1继续,即回到B。
- 来到D状态,此时D的状态为100。如果此时来了0,此时为1000,不是序列1001想要的,咋办?只能全部报废,重来,也就是回到A;如果来的是1,此时为1001,刚好是我们的序列,那你说该咋办?回到A?那肯定不行啊!题目说了可重叠,那末尾的这个1可以作为下一个序列的开头,那只能回到B开始喽。此时的输出为1。完事!!!
注意:不可重叠的我就不写了,其实就是最后一步有点区别。
2.1.1 牛刀小试
2.1.1.1 题目一
2.1.1.1.1 状态图
2.1.1.1.2 代码实现
`timescale 1ns/1ns
module sequence_test1(
input wire clk ,
input wire rst ,
input wire data ,
output reg flag
);
//*************code***********//
parameter IDLE = 6'b000000;
parameter s0 = 6'b000010;
parameter s1 = 6'b000100;
parameter s2 = 6'b001000;
parameter s3 = 6'b010000;
parameter s4 = 6'b100000;
reg [5:0] cur_state;
reg [5:0] nex_state;
//第一段:时序逻辑描述 状态转移
always @ (posedge clk or negedge rst) begin
if(!rst) begin
cur_state <= IDLE;
end
else begin
cur_state <= nex_state;
end
end
//第二段:组合逻辑描述 状态转移条件和规律
always @ (*) begin
case(cur_state)
IDLE : nex_state = (data == 1'b1) ? s0 : IDLE;
s0 : nex_state = (data == 1'b1) ? s0 : s1;
s1 : nex_state = (data == 1'b1) ? s2 : IDLE;
s2 : nex_state = (data == 1'b1) ? s3 : s1;
s3 : nex_state = (data == 1'b1) ? s4 : s1;
s4 : nex_state = (data == 1'b1) ? s0 : IDLE;
endcase
end
//第三段:时序逻辑或者组合逻辑 描述 输出状态
always @ (posedge clk or negedge rst) begin
if(!rst) begin
flag <= 1'b0;
end
else begin
//可以根据 次态单独判断(摩尔型) 或者 根据 现态和输入一起判断(米粒型)
// if(cur_state == s3 && data == 1'b1) begin
if(nex_state == s4) begin
flag <= 1'b1;
end
else begin
flag <= 1'b0;
end
end
end
//*************code***********//
endmodule
2.1.1.2 题目二
2.1.1.2.1 状态图
2.1.1.2.2 代码实现
`timescale 1ns/1ns
module sequence_test2(
input wire clk ,
input wire rst ,
input wire data ,
output reg flag
);
//*************code***********//
parameter IDLE = 5'b00001;
parameter s0 = 5'b00010;
parameter s1 = 5'b00100;
parameter s2 = 5'b01000;
parameter s3 = 5'b10000;
reg [4:0] cur_state;
reg [4:0] nex_state;
//第一步:时序逻辑描述状态转移
always @ (posedge clk or negedge rst) begin
if(!rst) begin
cur_state <= 5'b0;
end
else begin
cur_state <= nex_state;
end
end
//第二部:组合逻辑描述状态转移条件和规律
always @ (*) begin
case(cur_state)
IDLE : nex_state = (data == 1'b1) ? s0 : IDLE;
s0 : nex_state = (data == 1'b1) ? s0 : s1;
s1 : nex_state = (data == 1'b1) ? s2 : IDLE;
s2 : nex_state = (data == 1'b1) ? s3 : s1;
s3 : nex_state = (data == 1'b1) ? s0 : s1;
default : nex_state = IDLE;
endcase
end
//第三步:时序逻辑描述状态输出
always @ (posedge clk or negedge rst) begin
if(!rst) begin
flag <= 1'b0;
end
else begin
// if(cur_state == s2 && data == 1'b1) begin
if(cur_state == s3) begin
flag <= 1'b1;
end
else begin
flag <= 1'b0;
end
end
end
//*************code***********//
endmodule
注意第二个题和第一个题在第三步的时候不太一样,由于题中给出的波形要延后一拍,所以得注意一下!!!
2.1.2 总结
- FSM包括摩尔型和米粒型,摩尔型比米粒型多了一个状态,理想状态下三段式最后一段===> 摩尔型表示方式为:cur_state <= 最后一个状态,输出拉高;米粒型表示方式为:cur_state <= 最后一个状态 && data ,输出拉高。(上边两个题很奇怪,只能说看题中具体给出的波形吧)!!!
- 可重叠和不可重叠序列检测用三段状态机写的时候,差距就在第二段状态转移条件和规律,其他的两段不变。
2.2 移位寄存器法
这里我们以检测序列10010为例。
2.2.1 思路介绍
移位寄存器法比较简单粗暴,直接使用一个5位的寄存器寄存每一个码流,并在下一个时钟周期对其移位,再与需要的结果(10010)进行对比,根据对比结果输出检测结果。
非重复检测:最少都是码流”10010——10010“,那么两次成功输出会间隔最少5个时钟。所以我们可以设计一个计数器,当成功检测时将其清零,其他时候递加1。
重复检测:实际上重复检测的时候直接输出即可,而非重复检测则需要根据计数器的值进行判断。每次成功输出时需要判断此时计数器的值,若非重复检测则计数器的值应大于等于4(0-4共5个时钟)。
2.2.2 代码实现
//检测序列“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
=============================================================================
参考文献
声明
本人所有系列的文章,仅供学习,不可商用,如有侵权,请告知,立删!!!
本人主要是记录学习过程,以供自己回头复习,再就是提供给后人参考,不喜勿喷!!!
如果觉得对你有用的话,记得收藏+评论!!!