99网
您的当前位置:首页UART学习之路(四)VerilogHDL实现的简单UART,VIVADO下完成仿真

UART学习之路(四)VerilogHDL实现的简单UART,VIVADO下完成仿真

来源:99网
UART学习之路(四)VerilogHDL实现的简单UART,VIVADO下完成仿真

⽤VerilogHDL实现UART并完成仿真就算是对UART整个技术有了全⾯的理解,同时也算是Verilog⼊门了。整个UART分为3部分完成,发送模块

(Transmitter),接收模块(Receiver)和波特率发⽣模块(BuadRateGenerator)。发送模块相⽐于接收模块要简单⼀些,主要功能就是每1/9600s发送1bit的数据,接收模块就在采样时钟下完成数据的采样,波特率发送模块就是产⽣对应的波特率。UART的基本电路模型可以看UART学习之路(⼆) 基本时序介绍,当中对UART进⾏了完整的电路建模。1.发送模块模块代码:

`timescale 1ns / 1ps

module UART_TRANSMITTER(input [7:0] DataIn,//并⾏数据输⼊

input baud16x,TxEn,rstn,//baud16x=波特率×16,TxEn是并⾏数据装⼊使能信号,rstn复位信号output reg DataOut,//串⾏数据输出

output reg TxBusy// 说明串⼝正忙的信号,检测其下降沿就可以判断是否可以装⼊新的数据 );

reg [7:0] DataInReg;//输⼊数据寄存器 reg [7:0] cnt;//计数器

reg cnt_start;//计数开始标志位 reg TransEnIn0;//当前状态采样 reg TransEnIn1;//上⼀个状态采样

wire pos_en;

//采TxEn的上升沿

always@(posedge baud16x or negedge rstn) if(!rstn)begin

TransEnIn0<=1'b0; TransEnIn1<=1'b0; end

else begin

TransEnIn0 <= TxEn;//now

TransEnIn1 <= TransEnIn0;// delay end

// Description // I0 I1

// 0 0 : 1&0=0 // 1 0 : 1&1=1 // 1 1 : 1&0=0 // 0 1 :0&0=0

assign pos_en = TransEnIn0 & !TransEnIn1; //数据装⼊

always@(negedge rstn or posedge baud16x) if(!rstn)begin

DataInReg <= 8'd0; TxBusy <= 1'b0; end

else if(pos_en == 1'b1)begin DataInReg <= DataIn; cnt_start <= 1'b1; TxBusy <= 1'b1; end

else if(cnt >= 8'd160) begin cnt_start <= 1'b0; TxBusy <= 1'b0; end

//计数

always@(posedge baud16x or negedge rstn) if(!rstn)

cnt <= 8'd0;

else if(cnt_start == 1'b1) cnt <= cnt + 1'b1; else cnt <= 8'd0;

//UART 发送

always@(posedge baud16x or negedge rstn) if(!rstn)

DataOut <=1'b1;

else if(cnt_start == 1'b1) case(cnt)

8'd0:DataOut <= 1'b0;

8'd16:DataOut <= DataInReg[0]; 8'd32:DataOut <= DataInReg[1]; 8'd48:DataOut <= DataInReg[2]; 8'd:DataOut <= DataInReg[3]; 8'd80:DataOut <= DataInReg[4]; 8'd96:DataOut <= DataInReg[5]; 8'd112:DataOut <= DataInReg[6]; 8'd128:DataOut <= DataInReg[7]; 8'd144:DataOut <= 1'b1; endcase

else DataOut <= 1'b1;endmodule

TestBench

`timescale 1ns / 1ps

module TESTBENCH_UART_TRANSMITTER( );

parameter CLKPERIOD = 100;

reg [7:0] TempData; reg clk,en,nrst;

wire TxDataOut; wire TxOverFlag;

initial begin clk = 0; en = 0; nrst = 0;

TempData = 8'd0; end

//clk generate

always #(CLKPERIOD/2) clk = ~clk;

//复位

initial begin

#CLKPERIOD nrst = 1; end

//数据使能 initial begin

#CLKPERIOD en = 1;

TempData = 8'b1010_0101; #CLKPERIOD en = 0;

#(CLKPERIOD*200) en = 1; TempData = 8'b0101_1010; #CLKPERIOD en = 0; end

//version 1.0 2018-12-4 //module initial

UART_TRANSMITTER U1(//input .baud16x(clk), .TxEn(en), .rstn(nrst),

.DataIn(TempData), //output

.DataOut(TxDataOut), .TxBusy(TxOverFlag));endmodule仿真结果

分析:

需要发送的并⾏数据存储在TempData⾥⾯,第⼀次发送10100101,发送起始位bit0=0,之后是数据位bit1,bit2,bit3,bit4,bit5,bit6,bit7,bit8,最后是停⽌位bit9=1。第⼆次发送01011010,结果同第⼀次发送。第⼀张图⽚,当en信号输⼊由第变⾼,产⽣上升沿后,说明待发送的数据已经装⼊发送寄存器了,发送端开始发送,⾸先发送的是LSB =1,最后发送的是MSB=1。第⼆张图⽚中,同第⼀张图⽚的过程,⾸先发送LSB=0,最后发送MSB=0。TxOverFlag表明发送端的忙状态,⾼电平是忙,低电平是闲,通关检测该信号的下降沿可以判断发送是否完成。

