-
Notifications
You must be signed in to change notification settings - Fork 1
Home
Welcome to the ECP5/FT2232HQ wiki!
The first step in the bring up process is to program the EEPROM of the FT2232HQ to enable synchronous FIFO operation on startup. This can be done by using the FTDI provided utilities: FT_PROG or MProg. I used FT_PROG, the older of the two. Note that you will need a Windows machine to run FT_PROG as it is the only platform supported for FT_Prog (and the newer MProg alternative). The essential settings to set in FT_PROG are as follows:
- Enable 'Bus power' operation with current up to 500mA.
- Set Port A and Port B in '245 FIFO' mode.
- Set the Driver for Port A and Port B to D2XX.
Once all these settings are selected, press the EEPROM program button. You can find all the details of using FT_PROG in the FTDI User Guide for FT_PROG.
The next step is to program the FPGA with the basic loopback Verilog code. This code simply loops back each byte received from the FT2232 in the most basic way. The Verilog code is in the /hdl_loopback directory. Here are the steps to follow:
- Download the open source oss-cad-suite 0.1.0, unpack it into a folder say "/home/gil/tools-oss-cad-suite-0.1.0/".
- Edit fpga.sh to update the TOOLS_PATH shell script variable to point to the oss-cad-tools directory:
TOOLS_PATH="/home/gil/tools-oss-cad-suite-0.1.0"
- Run ./fpga.sh. Depending on how permissions are set on your machine you may have to enter your root password so that the openFPGALoader can write the bitstream to the FPGA RAM. The successful outcome looks like this:
Jtag probe limited to 3MHz
Jtag frequency : requested 6000000Hz -> real 3000000Hz
ret 0
Open file: DONE
b3bdffff
Parse file: DONE
Enable configuration: DONE
SRAM erase: DONE
Loading: [==================================================] 100.00%
Done
Disable configuration: DONE
The hardware uses a green LED to indicate that the ECP5 bitstream was successfully written to the FPGA SRAM.
The instructions for setting up the FTDI D2XX Drivers are included in the D2XX Driver download for your specific platform. I followed the instructions for Linux and MacOSX and the drivers installed without any problems.
Note that the driver download contains source code for sample apps. I built the samples and I used the EEPROM/read and EEPROM/write examples to test if I can read and write the EEPROM. The reason this was useful to me is that when I used FT_Prog in Windows I changed the Vendor ID in EEPROM and after that FT_Prog no longer discovered my FT2232 device (which is expected). The simple solution was to modify the EEPROM/write example by adding a call to FT_SetVIDPID with my new Vendor ID and Product ID so that FT_Open would work, then and setting the EEPROM fields (including VID and PID) to whatever values my application required. After successfully writing the EEPROM I got back to the default VID and PID.
In the host_loopback directory type make:
> make
cc main_loopback.c -o ft2232 -Wall -Wextra -lftd2xx -lpthread -lrt -Wl,-rpath /usr/local/lib -L/usr/local/lib
Finally run the app with ./run.sh.
> ./run.sh -c 64000 -p 128
Send bytes: 64000, packet bytes: 128
Done sending 64000 packets
8192000 bytes sent, 8192000 bytes received in 8713 ms. Tx: 940 KBps, Rx: 940 KBps
==== Test successful ====
The reason I am using a script to run the host app is the two lines before the ft2232 command line (see run.sh):
if lsmod | grep -wq "ftdi_sio"; then
sudo rmmod ftdi_sio
fi
if lsmod | grep -wq "usbserial"; then
sudo rmmod usbserial
fi
This eliminates the conflict between the D2XX drivers and the FTDI sio driver.
Optionally you can test the loopback Verilog code, without the host code by using the Verilog code available for simulation in sim_ft22232.sv. To run the loopback simulation use sim.sh.
> ./sim.sh -p 8
0INIT: Clock period 40690(ps).
20345 CORE: -- STATE_RESET_BEGIN
20345 FT_FIFO: -- Reset.
20345 FT2232: -- Reset.
427245 CORE: -- STATE_RESET_END
427245 FT2232: ---> Sending: 0.
458315 FT_FIFO: [STATE_RD] Read: 0.
474981 FT2232: ---> Sending: 1.
491647 FT_FIFO: [STATE_WR] Wrote: 0.
508313 FT2232: <--- Received: 0.
524979 FT_FIFO: [STATE_RD] Read: 1.
541645 FT2232: ---> Sending: 2.
558311 FT_FIFO: [STATE_WR] Wrote: 1.
574977 FT2232: <--- Received: 1.
591643 FT_FIFO: [STATE_RD] Read: 2.
608309 FT2232: ---> Sending: 3.
624975 FT_FIFO: [STATE_WR] Wrote: 2.
641641 FT2232: <--- Received: 2.
658307 FT_FIFO: [STATE_RD] Read: 3.
674973 FT2232: ---> Sending: 4.
691639 FT_FIFO: [STATE_WR] Wrote: 3.
708305 FT2232: <--- Received: 3.
724971 FT_FIFO: [STATE_RD] Read: 4.
741637 FT2232: ---> Sending: 5.
758303 FT_FIFO: [STATE_WR] Wrote: 4.
774969 FT2232: <--- Received: 4.
791635 FT_FIFO: [STATE_RD] Read: 5.
808301 FT2232: ---> Sending: 6.
824967 FT_FIFO: [STATE_WR] Wrote: 5.
841633 FT2232: <--- Received: 5.
858299 FT_FIFO: [STATE_RD] Read: 6.
874965 FT2232: ---> Sending: 7.
874965 FT2232: Done sending.
891631 FT_FIFO: [STATE_WR] Wrote: 6.
908297 FT2232: <--- Received: 6.
924963 FT_FIFO: [STATE_RD] Read: 7.
958295 FT_FIFO: [STATE_WR] Wrote: 7.
974961 FT2232: <--- Received: 7.
974961 FT2232: ==== Test successful. Received 8 bytes.
The performance depends to a great extent to the size of the packet being sent. I ran the host app with different packet sizes and you can see the results below. "No loopback" means that I modified the FPGA code to just read the data out of the FT2232 and throw it away (the fastest FPGA can run) i.e. no loopback. "With loopback" means FPGA loopback is enabled.
./run.sh -p 16384 -c 1000
No loopback: 16200000 bytes sent, 0 bytes received in 575 ms. Tx: 28173 KBps, Rx: 0 KBps
With loopback: 16384000 bytes sent, 16384000 bytes received in 1674 ms. Tx: 9787 KBps, Rx: 9787 KBps
./run.sh -p 8192 -c 1000
No loopback: 8192000 bytes sent, 0 bytes received in 344 ms. Tx: 23813 KBps, Rx: 0 KBps
With loopback: 8192000 bytes sent, 8192000 bytes received in 893 ms. Tx: 9173 KBps, Rx: 9173 KBps
./run.sh -p 4096 -c 1000
No loopback: 4096000 bytes sent, 0 bytes received in 239 ms. Tx: 17138 KBps, Rx: 0 KBps
With loopback: 4096000 bytes sent, 4096000 bytes received in 485 ms. Tx: 8445 KBps, Rx: 8445 KBps
./run.sh -p 2048 -c 1000
No loopback: 2048000 bytes sent, 0 bytes received in 186 ms. Tx: 11010 KBps, Rx: 0 KBps
With loopback: 2048000 bytes sent, 2048000 bytes received in 283 ms. Tx: 7236 KBps, Rx: 7236 KBps
./run.sh -p 1024 -c 1000
No loopback: 1024000 bytes sent, 0 bytes received in 159 ms. Tx: 6440 KBps, Rx: 0 KBps
With loopback: 1024000 bytes sent, 1024000 bytes received in 185 ms. Tx: 5535 KBps, Rx: 5535 KBps
./run.sh -p 512 -c 1000
No loopback: 512000 bytes sent, 0 bytes received in 156 ms. Tx: 3282 KBps, Rx: 0 KBps
With loopback: 512000 bytes sent, 512000 bytes received in 158 ms. Tx: 3240 KBps, Rx: 3240 KBps
That's it! The hardware verified and the host and FPGA code is tested in loopback mode.
One thing I did not do yet is to write the bitstream to the EEPROM. At this time the bitstream is written to the FPGA SRAM and therefore after powering the board down I will have to write the bitstream again after powering up next time. I still make many changes to the Verilog code and therefore writing to EEPROM is not useful at this time. I will do that in a later stage of the project.
Writing the loopback Verilog code mentioned in the Bring-up section was useful but the goal of the project is to provide Verilog code for interfacing to the FT2232 over the synchronous FIFO interface. Two asynchronous FIFOs are used by an application to send and receive data to and from the host allowing the application to operate at a clock frequency different than 60MHz (which is what the FT2232 synchronous FIFO is operating at). The following diagram shows the software architecture with its most important blocks.

