赞
踩
学习状态机,这是数电部分非常重要的基础知识,现在利用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代码
`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成功识别到了三次,产生三次高电位。修改代码就可以实现非循环检测,这里不再赘述。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。