目录
四、设计输入
五、仿真测试
六、下板测试
1.PC向FPGA发送数据
2.FPGA向PC发送数据
总结
如图所示思维导图,其中包含设计的状态、功能设计、以及信号设计,根据此设计,写出代码。
1.主模块:例化按键模块
/*************串口通信********************/
module serial_port(
input clk ,//50M时钟
input rst_n ,//复位
input [ 2: 0] key_in ,//按键输入
input rx_data ,//计算机端串口数据接收
output reg tx_data ,//FPGA发送数据
output reg [ 7: 0] led //[3:0]是板上LED0-LED3,[5:4]是RS232的Tx和Rx端的led指示灯,[7:6]是can的Rxd和Txd端的led指示灯
);
parameter time_1bit = 13'd5208 ;//1bit数据计数次数
parameter data = 8 'hff ;//FPGA发送数据时循环发送数据
reg [ 1: 0] state ;//状态,包含3个状态,空闲、PC向FPGA发送数据、FPGA向PC发送数据
reg [ 12: 0] cnt0 ;//发送1bit数据时钟数的计数,9600bps为1/9600s=104166.66ns=5208时钟周期
reg [ 3: 0] cnt1 ;//发送数据计数
reg flag ;//可以计数指示信号
reg data_in_vld ;//数据输入使能
wire add_cnt0 ;//计数器cnt0加一条件
wire end_cnt0 ;//计数器cnt0结束条件
wire add_cnt1 ;//计数器cnt1加一条件
wire end_cnt1 ;//计数器cnt1结束条件
wire neg_flag ;//始接收数据指示信号
reg rx_data_ff1 ;//rx_data打一拍,防止亚稳态
reg rx_data_ff2 ;//rx_data打两拍,防止亚稳态
reg rx_data_ff3 ;//再打一拍,获取rx_data_ff2的下降沿neg_flag
wire [ 9: 0] tx_data_f ;//带有起始位和停止位的数据
wire [ 3: 0] key_vld ;//按键输出
key_move(
.clk (clk) ,//50M时钟
.rst_n (rst_n) ,//复位
.key_in (key_in) ,//按键输入
.key_vld (key_vld) //按键输出
);
//状态输出
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)
state <= 2'b00;
else if(key_vld==3'b110)
state <= 2'b00;
else if(key_vld==3'b101)
state <= 2'b01;
else if(key_vld==3'b011)
state <= 2'b10;
else
state <= state;
end
//cnt0计数
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
cnt0 <= 0;
end
else if(add_cnt0)begin
if(end_cnt0)
cnt0 <= 0;
else
cnt0 <= cnt0 + 1'b1;
end
end
assign add_cnt0 = (state==2'b01 || state==2'b10) && flag==1;
assign end_cnt0 = add_cnt0 && cnt0==time_1bit-1;
////cnt1计数
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
cnt1 <= 0;
end
else if(add_cnt1) begin
if(end_cnt1)
cnt1 <= 0;
else
cnt1 <= cnt1 + 1'b1;
end
end
assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1==10-1;
////flag输出,flag==1时计数
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)
flag <= 0;
else if(state==2'b01&&neg_flag || state==2'b10&&data_in_vld==1)
flag <= 1;
else if(end_cnt1)
flag <= 0;
else
flag <= flag;
end
//数据输入使能
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)
data_in_vld <= 1;
else if(state==2'b10 && flag==1)
data_in_vld <= 0;
else if(state==2'b10 && flag==0)
data_in_vld <= 1;
end
//发送一个数据帧
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
tx_data <= 1;
end
else if(state==2'b10 && flag==1 && cnt0==0) begin
tx_data <= tx_data_f[cnt1];
end
end
assign tx_data_f = {1'b1,data,1'b0};
//打两拍,防止亚稳态,同时捕获下降沿
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_data_ff1 <= 1;
rx_data_ff2 <= 1;
rx_data_ff3 <= 1;
end
else begin
rx_data_ff1 <= rx_data ;
rx_data_ff2 <= rx_data_ff1;
rx_data_ff3 <= rx_data_ff2;
end
end
assign neg_flag = rx_data_ff2==0 && rx_data_ff3==1;
//输出led信号
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
led <= 8'hff;
end
else if(state==2'b01 && flag==1 && cnt0==time_1bit/2 && cnt1>0)begin
led[cnt1-1] <= rx_data_ff2;
end
end
endmodule
2.按键消抖模块
/**********按键按下,输出一个有用脉冲信号******/
module key_move(
input clk ,//50M时钟
input rst_n ,//复位
input [ KEY_W-1: 0] key_in ,//按键输入
output reg [ KEY_W-1: 0] key_vld //按键输出
);
parameter DATA_W = 20 ;
parameter KEY_W = 3 ;
parameter TIME_20MS = 1_000_000 ;
reg [DATA_W-1:0] cnt ;//计数器计数20ms,防止抖动
wire add_cnt ;//计数器加一条件
wire end_cnt ;//计数器结束条件
reg flag ;//计数条件
reg [KEY_W-1 :0] key_in_ff1 ;//按键输入打两拍,防止亚稳态
reg [KEY_W-1 :0] key_in_ff0 ;//按键输入打一拍
//计数器,计数20ms防止抖动
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
cnt <= 20'b0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 20'b0;
else
cnt <= cnt + 1'b1;
end
else begin
cnt <= 0;
end
end
assign add_cnt = flag==1'b0 && (&key_in_ff1==0);
assign end_cnt = add_cnt && cnt == TIME_20MS - 1;
//计数条件
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag <= 1'b0;
end
else if(end_cnt)begin
flag <= 1'b1;
end
else if(&key_in_ff1==1)begin
flag <= 1'b0;
end
end
//按键输入打两拍,防止亚稳态
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_in_ff0 <= {{KEY_W}{1'b1}};
key_in_ff1 <= {{KEY_W}{1'b1}};
end
else begin
key_in_ff0 <= key_in ;
key_in_ff1 <= key_in_ff0;
end
end
//按键按下,输出一个有用脉冲信号
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_vld <= {{KEY_W}{1'b1}};
end
else if(end_cnt)begin
key_vld <= key_in_ff1;
end
else begin
key_vld <= {{KEY_W}{1'b1}};
end
end
endmodule
编写仿真文件,测试其功能。不加按键模块,进行测试。
// Generated on "04/07/2020 11:04:56"
// Verilog Test Bench template for design : serial_port
//
// Simulation tool : ModelSim (Verilog)
//
`timescale 1 ns/ 1 ns
module serial_port_tb();
// constants
// test vector input registers
reg clk;
reg [2:0] key_in;
reg rst_n;
reg rx_data;
// wires
wire [7:0] led;
wire tx_data;
parameter clk_period = 20;
// assign statements (if any)
serial_port i1 (
// port map - connection between master ports and signals/registers
.clk(clk),
.key_in(key_in),
.led(led),
.rst_n(rst_n),
.rx_data(rx_data),
.tx_data(tx_data)
);
initial clk=0;
always #(clk_period/2) clk=~clk;
initial begin
//复位
#2;
rst_n = 0;
key_in = 3'b111;
rx_data = 1;
#(clk_period*5);
rst_n = 1;
#(clk_period*3);
//空闲状态
key_in = 3'b110;
#(clk_period);
key_in = 3'b111;
#(clk_period*50);
//状态1,接收PC端数据
key_in = 3'b101;
#(clk_period);
key_in = 3'b111;
#(clk_period*20);
//发送一个数据帧10'b0111111111
rx_data = 0;
#(clk_period*5208);
rx_data = 1;
#(clk_period*5208*9);
//发0;
#(clk_period*5208);
rx_data = 0送第二个数据帧
rx_data = ;
#(clk_period*5208*4);
rx_data = 1;
#(clk_period*5208);
rx_data = 0;
#(clk_period*5208);
rx_data = 1;
#(clk_period*5208);
rx_data = 0;
#(clk_period*5208);
rx_data = 1;
#(clk_period*5208);
//状态2,FPGA发送数据
key_in = 3'b011;
#(clk_period*5208*40);
$stop;
end
endmodule
按下按键2,进入PC向FPGA发送数据状态。
发送数据8'h00,8个LED应该都亮起。
发送数据8'hff,8个LED应全部灭掉。
发送数据8'ha0,8个LED应亮起部分。
按下按键3,可以看到串口收到FPGA发送的循环数据。
串口通信主要对其协议进行理解,了解其数据发送帧格式,其实是很简单的。