Course pages 2015–16
ECAD and Architecture Practical Classes
Exercise 3: Etch A Sketch in simulation
Functional modelling
In a typical design project, there are often several teams working in parallel. For instance, in a smartphone project the chip design team may be working on the hardware, the OS team may be working on the operating system and writing drivers, the applications team may be designing the user interface, the mechanical team working on the packaging and the manufacturing team making a production line to build and test the product cost-effectively. To reduce the time-to-market frequently these teams start their work concurrently: there is not time to perfect the hardware before writing the software.
To allow concurrent development we can simulate parts that do not yet exist. For example we can develop drivers on a simulation of the chip before silicon is available. However a full-scale simulation of the chip is very slow and unnecessarily detailed for the driver writer, and the driver writer still needs to wait for a large chunk of the chip design to be completed before proceeding.
One approach to address this is top-down refinement. We can write functional models for components that are yet to exist. These express the functionality required, before the implementation details have been determined. They are then made available for developers in other teams to start their work. Once the real implementation of the components exist they can replace the functional models, but we may retain them as they are often much quicker to simulate than the real thing. Functional models may make use of features not available in the target environment to aid debugging - for example simulation models can be written in software rather than hardware, and mechanical models might use rapidly 3D printed plastic instead of metal parts.
In Lab 2 we are going to synthesise an FPGA design containing the Yarvi processor and your SystemVerilog peripherals and download it onto an FPGA board. FPGA synthesis is a computationally hard problem and so can take up to 20 minutes to run. To aid development times you will test your software in the simulator against a functional model of the display and rotary dials on the FPGA board. The functional model will run in C and communicate over a TCP socket with your Yarvi SystemVerilog that runs in Modelsim, displaying in a window what you would see on the LCD on the FPGA board. By running in software simulation you are able to see the full behaviour of the CPU which would not be possible when it is running in hardware.
The Etch-a-Sketch
The goal of Lab 1 is to build a system to implement an Etch A Sketch in simulation. An Etch A Sketch is a 1950s mechanical toy bearing two knobs, which allows the user to draw patterns on the screen. The unit (mechanically) maintains an X,Y coordinate the pixel of which is continually painted black. When the left knob is rotated the X coordinate is increases or decreases, when the right knob is rotated the Y coordinate increases or decreases, which allows lines to be drawn. Moving both knobs at the same time enables drawing diagonal lines, and shaking the unit clears the display.
Avalon Memory-Mapped (Avalon MM) is Altera's on-FPGA memory bus. We have added a port to Yarvi so that it can be a master to peripherals on the Avalon bus. We have mapped the Avalon-MM bus into Yarvi space starting at 0x80000000. (While that will still be true on the FPGA, from the perspective of the FPGA it'll have another base – we'll come to that when we build it).
In our case we have two dials, a 'clear' button, and a 480x272 pixel display to show the picture. These are mapped into Yarvi memory space as follows: (the symbols are defined in avalon_addr.h and should all be added to AVALON_BASE to compute the address from the Yarvi perspective.).
Component | Yarvi Base | Length | Access | Symbol |
---|---|---|---|---|
64MB SDRAM | 0x80000000 | 64MiB | Read/write | SDRAM_BASE |
— includes display framebuffer | 0x80000000 | 480*272*16 bits = 261120 bytes | Read/write | FRAMEBUFFER_BASE |
LED output | 0x84000000 | 32 bits | Write only | PIO_LED_BASE |
Left dial counter | 0x84000100 | 32 bits | Read only | PIO_ROTARY_L |
Right dial counter | 0x84000200 | 32 bits | Read only | PIO_ROTARY_R |
Display buttons | 0x84000300 | 16 bits | Read only | PIO_BUTTONS |
We provide a display emulator program to implement these in software that Modelsim is able to connect to. You can run the emulator as follows:
- First, download a new copy of the Yarvi source and software, put them in a directory exercise3.
- Open a new terminal, source the $CLTEACH/swm11/setup.bash script, and change to the directory containing your new Yarvi source.
- Type: make
This will build the emulator for your local machine. - Type: ./displayemul
This will run the emulator, which will then listen for connections from Modelsim. It will only accept one connection at a time. - A black window will appear. This shows the display framebuffer, which you can write to from the Yarvi. You can also use the following keypresses:
Key | Action | Bit in button register |
---|---|---|
Cursor left | Left dial anticlockwise | n/a |
Cursor right | Left dial clockwise | n/a |
Cursor up | Right dial anticlockwise | n/a |
Cursor down | Right dial clockwise | n/a |
N | Left dial click | 14 |
M | Right dial click | 13 |
A | Button A press | 3 |
B | Button B press | 1 |
X | Button X press | 2 |
Y | Button Y press | 0 |
S | Navigation stick left | 8 |
F | Navigation stick right | 9 |
E | Navigation stick up | 11 |
C | Navigation stick down | 10 |
D | Navigation stick centre click | 12 |
Now run a Yarvi simulation in Modelsim. You should see:
# Display emulator setup... # Successful connection to display emulator
to indicate a connection has been successful. When you read or write emulated devices, for example:
lui s10, (0x80000000>>12) sw a0, 0(s10)
you should see logs in both the ModelSim window:
# 420 0000001c pre: imm=0x00080000, zero fill # 440 0000001c x26 <- 0x80000000 # 440 0000001c lui x26, 0x80000 # Display emulator write avalon[0] byteenable f <- 0xc # 480 00000020 sw x10, 0(x26)
and on the terminal in which you are running the display emulator:
Write address=0, byteenable=0xf, data=0xc ---- SDRAM write address 0x00000000 byte enable 0xffffffff <= 0x0000000c
The following C functions will enable access to the peripherals:
// this file contains the locations of the registers as described in the table above #include "avalon_addr.h" int avalon_read(unsigned int address) { volatile int *pointer = (volatile int *) address; return pointer[0]; } void avalon_write(unsigned int address, int data) { volatile int *pointer = (volatile int *) address; pointer[0] = data; }
For example, you read the left dial position with:
int left; left = avalon_read(AVALON_BASE + PIO_ROTARY_L);
When doing so, you should see both a log in the Modelsim trace and a message in the display emulator window.
You can plot a pixel with the following function:
// our pixel format in memory is 5 bits of red, 6 bits of green, 5 bits of blue #define PIXEL16(r,g,b) (((b & 0x1F)<<11) | ((g & 0x3F)<<5) | ((r & 0x1F)<<0)) // ... but for ease of programming we refer to colours in 8/8/8 format and discard the lower bits #define PIXEL24(r,g,b) PIXEL16((r>>3), (g>>2), (b>>3)) #define PIXEL_WHITE PIXEL24(0xFF, 0xFF, 0xFF) #define PIXEL_BLACK PIXEL24(0x00, 0x00, 0x00) #define PIXEL_RED PIXEL24(0xFF, 0x00, 0x00) #define PIXEL_GREEN PIXEL24(0x00, 0xFF, 0x00) #define PIXEL_BLUE PIXEL24(0x00, 0x00, 0xFF) #define DISPLAY_WIDTH 480 #define DISPLAY_HEIGHT 272 void vid_set_pixel(int x, int y, int colour) { // derive a pointer to the framebuffer described as 16 bit integers volatile short *framebuffer = (volatile short *) (AVALON_BASE + FRAMEBUFFER_BASE); // make sure we don't go past the edge of the screen if ((x<0) || (x>DISPLAY_WIDTH-1)) return; if ((y<0) || (y>DISPLAY_HEIGHT-1)) return; framebuffer[x+y*DISPLAY_WIDTH] = colour; }
Exercise
Write some C code to implement the Etch A Sketch. Your software should read the dial inputs and set the relevant pixel to white. When the left or right dial is clicked, the screen should clear to black. You'll find clearing the screen is slow in the emulator, so don't clear it on startup.
Optional exercise for starred Tick
Take a copy of your Etch A Sketch code and modify it to implement Pong.
Pong differs from Etch A Sketch in a key way: Etch A Sketch is time invariant, while Pong becomes unplayable if gameplay is too fast or too slow. To remedy this, first write a delay function that spins for 100 microseconds, and calibrate this with Modelsim. While you are running the code in simulation you should omit delays, but you will need to insert delays in the code when it runs on FPGA. (We can also include timers on the FPGA for real-world timing but, since the simulation speed depends on workload, it is difficult to calibrate gameplay in simulation)