通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,UART)

1. 介绍

通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,UART),是异步串行通信口(异步串口)的总称,亦是一种通用的数据链路协议

常说的串行通信口也是串口,串口的是指数据一bit一bit顺序传送,线路简单成本低,但传送速度慢。

而UART作为异步串口,其特征为:

● UART只有两根信号线——发送线 TX 和接受线 RX,分别用于数据单bit发送和接受

由于TX和RX为单端传输,因此当传输线上存在干扰时,传输的信号会受到干扰。
差分传输:使用两根线传输信号,两个线上信号振幅相等,极性相反。通过计算这两个信号的差实现逻辑0和1。
显然,差分传输的电平幅值会更高。其优点在于,当两根信号线上有相同or相似的干扰时(共模干扰),接收端会直接取差而消除干扰。
在这里插入图片描述
在这里插入图片描述

● 发送时将并行数据转化为串行,接受时将串行数据转换为并行。

● 数据传输无需时钟控制,通过数据的起始位和停止位实现同步

● 全双工,即某时刻双方可同时发送和接受数据

半双工,表明某一个时刻上,只能执行一方发送数据而另一方接收数据的操作模式。

在这里插入图片描述

2. RS232

RS232通信协议属于UART的一种,只有TX和RX两种单bit数据传输线。

本文将基于多个always模块,非状态机实现RS232的设计

基于rs232收发模块如下:

在这里插入图片描述

2.1. 协议

RS232的传输是基于帧结构进行传输的

每个数据包包含:

起始位:1bit,低电平0表示开始传输数据。

通过起始位0判断数据开始发送,因此在不传输数据时,TX和RX始终保持高电平

数据位:8bit

奇偶校验位:可选0~1bit,保证数据位 + 校验位 所有1的总个数是奇数or偶数

例如偶校验,数据位为01101101,共有6个1已经为偶数,则校验位为0

停止位:1bit,高电平1表示数据传输结束。

具体如下图所示;

在这里插入图片描述
数据传输衡量标准一般通过比特率和波特率来衡量:

波特率:每秒传输的码元个数,单位波特每秒Bps,即Baud/s

码元:传输数据信息的信号单元。

例如高低电平0V为0,5V为1,码元就为1bit,此时1 Bps = 1 bps
如果有四个电平即0V、2V、4V和6V分别为00、01、10和11,码元就为2bit,此时1 Bps = 2 bps

比特率:每秒传输的二进制位数,单位bps,即bit/s

对于RS232波特率就为比特率,常见的有4800,9600,115200等

2.2. 电平标准

FPGA串口输入输出为TTL(Transistor Transistor Logic)电平,3.3V为逻辑1,0V为逻辑0

而RS232自身为负电平标准:-15V ~ -5V为逻辑1,5V ~ 15V为逻辑0,所以需要电平转换电路转换为TTL电平才能实现通信

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

版权声明:本文为CSDN博主「Starry丶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Starry__/article/details/121277494

生成海报
点赞 0

Starry丶

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

RT-Thread Studio移植LAN8720A驱动

RTT网络协议栈驱动移植(霸天虎) 1、新建工程 ​ 工程路径不含中文路径名,工程名用纯英文不含任何符号。 2、用CubeMx配置板子外设 2.1、配置时钟 ​ 按照自己板子配置相应时钟。

【STM32Cube笔记】12-配置外部中断

【STM32Cube笔记】系列文章目录 1-基于STM32的VSCode入门级教程前言 2-STM32Cube安装教程 3-STM32CubeIDE汉化 4-STM32Cube配置时钟设置 5-跑马灯引脚配置 6-Cortex-M7内核基本配

stm32cubemx+HAL+串口接收中断

stm32cubemxHAL串口接收中断 在cubemx配置完串口和global interrupt后需要在keil中添加如下代码。 第一步:在main函数中添加接收中断标志位开启函数 HAL_UART_Receive_IT