Starting from:

$25

CPEN412 - Microcomputer Systems Design - Assignment 3 - Solved

Objectives

To add an SPI bus Controller to our 68k computer system
Hang a Flash Memory Chip from the SPI controller.
Write an application to program/load data to/from the SPI Flash Memory chip.
Integrate the finished application into the debug monitor to allow for the storing, loading and auto running of programs to/from Flash memory during power-on.
This assignment can be done in groups of 2
 

Important: If you are unfamiliar with the SPi bus operation and protocol, you must read Lecture 32 before commencing this Lab. This lab is mainly an exercise in writing embedded software.

 

Rational and Overview: There is a need for microcomputer firmware to be stored in Rom so that the CPU can boot directly from it when power is applied. Having large amounts of Rom that the CPU can execute code directly from is becoming less “fashionable” because the required chips to support it are both physically large (48 pin packages are common) and not particularly dense (in an age of multi-megabyte or even gigabyte storage) due to their reliance on NOR flash technology with larger storage cells. 


Over the last decade there has been a big shift towards using smaller, denser NAND Flash devices with serial load/store interfaces. This makes for a physically smaller device (8 pin surface mount packages are unbelievably tiny). However the serial nature of these devices requires a dedicated hardware controller to sit between the CPU and the Flash memory. 

 

The idea is that at power on, a small amount of ROM that the CPU can execute code directly from (e.g. our Rom from Lab 1 that holds our debug monitor) is used to “load” the actual application firmware from Flash memory, via the SPi controller, into Dram from where it can then be executed. That is our goal for this lab.

 

Being serial based means that we have to introduce a dedictated controller to handle the interface and protocol between the CPU and the flash memory chip itself and write the appropriate driver software to talk to the memory chip. Two serial interface standards are common:- SPI and IIC. 

Because SPI bus is generally faster than IIC bus (100Mbps vs 400kbps), it is the more common serial interface for large capacity Flash devices, allowing for faster loading of large programs from flash to dram. Some SPI memory chips have developed multiple serial data streams, e.g. quad stream devices which allow up to 4 parallel bits per clock to be transferred, effectively quadrupling the speed of the chips to 400Mbits/sec (with an appropriately fast controller and CPU that can keep up – or DMA assisted). 

 

EEProm chips on the other hand seem to be better supported on the IIC bus (but SPI devices are available). In this lab, we’ll use a simple “single data stream” SPI controller to control the above Flash memory device made by Winbond and write the software to drive it. Check out the data sheet for the chip and controller we will be using on Canvas.

 

Part A – The Hardware - Step 1



On Canvas you will find the verilog file “simple_spi_top.v” (downloaded originally from OpenCores.org) that implements a simple SPI bus controller. There is also a document describing the device in terms of its registers and the programming required to talk to it. This controller has a parallel data bus interface on one side that connects to the 68k and an SPI serial interface on the other side that we can connect to an SPI flash memory chip.

 

Download the verilog file and add it to your project (open it and tick the “add to project check box”). Create a symbol for the  “simple_spi_top.v” file so that we can paste it onto a schematic diagram later (see quartus menu File->Create/Update->Create Symbol File for current file, i.e. your verilog file). 

 

The SPI controller chip itself is designed to be "Wishbone Bus" compliant. Wishbone is an open standard parallel computer bus designed specifically for use inside FPGAs - it's an alternative to Altera's own proprietary “Avalon Bus" and ARMs “AMBA/AXi bus” - i.e. same intent and purpose. 

It's not too different in principle to the asynchronous bus the 68000 uses, i.e. it requires an acknowledge with each transfer, and is relatively easy to interface a Wishbone compliant chip to the 68k system we are building as shown in step 2 below.

 

 

Step 2

 

