IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> UART的RTL逻辑设计部分 -> 正文阅读

[游戏开发]UART的RTL逻辑设计部分

当UART完成了模块分解和输出之后,就可以开始设计逻辑了,设计好了再写代码!!!!


1. UART_TX

还是先从外围模块开始,UART_TX模块的功能比较简单,将多bit数据按照波特率解构成单bit发送到tx上即可。

由于tx端每一拍的数据都不同,即当前tx输出会与之前tx_data有关,因此输出输入是时序逻辑关系,此处使用状态机进行设计。

状态设计两种:IDLE和TRANS。咱先用语言描述每个状态要干啥 以及状态转移条件,再翻译成时序图,最终形成代码

IDLE状态下,一直等待要发送的数据出现,出现了就转入TRANS状态

TRANS状态下,将待发送的数据按照帧格式和波特率单bit发送,发完了转回IDLE。

状态转换图如下:

在这里插入图片描述

1.1. IDLE:等待数据到来

IDLE状态什么都不用做,只需找到一个条件判断有待发送数据即可进入TRANS状态。

● tx_data_val 作为标志

第一个想到的肯定是源时钟tx_clk嘛,采样到之后转入TRANS状态,再根据波特率计算多少个tx_clk周期发送1bit。

听着好像能实现啊,简单也有效,但是一画时序图问题就能发现问题:

● 检测tx_data_val为高之后,下一拍数据就变了咋办?

在这里插入图片描述

你说tx_data_val为高时,把tx_data存下来。

● 那行,再看这个:tx_data压根就没变,检测tx_data_val为高就进入TRANS去发送去,重复发送。

在这里插入图片描述

所以解决的办法是握手,让两个模块在时序上构成配合,是不是有点多bit跨时钟域的意思了?对于多bit数据传输,直接用FIFO简单有效。

● 异步FIFO读出 作为标志

这是想到UART_TX和UART_RX内嵌一个Baud Clock Generator嘛,它就产生一个bclk就是用来发送tx的,所以干脆使用异步FIFO。

那么使用异步FIFO如何实现IDLE向TRANS转换呢?

确定TRANS要转回IDLE状态,同时拉高rd_en,让FIFO 读新的数据,当确定会读出一个新的数据时,拉低rd_en转到TRANS

注意这里和tx_clk采样tx_data_val类似,不能只检测valid信号转移状态。必须保证能读出来有效的新数据再转移。

时序图如下,这样就可以在TRANS一直用该数据了,注意empty为空时要延长rd_en

在这里插入图片描述

所以异步FIFO方案的状态机变成:

在这里插入图片描述

1.2. TRANS:并串转换

OK现在有要发送的数据了,TRANS这边需要作一下几个工作:

● 读取FIFO的rdata值,计算奇偶校验位,并根据帧结构打包成tx_data_frm

● 根据波特率和bclk频率,实现tx_data_frm到tx的并串转换

● 确定发送完毕条件,返回IDLE状态

● 打包tx_data_frm

根据之前的时序,我们尽量在TRANS 第一拍 将FIFO的rdata根据帧结构打包成tx_data_frm

注意这个打包的if条件必须独一无二,条件不能与后面并串转换时重合

所以,可以通过可直接对rd_en打一拍来判断(rd_en_d1 == 1'b1 && valid == 1'b1)是否成立

IDLE那边已经保证了rd_en && !empty时转移到TRANS,所以这个valid == 1'b1的条件可以不加

在这里插入图片描述

奇偶校验位直接按位异或即可,结果为1表示有rdata有奇数个1,否则为偶数个。

● 并串转换

在之后TRANS的时间内,要通过tx_data_frm移位的方式实现单bit发送

别忘了波特率!波特率其实就是tx数据更新的频率!需对bclk计数的来判断什么时候移位1次

Baud Clock Generator产生的bclk频率并不等于波特率,而是波特率的整数倍,这是为了保证UART_RX对rx采样的“3个沿”条件,后文会讲到

例如我们可以这样如下图实现0~15循环计数,注意cnt要与tx_data_frm时序对齐

在这里插入图片描述

这个cnt是4bit的计数器,也可以换成16bit的移位寄存器

最后别忘了assign tx = tx_data_frm[0];

● 发送完毕标志

完成TX_DATA_WIDTH+3个位发送,就可以转入IDLE状态了。

我们怎么知道发送了这么多个bit?看来我们还需要另一个计算发了多少bit的计数器

2. UART_RX

3. baud_clk_gen

UART_RX和UART_TX内都有这个模块,而且这个模块的输入输出都是确定的,所以先写这个简单一点的。

