FPGA项目四:串口通信(中)

简介: FPGA项目四:串口通信

下面来设计计数器 cnt1,该计数器用于表示数到第几比特,每个比特结束,即“end_cnt0”时,cnt1 就会加 1。因此 cnt1 的加 1 条件是:end_cnt0。通过设计目标中可知 cnt1 的计数数量为 9。打开 GVIM,继续调用模板,在命令模式下输入“:Mdyjsq”,点击回车。将“add_cnt1”和“end_cnt1”补充完整,得到 cnt1 的代码为:

1670860371390.jpg

在设计 cnt0 时,考虑到其加 1 条件增加了辅助信号 flag_add,下面就来思考如何设计这一信号。通过分析可知 flag_add 具有两个变化点:变 0 和变 1,这两种变化可以从功能上进行理解。工程通电后,如果 PC 端没有发送任何数据,则 uart_rxd 始终保持为 1,cnt0 和 cnt1 无须计数,flag_add 信号也一直保持为 0。当 PC 端要发送数据时,uart_rxd 就会按串口时序产生变化,首先会发送一个开始位,即 uart_rxd 由 1 变成 0。FPGA 接收到这一信号后,就明白 PC 要开始传送数据,此时 cnt0和 cnt1 要计数了,即 flag_add 要变为 1。


可以通过下图来辅助分析,可以看到当 uart_rxd 为 1 时,flag_add 为 0,此时 PC 没有发来任何数据,cnt0 和 cnt1 不计数。当 uart_rxd 数第 1 比特,即开始位时,即通知 FPGA 准备好接收数据,这时 uart_rxd 变 0 时,flag_add 变 1,cnt0 和 cnt1 开始计数。

1670860379898.jpg

从上图可以很容易看出:当 uart_rxd 由 1 变 0 时,flag_add 就由 0 变成 1。其中,uart_rxd 信号的由 1 变 0 波动被称为下降沿,在图中可以清晰的看出此时波形是下降的。那么又如何得知下降沿的到来呢?这里就需要引入一个边沿检测电路工程来辅助设计。


3.2.1 边沿检测电路设计


检测 uart_rxd 的下降沿需要用到 FPGA 中的边沿检测技术。所谓边沿检测,就是检测输入信号或 FPGA 内部逻辑信号的跳变,即检测上升沿或下降沿。这一技术这在 FPGA 电路设计中被广泛应用,其电路图及各信号定义如下所示。

1670860394400.jpg

1670860400592.jpg

可以看出中间信号 trigger 与触发器的信号输入端 D 连接,将 trigger 信号取反后与触发器的输出tri_ff0 相与得到信号 neg_edge。如果 neg_edge=1 就表示检测到 trigger 的下降沿。将触发器的输出tri_ff0 取反与 trigger 相与后得到信号 pos_edge,如果 pos_edge=1 则表示检测到 trigger 的上升沿。


利用这一原理可以画出信号的波形图如下图所示。

1670860408889.jpg

tri_ff0 是触发器的输出,因此 tri_ff0 的信号与 trigger 信号只是相差了一个时钟周期。这里也可以这样理解:每个时钟上升沿看到的 tri_ff0 值实际上是上一个时钟看到的 trigger 信号值,即 tri_ff0的值是 trigger 在上一时刻的值。


以记录的体重值为例来帮助理解,假设第一天的体重是 49kg,第二天是 50kg,第三天是 50kg,第四天为 49kg,可以看到体重值在发生变化。此时 tigger 信号为当天记录的体重值,而 tri_ff0 信号为前一天的体重值,即第二天的 tigger 值为当天体重 50kg,tri_ff0 值为前一天体重 49kg,第三天的tigger 值为当天体重 50kg,tri_ff0 值为前一天体重 50kg,以此类推。在生活中将当天的体重和前一天进行对比就可以得知体重的变化,通过两个信号的对比也能得知信号的变化。比如第二天的 tigger值 50kg,此时 tri_ff0 为 49kg,50 大于 49,可以看到体重上升,对应信号变化即可视作迎来了一次上升沿。综上所述,uart_rxd 的上升沿/下降沿检测依据为这一刻的状态与上一刻的状态有 0 到 1 者 1 到 0 的变化。


从图 3.4- 22 可以看出,第 3 个时钟的上升沿处 trigger 值为 0,而 tri_ff0 值为 1,即 trigger 的值发生了从 1 到 0 的变化,即迎来下降沿,此时 neg_edge 为 1。反之,当 neg_edge 的值为 1,就表示检测到了 trigger 的下降沿。同理,在第 7 个时钟的上升沿处 trigger 值为 1,而 tri_ff0 值为 0,此时 pos_edge 的值为 1,表示检测到了 trigger 的上升沿。

