This repo contains the needed settings and code to use an FTDI FT232H as an asynchronous FIFO for data transfer from a FPGA (e.g.) to the PC (one direction only). The software was written from scratch using libusb.
The content of this repo is licenced under AGPLv3+ and comes WITHOUT ANY WARRANTY! It is really experimental. I did not disassemble any proprietary stuff.
I needed a way to shove parallel data (from a FPGA) through USB to the PC and this as fast as possible (good old UART is simple but slow!). I had a FT232H board laying around and at first glance it looked promising. However using Linux and not wanting to use proprietary stuff i had a really hard time making all this work (more or less, read on). Inside this repo i summarize my findings and provide some code to hopefuly help others getting started easier.
Please be aware that asynchronous FIFO mode is quite limited in speed and not reliable at all (there will be some data loss) if don't monitor #TXE and buffer your data. For stuff like video/audio this can be acceptable, but if you are transfering e.g. memory dumps you need to take some extra precautions (monitor #TXE and have a way to buffer your data for some time).
The FT232H supports a much faster synchronous FIFO mode, however it is much more difficult to use (at least for people like me who really have a hard time with FPGA-stuff) as you need to synchronize all transfers to a (in this case 60MHz) clock that is provided by the FT232H and you will still have to monitor #TXE and buffer your data somehow.
To use the FT232H in asynchronous FIFO mode you need to program the EEPROM that is (hopefully) attached to the IC. On my board (from Aliexpress) it is a Microchip 93LC56B which is a "2Kb Microwire (3-wire) Serial EEPROM with 16-Bit Organization".
Caution: If you use ftdi_eeprom
(from package ftdi-eeprom
on Debian) always specify a valid VID and PID inside the config file you use. If you don't do this the (stupid!) tool will assume 0x0000/0x0000 and effectively brick your board. It will still be visible by lsusb
but i was unable to erase the EEPROM with any tool i tried. Finally i had to desolder the (tiny - SOT23-6) EEPROM, solder it to an adapter board, use a Bus Pirate to erase it and solder it back. Yeah...
To avoid messing things up again i finally used the official FT_PROG tool inside a Windows-VM. The right setting is "245 FIFO". Maybe - i am not sure, i did - you also need to set the driver-setting to D2XXX.
TODO: How to program the EEPROM correctly with the open ftdi_eeprom
?
As i wanted to avoid proprietary stuff (as always) and because FTDI does not provide FOSS drivers i had to ressort to code based on reverse engineering, that is libFTDI. This stuff is free as in freedom but not so greatly documented imho. It still provides - in addition to ftdi_eeprom
- a tool ftdi_stream
but this is only for synchronous FIFO mode!
At this point i was stuck, so i wrote my own code from scratch, using only libusb
and entirely avoiding libftdi
.
If you want to use the FT232H in FIFO mode you really need to carefuly read the Technical Note 167 from FTDI. On page 12 there is a timing diagram for writing to the FT232H (that is sending data to the PC). If i understand this stuff correctly #WR is limited to 10MHz (period 100ns) which already severely impacts the possible throughput.
Beware of "inverted" logic for the control/status lines!
AD0-7 are the parallel inputs. AC2 is #RD and connected to Vcc (3,3V) as we only do writes. AC3 is #WR and connected to the FPGA or similar, a falling edge will trigger a write. AC1 is #TXE, monitor this signal with a scope (or your FPGA), if it goes high for more than 400ns you can't send more data (and/or you lost some already).
You need libusb
, on Debian and derived distributions try sudo apt install libusb-1.0-0-dev
.
There are 4 #define
that you can mess with before compiling:
- SIZE_BUF: The blocksize used for USB transfers. 512 bytes gave best results during my tests.
- LATENCY_TIMER_VALUE: The so called "latency timer". I did not dig too much into this, as far as i understood this is more important for small, occasional transfers; not continous streaming. I set this to max (255ms) most of the time.
- NB_PAR_TRANSFERS: Number of "parallel" (thats not the right word...) transfers used by the tool/libusb. Something between 8 and 16 (or smaller for occasional transfers) should be fine.
If you need maximum throughput (and can't use the synchronous mode) you will need to experiment with these parameters, at least the last 2.
- RUN_FOR_SECONDS: The number of seconds the program should run before exiting. Set this to 0 to run forever/until Ctrl+C.
gcc -Wall -Wextra -Werror -I/usr/include/libusb-1.0 -O3 -o ft232h_rx main.c -lusb-1.0
The tool does not accept any arguments. It will print some messages to stderr
while opening the first FT232H (0x0403/0x6014) found and then spit out received data on stdout
. For my tests i either used something like ./ft232h_rx | pv -trbW > /dev/null
or ./ft232h_rx | pv -trbW > file.bin
. pv
is a handy tool that will show the passed time, the total size of data that passed the pipe and the calculated throughput.
If you directly get an "Input/Output Error" when starting the tool you might need to do a sudo modprobe -r ftdi_sio
to unload the default driver that claims the FT232H.
Well, as i said above it is not great and highly unpredictable. I did most of my tests with a clock of 7,5MHz on #WR (which equals to a bit more than 7MiB/s throughput) and a LFSR running on the FPGA. Sometimes i had buffer overflows (#TXE high for >400ns) really quick (less than a minute), sometimes (not often) i was able to stream data without problems for several minutes. I randomly checked the received data using some C-code and it was correct. If you want the Verilog code and the piece of C code to generate the LFSR data open an issue, but please be aware that it is hacked together and i am really bad with Verilog/FPGA-stuff...
I did not test "burst writes" as this is not what i am currently interested in / what i currently need.
If you do not need the speed you can use the asynchronous mode, otherwise you will need the synchronous mode with the 60MHz clock provided by the FT232H. In both cases you will need to monitor #TXE and so there must be some kind of buffer inside your FPGA to avoid loss of data. If you use a microcontroller this can be a problem really quick if you lack RAM. For an FPGA with integrated RAM it is still a hurdle as you probably need a dual port FIFO or something like this and these things are a pain (for beginners).