This is the final post in a series of three to explain how to use the FT232RL USB to UART Bridge to program a Cyclone II FPGA.
The first part in the series describes how to compile and install the hardware drivers in Ubuntu that we need to use to access the FT232RL functions. The second part describes the hardware modifications and connections required to interface the FT232RL to SparkFun’s Cyclone II Breakout board.
Let me give you a quick high-level overview of what we must do in order to configure the Cyclone II using the FT232RL, after which we will dive head first into all the juicy details.
High Level Steps:
In Quartus II:
- Convert the FPGA Config file from SOF to RBF (Raw Binary File) in Quartus.
In Python:
- Read and process the .rbf configuration file into a serial bit stream. We then need to “weave” in the DCLK transitions to create a parallel byte stream which will be sent to the FT232RL. More on that in a bit.
- Open a connection to the FT232RL hardware, and configure it for Bit Bang mode operation.
- Trigger the Cyclone II into configuration mode.
- Push the bit stream to the FPGA.
- Close our FT232RL connection.
Generate the RBF File (Quartus II)
Once you “compile” (synthesis) an FPGA design in the Quartus II software, the tool generates a file which contains all the information required to configure your FPGA. However, the default file type is a .SOF which I’m not sure how to manage. However there are other file types available to use for configuration, so much so that Quartus II has a built in tool to convert between formats.
We will be converting the SOF file to an RBF, Raw Binary File which appears to be easier to work with.
The graphic below highlights these steps you will need to take:
(First, open the Convert Programming File tool from the menu.)
- Select the Programming file type as “Raw Binary File (.rbf)
- Set the new file’s path and name.
- Add the source SOF file to be converted. (This file should be in the root directory of your FPGA’s project after you synthesis the design)
- Click on the Properties button and enable Compression. This reduces the number of bits we need to send. (The CII has an onboard decompression engine)
- The final step is to click the “Generate” button.
Python Code
First, I just want to throw all the code out there for people who would like to copy & paste it in it’s entirety, next I’ll break it down and explain its components.
''' Created on Feb 21, 2011 @author: Chris Zeh ''' from binascii import hexlify import ftdi NCONFIG_HIGH = 0x4 #Bitbang Pin #3 (NCONFIG) DCLK_HIGH = 0x2 #Bitbang Pin #2 (DCLK) def InitFDTI(): _ftdic = ftdi.ftdi_context() ftdi.ftdi_init(_ftdic) ftdi.ftdi_usb_open(_ftdic,0x0403,0x6001) #The last 2 params identify the Vendor_ID and Product_ID programmed into your FT chip. 0x6001 is FT232RL #Go here to find the default Product_ID for different parts: #www.ftdichip.com/Documents/technicalnotes/tn_100_usb_vid-pid_guidelines.pdf ftdi.ftdi_enable_bitbang(_ftdic,0xFF) ftdi.ftdi_set_baudrate(_ftdic,9600*16) #Not certain what is the fastest setting possible yet. return _ftdic def StartConfig(ftdic): #To start the config process we need to take FPGA's NCONFIG pin LOW then HIGH ftdi.ftdi_write_data(ftdic,chr(0),1) ftdi.ftdi_write_data(ftdic,chr(NCONFIG_HIGH),1) def toBINstr(hexstr): #Converts a hex string ('\xff' to a binary str 11111111) data_byte = eval('0x' + hexlify(hexstr)) bindata = '' for b in range(7,-1,-1): bindata+=str(data_byte>>b & 1) return bindata def ReadFile(path): f = open(path,'r') dat = f.read() f.close() return dat #Read all of the data into a string ftdic = InitFDTI() print 'Device Opened' StartConfig(ftdic) data = ReadFile('/home/chris/Projects/Saturn Project/FPGA Code/Hello World/Hello_World_Compressed.rbf') NewData='' print 'Preparing the Config Data' #We set the current state of all 8 Bit-Bang pins of the FT232RL by sending it one byte. # each bit in said byte cooresponds to one pin on the FT232RL. #So we need to convert the bits in the RBF file into bytes to send to the FT232RL, setting the DATA0 pin and then "clocking" it in. #The following generates the byte stream from the RBF file. #Basically, every config bit we send to the FPGA needs to be clocked in. #So take the DCLK low. Set the DATA0 to the value of the bit we want #to send. Then take the DCLK high. Repeat for all bits in the file. #You can see we need to send 3 Bytes to the FT232RL in order to send one #configuration bit. for byte in range(0,len(data)): bits = toBINstr(data[byte]) for bit in range(-1,-9,-1): #The strange range parameters are to reorder the bits in the RBF correctly for the FPGA NewData += chr(int(bits[bit]) | NCONFIG_HIGH) #Set DATA0 to the bit, Set DCLK Low. (Be sure to keep holding up the NCONFIG pin) NewData += chr(int(bits[bit]) | NCONFIG_HIGH | DCLK_HIGH) #Clock the data in NewData += chr(int(bits[bit]) | NCONFIG_HIGH) #Falling edge of the clock, this can probably be removed but I have to check the timing to be sure (setup/hold times). print 'Writing to the FPGA' #The following code is/was needed for version 0.17-1 of the ftdi driver (There is a bug where too large a buffer kicks back a -14 memory error) #Re = '' #for x in range(1,24): #Re = NewData[(x-1)*2**18:x*2**18] #rc = ftdi.ftdi_write_data(ftdic,Re,len(Re)) #print rc #Version 0.18 allows for the full buffer to be passed: rc = ftdi.ftdi_write_data(ftdic,NewData,len(NewData)) print 'Configured!' ftdi.ftdi_usb_close(ftdic)
Breaking down the code.
Alright, we’ll skip past the declarations for now and start with the code in the order it executes.
First step is to initialize our connection with the ftdi driver and connect to the FT232RL device:
ftdic = InitFDTI()
def InitFDTI(): _ftdic = ftdi.ftdi_context() ftdi.ftdi_init(_ftdic) ftdi.ftdi_usb_open(_ftdic,0x0403,0x6001) #The last 2 params identify the Vendor_ID and Product_ID programmed into your FT chip. 0x6001 is FT232RL #Go here to find the default Product_ID for different parts: #www.ftdichip.com/Documents/technicalnotes/tn_100_usb_vid-pid_guidelines.pdf ftdi.ftdi_enable_bitbang(_ftdic,0xFF) ftdi.ftdi_set_baudrate(_ftdic,9600*16) #Not certain what is the fastest setting possible yet. return _ftdic
If you are using a device other than the FT232RL you might need to modify the Product_ID in the ftdi_usb_open
function. See the comments in the code for more details.
The ftdi_enable_bitbang
function configures the 8 bit-bang pins to either an Input or an Output. For this example I’m setting all 8 pins to be Outputs, hence the 0xFF
. If you wanted all the pins to be Inputs you would send 0x00
, and if you wanted half to be input and half output: 0xF0.
According to the documentation this ftdi_enable_bitbang
function is depreciated and favors the new function ftdi_set_bitmode
. This new function wasn’t working for me, but maybe you’ll have more luck with it than I did.
The next step is to signal to the FPGA that we are going to send it a configuration stream. We need to yank its NCONFIG line low then high:
StartConfig(ftdic)
def StartConfig(ftdic): #To start the config process we need to take FPGA's NCONFIG pin LOW then HIGH ftdi.ftdi_write_data(ftdic,chr(0),1) ftdi.ftdi_write_data(ftdic,chr(NCONFIG_HIGH),1)
We send data to the FT232RL as a Byte of data (ftdi_write_data
), with each bit corresponding to the state of each pin. You can see I defined the NCONFIG_HIGH to set the 3rd pin high:
NCONFIG_HIGH = 0x4 #Bitbang Pin #3 (NCONFIG) DCLK_HIGH = 0x2 #Bitbang Pin #2 (DCLK)$
Next I load the FPGA’s configuration file into memory:
data = ReadFile('/home/chris/Projects/Saturn Project/FPGA Code/Hello World/Hello_World_Compressed.rbf')
Next we need to process the RBF file to get it ready to send to the FT232RL and the Cyclone II:
for byte in range(0,len(data)): bits = toBINstr(data[byte]) for bit in range(-1,-9,-1): #The strange range parameters are to reorder the bits in the RBF correctly for the FPGA NewData += chr(int(bits[bit]) | NCONFIG_HIGH) #Set DATA0 to the bit, Set DCLK Low. (Be sure to keep holding up the NCONFIG pin) NewData += chr(int(bits[bit]) | NCONFIG_HIGH | DCLK_HIGH) #Clock the data in NewData += chr(int(bits[bit]) | NCONFIG_HIGH) #Falling edge of the clock, this can probably be removed but I have to check the timing to be sure (setup/hold times).
Here is the key sentence from the Altera CII documentation explaining how the data needs to be sent to the FPGA:
send the configuration data on the DATA0 pin one bit at a time. If you are using configuration data in RBF, HEX, or TTF format, send the least significant bit (LSB) of each data byte first. For example, if the RBF contains the byte sequence 02 1B EE 01 FA, you should transmit the serial bitstream 0100-0000 1101-1000 0111-0111 1000-00000101-1111 to the device first.
Now, every bit we send on DATA0 needs to be clocked-in, which is why we set the DCLK low, put the data on the DATA0 pin, then take DCLK high. You can see for each configuration bit we want to send to the FPGA, we are sending 3 bytes to the FT232RL.
(If you are trying to use this code on Windows, you might have to adjust the code to avoid Endian problems)
Now that we have the byte stream generated we need to send it to the FT232RL. Fortunately, the ftdi
library makes this pretty easy for us:
rc = ftdi.ftdi_write_data(ftdic,NewData,len(NewData))
I had a problem using the older version of the libftdi
driver (v0.17-1) where sending the full buffer in one go would kick back a -14 memory error. The following code was a workaround to send the data in smaller bits:
#The following code is/was needed for version 0.17-1 of the ftdi driver (There is a bug where too large a buffer kicks back a -14 memory error) #Re = '' #for x in range(1,24): #Re = NewData[(x-1)*2**18:x*2**18] #rc = ftdi.ftdi_write_data(ftdic,Re,len(Re)) #print rc
Finally, let’s close our connection with the ftdi device:
ftdi.ftdi_usb_close(ftdic)
And that is all there is to it! After running the code your FPGA should be up and running.
I had a lot of problems initially because of Ubuntu’s security blocking me from accessing the FT232RL, so be sure to read my first post describing how to setup a udev
rule to allow non-root access to the device.
I recommend looking at the libftdi
documentation as well as the Cyclone II Configuration documentation if you have any problems with the code and/or configuration process.
Best of luck. Please leave a comment and let me know if this code has helped you, or if you have any questions.
CII_Config_ExampleSimple.zip
Contains:
- CII_Config_ExampleSimple.py
Hi,I found that you used ‘chr(0)’ and ‘NewData += chr(int(bits[bit]) | NCONFIG_HIGH)’
in python ‘chr’ neams the asiic str,if you run ‘chr(0)’ it return a blank
what did you mean here?
Chr(0) is just to give a byte with all zero’s to the FTDI driver. It doesn’t print out because an ascii 0 is a null.
[…] my next post I’m going to show a technique I’ve come up with to configure the Cyclone II FPGA using […]
I’m using the latest, version 0.20
Okay. I saw your message in the libftdi message group, seeing Jim’s response, your best bet might be reverting to the 0.18 version and applying my patch (Or just grab 0.19 which has it already included) or I suppose trying to pull the 1.x branch and see how that works.
I had a quick peak at the ftdi.i file in the 0.20 branch and there are some huge changes from 0.18, I’m not expert enough in SWIG to know if they are correct or better though.
Finally, i take SRunner and i will modified the C code to make it work with a µprocessor and not a ByteBlaster cable.
I believe that is describing programming the FPGA using the Active Serial (AS) method, which is totally fine. Basically the FPGA accesses the memory and configures itself. I’m not sure the pricing on the Spansion ICs but here is a bunch of similar products at DigiKey (http://search.digikey.com/scripts/DkSearch/dksus.dll?Cat=2556265)
It looks like this might be even cheaper than buying some nvram. Be sure to get a memory that is sized correctly for your application.
Good luck, let me know how it goes.
Thanks for your answer, but i think i find a solution : http://www.spansion.com/Support/AppNotes/Configuring_Altera_FPGAs_via_SPI_Flash_AN.pdf
And i emulate the SPI interface with the microcontroller to configure the flash memory. Do you think it’s possible ??
Hi Pasto,
Currently in my setup all of the configuration data is stored on my personal computer so I don’t have any external memory connected.
Since you have a microcontroller doing all the programming work, you should be able to use any kind of non-volatile memory. (http://search.digikey.com/scripts/DkSearch/dksus.dll?Cat=2556980&k=non-volatile). Of course you will need a way to transfer the configuration file from your PC to the external memory…
So, It might be worth looking into using something like a removable SD card, especially if your microcontroller has easy file access functions.
I hope that is helpful…
–Chris
hi, i just want to know what external memory you use to store the configuration of FPGA and how you interface this one. Because i try to configure a cyclone II in PS configuration with a microcontroller. thanks for your help and sorry if i make a mistake i don’t speak english very well.
Hi Bryan, I actually had the exact same problem you’re having with the read pins function. I found a fix, but I’m still waiting for feedback from the developers to see if it is the preferred fix. (Here is the post: http://developer.intra2net.com/mailarchive/html/libftdi/2011/msg00075.html). I don’t think many people are using the library in Python yet 🙂
Basically we need to recompile the library adding a line to the ftdi.i file to allow us to work with uchar* types since it wasnt built to support ctypes. I’ll put up a tutorial when I return from vacation, but let me know if you’re successful in c++
Could you give a sample of how to read the pins after applying the patch? I’m still having no luck.
I’ve tried:
pins = ftdi.new_ucharp()
ftdi.ftdi_read_data(ftdic, pins, 1)
print hex(ftdi.ucharp_value(pins))
thanks!
Hi tuxGurl,
At one point in the development the wrappers were supposedly cleaned up so we no longer have to initialize the ucharp, making the Python interface a little more elegant. (http://developer.intra2net.com/mailarchive/html/libftdi/2011/msg00588.html)
I haven’t had a chance to play with the latest builds now that I’ve moved on to using the DE0-Nano.
I’ll take a look at this in the next couple of days, but you could also try dropping a message in that libftdi mailing list, they might get you fixed faster than I can.
Quick question, which version of the libftdi are you running?
Hey Chris. Thank you so much for posting your code. I’ve played around with it for a bit. I’m having some trouble trying to perform pin reads over this interface, using the ftdi_read_pins(struct ftdi_context *ftdi, unsigned char *pins) function. Something about trying to use pointers in python just never seems to work. I’m trying out the ctypes library but I’m still not having much luck. I think I might just port your programming procedure back into c++.