综上所述,可以得出 Verilog 实现边沿检测电路的代码如下:

1670860417271.jpg


3.2.2 异步信号同步化


在边沿检测波形的讨论中将 trigger 信号视为理想的同步信号,即 trigger 满足 D 触发器的建立和保持时间,这种情况下在同步系统中实现边沿检测不是问题。但如果 trigger 信号不是理想的同步信号(如外部按键信号或是本工程的 uart_rxd 信号)时,信号的变化由外部传输指令给 FPGA,对于 FPGA 来说这些信号什么时候产生变化是完全随机的。因此,很可能出现信号在时钟上升沿发生变化,无法满足触发器的建立时间和保持时间要求,从而出现亚稳态,导致系统崩溃。详细原因可以参看至简设计法 D 触发器中亚稳态一节的相关内容。根据这一结论,本设计需要对输入的信号延迟两拍即先用两个触发器寄存后再进行使用。如下图所示,用 2 个触发器对信号进行寄存,确定了信号的稳定性,然后再进行边沿检测,满足了同步系统中实现边沿检测的需求。

1670860441851.jpg

因此,在边沿检测代码设计前需要先对触发器进行设计。假设输入的信号 trigger 不是同步信号,则该信号需要用 2 个触发器进行寄存,得到信号 tri_ff0 和 tri_ff1。需要特别注意的是,在第一个触发器阶段,信号依旧存在亚稳态的情况,因此 tri_ff0 绝对不可以作为条件使用,只能以信号 tri_ff1 作为条件。得到同步信号后用寄存器寄存,得到信号 tri_ff2,根据 tri_ff1 和 tri_ff2 就可以得到边沿检测结

果。即当 tri_ff1==1 且 tri_ff2==0 时,上升沿的 pos_edge 有效;当 tri_ff1==0 且 tri_ff2==1 时,下降沿的 neg_edge 有效,其具体代码如下:

1670860449305.jpg

综上所述,如果输入信号是异步信号,需要先对其进行同步化之后再做检测,即通过延迟两拍的方式实现信号的同步化,再通过延迟一拍的方式实现边沿检测电路。反之,如果输入信号本身就是同步信号,则没有必要进行同步化了,可以直接对其进行边沿检测。


回到本设计中,此时需要检测的是 uart_rxd 的下降沿,并以此为条件拉高信号 flag_add。由于PC 端随机地给 FPGA 发送数据,其发送时刻是不确定的,因此本设计中 uart_rxd 是异步信号。根据边缘检测的设计方法,此时需先将 uart_rxd 进行同步化后再对其进行下降沿检测。其具体设计代码如下:

1670860458112.jpg

如此一来,flag_add 变 1 的条件就变成:uart_rxd_ff10 && uart_rxd_ff21。确定 flag_add 变 1 条件后,再来讨论一下其变 0 的条件。当完成 9 比特数据的传输后,flag_add 的值变为 0,不再计数。因此,其变 0 条件为 end_cnt1。根据以上分析可以得到 flag_add 的代码如下。

1670860474935.jpg

下面来设计 data 信号,根据串口时序可知,该信号的值来自下图中第 2~第 9 比特的值。其中第 2 比特的值赋给 data[0],第 3 比特的值赋给 data[1],以此类推,第 9 比特的值赋给 data[7]。

1670860483760.jpg

由于每一个比特都会持续 5208 个时钟周期,因此必须选定一个具体时刻来将值赋给 data。此处可能会有这样的疑虑:直接在每个比特计数结束的时刻,即在 end_cnt0 的时刻赋值不就可以了么?

然而实际上,这一时刻并不可取。end_cnt0 的时刻在下图中用点表示出来,此时的 5208 个时钟周期是理想、估算的数值,而在实际计算中很有可能出现偏差,如果在 end_cnt0 的时候取值,就有可能会出现错误。

1670860491262.jpg

因此,最保险的做法是在中间点取值,如下图所示。在这种取值方法下,即使有较多的偏差,也不会影响到采样的正确性。

1670860498448.jpg

综上所述,本设计在 cnt0 计数到一半时将采到的当前 uart_rxd 值赋给 led,其中第 2 比特赋给led[0],第 3 比特赋给 led[1],以此类推,第 9 比特赋给 led[7]。将其用代码的形式表现出来则为:当add_cnt0 && cnt0==5208/2 -1 时,如果 cnt1==1,则将 uart_rxd_ff1 赋给 led[0]。如果 cnt1==2,则将 uart_rxd 赋给 led[1],以此类推,如果 cnt1==8,将 uart_rxd_ff1 赋给 led[7]。其具体代码如下所示。

