编程爱好者联盟 2016-12-08
cnblogs.com Yeats叶子 原创,转载请注明原始地址 - http://www.cnblogs.com/xiedidan/p/ft232h-poc.html
Abstract
FT232H出来有些年了,使用类似FIFO的接口,经过测试,同步模式下器件向PC发送数据可以跑到38-40MByte/s。
但是我发现网上(OpenCores、Amobbs、博客等)找到的FT232H同步模式IP或者示例,每隔512字节就会丢一次数据。
本文就是解决丢数据问题并给出一个正确实现的POC(Proof Of Concept)。
Introduction
硬件:
FPGA开发板:Arrow BeMicro CV
FT232H验证板:FTDI UM232H
软件环境:
Win10
D2XX Driver
Quartus II 15.1
VS2015
参考链接:
FT232H产品详情页
Problem
先重现一下问题,根据FT232H Datasheet上的时序图和Altera DCFIFO的用户手册,参考网上的实现,设计时序如下:
data总线上深色的部分表示被FT232H锁存的数据,注意在传输开始的时候第一个字节是高阻态输出,也就是说开始会有一个字节的错误,这在流式应用中问题不大,也很好解决。
把FIFO的q与FT232H的data信号直接相连,FIFO的读时钟使用FT232H的clock时钟(60MHz)。逻辑实际上是用txe_n来控制wr_n和rdreq,图上没有考虑rdempty信号。
首先考虑wr_n,由于FT232H Datasheet上说txe_n和wr_n同时有效的时候,数据被锁存,所以为了保证速度,利用组合逻辑在txe_n有效的同一拍内使得wr_n有效。
其次是rdreq,这个信号也可以在txe_n有效的同时立即有效,DCFIFO的数据是在rdreq之后一拍有效,所以每次txe_n有效之后,FT232H都会先锁存上一次无效时FIFO打出(但未锁存)的数据。具体参看上图第9拍。
实现以上两个信号的逻辑:
assign wr_n = (~rst_n) | txe_n | rdempty; assign rdreq = rst_n & (~txe_n) & (~rdempty);
编写测试程序发现,每输出512字节就会少一个数据(接收端只收到511个字节),问题得到重现。
Solution
其实看FT232H时序图的时候,就发觉图上有很多问题没说清楚,比如图上txe_n有效之后,wr_n是过了一两个周期才有效的,但是Datasheet并没有硬性标出具体延迟的要求。
检索国外讨论区的时候也有老外指出需要一些额外的延迟来保证数据正确传送(没有说具体要多少个周期)。
那么思路基本明确了,就是让FIFO延迟一些输出数据,也就是rdreq延迟一些(wr_n依然保持与txe_n同步,一次变动一个信号更容易定位问题)。设计新的时序图如下:
这个时序实际上是认为txe_n有效之后的下下拍才会锁存数据。具体实现:
1 always @(negedge rst_n or posedge clock) 2 if (~rst_n) 3 last_txe_n <= 1'b1; 4 else 5 last_txe_n <= txe_n; 6 7 assign wr_n = (~rst_n) | txe_n | rdempty; 8 assign rdreq = rst_n & (~last_txe_n) & (~txe_n) & (~rdempty);
这里引入了一个last_txe_n信号,就是简单的把txe_n同步一拍。
经过测试,这个实现可以稳定在38MByte/s,同时没有数据丢失。
问题解决。
Conclusion
FT232H Datasheet上说txe_n和wr_n同时有效时,锁存数据,这没错。但是前提是txe_n有效之后下下拍这个逻辑才成立——这一点Datasheet上表达的比较含糊。
其次,网上爬文的时候也有网友指出txe_n有可能在时钟下降沿打出来的,这一点在Datasheet的时序图上有反映,但是也没有明确的说明。(不过这个问题不影响逻辑实现。)
但是我不太明白为什么网上的实现都不对,也许是芯片版本或者FIFO IP版本不同?
这个问题在FPGA上需要专门处理。对于MCU等比较慢的场合,应该不用特别考虑延时,跳进中断处理都需要N多个时钟周期了。
最后是我的POC工程:
https://github.com/xiedidan/ft232h-core
其中,ft232h-speed-test是用VC2015编写的测试程序,ft232h_simple是一个简单的写时序示例,ft232h_fifo是结合了Altera DCFIFO的写示例。
安装好编译器和运行插件后,C/C++程序就可以运行了。"request": "launch", // 请求配置类型,可以为launch(启动)或attach(附加)。"stopAtEntry"