本文为 FPGA 学习总结,欢迎分享交流。
运行环境
- windows10
- Vivado 2018.3
- Modelsim 10.7
状态机是数字逻辑系统的核心,是重要的时序电路。通常包括三个部分:一是下一个状态的逻辑 电路,二是存储状态机当前状态的时序逻辑电路,三是输出组合逻辑电路。状态机的结构如下图:
根据状态机的输出信号是否与电路的输入有关分为 Mealy 型状态机和 Moore 型状态机。电路的输出信号与电路当前状态和电路的输入有关,称为 Mealy 型状态机;电路的输出信号仅与电路当前状态有关,称为 Moore 型状态机。
一段式状态机
// detect1.v
module detect_1(
input clk_i,
input rst_n_i,
output out_o
);
reg out_r;
//状态声明和状态编码
reg [1:0] state;
parameter [1:0] S0=2'b00;
parameter [1:0] S1=2'b01;
parameter [1:0] S2=2'b10;
parameter [1:0] S3=2'b11;
always@(posedge clk_i)
begin
if(!rst_n_i)begin
state<=0;
out_r<=1'b0;
end
else
case(state)
S0 :
begin
out_r<=1'b0; // 输出和状态转移在一个always模块中
state<= S1;
end
S1 :
begin
out_r<=1'b1;
state<= S2;
end
S2 :
begin
out_r<=1'b0;
state<= S3;
end
S3 :
begin
out_r<=1'b1;
end
endcase
end
assign out_o=out_r;
endmodule
两段式状态机
// detect2.v
`timescale 1ns / 1ps
module detect_2(
input clk_i,
input rst_n_i,
output out_o
);
reg out_r;
// 状态声明和状态编码
reg [1:0] Current_state;
reg [1:0] Next_state;
parameter [1:0] S0=2'b00;
parameter [1:0] S1=2'b01;
parameter [1:0] S2=2'b10;
parameter [1:0] S3=2'b11;
// 时序逻辑:描述状态转换
always@(posedge clk_i)
begin
if(!rst_n_i)
Current_state<=0;
else
Current_state<=Next_state;
end
// 组合逻辑:描述下一状态和输出
always@(*)
begin
case(Current_state)
S0 :
begin
out_r=1'b0;
Next_state= S1;
end
S1 :
begin
out_r=1'b1;
Next_state= S2;
end
S2 :
begin
out_r=1'b0;
Next_state= S3;
end
S3 :
begin
out_r=1'b1;
Next_state=Next_state;
end
endcase
end
assign out_o=out_r;
endmodule
三段式状态机
// detect3.v
module detect_3(
input clk_i,
input rst_n_i,
output out_o
);
reg out_r;
// 状态声明和状态编码
reg [1:0] Current_state;
reg [1:0] Next_state;
parameter [1:0] S0=2'b00;
parameter [1:0] S1=2'b01;
parameter [1:0] S2=2'b10;
parameter [1:0] S3=2'b11;
// 时序逻辑:描述状态转换
always@(posedge clk_i)
begin
if(!rst_n_i)
Current_state<=S0;
else
Current_state<=Next_state;
end
// 组合逻辑:描述下一状态
always@(*)
begin
case(Current_state)
S0:
Next_state = S1;
S1:
Next_state = S2;
S2:
Next_state = S3;
S3:
Next_state = Next_state;
default :
Next_state = S0;
endcase
end
// 输出逻辑:让输出 out,经过寄存器 out_r 锁存后输出,消除毛刺
always@(*)
begin
case(Current_state)
S0,S2:
out_r<=1'b0;
S1,S3:
out_r<=1'b1;
default :
out_r<=out_r;
endcase
end
assign out_o=out_r;
endmodule
例程
我们用一个例程对三种状态机的设计进行进一步的理解。下面为以上三种方式的仿真文件:
// tb_detect.v
`timescale 1ns / 1ps
module tb_detect();
reg clk_i;
reg rst_n_i;
wire out_o_1,out_o_2,out_o_3;
detect_1 detect_1(
.clk_i(clk_i),
.rst_n_i(rst_n_i),
.out_o(out_o_1)
);
detect_2 detect_2(
.clk_i(clk_i),
.rst_n_i(rst_n_i),
.out_o(out_o_2)
);
detect_3 detect_3(
.clk_i(clk_i),
.rst_n_i(rst_n_i),
.out_o(out_o_3)
);
initial begin
clk_i =0;
rst_n_i =0;
#20
rst_n_i =1;
end
always #5 clk_i = ~clk_i;
endmodule
在 vivado 中点击 SIMULATION 运行代码进行仿真,三种状态机得到的结果都为 0101:
我们添加断点进行深入研究。给每个文件的 always
模块添加断点,并将对应的信号加入到仿真窗口中进行观察:
对于一段式状态机,在复位完成后才开始输出 0101;对于两段和三段式状态机,因为输出部分为组合逻辑,复位状态时,当前状态为 0,因此在复位之前全部输出 0,在复位完成的第一个状态中输出为 1,再输出 01。
编写 TESTBENCH 的目的是为了对使用硬件描述语言设计的电路进行仿真验证,测试设计电路的功能、性能与设 计的预期是否相符。通常,编写测试文件的过程如下:
- 产生模拟激励(波形文件);
- 将产生的激励加入到被测试模块中并观察其响应;
- 将输出响应与期望值相比较
状态机的设计是时序电路的核心内容,要仔细把握三种状态机的区别与练习,并熟练运用。