2.接收模块模块代码

`timescale 1ns / 1ps/*2018-9-21 verson 1.0 baud rate = 9600 bit/s

1 bit takes 1/9600s = 104.167 us ~= 104167nsinput frequency of clk is 100Mhz*/

// baud rate T_bdr N System clock = 50M// 9600 104167ns 104167/System_clk_priod 5208-1// 19200 52083ns 52083/System_clk_priod 2604-1// 38400 26041ns 26041/System_clk_priod 1302-1// 57600 17361ns 17361/System_clk_priod 868-1// 115200 8680ns 8680/System_clk_priod 434-1 //

//Input clock is 16x bound rate, the first sample data is at 24, and the next sample data is at 40// 24,40,56,72,88,104,120,136,152module UART_RECEIVE(input baud16x,rstn,input recv,

output reg [7:0] rdata,output reg recv_ready );

reg recvIn0;//下降沿捕捉,当前时刻值 reg recvIn1;//下降沿捕捉,上⼀个时刻值

reg[7:0] cnt_bit; reg RecvNeFlag; //起始位获取

always@(posedge baud16x or negedge rstn) if(!rstn) begin recvIn0 <= 1'b1; recvIn1 <= 1'b1; end

else begin /*下降沿采样*/

recvIn0 <= recv;//当前时刻的recv给recvIn0

recvIn1 <= recvIn0;//前⼀个时刻的recv给recvIn1 end

wire neg_rec;

assign neg_rec = !recvIn0 && recvIn1; /*下降沿判断,打开接收功能标志*/

always@(negedge rstn or posedge baud16x) if(!rstn) begin

RecvNeFlag <= 1'b0; recv_ready <= 1'b1; end

else if(neg_rec == 1'b1)begin RecvNeFlag <= 1'b1; recv_ready <= 1'b0; end

else if(cnt_bit == 8'd152)begin RecvNeFlag <= 1'b0; recv_ready <= 1'b1; end

//bit计数

always@(posedge baud16x or negedge rstn) if(!rstn)

cnt_bit <= 1'b0;

else if(RecvNeFlag == 1'b1) cnt_bit <= cnt_bit + 1'b1; else cnt_bit <= 8'd0;

//采样接收

always@(posedge baud16x or negedge rstn) if(!rstn)

rdata <= 8'b0000_0000; else case(cnt_bit)

8'd24:rdata[0] <= recv;//数据位第1位 8'd40:rdata[1] <= recv; 8'd56:rdata[2] <= recv;

8'd72:rdata[3] <= recv; 8'd88:rdata[4] <= recv; 8'd104:rdata[5] <= recv; 8'd120:rdata[6] <= recv;

8'd136:rdata[7] <= recv;//数据位第8位 endcaseendmodule2.TestBench

`timescale 1ns / 1ps

module TESTBENCH_UART_RECEIVE( );

parameter CLKPERIOD=100;

reg GCLK; reg nrst; reg DataIn;

wire [7:0] recv_data; wire RecvDoneFlag; //初始化 initial begin nrst = 0; GCLK = 0; DataIn = 1; end

//⽣成时钟激励

always #(CLKPERIOD/2) GCLK = ~GCLK;

//复位使能

initial #(CLKPERIOD*10) nrst = 1;

//模拟发送数据 initial begin

#(CLKPERIOD*49) DataIn = 0; #(CLKPERIOD*16) DataIn = 1; #(CLKPERIOD*16) DataIn = 0; #(CLKPERIOD*16) DataIn = 1; #(CLKPERIOD*16) DataIn = 0; #(CLKPERIOD*16) DataIn = 1; #(CLKPERIOD*16) DataIn = 0; #(CLKPERIOD*16) DataIn = 0; #(CLKPERIOD*16) DataIn = 1; #(CLKPERIOD*16) DataIn = 1; end

UART_RECEIVE U1(//input .baud16x(GCLK), .rstn(nrst), .recv(DataIn), //output

.rdata(recv_data),

.recv_ready(RecvDoneFlag));endmodule3.仿真结果

分析:

模块复位输出8'b0000_0000,接收采样在输⼊的中点处。串⾏输⼊数据,转换成并⾏的数据。第⼀张图发送的数据是8'b1111_1111,在接受端依次接收为8'b0000_0001,8'b0000_0011,…… ……,8'b1111_1111。接收到起始位后RecvDoneFlag信号由⾼拉低,表明接收端正忙,处于接收状态,这个地⽅信号名字打错了,后⾯懒得改了。接收完成后,RecvDoneFlag将由低拉⾼,判断接收完成只需要检测该信号的上升沿就⾏。第⼆张图发送的数据是

8'b1001_0101,过程如同第⼀张图。观察时序图,发现recv_data数据变化的节点并不是8个,⽽是4个,这是因为模块默认输出的信号是8'b0000_0000,只有出现1的地⽅数据才会发⽣跳转(出现采样的痕迹),8’b1001_0101中有4个1,所以只有4个。

3.波特率发⽣模块实际上就是对100Mhz的时钟进⾏分频,分成BaudRate*16的时钟提供给发送和接收模块。4.参考代码:https://github.com/jamieiles/uart ,GitHub上别⼈的另外⼀种实现⽅式。

因篇幅问题不能全部显示,请点此查看更多更全内容