EECS 373 Lab 3: Introduction to Memory Mapped IO

Copyright © 2010 - Matt Smith, Thomas Schmid, Ye-Sheng Kuo, and Prabal Dutta.

Schedule

See posted lab schedule for due dates, in-lab, and post-lab due dates.

Objectives

In Lab 2 we provided you with the "hardware" to read and write the LEDs and switches from software. In fact, we used dedicated hardware that Actel provides for this purpose. In this lab we will discover how to create our own hardware in the FPGA for this purpose. You will provide the memory locations (registers), data path and control to access registers connected to the LEDs and switches.

Pre-Lab Assignment

There is no Pre-Lab assignment for this lab. Read over the In-Lab assignment and get started.

In-Lab Assignment

The In-Lab assignment is a tutorial that shows you how to interface to the LEDs and push button switches as memory mapped IO (MMIO), that is we will read and write the LEDs and switches as though they are memory locations. The functional components of the MMIO interface are organized a bit like this.

We will implement the register control, registers, connections to the LEDS and switches in Verilog. We will use some prepackaged "core" from Actel and the Libero Smart Design environment to make the bus connections.

Step 1: Creating the IO Registers

We will create registers in the FPGA that will act as the storage element for the memory mapped IO interface. Lets start with LED register. The register will be 8 bit, read/write with the output ported directly to the LEDS. A reset is provided that sets the register to logical 0. We have provided you with the following example code to do this. Create a new project, create a new Verilog file as you did in lab one and copy the the following contents into it. Save the file. Do a HDL check to be sure all is well.

// ledreg.v

module ledreg( clk, nreset, wr_en, rd_en, data_in, data_out, led_port); 
    
//Inputs Declarations 
input  clk;             //Clock
input  nreset;          //active low reset  
input  wr_en;           //Write Enable 
input  rd_en;           //Read Enable  
input  [7:0] data_in;   //Data Input 
  
//output Declarations 
output [7:0] data_out;  //Data Output 
output [7:0] led_port;  //path to led connection
  
//reg Declarations 
reg [7:0] ledioreg;   //the io register 
reg [7:0] data_out;   //Data Output
wire [7:0] led_port;

//map led register to leds
assign led_port = ledioreg;

