完FPGA板子读代码是很重要的,特别是对于学C语言的宝贝们,从编程转来搞FPGA可能会对pipeline的编程比较不适应。
上篇点灯的代码解读讲了一些特别基础的代码,这里的串口通讯如果能自己写出来,可谓是入了门了。
本文可以帮助初学者整理进行FPGA开发必要的思维流程,这是非常非常重要的,只有思路对了,以后的路才会顺畅。

废话不多说,我们直接烧程序,先有个感性认知。

然后在Ubuntu下打开gtkterm,可以通过USB转串口的设备节点跟板子通讯,本例是一个回显的例子,接收的数据会直接发送回去,所以键盘按下什么就能返回什么。

这种时候我们可能会以为出现了幻觉,所以可以把代码改一下,在这里给它加个1。

烧录以后,再按按键,发现123变成了234。果然不是幻觉,我们可以继续看代码了,经验上讲以上步骤还是必要的,以免被坑。

同样还是从top顶层模块看起,输入脚有个25m的时钟,输出有个灯,这跟点灯程序一样。然后串口有收发两个脚。然后我去对一下原理图。

原理图要看底板的原理图,第三页,就是我们用来供电的那个typeC口其实是连接到一个USB转串口的模块,也就是我们熟悉的CH340芯片,玩单片机的宝贝们肯定都懂。这个原理图其实看着有点晕,反正大致意思就是串口发送接收接到了H10和H11这两个脚上。

管脚配置里面可以看到,外部时钟脚和led的脚跟之前点灯的程序一样,下面就是多了H10和H11两个脚,分别对于串口的RX和TX。然后它们的电平是3.3V的。如果这里看着难受的话可以把它改到其他脚上,然后接一个自己的USB转串口的板子上,自己的串口板只需要多接个GND地线,无需接3.3V电源,加上RX和TX一共3根线,注意RX和TX可能需要反一下。

https://zhuanlan.zhihu.com/p/689643287
到这里又要啰嗦一下,FPGA调一个接口,首先就是我们需要清楚的知道接口有个的数据定义,协议等知识。不能上来就研究代码,否则可能会迷糊。这里是我搜索出来知乎的帖子,讲串口协议的,有需要的话可以补充一下底层知识。
当然相信没几个人不知道串口的,但FPGA开发它还是蛮多套路的,比如接收一帧数据该如何接收。
大致的思路是这样的,我们需要用一个比串口波特率更高的采样信号去采集,串口的RX上什么时候出现起始位,然后接收每个数据位,最后延时一个停止位,再循环检测起始位,接收下一帧数据。

然后代码我就不再讲倍频和reset逻辑了,点灯程序已经讲过了。
直接看接收的代码。
这里看到模块的调用,可以想象成我们在板子上焊了一块芯片,它有一个clk脚rst_n脚;然后连到了top顶层的UART的RX上;收到数据后会返回一个接收完成的标志,8位的接收到的数据;最后还需要一个比波特率大的,这里是大16倍的采样时钟。

进入接收模块的代码里,我们看看芯片内部是怎样实现的。这里作者用ASCCII码画了一个时序图,这个太有用了,看代码的时候需要反复的看这个图。
简单说一下这个时序图,IDLE的时候是检测起始位的状态,起始位start是bit0,这里看着它是低电平的;然后到接收数据的状态,也就是bit1到bit8这8位;最后是end停止位,回到IDLE。
所以程序的思路就有了,我们需要在16倍波特率的采样周期上,不停的检测RX脚上的电平,完成串口通讯一帧数据的接收。

接着看代码,作者大神首先把RX脚做了个同步,这个套路不看代码是学不到的,久了看见这种代码脑袋里面会浮现出一个时序图,大概就能看到clk和rx信号的时序,然后理解到为什么要同步。
注意这个模块有两个时钟,一个是系统时钟,一个是16倍的波特率的时钟,它这同步的是系统时钟。咱们先别晕在这里,继续往下看。

后面的代码是个状态机,这代码还有点多,我抓屏一爪还抓不完。大家可以打开代码自己对着看,反正行号可以看出是讲到哪里了。状态机跟作者的时序图是一致的,就是那些IDLE,start,end之类的状态。
这里还有个特别玄乎的套路,本人也不是大神,所以也没看明白。就是这个状态机是用系统时钟来检测的,那个16倍波特率的采样时钟是在下面用个if来判断的,就是采样信号为高的时候去检测数据信号的高低。本宝认为,为什么不直接把采样信号放在always语句上面用呢。手贱的同学可以改一下试试,看看串口会不会丢数据,试完记得告诉我结论。

IDLE状态没什么看头,我们看看start状态。它这里有个采样的计数,一个bit采样16次。这里首先是从IDLE进入START状态需要rx管脚为低电平,并且在16次采样的中间那会不变成低电平,才认为起始信号有效。(为什么不判断全部为高?或者前半段为高?)
反正最重要的是数到16个采样就切到下一个状态,不能快也不能慢,保证时序要求。
这里还有一个套路是我们会看到那些a=a,b=b的语句,其实我感觉是可以删掉的,不知道是不是作者年级比较大,或许以前古时候的综合器不写else后面的东西,它会乱综合一些东西出来。好奇宝宝们可以写个测试程序,看看RTL电路有什么区别,同样,测试出结论以后记得告诉我……

采样状态的逻辑看了半天,也没真正做采样的事情,只是输出了一个变量rxd_cnt,这个变量表示采样的是第几个bit,还要注意的是这个变量在什么时候被锁存改变的,我们注意到是在采样计数为最后一个的时候锁存的。

停止状态啥也没干,只是保证延时一个波特率的时间而已。

接着看代码,上面采样状态输出了一个变量rxd_cnt,然后这里才进行真正的采样操作,它同时判断了采样计数器cmp_cnt,保证是在波形中部进行采样。可能这么做是增加鲁棒性。相信看到这里有些人会表示不服,我们可以把串口线接长一点,中间再加点电磁干扰,这样比较一下如果不在中间采样会不会导致丢数据的比率变高。同样,如果有人测试了,记得把结论告诉我!!!

最后一段代码是输出一帧率数据接收完成的标志,可以看到它的锁存逻辑是在停止位发送完毕的时候持续了一个采样时钟的高电平。同时锁存输出数据。

再往下就是看发送逻辑了,这块逻辑跟接收逻辑几乎一样。其实发送逻辑大可不必这么精细,因为接收才需要高频率的采样,发送只要保证时序就可以了。如果实在看不懂接收逻辑,我建议大家还是直接写一下发送逻辑,比如就按作者画的时序图,先用pll生成一个波特率的时钟,再按照时钟调整TX管脚的高低电平即可。最后用上面讲的接长串口线的方法测试一下数据传输的效率。

大半夜的不知道谁拍了我一下,我就突然看到这块代码有点不对……这个case里面的语句的等于符号前面居然没有小于符号了。这种情况奶奶没教过啊,而且它always语句里面是个*号。其实verilog我也是只学了三天,所以不太确定这是不是就是个组合逻辑电路。哎,这个迷哪位好心人回帖告诉我一声好不好。

万一有人整篇文章都看不懂,这里有个好玩的东西,test_io这个脚是接到灯上的,它这里的逻辑是TX和RX上只要有数据变化,灯就会闪烁,实测我一直往串口输入字符a,这个灯是可以看到闪烁的,闪的比较暗,大家可以关灯看看。
总结一下,关于接口的实现,无论接口多复杂,其实也是逻辑电平的控制,但前提是需要对协议非常的熟悉,再就是一下FPGA代码的套路了,这些套路都是一点一点看众大神的代码悟出来的。