1670860515056.jpg

对以上代码进行优化,可简写为:

1670860523524.jpg

在进行设计时,通常是首先想到实现功能,所以会先写出第一段的优化前代码。在功能实现的前提下,再考虑有没有优化空间,从而对其进行优化得到第二段代码。设计过程中,好的代码也是这样一步步优化出来的。至此,主体程序已经完成。


3.3 信号定义


下面需要将 module 补充完整,首先来定义信号类型。reg 和 wire 的判断很容易搞不清楚总会有其余的联想,比如认为 reg 就是寄存器,wire 是线;或者认为 reg 的会综合成寄存器,wire 不会综合成寄存器。但是这些其实和 reg 型还是 wire 型都是没有关系的,因此在信号类型判断时不需要做任何的联想,只要记住一个规则“用 always 实现的是 reg 型,其他都是 wire 型”就可以了。


cnt0 是用 always 产生的信号,因此类型为 reg。根据前文分析可知,该计数器计数的最大值为5208,因此需要用 13 根线表示,即位宽是 13 位。


关于信号位宽的获取,至简设计法在此分享一个非常实用的技巧:打开计算器,点击“查看”,选择“程序员”模式,在“十进制”下将信号值输入,就会获得对应的信号位宽,如下图所示,将计数器的最大值 5208 输入,可以看出其位宽为 13。

1670860539093.jpg

综上所述,cnt0 的信号定义代码如下:

1670860548989.jpg

cnt1 也是用 always 产生的信号,因此类型为 reg。cnt1 计数的最大值为 9,需要用 4 根线表示,即位宽是 4 位。编辑模式下输入“Reg4”调用至简设计法模板,补充完整后得到代码表示如下:

1670860558553.jpg

add_cnt0 和 end_cnt0 都是用 assign 方式设计的,因此类型为 wire。其值是 0 或者 1,用 1 根线表示即可。编辑模式下输入“Wire1”调用模板,得到代码表示如下:

1670860570581.jpg

add_cnt1 和 end_cnt1 也是用 assign 方式设计的,因此类型为 wire。其值是 0 或者 1,用 1 根线表示即可,其代码如下:

1670860580643.jpg

flag_add 是用 always 方式设计的,因此类型为 reg。其值是 0 或者 1,用 1 根线表示即可。编辑模式下输入“Reg1”调用模板,得到代码如下:

1670860590901.jpg

uart_rxd_ff0、uart_rxd_ff1 和 uart_rxd_ff2 也都是用 always 方式设计的,因此类型为 reg。并且其值是 0 或 1,需要 1 根线表示即可。编辑模式下输入“Reg1”调用模板,得到代码表示如下:

1670860603907.jpg

至此,整个代码的设计工作已经完成,完整的工程代码如下:

1670860614033.jpg

1670860620892.jpg

1670860630681.jpg

相关文章
|
5月前
|
测试技术 异构计算
【FPGA基础入门实践】Verilog 基本项目操作逐步演示
【FPGA基础入门实践】Verilog 基本项目操作逐步演示
78 0
|
异构计算
FPGA项目五:数码管动态扫描(下)
FPGA项目五:数码管动态扫描
150 0
FPGA项目五:数码管动态扫描(下)
|
程序员 异构计算
FPGA项目五:数码管动态扫描(中)
FPGA项目五:数码管动态扫描
205 0
FPGA项目五:数码管动态扫描(中)
|
异构计算
FPGA项目五:数码管动态扫描(上)
FPGA项目五:数码管动态扫描
215 0
FPGA项目五:数码管动态扫描(上)
|
异构计算
FPGA项目四:串口通信(下)
FPGA项目四:串口通信
93 2
FPGA项目四:串口通信(下)
|
芯片 异构计算
FPGA项目四:串口通信(上)
FPGA项目四:串口通信
294 0
FPGA项目四:串口通信(上)
|
异构计算
FPGA项目三:PWM呼吸灯(下)
FPGA项目三:PWM呼吸灯
161 1
FPGA项目三:PWM呼吸灯(下)
|
8天前
|
机器学习/深度学习 算法 异构计算
m基于FPGA的多通道FIR滤波器verilog实现,包含testbench测试文件
本文介绍了使用VIVADO 2019.2仿真的多通道FIR滤波器设计。展示了系统RTL结构图,并简述了FIR滤波器的基本理论,包括单通道和多通道的概念、常见结构及设计方法,如窗函数法、频率采样法、优化算法和机器学习方法。此外,还提供了Verilog核心程序代码,用于实现4通道滤波器模块,包含时钟、复位信号及输入输出接口的定义。
28 7

热门文章

最新文章