//read led register
always @(posedge clk, negedge nreset) 
    begin : READ_GEN 
        if(nreset == 1'b0)
            begin
                data_out <= 8'h00;
            end
        else if(rd_en)
            begin  
                data_out <= ledioreg; 
end           end 

//write led register 
always @(posedge clk, negedge nreset) 
    begin : WRITE_GEN 
        if(nreset == 1'b0)
            begin
                ledioreg <= 8'h00;
            end
        else if(wr_en)
            begin  
               	ledioreg <= data_in;
            end     	 
end 
endmodule

Next lets create a register for the switches. Although we only need 2 bits for the switches, we will create an 8 bit register, make it read only with a direct input connection from the switches. We have provided you with the following example code to do this. Create a new Verilog file as you did in lab one and copy the the following contents into it. Save the file. Do a HDL check to be sure all is well.

// swreg.v
module swreg( clk, nreset, rd_en, sw_port, data_out); 
   
//Inputs Declarations 
input  clk;             //Clock
input  nreset;          //active low reset  
input  rd_en;           //Read Enable  
input [1:0] sw_port;    //path from switches
  
//output Declarations 
output [7:0] data_out;
  
//reg Declarations 
reg [7:0] data_out;
wire [1:0] sw_port;

//read led register
always @(posedge clk, negedge nreset) 
begin : READ_GEN 
        if(nreset == 1'b0)
            begin
                data_out <= 8'h00;
            end
        else if(rd_en)
            begin  
                data_out[0] <= sw_port[0];
                data_out[1] <= sw_port[1];
             end 
end
endmodule

Step 2: Creating and APB3 "wrapper"

The wrapper provides a control and data path interface between the IO register and the Cortex M3 APB3 bus. The APB3 bus (advanced peripheral bus) is designed to interface with slow IO and has a simple protocol. Transfers fundamentally consists of two cycles: the setup phase and access phase. During the setup phase, the processor provides unique information that identifies the transaction such as read/write, device address, etc. During the access phase, the device either provides or excepts data from the processor. Additionally, the device provides a control signal during the access phase to the processor to extend the phase if more time is needed to latch or provide data. For more detail, you can see the reference at the end of the lab. For your convenience, the read/write transfer cycles with and without extended accesses are provide below. A description of the signals follows.

A quick summary of the APB3 signals:

PCLK: The bus clock. A transfers are synched to this clock.

PSEL: A signal provided by the APB3 bus interface that is used to uniquely select the device over some predefined address range.

PADDR: Used to address within the predefined address range.

PWRITE: Determines if it is a read (low) or write cycle (high).

PENABLE: Identifies the access phase.

PREADY: Signal used by peripheral to extend access phase. Low extends the cycle, high releases the cycle.

PWDATA and PRDATA: Write and read data buses.

PSLVERR: Use by the peripheral to indicate a transfer error.

How can we use these transactions to read and write our IO registers? Consider the case of writing the LED register. Examining the verilog code we see that the register is written on the rising edge of clk and wr_en true. To generate wr_en signal, the device has to be selected (PSEL == 1), it has to be a write cycle (PWRITE == 1) and it should be an access phase (PENABLE == 1).

So we have wr_en = PSEL && PWRITE && PENABLE with the PCLK used as the register clock.

The read register is similarly accessed except PWRITE == 0 and the processor requires that the data is available at the beginning of the access cycle so PENABLE is ommited.

So we have wr_en = PSEL && !PWRITE with the PCLK used as the register clock.

There is no need to extend the access cycle, so PREADY is set logical 1. There is no criteria for PSLVERR so it is driven logical 0.

The following code is working Verilog APB3 wrappers for the LED and switch registers. Notice that these wrappers port some the APB3 signals to the user IO so we can observe them. Create Verilog files, copy this code and verify with the HDL checker.

// ledregwrp.v
module ledregwrp(
  PCLK,
  PENABLE,
  PSEL,
  PRESERN,
  PWRITE,
  PREADY,
  PSLVERR,
  PWDATA,
  PRDATA,
  LEDCON,
  TPS);

input PCLK,PENABLE, PSEL, PRESERN, PWRITE;
input  [7:0] PWDATA;
output [7:0] PRDATA, LEDCON;
output PREADY, PSLVERR;
output [4:0] TPS;//test points

wire rd_enable;
wire wr_enable;

assign wr_enable = (PENABLE &&  PWRITE && PSEL);
assign rd_enable = (!PWRITE && PSEL); //Data is ready during first cycle to make it availble on the bus when PENABLE is asserted

ledreg ledreg_0 (.clk(PCLK), .nreset(PRESERN), .wr_en(wr_enable), 
        .rd_en(rd_enable), .data_in(PWDATA), .data_out(PRDATA), .led_port(LEDCON));
        
assign PREADY = 1'b1; 
assign PSLVERR = 1'b0;

assign TPS[0] = PCLK;
assign TPS[1] = PWRITE;
assign TPS[2] = PSEL;
assign TPS[3] = PENABLE;
assign TPS[4] = PREADY;       
    
endmodule

// swregwrp.v
module swregwrp(
  PCLK,
  PENABLE,
  PSEL,
  PRESERN,
  PWRITE,
  PREADY,
  PSLVERR,
  PWDATA,
  PRDATA,
  SWCON,
  TPS);

input PCLK,PENABLE, PSEL, PRESERN, PWRITE;
input  [7:0] PWDATA;
input [1:0] SWCON;
output [7:0] PRDATA;
output PREADY, PSLVERR;
output [4:0] TPS;//test points

wire rd_enable;
wire wr_enable;

assign rd_enable = (!PWRITE && PSEL); //Data is ready during first cycle to make it availble on the bus when PENABLE is asserted

swreg swreg_0 (.clk(PCLK), .nreset(PRESERN),
.rd_en(rd_enable), .data_out(PRDATA), .sw_port(SWCON));
        
assign PREADY = 1'b1; 
assign PSLVERR = 1'b0;

assign TPS[0] = PCLK;
assign TPS[1] = PWRITE;
assign TPS[2] = PSEL;
assign TPS[3] = PENABLE;
assign TPS[4] = PREADY;       
    
endmodule

Step 3: Provide Microcontroller Subsystem Components

To complete the connection between the IO registers and APB3 bus, we will need access to the system power up reset, a clock for the bus and connections to the APB3 data and control path. Access to these connections is provided thru the SmartFusion MSS (Microcontroller Subsystem). Mnay standard microcontroller subsytem components are provided including general purpose timers, serial IO interfaces, memory controllers, etc. These components are configured under the MSS (Microcontroller Subsystem) icon.

Step 3.1: Enable Relevant MMS Components

Double click on the MSS icon in the project flow window and provide a name to the MSS configure such as mmiolab. A view of the MSS system will open revealing all the available components and connections. All the components are enabled to begin. We will only need the Reset Management, Fabric Interface and Clock Management Component. The components are enabled by selecting the check box in the lower right corner. Notice that some components like the Clock Management do not have the option to exclude since they are needed for any configuration. Turn off all the components we don't need and your canvas should look something like this.

Step 3.2: Configure Clock Manager
This component provides clock sources for the various system components and custom FPGA hardware. We will use a clock that can be mapped to the FPGA hardware for the APB3 bus clock. Double click on the Clock Management icon. FAB_CLK (fabric clock) is the FPGA clock. Enable by clicking on the box. We are going to divide the clock down from the source clock (100 MHz) to 25 MHz. Do this by selecting the drop down menu just upstream from the FAB_CLK output and select divide by 4. The default source clock should be the RC oscillator (upper left). Also, you should notice the FAB_CLK added as an output port (upper right) from the MSS system to the FGPA fabric when you return to the MSS canvas. The connections are not shown between the components and MSS boundary. You can add them under the Canvas menu if you wish (show nets). The clock manager configuration should look something like this before closing.

Step 3.3: Configure Reset Manager

The reset manager provides power up system reset to the various components and FPGA fabric. We need provide the power up reset to the FPGA fabric. Open the Reset Manager and select "Enable MSS to Fabric Reset". It should look like this and you should notice a reset output to the FPGA fabric on the MSS canvas.

Step 3.4: Configure Fabric Interface

The Fabric Interface component is use configure bus ports to custom FPGA hardware. In this case we must configure a APB3 interface. Double click on the icon opening the fabric interface configuration. Notice that the MSS clock frequency and fabric clock frequency are inherited from the clock settings. We need a APB3 interface so select "AMBA APB3". The interface will act as a "Master" controlling the LED and switch registers so configure as MSS Master. The setting should look like this.

Step 3.5: Generate MSS
Finally HDL has to be generated from the MSS for synthesis. Click the generate icon or select under the design menu. Make sure there are no errors indicated in the sript below. The MSS generator will warn you that a root has not been set for the design hierarchy. You can ignore this for now. The final canvas should look something like this with the net view enabled (not necessary). Inspect the project explorer in the files tab and you will see Verilog files added for the MSS components.

Step 4: Connecting the IO Register Wrappers to the APB3

Libro provides a graphical editor for making system component connections between MSS components, custom logic and other high level core. We will use this tool to make our connection between the IO register wrappers and the Cortex M3 APB3 bus. Open SmartDesign (click on the ICON in Project Flow), select SmartDesign Component, choose a descriptive name such as apb3_to_ledswreg. A canvas should open without any components.

Step 4.1: Set Design Heirarchy Root

Select the Hierarchy tab in the Design Explorer, select the apb3_to_ledswreg, right click and set as the root. This sets the apb3_to_ledswreg as the top level in the design heirarchy. The file heirarchy should look something like this. Notice IP from the MSS configuration and the verilog files we provided earlier.

Notice that in the design flow apb3_to_ledswreg is set as the root in the upper right corner.

Step 4.2: Instantiate MSS Components in SmartDesign Canvas

We need to start by instantiating the MSS components we establishes earlier in the SmartDesign Canvas. Select IP mssio in the Design Explorer and drag it into the SmartDesign canvas. Or, select IP mssio, right click and choose instantiate in apb3_to_ledswreg. The MSS components we configured will be represented as a block with the ports we provided. The reset connection is automatically made since this is a fixed input. The block should look something like this.

Step 4.3: Intstantiate Register APB3 Wrappers in SmartDesign Canvas

Again, select the ledregwrp and swregwrp and drag onto the canvas. The blocks will be instantiated and look a bit like this.

Step 4.4: Convert IO Register APB3 Wrapper Ports to APB3 Slave Interface
<\p> Next we need to repackage the wrapper ports so that they interface a bit easier with standard APB3 signals. SmartDesign has a handy tool for this. Select the Bus Definitions tab (lower right) in the Catalog window (right). Select APB, AMBA, AMBA2, slave. Drag this onto one of the instantiation blocks of one of the wrappers. You will be prompted to associate the signals of the wrapper with standard APB3 signals. Many of these can be associated automatically by using the map by name feature (upper right) if we took care to use similar names when we created the wrapper ports. You will find most of ths signals mapped except PSELx and PADDR. Use the signal window set PSELx to PSEL and the width window to set to 1 bit. We are not using PADDR so it can remain blank. The result should look like this.

Repeat the process for the other wrapper. The blocks should now look like this. Notice that all the APB3 signals are contained in one bus connection.

Step 4.5: Intstantiate APB3 Core Interface

Under bus interfaces in the catalog window (left) select CoreAPB3 and drag onto the canvas. A configuration window will appear. Basically allows you to determine how many IO devices you wish to select and how much memory space you wish to allocate per device. Keep all the settings but reduce the slot number (devices) to just two. It should look like this.

Acknowledge this window and a APB3 bus connection icon will appear in the canvas. You may want to position it and size it. The canvas should look like this now.

Step 4.6: Connect IO Register APB3 Wrappers to APB3 Core Interface

Finally, we can connect the components. SmartDesign has a handy tool for this to. Right click on the canvas and select auto connect. A window will appear asking you to acknowledge the addresses SmartDesing is assigning to the 2 registers. Notice that each address has an address range of 256 bytes as set in the configurator earlier. Agree to these addresses. The window looks a bit like this.

After acknowledging the memory locations the APB3 connections will be made and look a bit like this.

Step 4.7: Connect Resets
Athough the PCLK and FAB_CLK have different names, the tool assumed you wanted to connect these. In this case it is what we intended, but be careful when using the auto connect and be sure the connections are correct. The reset will have to be connected manually. We need to connect the fabric reset ouput from the MSS block to the preset inputs of the register wrappers. To connect two ports or more ports, select all the ports to be connected (control, shift, click). Right click over one of the selected ports and select connect. The canvas will no show the connection.

Notice that the components are all located to one side of the canvas and the reset connection has to be routed over one of the components. SmartDesign has an auto component arrange feature to nicely distribute the components. Right click in the pain and select auto arrange instances. Usually this works, but you might have to manually adjust too. Also use the auto fit feature after auto arranging. It is this middle icon.

Here is what happened to the above canvas after auto arranging.

Step 4.8: Connect LEDs, Switch Ports and Test Points
Finally we need to connect the LEDs, Switches and Test Points to the appropriate FPGA pins. Since the test points are identical for both wrappers we need only connect one set. You can add FPGA ports to the canvas by right clicking just outside the canvas and selecting add port. However, when you have an existing port on the canvas there is a much easier way. Select the port you wisth to provide connection to, right click and select promote to top level. This will create the connection automatically. After adding these connections and auto arranging instances the canvas should look like a bit like this.

Step 4.9: Check Design and Generate HDL for Synthesis
Before synthesis these graphical components need to be converted to HDL. Before doing so, we will run design check. It is the left icon here.

You will notice that the set of unconnected test points are indicated as floating or unconnected. This is fine since we did it intentionally. Next generate HDL by selectin the right icnon here.

One warning about the unconnected testpoints should be indicated. You should see new verilog under the apb3_to_ledswreg icon in file view of design explorer.

Step 5: Synthesize all Components

Synthesize all the components like we did in lab 1 by selecting the Synthesis icon and Running. Correct any errors that might occur. Before we can synthesize, you must go back to the MSS configuration and generate design again. If this is closed, you can open by clicking on the icon in the hierarchy vew in design explorer. If you don't do this, you will get a cryptic error from the Synopsis indicated that there is an error and it can't synthesize.

Step 6: Assign FPGA Pins

Assign FPGA pins to the LEDS, switches and Test Points like you did in lab1. You can ignore the MSS_Reset and just leave it unassigned. It will be automatically assigned to a fixed pin.

Step 7: Place and Route

Run place and route like you did in lab 1.

Step 8: Simulation

While it is possible to simulate the APB3 bus, we will skip this step for now and verify on the kit.

Step 9: Program FPGA

Program the FGPA like you did in lab 1.

Step 10: Debug and Verify with SoftConsole

We will use SoftConsole and the debugger to verify the operation of the IO registers. We will use some GDB read/write commands via the SoftConsole debugger connection to verify the operation of the registers. We will also observe some of the APB3 bus control signals with the scope.
Step 10.1: Create a SoftConsole Project and Debugger Session
Open SoftConsole and create a C project like you did in lab 2. Create a very simple program with just a main and while(1) loop. For example,

int main() { while(1); return 0; }

Take care to add the CMSIS firmware and build target info (see lab 2 for details). Build the code. Setup a debug configuration and open a debug session.

Step 10.2: Use GDB Commands to Verify IO Register Function
GDB provides some basic read/write commands for accessing memory. We could do this thru the command prompt, but we would have to go thru all the setup you experienced in lab 2. Once the debug connecton is open in SoftConsole, you can type any GDB commnand in the console window at the bottom in Debug view.

The write GDB command is "set". The format is:

set *(type *) address = contents

type is char, halfword or word

address location

value to be written

When we settup the APB3 inteface we discoved that register base addresses were:

LED register base address = 0x40050000

Switch register base address = 0x40050100

Although the LED register is 8 bits, we can address it as a word (32 bits) and take care to use values less then 0xFF. Try typing in some values using the expression:

set *(int *) 0x40050000 = value

Remember the LEDs are active low.

The read GDB command is "x" or examine. The format is:

x/nfu address n, The repeat count is a decimal integer; the default is 1. It specifies how much memory (counting by units u) to display.

f, The display format is one of the formats used by print, `s' (null-terminated string), or `i' (machine instruction). The default is `x' (hexadecimal) initially. The default changes each time you use either x or print.

u, The unit size where: b (bytes), h (half words), w (words and the default)

address is the memory location Although the register is 8 bits, lets just read it as 32 bit value and only examine the lower least significant byte.

The command is:

x 0x40050100

Give it a try. Remember the switches are active low.
Step 10.3: Use GDB Commands to Observe the APB3 Bus Signals
Lets observe some basic APB3 bus operaton with the scope. We ported the following signals from with our Verilog code.

USER IO 1 = PCLK;

USER IO 2 = PWRITE;

USER IO 3 = PSEL;

USER IO 4 = PENABLE;

USER IO 5 = PREADY;

Lets verify the PCLK frequency first. Remember we set this to 25 MHz with the clock manager.

Note, it may be necessary to power reset the kit to get the FAB clock and in this case the APB3 clock set. You can try the RESET button or it may be necessary to disconnect the USB power cable (nearest push button). This will disrupt the SoftConsole debug connection. You need to reload the configuration to rescover the connection. Click on the following icon (upper left) in the debug window and reload the configuration

PSEL or peripheral select determines if the register is selected. Lets monitor this signal and trigger on it going from low to high. It will only last for one access so set the trigger up for single sweep. Use the LED register access command:

set *(int *) 0x40050000 = 0x55

Attach the clock output to channel 2. Execute the command and see if you can capture it on the scope. It should look like this.

Notice that PSEL starts just after the rising edge of clock and last for about 2 clock periods as indicated in the timing diagrams above.

Keep PSEL on channel one, but put probe 2 on the PENABLE. Again trigger as above. It should look something like this.

Notice that PENABLE lasts for one clock period during the access phase (2nd clock cycle) as shown in the timing diagrams above.

Keep PSEL on channel one, but put probe 2 on the WRITE line. Notice it stays active (logical high) for the duration of both setup and access cycles.

Keep the scopes connected as in the previous step, but this time try executing the read command to the switch register.

x 0x40050100

Notice that the scope does not trigger. The PSEL that is ported on this test point is from the LED register. It must not respond to a switch access! If you wish to observe the PSEL for the switch register we would have to go back and port it in SmartDesign to a user IO pin.
Step 10.4: Write a Simple Program
Lets write a simple program that reads the switches and writes the LEDs. The program will decode the switches to create a LED pair separation that is proportional to the binary weighted value of the switches value.

Copy the following code into the the main you were using. You can build the code from the build icon in the debugger window. Then reload the compiled code by executing the debugger configuation icon shown above.

#include <inttypes.h> 
#define LEDIO_REG_BASE 0x40050000
#define SWIO_REG_BASE 0x40050100

int main()
{
    /* Soft Reset Register */
    uint32_t * SYSREG_SOFT_RST_CR = (uint32_t *)(0xE0042030);

    uint32_t temp = 0;
    uint32_t* LEDIO_OUT = (uint32_t*)LEDIO_REG_BASE;
    uint32_t* SWIO_IN = (uint32_t*)SWIO_REG_BASE;

    while(1){
        temp = *SWIO_IN;
        if (temp == 3)
            *LEDIO_OUT = ~0x81;
        else if (temp == 2)
            *LEDIO_OUT = ~0x42;
        else if (temp == 1)
            *LEDIO_OUT = ~0x24;
        else
            *LEDIO_OUT = ~0x18;
    }
    return 0;
}

Try running the code and observing PSEL and PENABLE. You will notice you do not have to single sweep trigger since the writes are triggering at a regular rate.

Lab 3 Debugging Pit Falls

1) You have to stop running the debugger if you want to want to load new hardware to the FPGA. Flash Pro will give a cryptic error if you fail to do this.

2) Sometimes when running the debugger things just hang. Try reloading the debug session or you may have to press the RESET switch on the kit.

3) If you change the settings for the FAB clock and reprogram the FPGA, it may be necessary to power reset the kit for new FAB clock setting to work. Disconnect the USB power cable (nearest push button) and reconnect. This will disrupt the SoftConsole debug connection. You need to reload the debug configuration to recover the connection.