Within the Quartus project provided by your instructor, you will find a virtually empty new schematic/block diagram file called IIC_SPI_Interface which will be usd for this lab (on SPI) and another lab (on IIC bus). Paste a copy of the “simple_spi_top.v” symbol onto it and wire up as shown in the schematic below. The 68k interface in on the left, the SPI is on the right (bottom 4 signals – sck_o, mosi_o, miso_i, SSN_O[7..0]). Note there may be other signals in that schamatic that are not required for this lab. Do not delete them, we’ll come back to then in another lab.

 

 

As you can see there are other logic gates on this circuit. Add those to your schematic from the Quartus logic library. The outline of the SPI_BUS_DECODER is given below.

You will notice that the clock signal (Clk) has a divide-by- two flip flop associated with it. This was found necessary because the Simple SPI controller has a pair of FIFO buffers to hold data read from and written to the SPI memory chip. These buffers were designed to take advantage of the Wishone Bus standard’s “burst” data transfer mode where, with each rising edge of the clock, a new item of data is transferred between FIFO and memory chip. 

 

Simulation and waveform analysis revealed that 68k bus cycles (which take 4 clock periods) were generating 2 rising edge clocks with each read or write operation, when AS_L (which activates the chip) was active. You can see this on Pages 12 and 31 of Lecture 7/8. This was found to be upsetting the operation of the FIFO buffers in the SPI controller as alternate bytes were “lost”. 

 

To fix this, the simplest solution was to halve the clock frequency to the SPI controller relative to the 68k so that it saw just 1 rising clock edge per 68k read or write operation. Consequently the SPI controller runs at 12.5Mhz when the 68k clock is 25Mhz. An alternative would have been to design a simple state machine to ensure only 1 clock edge is delivered to the SPI controller.

 

This is the kind of problem you sometimes have to deal with when you interface technology from different vendors, sometimes it needs some “glue and duct tape” to get it all to work together. 

 

A VHDL file for the SPI_BUS_Decoder at the top is given below. Modify to suit the range of addresses for the controller as detailed in the TODO comments below

 

module SPI_BUS_Decoder (

                input unsigned [31:0] Address,

                input SPI_Select_H,

                input AS_L

                                

                output reg SPI_Enable_H

);

 

always@(*) begin

 

                // defaults output are inactive, override as required later

SPI_Enable_H <= 0 ;

                

                //  TODO: design decoder to produce SPI_Enable_H for addresses in range

                //  [00408020 to 0040802F]. Use SPI_Select_H input to simplify decoder

                // this comes from the IOSelect_H signal on the top level schematic which is asserted high for CPU

                // addresses in the range hex [0040 0000 - 0040 FFFF] so you only need to decode the lower 16 address lines 

                // in conjunction with SPI_Select_H (think about it)

                //  AS_L must be included in decoder decision making to make sure only 1 clock edge seen by 

                // SPI controller per 68k read/write. You don’t have to do anything more.

 

                                

                end

endmodule              

 

Step 3

When you have finished, you will see that on the top level schematic in your project, you have the circuit shown below. Make a final check that the pins shown below have been correctly assign in the Quartus Pin Planner as shown in the green comments below/right.



 

 

 

 

There should be no need to modify the main Dtack generator (or address decoder) on the top level schematic as it already produces a Dtack (with no wait states) for any address in the range [0040 0000 - 0040 FFFF].

 


Step 4

Compile the design, (having checked that the connections for the SPI signals in the Pin planner, are correct) then download to your Altera board.

 

If you have done the design correctly, you should be able to use the debug monitor to verify that you can communicate with the SPI controller by dumping memory occupied by the controller (see example below). You should something like this with hex 50 in the first location of 0040 8020. This is is a feature of the design of the SPI controller. If you can see something similar to this (data every alternate byte but perhaps 01 rather than 50 in the first byte) the SPI controller interface is working and the 68k can talk to it.

 

  

 

Step 5