The most important Verilog module is in ft2232_fifo.sv. This module interfaces on one side to the FT2232 synchronous FIFO and on the other side with two asynchronous FIFOs. At first look it looks more complicated than it needs to be but if you look closer you'll see that the code needs to switch between read and writes on the FT2232 side and it has to deal with FIFO full/empty on both sides. I wish that the FT2232 have separate in and out 8 bit buses and not one 8 bit bidirectional bus that is controlled by the OE signal. I would accept the increased cost due to more pins.
The host and the HDL code implement three tests: TEST 0: The host sends frames to the FPGA. TEST 1: The host sends frames to the FPGA and the FPGA sends frames back to the host each containing one byte received. TEST 2: The FPGA sends frames to the host.
A frame has a one byte header: three bits represent the command type and five bits represent the 'encoded' number of bytes following the header. If bit 4 is not set the last 4 bits represent the size of the payload. If however bit 4 is set, the last 4 bits are ignored and the next two bytes specify the length of the payload. This scheme allows optimization of small frames as well as frame payloads up to 16,383 bytes. For all tests the size of the frame can be configured from 1 to 16,383 bytes. The number of frames sent can be configured from 1 to 16,383.
The commands sent by the host C code are as follows:
CMD_HOST_START is sent with a payload of 3 bytes:
- The test to run (from 0 to 2) [1 bytes]
- The size of frame payload [2 bytes]
- The number of frames sent [2 bytes]
CMD_HOST_DATA is sent with the payload specified by a command line parameter.
CMD_HOST_STOP has no payload and it is used by the host when it completes the test.
The commands sent by the FPGA are as follows:
CMD_FPGA_DATA is sent with a payload of bytes representing data originating at the FPGA. The data consists of monotonically increasing byte values (that persist across multiple CMD_FPGA_DATA commands).
CMD_FPGA_LOOPBACK is used to loop back data received by the FPGA with a payload of one byte. It was only used for debugging.
Finally CMD_FPGA_STOPPED is always sent by the FPGA at the end of a test. This command can be sent in response to CMD_HOST_STOP (for test 0 and 1) or when the FPGA is done sending all the data it needed to sent (test 2).
The host sends CMD_HOST_START [test 0, number of payload bytes per frame, number of frames]. The host sends CMD_HOST_DATA commands with the number of payload bytes specified by CMD_HOST_START. It sends a number of such frames equal to the number of frames specified in CMD_HOST_START. The host sends CMD_HOST_STOP to which the FPGA responds with CMD_FPGA_STOPPED [error code]. If there is no error, the error code is 0. CMD_FPGA_STOPPED may be sent at any time by the FPGA if an error is detected during the test. For example if the FPGA receives a CMD_HOST_DATA command with a payload that does not match the one specified in the CMD_HOST_START, it will send an error code as well as the payload it received. The error codes are defined in test_definitions.svh.
The host sends CMD_HOST_START [test 1, number of payload bytes per frame, number of frames]. The host sends CMD_HOST_DATA commands with the number of payload bytes specified by CMD_HOST_START. It sends a number of such frames equal to the number of frames specified in CMD_HOST_START. The FPGA sends back a CMD_FPGA_DATA frame with a payload of one for each payload byte received from the host. The host sends CMD_HOST_STOP to which the FPGA responds with CMD_FPGA_STOPPED [error code]. Again, CMD_FPGA_STOPPED may be sent at any time by the FPGA if an error is detected during the test.
The host sends CMD_HOST_START [test 2, number of payload bytes per frame, number of frames] telling the FPGA to send the specified number of frames each with the specified number of payload bytes. The FPGA sends CMD_FPGA_DATA commands per the host request. The FPGA sends CMD_FPGA_STOPPED indicating to the host that it is done sending data.
The Verilog code that implements the modules in the block diagram depicted above is located in the /hdl_test directory and the C code is in the /host_test directory.
First let's get the configuration out of the way. Edit fpga.sh and sim.sh to update the TOOLS_PATH shell script variable to point to the oss-cad-tools directory:
TOOLS_PATH="/home/gil/tools-oss-cad-suite-0.1.0"
Next you need to program the FPGA with the test code. To do that using the fpga.sh script:
> ./fpga.sh
One useful command line parameter is 'f'. It configures the number of address bits for the IN and OUT asynchronous FIFOs. The default is 5 bits (32 bytes). I tested with a FIFO as small as 8 bytes and as large as 256 bytes.
> ./fpga.sh -f 3
To build the host app type 'make' in the host_test directory:
> make
c main.c -o ft2232 -Wall -Wextra -lftd2xx -lpthread -lrt -Wl,-rpath /usr/local/lib -L/usr/local/lib
You are now ready to run the tests by using the run.sh script (which invokes the app you just built). Run test 0:
> ./run.sh -t 0 -p 1 -c 1 -v
Start test: 0, payload length: 1, packet count: 1
Sending: 5
Sending: 0
Sending: 0
Sending: 1
Sending: 0
Sending: 1
Sending: 48
Sending: 0
Sending: 1
Sending: 0
Sending: 64
CMD_FPGA_STOPPED with payload: 1 bytes
===== Test OK =====
11 bytes sent, 2 bytes received in 3 ms. Tx: 3 KBps, Rx: 0 KBps
Run test 1:
> ./run.sh -t 1 -p 1 -c 2 -v
Test number: 1, payload length: 1, packet count: 2
Sending: 3
Sending: 1
Sending: 1
Sending: 2
Sending: 65
Sending: 0
Sending: 65
Sending: 1
Sending: 128
CMD_FPGA_DATA with payload: 1 bytes
STATE_RX_STREAM_PAYLOAD: 0
CMD_FPGA_DATA with payload: 1 bytes
STATE_RX_STREAM_PAYLOAD: 1
CMD_FPGA_STOPPED with payload: 1 bytes
===== Test OK =====
9 bytes sent, 6 bytes received in 3 ms
Run test 2:
> ./run.sh -t 2 -p 2 -c 1 -v
Start test: 2, payload length: 2, packet count: 1
Sending: 5
Sending: 2
Sending: 0
Sending: 2
Sending: 0
Sending: 1
STATE_RX_STREAM_PAYLOAD_LENGTH: 2 bytes
STATE_RX_STREAM_PAYLOAD: 0
STATE_RX_STREAM_PAYLOAD: 1
CMD_FPGA_STOPPED with payload: 1 bytes
===== Test OK =====
6 bytes sent, 7 bytes received in 1 ms. Tx: 6 KBps, Rx: 7 KBps
Of course you can test with more data by using up to 16363 bytes of payload and up to 16383 frames. To evaluate the speed of the transfer from the host to the FPGA, run test 0 without the verbose flag:
./run.sh -t 0 -p 16383 -c 10
Start test: 0, payload length: 16383, packet count: 10
===== Test OK =====
163867 bytes sent, 2 bytes received in 18 ms. Tx: 9103 KBps, Rx: 0 KBps
To evaluate the speed of the transfer from the FPGA to the host, run test 2 without the verbose flag:
./run.sh -t 2 -p 16383 -c 10
Start test: 2, payload length: 16383, packet count: 10
===== Test OK =====
6 bytes sent, 163862 bytes received in 19 ms. Tx: 0 KBps, Rx: 8624 KBps
One useful command line parameter during debugging was the run.sh shell script 's' option. It outputs bytes one by one with a half a second delay between bytes.
Tests run also entirely in Verilog due to the simulation code in sim_ft2232.sv.
You can run the simulator by using the sim.sh shell script:
> /sim.sh -t 0 -p 1 -c 1 -f 4
0 INIT: Clock period 40690(ps).
20345 CORE: -- STATE_RESET_BEGIN
20345 FT_FIFO: -- Reset.
20345 CTRL: -- Reset.
20345 FT2232: -- Reset.
427245 CORE: -- STATE_RESET_END
427245 FT2232: ---> [STATE_OUT_START_CMD] 3.
474981 FT_FIFO: ---> [STATE_RD_DATA] Wr IN: 3 (IN afull: 0, full: 0). RD : 0
491647 FT2232: ---> [STATE_OUT_START_PAYLOAD] [Test number] 0.
508313 FT_FIFO: ---> [STATE_RD_DATA] Wr IN: 0 (IN afull: 0, full: 0). RD : 0
524979 FT2232: ---> [STATE_OUT_START_PAYLOAD] [Packet length] 1.
541645 FT_FIFO: ---> [STATE_RD_DATA] Wr IN: 1 (IN afull: 0, full: 0). RD : 0
558311 FT2232: ---> [STATE_OUT_START_PAYLOAD] [Packet count] 1.
574977 FT_FIFO: ---> [STATE_RD_DATA] Wr IN: 1 (IN afull: 0, full: 0). RD : 0
591643 FT2232: ---> [STATE_OUT_DATA_CMD] 65.
608309 FT_FIFO: ---> [STATE_RD_DATA] Wr IN: 65 (IN afull: 0, full: 0). RD : 0
624975 FT2232: ---> [STATE_OUT_DATA_PAYLOAD] 0 (remaining 0).
641641 FT_FIFO: ---> [STATE_RD_DATA] Wr IN: 0 (IN afull: 0, full: 0). RD : 0
658307 FT2232: ---> [STATE_OUT_STOP_CMD] 128.
671385 CTRL: ---> [STATE_FIFO_CMD] Rd IN: CMD_HOST_START.
674973 FT_FIFO: ---> [STATE_RD_DATA] Wr IN: 128 (IN afull: 0, full: 0). RD : 0
752765 CTRL: ---> [STATE_FIFO_PAYLOAD for CMD_HOST_START] [Test number] Rd IN: 0.
834145 CTRL: ---> [STATE_FIFO_PAYLOAD for CMD_HOST_START] [Payload bytes] Rd IN: 1.
915525 CTRL: ---> [STATE_FIFO_PAYLOAD for CMD_HOST_START] [Packet count] Rd IN: 1.
996905 CTRL: ---> [STATE_FIFO_CMD] Rd IN: CMD_HOST_DATA payload bytes: 1.
1078285 CTRL: ---> [STATE_FIFO_PAYLOAD for CMD_HOST_DATA] Rd IN: 0.
1159665 CTRL: ---> [STATE_FIFO_CMD] Rd IN: CMD_HOST_STOP.
1159665 CTRL: ==== TEST OK ====.
1200355 CTRL: <--- [STATE_WR_BUFFER] Wr OUT [ 0]: 193.
1241045 CTRL: <--- [STATE_WR_BUFFER] Wr OUT [ 1]: 0.
1308281 FT_FIFO: <--- [STATE_WR_DATA] Wr FT2232: 193.
1324947 FT2232: <--- [STATE_IN_CMD] CMD_FPGA_STOPPED [payload bytes: 1].
1358279 FT_FIFO: <--- [STATE_WR_DATA] Wr FT2232: 0.
1374945 FT2232: <--- [STATE_IN_PAYLOAD for CMD_FPGA_STOPPED] Error code: 0.
1374945 FT2232: ==== TEST OK ====.
5000000000 SIM: ---------------------- Simulation end [Timeout] ------------------------
The simulator is more verbose to help the user understand the code behavior.
To understand how an application can be developed based on the proposed architecture I developed the code in the hdl_audio and the matching host_audio directories. The host_audio code reads the contents of a WAV file and sends it to the FPGA by specifying an output port: I2S or a variant of SPDIF such as TOSLINK, COAX or AES3. The FPGA reads the data and redirects the audio samples to the I2S or the SPDIF modules. An extension board was developed for providing I2S over HDMI and COAX output.
The SPDIF and I2S outputs have not been verified. The intent was is verify it against a DAC that has I2S and SPDIF COAX inputs. One such DAC is SMSL D400EX.
Checkout the project and open it. In the Configure Paths dialog add: Name: ECP5_BGA381_FT2232HQ_FIFO and Path: "The full path to the GitHub directory"/GitHub/ECP5_BGA381_FT2232HQ_FIFO
In the Manage Symbol Libraries click the Project Specific Libraries and add: Name: ECP5_BGA381_FT2232HQ_FIFO and Library Path: ${ECP5_BGA381_FT2232HQ_FIFO}/symbols/Symbols.kicad_sym
In the Manage Footprint Libraries click the Project Specific Libraries and add: Name: ECP5_BGA381_FT2232HQ_FIFO and Library Path: ${ECP5_BGA381_FT2232HQ_FIFO}/footprints/Footprints.pretty