学习状态机,这是数电部分非常重要的基础知识,现在利用Verilog来实现,并用modelsim进行仿真。
序列检测并非完全等价于状态机,而是状态机重要应用之一。本次实验进行序列检测1101,当这个序列出现时,输出高电位,其他状态都为0。
常见的序列检测有循环检测和非循环检测两种,循环检测就是上一个序列结尾可以作为下一个序列的开端,例如110110111001101,在第一个1101到来后会输出1,同时结尾1也可以作为下一个1101序列的开端,因此整个序列可以产生3个高电位;而如果是非循环检测,每一个序列不能重复使用,上个1101出现后,这4位信号被“丢弃”,只有下一个完整的1101出现才再次出现高电位输出,则对于这整个序列来说只输出2个高电位。
这对于状态机编码来说会产生一定的差别,对于循环编码而言,可以画出以下状态机图
状态d出现后,下一位输入如果是1,则继续可以作为下一个1101序列的开端,因此可以回到b状态。
而对于非循环编码来说,其状态机图可以如下所示
这里引入了新的状态e,为了使电路可以正常循环往复,虽然名为“非循环检测”,还是要把e状态的下一个状态与a状态链接起来。
对于状态机的画法是数电时序逻辑知识的基础部分,如果不清楚应当重新回到数电部分进行理解,这里强调一句,我们一般假定第一个状态是被检测状态首位的非状态。例如我们这里要检测1101,即我们总是假定,此处的a状态是“输入为0的一个状态”,因此才有状态图中状态a当下一位输入为1时进入下一个新状态,而如果输入仍为0则保持原状态。
下面进行Verilog编码,首先是核心代码
module state_machine (
input sys_clk,
input sys_rst_n,
input din,
output reg dout
);
// 非循环检测状态赋值
// localparam a = 3'b000;
// localparam b = 3'b001;
// localparam c = 3'b010;
// localparam d = 3'b011;
// localparam e = 3'b100;
//循环检测状态赋值
localparam a = 2'b00;
localparam b = 2'b01;
localparam c = 2'b10;
localparam d = 2'b11;
reg [2:0] current_state;
reg [2:0] next_state;
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
current_state <= a;
end else begin
current_state <= next_state;
end
end
//1101序列检测器
always @(*) begin
case (current_state)
a:if(din==1'b1) begin
next_state = b;
end else begin
next_state = a;
end
b:if(din==1'b1) begin
next_state = c;
end else begin
next_state = a;
end
c:if(din==1'b0) begin
next_state = d;
end else begin
next_state = c;
end
d:if(din==1'b1) begin
next_state = b;
end else begin
next_state = a;
end
//如果是非循环检测则需要引入以下代码
// e:if(din==1'b1) begin
// next_state = b;
// end else begin
// next_state = a;
// end
default: next_state = a;
endcase
end
always@* begin
if(current_state==d && next_state==b) begin
dout <= 1;
end else begin
dout <= 0;
end
end
endmodule
下面是编写testbench代码文章来源:https://www.toymoban.com/news/detail-755574.html
`timescale 1ns/1ps
module tb_state_machine();
reg sys_clk;
reg sys_rst_n;
reg din;
wire dout;
parameter PERIOD = 20;
// parameter DIN_LIST = 21'b10111011011010010020;
reg [20:0] din_list;
integer i;
always begin
sys_clk = 1'b0;
#(PERIOD/2) sys_clk = 1'b1;
#(PERIOD/2);
end
initial begin
sys_rst_n <= 1'b0;
// din_list <= DIN_LIST;
#20
sys_rst_n <= 1'b1;
din <= 1'b0;
// for (i = 0; i < 21 ;i=i+1 ) begin
// #PERIOD
// din <= din_list[i];
// end
#20 //注意,此处每一个间隔时间必须与PERIOD时间相同,否则无法正常识别
din <= 1'b1;
#20
din <= 1'b0;
#20
din <= 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;
#20
din <= 1'b1;
#20
din <= 1'b0;
#20
din <= 1'b1;
#20
din <= 1'b0;
end
// always
// #(PERIOD/2) sys_clk = ~sys_clk;
// always@(posedge sys_clk or negedge sys_rst_n) begin
// #20
// din_list = {din_list[19:0],din[20]};
state_machine u_state_machine (
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.din (din),
.dout (dout)
);
endmodule
我这里编写的testbench比较繁琐,其中注释部分标出了另一种用for循环产生一批序列的方法。注意,我已经在代码中用注释标出,序列产生的时间间隔必须与时钟周期相等,否则状态机无法正常识别!
用modelsim进行仿真,结果如下
这是循环检测结果,可见,对于序列010111011011010成功识别到了三次,产生三次高电位。修改代码就可以实现非循环检测,这里不再赘述。文章来源地址https://www.toymoban.com/news/detail-755574.html
到了这里,关于verilog实现1101序列检测的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!