FPGA——UART串口通信
文章目录
- 前言
- 一、UART通信协议
- 1.1 通信格式
- 2.2 MSB或LSB
- 2.3 奇偶校验位
- 2.4 UART传输速率
- 二、UART通信回环
- 2.1 系统架构设计
- 2.2 fsm_key
- 2.3 baud
- 2.4 sel_seg
- 2.5 fifo
- 2.6 uart_rx
- 2.7 uart_tx
- 2.8 top_uart
- 2.9 发送模块时序分析
- 2.10 接收模块的时序分析
- 2.11 FIFO控制模块时序分析
- 三、仿真
- 3.1 testbench
- 3.2 接收模块仿真分析
- 3.3 发送模块仿真分析
- 3.4 FIFO控制模块仿真分析
- 四、上板验证
- 五、总结
前言
本篇博客的实验内容是实现uart串口通信发送数据和接收数据,并加入按键模块调整波特率,数码管模块显示波特率,实现不同波特率下的PC端与FPGA板子的数据回环,并加入FIFO IP核对数据进行缓存,当数据大于等于8个的时候,一次性发送8个数据出去。
实验环境:quartus 18.1 modelsim Cyclone IV开发板
一、UART通信协议
UART 即通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),是一种串行、异步、全双工的通信协议。特点是通信线路简单,适用于远距离通信,但传输速度慢。它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
1.1 通信格式
它的一帧数据由4部分组成:
起始位(1bit)
数据位(6\7\8 bit)
奇偶校验位(1bit)
停止位(1bit\1.5bit\2bit)
注意:它在空闲状态下为高电平,起始位为低电平,停止位为高电平。它的数据位甚至可以为5位或者4位,但是不能为9位及以上。
为什么起始位是低电平?
因为它的空闲位为高电平,必须有一个由高到低的下降沿变化才能知道后面要传输数据了,不然如果起始位为高电平那它怎么知道你何时传数据呢?
2.2 MSB或LSB
它的数据位传输顺序可以采用MSB也可以采用LSB,由通信双方决定。
MSB:数据高位先发送
LSB:数据低位先发送
在本次实验的工程里面采用的是LSB的格式传输。
2.3 奇偶校验位
奇校验:当实际数据中“1”的个数为奇数的时候,这个校验位就是“0”,否则这个校验位就是“1”;
偶校验:当实际数据中“1”的个数为偶数的时候,这个校验位就是“0”,否则这个校验位就是“1”。
注意:奇偶校验只能检验奇数个数据的错误,对于偶数个数据的错误无法检测。
本次实验没有用到奇偶校验,因此在这里单独进行说明:
在接收模块中接收串行数据的校验方法:
奇校验实现方式:校验位默认为高电平,每检测到1,则状态翻转
偶校验实现方式:校验位默认为低电平,每检测到1,则状态翻转
核心代码:
在接收模块接收并行数据进行校验:
偶校验:将输入数据按位异或
奇校验:将输入数据按位异或再取反(与偶校验相反)
核心代码:
2.4 UART传输速率
UART的数据传输速率用波特率(baud)表示,常见的波特率有9600、19200、38400、57600、115200,最常用的是9600和115200。
通信双方必须保持一致的波特率,不然数据就会出错,这个在最后我们的上板验证会体现。
以9600为例,它代表我们传输1bit数据所需要的时间是1/9600=104160ns,我们的时钟周期为20ns,所以传输1bit数据需要104160/20 = 5208个时钟周期。
注意:波特率和比特率的区别
比特率:是指每秒传送的比特(bit)数。单位为 bps(Bit Per Second),比特率越高,每秒传送数据就越多。
波特率:在电子通信领域,波特(Baud)即调制速率,指的是有效数据信号调制载波的速率,即单位时间内载波调制状态变化的次数。它是对符号传输速率的一种度量,1波特即指每秒传输1个符号,而通过不同的调制方式,可以在一个码元符号上负载多个bit位信息。
在信息传输通道中,携带数据信息的信号单元叫码元,每秒钟通过信道传输的码元数称为码元传输速率,简称波特率。波特率是传输通道频宽的指标。
比特率=波特率x单个调制状态对应的二进制位数。
二、UART通信回环
2.1 系统架构设计
该系统分为了6个模块,分别是按键消抖模块(fsm_key)、波特率设置模块(baud)、数码管显示模块(sel_seg)、接收模块(uart_rx)、FIFO数据缓存模块(fifo)以及发送模块(uart_tx)。
首先是通过按键调整波特率:输入的按键信号进入按键消抖模块,然后将消抖后的按键信号传给波特率设置模块,通过按键信号可以设置不同的波特率,有常用的波特率选择:9600、19200、38400、57600、115200。默认情况下是9600。然后把设置好的波特率传给数码管显示模块进行波特率的显示。同时将设置好的波特率传给接收和发送模块,两个模块的波特率应该保持一致。同时接收模块接收了外界传入的串行数据后,将串行数据转成并行数据传给FIFO模块进行数据缓存,当FIFO里面缓存的数据大于等于8个数据时,FIFO模块将开启数据发送使能信号,然后把读取到的一个数据传给发送模块,然后发送模块再把并行数据转成串行数据一个一个的发送出去。每发送完一个数据后都拉高一个数据发送完成的使能信号传给FIFO模块,FIFO在根据这个使能信号再读一个数据传给发送模块进行数据发送,重复该步骤直到发送出8个数据为止。
2.2 fsm_key
//三段式状态机实现按键消抖
module fsm_key #(parameter CNT_20MS = 26'd1_000_000,KEY_NUM = 3'd1)(//20MS计数器和按键的数量input clk ,//时钟信号input rst_n ,//复位信号input [KEY_NUM-1:0] key_in ,//按键输入信号output reg [KEY_NUM-1:0] key_out //按键输出信号
);parameter IDLE = 4'b0001, //空闲状态DOWN = 4'b0010, //按键按下抖动状态HOLD = 4'b0100, //消抖后保持低电平有效状态UP = 4'b1000; //释放抖动状态reg [3:0] state_c;//现态reg [3:0] state_n;//次态reg [25:0] cnt_20ms;//20ms计数寄存器wire add_cnt_20ms;//开始计数wire end_cnt_20ms;//结束计数reg [KEY_NUM-1:0] key_r0;//同步reg [KEY_NUM-1:0] key_r1;//打拍wire [KEY_NUM-1:0] nedge;//下降沿wire [KEY_NUM-1:0] podge;//上升沿//第一段状态机:时序逻辑,描述状态空间的切换always @(posedge clk or negedge rst_n) beginif(!rst_n)beginstate_c <= IDLE;endelse beginstate_c <= state_n;endend//第二段状态机:组合逻辑,描述状态转移条件always @(*) begincase (state_c)IDLE:beginif(nedge)begin //检测到下降沿进入抖动状态state_n = DOWN;endelse beginstate_n = state_c;endendDOWN:beginif(podge)begin //检测到上升沿证明是一个抖动回到空闲状态state_n = IDLE;endelse if(end_cnt_20ms && podge == 1'b0)begin//计时结束并且没有出现上升沿就证明是一个有效按下,进入下一个状态state_n = HOLD;endelse beginstate_n = state_c;endendHOLD:beginif(podge)begin//出现上升沿,进入释放抖动状态state_n = UP;endelse beginstate_n = state_c;endendUP :beginif(end_cnt_20ms)begin//延时结束进入空闲状态state_n = IDLE;endelse beginstate_n = state_c;endend default: state_n = state_c;endcaseend//第三段状态机,组合时序都可以,描述输出always @(posedge clk or negedge rst_n) beginif(!rst_n)beginkey_out <= {KEY_NUM{1'b0}};endelse if(state_c == DOWN && end_cnt_20ms)beginkey_out <= ~key_r1;endelse beginkey_out <= {KEY_NUM{1'b0}};endend//打拍寄存always @(posedge clk or negedge rst_n) beginif(!rst_n) beginkey_r0 <= {KEY_NUM{1'b1}}; //{3{1'b1}} -> 3'b111 {}拼接符号key_r1 <= {KEY_NUM{1'b1}};endelse beginkey_r0 <= key_in;key_r1 <= key_r0; endend// always @(posedge Clk or negedge Rst_n)begin // if(!Rst_n)begin// key_r <= 10'b11;// end // else begin// key_r <= {key_r[0],&key_in}; //打拍寄存// end// end assign nedge = ~key_r0 & key_r1;//要用按位与,因为不止一个按键信号assign podge = ~key_r1 & key_r0;//20MS计数器always @(posedge clk or negedge rst_n) beginif(!rst_n)begincnt_20ms <= 26'd0;endelse if(add_cnt_20ms)beginif(end_cnt_20ms)begincnt_20ms <= CNT_20MS;endelse begincnt_20ms <= cnt_20ms + 1'd1;endendelse begincnt_20ms <= 26'd0;endendassign add_cnt_20ms = state_c == DOWN || state_c == UP;assign end_cnt_20ms = add_cnt_20ms && (cnt_20ms == CNT_20MS - 1'd1);endmodule
2.3 baud
module baud(input clk ,input rst_n ,input key ,//按键输入信号output reg [16:0] baud //波特率
);reg [2:0] cnt;//设置波特率的计数器:0为9600,1为19200,2为38400,3为57600,4为115200always @(posedge clk or negedge rst_n) beginif(!rst_n)begincnt <= 3'd0;endelse if(key)beginif(cnt == 3'd4)begincnt <= 3'd0;endelse begincnt <= cnt + 1'd1;endendelse begincnt <= cnt;endendalways @(posedge clk or negedge rst_n) beginif(!rst_n)beginbaud <= 17'd9600;endelse begincase (cnt)0: baud <= 17'd9600 ;1: baud <= 17'd19200 ;2: baud <= 17'd38400 ;3: baud <= 17'd57600 ;4: baud <= 17'd115200 ; default: baud <= 17'd9600 ;endcaseendendendmodule
2.4 sel_seg
module sel_seg(input clk ,//系统时钟input rst_n ,//复位信号input [16:0] baud ,//波特率output reg [5:0] sel ,//位选信号output reg [7:0] seg //段选信号
);parameter ZERO = 8'b1100_0000 ,ONE = 8'b1111_1001 ,TWO = 8'b1010_0100 ,THREE = 8'b1011_0000 ,FOUR = 8'b1001_1001 ,FIVE = 8'b1001_0010 ,SIX = 8'b1000_0010 ,SEVEN = 8'b1111_1000 ,EIGHT = 8'b1000_0000 ,NINE = 8'b1001_0000 ,A = 8'b1000_1000 ,B = 8'b1000_0011 ,C = 8'b1100_0110 ,D = 8'b1010_0001 ,E = 8'b1000_0110 ,F = 8'b1000_1110 ;parameter CNT_20US = 10'd999 ;//20us需要1000个时钟周期reg [9:0] cnt_20us ;//20us计数寄存器wire add_cnt_20us ;//开始计数标志符wire end_cnt_20us ;//结束计数标志符reg [4:0] number ;//数码管要显示的数字//20us计数器always @(posedge clk or negedge rst_n) beginif(!rst_n)begincnt_20us <= 10'd0;endelse if(add_cnt_20us)beginif(end_cnt_20us)begincnt_20us <= 10'd0;endelse begincnt_20us <= cnt_20us + 1'd1;endendelse begincnt_20us <= cnt_20us;endendassign add_cnt_20us = 1'b1;assign end_cnt_20us = add_cnt_20us && cnt_20us == CNT_20US;//位选信号控制,每20us刷新一次always @(posedge clk or negedge rst_n) beginif(!rst_n)beginsel <= 6'b111_110;endelse if(end_cnt_20us)beginsel <= {sel[4:0],sel[5]};endelse beginsel <= sel;endend//每个数码管需要显示的数字always @(*) begincase (sel)6'b111_110: number = baud / 100000 ;//最左侧数码管 6'b111_101: number = baud % 100000 / 10000 ;6'b111_011: number = baud % 10000 / 1000 ;6'b110_111: number = baud % 1000 / 100 ;6'b101_111: number = baud % 100 / 10 ; 6'b011_111: number = baud % 10 ;//最右侧数码管default: number = 4'd0;endcaseend//段选信号控制显示always @(*) begincase (number)4'd0: seg <= ZERO ;4'd1: seg <= ONE ;4'd2: seg <= TWO ; 4'd3: seg <= THREE ;4'd4: seg <= FOUR ;4'd5: seg <= FIVE ;4'd6: seg <= SIX ;4'd7: seg <= SEVEN ;4'd8: seg <= EIGHT ;4'd9: seg <= NINE ;default:seg <= ZERO ; endcaseendendmodule
2.5 fifo
这里的FIFO是调用的IP核,使用的前显模式
module fifo(input clk ,input rst_n ,input [7:0] rx_dout ,//接收并行数据input dout_sign ,//接收完成input dout_sign_tx,output tx_req ,//发送请求拉高时开始发送output [7:0] tx_din ,//输入并行数据output empty ,//空标志output full ,//满标志output [6:0] usedw //已经用了多少空间
);reg tx_rd ;//读数据信号寄存reg flag ;//状态信号,为0时缓存数据,为1时发送数据,>=8时为1;reg [3:0] cnt_tx ;//一次性发送8个数据出去wire add_cnt_tx ;//发送开始信号wire end_cnt_tx ;//发送结束信号 reg rdreq ;//读取数据请求 fifo_128x8 fifo_128x8_inst (.clock ( clk ),.data ( rx_dout ),.rdreq ( rdreq ),.wrreq ( dout_sign ),.empty ( empty ),.full ( full ),.q ( tx_din ),.usedw ( usedw ));//读取数据信号使能always @(posedge clk or negedge rst_n) beginif(!rst_n)beginrdreq <= 1'b0;endelse if((tx_rd || dout_sign_tx) && !end_cnt_tx)beginrdreq <= 1'b1;endelse beginrdreq <= 1'b0;endend//当可度量达到标志时第一次读取数据使能信号always @(posedge clk or negedge rst_n) beginif(!rst_n)begintx_rd <= 1'b0;endelse if(!flag && usedw >= 7'd8)begintx_rd <= 1'b1;endelse begintx_rd <= 1'b0;endend//状态信号,0为空闲状态,1为读取数据状态 always @(posedge clk or negedge rst_n) beginif(!rst_n)beginflag <= 1'b0;endelse if(end_cnt_tx)beginflag <= 1'b0;endelse if(!flag && usedw >= 7'd8)begin//空闲状态时检测剩余可读量达到标志flag <= 1'b1;endelse beginflag <= flag;endend//8个数据计数器always @(posedge clk or negedge rst_n) beginif(!rst_n)begincnt_tx <= 3'd0;endelse if(add_cnt_tx)beginif(end_cnt_tx )begincnt_tx <= 3'd0;endelse begincnt_tx <= cnt_tx + 1'd1;endendelse begincnt_tx <= cnt_tx;endendassign add_cnt_tx = flag && dout_sign_tx;assign end_cnt_tx = add_cnt_tx && cnt_tx == 3'd7;assign tx_req = rdreq;endmodule
2.6 uart_rx
module uart_rx(input clk ,input rst_n ,input rx_din ,//输入串行数据input [16:0] baud ,//波特率output [7:0] rx_dout ,//接收并行数据output reg dout_sign//接收完成
);parameter CNT_1S = 26'd50_000_000;// parameter BAUD = 14'd9600;reg [12:0] cnt_baud ;//波特率计数器wire add_baud ;//波特率开始计数wire end_baud ;//波特率结束计数reg [3:0] cnt_bit ;//比特计数器wire add_bit ;//比特开始计数wire end_bit ;//比特结束计数reg [9:0] rx_data ;//数据寄存器reg rx_din_r0 ;//同步reg rx_din_r1 ;//打拍wire nedge ;//起始位的下降沿reg rx_flag ;//开始接收数据标志//波特率计数器always @(posedge clk or negedge rst_n) beginif(!rst_n)begincnt_baud <= 13'd0;endelse if(rx_flag == 1'b0)begincnt_baud <= 13'd0;endelse if(add_baud)beginif(end_baud)begincnt_baud <= 13'd0;endelse begincnt_baud <= cnt_baud + 1'd1;endendelse begincnt_baud <= cnt_baud;endendassign add_baud = rx_flag;assign end_baud = add_baud && cnt_baud == (CNT_1S/baud) - 1'd1;//根据波特率求得传输1bit所需要的时钟周期//比特计数器always @(posedge clk or negedge rst_n) beginif(!rst_n)begincnt_bit <= 4'd0;endelse if(rx_flag == 1'b0)begincnt_bit <= 13'd0;endelse if(add_bit)beginif(end_bit)begincnt_bit <= 4'd0;endelse begincnt_bit <= cnt_bit + 1'd1;endendelse begincnt_bit <= cnt_bit;endendassign add_bit = end_baud;assign end_bit = add_bit && cnt_bit == 4'd9;//一个数据有1bit起始位,8bit数据位,1bit结束位//同步打拍检测下降沿always @(posedge clk or negedge rst_n) beginif(!rst_n)beginrx_din_r0 <= 1'b1;rx_din_r1 <= 1'b1;endelse beginrx_din_r0 <= rx_din;rx_din_r1 <= rx_din_r0;endendassign nedge = ~rx_din_r0 && rx_din_r1;//同步,打拍,检测到下降沿开始接收数据//同步完成检测下降沿将开始的接收的标志置为1always @(posedge clk or negedge rst_n) beginif(!rst_n)beginrx_flag <= 1'b0;endelse if(nedge)beginrx_flag <= 1'b1;endelse if(rx_data[0] == 1'b1 || end_bit)begin//开始位为1,意外关闭rx_flag <= 1'b0;endelse beginrx_flag <= rx_flag;endend//缓存接收到的数据always @(posedge clk or negedge rst_n) beginif(!rst_n)beginrx_data <= 10'b0;endelse if(rx_flag && cnt_baud == (CNT_1S/baud-1)/2)beginrx_data[cnt_bit] <= rx_din_r1;endelse beginrx_data <= rx_data;endend//结束位为1时才接收数据,否则让数据为0视为无效数据assign rx_dout[7:0] = rx_data[8:1] ;//接收到一个完整的有效数据后接收完成信号拉高always @(posedge clk or negedge rst_n) beginif(!rst_n)begindout_sign <= 1'b0;endelse if(end_bit && rx_data[9] == 1'b1)begindout_sign <= 1'b1;endelse begindout_sign <= 1'b0;endendendmodule
2.7 uart_tx
module uart_tx (input clk ,input rst_n ,input tx_req ,//发送请求拉高时开始发送input [7:0] tx_din ,//输入并行数据input [16:0] baud ,//波特率output reg tx_dout,//发送串行数据,低位在前,高位在后output dout_sign_tx //发送结束标志
);parameter CNT_1S = 26'd50_000_000;// parameter BAUD = 14'd9600;reg [12:0] cnt_baud ;//波特率计数器wire add_baud ;//波特率开始计数wire end_baud ;//波特率结束计数reg [9:0] tx_data ;//10bit数据reg tx_flag ;//发送标志reg [3:0] cnt_bit ;//比特计数器wire add_bit ;//比特开始计数wire end_bit ;//比特结束计数//波特率计数器always @(posedge clk or negedge rst_n) beginif(!rst_n)begincnt_baud <= 13'd0;endelse if(add_baud)beginif(end_baud)begincnt_baud <= 13'd0;endelse begincnt_baud <= cnt_baud + 1'd1;endendelse begincnt_baud <= cnt_baud;endendassign add_baud = tx_flag;assign end_baud = add_baud && cnt_baud == (CNT_1S/baud - 1'd1);//根据波特率求得传输1bit所需要的时钟周期//比特计数器always @(posedge clk or negedge rst_n) beginif(!rst_n)begincnt_bit <= 4'd0;endelse if(add_bit)beginif(end_bit)begincnt_bit <= 4'd0;endelse begincnt_bit <= cnt_bit + 1'd1;endendelse begincnt_bit <= cnt_bit;endendassign add_bit = end_baud;assign end_bit = add_bit && cnt_bit == 4'd9;//一个数据有1bit起始位,8bit数据位,1bit结束位//发送请求拉高时寄存数据always @(posedge clk or negedge rst_n) beginif(!rst_n)begintx_data <= 10'b0;endelse if(tx_req)begintx_data <= {1'b1,tx_din,1'b0};endelse begintx_data <= tx_data;endend//发送请求拉高时保持发送信号拉高直到发送完毕always @(posedge clk or negedge rst_n) beginif(!rst_n)begintx_flag <= 1'b0;endelse if (tx_req)begintx_flag <= 1'b1;endelse if(end_bit)begintx_flag <= 1'b0;endelse begintx_flag <= tx_flag;endend//串行数据输出always @(posedge clk or negedge rst_n) beginif(!rst_n)begintx_dout <= 1'b1;endelse if(tx_flag && cnt_baud)begintx_dout <= tx_data[cnt_bit];endelse begintx_dout <= tx_dout;endendassign dout_sign_tx = end_bit;//直到没有再发送就结束了
endmodule
2.8 top_uart
module top_uart(input clk ,input rst_n ,input key ,//按键输入信号input rx ,//输入串行数据output tx ,//输出串行数据output [5:0] sel ,//位选信号output [7:0] seg //段选信号
);wire [7:0] rx_dout;//串转并数据wire rx_dout_sign;wire tx_dout_sign;wire tx_req;wire [7:0] tx_din;wire key_r;wire [16:0] baud_r;fsm_key fsm_key_inst(.clk (clk),//时钟信号.rst_n (rst_n),//复位信号.key_in (key),//按键输入信号.key_out (key_r) //按键输出信号);baud baud_inst(.clk (clk),.rst_n (rst_n),.key (key_r),//按键输入信号.baud (baud_r) //波特率);sel_seg sel_seg_inst(.clk (clk),//系统时钟.rst_n (rst_n),//复位信号.baud (baud_r),//波特率.sel (sel),//位选信号.seg (seg) //段选信号);uart_rx uart_rx_inst(.clk (clk ),.rst_n (rst_n ),.rx_din (rx ),.baud (baud_r ),.rx_dout (rx_dout ),.dout_sign (rx_dout_sign));fifo fifo_inst(.clk (clk),.rst_n (rst_n),.rx_dout (rx_dout),//接收并行数据.dout_sign (rx_dout_sign),//接收完成.dout_sign_tx (tx_dout_sign),.tx_req (tx_req),//发送请求拉高时开始发送.tx_din (tx_din) //输入并行数据// .empty (),//空标志// .full (),//满标志// .usedw () //已经用了多少空间);uart_tx uart_tx_inst(.clk (clk ),.rst_n (rst_n ),.tx_req (tx_req ),.tx_din (tx_din ),.baud (baud_r ),.tx_dout (tx ),.dout_sign_tx (tx_dout_sign));endmodule
2.9 发送模块时序分析
从时序图看,这里想要发送的并行数据是8’b11010011。首先是发送请求信号拉高,此时发送标志信号拉高直到数据发送完成。采用一个10bit的寄存器对并行数据进行寄存,同时由于是低位在前,高位在后,开始位为0,停止位为1,因此寄存的数据为10’b1110100110。同时在发送标志信号拉高时,cnt_baud计数器开始计数,该计数器是计的发送1bit数据所需要的系统时钟周期。计满时cnt_bit计数器加1,然后发送的串行数据就是根据cnt_bit计数器对寄存的数据进行一个一个的发送。在出现发送停止位后就表明这个数据发送完成了,因此发送结束标志信号dout_sign就拉高。
2.10 接收模块的时序分析
从这张时序图看,收到了一串的串行数据,首先需要对这一串数据进行同步打拍,通过同步打拍判断出下降沿,出现下降沿时开始接收数据的标志信号rx_flag拉高直到接收数据完成。然后依旧是两个计数器与发送模块的计数器功能一样,cnt_baud和cnt_bit。然后采用一个10bit的数据寄存器rx_data。为了接收到的数据稳定,因此采用当baud计数器过了一半时根据传入的串行数据打拍后的波形数据写入寄存器,同时判断了开始位为1和停止位为0的话都属于无效数据,不会接收无效数据。当数据有效时,最终接收到的并行数据rx_dout就为rx_data寄存器的[8:1]位。同时接收完毕后将接收完成的标志信号拉高。
2.11 FIFO控制模块时序分析
当FIFO中缓存的数据大于等于8个数据时,开启读取数据的状态,开启读取第一个数据的使能信号,将读取到的数据发给数据发送模块,当数据发送模块将一个数据发送完成后,将数据发送完成的标志信号dout_sign_tx传给FIFO控制模块,cnt_tx计数器加1,同时重新读取一个新的数据传给数据发送模块进行发送,直到发送完8个数据才重新进入空闲状态。并且由于FIFO是使用的前显模式,因此当读取数据有效时读取的数据是前面显示的那个数据。
三、仿真
3.1 testbench
`timescale 1ns/1ns
module top_uart_tb();reg clk ;reg rst_n ;reg rx ;reg key_r ;wire tx ;defparam uart_rx_inst.CNT_1S = 26'd50;defparam uart_tx_inst.CNT_1S = 26'd50;parameter CYCLE = 20;wire [7:0] rx_dout;//串转并数据wire rx_dout_sign;wire tx_dout_sign;wire tx_req;wire [7:0] tx_din;wire [6:0] usedw ;wire [16:0] baud_r;reg [7:0] test ;baud baud_inst(.clk (clk),.rst_n (rst_n),.key (key_r),//按键输入信号.baud (baud_r) //波特率);uart_rx uart_rx_inst(.clk (clk ),.rst_n (rst_n ),.rx_din (rx ),.baud (baud_r ),.rx_dout (rx_dout ),.dout_sign (rx_dout_sign));fifo fifo_inst(.clk (clk),.rst_n (rst_n),.rx_dout (rx_dout),//接收并行数据.dout_sign (rx_dout_sign),//接收完成.dout_sign_tx (tx_dout_sign),.tx_req (tx_req),//发送请求拉高时开始发送.tx_din (tx_din), //输入并行数据// .empty (),//空标志// .full (),//满标志.usedw (usedw) //已经用了多少空间);uart_tx uart_tx_inst(.clk (clk ),.rst_n (rst_n ),.tx_req (tx_req ),.tx_din (tx_din ),.baud (baud_r ),.tx_dout (tx ),.dout_sign_tx (tx_dout_sign));always #(CYCLE/2) clk = ~clk;initial beginclk = 1'b0;rst_n = 1'b0;test = 8'b0;#(CYCLE/2 + 3);rst_n = 1'b1;repeat(8)begintest = test + 1'b1;rx = 1'b0;#(5*CYCLE);rx = test[0];#(5*CYCLE);rx = test[1];#(5*CYCLE);rx = test[2];#(5*CYCLE);rx = test[3];#(5*CYCLE);rx = test[4];#(5*CYCLE);rx = test[5];#(5*CYCLE);rx = test[6];#(5*CYCLE);rx = test[7];#(5*CYCLE);rx = 1'b1;#(30*CYCLE);end#(2000*CYCLE);$stop;endendmodule
3.2 接收模块仿真分析
从这张仿真波形图可以看到,发送的标志信号拉高后,两个计数器正常工作,然后10bit的数据寄存器rx_data。在cnt_baud计数器过了一半时才会根据传入的串行数据打拍后的波形数据写入寄存器,同时判断了开始位为1和停止位为0的话都属于无效数据,不会接收无效数据。当数据有效时,最终接收到的并行数据rx_dout就为rx_data寄存器的[8:1]位。同时接收完毕后将接收完成的标志信号拉高。
3.3 发送模块仿真分析
从这张仿真波形图可以看到,当发送请求信号tx_req拉高后,发送标志信号tx_flag拉高直到发送完毕。同时10bit的数据寄存器将tx_din的并行数据存入并加上开始位0,停止位1。然后两个计数器开始正常计数。然后发送的串行数据tx_dout就是根据cnt_bit计数器对寄存的数据进行一个一个的发送。在出现发送停止位后就表明这个数据发送完成了,因此发送结束标志信号dout_sign就拉高。
3.4 FIFO控制模块仿真分析
从这张仿真波形图可以看出,当FIFO中缓存的数据大于等于8个数据时,开启读取数据的状态,开启读取第一个数据的使能信号,将读取到的数据发给数据发送模块,当数据发送模块将一个数据发送完成后,将数据发送完成的标志信号dout_sign_tx传给FIFO控制模块,cnt_tx计数器加1,同时重新读取一个新的数据传给数据发送模块进行发送,直到发送完8个数据才重新进入空闲状态。并且由于FIFO是使用的前显模式,因此当读取数据有效时读取的数据是前面显示的那个数据。
四、上板验证
从图片中可以看出,当发送数据小于8个时,它将数据接收并存入了FIFO缓冲器。当再次输入几个数据时,它会一次性输出8个数据,并且先输出之前存入的数据。当我们切换了波特率,PC端设置的波特率与FPGA板子上设置的波特率不同时,我们发送的数据就会解析混乱,变成一些乱码存入缓存器然后达到8个数据时输出。当我们重新调整波特率,使得双方波特率一致后,又能够收发同步了。
五、总结
因为该工程是分模块化的设计,因此如果只需要串口的发送和接收功能就只需要将这两个模块直接拿去用就可以了,大大方便了之后其他工程的设计。另外这只是我的一点学习笔记,如果有错误还请提出。
相关文章:
![](https://img-blog.csdnimg.cn/dd20c5b8359d469f9ca33d756fd0f6de.png#pic_center)
FPGA——UART串口通信
文章目录 前言一、UART通信协议1.1 通信格式2.2 MSB或LSB2.3 奇偶校验位2.4 UART传输速率 二、UART通信回环2.1 系统架构设计2.2 fsm_key2.3 baud2.4 sel_seg2.5 fifo2.6 uart_rx2.7 uart_tx2.8 top_uart2.9 发送模块时序分析2.10 接收模块的时序分析2.11 FIFO控制模块时序分析…...
![](https://img-blog.csdnimg.cn/img_convert/a18f6b607120430d56b5861d943e4751.jpeg)
华为云Stack的学习(七)
八、华为云Stack存储服务介绍 1.云硬盘EVS 云硬盘(Elastic Volume Service,EVS),又名磁盘,是一种虚拟块存储服务,主要为ECS(Elastic Cloud Server)和BMS(Bare Metal Se…...
![](https://www.ngui.cc/images/no-images.jpg)
安装k8s集群
一、前置环境配置 安装两台centos 实验环境,一台pc配有docker环境,有两个centsos7容器,其中一个容器作为master,一个作为node。如果master与node都是用默认端口,会存在冲突,所以在此基础上做细微的调整。…...
![](https://www.ngui.cc/images/no-images.jpg)
C++中编写没有参数和返回值的函数
C中编写没有参数和返回值的函数 返回值为 void 函数不需要将值返回给调用者。为了告诉编译器函数不返回值,返回类型为 void。例如: #include <iostream>// void means the function does not return a value to the caller void printHi() {std…...
![](https://img-blog.csdnimg.cn/da988ade4d9f4b76afac2387ca116a0d.png)
SWC 流程
一个arxml 存储SWC (可以存多个,也可以一个arxml存一个SWC)一个arxml 存储 composition (只能存一个)一个arxml 存储 system description (通过import dbc自动生成system) 存储SWC和composition的arxml文件分开&#…...
![](https://img-blog.csdnimg.cn/ed1a562cca2d4b06a134f4e30e0d403b.png)
怒刷LeetCode的第10天(Java版)
目录 第一题 题目来源 题目内容 解决方法 方法一:两次拓扑排序 第二题 题目来源 题目内容 解决方法 方法一:分治法 方法二:优先队列(Priority Queue) 方法三:迭代 第三题 题目来源 题目内容…...
![](https://img-blog.csdnimg.cn/76436d3b7f9c4f25944d3082da6eeb4c.png)
java框架-Springboot3-场景整合
文章目录 java框架-Springboot3-场景整合批量安装中间件NoSQL整合步骤RedisTemplate定制化 接口文档远程调用WebClientHttp Interface 消息服务 java框架-Springboot3-场景整合 批量安装中间件 linux安装中间件视频 NoSQL 整合redis视频 整合步骤 RedisTemplate定制化 Re…...
![](https://img-blog.csdnimg.cn/3cb16de5423c49c5a666ba4386b9943d.png)
在Bat To Exe Converter,修改为当异常结束或终止时,程序重新启动执行
在Bat To Exe Converter,修改为当异常结束或终止时,程序重新启动执行 .bat中的代码部分: .bat中的代码echo offpython E:\python\yoloProjectTestSmallLarge\detect.pypause,我想你能帮在Bat To Exe Converter,修改成…...
![](https://img-blog.csdnimg.cn/c949854929534e73a6a4dc3c0ad6a6d9.png)
PythonWeb服务器(HTTP协议)
一、HTTP协议与实现原理 HTTP(Hypertext Transfer Protocol,超文本传输协议)是一种用于在网络上传输超文本数据的协议。它是Web应用程序通信的基础,通过客户端和服务器之间的请求和响应来传输数据。在HTTP协议中连接客户与服务器的…...
![](https://img-blog.csdnimg.cn/4aef066debe84fcaaa0d1797eddadd6c.png)
Northstar 量化平台
基于 B/S 架构、可替代付费商业软件的一站式量化交易平台。具备历史回放、策略研发、模拟交易、实盘交易等功能。兼顾全自动与半自动的使用场景。 已对接国内期货股票、外盘美股港股。 面向程序员的量化交易软件,用于期货、股票、外汇、炒币等多种交易场景ÿ…...
![](https://img-blog.csdnimg.cn/317e7a72d4024881a5441e8c0be53d0c.png)
c语言进阶部分详解(经典回调函数qsort()详解及模拟实现)
大家好!上篇文章(c语言进阶部分详解(指针进阶2)_总之就是非常唔姆的博客-CSDN博客)我已经对回调函数进行了初步的讲解和一个简单的使用事例,鉴于篇幅有限没有进行更加详细的解释,今天便来补上。…...
![](https://www.ngui.cc/images/no-images.jpg)
win下 lvgl模拟器codeblocks配置
链接: 官方lvgl的codeblocks官方例子 下载慢的话,可能需要点工具。 需要下载的东西 https://github.com/lvgl/lv_port_win_codeblocks https://github.com/lvgl/lv_drivers/tree/4f98fddd2522b2bd661aeec3ba0caede0e56f96b https://github.com/lvgl/lvgl/tree/7a23…...
![](https://img-blog.csdnimg.cn/img_convert/da0902ca0b5274d681301e795ea5396c.gif)
Quartus出租车计价器VHDL计费器
名称:出租车计价器VHDL计费器 软件:Quartus 语言:VHDL 要求: 启动键start表示汽车启动,起步价7元,同时路程开始计数,停止键stop表示熄火,车费和路程均为0,当暂停键pa…...
![](https://img-blog.csdnimg.cn/84ceb1af8b364c1caf8f641a9a9637a3.png)
浅谈单元测试:测试和自动化中的利用
【软件测试面试突击班】如何逼自己一周刷完软件测试八股文教程,刷完面试就稳了,你也可以当高薪软件测试工程师(自动化测试) 浅谈单元测试是一件棘手的事情。我很确定测试人员在某个时候会抱怨开发人员没有正确地进行单元测试&…...
![](https://www.ngui.cc/images/no-images.jpg)
深度详解Java序列化
大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…...
![](https://www.ngui.cc/images/no-images.jpg)
Linux下的网络编程——B/S模型HTTP(四)
前言: HTTP是基于B/S架构进行通信的,而HTTP的服务器端实现程序有httpd、nginx等,其客户端的实现程序主要是Web浏览器,例如Firefox、Internet Explorer、Google Chrome、Safari、Opera等,此外,客户端的命令…...
![](https://img-blog.csdnimg.cn/205b782db9254a2292f3307a16d1dd84.png)
Go语言入门篇
目录 一、基础数据类型 1.1 变量的定义方式 1.2 用%T输出变量的类型 二、复合数据类型 2.1 数组 2.1.2、数组的遍历 2.1.3 数组传参 2.2. 切片slice 2.2.1. 初始化切片 2.2.2. append向切片中追加元素 2.2.3. 切片的截取 2.3. map 2.3.1. map初始化 2.3.2. 添加和…...
![](https://img-blog.csdnimg.cn/49ed4c4348a34ff6a43a8eeb3b0c3162.png)
基于springboot+vue的青年公寓服务平台
博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容:毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…...
![](https://www.ngui.cc/images/no-images.jpg)
Spring-ImportSelector接口功能介绍
ImportSelector接口是至spring中导入内部类或者外部类的核心接口,只需要其定义的方法内返回需要创建bean的class字符串就好了,比如:当我们引入一个外部share包,我们拿到里面的Class返回出去,就能得到这个bean,是多么神…...
![](https://img-blog.csdnimg.cn/53b7c293b2f94eb293caae3b0317ce6e.png)
YOLOv5如何训练自己的数据集
文章目录 前言1、数据标注说明2、定义自己模型文件3、训练模型4、参考文献 前言 本文主要介绍如何利用YOLOv5训练自己的数据集 1、数据标注说明 以生活垃圾数据集为例子 生活垃圾数据集(YOLO版)点击这里直接下载本文生活垃圾数据集 生活垃圾数据集组成&…...
![](https://img-blog.csdnimg.cn/3b3e03b17a1c481ca3478f97aff593e1.png)
李航老师《统计学习方法》第1章阅读笔记
1.1 统计学习 统计学习的特点 统计学习:计算机基于数据构建概率统计模型并运用模型对数据进行预测与分析 现在人们提及机器学习时,往往指统计机器学习,所以可以认为本书介绍的是机器学习方法 统计学习的对象 统计学习研究的对象是数据(data)…...
![](https://img-blog.csdnimg.cn/img_convert/5b51d24960a4533a68f4ed38059b1ca1.png)
基于微信小程序的背单词学习激励系统设计与实现(源码+lw+部署文档+讲解等)
文章目录 前言用户微信端的主要功能有:管理员的主要功能有:具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序(小蔡coding)有保障的售后福利 代码参考源码获取 前言 💗博主介绍:✌全网粉…...
![](https://img-blog.csdnimg.cn/d79ca58777464d128bfeb5f1f11320e1.png)
VScode调试复杂C/C++项目
以前都是用的VScode调试c/cpp的单个文件的编译和执行, 但是一遇到大型项目一般就用gdb了, gdb的调试效率和VScode差距还是比较大的, 但最近发现VScode其实也能调试复杂的cpp项目, 所以记录一下. 首先明确一下几点: 首先cpp文件需要经过编译, 生成可执行文件, 然后通过运行/调…...
![](https://img-blog.csdnimg.cn/img_convert/a08571f736dc04b5bef51cd09e8e3a0a.gif#pic_center)
【Hash表】字母异位词分组-力扣 49 题
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…...
![](https://img-blog.csdnimg.cn/631ca6ecb5214151a9843065aaa4669c.png)
展示日志log4.properties
log4.properties 1.log4.properties 此时文件主要用于展示日志的输出的级别的信息。 # Set root category priority to INFO and its only appender to CONSOLE. #log4j.rootCategoryINFO, CONSOLE debug info warn error fatal log4j.rootCategoryinfo, CONSO…...
![](https://img-blog.csdnimg.cn/581de8804ad843ce8f9a4cff02c9225a.png)
基于PLE结合卡尔曼滤波的RSSI定位算法matlab仿真
目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 ............................................................... for Num_xb Num_xb2Num_…...
![](https://www.ngui.cc/images/no-images.jpg)
uniapp项目实践总结(十九)版本更新和热更新实现方法
导语:当一个 APP 应用开发完成以后,就要上架应用商店,但有时候修改一些小问题或者推出一些活动,又不想频繁地提交应用商店审核,那么就可以使用应用内更新功能来进行应用的版本升级更新或热更新,下面就介绍一下实现的方法。 目录 准备工作原理分析实战演练案例展示准备工作…...
一起学数据结构(8)——二叉树中堆的代码实现
在上篇文章中提到,提到了二叉树中一种特殊的结构——完全二叉树。对于完全二叉树,在存储时,适合使用顺序存储。对于非完全二叉树,适合用链式存储。本文将给出完全二叉树的顺序结构以及相关的代码实现: 1. 二叉树的结构…...
![](https://img-blog.csdnimg.cn/dc40b1fe379a454899b5b64093898e50.png)
Linux环境变量配置说明(配置jdk为例-摘录自尚硅谷技术文档)
配置环境变量的不同方法 Linux的环境变量可在多个文件中配置,如/etc/profile,/etc/profile.d/.sh,~/.bashrc,~/.bash_profile等,下面说明上述几个文件之间的关系和区别。 bash的运行模式可分为login shell和non-login shell。 例…...
![](https://www.ngui.cc/images/no-images.jpg)
idea常用插件笔记
文章目录 Free Mybatis Toollombok插件idea插件导出导入 idea提供了很多好用的插件,之前都装了的,但是换了下电脑,什么都没了,所以记录下方便以后用。 Free Mybatis Tool mybatis跳转插件,再也不用费力的找xml了。 l…...
![](https://www.oschina.net/img/hot3.png)
企业门户定制网站建设公司/seo 0xu
2019独角兽企业重金招聘Python工程师标准>>> 一、初始化本地git项目 1. git init 2. git add -A 3. git commit -m "初始化仓库"####二、在github上创建项目 如: https://github.com/zkj/easyjava.git三、将github上的项目pull下来 git pull o…...
![](https://img2018.cnblogs.com/blog/1606021/201905/1606021-20190530123306429-198187998.png)
wordpress隐藏音乐/seo如何优化排名
上一篇博客我和大家分享了vue render函数的基础使用 这篇博客我们来简单讲一讲render函数他是怎么实现得 先来一张官方得图 在实例初始化得时候,html通过render函数编译生成了一个虚拟dom,视图层会根据虚拟dom生成一个真实dom 然后如果响应数据发生变化得…...
![](https://img-blog.csdnimg.cn/img_convert/e90cc3872519230cae4950d58ffa7555.gif)
淘宝客网站域名/定制网站开发公司
很多研新们在刚开始接触文章写作时总是不在意参考文献的标注,总觉得Conclusion写完后就算大功告成,然后参考文献草草标注。事实上,参考文献的标注能直接体现学术规范与否,甚至能直接关系到作者的学术诚信问题。那么,到…...
![](https://img-blog.csdnimg.cn/ffcc3ca81fa1467f89caa1fcaccc9914.png#pic_center)
响应式网站文字大小/广州百度推广外包
爬虫JS逆向之空中网模拟登录 网址:空中网 开发者工具抓包逆向分析 通过浏览器自带的开发者工具,我们可以点击登录按钮,抓到登录有关的包,对比其中的参数,发现加密的参数是password,也就是我们的密码。 找到加密的地…...
![](https://img-blog.csdnimg.cn/3b7a31bd46ca4848b099bbad3b9cd594.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAc3h4YmxvZ3M=,size_19,color_FFFFFF,t_70,g_se,x_16)
营销型 网站开发/站长工具ip地址查询
【Abstract】 基于单幅深度图像的手部姿态估计是计算机视觉和人机交互领域的一个重要课题。尽管在卷积神经网络的推动下,这一领域最近取得了进展,但精确的手部姿态估计仍然是一个具有挑战性的问题。本文提出了一种新的姿态引导的结构区域集成网络(Pose-…...
![](/images/no-images.jpg)
大学生做网站怎么赚钱/杭州千锋教育地址
作者很直男,兄弟们直接复制代码看效果吧!!! 一些小细节,大家可以自己动手修改。 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"…...