这个模块的功能有两部分:根据clk的频率和波特率设定实现时钟bclk,以及根据bclk_en对bclk进行门控

3.1. bclk 产生

其实就是一个时钟分频就OK了,让bclk的频率就是波特率就完事了,这样的话UART_TX的tx线每个bclk周期发送1bit数据就可以了。

一般来说这个bclk是可以实现的,因为波特率比如4800、9600、115200这种频率非常非常低,所以时钟分频可以分频出来。

然后UART_RX接收的话,数据帧的每一位在rx上持续的时间恰为bclk的一个周期,也是bclk每一拍收个数,这里不合适。

首先,虽然通信双方商议好了波特率,但是各自的时钟不同源,所以UART_RX通过rx接收单bit数据就要解决一个单bit跨时钟域的问题。

那么既然是跨时钟域,单bit跨时钟域电平同步是吧?直接电平同步的条件是什么?

bclk得满足 波特率的“3个沿”

直接电平同步的条件是采样的脉冲 能容纳采样时钟的3个沿,否则就如下图所示采样电压过低导致没采到!

在这里插入图片描述

那怎么办?对脉冲展宽嘛?波特率都定死了,哪能展宽啊?那只能——增加bclk频率 满足3个沿

那波特率又该怎么保证呢?根据bclk频率计算数据帧每bit 需要持续bclk多少个周期就行

例如波特率是9600 bps,产生的bclk频率是50MHZ。
UART_TX发射数据的话,就用计数器,每经过 50 × 1 0 6 / 9600 ≈ 5208 50×10^6/9600≈5208 50×106/96005208个bclk周期发射一位数据
UART_RX接受的话,检测到帧头0就开始计数,每经过5208个bclk周期采样一次
异步时钟亚稳态 的解决方案——单bit信号

用bclk还是rx_clk ?

那又该问了,那要是这样还产生bclk干嘛呀,直接用rx_clk或tx_clk持续一定周期满足波特率不完事了?

确实如此,但还是从低功耗的角度考虑的。

比如rx_clk和tx_clk的频率肯定比bclk高,所以计数器的位数会比较大,而且UART_TX如果没有数据发送的话,可以考虑将bclk停掉。

3.2. bclk 门控

4. UART

2.3. 接受模块UART_RX设计

RS232包括接受模块RX和发送模块TX,接受模块需要将接受的串行数据转换为并行数据,并输出数据有效位。

输入

● sys_clk:50MHZ工作时钟,1bit
● sys_rst_n:复位信号,低电平有效,1bit
● rx:接收信号,1bit

复位信号结尾的_n表示低电平复位

输出

● po_data:转换成的并行数据,8bit
● po_flag:数据有效的标志信号,1bit

即如下图所示

在这里插入图片描述

模块框架为:

module uart_rx(
	input  sys_clk,
	input sys_rst_n,
	input rx,

	output reg [7:0] po_data,
	output reg po_flag
	);

endmodule

2.3.1. 亚稳态与电平同步

由于RX接受数据没有时钟同步,仅仅通过探测每一帧的起始位低电平判断数据开始接受。

因此对于sys_clk来说,极有可能刚好采样到信号上升沿or下降沿的中间位置,进而产生亚稳态。

即时钟到来前后,信号均是不稳定的,所以即可以说完全不满足建立时间和保持时间。
同时亚稳态的问题还会出现在:慢时钟域 同步到 快时钟域的情形

解决方法:电平同步,即连续打两拍,分别经过rx_reg1和rx_reg2

原理是:一级寄存器rx_reg1输入为亚稳态信号,则rx_reg1输出需要额外经过 T m e t 1 T_{met1} Tmet1?的时间(称决断时间)才能稳定下来,而且该时间可能比较长甚至到下一个采样时刻还未稳定。

但是又加了第二层寄存器rx_reg2,此时的决断时间 T m e t 2 T_{met2} Tmet2?会很小,一般不会延伸至下一个采样时刻,进而得到稳定输出。

在这里插入图片描述

加上异步复位,有代码:

reg rx_reg1;
reg rx_reg2;

always@(posedge sys_clk or negedge sys_rst_n)
begin
	if(!sys_rst_n)
	begin
		rx_reg1 <= 1'b1;
		rx_reg2 <= 1'b1;
	end
	else
	begin
		rx_reg1 <= rx;
		rx_reg2 <= rx_reg1;
	end
end

2.3.2. 收数使能

由于起始位为低电平,所以需要检测接收信号何时拉低。