Post-Lab Assignment

For the post lab, we will add the decoder from lab 1 to this reference design as a MMIO device. You can use either the one you modified for the post lab or the In-Lab version. You will remove the switch and LED connections and replace them with registers. You will need a write only register to provide the inputs and a read only register for the outputs.

There are probably a few different ways to implement this, but assigning the read and write registers unique addresses is probably the easiest. Create a wrapper for each register and port the register to the decoder. You will need to add a few more slots to the APB3core interface to accomodate the additional registers.

Debug the decoder by using the GDB read and write commands or writing a simple program. Add test points to the decoder read register and observe the read access by triggering on PSEL and observing PENABLE and PWRITE. Take a picture of both or sketch (include time/div) for your post lab.

When you are statisfied the decoder is working, write a C program that uses the switches as inputs to the decoder and the LEDs as outputs from the decoder.

Post Lab Deliverables

1) Submit a hardcopy of your C code.

2) Submit a hardcopy of your Verilog register wrappers for the decoder interface.

3) Submit scope pictures of the read accesses.

4) Demonstrate the operation of your system at the beginning of the due lab date or before during staffed open hours. Provide a single page with your names, unique names and the lab number for the lab instructor to sign.

5) Answer the following questions:

5.1) It is obvioulsy simpler to implement the decoder directly in hardware like we did in lab 1. What are the advantages of using the processor, the switches and leds as general purpose IO and the decoder as a user accessable special purpose hardware function?

5.2) what are the advantages of implementing the decoder as dedicated hardware with hardwired IO?

References