上一篇博文:【入门学习三】基于 FPGA 使用 Verilog 实现按键状态机代码及原理详解
本文内容:从 PC 上位机通过 COM 发送数据给 FPGA ,FPGA 接收到数据后,将数据回传给 PC 上位机。
串行通信 | 同步通信 | 带时钟同步信号的数据传输 | 如 I2C、SPI |
异步通信 | 不带时钟同步信号的数据传输 | 如 UART、单总线 | |
传输方向 | 单工 | ||
半双工 | 如 I2C、单总线 | ||
全双工 | 如 SPI、UART |
顶层模块设计 uart.v:
`define BAUD_115200
//`define BAUD_9600
module uart(
input clk ,
input rst_n ,
input rx ,
output tx
);
/*
波特率9600 19200 38400 115200
如果定义 BAUD_115200,那么 baud_sel 为 0,则波特率为 115200
如果定义 BAUD_9600,那么 baud_sel 为 3,则波特率为 9600
*/
wire [1:0] baud_sel;
`ifdef BAUD_115200
assign baud_sel = 0;
`elsif BAUD_9600
assign baud_sel = 3;
`endif
//信号定义
wire [7:0] rx_data ;
wire rx_data_vld ;
wire [7:0] tx_data ;
wire tx_data_vld ;
wire tx_rdy ;
uart_rx u_uart_rx( //接收模块 串并转换
.clk (clk ),
.rst_n (rst_n ),
.baud_sel (baud_sel ),
.rx (rx ),
.rx_data (rx_data ),
.rx_data_vld(rx_data_vld)
);
ctrl u_ctrl(
.clk (clk ),
.rst_n (rst_n ),
.din (rx_data ),
.din_vld (rx_data_vld),
.dout (tx_data ),
.dout_vld (tx_data_vld),
.tx_rdy (tx_rdy )
);
uart_tx u_uart_tx( //发送模块 并串转换
.clk (clk ),
.rst_n (rst_n ),
.baud_sel (baud_sel ),
.tx_din (tx_data ),
.tx_din_vld (tx_data_vld),
.tx_rdy (tx_rdy ),
.tx (tx )
);
endmodule
uart_rx.v
module uart_rx(
input clk , //时钟信号
input rst_n , //复位
input [1:0] baud_sel , //波特率标志
input rx , //数据输入口
output reg [7:0] rx_data , //接收的数据
output reg rx_data_vld //接收完成标志信号
);
//信号定义
reg [12:0] cnt_bps ; //波特率时钟周期计数器
reg add_flag ; //计数器使能信号,数据接收标志
reg [3:0] cnt_bit ; //比特计数器
reg [9:0] rx_data_r ; //接收数据寄存器
reg rx_r0 ; //同步
reg rx_r1 ; //打拍 检测下降沿
wire rx_nedge ; //接收信号的下降沿
reg [12:0] baud_bps ; //根据不同波特率选择的计数值
//波特率时钟周期计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bps <= 0;
end
else if(add_flag)begin
//接收到使能信号,开始计数一个波特周期
if(cnt_bps == baud_bps - 1)
cnt_bps <= 0;
else
cnt_bps <= cnt_bps + 1;
end
end
//比特计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(cnt_bps == baud_bps - 1)begin
//计数 0-9 比特,第 0 比特为低电平起始信号
//第 1-8 比特为数据位
//第 9 比特为停止位
if(cnt_bit == 9 || rx_data_r[0] == 1'b1)
//如果计满到了第 9 比特或者第 0 比特起始位为高电平
//那么就归零
//这里起始电平为高电平说明数据接收错误,也要归零
cnt_bit <= 0;
else
cnt_bit <= cnt_bit + 1;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
add_flag <= 1'b0;
end
else if(rx_nedge)begin
//检测到下降沿,那么使能信号拉高,标志有数据输入
add_flag <= 1'b1;
end
else if(cnt_bps == baud_bps-1 && (cnt_bit == 9 || rx_data_r[0] == 1'b1))begin
//如果数据接收了 10 个比特或者起始电平为高(数据接受错误)
//那么使能信号归零
add_flag <= 1'b0;
end
end
//baud_bps
always @(*) begin
//根据不同的波特率选择不同的时钟周期
case(baud_sel)
0:baud_bps = 434;
1:baud_bps = 1302;
2:baud_bps = 2604;
3:baud_bps = 5208;
default:baud_bps = 434;
endcase
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_r0 <= 0;
rx_r1 <= 0;
end
else begin
rx_r0 <= rx; //同步
rx_r1 <= rx_r0; //打拍
end
end
//获取到下降沿
assign rx_nedge = ~rx_r0 & rx_r1;
//rx_data_r
always @ (posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_data_r <= 0;
end
else if(add_flag & cnt_bps == (baud_bps>>1))begin
//如果使能信号且当前为一个波特周期的一半
//那么将此时 rx 的数据输入电平存储到数据寄存器中
//rx_data_r <= {rx,rx_data_r[9:1]}; //第一种表达方式
rx_data_r[cnt_bit] <= rx; //第二种表达方式
end
end
//rx_data
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_data <= 0;
end
else begin
//将数据寄存器中的值传给 rx_data
//由 rx_data 传递接收数据给控制模块处理
rx_data <= rx_data_r[8:1];
end
end
//rx_data_vld
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_data_vld <= 0;
end
else if(cnt_bps == baud_bps - 1 && cnt_bit == 9)begin
//接收完毕后,传递完成接收标志信号
rx_data_vld <= 1'b1;
end
else begin
rx_data_vld <= 1'b0;
end
end
endmodule
程序执行过程:
rx_data_r[0] | rx_data_r[1] | rx_data_r[2] | rx_data_r[3] | rx_data_r[4] | rx_data_r[5] | rx_data_r[6] | rx_data_r[7] | rx_data_r[8] | rx_data_r[9] | 十六进制 |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 2 |
0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 4 |
1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 12 |
1 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 25 |
1 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 48 |
0 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 96 |
1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 12D |
ctrl.v:
module ctrl(
input clk , //时钟信号
input rst_n , //复位
input [7:0] din , //接收到的数据
input din_vld , //数据有效性,高位有效,低位无效
input tx_rdy , //发送准备信号
output reg [7:0] dout , //数据输出
output reg dout_vld //数据有效性
);
//信号定义
reg rd_req ; //使能读操作
wire wr_req ; //使能写操作
wire empty ; //缓存空信号
wire full ; //缓存满信号
wire [7:0] q_dout ; //缓存输出数据
wire [3:0] usedw ; //缓存数据深度
reg rd_flag ;
//rd_flag
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rd_flag <= 0;
end
else if(usedw >= 4)begin
//如果缓存数据深度大于等于 4
//拉高读标志
rd_flag <= 1'b1;
end
else if(empty)begin
//如果缓存数据深度小于等于 0
//拉低读标志
rd_flag <= 1'b0;
end
end
always @(*) begin
if(rd_flag && tx_rdy)
//如果读标志且输出数据标志位
//那么使能读缓存标志
rd_req = 1'b1;
else
rd_req = 1'b0;
end
//dout
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
dout <= 0;
end
else begin
//从缓存中获取数据
dout <= q_dout;
end
end
//dout_vld
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
dout_vld <= 1'b0;
end
else begin
//使能数据输出标志
dout_vld <= rd_req;
end
end
//fifo核,先进先出缓存
fifo fifo_inst (
.aclr (~rst_n ),
.clock (clk ),
.data (din ),
.rdreq (rd_req ),
.wrreq (wr_req ),
.empty (empty ),
.full (full ),
.q (q_dout ),
.usedw (usedw )
);
//如果缓存中没满并且有数据进来
//那么使能读操作
assign wr_req = full == 1'b0 && din_vld;
endmodule
wire [3:0] usedw ; //缓存数据深度
wire empty ; //缓存空信号
程序执行过程:
uart_tx.v
module uart_tx (
input clk , //时钟信号
input rst_n , //复位
input [1:0] baud_sel , //波特率标志
input [7:0] tx_din , //数据输入
input tx_din_vld , //数据输入标志位
output reg tx_rdy , //数据发送准备标志
output reg tx //数据发送
);
reg [12:0] cnt_bps ; //波特率周期计数器
reg add_flag ; //计数器使能信号
reg [3:0] cnt_bit ; //比特计数器
reg [9:0] tx_data_r ; //接收数据寄存器
reg [12:0] baud_bps ; //根据不同波特率选择的计数值
//计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bps <= 0;
end
else if(add_flag)begin
//计数器开始计数
//计满一个波特率周期
if(cnt_bps == baud_bps-1)
cnt_bps <= 0;
else
cnt_bps <= cnt_bps + 1;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(cnt_bps == baud_bps-1)begin
//对比特计数,共 10 位
if(cnt_bit == 9)
cnt_bit <= 0;
else
cnt_bit <= cnt_bit + 1;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
add_flag <= 1'b0;
end
else if(tx_din_vld)begin
//数据发送使能信号
add_flag <= 1'b1;
end
else if(cnt_bit == 9 && cnt_bps == baud_bps-1)begin
//数据发送完毕,则归零处理
add_flag <= 1'b0;
end
end
//baud_bps
always @(*) begin
//波特率周期选择
case(baud_sel)
0:baud_bps = 434;
1:baud_bps = 1302;
2:baud_bps = 2604;
3:baud_bps = 5208;
default:baud_bps = 434;
endcase
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_data_r <= 0;
end
else if(tx_din_vld)begin
//准备要发送的数据
//在数据两端添加起始位和停止位
tx_data_r <= {1'b1,tx_din,1'b0};
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx <= 1'b1;
end
else if(add_flag && cnt_bps == 1)begin
//发送数据
tx <= tx_data_r[cnt_bit];
end
end
//tx_rdy
always @(*) begin
if(tx_din_vld || add_flag)
//使能数据准备发送标志
tx_rdy = 1'b0;
else
tx_rdy = 1'b1;
end
endmodule
程序执行过程:
管脚定义
效果展示
附带整个项目文件:https://pan.baidu.com/s/174JjbUw1mYEUKV_VA3-DWQ——提取码:psdo