One of the reasons for respinning the CAT Board PCB was to get the SPI flash chip connected correctly to the Lattice FPGA. The flash+FPGA+Raspberry Pi interconnection is complicated because it has to operate in three different modes:
- During development or when the FPGA is being used dynamically, the RPi has to be able to load a configuration bitstream into the FPGA.
- After development is completed, the RPi may want to store the finished FPGA bitstream into the flash.
- When the CAT Board is used stand-alone or when the FPGA performs some fixed function for the RPi, the FPGA has to be able to configure itself upon powerup with the bitstream stored in the flash.
All three modes have to share a single SPI bus between all the devices while using a minimum of RPi GPIO signals and extra circuitry.
Mode 3 is the easiest: when the FPGA powers up (or is reset), it checks to see if the SPI CS line is pulled high and, if so, becomes an SPI master and reads its configuration bitstream from the flash. The RPi GPIO signals are hidden behind the series resistors and can't interfere.
Mode 2 is slightly more difficult. When the RPi is storing a bitstream by sending it to the flash chip's SI input, the SPI CS line is pulled low. But that also enables the SPI interface of the FPGA which could lead to interference if the FPGA's SDO output becomes active. To prevent this, the RPi asserts the reset pin of the FPGA so its SPI port can't turn on. Problem solved.
Mode 1 is the most difficult. The RPi pulls the SPI CS line low as it removes the reset from the FPGA. This places the FPGA in slave mode so the RPi can send a bitstream to the FPGA's SPI port. But this also enables the flash chip's SPI port, which means the flash's SO output could interfere with the bitstream data. Unfortunately, there's no reset pin on the flash to keep it quiet. However, the flash does have a deep power-down mode that is entered by sending a specific command to the flash. Once in this state, it will not respond to anything until it receives another specific command to wake up. The RPi can then transfer the bitstream to the FPGA. During the transfer, there's no chance the wake-up command will be sent accidentally to the flash's SI input because 1) neither the RPi or FPGA will be driving that signal line during the configuration process, and 2) the flash only executes a command once its CS input goes high but the SPI CS line is held low for the entire duration of the bitstream transfer. So the flash will stay quiet and the RPi can send the bitstream to the FPGA in peace.
Another complication of the shared SPI bus is that the roles of the RPi's MOSI and MISO pins are reversed in modes 1 and 2. In mode 1, the RPI's MOSI output pin drives the SDI input of the FPGA and the FPGA's SDO output drives the RPI's MISO input. That allows the hardware SPI port of the RPi to be used for the SPI transactions. But in mode 2, the RPi's MOSI pin acts as an input to receive data from the flash's SO output and the RPI's MISO pin has to drive the SI input pin of the flash. That precludes the use of the RPI's SPI hardware and the SPI transfers to/from the flash have to be done using bit banging.
I searched for a ready-made SPI bit-banger program but nothing great popped up. So I just wrote one in Python using the RPi.GPIO library. It consists of a class for handling individual pin I/O, another class for SPI transactions, and a final class that handles most of the commands for the serial flash chip.
To test the code, I first tried the command to read the device ID from the flash. The manufacturer and device IDs should have been 0x1F and 0x8401, respectively. Instead, I got 0xFF and 0xFFFF. That's OK; nothing ever works the first time.
I probed with an oscilloscope to make sure the RPi was driving the correct pins of the flash. No problem there except that the SO pin was always high (naturally).
Next, I pulled out my old HP LogicDart and sampled the waveforms on the CS, SCK and SI pins. Once again, no problems: the clock edge was occurring in the correct place and the command byte was what it should be.
Possibly the chip was fried, so I replaced it. Same result.
Maybe something else on the board was holding the SO pin of the flash high. I desoldered the pin from the PCB pad and probed it directly. Same result.
Then I thought: "What if the flash is in the deep power-down mode? Then it would never react to the device ID command." The datasheet says the flash is not supposed to be in deep power-down mode unless it is explicitly commanded to do so, but I didn't have any better ideas so I sent the command to wake the chip before asking for the ID...
In retrospect, it's obvious what was happening. When the CAT Board is attached to the RPi and power is first applied, the FPGA comes up in SPI master mode and tries to read its configuration bitstream from the flash chip. Whether it finds a valid bitstream or not, the last thing the FPGA does is send the deep power-down command to the flash to save power. So by the time the RPi was fully booted and I ran my bit-bang code, the flash was already asleep.
Once that mystery was solved, the rest of the commands for erasing, programming and verifying the flash worked right away. Then it was just a matter of storing a bitstream into the flash and seeing if the FPGA would configure itself without the aid of the RPi. The following video demonstrates this: