FPGA as USB PIA


alex

Recommended Posts

I recently needed to bitbang some data on several IO pins and was considering my options. I didn't want to write HDL code and run it on a FPGA, I didn't even want to write C code and compile it and then run it under a soft processor on a FPGA.

 

What I wanted was to write code on a PC, compile it, run it on the PC and quickly see the the results on a bunch of IO pins, kind of like the old days of the parallel port. My first thought was to use a FT2232H breakout board, the FTDI chip has a bunch of ports and several operation modes:

 

post-29560-0-48337800-1388094755.png

 

However none of these modes would give me more than two 8-bit ports, one for channel A and one for channel B. Then I remembered that on a Pipistrello, Magnus had connected the entire port B to the FPGA allowing parallel data transfer. My thought was, why not use the FPGA's 48 IO pins as a PIA chip (Peripheral Interface Adaptor) and read/write them programmatically from the PC via the FTDI chip.

 

After reading the FTDI datasheet cover to cover as well as the FTDI Programmer's Guide I still had questions as to how to enable the various modes. For example the ftd2xx.h file contains only the following modes that can be set via the FT_SetBitMode function:

#define FT_BITMODE_RESET 0x00#define FT_BITMODE_ASYNC_BITBANG 0x01#define FT_BITMODE_MPSSE 0x02#define FT_BITMODE_SYNC_BITBANG 0x04#define FT_BITMODE_MCU_HOST 0x08#define FT_BITMODE_FAST_SERIAL 0x10#define FT_BITMODE_CBUS_BITBANG 0x20#define FT_BITMODE_SYNC_FIFO 0x40

So how does one set the "245 FIFO" mode from the datasheet? Note that this isn't the same as the "245 FIFO SYNC" which is a special fast mode that uses channel A (connected to JTAG on the Pipi) and also uses all the resources on the FTDI chip which makes channel B become inactive.

 

After much googling it turns out that the answer is easy and a little weird. "245 FIFO" mode is not set programmatically by calling the FT_SetBitMode function, instead this mode is programmed in the FTDI's eeprom chip for channel B, with a free FTDI Utility program, then on powerup, the chip will use "245 FIFO" mode for that channel.

 

With the programming side sorted, it was a simple matter to come up with some HDL code to run on the FPGA that would allow the user to set and read pins. It is a simple state machine that receives commands from the PC and executes them. Each transfer is two bytes, a command and data byte. The 48 pins on the FPGA are treated as six 8-bit ports AL, AH, BL, BH, CL, CH just like the wing groupings. Each pin can be individually set as an output (direction bit = 0) or an input (direction bit = 1). When the pin is an input it is tristated but has a pullup (in the .ucf file) so this can be changed as required.

 

On the PC side the programming is easy, you need to check the FTDI programming manual in the link above which has example code in it for every function call. Here's a quick guide to get something minimal up and running. This is written for Windows (minGW environment) and should therefore apply equally well to Linux.

 

Grab ftd2xx.h and ftd2xx.lib from the drivers package on the FTDI web site. You can drop these into your include and lib directories respectively in your minGW installation.

To compile you need a line similar to gcc -o your_program your_program.c -lftd2xx

 

Inside your program you then need to #include "ftd2xx.h"

 

Some useful defines would be:
#define PORT0   0x00#define PORT1   0x01#define PORT2   0x02#define PORT3   0x03#define PORT4   0x04#define PORT5   0x05#define GET_VAL 0x00#define SET_VAL 0x08#define SET_DIR 0x10

Then you need to open the FTDI port with:

ftStatus = FT_OpenEx("Pipistrello LX45 B", FT_OPEN_BY_DESCRIPTION, &ftHandle);if (ftStatus != FT_OK) {  printf("Failure\n");} else {  printf("Success\n");}

For the above to work you should make sure that your Pipistrello's  board FTDI chip EEPROM is programmed with the name string "Pipistrello LX45", if you have changed it you need to adjust accordingly. 

 

Every function call in the FTDI library returns an FT_OK on success so you need to check that for error handling.

 

To set port 0 to all inputs you would use something like this:

buffer[0]  = SET_VAL | PORT0;buffer[1]  = 0xFF;ftStatus = FT_Write(ftHandle, buffer, 2, &BytesWritten);

To read 1 byte from port 1 you could do this:

buffer[0] = GET_VAL| PORT1;ftStatus  = FT_Write(ftHandle, buffer, 1, &BytesWritten);ftStatus |= FT_Read (ftHandle, buffer, 1, &BytesReceived);

Each USB transaction (call to FT_Read / FT_Write) introduces latency because the data has to be packaged into a USB packet and transferred complete with USB protocol overhead such as handshaking. A trick you could use is to package multiple write commands into just one packet. For example suppose you want to write a value to port 0 and then strobe low a pin on port 1 to indicate to some attached circuit that new data is available. You could do this in three USB calls, write data to port 0, write to port 1 to bring pin low, write to port 1 to bring pin high, or you could just do this in one call:

buffer[0]  = SET_VAL | PORT0;buffer[1]  = 0x12; // output data on port 0buffer[2]  = SET_VAL | PORT1;buffer[3]  = 0xFE; // drop bit 0buffer[4]  = SET_VAL | PORT1;buffer[5]  = 0xFF; // raise bit 0ftStatus  = FT_Write(ftHandle, buffer, 6, &BytesWritten);

