|
|
 |
FPGA design from scratch. Part 24
System simulations
System simulation is an effective method for verifying that software drivers are compatible with the hardware. Using a simulator for that purpose offers several advantages over GDB. For example, no physical link to the hardware is required and internal system signals may be easily monitored. In addition, the hardware does not need to be run through the implementation tools to run a system simulation, allowing the quick analysis of the effects of changes to the code or the generics. The flow presented here can easily be modified to support structural and timing simulation.
The EDK System Simulation guide presents Xilinx idea about system simulations. They use Modelsim and the setup is to complicated for my taste. We will stay with NCSIM and the simulation strategy we formulated in part 23. We will specify testcases to verify all interfaces in our system. Each testcase will include a c-program to access the peripheral and it's registers. Here is our system.

DDR SDRAM controller
The DDR SDRAM controller (opb_ddr) connects the DDR SDRAM to the OPB. The DDR SDRAM is a Micron MT46V16M16P-6T.
Features
- OPB interface
- Performs device initialization sequence upon power-up and reset conditions for ~200uS. Provides a parameter to adjust this time for simulation purposes only.
- Performs auto-refresh cycles
- Supports CAS latencies of 2 or 3 set by a design parameter
- Supports 16, 32 and 64 bits DDR data widths set by a designparameter
- Supports indeterminate burst length
- Provides big-endian connections to memory devices
- Supports multiple (up to 4) DDR memory banks.
DDR SDRAM Initialization
Prior to normal operation, DDR SDRAMs must be powered up and initialized in a predefined manner. Operational procedures, other than those specified, may result in undefined operation. To ensure device operation, the DRAM must be initialized as described in the following steps:
- Simultaneously apply power to VDD and VDDQ
- Apply VREF and then VTT power. VTT must be applied after VDDQ to avoid device latchup, which may cause permanent damage to the device. Exept for CKE, inputs are not recognized as valid until after VREF is applied.
- Assert and hold CKE at a LVCMOS logic LOW. Maintaining an LVCMOS LOW level on CKE during power-up is required to ensure that the DQ and DQS outputs will be in the High-Z state, where they will remain until driven in normal operation (by a read access).
- Provide stable clock signals.
- Wait at least 200μs.
- Bring CKE HIGH, and provide at least one NOP or DESELECT command. At this point, the CKE input changes from a LVCMOS input to a SSTL_2 input only and will remain a SSTL_2 input unless a power cycle occurs.
- For more information about the initialization see the DDR SDRAM data sheet.
Setting the initialization time
The OPB DDR block will insert a 200us delay before it asserts DDR_init_done (see waveform plot) to allow time for DDR SDRAM initialization to happen.

For simulation purpose only we can change that time by editing the wrapper file ddr_sdram_64mx32_wrapper.vhd. Look for the line: C_SIM_INIT_TIME_PS => 200000000 and change it to C_SIM_INIT_TIME_PS => 20000 (20ns).

C program
This program will write and read three addresses in the DDR SDRAM.
int main()
#define poke(addr,val) (*(unsigned char*) (addr) = (val)) #define pokew(addr,val) (*(unsigned*) (addr) = (val)) #define peek(addr) (*(unsigned char*) (addr)) #define peekw(addr) (*(unsigned*) (addr))
{
unsigned char byte_of_data; unsigned word_of_data; pokew(0x44000000,0xffffffff); pokew(0x46000004,0xaaaaaaaa); pokew(0x47fffffc,0x55555555); word_of_data = peekw(0x44000000); word_of_data = peekw(0x46000004); word_of_data = peekw(0x47fffffc); return 0; } Assembly code
0: 3021fff0 addik r1, r1, -16 4: fa61000c swi r19, r1, 12 8: 12610000 addk r19, r1, r0 c: 3060ffff addik r3, r0, -1 10: b0004400 imm 17408 14: f8600000 swi r3, r0, 0 18: b000aaaa imm -21846 1c: 3060aaaa addik r3, r0, -21846 20: b0004600 imm 17920 24: f8600004 swi r3, r0, 4 28: b0005555 imm 21845 2c: 30605555 addik r3, r0, 21845 30: b00047ff imm 18431 34: f860fffc swi r3, r0, -4 38: b0004400 imm 17408 3c: e8600000 lwi r3, r0, 0 40: f8730008 swi r3, r19, 8 44: b0004600 imm 17920 48: e8600004 lwi r3, r0, 4 4c: f8730008 swi r3, r19, 8 50: b00047ff imm 18431 54: e860fffc lwi r3, r0, -4 58: f8730008 swi r3, r19, 8 5c: 10600000 addk r3, r0, r0 60: 10330000 addk r1, r19, r0 64: ea61000c lwi r19, r1, 12 68: 30210010 addik r1, r1, 16 6c: b60f0008 rtsd r15, 8 70: 80000000 or r0, r0, r0
Simulation result

LED displays and push buttons
The LED displays (LED_Positions and LEDs_4Bit) and the push buttons are connected to the general purpose input/outputs and through the OPB General Purpose Input/Output (GPIO) controller to the OPB bus.
Features
- OPB v2.0 bus interface with byte-enable support
- Configurable as single or dual GPIO channel(s)
- Number of GPIO bits configurable from 1 to 32 bits
- Each GPIO bit dynamically programmable as input or output
- Can be configured as inputs-only on a per channel basis to reduce resource utilization
- Ports for both 3-state and non 3-state connections
- Independent reset values for each bit of all registers
- Optional interrupt request generation
Address ranges
0x40020000-0x4002ffff LEDs_Positions 0x40040000-0x4004ffff LEDs_4Bit 0x40000000-0x4000ffff Push_Buttons_Position
OPB GPIO Registers
There are four internal registers in the OPB GPIO design as shown in the table. These registers are implemented in the GPIO_CORE interface module. The memory map of the OPB GPIO design is determined by setting the C_BASEADDR parameter. The internal registers of the OPB GPIO are at a fixed offset from the base address.
Register Name
| Description | OPB Address
| Access | | GPIO_DATA | Channel 1 OPB GPIO Data Register | C_BASEADDR + 0x00 | Read/Write | | GPIO_TRI | Channel 1 OPB GPIO 3-state Register | C_BASEADDR + 0x04 | Read/Write | GPIO2_DATA
| Channel 2 OPB GPIO Data register
| C_BASEADDR + 0x08 | Read/Write | | GPIO2_TRI | Channel 2 OPB GPIO 3-state Register
| C_BASEADDR + 0x0C
| Read/Write | C program
int main()
#define poke(addr,val) (*(unsigned char*) (addr) = (val)) #define pokew(addr,val) (*(unsigned*) (addr) = (val)) #define peek(addr) (*(unsigned char*) (addr)) #define peekw(addr) (*(unsigned*) (addr))
{
unsigned char byte_of_data; unsigned word_of_data; // Write to LEDs pokew(0x40020000,0xffffffff); pokew(0x40020004,0x00000000); pokew(0x40020008,0x33333333); pokew(0x4002000c,0x00000000); pokew(0x40040000,0x55555555); pokew(0x40040004,0x00000000); pokew(0x40040008,0x11111111); pokew(0x4004000c,0x00000000); // Read push buttons pokew(0x40000004,0xffffffff); word_of_data = peek(0x40000000); pokew(0x4000000c,0xffffffff); word_of_data = peek(0x40000008); return 0;
Assembly code
0: 3021fff0 addik r1, r1, -16 4: fa61000c swi r19, r1, 12 8: 12610000 addk r19, r1, r0 c: 3060ffff addik r3, r0, -1 10: b0004002 imm 16386 14: f8600000 swi r3, r0, 0 18: b0004002 imm 16386 1c: f8000004 swi r0, r0, 4 20: b0003333 imm 13107 24: 30603333 addik r3, r0, 13107 28: b0004002 imm 16386 2c: f8600008 swi r3, r0, 8 30: b0004002 imm 16386 34: f800000c swi r0, r0, 12 38: b0005555 imm 21845 3c: 30605555 addik r3, r0, 21845 40: b0004004 imm 16388 44: f8600000 swi r3, r0, 0 48: b0004004 imm 16388 4c: f8000004 swi r0, r0, 4 50: b0001111 imm 4369 54: 30601111 addik r3, r0, 4369 58: b0004004 imm 16388 5c: f8600008 swi r3, r0, 8 60: b0004004 imm 16388 64: f800000c swi r0, r0, 12 68: 3060ffff addik r3, r0, -1 6c: b0004000 imm 16384 70: f8600004 swi r3, r0, 4 74: b0004000 imm 16384 78: e8600000 lwi r3, r0, 0 7c: f8730008 swi r3, r19, 8 80: 3060ffff addik r3, r0, -1 84: b0004000 imm 16384 88: f860000c swi r3, r0, 12 8c: b0004000 imm 16384 90: e8600008 lwi r3, r0, 8 94: f8730008 swi r3, r19, 8 98: 10600000 addk r3, r0, r0 9c: 10330000 addk r1, r19, r0 a0: ea61000c lwi r19, r1, 12 a4: 30210010 addik r1, r1, 16 a8: b60f0008 rtsd r15, 8 ac: 80000000 or r0, r0, r0
Simulation result

Embedded Test Controller
The Embedded Test Controller (ETC) is a custom IP that implements a 1149.1 complient JTAG tester. It can run JTAG test sequences on the board and/or the complete system during startup and normal operation.
ETC address map
Register/ Memory Name
| Size | Access | Address | | Control | 32 | Write | 0x42a10000 | Status
| 32 | Read | 0x42a10004 | | Execute | 32 | Write | 0x42a10008 | | Debug | 32 | Read | 0x42a1000c
| Test program RAM
| 1Kx32 | Write | 0x42a08000 | Test result RAM
| 1Kx32 | Read | 0x42a09000 | C programint main()
#define poke(addr,val) (*(unsigned char*) (addr) = (val)) #define pokew(addr,val) (*(unsigned*) (addr) = (val)) #define peek(addr) (*(unsigned char*) (addr)) #define peekw(addr) (*(unsigned*) (addr))
{
unsigned char byte_of_data; unsigned word_of_data; // Write testprogram pokew(0x42a08000,0x800000f1); pokew(0x42a08004,0x80000000); pokew(0x42a08008,0x800000f2); pokew(0x42a0800c,0x8000000c); // Write control register in ETC // Enable TCK clock pokew(0x42a10000,0x000000a0); // Read status register in ETC word_of_data = peekw(0x42a10004); // Read debug register in ETC word_of_data = peekw(0x42a1000c); // Start test program pokew(0x42a10008,0x00000001);
Simulation result
 Debugging the On-chip Peripheral Bus
When I started to debug the ETC IP I quickly found out that my implementation and the Xilinx implementations of the OPB interface differed.
Specifications
Here is the IBM On-Chip Peripheral Bus Architecture Specification and here is the Xilinx implementation MicroBlaze write cycle. Let's take a closer look at the OPB. Here are two plots showing all signals in the Xilinx implemantation of the OPB interface.
Here are all the input signals to the mb_opb block.

Here are all the output signals from the mb_opb block.

OPB signal description
| Signal | Description | Width
| Used in ETC
| | OPB_ABus | Address bus
| 32
| Yes
| | OPB_BE | Data byte enable
| 4
| Yes
| | OPB_beAck | OPB byte enable acknowledge
| 1
| No | | OPB_beXfer | OPB byte enable transfer
| 1
| No | OPB_busLock
| OPB bus arbitration lock | 1
| No | | OPB_Dbus | OPB Ddata bus
| 32
| Yes
| | OPB_dwAck | OPB doubleword acknowledge | 1
| No | | OPB_dwXfer | OPB doubleword transfer
| 1
| No | | OPB_errAck | OPB error acknowledge | 1
| Yes, always = 0
| OPB_fwAck
| OPB fullword acknowledge
| 1
| Yes, always = 0
| OPB_fwXfer
| OPB fullword transfer
| 1
| No
| OPB_hwAck
| OPB halfword acknowledge
| 1
| No
| OPB_hwXfer
| OPB halfword transfer
| 1
| No
| OPB_MGrant
| OPB master bus grant
| 2
| No
| OPB_MRequest
| OPB Master bus request
| 2
| No
| OPB_pendReq
| OPB pending master request
| 2
| No
| OPB_rdDbus
| OPB read data bus
| 32
| No
| OPB_retry
| OPB bus cycle retry
| 1
| Yes, always = 0
| OPB_RNW
| OPB read not write (write=L)
| 1
| Yes
| OPB_Rst
| OPB reset
| 1
| Yes
| OPB_select
| OPB select
| 1
| Yes
| OP_seqAddr
| OPB sequential address
| 1
| No
| OPB_timeout
| OPB timeout error
| 1
| No
| OPB_toutSup
| OPB timeout suppress
| 1
| Yes, always = 0
| OPB_wrDbus
| OPB write data bus
| 32
| No
| OPB_xferAck
| OPB transfer acknowledge
| 1
| Yes
|
Final result
It took me a couple of days to sort out all issues around the ETC OPB interface. Here is what I found out. Right or wrong?
- The read and write cycles can be two or more clock cycles long. The OPB interface can decide how long the read and write cycles will be by asserting the OPB_xferAck in the last clock cycle. I am using 3 clock cycles in the ETC OPB interface.
- The fullword and halfword control signals are not implemented in the Xilinx version of OPB. Use the signal OPB_BE instead. OPB_BE = 4'b1111 enable all four bytes when reading and writing 32 bit words.
- Always keep the read data bus low when no valid read cycle is taking place. Read data busses from all peripherals are or'ed together in the mb_opb block and only one peripheral at a time can drive the bus high. Important !!!
- You don't have to implement the signals OPB_errAck, OPB_toutSup and OPB_retry. I have set them all low.
Here is my OPB interface to the ETC block. Comments and improvements are welcomed.
Top Next Previous
|