Electronics » spartan 2 config with a PIC & serial EEPROM..
I needed a simple way of configuring a Xilinx Spartan 2 FGPA using
ubiquitous, cheap parts. The Xilinx configuration chips are probably
the easiest but a little expensive so I ended up using a serial EEPROM
and PIC microcontroller with the FPGA in "slave serial" mode. Not the
fastest method, but good enough.
The choice of microcontroller wasn't going to be much of a problem, any of
the 8-pin PICs would do - I already had some 12F675s so I used one of those.
Both the serial EEPROM and the PIC will work at 3.3 Volts which is the
FPGA's native I/O voltage (the IOs can be 5 volt tolerant on the Spartan2
but I wasnt 100% certain the config pins were too). The PIC's internal 4MHz clock
could be used to save parts and pins without sacrficing too much speed. The EEPROM
has a maximim data rate of around 500KHz with a 1 microsecond clock high / 1
microsecond clock low duty cycle at 3.3 Volts anyway - so the 1 microsecond
instruction clock of the PIC wouldnt be slowing things down too much.
Next I needed a serial EEPROM which a) was big enough to hold the configuration file
and b) I could program with the cheapo "Willem Enhanced EPROM programmer" I
bought off eBay :) The Spartan 2 FPGA chips I was targetting were the low-end
XC2S15 and XC2S30, these have config files of less than 64K Bytes so it just a
case of scanning through the list of serial EEPROMs supported by my Willem programmer.
The AT24C512 looked like a suitable candidate, its a common cheap chip with
readily available datasheets etc.
Before reading up on the I2C serial protocol used in the EEPROM I kind of expected
the data would just clock out in a stream of bits every input clock, but
of course there's a little more to it than that. Firstly there are commands
to tell the EEPROM what to do (read or write, from which address etc) and secondly
each data byte requires a 9th clock cycle for an "acknowledge" signal.
Electrically its all very simple, just two pins form the comms channel, a
clock input pin and a serial data input/output pin. The Serial Data pin is
an input with open-drain output so that multiple device outputs can be connected
together on a simple bus, as such it requires a pull-up resistor around 2Kohms.
As mentioned, the EEPROM's Serial Data pin is bidirectional and the PIC needs to be
able to pull it low when sending commands etc. The PIC I was using doesnt have an open
drain output driver so I tried a few different ideas: Switching the port data direction
of a PIC pin from input to output (driven with a zero) would sink the current from
the Serial Data pull-up resistor and make the pin low - this worked but felt a bit
bodgy and cumbersome (especially having to switch register pages in the PIC software
to access the port IO direction register). Next I tried simply driving a transistor,
(its collector to the serial_data pin) this didnt seem to work well (my scope showed the
switching was delayed too much). Finally I realized I could just use a diode (1N4148,
cathode to PIC) to pull the line low when the PIC port pin was loaded with a zero
(a "one" would be blocked by the diode). The Vin_Lo spec of the EEPROM is 0.3 x Vcc
so at 3.3 Volts, even with a voltage drop up to 0.7V on the silicon diode, the
pin would still be below the threshold for a "zero" - I settled on this method.
The I2C EEPROM protocol...
The EEPROM clocks in data on the rising edge of the CLOCK line and outputs
data on the falling edge of the clock.
You can only change the Serial_Data line when the clock line is low, otherwise the
EEPROM thinks you are sending it a START command (data falls during high clock)
or END command (data rises during high clock).
Nine clocks are required per byte. When sending command bytes to the EEPROM, the EEPROM
responds with an acknowledge during the 9th clock. When receiving bytes from the EEPROM,
you must send an acknowledge signal on the 9th clock (see below).
There are various ways of reading and writing data (random, sequential) etc.
For the FPGA config project I was only interested in sequencially reading the
chip (I left the writing to my EEPROM programmer PCB) so that's what I'll describe
here.
To set up a sequential read from the EEPROM, the process is:
Send a START command (pull data low whilst clock is high)
Send the CONTROL byte "10100000" (Set address)
Send high ADDRESS byte (zero to start from beginning, obviously:)
Send low ADDRESS byte ("")*
Send another START command (pull data low whilst clock is high)
Send a CONTROL byte "10100001" (request read)
(Remember, after each byte, release the data line and send an additional clock to
receive the EEPROM's acknowledge.)
* Some smaller capacity I2C type EEPROMs use only one address byte - check datasheets
Now, to read the data...
LOOP:
Bit 7 of the byte addressed above will be present on the Serial_Data pin
Clock high, Clock low
Bit 6 appears on the Serial_Data Pin
Clock high, Clock low
Bit 5 appears on the Serial_Data Pin
Clock high, Clock low
Bit 4 appears on the Serial_Data Pin
Clock high, Clock low
Bit 3 appears on the Serial_Data Pin
Clock high, Clock low
Bit 2 appears on the Serial_Data Pin
Clock high, Clock low
Bit 1 appears on the Serial_Data Pin
Clock high, Clock low
Bit 0 appears on the Serial_Data Pin
If you're done reading goto "ESCAPE" otherwise you have to now send an
Acknowledge pulse to the EEPROM.. Like this:
Pull serial_data pin low - This is our Acknowledge to the EEPROM
Clock high, Clock low - ""
Release serial_data pin - ""
(the internal address counter is incremented after each byte read so
there is no need to specify another address).
Goto LOOP
ESCAPE:
Release serial_data pin (this is a "non-ack")
Clock high, Clock low
Pull data low ready for STOP command
Send a STOP command (pull data high whilst clock is high)
Naturally, unless the PIC itself is reading in the data from the EEPROM
for internal use you'll need your PIC code to generate a seperate clock
signal for your target device (you dont want it seeing the 9th bits / control
commands etc). You'll always going to get a gap of between the bytes
where the Acknowledge signals are taking place but most of the time it
wont be an issue.
On the FPGA side of things..
Serial configuration is quite straightforward. The various pins (other than the
usual VCCint, VCCIO, GND etc) involved are shown below:
INIT - goes high when FPGA is ready for config data (low if config fails CRC check)
DONE - goes high when FPGA is configured
PROGRAM - pull low to reconfigure the FPGA.
D_In(D0) - Serial Data in
C_CLK - Data clock in
M0,M1,M2 - Config mode pins (see below)
When using slave serial mode (ie: external clock) the FPGA's config
mode pins M0/M1/M2 need to be pulled high. The Serial_Data line from the EEPROM
and Output_Clock from PIC were connected to the FPGA's D_In/D0 and C_CLK pins
respectively. INIT,DONE and PROGRAM were pulled high via 3.3Kohm resitors
and connected to inputs on the PIC.
(The Spartan2 data sheet mentions that CS and WRITE are not used during slave
serial config but should not be toggled during the process so I pulled
these lines up, as well as the JTAG pins (TD0,TDI,TMS.TCK) just to be on
the safe side).
Upon power up, my PIC code waits a fraction of second for things to settle,
ensures the INIT line is high, sends the config file, checks to make sure
the DONE and INIT lines are high and then busy waits until PROGRAM is
pulled low, at which point the config sequence restarts. If, after the
config file was sent, either INIT or DONE are low, the code briefly
switches the data direction of the PIC pin connected to the PROGRAM line,
this pulls it low signalling the FPGA to reboot - the PIC then
restarts the config process. This way the PIC or an external switch
can cause the FPGA to reconfigure.
The config file:
The Xilinx ISE (7.1) software outputs a config file in raw binary (as a
*.bit file in the project's folder) when you click "Generate Programming
File" - Note: This version has some header data which you can remove
with a hex editor - the real config data begins with $FFFFFFFF,$AA995566.
Alternatively, to save trimming the header each time, right click "Generate Programming
File", choose "Properties" and tick the "Create binary configuration file"
option. Then when you generate the config file you'll have a *.bin file
with no header info ready to burn straight to the EEPROM. (The FPGA accepts
the bit sequence with MSB first and the EEPROM outputs the bytes in the
same manner, so there's no need to change the bit order or anything).
Naturally for the larger FPGA's you need much bigger EEPROMs. Its possible
to use multiple AT24C512s, using the hardwired device address bits as chip
selects but it probably wouldnt be cost effective - you'd probably want
a faster solution too.
And.. that's about it - my PIC code and schematic is here.
The code could probably be optimized to gain some extra speed (I avoided using
read-modify-write instructions on the GPIO port for example)
Links / Further Reading:
AT24C512 Datasheet
The I2C bus FAQ
PIC 12F629 Datasheet
Xilinx Spartan 2 Datasheet 1
|