Your Instructor will give you an 8 pin SPI Flash memory chip (32M bits/2Mbytes) made by Winbond (Part # W25Q32). It comes in an 8 pin DIP package. The data sheet for this device is given on Canvas. 

 

IMPORTANT: Your instructor has mailed out to you a number of 8 pin DIP packaged chips. It’s important that you identify the correct chip for this Assignment. Because the chip is so small it can be hard to read the writing on it, so maybe use the camera in your cellphone and zoom in on it. It should have writing similar to the image below which is an example of a surface mount device. Yours however is DIP and has longer pins for pushing into a breadboard. 

Also make sure you identify pin 1 correctly by the small indent shown outlined in red below. This is especially important when connecting power. If you blow them up, it may be impossible to get another chip before the deadline so DOUBLE/TRIPLE CHECK everything before applying power.

 

  

 

Important: This device operates at 3.3v DO NOT connect it to 5v or you might blow it up. There is a 3.3v pin on the DE1 GPIO connectors (Pin 29 below).

 

This device connects via wires to the GPIO_0 port using the pins highlighted below. Although it is capable of Dual channel mode, our SPI controller is not, so we will use it in single channel only

 

 

  

 

If you have made the correct pin assignments in the Pin Planner, the SPI controller signals will have been brought out to the GPIO pins 31 to 34 (see above). We need to connect these to the SPI memory chip, the pin out for which is given below. 

 

The signals WP# (Write Protect) and HOLD# (hold/pause operation) should be connected to 3.3 volts (logic 1) to allow writing to the chip and normal operation (see the data sheet for the WinBond device). 



Signal CS# (Memory Chip) is an active low chip select signal and should be connected to SSN_O[0] on the SPI controller (Pin 32 - GPIO 0).  Note that none of the remaining SSN_O[7:1] signals on the controller are needed, but could be added in the pin planner if you wished to hang/control up to 8 independent SPI devices off one SPI controller.


MOSI – Master Out/Slave In (Pin 33 - GPIO 0) connects to SI (Pin 5 on Memory Chip)
MISO – Master In/Slave Out (Pin 34 - GPIO 0) connects to SO (Pin 2 on Memory Chip)

 

Remember that each SPI slave device has it’s own chip select allowing multiple devices to share the same SPI controller. 

 

 

 

Connect the Flash chip to your SPI controller by making connections from the appropriate pins on the GPIO 0 connector on the Altera board (as shown above), to connections to the flash chip on the breadboard. 

 

 


Part B – Writing the SPI Controller Software

 

On Canvas you’ll also find the SPI Controller documentation which describes the initialisation and register operation of the SPI controller chip. This is not very complicated (SPI is a simple protocol) but you will have to wade through it to understand it. 

 

You’ll also have to read this in conjunction with the data sheet for the Flash memory chip to understand how to get the SPi Controller to drive that.

 

The following C code contains functions to perform low level initialisation, polling and reading/writing of data to the SPI Controller. In conjunction with the SPI controller documentation, you should make modifications to fill in the missing code as per the “TODO” comments below. We will use these functions later to invoke higher level communication with the Flash chip.

 

/*************************************************************

** SPI Controller registers

**************************************************************/

// SPI Registers

#define SPI_Control         (*(volatile unsigned char *)(0x00408020))

#define SPI_Status          (*(volatile unsigned char *)(0x00408022))

#define SPI_Data            (*(volatile unsigned char *)(0x00408024))

#define SPI_Ext             (*(volatile unsigned char *)(0x00408026))

#define SPI_CS              (*(volatile unsigned char *)(0x00408028))

 

// these two macros enable or disable the flash memory chip enable off SSN_O[7..0]

// in this case we assume there is only 1 device connected to SSN_O[0] so we can

// write hex FE to the SPI_CS to enable it (the enable on the flash chip is active low)

// and write FF to disable it

 

#define   Enable_SPI_CS()             SPI_CS = 0xFE

#define   Disable_SPI_CS()            SPI_CS = 0xFF 

 

/******************************************************************************************

** The following code is for the SPI controller

*******************************************************************************************/

// return true if the SPI has finished transmitting a byte (to say the Flash chip) return false otherwise

// this can be used in a polling algorithm to know when the controller is busy or idle.

 

int TestForSPITransmitDataComplete(void)    {

 

    /* TODO replace 0 below with a test for status register SPIF bit and if set, return true */

    return 0;

}

 

/************************************************************************************

** initialises the SPI controller chip to set speed, interrupt capability etc.

************************************************************************************/

void SPI_Init(void)

{

    //TODO

    //

    // Program the SPI Control, EXT, CS and Status registers to initialise the SPI controller

    // Don't forget to call this routine from main() before you do anything else with SPI

    //

    // Here are some settings we want to create

    //

    // Control Reg     - interrupts disabled, core enabled, Master mode, Polarity and Phase of clock = [0,0], speed =  divide by 32 = approx 700Khz

    // Ext Reg         - in conjunction with control reg, sets speed above and also sets interrupt flag after every completed transfer (each byte)

    // SPI_CS Reg      - control selection of slave SPI chips via their CS# signals

    // Status Reg      - status of SPI controller chip and used to clear any write collision and interrupt on transmit complete flag

}

 

/************************************************************************************

** return ONLY when the SPI controller has finished transmitting a byte

************************************************************************************/

void WaitForSPITransmitComplete(void)

{

    // TODO : poll the status register SPIF bit looking for completion of transmission

    // once transmission is complete, clear the write collision and interrupt on transmit complete flags in the status register (read documentation)

    // just in case they were set

}

 

 

/************************************************************************************

** Write a byte to the SPI flash chip via the controller and returns (reads) whatever was

** given back by SPI device at the same time (removes the read byte from the FIFO)

************************************************************************************/

int WriteSPIChar(int c)

{

    // todo - write the byte in parameter 'c' to the SPI data register, this will start it transmitting to the flash device

    // wait for completion of transmission

    // return the received data from Flash chip (which may not be relevent depending upon what we are doing)

    // by reading fom the SPI controller Data Register.

    // note however that in order to get data from an SPI slave device (e.g. flash) chip we have to write a dummy byte to it

    //

    // modify '0' below to return back read byte from data register

    //

    

    return 0;                   

}

 

Talking to the Flash Chip

To talk to the flash chip involves writing data to it reading data back. This is accomplished via the SPI Controller. Obviously we have to enable the flash chip before we speak to it, that is, set it’s CS# pin low by writing to the SPI controller CS register (see the two macros near the top of the above code to enable and disable a chip). This allows us to control the SSN_O[7..0] output to enable 1 of 8 SPI slave devices

 

IMPORTANT: SPI bus does not support independent reading and writing to a slave SPI device (e.g. our flash chip). Reading data from any slave (e.g. memory) device is accomplished by writing to it (using dummy data), at which point, data is simultaneously received back. In fact every write to a slave device always returns a byte back, even if we are not expecting anything. 

 

Even if you don’t want that data and don’t intend to do anything with it, you always get something back when you write – this has to remembered, especially in our case where our SPI controller has a read FIFO buffer. This explains why in the WriteSPIChar() function above you must remember to remove/read the byte you get back after any write operation otherwise your receive FIFO will fill up or get out of sync, neither of which are good. 

 

If you read hex FF when you thought you should be getting something more sensible, it’s a good indication your read FIFO has got out of sync. This is because the flash chip TriStates its SO (Serial Data Out) signal when you are writing commands/addresses etc to it. TriStates will be read by the SPI controller as logic 1’s i.e. the byte 0xFF (see Figure 13 below as an example).

 

Controlling the Flash Chip

To get the Flash chip to do anything it is important to understand the set of commands expected/imposed by the slave flash chip, i.e. the ones designed into it by the manufacturer (Winbond in the case of the flash memory chip). 

 

Different chips will have different commands depending upon what they do. Obviously the documentation for the chip should be read, but briefly, to get the chip to do anything, you have to enable the chip by setting bit 0 of the SSN_O[7..0] outputs low via the SPI controller CS register. This output is connected to the memory chip’s CS# signal, assuming you have wired it correctlty in the pin planner and on the breadboard.

 

Once enabled, control of the flash chip is accomplished by issuing commands to it, i.e. we write bytes to it, via the SPI controller. Some of these commands require additional bytes, e.g. a 24 bit (3 byte) address. 

 

At the end of a each command, it is usually necessary to remove the enable from the chip at which point the chip takes action on the command, e.g. writing data to a location/address. Don’t just leave CS# permanently enabled because you think “well we only have one SPI slave and thus there’s no harm in leaving it enabled it all the time” – This will not work.

 

Study the data sheet carefully, particularly the timing/protocol diagrams for each command. As as example consider the following image taken from the flash chip documentation.

 

   

 

Here you can see that CS# has to be taken low before we want to talk to the flash chip. In this case the command we are sending is one designed to write some data (upto 256 bytes in this instance) to the chip starting at a particular internal flash memory chip address. 

 

To get the chip to write this data, we first send the byte (02) which is the command for Page Program to the chip by writing 02 to the SPI controller Data Register. This is followed by 3 more bytes (to the same SPI controller data register) which contain the 24 bit internal flash address. The first location in this chip is obviously location 0.

 

Once we have sent the internal address, we can send upto 256 bytes of data by writing them one byte at a time to the SPI controller data register. This is the maximum size of a page on this device. If you need to write more than 256 bytes of data, you have to break that data down into pages of at most 256 bytes.

 

In other words, you have to split a large block of data, such as 1024 bytes into 4 chunks of 256 bytes and write them with 4 separate operations. Of course you could write less than 256 bytes also, e.g. perhaps you only want to change 1 byte. 

 

At the end of the command you must take CS# high and the chip will perform the actual internal memory cell writes. 

 

Note that while the device is performing the write internally it will effectively be busy, so we have to check for internal completion of that write (and in fact any chip/sector/block erase command) by reading/polling the flash chip’s status register. 

 

Don’t confuse this with reading the SPI controller’s status register – they are two separate registers on two separate devices.

 

Finally as we saw in the lecture on Flash technology you cannot just overwrite a location to change the data stored there. You will have to erase the sector or block containing that location or just erase the whole chip before you can write to that location again.

 

Sending Multiple Bytes – Checking for Transmission Complete

It’s important to make sure that the SPI controller has finished writing the last bit of any data you gave it, before sending another command/address/byte etc. This can be done by polling the SPI controller status register. A copy of the relevant section from the SPI controller data sheet (on Canvas) is shown below which show the 8 bits of this register.

 

Bit #
Access
Description
7
R/W
SPIF
6
R/W
WCOL
5:4
R
Reserved
3
R
WFFULL
2
R
WFEMPTY
1
R
RFFULL
0
R
RFEMPTY
 

When we write data to the SPI controller, it takes time to send that data out serially to the memory chip, so you cannot just write byte after byte to the SPI controller without checking to ensure that the last byte has been transmitted. Likewise, you should NOT remove CS# on the slave device until the last bit of any data has been sent.

 

Checking for completion of transmission can be performed in software by checking Bit 7 – SPIF. This is set to ‘1’ when the all bits have been transferred serially to the flash chip.

 

Polling for completion of commands in the Flash Memory chip

The illustration below shows the timing/protocol of polling the Flash chip status via the SPi controller. This is something we should always perform before writing any new command to the chip (i.e. check that the flash memory chip is ready to accept new commands), this is particularly relevent when writing data to the flash chip or erasing content, since these actions take significant time and the SPi Flash memory chip will not accept new commands while those previous operations are still in progress.



  

Here we take CS# low to enable the flash chip, then write the command (05) – i.e. a “read memory chip status register” command, followed by a dummy byte (which will allow us to read back the status of the flash memory chip into our SPI controller. Once the status byte has been received, we can examine it to check the status of the flash memory chip (is it busy or idle). 



The timing diagram above implies that we can keep writing dummy bytes and hence continue reading the status of the flash memory chip (as is typical in a polling routine) as long as CS# is held low. Once we take CS# high, then the command is terminated.

 

Erasing the Chip

The timing diagram for this command is shown below and is simple, but it can take some time for the Flash chip to complete this operation so don’t assume it happens instantly, you need to check the flash status register (see above) to determine when this operation has completed.

 

   

 

 

Enabling the Device for Writing/Erasing

Apart from the physical WP# pin on the Flash chip, the device also has its own internal write protect operation. This is to protect the device from accidental erasing/programming by a program that might have crashed and might be writing data accidentally to the flash chip.

 

If you want to modify the contents of the chip in any way, e.g. write new data to it, erase the chip/block/sector, write to the flash status register etc, you have to send a write enable command to the chip first. 

 

Important: After completion of a command that modifies the contents of the chip (write/erase etc), the device will always return to a write disabled state. This is important when writing lots of data to the chip since we have break K bytes of data down into pages of 256 bytes with a write command for each. You will have to remember to enable writing before any new write or erase command.

 

To enable writing again (after completion of a previous write), send the command 06 to the flash chip. Again use CS# to enable and disable at the start/end of this command.

 

   

 

 

Reading from the Flash Chip

Reading is actually much simpler than writing, since you do not have to enable writing (obviously). In fact it’s a sensible idea to make sure you do NOT enable writing when you only want to read.

 

In addition, reading does not require the data to be broken down into pages of 256 bytes and thus we do not need to issue multiple read commands. In fact you can issue a single read command (03) followed by a 24 bit internal start address and then keep reading bytes until you are done. In theory all 2Mbytes of the chip can be read after issuing 1 read command. At the end of your read, to terminate any further reading you must take CS# high. The timing diagram is shown below.

 

  

 

How do I read the data?

After the command 03 and the 24 bit address have been sent, you can then write dummy bytes to the device (any data will do, they are ignored by the memory chip). At this point it will return the data stored in successive incremental locations within the memory chip back your SPi controller with each dummy write. You can read these out 1 byte at a time by reading the SPI Controller Data Register. 

 

Don’t forget to poll the SPIF bit in the SPI controller status register to ensure you have transmitted all 8 bits of your dummy byte before attempting to read the SPI Controller Data register i.e. you cannot expect to get 8 bits of data back from the memory chip until it has received 8-bits from the SPI controller. Remember their shift registers are linked (see slide 6 of lecture 32). For every bit of the dummy byte your SPi controller sends, the memory chip will transmit 1 bit back. This should be taken care of by your WriteSPIChar() function (see previous description above)

 


OK – So what do I have to do?

 

1.      Write a stand alone ‘C’ code application by starting a new project in IDE68k. This program should demonstrate the ability to read and write data to the flash device.                                                                                                 60%

 

2.      Based on knowledge of updating the debug monitor in Assignment 1, integrate your code from step 1 above into the debug monitor to allow for the storing and loading of 1 user program to/from flash memory. 

Note it will be necessary to erase the chip (or the appropriate blocks or sectors) before programming it.

 

The debug monitor already has menu options ‘P’ (for program flash) and ‘C’ for copy and run flash content to dram memory). In addition, after a reset or power on, if Slider Switch SW9 (the left most switch on the DE1) is in the “UP” position, the debug monitor will automatically invoke the ‘C’ command to load and then run a user program directly from flash into Dram. Check out the function main() inside the debug monitor source file. 

User programs are stored in Dram at the base address of the main system memory, i.e. address hex 0800_0000. Assume the program is no bigger than 256kbytes, i.e. we only need to program/copy the first 256k bytes of memory to/from flash memory. 

When programming the flash chip you should also verify and check for success by reading the program back from flash and comparing it with main memory. 

 

3.      Convert your new Quartus design with the new debug monitor into a “turn key” system i.e. program a “.jic” file into the DE1 board flash serial prom as you did in assignment 1 and 2.                                                                         40%

 

More products