文章目录[隐藏]
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
暂无评论