Finally, always be a good boy or girl and close the door when you leave.

ftStatus = FT_Close(ftHandle);

This is of course the bare minimum to get you going, you simply must read the FTDI programmer's manual to get a feel of all the functions available to you. I recommend you check out FT_SetUSBParameters, FT_SetTimeouts and FT_SetLatencyTimer. Also a very useful command is FT_Purge to clear the FIFO's.

 

Oh this reminds me. The FTDI chip uses FIFO's on both the read and write channels. This means of course that you don't have to read each data byte immediately if you don't have to. For example suppose you want to read all six ports. You may think you'd have to write the read command for port 0 then read the value returned then repeat for all the other ports meaning you have to call the FTDI procedures in this order: read, write, read, write, read, write, read, write, read, write, read, write.

 

Because of the FIFOs however you could simply send six read commands packaged into one USB packet then simply read six bytes for a total of two function calls, one read and one write as below:

buffer[0]  = GET_VAL | PORT0;buffer[1]  = GET_VAL | PORT1;buffer[2]  = GET_VAL | PORT2;buffer[3]  = GET_VAL | PORT3;buffer[4]  = GET_VAL | PORT4;buffer[5]  = GET_VAL | PORT5;ftStatus  = FT_Write(ftHandle, buffer, 6, &BytesWritten);ftStatus |= FT_Read (ftHandle, buffer, 6, &BytesReceived);

Below is the source code of the project. It's very small and very simple.

Pipistrello_fifo.zip

Link to comment
Share on other sites

Yes, it's pretty easy to use ftd2xx in fifo mode.  One thing though is that I do not recommend using FT_Prog to change port B mode from serial to async fifo due to the way FT_Prog works.  You would think that it will only change that parameter but it will actually wipe out all content of the eeprom and replace it with what it think the data should be based on what is read in during the scan operation.  In the Pipistrello case it will wipe out all Pipistrello-specific data such as the serial number and other information that is written to the eeprom before the board is shipped.

 

If you just want to toggle port B between serial and async fifo mode then I recommend using this little utility:

http://www.saanlima.com/download/pipistrello-v2.0/setmode.zip (windows executable and source code)

 

"setmode fifo" will change port B to async fifo mode

"setmode uart" will change it back to serial mode

Link to comment
Share on other sites

  • 3 years later...

Hi Magnus,

Here is a copy of setmode compiled for Linux Ubuntu x86 -- might be good to include the contents in your setmode.zip?

It would be good to port setmode to libftdi rather than the proprietary libftd2xx library. It should be pretty easy to port but I didn't want change too many things at once and when I got the setmode working with libftd2xx on Linux the motivation to do the porting rapidly dissipated :-P

It also seems that sigrok only works with the pipistrello in FIFO mode, even when I load the serial/uart bit streams? I'm planning on adding some code to the p-ols driver in sigrok to check for this.

Just for interest, I'm currently using sigrok+pipistrello for working on AC'97 for our FPGA SoC. AC'97 is 12MHz, so the 100MHz sample rate is useful to make sure alignment is correct and get multiple samples per bit.

I'm also interested in adding TMDS (HDMI / DVI) decoding to sigrok as part of my "tmds_encoding" stuff in the hope of getting more people understanding that protocol. Do you have any advice on getting the pipistrello to receive that? At 640x480 the pixel clock is 25.175MHz and hence will need the SERDES operating at ~1Gb/s to get 4 samples per bit (as bit clock is 10*pixel clock). This is within the range of the Spartan 6 but will require a bit of fiddling.

Tim 'mithro' Ansell
TimVideos & HDMI2USB.tv

setmode.tar.gz

Link to comment
Share on other sites

Hi Tim,

Cool.  I will include the Linux version.

Yeah, the sigrok p-ols driver is fifo mode only.  In serial mode the (i.e. with  a serial mode bitstream loaded and the FT2232H is serial mode) the board is basically a Open Bench Logic Sniffer with higher baud rate (921600 instead of 115200) and more memory (64MB instead of 24kB) and it should be trivial to modify the sigrok ols driver to support pipistrello in serial mode.  Note that the SUMP protocol limits the samples to 256k so if you want to sample more you need to use the extended range registers that I added (see the p-ols driver).

As for the sampling of TMDS data, you should be able to use the HDMI connector for that but you would need to add the 50 ohm terminator resistor packs on the bottom side of the board (the layout is prepared for this).  Initially the plan was to support both HDMI-out and HDMI-in and an earlier XL9 board I made worked great in both modes but I was sloppy when I did the Pipistrello layout and did not place the CLK pair on GCLK pins :( . However, if you just want to asynchronously sample the TMDS data at 4x the bit rate then this should work fine since you don't use the CLK pair as an input clock.  You would need to change the Pipistrello OLS code to use SERDES for sampling the data etc. but you should be fine from memory bandwidth standpoint (4 bits at ~1Gb/s is 500 MB/s and the memory controller on Pipistrello can easily do that when using the maximum burst length).  If you want to work on this then I can hopefully give you a hand.

Cheers, Magnus

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.