目錄
四、設計輸入
如圖所示思維導圖,其中包含設計的狀態、功能設計、以及信號設計,根據此設計,寫出代碼。
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
六、下板測試
1.PC向FPGA發送數據
按下按鍵2,進入PC向FPGA發送數據狀態。
發送數據8'h00,8個LED應該都亮起。
發送數據8'hff,8個LED應全部滅掉。
發送數據8'ha0,8個LED應亮起部分。
2.FPGA向PC發送數據
按下按鍵3,可以看到串口收到FPGA發送的循環數據。
總結
串口通信主要對其協議進行理解,瞭解其數據發送幀格式,其實是很簡單的。