两个基础的串口实验
两个基础的串口实验
一.说明
这两个实验都是根据特权的深入浅出玩转FPGA上的两个实例做的,分别是笔记16中的串口通信实验和笔记17中的基于FIFO的串口发送机设计。
二.实验过程
2.1 实验1 串口通信实验
2.1.1基本过程
这个实验的基本过程是PC机首先通过串口向FPGA发送数据,FPGA每接收到一个单位的数据,就马上再通过串口向PC机发回接收到的数据,借助于串口调试助手,可以观察发送的数据和接收的数据是否相同。
2.1.2整体和子模块功能分析
这个串口发送接收系统可分为4个子模块,分别是串口接收模块、串口接收波特率控制模块、串口发送模块、串口发送波特率控制模块。其中,串口接收模块根据串口帧格式将PC机向FPGA发送的串口数据依次读取下来,完成串转并的操作,将串口接收线上的数据存入一个8位的寄存器中,并且,串口接收模块会给串口接收波特率控制模块提供相应的使能信号,使得接收波特率控制模块会给串口接收模块反馈相应的满足一定时序要求的串口数据采样信号,最后,串口接收模块还会给串口发送模块提供一个发送使能信号(实际上是表示接收完成的一个信号),使得在FPGA完整地接收到一个单位的数据后,串口发送模块再将数据送出去,而在其他时间,发送使能信号无效时,串口接收模块将持续发送高电平信号;串口接收波特率控制模块根据串口接收模块提供的使能信号,再根据指定的波特率,输出满足波特率要求的采样信号,将这个采样信号输出给串口接收模块,从而串口模块能够从串口接收数据线上取得正确的数据锁存起来;串口发送模块在FPGA接收到一个完整的单位数据时(串口发送模块通过串口接收模块发出的使能信号知道这一点),再按照串口数据帧格式将这个数据发送出去,并且,和接收模块类似,要使发送模块发送的数据满足串口数据帧格式,必须需要一个控制信号,这个信号由串口发送波特率控制模块提供,串口发送模块也必须给这个发送波特率控制模块提供相应的使能信号,这个使能信号在串口发送时期使能,其余时间均无效。
需要注意的是,上面的串口发送波特率控制模块和串口接收波特率控制模块在具体实现的时候,都是用同一个Verilog模块进行例化的,但是,进行例化时,前面提到的那个使能信号是不同的,并且它们输出的数据的流向也是不同的,所以,实际上,这是两个完全独立的模块,这种方法称为逻辑复制。
2.1.3Verilog实现代码
(1)串口接收模块
uart_rx.v
`timescale 1ns / 1psmodule uart_rx( //串口接收模块 clk,rst_n, rs232_rx,clk_bps, bps_start,rx_int,rx_data );input clk; //50MHz主时钟input rst_n; //低电平复位信号input rs232_rx; //RS232接收数据信号input clk_bps; //此时clk_bps的高电平为接收数据的中间采样点output bps_start; //接收到数据后,波特率时钟启动信号置位output[7:0] rx_data; //接收数据寄存器,保存直至下一个数据来到 output rx_int; //接收数据中断信号,接收到数据期间始终为高电平,传送给 //串口发送模块,使得串口正在进行接收数据的时候,发送模块不工作, //避免了一个完整的数据(1位起始位、8位数据位、1位停止位)还没有 //接收完全时,发送模块就已经将不正确的数据传送出去//-----------------------------------------------------------------------------//边沿检测程序,检测rs232_rx信号,即串口线上传向FPGA的信号的下降沿//这个下降沿信号表示一个串口数据帧的开始reg rs232_rx0,rs232_rx1,rs232_rx2,rs232_rx3; //接收数据寄存器,滤波用wire neg_rs232_rx; //表示数据线接收到下降沿always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin rs232_rx0 <= 1'b0; rs232_rx1 <= 1'b0; rs232_rx2 <= 1'b0; rs232_rx3 <= 1'b0; end else begin rs232_rx0 <= rs232_rx; rs232_rx1 <= rs232_rx0; rs232_rx2 <= rs232_rx1; rs232_rx3 <= rs232_rx2; endend//下面的下降沿检测可以滤掉<20ns-40ns的毛刺(包括高脉冲和低脉冲毛刺),//这里就是用资源换稳定(前提是我们对时间要求不是那么苛刻,因为输入信号打了好几拍) //(当然我们的有效低脉冲信号肯定是远远大于40ns的)assign neg_rs232_rx = rs232_rx3 & rs232_rx2 & ~rs232_rx1 & ~rs232_rx0; //接收到下降沿后neg_rs232_rx置高一个时钟周期//----------------------------------------------------------------------------reg bps_start_r;assign bps_start = bps_start_r;reg[3:0] num; //移位次数reg rx_int; //接收数据中断信号,接收到数据期间始终为高电平always @ (posedge clk or negedge rst_n) if(!rst_n) begin bps_start_r <= 1'b0; rx_int <= 1'b0; end else if(neg_rs232_rx) begin //接收到串口接收线rs232_rx的下降沿标志信号 bps_start_r <= 1'b1; //启动串口准备数据接收 rx_int <= 1'b1; //接收数据中断信号使能 end else if(num==4'd12) begin //接收完有用数据信息 bps_start_r <= 1'b0; //数据接收完毕,释放波特率启动信号 rx_int <= 1'b0; //接收数据中断信号关闭 endreg[7:0] rx_data_r; //串口接收数据寄存器,保存直至下一个数据来到//-----------------------------------------------------------------------------assign rx_data = rx_data_r; reg[7:0] rx_temp_data; //当前接收数据寄存器always @ (posedge clk or negedge rst_n) if(!rst_n) begin rx_temp_data <= 8'd0; num <= 4'd0; rx_data_r <= 8'd0; end //else if(rx_int) //特权的代码中有这一句话,意思是在处于接收状态时 //(rx_int=1表示处于接收状态)才进行下面的处理,实际上 //只有处于接收状态时,才有相应的clk_bps信号,所以 //实际上不需要对rx_int信号进行判断 else begin //接收数据处理 if(clk_bps) begin //读取并保存数据,接收数据为一个起始位,8bit数据,1或2个结束位 num <= num+1'b1; case (num) 4'd1: rx_temp_data[0] <= rs232_rx; //锁存第0bit 4'd2: rx_temp_data[1] <= rs232_rx; //锁存第1bit 4'd3: rx_temp_data[2] <= rs232_rx; //锁存第2bit 4'd4: rx_temp_data[3] <= rs232_rx; //锁存第3bit 4'd5: rx_temp_data[4] <= rs232_rx; //锁存第4bit 4'd6: rx_temp_data[5] <= rs232_rx; //锁存第5bit 4'd7: rx_temp_data[6] <= rs232_rx; //锁存第6bit 4'd8: rx_temp_data[7] <= rs232_rx; //锁存第7bit default: ; endcase end else if(num == 4'd12) begin //我们的标准接收模式下只有1+8+1(2)=11bit的有效数据 num <= 4'd0; //接收到STOP位后结束,num清零 rx_data_r <= rx_temp_data;//把数据锁存到数据寄存器rx_data中 end endendmodule
(2)波特率控制模块
speed_select.v
`timescale 1ns / 1psmodule speed_select( clk,rst_n, bps_start,clk_bps );input clk; // 50MHz主时钟input rst_n; //低电平复位信号input bps_start; //接收到数据后,波特率时钟启动信号置位 //或者开始发送数据时,波特率时钟启动信号置位output clk_bps; // clk_bps的高电平为接收或者发送数据位的中间采样点 //-----------------------------------------------------------------------------//以下波特率分频计数值可参照上面的参数进行更改//计算方法://以9600bps为例,9600bps表示每秒9600bit,则传输1bit需要10^9/9600=104166ns,//所以再我们使用50MHz的时钟频率的前提下,需要104166/20=5208个时钟周期//5208个时钟周期内传送了1bit位,则在中间的时刻处,进行取样(接收模块)或者//将中间时刻作为发送数据的数据改变点(发送模块) `define BPS_PARA 5207//波特率为9600时的分频计数值`define BPS_PARA_2 2603//波特率为9600时的分频计数值的一半,用于数据采样//-----------------------------------------------------------------------------reg[12:0] cnt; //分频计数reg clk_bps_r; //波特率时钟寄存器//-----------------------------------------------------------------------------//reg[2:0] uart_ctrl;//uart波特率选择寄存器//特权的代码中有这一句,但是一直没有用到//这里将其去掉,对我们的程序没有影响//-----------------------------------------------------------------------------always @ (posedge clk or negedge rst_n) if(!rst_n) cnt <= 13'd0; else if((cnt == `BPS_PARA) || !bps_start) cnt <= 13'd0; //波特率计数清零 else cnt <= cnt+1'b1; //波特率时钟计数启动always @ (posedge clk or negedge rst_n) if(!rst_n) clk_bps_r <= 1'b0; else if(cnt == `BPS_PARA_2) clk_bps_r <= 1'b1; // clk_bps_r高电平为接收数据位的中间采样点,同时也作为发送数据的数据改变点 else clk_bps_r <= 1'b0;assign clk_bps = clk_bps_r;endmodule
(3)串口发送模块
uart_tx.v
`timescale 1ns / 1psmodule uart_tx( clk,rst_n, rx_data,rx_int,rs232_tx, clk_bps,bps_start ); input clk; // 50MHz主时钟input rst_n; //低电平复位信号input clk_bps; // clk_bps_r高电平作为发送数据的数据改变点input[7:0] rx_data; //接收数据寄存器input rx_int; output rs232_tx; // RS232发送数据信号output bps_start; //接收或者要发送数据,波特率时钟启动信号置位//------------------------------------------------------------------------------//边沿检测,检测rx_int信号的下降沿,rx_int信号的下降沿表示接收完全reg rx_int0,rx_int1,rx_int2; //rx_int信号寄存器,捕捉下降沿滤波用wire neg_rx_int; // rx_int下降沿标志位always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin rx_int0 <= 1'b0; rx_int1 <= 1'b0; rx_int2 <= 1'b0; end else begin rx_int0 <= rx_int; rx_int1 <= rx_int0; rx_int2 <= rx_int1; endend//捕捉到下降沿后,neg_rx_int拉高保持一个主时钟周期assign neg_rx_int = ~rx_int1 & rx_int2;//------------------------------------------------------------------------------reg[7:0] tx_data; //待发送数据的寄存器reg bps_start_r;assign bps_start = bps_start_r;//------------------------------------------------------------------------------//reg tx_en; //发送数据使能信号,高有效//接收数据中断信号,接收到数据期间始终为高电平,在该模块中利用它的下降沿来启动串口//发送数据实际上这个信号是不需要的,因为在串口发送数据模块,clk_bps信号会给发送//数据的always模块提供一个时钟周期宽的高电平信号,在这个时钟周期内,会发送出去1bit//的数据信息或者控制信息(起始位、停止位)//而在其余时间(不发送的时间),由于串口发送数据模块给发送波特率控制信号提供//的计数使能信号bps_start一直是无效,所以clk_bps一直保持低电平//所以此时不会发送数据,而不需要专门用一个tx_en信号进行控制//------------------------------------------------------------------------------reg[3:0] num;always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin bps_start_r <= 1'b0; //tx_en <= 1'b0; tx_data <= 8'd0; end else if(neg_rx_int) begin //接收数据完毕,准备把接收到的数据发回去 bps_start_r <= 1'b1; tx_data <= rx_data; //把接收到的数据存入发送数据寄存器 //tx_en <= 1'b1; //进入发送数据状态中 end else if(num==4'd11) begin //数据发送完成,复位 bps_start_r <= 1'b0; //tx_en <= 1'b0; endendreg rs232_tx_r;assign rs232_tx = rs232_tx_r;always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin num <= 4'd0; rs232_tx_r <= 1'b1; end //else if(tx_en) begin else begin if(clk_bps) begin num <= num+1'b1; case (num) 4'd0: rs232_tx_r <= 1'b0; //发送起始位 4'd1: rs232_tx_r <= tx_data[0]; //发送bit0 4'd2: rs232_tx_r <= tx_data[1]; //发送bit1 4'd3: rs232_tx_r <= tx_data[2]; //发送bit2 4'd4: rs232_tx_r <= tx_data[3]; //发送bit3 4'd5: rs232_tx_r <= tx_data[4]; //发送bit4 4'd6: rs232_tx_r <= tx_data[5]; //发送bit5 4'd7: rs232_tx_r <= tx_data[6]; //发送bit6 4'd8: rs232_tx_r <= tx_data[7]; //发送bit7 4'd9: rs232_tx_r <= 1'b1; //发送结束位 default: rs232_tx_r <= 1'b1; endcase end else if(num==4'd11) num <= 4'd0; //复位 endendendmodule
(4)顶层模块
uart_top.v
`timescale 1ns / 1psmodule uart_top(clk,rst_n,rs232_rx,rs232_tx);input clk; // 50MHz主时钟input rst_n; //低电平复位信号input rs232_rx; // RS232接收数据信号output rs232_tx; // RS232发送数据信号wire bps_start1,bps_start2; //接收到数据后,波特率时钟启动信号置位wire clk_bps1,clk_bps2; // clk_bps_r高电平为接收数据位的中间采样点,同时也作为发送数据的数据改变点 wire[7:0] rx_data; //接收数据寄存器,保存直至下一个数据来到wire rx_int; //接收数据中断信号,接收到数据期间始终为高电平//----------------------------------------------------//下面的四个模块中,speed_rx和speed_tx是两个完全独立的硬件模块,可称之为逻辑复制//(不是资源共享,和软件中的同一个子程序调用不能混为一谈)////////////////////////////////////////////speed_select speed_rx( .clk(clk), //波特率选择模块 .rst_n(rst_n), .bps_start(bps_start1), .clk_bps(clk_bps1) );uart_rx uart_rx1( .clk(clk), //接收数据模块 .rst_n(rst_n), .rs232_rx(rs232_rx), .rx_data(rx_data), .rx_int(rx_int), .clk_bps(clk_bps1), .bps_start(bps_start1) );/////////////////////////////////////////// speed_select speed_tx( .clk(clk), //波特率选择模块 .rst_n(rst_n), .bps_start(bps_start2), .clk_bps(clk_bps2) );uart_tx uart_tx2( .clk(clk), //发送数据模块 .rst_n(rst_n), .rx_data(rx_data), .rx_int(rx_int), .rs232_tx(rs232_tx), .clk_bps(clk_bps2), .bps_start(bps_start2) );endmodule
2.1.4测试程序的设计
这里编写相应的testbench文件对这个串口通信功能进行功能仿真,testbench的核心是,模拟PC机按照串口帧格式给FPGA提供输入串口信号,这里就简单地模拟PC机每当上一次发送完成后隔1ms再向FPGA发送下一个数据,并且这里发送的数据就简单得用循环发送两个数据0xA9和0xD4来模拟。
Testbench的Verilog实现代码如下:
Test_uart_top.vt
`timescale 1 ns/ 1 psmodule test_uart_top();reg clk;reg rs232_rx;reg rst_n;// wires wire rs232_tx;// assign statements (if any) uart_top i1 (// port map - connection between master ports and signals/registers .clk(clk), .rs232_rx(rs232_rx), .rs232_tx(rs232_tx), .rst_n(rst_n));initial clk = 0;always #10 clk = ~clk;initialbegin rst_n = 0; #60 rst_n = 1;end//这里模拟PC机循环发出两字节的数据前一个数据是0xA9(8'b1010_1001),//后一个数据是0xD4(8'b1101_0100)//数据的格式采用1字节起始位(0)、8字节数据、1字节停止位(1)initial begin #100 rs232_rx = 1; forever begin //1ms时开始发送第一个数据 #1000000 rs232_rx = 0; //第一个数据起始位--->0 #104166 rs232_rx = 1; //第一个数据0bit--->1 #104166 rs232_rx = 0; //第一个数据1bit--->0 #104166 rs232_rx = 0; //第一个数据2bit--->0 #104166 rs232_rx = 1; //第一个数据3bit--->1 #104166 rs232_rx = 0; //第一个数据4bit--->0 #104166 rs232_rx = 1; //第一个数据5bit--->1 #104166 rs232_rx = 0; //第一个数据6bit--->0 #104166 rs232_rx = 1; //第一个数据7bit--->1 #104166 rs232_rx = 1; //第一个数据停止位--->1 #104166 ; //停止位的持续时间 //1ms后开始发送第二个数据 #1000000 rs232_rx = 0; //第二个数据起始位--->0 #104166 rs232_rx = 0; //第二个数据0bit--->0 #104166 rs232_rx = 0; //第二个数据1bit--->0 #104166 rs232_rx = 1; //第二个数据2bit--->1 #104166 rs232_rx = 0; //第二个数据3bit--->0 #104166 rs232_rx = 1; //第二个数据4bit--->1 #104166 rs232_rx = 0; //第二个数据5bit--->0 #104166 rs232_rx = 1; //第二个数据6bit--->1 #104166 rs232_rx = 1; //第二个数据7bit--->1 #104166 rs232_rx = 1; //第二个数据停止位--->1 #104166 ; //停止位的持续时间 endendinitialbegin #100000000 $stop;endendmodule
功能仿真的波形如下:
从图中大概能够看出,串口发送线(这里发送和接收均是相对于FPGA而言)上的数据实际上就是串口接收线上数据的一个延时。
将其中前10ms的波形放大,如下所示:
2.1.5板级验证结果
2.1.6实验总结
(1)首先,开发板的原理图上,串口芯片那一部分有问题,如下所示:
实际上,进行测量后,I/O160接到的是MAX232的9脚,I/O163接到的是MAX232的10脚。
(2)和特权同学代码的两个不同之处
① 特权同学在波特率控制模块中,有这么一句:
reg[2:0]uart_ctrl;
定义了uart_ctrl这么一个信号,按照特权同学的注释,是uart波特率选择寄存器,但是后面的程序中一直没有用到,所以在程序中将其去掉。
②在串口接收和串口发送模块中,特权同学均用了一个信号用来表示正在接收数据和正在发送数据,这两个信号分别是rx_int信号和tx_en信号,实际上,在接收和发送具体的每个bit位时,不需要用这样的信号进行控制,因为,当系统正处在接收状态时,clk_bps会在相应的采样点提供一个时钟周期宽度的高电平,在这个点上,接收模块会对发向FPGA的串口线上的数据进行采样,而在其它时间(不在接收时),clk_bps一直保持低电平状态,接收模块不采样,系统处在发送状态时同理,clk_bps也有这个规律,进一步,考虑到rx_int信号是串口接收模块uart_rx的输出信号,用来给串口发送模块uart_tx提供接收完成的信息,所以是必须的,但是在uart_rx模块中,不需要rx_int信号对接收过程进行控制,而tx_en信号则完全可以删去。
下面以仿真的结果进行说明:
上图是对特权同学的Verilog描叙代码进行功能仿真的结果,从中可以看出,在rx_int信号和tx_en信号有效的期间,相应的clk_bps均有相应的高电平采样信号或高电平使能信号,而在rx_int信号和tx_en信号无效的期间,相应的clk_bps均保持为0,所以在相应的代码中,去掉了这两个信号对接收和发送过程的控制语句(从前面的代码注释中可以看出去掉了哪些语句,实际上主要是去掉了两个elseif语句)。
而且从上面的图中,可以看出,改变前后其它信号的仿真结果完全相同。
(3)有关特权同学编写的边沿检测程序的说明
最早看到这种边沿检测程序是在特权同学的深入浅出FPGA书上的键盘消抖实验中,当时我是刚刚从同学那边借来了一块FPGA开发板,开始动手做FPGA方面的实验,这个程序当时就看了好久才懂,后来在用FPGA做其它的一些实验时,需要用到边沿检测时,都是套用那个键盘程序。。。。下面对这个串口通信实验中的边沿检测部分进行说明。这一部分仿真结果:
从图中可以看出,在100ns处,串口接收到的信号rs232_rx出现了第一个下降沿,结果,在130ns处,neg_rs232_rx信号出现了宽度是1个时钟周期的高有效电平,这个信号用来作为串口开始接收数据的标志,这段代码实现下降沿检测的原理是,rs232_rx、rs232_rx0、rs232_rx1、rs232_rx2、rs232_rx3这四个信号,每一个信号分别是前一个信号的一个时钟周期的延时,所以rs232_rx0表示的是后面的数据,rs232_rx3表示的是最前面的数据,所以用这样一个语句:
assignneg_rs232_rx=rs232_rx3&rs232_rx2&~rs232_rx1&~rs232_rx0;
表示当rs232_rx上的数据是从1变到0时,neg_rs232_rx信号会出现一个时钟周期长度的高电平。
2.2 实验2 基于FIFO的串口发送机设计
2.2.1说明
这个实验和特权同学的实验的大概思路是相同的,但是,具体的是看了特权同学的视频以及书上的相关内容后自己编写相应的Verilog代码实现的。
2.2.2整体和子模块功能分析
实现串口发送机的功能,分为3个子模块,分别为发送数据产生模块datagene、fifo_u模块以及串口发送模块uart_tx_top,其中,串口发送模块和实验1类似,分为发送模块和波特率控制模块,但是加上了对FIFO进行控制的部分。
这几个模块中,datagene模块产生每隔1s递增1的数据送到FIFO模块的输入端,并且产生FIFO的写使能信号,使得FIFO在输入端的数据稳定时将输入端的数据锁存;FIFO模块就是用ALTERA的FIFOIP核实现的,是一个数据宽度为8位的FIFO,uart_tx模块一方面要产生FIFO的读使能信号,这里我们为了使得FIFO每写入一个数据就将这个数据输出,所以读使能信号在相应的写使能信号之后并在写入下一个数据之前有效,另一方面,uart_tx模块将FIFO输出端的数据通过串口线输出到PC机上。
2.2.3Verilog实现代码
(1)数据产生模块
datagene.v
module datagene( input clk, input rst_n, output reg fifo_wrreq, output reg [7:0] fifo_data );reg [25:0] cnt_1s; //定时1s的计数器always @(posedge clk or negedge rst_n)if(!rst_n) cnt_1s <= 0;else if(cnt_1s == 26'd50000000) cnt_1s <= 0;else cnt_1s <= cnt_1s + 1; always @(posedge clk or negedge rst_n) //输出写使能信号if(!rst_n) fifo_wrreq <= 0;else if(cnt_1s == 26'd25000000) //在两个不同的数的中间FIFO读取送到其上的数 fifo_wrreq <= 1;else fifo_wrreq <= 0;always @(posedge clk or negedge rst_n) //每隔1s使送给FIFO的数递增1if(!rst_n) fifo_data = 8'h0;else if(cnt_1s == 26'd50000000) fifo_data = fifo_data + 1;endmodule
(2)FIFO模块
用QUARTUS中的MegaWizzard实现即可
如上图所示,wrreq和rdreq分别是写使能信号和读使能信号,empty是表示FIFO是否是空的信号,当FIFO空时,empty是1,否则,empty是0,empty信号将作为串口发送模块的输入信号,当empty由低变到高的时候,表示FIFO的数据已读出,串口发送模块此时会将这个数据发到PC机上。
(3)串口发送模块
和实验1相同,分成两个部分,发送模块和波特率选择模块(去掉了接收模块),Verilog实现的代码分别为:
① uart_tx.v
`timescale 1ns / 1psmodule uart_tx( input clk, input rst_n, input clk_bps, input [7:0] tx_data, input tx_start, output bps_start, output reg fifo_rdreq, output rs232_tx );reg tx_start0,tx_start1,tx_start2; //rx_int信号寄存器,捕捉下降沿滤波用wire neg_tx_start; // rx_int下降沿标志位always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin tx_start0 <= 1'b0; tx_start1 <= 1'b0; tx_start2 <= 1'b0; end else begin tx_start0 <= tx_start; tx_start1 <= tx_start0; tx_start2 <= tx_start1; endendassign neg_tx_start = tx_start1 & ~tx_start2; //捕捉到上升沿后,neg_tx_start拉高保持一个主时钟周期//这一句话表示前一时刻是1,后一时刻是0,所以是下降沿,//这里,tx_start2保存的是前一时刻的值,tx_start1保存的是后一时刻的值reg bps_start_r;reg[3:0] num;always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin bps_start_r <= 1'b0; end else if(neg_tx_start) begin //接收数据完毕,准备把接收到的数据发回去 bps_start_r <= 1'b1; end else if(num==4'd11) begin //数据发送完成,复位 bps_start_r <= 1'b0; endendassign bps_start = bps_start_r;reg rs232_tx_r;assign rs232_tx = rs232_tx_r;always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin num <= 4'd0; rs232_tx_r <= 1'b1; end else begin if(clk_bps) begin num <= num+1'b1; case (num) 4'd0: rs232_tx_r <= 1'b0; //发送起始位 4'd1: rs232_tx_r <= tx_data[0]; //发送bit0 4'd2: rs232_tx_r <= tx_data[1]; //发送bit1 4'd3: rs232_tx_r <= tx_data[2]; //发送bit2 4'd4: rs232_tx_r <= tx_data[3]; //发送bit3 4'd5: rs232_tx_r <= tx_data[4]; //发送bit4 4'd6: rs232_tx_r <= tx_data[5]; //发送bit5 4'd7: rs232_tx_r <= tx_data[6]; //发送bit6 4'd8: rs232_tx_r <= tx_data[7]; //发送bit7 4'd9: rs232_tx_r <= 1'b1; //发送结束位 default: rs232_tx_r <= 1'b1; endcase end else if(num==4'd11) num <= 4'd0; //复位 end end //----------------------------------------------------------------//下面的这一部分代码是在原来的串口发送模块中添加的。用来产生FIFO读使能信号//仿照data_gene模块产生FIFO写使能信号的过程,产生一个FIFO读使能信号reg [25:0] cnt_1s; //定时1s的计数器always @(posedge clk or negedge rst_n)if(!rst_n) cnt_1s <= 0;else if(cnt_1s == 26'd50000000) cnt_1s <= 0;else cnt_1s <= cnt_1s + 1; always @(posedge clk or negedge rst_n) //输出读使能信号if(!rst_n) fifo_rdreq <= 0;else if(cnt_1s == 26'd37500000) //在两个不同的数的3/4的时刻FIFO输出送到其上的数 fifo_rdreq <= 1; //uart串口模块读取这个数值并送往PC机else //注意这个读使能信号和写使能信号之间的时序关系 fifo_rdreq <= 0;//-------------------------------------------------------------------endmodule
② speed_select.v
`timescale 1ns / 1psmodule speed_select( clk,rst_n, bps_start,clk_bps );input clk; // 50MHz主时钟input rst_n; //低电平复位信号input bps_start; //接收到数据后,波特率时钟启动信号置位output clk_bps; // clk_bps的高电平为接收或者发送数据位的中间采样点 //以下波特率分频计数值可参照上面的参数进行更改`define BPS_PARA 5207 //波特率为9600时的分频计数值`define BPS_PARA_2 2603 //波特率为9600时的分频计数值的一半,用于数据采样reg[12:0] cnt; //分频计数reg clk_bps_r; //波特率时钟寄存器//----------------------------------------------------------reg[2:0] uart_ctrl; // uart波特率选择寄存器//----------------------------------------------------------always @ (posedge clk or negedge rst_n) if(!rst_n) cnt <= 13'd0; else if((cnt == `BPS_PARA) || !bps_start) cnt <= 13'd0; //波特率计数清零 else cnt <= cnt+1'b1; //波特率时钟计数启动always @ (posedge clk or negedge rst_n) if(!rst_n) clk_bps_r <= 1'b0; else if(cnt == `BPS_PARA_2) clk_bps_r <= 1'b1; // clk_bps_r高电平为接收数据位的中间采样点,同时也作为发送数据的数据改变点 else clk_bps_r <= 1'b0;assign clk_bps = clk_bps_r;endmodule
③串口发送顶层模块
uart_tx_top.v
module uart_tx_top( input clk, input rst_n, input tx_start, input [7:0] tx_data, output fifo_rdreq, output rs232_tx );wire bps_start;wire clk_bps;speed_select speed_select_tx ( clk,rst_n, bps_start,clk_bps );uart_tx uart_tx( clk,rst_n, clk_bps, tx_data, tx_start, bps_start, fifo_rdreq, rs232_tx ); endmodule
2.2.4测试程序的设计
这个测试文件中只需给定时钟信号和复位信号即可。
test_fifouart.vt
// Generated on "07/14/2007 17:44:19" // Verilog Test Bench template for design : fifouart// // Simulation tool : ModelSim-Altera (Verilog)// `timescale 1 ns/ 1 psmodule test_fifouart();reg clk;reg rst_n;// wires wire rs232_tx;// assign statements (if any) fifouart i1 (// port map - connection between master ports and signals/registers .clk(clk), .rs232_tx(rs232_tx), .rst_n(rst_n));initial clk = 0;always #10 clk = ~clk;initial begin rst_n = 0; #100 rst_n = 1;end initial #3000000000 $stop; endmodule
功能仿真的波形如下所示:
观察其中输出0x03部分的波形,如下所示:
从图中可以看出,依次输出的是0x03的低位到高位数据。
2.2.5板级验证结果
从图中可以看出,FPGA将存入FIFO中的数据依次向PC机发送。
2.2.6实验总结
(1)在进行板级验证的时候,遇到了一个问题,当我们将复位信号分配到开发板上的一个按键的时候,出现了如下的结果:
从图中可以看出,时不时地会自动复位,但是当我们将复位信号分配到这块FPGA的全局复位端时,结果就是正确的,这是不是按键抖动的原因????
(2)从前面仿真的波形结果中可以看出,pos_tx_start信号和fifo_rdreq信号非常相近,只不过放大后能够看出,pos_tx_start信号实际上比fifo_rdreq信号延时了3个时钟周期,再重新审视pos_tx_start信号的产生过程,首先,串口发送模块uart_tx在特定的时刻发出一个时钟周期宽的fifo_rdreq信号,将FIFO中的数据从FIFO中读出,然后FIFO的empty输出端将由低变高,串口发送模块uart_tx中通过边沿检测发现了这么一个上升沿,然后在这个上升沿之后使pos_tx_start信号在一个时钟周期内有效。再来看pos_tx_start信号的作用,其作用就是在它有效的那个时钟周期内,使能bps_start信号,使得bps_start信号从这个时刻开始一直到一个单位的数据帧发送完成时都是有效的,从而波特率的计数器能够在这一段时间进行技术并输出控制串口发送模块输出数据的控制信号,所以,完全可以去掉这么一个边沿检测的程序,直接用fifo_rdreq信号代替pos_tx_start信号,这样做的话,仅仅是bps_start信号提前有效了3个时钟周期,而3时钟周期和在发送一个单位的数据帧时,bps_start有效的总时间相比,是微乎其微的,所以对结果几乎没有影响。