问题引入:实现串行序列10110的检测,输出为高则表示检测到。
方法1:状态机实现
拓展:
一段式、二段式、三段式状态机;
参考链接:verilog状态机: 一段式,两段式,三段式_miaomiaofine_新浪博客 (sina.com.cn)
总结:状态机应包含三个部分:①状态转移部分(现态、次态转换顺序)、②判断状态转移条件(不同输入对状态转换的影响)、③输出状态(状态或输入对输出的影响)
一段式:一个always块——①、②、③都放在一起;
二段式:两个always块——一个always块放时序部分:①,另一个always块放组合部分:②、③;
注意:②中的组合部分的case敏感信号为current state;因为②、③在一起,二段式的状态机会有毛刺,需要拍频(加一个触发器)以消除毛刺;
三段式:三个always块——分别放①、②、③;
注意:三段式的输出通过触发器产生,不会有毛刺;③中的输出状态的case敏感信号为next_state,更利于时序路径分组,一般来说在FPGA/CPLD等可编程逻辑器件上的综合与布局布线效果更佳。
Moore型、Mealy型状态机的比较
Moore型:输出仅有当前状态有关,所以输出和状态写在一起。用作序列检测时会比Mealy型多一个状态;
Mealy型:输出不仅与当前状态有关,还与输入有关;
源代码:
module sequence_detect(
//input
clk,
rst_n,
d_in,
//output
d_out
);
input clk;
input rst_n;
input d_in;
output reg d_out;
parameter IDLE = 3'd0;
parameter A = 3'd1;
parameter B = 3'd2;
parameter C = 3'd3;
parameter D = 3'd4;
parameter E = 3'd5;
reg [2:0] c_state,n_state;
//1、状态寄存器:描述对应当前状态的状态寄存器,非阻塞赋值
always@(posedge clk, negedge rst_n)
begin
if(!rst_n)
c_state <= IDLE;
else
c_state <= n_state;
end
//2、 描述下一状态的状态寄存器,阻塞赋值
always@(*)
begin
case(c_state)
IDLE:
if(d_in) n_state = A;
else n_state = IDLE;
A:
if(!d_in) n_state = B;
else n_state = A;
B:
if(d_in) n_state = C;
else n_state = A;
C:
if(d_in) n_state = D;
else n_state = B;
D:
if(!d_in) n_state = E;
else n_state = A;
E:
if(d_in) n_state = A;
else n_state = IDLE;
default:
n_state = IDLE;
endcase
end
//3、描述输出,非阻塞赋值
always @(posedge clk, negedge rst_n)
begin
if(!rst_n)
d_out <= 1'b0;
else
case(n_state)//对次态敏感
IDLE:
d_out <= 'b0;
A:
d_out <= 'b0;
B:
d_out <= 'b0;
C:
d_out <= 'b0;
D:
d_out <= 'b0;
E:
d_out <= 'b1;
default:
d_out <= 'b0;
endcase
end
endmodule
Testbench:
`timescale 1ns / 10ps
module sequence_detect_tb;
reg clk;
reg rst_n;
reg [17:0] data;
wire x_in;
wire z_out;
/***********************************************************/
initial
begin
clk = 1'b0;
rst_n = 1'b0;
#100;
rst_n = 1'b1;
end
/***********************************************************/
always #10 clk = ~clk;
/***********************************************************/
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
data <= 18'b11_0110_0100_0010_1101;
else
data <= {data[16:0],data[17]};
end
/***********************************************************/
assign x_in = data[17];
/***********************************************************/
sequence_detect sequence_detect_inst(
//input
.clk(clk),
.rst_n(rst_n),
.d_in(x_in),
//ouput
.d_out(z_out)
);
/***********************************************************/
endmodule
仿真结果:
方法2:移位寄存器+比较器
先上电路图
实现思路:通过移位寄存器将输入的一位二进制数进行移位,再移位寄存器的输出值q与比较器中的检测序列进行比较,若相同则输出高电平
源代码:
//不同状态机检测序列10110
module seq_det_com(
input clk,
input rst_n,
input din,
output wire dout,
output reg [4:0]q
);
assign dout = (q == 5'b10110)?1'b1:1'b0;
always @(posedge clk,negedge rst_n)
begin
if(!rst_n)
q<=5'b0;
else
q<={q[3:0],din};
end
endmodule
testbench:
`timescale 1ns/1ns
module seq_det_com_tb;
reg clk;
reg rst_n;
wire din;
reg [17:0] data;
wire dout;
/***********************************************************/
initial
begin
clk = 1'b0;
rst_n = 1'b0;
#10;
rst_n = 1'b1;
end
/***********************************************************/
always #10 clk = ~clk;
/***********************************************************/
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
data <= 18'b11_0110_0100_0010_1101;//待检测序列
else
data <= {data[16:0],data[17]};//并行转成串行,当然也可以串行输入
end
/***********************************************************/
assign din = data[17];
/***********************************************************/
seq_det_com my_seq_det_com(
//input
.clk(clk),
.rst_n(rst_n),
.din(din),
//ouput
.dout(dout)
);
/***********************************************************/
endmodule
仿真结果:
由上面的仿真结果可知,输出dout相对din晚了一个时钟周期,因为由于移位寄存器的赋值是在always块中,故而相对实际延迟了一个clk.
总结:
- 跟用状态机实现的区别在于,使用移位寄存器需要存储所有的码字,因此如果序列长度为N,则该方法需要消耗的寄存器就是N个。而使用状态机实现时,每个状态代表部分码字,如果使用十进制编码,则只需要使用log2(N)个寄存器即可编码所有状态,从寄存器资源的角度来看FSM实现起来代价较小。
- 此外,寄存器版本每来一个码元都要比较所有码字,因此需要消耗N个比较器,而FSM的的状态寄存器每一位在状态转移时都需要不同的译码逻辑,如果状态转移比较简单,组合逻辑可能会比移位寄存器少,状态转移复杂的化就不好说了。
- 当然,移位寄存器的版本编码更加简洁明了。