电平拉低可通过检测最后一级寄存器和倒数第二级寄存器输出来判断,但由于此处rx_reg1的输出为亚稳态,所以此处再在rx_reg2后面加一级寄存器rx_reg3,并用rx_reg2和rx_reg3的输出判断电平拉低

之前的打拍代码更改:

reg rx_reg1;
reg rx_reg2;
reg rx_reg3;

always@(posedge sys_clk or negedge sys_rst_n)
begin
	if(!sys_rst_n)
	begin
		rx_reg1 <= 1'b1;
		rx_reg2 <= 1'b1;
		rx_reg3 <= 1'b1;
	end
	else
	begin
		rx_reg1 <= rx;
		rx_reg2 <= rx_reg1;
		rx_reg3 <= rx_reg2;
	end
end

加入一个信号start_negedge表示起始位到达的标志,该信号升高则表示该时刻到达。

但注意电平拉低仅仅是一个时钟时刻的事件,进行收数需要每个时钟作判断,所以需要维护一个work_en在可以收数时恒为高电平,收够了数就变成低电平。

reg start_negedge;
always@(posedge sys_clk or negedge sys_rst_n)
begin
	if(!sys_rst_n)
		start_negedge <= 1'b0;
	else if(rx_reg2 == 1'b0 && rx_reg3 == 1'b1 && work_en == 0)
			start_negedge <= 1'b1;
		else
			start_negedge <= 1'b0;
end	

注意由于是非阻塞赋值,起始位到达rx_reg3的时刻与start_negedge拉高的时刻是对齐的

加入计数使能信号work_en,并在start_negedge升高时work_en开始变高,在收够了数才变低.

bit_cnt为计数器,算上起始位收了9个数才可以结束。

