当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/9600≈5208个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
module tb_uart_rx();
reg sys_clk;
reg sys_rst_n;
reg rx;
wire [7:0] po_data;
wire po_flag;
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
rx <= 1'b1;
#20;
sys_rst_n <= 1'b1;
end
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
always #10 sys_clk = ~sys_clk;
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);
end
endtask
uart_rx uart_rx_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.rx (rx ),
.po_data (po_data ),
.po_flag (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
module tb_uart_tx();
reg sys_clk;
reg sys_rst_n;
reg [7:0] pi_data;
reg pi_flag;
wire tx;
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#20;
sys_rst_n <= 1'b1;
end
initial begin
pi_data <= 8'b0;
pi_flag <= 1'b0;
#200
pi_data <= 8'd0;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*20*10);
pi_data <= 8'd1;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*20*10);
pi_data <= 8'd2;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*20*10);
pi_data <= 8'd3;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*20*10);
pi_data <= 8'd4;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*20*10);
pi_data <= 8'd5;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*20*10);
pi_data <= 8'd6;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*20*10);
pi_data <= 8'd7;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
end
always #10 sys_clk = ~sys_clk;
uart_tx uart_tx_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.pi_data (pi_data ),
.pi_flag (pi_flag ),
.tx (tx )
);
endmodule
仿真10ms,结果如下:
可以看出发数的帧结构顺序是正确的。
2.5. 顶层模块RS232设计
接下来就可以进行顶层模块设计,为了测试收发模块的功能,可设计回环测试loopback。
之后再介绍实际场景下的顶层模块如何编写。
2.5.1. 回环测试设计
如图所示loopback
所以顶层模块如下:
`timescale 1ns / 1ps
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
module tb_rs232();
wire tx ;
reg sys_clk ;
reg sys_rst_n ;
reg rx ;
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
rx <= 1'b1;
#20;
sys_rst_n <= 1'b1;
end
initial begin
#200
rx_byte();
end
always #10 sys_clk = ~sys_clk;
task rx_byte();
integer j;
for(j=0; j<8; j=j+1)
rx_bit(j);
endtask
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);
end
endtask
rs232 rs232_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.rx (rx ),
.tx (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
|