目录
一、 状态机导读
1.1 理论学习
1.2 状态机的表示
1.3 状态机编码
1.4 状态机描述方式
二 、实战演练一(来自野火)
2.1 实验目标
2.2 模块框图
2.3 状态转移图绘制
2.4 设计文件
2.5 仿真测试文件
2.6 仿真结果
三、 实战演练二(来自野火)
3.1 实验目标
3.2 模块框图
3.3 状态转移图绘制
3.4 设计文件
3.5 仿真测试文件
3.6 仿真结果
四、 实战演练三(来自小梅哥)
4.1 实验目标
4.2 模块框图
4.3 端口功能描述
4.4 设计文件
4.5 仿真测试文件
4.6 仿真结果
五、 实战演练四(来自小梅哥)
5.1 实验目标
5.2 设计文件
5.2.1 顶层文件
5.2.2 串口接收文件
5.2.3 字符检测模块
5.3 引脚约束文件
5.4 板上验证
module simple_fsm(
input sys_clk,
input sys_rst_n,
input pi_money,
output reg po_cola
);
parameter IDLE = 0;//记住独热码,二进制码,格雷码的区别。
parameter ONE = 1;
parameter TWO = 2;
reg [1:0]state;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)begin
state <= IDLE;
po_cola <= 0;
end
else begin
case(state)
IDLE: begin
if(pi_money)begin
state <= ONE;
po_cola <= 0; //可以使用两个时序逻辑分别描述状态转换和输出。
end
else
state <= IDLE;
end
ONE:
if(pi_money)begin
state <= TWO;
po_cola <= 0;
end
else
state <= ONE;
TWO:
if(pi_money)begin
state <= IDLE;
po_cola <= 1;
end
else
state <= TWO;
default:begin state <= IDLE;po_cola <= 0;end
endcase
end
endmodule
`timescale 1ns / 1ps
module simple_fsm_test();
reg sys_clk ;
reg sys_rst_n;
reg pi_money;
wire po_cola;
simple_fsm simple_fsm_inst(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.pi_money(pi_money),
.po_cola(po_cola)
);
initial sys_clk = 1;
always #10 sys_clk = ~sys_clk;
initial
begin
sys_rst_n = 0;
pi_money = 0;
#201;
sys_rst_n = 1;
#20000;
pi_money = 1;
#20;
pi_money = 0;
#20000;
pi_money = 1;
#20;
pi_money = 0;
#20000;
pi_money = 1;
#20;
pi_money = 0;
#20000;
$stop;
end
endmodule
端口列表与功能总结如下面表格所示。
module hard_fsm(
input sys_clk,
input sys_rst_n,
input pi_money_half,
input pi_money_one,
output reg po_cola,
output reg po_money
);
parameter IDLE = 0,//使用二进制码
HALF = 1,
ONE = 2,
ONE_HALF = 3,
TWO = 4,
TWO_HALF = 5;
reg [2:0]state;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)begin
state <= IDLE;
end
else begin
case(state)
IDLE:if(pi_money_half)begin
state <= HALF;
end
else if(pi_money_one)begin
state <= ONE;
end
else begin
state <= IDLE;
end
HALF:if(pi_money_half)begin
state <= ONE;
end
else if(pi_money_one)begin
state <= ONE_HALF;
end
else begin
state <= HALF;
end
ONE:if(pi_money_half)begin
state <= ONE_HALF;
end
else if(pi_money_one)begin
state <= TWO;
end
else begin
state <= ONE;
end
ONE_HALF:if(pi_money_half)begin
state <= TWO;
end
else if(pi_money_one)begin
state <= IDLE;
end
else begin
state <= ONE_HALF;
end
TWO:if(pi_money_half || pi_money_one)begin
state <= IDLE;
end
else begin
state <= TWO;
end
default:state <= IDLE;
endcase
end
always@(posedge sys_clk or negedge sys_rst_n)//使用两段式状态机写法
if(!sys_rst_n)
po_cola <= 0;
else if(((state == TWO) && (pi_money_half == 1))
||((state ==ONE_HALF) &&(pi_money_one == 1))
||((state ==TWO) &&(pi_money_one == 1)))
po_cola <= 1;
else
po_cola <= 0;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
po_money <= 0;
else if((state == TWO) && (pi_money_one == 1))
po_money <= 1;
else
po_money <= 0;
endmodule
`timescale 1ns/1ns
module hard_fsm_tb();
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//reg define
reg sys_clk;
reg sys_rst_n;
reg pi_money_one;
reg pi_money_half;
reg random_data_gen;
//wire define
wire po_cola;
wire po_money;
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//初始化系统时钟、全局复位
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
//sys_clk:模拟系统时钟,每 10ns 电平翻转一次,周期为 20ns,频率为 50MHz
always #10 sys_clk = ~sys_clk;
//random_data_gen:产生非负随机数 0、1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
random_data_gen <= 1'b0;
else
random_data_gen <= {$random} % 2;
//pi_money_one:模拟投入 1 元的情况
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_money_one <= 1'b0;
else
pi_money_one <= random_data_gen;
//pi_money_half:模拟投入 0.5 元的情况
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_money_half <= 1'b0;
else
//取反是因为一次只能投一个币,即 pi_money_one 和 pi_money_half 不能同时为 1
pi_money_half <= ~random_data_gen;
//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//------------------------complex_fsm_inst------------------------
hard_fsm hard_fsm_inst(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.pi_money_one (pi_money_one ), //input pi_money_one
.pi_money_half (pi_money_half ), //input pi_money_half
.po_cola (po_cola ), //output po_money
.po_money (po_money ) //output po_cola
);
endmodule
module hello(
input sys_clk,
input sys_rst_n,
input [7:0]data_in,//输入数据
input data_in_valid,//有效数据输入
output reg check_ok//检测出来一个hello就出现一个高脉冲
);
//定义5个状态
localparam CHECK_h = 0,
CHECK_e = 1,
CHECK_l1 = 2,
CHECK_l2 = 3,
CHECK_o = 4;
reg [2:0]state;
//我使用的是新二段式状态机,小梅哥用的是一段式状态机。
always@(posedge sys_clk or posedge sys_rst_n)
if(!sys_rst_n)
state <= CHECK_h;
else begin
case(state)
CHECK_h:begin
if(data_in_valid && data_in == "h")
state <= CHECK_e;
else
state <= CHECK_h;
end
CHECK_e:begin
if(data_in_valid && data_in == "e")//千万注意if还有else if的顺序问题,不然可能会造成检测不成功的现象出现。比如出现hhello就无法检测出来。
state <= CHECK_l1;
else if(data_in_valid && data_in == "h")
state <= CHECK_e;
else if(data_in_valid)
state <= CHECK_h;
else
state <= CHECK_e;
end
CHECK_l1:
if(data_in_valid && data_in == "l")
state <= CHECK_l2;
else if(data_in_valid && data_in == "h")
state <= CHECK_e;
else if(data_in_valid)
state <= CHECK_h;
else
state <= CHECK_l1;
CHECK_l2:
if(data_in_valid && data_in == "l")
state <= CHECK_o;
else if(data_in_valid && data_in == "h")
state <= CHECK_e;
else if(data_in_valid)
state <= CHECK_h;
else
state <= CHECK_l2;
CHECK_o:
if(data_in_valid && data_in == "o")
state <= CHECK_h;//检测完毕回到起始状态
else if(data_in_valid && data_in == "h")
state <= CHECK_e;
else if(data_in_valid)
state <= CHECK_h;
else
state <= CHECK_o;
endcase
end
//输出单独控制逻辑
always@(posedge sys_clk or posedge sys_rst_n)
if(!sys_rst_n)
check_ok <= 0;
else if((state == CHECK_o) && data_in_valid && (data_in == "o"))
check_ok <= 1;
else if(state == CHECK_h)
check_ok <= 0;
else
check_ok <= check_ok;
endmodule
`timescale 1ns / 1ps
`define CLK_PERIOD 20
module hello_tb();
reg sys_clk;
reg sys_rst_n;
reg data_valid;
reg [7:0]data_in;
wire check_ok;
hello hello(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.data_in(data_in),
.data_in_valid(data_valid),
.check_ok(check_ok)
);
initial sys_clk = 1;
always#(`CLK_PERIOD/2) sys_clk = ~sys_clk;
initial
begin
sys_rst_n = 0;
data_valid = 0;
data_in = 0;
#(`CLK_PERIOD*20);
sys_rst_n = 1;
#(`CLK_PERIOD*20 + 1);
repeat(2)
begin
gen_char("I");
#(`CLK_PERIOD);
gen_char("A");
#(`CLK_PERIOD);
gen_char("h");
#(`CLK_PERIOD);
gen_char("e");
#(`CLK_PERIOD);
gen_char("l");
#(`CLK_PERIOD);
gen_char("l");
#(`CLK_PERIOD);
gen_char("h");
#(`CLK_PERIOD);
gen_char("h");
#(`CLK_PERIOD);
gen_char("e");
#(`CLK_PERIOD);
gen_char("l");
#(`CLK_PERIOD);
gen_char("l");
#(`CLK_PERIOD);
gen_char("o");
#(`CLK_PERIOD);
gen_char("e");
#(`CLK_PERIOD);
gen_char("h");
#(`CLK_PERIOD);
gen_char("h");
#(`CLK_PERIOD);
gen_char("o");
#(`CLK_PERIOD);
end
#200;
$stop;
end
task gen_char;
input [7:0]char;
begin
data_in = char;
data_valid = 1'b1;
#(`CLK_PERIOD);
data_valid = 1'b0;
end
endtask
endmodule
module fsm_hello_test(
input sys_clk,
input sys_rst_n,
input uart_rxd,
output reg Led
);
wire [7:0]data_in;
wire data_in_valid;
wire check_ok;
zdyz_rs232_rx #(
.CLK_FREQ (5000_0000),//波特率设置
.UART_BPS (115200)
)
zdyz_rs232_rx(
.sys_clk(sys_clk) , //系统时钟
.sys_rst_n(sys_rst_n) , //系统复位,低有效
.uart_rxd(uart_rxd) , //UART 接收端口
.uart_rx_done(data_in_valid), //UART 接收完成信号,接收完成后就代表数据有效
.uart_rx_data(data_in) //UART 接收到的数据送给字符检测模块作为输入
);
hello hello(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.data_in(data_in),
.data_in_valid(data_in_valid),
.check_ok(check_ok)
);
//每检测一次,LED就要翻转一次
always@(posedge sys_clk or posedge sys_rst_n)
if(!sys_rst_n)
Led <= 0;
else if(check_ok)
Led <= ~Led;
else
Led <= Led;
endmodule
(个人觉得正点原子的串口接收模块比小梅哥的简单易懂,实用)
module zdyz_rs232_rx(
input sys_clk , //系统时钟
input sys_rst_n , //系统复位,低有效
input uart_rxd , //UART 接收端口
output reg uart_rx_done, //UART 接收完成信号
output reg [7:0] uart_rx_data //UART 接收到的数据
);
parameter CLK_FREQ = 5000_0000; //系统时钟频率
parameter UART_BPS = 115200 ; //串口波特率
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数 BPS_CNT 次
reg uart_rxd_d0;
reg uart_rxd_d1;
reg rx_flag ; //接收过程标志信号
reg [3:0] rx_cnt ; //接收数据位计数器
reg [15:0] baud_cnt ; //波特率计数器(位宽为16,防止溢出)
reg [7:0 ] rx_data_t ; //接收数据寄存器
wire start_flag;//开始接收的标志,下降沿到来。
//打两拍:波特率时钟和系统时钟不同步,为异步信号,所以要进行打拍处理,防止产生亚稳态
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
uart_rxd_d0 <= 1'b0;
uart_rxd_d1 <= 1'b0;
end
else begin
uart_rxd_d0 <= uart_rxd;
uart_rxd_d1 <= uart_rxd_d0;
end
end
assign start_flag = (uart_rxd_d0 == 0)&&(uart_rxd_d1 == 1);//下降沿到来的表示方法
// rx_flag接收信号的拉高与拉低
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_flag <= 1'b0;
else if(start_flag) //检测到起始位
rx_flag <= 1'b1; //接收过程中,标志信号 rx_flag 拉高
//在停止位一半的时候,即接收过程结束,标志信号 rx_flag 拉低
else if((rx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1))//rx_flag 要提前拉低,防止其影响下一帧数据的接收
rx_flag <= 1'b0;
else
rx_flag <= rx_flag;
end
//波特率的计数器计数逻辑
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
baud_cnt <= 0;
else if(rx_flag)begin
if(baud_cnt == BAUD_CNT_MAX - 1)
baud_cnt <= 0;
else
baud_cnt <= baud_cnt + 1;
end
else
baud_cnt <= 0;
end
//位计数实现逻辑
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
rx_cnt <= 0;
else if(rx_flag)begin
if(baud_cnt == BAUD_CNT_MAX - 1)
rx_cnt <= rx_cnt + 1;
else
rx_cnt <= rx_cnt;
end
else
rx_cnt <= 0;//其他情况下都为0,所以不用担心计数超过9,且其计数也不会超过9,当rx_flag为0时就不计数了
end
//根据 rx_cnt 来寄存 rxd 端口的数据
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_data_t <= 0;
else if(rx_flag)begin //系统处于接收过程时
if(baud_cnt == BAUD_CNT_MAX/2 - 1)begin//判断 baud_cnt 是否计数到数据位的中间
case(rx_cnt)
1:rx_data_t[0] <= uart_rxd_d1; //寄存数据的最低位
2:rx_data_t[1] <= uart_rxd_d1;
3:rx_data_t[2] <= uart_rxd_d1;
4:rx_data_t[3] <= uart_rxd_d1;
5:rx_data_t[4] <= uart_rxd_d1;
6:rx_data_t[5] <= uart_rxd_d1;
7:rx_data_t[6] <= uart_rxd_d1;
8:rx_data_t[7] <= uart_rxd_d1;//寄存数据的高低位
default:rx_data_t <= rx_data_t;
endcase
end
else
rx_data_t <= rx_data_t;
end
else
rx_data_t <= 0;
end
//给接收完成信号和接收到的数据赋值
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
uart_rx_done <= 0;
uart_rx_data <= 0;
end
//当接收数据计数器计数到停止位,且 baud_cnt 计数到停止位的中间时
else if((rx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1))begin
uart_rx_done <= 1; //拉高接收完成信号
uart_rx_data <= rx_data_t;//并对 UART 接收到的数据进行赋值
end
else begin
uart_rx_done <= 0;
uart_rx_data <= uart_rx_data;
end
end
endmodule
module hello(
input sys_clk,
input sys_rst_n,
input [7:0]data_in,//输入数据
input data_in_valid,//有效数据输入
output reg check_ok//检测出来一个hello就出现一个高脉冲
);
//定义5个状态
localparam CHECK_h = 0,
CHECK_e = 1,
CHECK_l1 = 2,
CHECK_l2 = 3,
CHECK_o = 4;
reg [2:0]state;
//我使用的是新二段式状态机,小梅哥用的是一段式状态机。
always@(posedge sys_clk or posedge sys_rst_n)
if(!sys_rst_n)
state <= CHECK_h;
else begin
case(state)
CHECK_h:begin
if(data_in_valid && data_in == "h")
state <= CHECK_e;
else
state <= CHECK_h;
end
CHECK_e:begin
if(data_in_valid && data_in == "e")//千万注意if还有else if的顺序问题,不然可能会造成检测不成功的现象出现。比如出现hhello就无法检测出来。
state <= CHECK_l1;
else if(data_in_valid && data_in == "h")
state <= CHECK_e;
else if(data_in_valid)
state <= CHECK_h;
else
state <= CHECK_e;
end
CHECK_l1:
if(data_in_valid && data_in == "l")
state <= CHECK_l2;
else if(data_in_valid && data_in == "h")
state <= CHECK_e;
else if(data_in_valid)
state <= CHECK_h;
else
state <= CHECK_l1;
CHECK_l2:
if(data_in_valid && data_in == "l")
state <= CHECK_o;
else if(data_in_valid && data_in == "h")
state <= CHECK_e;
else if(data_in_valid)
state <= CHECK_h;
else
state <= CHECK_l2;
CHECK_o:
if(data_in_valid && data_in == "o")
state <= CHECK_h;//检测完毕回到起始状态
else if(data_in_valid && data_in == "h")
state <= CHECK_e;
else if(data_in_valid)
state <= CHECK_h;
else
state <= CHECK_o;
endcase
end
//输出单独控制逻辑
always@(posedge sys_clk or posedge sys_rst_n)
if(!sys_rst_n)
check_ok <= 0;
else if((state == CHECK_o) && data_in_valid && (data_in == "o"))
check_ok <= 1;
else if(state == CHECK_h)
check_ok <= 0;
else
check_ok <= check_ok;
endmodule
set_property PACKAGE_PIN U18 [get_ports sys_clk]
set_property IOSTANDARD LVCMOS33 [get_ports sys_clk]
set_property IOSTANDARD LVCMOS33 [get_ports Led]
set_property IOSTANDARD LVCMOS33 [get_ports sys_rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports uart_rxd]
set_property PACKAGE_PIN F20 [get_ports sys_rst_n]
set_property PACKAGE_PIN K16 [get_ports uart_rxd]
set_property PACKAGE_PIN G17 [get_ports Led]
本文所有程序均经过板上验证过,均正常,放心使用参考。