reg work_en;
reg [3:0] bit_cnt;
always@(posedge sys_clk or negedge sys_rst_n)
begin
	if(!sys_rst_n)
		work_en <= 1'b0;
	else if(start_negedge == 1'b1)
			work_en <= 1'b1;
		else if(bit_cnt == 10)
			work_en <= 1'b0;
end	

注意此处未说明不满足三个if里条件的work_en如何变化,则默认维持前一时刻的电平

2.3.3. 异步计数

但是注意由于uart是异步的,波特率选择9600bps不等于时钟频率,因此需要计算接受每bit数据需要的时钟周期个数,时钟50MHZ,则可以计算: 1 9600 × 50 × 1 0 6 = 5208 \frac{1}{9600} \times 50\times 10^6=5208 96001?×50×106=5208

也就是说每bit数据会在rx上保持5208个sys_clk时钟周期

所以再加入变量baud_cnt计算每bit之间经过的时钟数。

为了取得最稳定的数据,bit_cnt加一的时机选择 每bit数据中间的计数时钟,即baud_cnt == 5208/2的时刻

reg [15:0] baud_cnt;
always@(posedge sys_clk or negedge sys_rst_n)
begin
	if(!sys_rst_n)
		baud_cnt <= 16'b0;
	else if(baud_cnt == 5207 || work_en == 0)
			baud_cnt <= 16'b0;
		else
			baud_cnt <= baud_cnt + 16'b1;
end

可以看出实际上,work_en仅用于指导时钟计数baud_cnt开始

always@(posedge sys_clk or negedge sys_rst_n)
begin
	if(!sys_rst_n)
		bit_cnt <= 4'b0;
	else if(baud_cnt == 2603)
			bit_cnt <= bit_cnt + 4'b1;
		else if(bit_cnt == 10)
			bit_cnt <= 4'b0;
end

2.3.4. 串并转换

之后需要将接受的数据依次存储到一个寄存器中,名为rx_data,且注意串口发送数据往往是从低到高位发送数据,并且每次接收都存储在一个寄存器中,实现串行到并行的转换,即边收数边转换

但是需要给出接收数据的条件。此处认为bit_cnt每增加1,就表示记了一个位,除去起始位就可以收数了。

而bit_cnt加1的条件为baud_cnt == 2603

reg [7:0] rx_data;

always@(posedge sys_clk or negedge sys_rst_n)
begin
	if(!sys_rst_n)
		rx_data <= 8'b0;
	else if(baud_cnt == 2603)
			rx_data <= {rx,rx_data[7:1]};
end

那么何时将rx_data赋值给po_data呢?当数据完成移位之后就可以赋值了,并为po_flag输出高。

显然是bit_cnt计数到9时就表示移位结束。

always@(posedge sys_clk or negedge sys_rst_n)
begin
	if(!sys_rst_n)
		po_data <= 8'b0;
	else if(bit_cnt == 9)
			po_data <= rx_data;
end

always@(posedge sys_clk or negedge sys_rst_n)
begin
	if(!sys_rst_n)
		po_flag <= 8'b0;
	else if(bit_cnt == 10)
			po_flag  <= 8'b1;
end

注意po_data和po_flag均为一直保持输出最近一次接收到的数
上面baud_cnt == 2603 和 bit_cnt==9都可以设立一个什么flag作为标志以增强程序可读性

整体的时序图如下,可能与程序存在出入:

在这里插入图片描述

就此实现了uart_rx模块全部代码,可以仿真了。

2.3.5. 仿真

仿真代码如下:

`timescale  1ns/1ns

// Author        : EmbedFire
// Create Date   : 2019/06/12
// Module Name   : tb_uart_rx
// Project Name  : rs232
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description   : 
// 
// Revision      : V1.0
// Additional Comments:
// 
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司    : http://www.embedfire.com
// 论坛    : http://www.firebbs.cn
// 淘宝    : https://fire-stm32.taobao.com


module  tb_uart_rx();

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//reg   define
reg             sys_clk;
reg             sys_rst_n;
reg             rx;

//wire  define
wire    [7:0]   po_data;
wire            po_flag;

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//初始化系统时钟、全局复位和输入信号
initial begin
        sys_clk    = 1'b1;
        sys_rst_n <= 1'b0;
        rx        <= 1'b1;
        #20;
        sys_rst_n <= 1'b1;
end

//模拟发送8次数据,分别为0~7
initial begin
        #200
        rx_bit(8'd0);  //任务的调用,任务名+括号中要传递进任务的参数
        rx_bit(8'd1);
        rx_bit(8'd2);
        rx_bit(8'd3);
        rx_bit(8'd4);
        rx_bit(8'd5);
        rx_bit(8'd6);
        rx_bit(8'd7);
end

//sys_clk:每10ns电平翻转一次,产生一个50MHz的时钟信号
always #10 sys_clk = ~sys_clk;

//定义一个名为rx_bit的任务,每次发送的数据有10位
//data的值分别为0~7由j的值传递进来
//任务以task开头,后面紧跟着的是任务名,调用时使用
task rx_bit(
    //传递到任务中的参数,调用任务的时候从外部传进来一个8位的值
        input   [7:0]   data
);
        integer i;      //定义一个常量
//用for循环产生一帧数据,for括号中最后执行的内容只能写i=i+1
//不可以写成C语言i=i++的形式
        for(i=0; i<10; i=i+1) begin
            case(i)
                0: rx <= 1'b0;
                1: rx <= data[0];
                2: rx <= data[1];
                3: rx <= data[2];
                4: rx <= data[3];
                5: rx <= data[4];
                6: rx <= data[5];
                7: rx <= data[6];
                8: rx <= data[7];
                9: rx <= 1'b1;
            endcase
            #(5208*20); //每发送1位数据延时5208个时钟周期
        end
endtask         //任务以endtask结束

//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//------------------------uart_rx_inst------------------------
uart_rx uart_rx_inst(
        .sys_clk    (sys_clk    ),  //input           sys_clk
        .sys_rst_n  (sys_rst_n  ),  //input           sys_rst_n
        .rx         (rx         ),  //input           rx
                
        .po_data    (po_data    ),  //output  [7:0]   po_data
        .po_flag    (po_flag    )   //output          po_flag
);

endmodule

设计仿真时间为10ms,仿真结果如下图所示。可以看出po_data输出正常,各计数器输出正常。

在这里插入图片描述

2.4. 发送模块UART_TX设计

发送模块需要将PC端传来的并行数据转换为串行数据,并发送出去。

输入

● sys_clk:50MHZ工作时钟,1bit
● sys_rst_n:复位信号,低电平有效,1bit
● pi_data:接受的并行数据,8bit
● pi_flag:数据有效的标志信号,1bit

输出

● tx:发送信号,1bit,发数波特率为9600bps

注意8位pi_data和数据有效信号pi_flag是只在一个周期发送过来。

即如下图所示

在这里插入图片描述

模块框架为:

module uart_tx(
	input sys_clk,
	input sys_rst_n,
	input [7:0] pi_data,
	input pi_flag,

	output reg tx,
	);

endmodule

2.4.1. 帧结构

po_data的8bit仅为数据位,需要按照帧结构构造,这个比较简单。

不过注意tx发送的时候也是 先发低位,再发高位

reg [9:0] tx_data;
always@(posedge sys_clk or negedge sys_rst_n)
begin
	if(!sys_rst_n)
		tx_data <= 10'b0;
	else if(pi_flag)
			tx_data <= {1'b1,pi_data,1'b0}; 
end

2.4.2. 发数使能

实际上发数和收数非常类似,发数不用考虑亚稳态问题,并且完成的是并转串的过程,过程类似。

由于pi_flag只是一个周期的信号,因此需要维护一个work_en作为发数使能,在pi_flag上升沿时,work_en保持一直为高即可。

依旧使用bit_cnt计算发送的比特数,与rx模块一致:当bit_cnt == 9时,work_en复位

reg work_en;
reg [3:0] bit_cnt;
always@(posedge sys_clk or negedge sys_rst_n)
begin
	if(!sys_rst_n)
		work_en <= 1'b0;
	else if(pi_flag)
			work_en <= 1'b1;
		else if(bit_cnt == 10)
				work_en <= 1'b0;
end

2.4.3. 异步计数

由于发数也要求9600bps的波特率,所以需要baud_cnt计算时钟个数,每隔5208个sys_clk就可以令bit_cnt加1。

同时在work_en == 0停止计数。

而bit_cnt则也是在计第10个数之后复位。

always@(posedge sys_clk or negedge sys_rst_n)
begin
	if(!sys_rst_n)
		baud_cnt <= 16'b0;
	else if(baud_cnt == 5207 || work_en == 0)
			baud_cnt <= 16'b0;
		else
			baud_cnt <= baud_cnt + 16'b1;
end

always@(posedge sys_clk or negedge sys_rst_n)
begin
	if(!sys_rst_n)
		bit_cnt <= 4'b0;
	else if(baud_cnt == 1)
			bit_cnt <= bit_cnt + 4'b1;
		else if(bit_cnt == 10)
			bit_cnt <= 4'b0;
end

bit_cnt加1的baud_cnt位置可以任意给定

2.4.4. 并串转换

然后每次在bit_cnt加1的时候发送对应位即可,注意tx发送也是

always@(posedge sys_clk or negedge sys_rst_n)
begin
	if(!sys_rst_n)
		tx <= 1'b1;
	else if(baud_cnt == 1)
			begin
			case(bit_cnt)
				0:	tx <= 1'b0;
				1:	tx <= pi_data[0];
				2:	tx <= pi_data[1];
				3:	tx <= pi_data[2];
				4:	tx <= pi_data[3];
				5:	tx <= pi_data[4];
				6:	tx <= pi_data[5];
				7:	tx <= pi_data[6];
				8:	tx <= pi_data[7];
				9:	tx <= 1'b1;
			default:	tx <= 1'b1;
			endcase
		end
end

注意如果写成tx <= tx_data[0],则还需要为tx_data设定右移位操作,比较繁琐,不过读者可以试一试。
在这里插入图片描述

2.4.5. 仿真

仿真程序如下:

`timescale  1ns/1ns
/
// Author        : EmbedFire
// Create Date   : 2019/06/12
// Module Name   : tb_uart_tx
// Project Name  : rs232
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description   : 
// 
// Revision      : V1.0
// Additional Comments:
// 
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司    : http://www.embedfire.com
// 论坛    : http://www.firebbs.cn
// 淘宝    : https://fire-stm32.taobao.com


module  tb_uart_tx();

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//reg   define
reg         sys_clk;
reg         sys_rst_n;
reg [7:0]   pi_data;
reg         pi_flag;

//wire  define
wire        tx;

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//初始化系统时钟、全局复位
initial begin
        sys_clk    = 1'b1;
        sys_rst_n <= 1'b0;
        #20;
        sys_rst_n <= 1'b1;
end

//模拟发送7次数据,分别为0~7
initial begin
        pi_data <= 8'b0;
        pi_flag <= 1'b0;
        #200
        //发送数据0
        pi_data <= 8'd0;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
//每发送1bit数据需要5208个时钟周期,一帧数据为10bit
//所以需要数据延时(5208*20*10)后再产生下一个数据
        #(5208*20*10);
        //发送数据1
        pi_data <= 8'd1;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*20*10);
        //发送数据2
        pi_data <= 8'd2;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*20*10);
        //发送数据3
        pi_data <= 8'd3;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*20*10);
        //发送数据4
        pi_data <= 8'd4;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*20*10);
        //发送数据5
        pi_data <= 8'd5;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*20*10);
        //发送数据6
        pi_data <= 8'd6;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*20*10);
        //发送数据7
        pi_data <= 8'd7;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
end

//sys_clk:每10ns电平翻转一次,产生一个50MHz的时钟信号
always #10 sys_clk = ~sys_clk;

//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//------------------------uart_rx_inst------------------------
uart_tx uart_tx_inst(
        .sys_clk    (sys_clk    ),  //input           sys_clk
        .sys_rst_n  (sys_rst_n  ),  //input           sys_rst_n
        .pi_data    (pi_data    ),  //output  [7:0]   pi_data
        .pi_flag    (pi_flag    ),  //output          pi_flag

        .tx         (tx         )   //input           tx
);

endmodule

仿真10ms,结果如下:

在这里插入图片描述
在这里插入图片描述
可以看出发数的帧结构顺序是正确的。

2.5. 顶层模块RS232设计

接下来就可以进行顶层模块设计,为了测试收发模块的功能,可设计回环测试loopback。

之后再介绍实际场景下的顶层模块如何编写。

2.5.1. 回环测试设计

如图所示loopback

在这里插入图片描述
所以顶层模块如下:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2021/11/28 18:02:56
// Design Name: 
// Module Name: rs232
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module rs232(
	input sys_clk,
	input sys_rst_n,
	input rx,
	
	output tx
    );
	
wire [7:0] data;
wire flag;

uart_tx		uart_tx_inst(
	.sys_clk	(sys_clk),
	.sys_rst_n	(sys_rst_n),
	.pi_data	(data),
	.pi_flag	(flag),
	
	.tx			(tx)
    );
uart_rx		uart_rx_inst(
	.sys_clk	(sys_clk),
	.sys_rst_n	(sys_rst_n),
	.rx			(rx),
	
	.po_data	(data),
	.po_flag	(flag)
    );
endmodule

仿真模块如下:

`timescale  1ns/1ns

// Author        : EmbedFire
// Create Date   : 2019/06/12
// Module Name   : tb_rs232
// Project Name  : rs232
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description   :
//
// Revision      : V1.0
// Additional Comments:
// 
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司    : http://www.embedfire.com
// 论坛    : http://www.firebbs.cn
// 淘宝    : https://fire-stm32.taobao.com


module  tb_rs232();

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//wire  define
wire    tx          ;

//reg   define
reg     sys_clk     ;
reg     sys_rst_n   ;
reg     rx          ;

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//初始化系统时钟、全局复位和输入信号
initial begin
    sys_clk    = 1'b1;
    sys_rst_n <= 1'b0;
    rx        <= 1'b1;
    #20;
    sys_rst_n <= 1'b1;
end

//调用任务rx_byte
initial begin
    #200
    rx_byte();
end

//sys_clk:每10ns电平翻转一次,产生一个50MHz的时钟信号
always #10 sys_clk = ~sys_clk;

//创建任务rx_byte,本次任务调用rx_bit任务,发送8次数据,分别为0~7
task    rx_byte();  //因为不需要外部传递参数,所以括号中没有输入
    integer j;
    for(j=0; j<8; j=j+1)    //调用8次rx_bit任务,每次发送的值从0变化7
        rx_bit(j);
endtask

//创建任务rx_bit,每次发送的数据有10位,data的值分别为0到7由j的值传递进来
task    rx_bit(
    input   [7:0]   data
);
    integer i;
    for(i=0; i<10; i=i+1)   begin
        case(i)
            0: rx <= 1'b0;
            1: rx <= data[0];
            2: rx <= data[1];
            3: rx <= data[2];
            4: rx <= data[3];
            5: rx <= data[4];
            6: rx <= data[5];
            7: rx <= data[6];
            8: rx <= data[7];
            9: rx <= 1'b1;
        endcase
        #(5208*20); //每发送1位数据延时5208个时钟周期
    end
endtask

//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//------------------------ rs232_inst ------------------------
rs232   rs232_inst
(
    .sys_clk    (sys_clk    ),  //input         sys_clk
    .sys_rst_n  (sys_rst_n  ),  //input         sys_rst_n
    .rx         (rx         ),  //input         rx

    .tx         (tx         )   //output        tx
);

endmodule

仿真结果如图所示:

在这里插入图片描述

2.5.2. 顶层模块设计

3. 基于状态机的UART

由于UART的TX和RX模块涉及到串并转换,所以必须控制FIFO的读写使能,使用状态机可读性更强。

代码见下

3.1. SPI_RX

`timescale 1ns/1ns


module spi_rx(
		input			reset,
		input 			rx_clk,
		input 			rx_dat,
		input 		 	user_clk,
		input 			user_rst,
		input 			user_rd_en,
		
		output 			user_vout,
		output 	[15:0]	user_dout,
		output 			empty
		);

parameter IDLE = 2'd0,
		  RX   = 2'd1,
		  DONE = 2'd2;

reg					rx_dat_d1, rx_dat_d2, rx_dat_d3;
reg 	[15:0] 		rx_dat_vec;
(* fsm_safe_state = "reset_state" *)	reg  [1:0]	cur_state;
(* fsm_safe_state = "reset_state" *)	reg  [1:0]	nxt_state;
reg		[3:0]		cnt;
reg					rd_en;
wire	[15:0]		rd_dat; 
wire 				rst_fifo;

always@(posedge rx_clk)
begin
	if(reset == 1'b1)
	begin
		rx_dat_d1  <= 1'b0;
		rx_dat_d2  <= 1'b0;
		rx_dat_d3  <= 1'b0;
	end
	else
	begin
		rx_dat_d1  <= rx_dat;
		rx_dat_d2  <= rx_dat_d1;
		rx_dat_d3  <= rx_dat_d2;
	end
end

always@(posedge rx_clk)
begin
	if(reset == 1'b1)
		rx_dat_vec <= 0;
	else
		rx_dat_vec <= {rx_dat_vec[14:0],rx_dat_d2};
end

always@(posedge rx_clk)
begin
	if(reset == 1'b1)
		cur_state <= IDLE;
	else
		cur_state <= nxt_state;
end


always@(*)
begin
	case(cur_state)
	IDLE:
		if((rx_dat_d2 == 1'b0) && (rx_dat_d3 == 1'b1))
			nxt_state = RX;
		else
			nxt_state = IDLE;
	RX:
		if(&cnt == 1'b1)
			nxt_state = DONE;
		else
			nxt_state = RX;
	DONE:
			nxt_state = IDLE;
	default: 
			nxt_state = IDLE;
	endcase
end

always@(posedge rx_clk)
begin
	if(reset == 1'b1)
		cnt <= 0;
	else if(cur_state == RX)
		cnt <= cnt + 1'b1;
end

reg				wr_en;
reg 	[7:0]	wr_en_dly;

reg		[15:0]	wr_dat;
reg		[15:0]	wr_dat_d1;
reg		[15:0]	wr_dat_d2;
reg		[15:0]	wr_dat_d3;
reg		[15:0]	wr_dat_d4;
reg		[15:0]	wr_dat_d5;

always@(posedge rx_clk)
begin
	if(reset == 1'b1)
		wr_en <= 1'b0;
	else if(cur_state == DONE)
		wr_en <= 1'b1;
	else
		wr_en <= 1'b0;
end

always@(posedge rx_clk)
begin
	if(reset == 1'b1)
		wr_dat <= 16'b0;
	else if(cur_state == DONE)
		wr_dat <= rx_dat_vec;
	else
		;
end

always@(posedge rx_clk)
begin
	if(reset == 1'b1)
		wr_en_dly <= 8'b0;
	else 
		wr_en_dly <= {wr_en_dly[6:0],wr_en};
end

always@(posedge rx_clk)
begin
	if(reset == 1'b1)
	begin
		wr_dat_d1 <= 16'b0;
		wr_dat_d2 <= 16'b0;
		wr_dat_d3 <= 16'b0;
		wr_dat_d4 <= 16'b0;
		wr_dat_d5 <= 16'b0;
	end
	else
	begin
		wr_dat_d1 <= wr_dat;
		wr_dat_d2 <= wr_dat_d1;
		wr_dat_d3 <= wr_dat_d2;
		wr_dat_d4 <= wr_dat_d3;
		wr_dat_d5 <= wr_dat_d4;
end

fifo_w16_dlk_new	U_RX_FIFO(
	.rst				(reset			),
	.wr_clk				(rx_clk			),
	.rd_clk				(user_clk		),
	.din				(wr_dat_d5		),
	.wr_en				(wr_en_dly[4]	),
	.rd_en				(rd_en			),
	.dout				(rd_dat			),
	.full				(full			),
	.empty				(empty			),
	.almost_empty		(almost_empty	),
	.valid				(valid			),
	.wr_data_count		(				),
	);


always@(posedge user_clk)
begin
	if(user_rst == 1'b1)
		rd_en <= 1'b0;
	else
		rd_en <= (empty == 1'b0) ? user_rd_en : 1'b0;
end

assign user_vout = valid;
assign user_dout = rd_dat;

endmodule

3.2. SPI_TX

`timescale 1ns/1ns

module spi_tx(
		input			reset,
		input 			user_clk,
		input 			user_vin,
		input [63:0] 	user_din,
		input 			tx_clk,
		input 			sclk_rst,
		
		output 			tx_clk_inv,
		output 			tx_dat,
		output 			wire_tx_rd_fifo_empty
		);

parameter IDLE = 1'b0,
		  READ = 1'b1;

reg 	[18:0] 		valid_dly = 19'd0;
reg					wr_en;
reg		[63:0]		wr_dat;

(* fsm_safe_state = "reset_state" *)	reg 	cur_state;
(* fsm_safe_state = "reset_state" *)	reg 	nxt_state;

wire 			full;
wire 			empty;
wire	[15:0]	rd_dat;

assign wire_tx_rd_fifo_empty = empty;

always@(posedge user_clk)
begin
	if(reset == 1'b1)
	begin
		wr_en  <= 1'b0;
		wr_dat <= 64'b0;
	end
	else
	begin
		wr_en  <= user_vin & (!full);
		wr_dat <= user_din;
	end
end

fifo_generator_0	U_TX_FIFO(
	.rst				(reset		),
	.wr_clk				(user_clk	),
	.rd_clk				(tx_clk		),
	.din				(wr_dat		),
	.wr_en				(wr_en		),
	.rd_en				(rd_en		),
	.dout				(rd_dat		),
	.full				(full		),
	.empty				(empty		),
	.valid				(valid		)
	);


always@(posedge tx_clk)
begin
	if(sclk_rst == 1'b1)
		cur_state <= IDLE;
	else
		cur_state <= nxt_state;
end

always@(*)
begin
	case(cur_state)
	IDLE:
		if(empty == 1'b0)
			nxt_state = READ;
		else
			nxt_state = IDLE;
	READ:
		if(valid_dly[18] == 1'b1)
			nxt_state = IDLE;
		else
			nxt_state = READ;
	default: 
			nxt_state = IDLE;
	endcase
end

always@(posedge tx_clk)
begin
	if(sclk_rst == 1'b1)
		rd_en <= 1'b0;
	else if((cur_state == IDLE) && (empty == 1'b0))
		rd_en <= 1'b1;
	else
		rd_en <= 1'b0;
end

reg		[17:0]		tmp_dat = 18'h3_FFFF;

always@(posedge tx_clk)
begin
	if(sclk_rst == 1'b1)
		tmp_dat <= 18'h3_FFFF;
	else if(valid == 1'b1)
		tmp_dat <= {1'b0,rd_dat,1'b1};
	else
		tmp_dat <= {tmp_dat[16:0],1'b1};
end

always@(posedge tx_clk)
begin
	if(sclk_rst == 1'b1)
		valid_dly <= 19'b0;
	else
		valid_dly <= {valid_dly[17:0],valid};
end

assign tx_dat = tmp_dat[17];

endmodule

3.3. UART_SYNC

`timescale 1ns/1ns


module uart_sync(
		input 			clk,
		input 			rst_clk,
		input 			clk1,
		input 			clk1_dsprst,
		input 			clk2,
		input 			clk2_dsprst,
		input 			sclk_in,
		input 			rst,
		
		input 			wr_fifo_en,
		input [63:0] 	data_to_fifo,
		output 			wire_tx_rd_fifo_empty,
		
		input 			rd_fifo_en0,
		output [15:0] 	data_from_fifo,
		input 			rd_fifo_en1,
		output [15:0] 	data_from_fifo1,
		
		input 			rxd,
		output 			txd,
		input 			rx_sclk,
		output 			tx_sclk,
		input 			rx_sclk_rst,
		output 			rx_fifo_empty,
		output 			irq
		);

spi_tx U_TX(
	.reset						(rst_clk			),
	.user_clk					(clk				),
	.user_vin					(wr_fifo_en			),
	.user_din					(data_to_fifo		),
	.tx_clk						(sclk_in			),
	.sclk_rst					(rst				),
	
	.tx_clk_inv					(tx_sclk			),
	.tx_dat						(txd				),
	.wire_tx_rd_fifo_empty		(wire_tx_rd_fifo_empty)
	);
	
spi_rx U_RX(
	.reset						(rx_sclk_rst	),
	.rx_clk						(rx_sclk		),
	.rx_dat						(rxd			),
	
	.user_clk					(clk2			),
	.user_rst					(clk2_dsprst	),
	.user_rd_en					(rd_fifo_en1	),
	.user_vout					( 				),
	.user_dout					(data_from_fifo1),
	.empty						(rx_fifo_empty	)
	);

endmodule

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-05-05 11:52:21  更:2022-05-05 11:53:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 10:32:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码