上一篇博文:【入门学习三】基于 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