目录
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]
本文所有程序均经过板上验证过,均正常,放心使用参考。