Introduction
In this first lab, you will learn how to work with an ARM processor and the basics of ARM assembly
by programming some common routines. After you complete the tasks, you should demonstrate
your work to a TA.
1 Working with the DE1-SoC Computer System
For this course, we will be working with the DE1-SoC Computer System, which is composed of an
ARM Cortex-A9 processor and peripheral components located on the FPGA on your DE1-SoC board.
The IDE we will be using is the Intel FPGA Monitor Program 16.1. In this part of the lab, you will
learn how to program the Computer System in ARM assembly.
1.1 Learn about the tools
Before you move on, you should read the Introduction to the ARM Processor Using Altera Toolchain
and acquaint yourself with the Altera Monitor Program Tutorial for ARM. You will also find it useful
to refer to the ARM Architecture Reference Manual. These documents can be found on myCourses. It
may help to keep these manuals open as you work.
1
1.2 Your first assembly program
1. Open the ‘Intel FPGA Monitor Program 16.1’ from the desktop icon and select File->New.
Figure 1: Your first assembly program – Step 1
2
2. In the new editor window, type out the code as shown in Figure 2 and save this file as ‘part1.s’
within a new folder ‘GXX Lab1’ on your network drive. Here, GXX stands for your group
number! eg. Group 1 would be G01 Lab1. The code is a simple program to find the maximum number from a list of ‘NUMBERS’ with length ‘N’. Notice the extensive use of comments!
This practice should be used throughout this course, especially with assembly programming!
NOTE: The indentation is important. The code will not compile if not indented as shown.
Figure 2: Your first assembly program – Step 2
3
3. Open the ‘Intel FPGA Monitor Program 16.1’ from the desktop icon and select File->New
Project.
Figure 3: Your first assembly program – Step 3
4
4. Set the project directory to GXX Lab1 and set the project name to GXX Lab1. Select the ‘ARM
Cortex-A9’ processor architecture, and click ‘Next’.
Figure 4: Your first assembly program – Step 4
5
5. In the next window, under ‘Select a system’ select the ‘DE1-SoC Computer’ and click ‘Next’.
Figure 5: Your first assembly program – Step 5
6
6. In the next window, under ‘Program type’ select the ‘Assembly Program‘ and click ‘Next’.
Figure 6: Your first assembly program – Step 6
7
7. In the next window ‘Specify program details’, click on ‘Add…’ and select the file ‘part1.s’ created in step 1, and click ‘Next’.
Figure 7: Your first assembly program – Step 7
8
8. In the next window ‘Specify system parameters’, ensure that the board is detected in the ‘Host
connection’ box, and click ‘Next’. Note that the board has to be plugged in via USB and powered on to be detected.
Figure 8: Your first assembly program – Step 8
9
9. In the next window ‘Specify program memory settings’, simply click ‘Finish’.
Figure 9: Your first assembly program – Step 9
10
10. A dialogue box should now pop up, asking whether you would like to download the system
onto the board. If you were successfully able to flash your JIC file in Lab0, click ‘No’, otherwise
click ‘Yes’.
Figure 10: Your first assembly program – Step 10
11
1.3 Using the IDE
Now that we have created our first assembly project, let’s take a look at some of the features of the
IDE and use them in order to debug this program and verify that it works as desired
NOTE: This section only provides a very brief introduction to the IDE. More detailed information can and should be obtained in the documentation and by experience!
1. Figure 11 shows the useful features of the IDE when a project is opened. We can say that we
are now in ‘development mode’ – where the code is not loaded onto the board and we are in
the process of writing code and compiling it to check for errors.
The green box highlights the different IDE window tabs, and since we are in development
mode, the only useful window is the ‘Editor’ window where code can be created/modified.
You can add/remove windows using the ‘Windows’ menu at the top.
The red box highlights three useful buttons in development mode – ‘Compile’, ‘Load’, and
‘Compile & Load’. Their functions are self-explanatory.
Actually, ‘compiling’ refers to converting higher level computer code (such as C code) into assembly instructions. What we are doing here is ‘assembling’, which refers to the conversion of
assembly instructions into machine code. However, since Altera has decided to call it the ‘Compile’
button, we will stick with that name for the sake of clarity.
Figure 11: Using the IDE – Development mode
12
2. When the code is loaded onto the board (by clicking either ‘Load’ or ‘Compile & Load’), we
can say that we are now in ‘debug mode’. The IDE is now connected to the board via a debug
server, and we can send execution instructions to the board and receive data (such as register
and memory values) back from the board.
The green box highlights the two important windows in this mode. In the Disassembly window, we can see the code that is being executed, as well as the current instruction when the
code is paused. We also have the ability to set/remove breakpoints by clicking on the grey area
to the left of the instruction. The Disassembly window is the most important window in debug
mode. In the Memory window, we can see the contents of a desired memory location, but only
when the program is paused!
The red box highlights the useful buttons in debug mode. Using them, we can ‘Continue’,
‘Pause’ and ‘Restart’ the program execution. We can also step by a single instruction, or step
over multiple instructions. Finally, we can also disconnect from the board.
Figure 12: Using the IDE – Debug mode
13
3. Now let’s run the code and verify the result. Before you do this, make sure you have read the
code and understand how it works, otherwise you won’t know what it is that you’re checking!
Ensure that we are in debug mode and looking at the Disassembly window. Click on the
‘Continue’ button, and then click on the ‘Pause’ button. The code should stop at the B END
instruction. Notice how the contents of the registers have now changed, and R0 contains the
expected value!
Experiment with the IDE features by restarting the program from the first instruction and
arriving at the end via steps and breakpoints.
Finally, note the address 0x00000038 of RESULT, as it will be used in the next part.
Figure 13: Using the IDE – The Disassembly window
14
4. Now move over to the Memory window, and search for the value in the address of RESULT.
Once again, we can see that the expected value has appeared in that memory location.
Figure 14: Using the IDE – The Memory window
15
2 Some programming challenges
Now that you have gone through a simple example in which we have given you the program to be executed, you should complete the following tasks, which will require you to write your own programs.
NOTE: You will have to add the new files you will create to your current project GXX Lab1.
Since the same label ‘ start’ cannot be used in multiple files, and subroutines are beyond the
scope of this lab, the workaround you should use in this lab is to only have one file added to
the project at any given time!
2.1 Fast standard deviation computation
Suppose that you would like to use the ARM processor to compute the standard deviation of a signal
X = {x1, x2, . . . , xN }. The formula for the standard deviation is:
σˆ =
sPN
i=1(xi − µˆ)
2
N − 1
(1)
where µˆ is the average value of the signal. Unfortunately, implementing this formula requires multiplication, division, and square root operations, which are not available as instructions on all processors and are slow to emulate using other instructions. The standard deviation can be approximately
computed in a more hardware-friendly way using the so-called “range rule”:
σˆ ≈
xmax − xmin
4
(2)
where xmax and xmin are the maximum value and minimum value of the signal, respectively.
Write an ARM assembly program which computes the standard deviation of a signal, using the
range rule. The program should accept input values – more specifically, the number of samples in
the signal and their values – using a similar approach as shown in Part 1. Save your code in a file
named ‘stddev.s’
(Hint: you can reuse your code from Part 1 to compute the maximum value. Then, you can make
a simple modification to this code to get code which computes the minimum. Also, remember that
dividing by a power of 2 can be implemented using shift instructions.)
16
2.2 Centering an array
It is often necessary to ensure that a signal is “centered” (that is, its average is 0). For example,
DC signals can damage a loudspeaker, so it is important to center an audio signal to remove DC
components before sending the signal to the speaker.
You can center a signal by calculating the average value of the signal and subtracting the average from every sample of the signal. Write an ARM assembly program to center a signal. In this
example, store the resulting centered signal ‘in place’ – i.e. in the same memory location that the
input signal is passed in. The program should be able to accept the signal length as an input parameter. In order to simplify calculations, work with the assumption that only signal lengths that
are powers of two can be passed to the program. Save your code in a file named ‘center.s’
2.3 Sorting
Write an ARM assembly program which sorts an array in ascending order. You could use the simple
bubble sort algorithm:
// Given an a r r ay A of l e n g t h N
s o r t e d = f a l s e
wh i le no t s o r t e d :
s o r t e d = t r u e
fo r i = 2 t o N :
i f A[ i ] < A[ i −1], swap A[ i ] wi th A[ i −1] and s e t s o r t e d = f a l s e
You could also implement a more sophisticated sorting algorithm. Store the resulting sorted array
‘in place’. The program should be able to accept the array length as an input parameter. Save your
code in a file named ‘sort.s’
17
3 Grading and report
The TA will ask to see the following deliverables during the demo (the corresponding portion of your
grade for each is indicated in brackets):
• Largest integer program (10%)
• Standard deviation program (15%)
• Centering program (25%)
• Sorting program (30%)
A portion of the grade is reserved for answering questions about the code, which is awarded individually to group members. All members of your group should be able to answer any questions the
TA has about any part of the deliverables, whether or not you wrote the particular part of the code
the TA asks about. Full marks are awarded for a deliverable only if the program functions correctly
and the TA’s questions are answered satisfactorily.
Finally, the remaining 20% of the grade for this Lab will go towards a report. Write up a short
(2-3) page report that given a brief description of each part completed, the approach taken, and the
challenges faced, if any. Please don’t include the entire code in the body of the report. Save the
space for elaborating on possible improvements you made or could have made to the program, such
as a feature to detect empty (length 0) arrays, etc.
Your final submission should be a single compressed folder that contains your report and the
four assembly files – ‘part1.s’, ‘stddev.s’, ‘center.s’, and ‘sort.s’.
This Lab will run for two weeks, from January 22nd to February 2nd, during which you should
demo your code within your assigned lab period. The report for Lab 1 is due by 11:59 pm, February 9th.
In this lab, you will learn how to use subroutines and the stack, program in C, and call code written
in assembly from code written in C.
1 Subroutines
1.1 The stack
The stack is a data structure which can be helpful for situations when there are not enough registers
for a program to use only registers to store data. You will also need to make use of the stack when
calling subroutines to save the state of the code outside of the subroutine.
Review how the PUSH and POP instructions work. Note that pushing and popping can be implemented without using the PUSH and POP instructions by using other ARM instructions. Rewrite
the following PUSH and POP instructions using only other instructions:
• PUSH {R0}
• POP {R0 – R2}
Write a test program in assembly to show that your rewritten versions correctly implement PUSH
and POP. You should be able to show the TA the contents of main memory changing as registers are
pushed onto the stack.
1.2 The subroutine calling convention
The convention which we will use for calling a subroutine in ARM assembly is as follows.
The caller must:
• Move arguments into R0 through R3. (If more than four arguments are required, the caller
should push the arguments onto the stack.)
• Call the subroutine using BL
The callee must
• Move the return value into R0
• Ensure that the state of the processor is restored to what it was before the subroutine call
• Use BX LR to return to the calling code
1
(The state can be saved and restored by pushing R4 through LR onto the stack at the beginning
of the subroutine and popping R4 through LR off the stack at the end of the subroutine.)
Convert your program from Lab 1 for finding the max of an array into a program which uses a
subroutine. The subroutine should return the max in R0.
1.3 Fibonacci calculation using recursive subroutine calls
A recursive subroutine is a subroutine which calls itself. You can calculate the nth Fibonacci number,
Fn (where F0 = 1, F1 = 1, F2 = 2, F3 = 3, F4 = 5, . . . ), using a recursive subroutine as follows:
Fi b (n ) :
i f n >= 2 :
re turn Fi b (n−1) + Fi b (n−2)
i f n < 2 :
re turn 1
For example, F4 is computed as follows: Fib(4) = Fib(3) + Fib(2) = (Fib(2) + Fib(1)) + (Fib(1) +
Fib(0)) = ((Fib(1) + Fib(0)) + 1) + (1 + 1) = (1 + 1 + 1) + (1 + 1) = 5
Write an assembly program which computes the nth Fibonacci number in this way. Your program
should have a main section which calls the Fibonacci subroutine recursively for the above pseudocode.
2
Figure 1: C code for computing the max.
2 C Programming
Assembly language is useful for writing fast, low-level code, but it can be tedious to work with.
Often, high-level languages like C are used instead.
2.1 Pure C
We will first go through an example of programming in straight C.
• Create a new project, performing the same steps as you performed for an assembly project.
However, when the New Project Wizard asks what program type you would like, select “C
Program”. Click the box next to “Include a sample program with the project”, and select the
“Getting Started” program.
• Delete all the code in “getting started.c” and replace it with the incomplete C program shown
in Figure 1.
• Fill in the code with a for-loop which iterates through the array to find the maximum.
• Compile and run the C program the same way you compile and run assembly programs. Notice
that the disassembly viewer shows how the compiler has translated C into assembly.
2.2 Calling an assembly subroutine from C
It is also possible to mix C and assembly. You will need to do this from Lab 3 onward. Perform the
following steps to write a C program which calls an assembly subroutine.
• Create a new C project, as you did in Section 2.1.
• Add a file to your project called “subroutine.s”. (The filename does not matter, but it should
have the .s extension.)
• Copy the code from Figure 2 into “subroutine.s”. This code computes the maximum of two
numbers and returns the result. Notice that the subroutine does not bother to save and restore
the caller state. This is sometimes OK to do, in subroutines which do not change the state.
• Next, edit your C program so that it contains the code in Figure 3. This code uses the assembly
subroutine to compute the max of two numbers.
• Compile and run the program. Find the main section and the MAX 2 section, and put breakpoints to see the processor run those sections.
• Finally, rewrite your C program to find the max of a list using the MAX 2 subroutine.
3
Figure 2: Assembly code with MAX 2 subroutine.
Figure 3: C code which calls MAX 2 subroutine.
4
3 Grading
The TA will ask to see the following deliverables during the demo (the corresponding portion of your
grade for each is indicated in brackets):
• Test program with rewritten PUSH and POP instructions (15%)
• Assembly code which computes the max of an array using an assembly subroutine (15%)
• Fibonacci program with recursive subroutine (20%)
• C code which computes the max of an array using C (15%)
• C code which computes the max of an array using an assembly subroutine (15%)
Full marks are awarded for a deliverable only if the program functions correctly and the TA’s questions are answered satisfactorily.
A portion of the grade is reserved for answering questions about the code, which is awarded individually to group members. All members of your group should be able to answer any questions
the TA has about any part of the deliverables, whether or not you wrote the particular part of the
code the TA asks about. Full marks are awarded for a deliverable only if the program functions
correctly and the TA’s questions are answered satisfactorily.
Finally, the remaining 20% of the grade for this Lab will go towards a report. Write up a short
(3-4) page report that gives a brief description of each part completed, the approach taken, and the
challenges faced, if any. Please don’t include the entire code in the body of the report. Save the
space for elaborating on possible improvements you made or could have made to the program.
Your final submission should be a single compressed folder that contains your report and all the
code files (.c and .s).
This Lab will run for two weeks, from February 5th to February 16th. You should demo your
code during those dates, within your assigned lab period. The report for Lab 2 is due by 11:59
pm, February 23rd.
Introduction
This lab introduces the basic I/O capabilities of the DE1-SoC computer – the slider switches, pushbuttons, LEDs and 7-Segment displays. After writing assembly drivers that interface with the I/O
components, timers and interrupts are used to demonstrate polling and interrupt based applications
written in C.
1
1 Creating the Project in the Altera Monitor Program
IMPORTANT: The project is structured as outlined below to introduce concepts that are used
in writing well organized code. Furthermore, drivers for configuring the Generic Interrupt
Controller (GIC) will be provided in the latter part of this lab, and the driver code relies on
this project structure. The code will not compile if the project is not organized as described!
First, create a new folder named GXX Lab3, where GXX is the corresponding group number.
Within this folder, create a new folder named drivers. Finally, within the drivers folder, create three
folders: asm, src and inc. The final folder structure is shown in Figure 1.
GXX Lab3
drivers
asm
inc
src
Figure 1: The project folder structure
Create a new file main.c and save it in the GXX Lab3 folder.
Next, open the Altera Monitor Program and create a new project. Select the created folder
GXX Lab3 as the project directory, name the project GXX Lab3, set the architecture to ARM Cortex-A9
and click ‘Next’.
When asked to select a system, select De1-SoC Computer from the drop-down menu and click
‘Next’.
Set the program type as C Program and click ‘Next’.
In the next menu, add main.c to the source files.
In the System Parameters menu, ensure that the board is detected in the ‘Host connection’ dialogue box and click ‘Next’.
Finally, in the memory settings menu, change the Linker Section Presents from ‘Basic’ to ’Exceptions’ and click ‘Finish’.
2
2 Basic I/O
For this part, it is necessary to refer to sections 2.5.6 – 2.5.10 (pp. 8 – 10) and 3.4 (pp. 20 – 21) in
the De1-SoC Computer Manual.
Brief overview
The hardware setup of the I/O components is fairly simple to understand. The ARM cores have
designated addresses in memory that are connected to hardware circuits on the FPGA, and these
hardware circuits in turn interface with the physical I/O components.
In the case of most of the basic I/O, the FPGA hardware can be as simple as a direct mapping
from the I/O terminals to the memory address designated to it. For instance, the state of the slider
switches is available to the FPGA on bus of 10 wires which carry either a logical ’0’ or ’1’. This
bus can be directly passed as ’write-data’ to the memory address reserved for the slider switches
(0xFF200040 in this case).
It is useful to have slightly more sophisticated FPGA hardware. For instance, in the case of the
push-buttons, in addition to knowing the state of the button it is also helpful to know whether a
falling edge is detected, signalling a keypress. This can be achieved by a simple edge detection circuit in the FPGA.
The FPGA hardware to interface with the I/O is part of the De1-SoC computer, and is loaded
when the .sof file is flashed onto the board. This section will deal with writing assembly code to
control the I/O interact by reading from and writing to memory.
Getting started: Drivers for slider switches and LEDs
• Slider switches:
Create a new assembly file called slider switches.s in the GXX Lab3/drivers/asm directory.
Create a new subroutine labelled read slider switches ASM, which will read the value at the
memory location designated for the slider switches data into the R0 register, and then branch
to the link register. Make the subroutine visible to other files in the project by using the .global
assembler directive. Remember to use the ARM function calling convention, and save the context
if needed!.
Next, create a new header file called slider switches.h in the GXX Lab3/drivers/inc directory.
The header file will provide the C function declaration for the slider switches assembly driver.
Declare the function as extern int read slider switches ASM(), and make use of preprocessor
directives to avoid recursive inclusion of the header file.
To help get started, code for the slider switches driver has been provided in Figure 2. Use this
as a template for writing future driver code.
• LEDs:
Create a new assembly file called LEDs.s in the GXX Lab3/drivers/asm directory. Create two
subroutines – read LEDs ASM and write LEDs ASM. Again, export both subroutines using the
.global assembler directive
Similar to the slider switches driver, the read LEDs ASM subroutine will load the value at the
LEDs memory location into R0 and then branch to LR. The write LEDs ASM subroutine will
store the value in R0 at the LEDs memory location, and then branch to LR.
Create a new header file called LEDs.h in the GXX Lab3/drivers/inc directory. Provide function
declarations for both the subroutines. The function declaration will not be the exact same
as in the slider switches; one of these functions will have to accept an argument!
3
(a) Assembly file (b) Header file
Figure 2: Code for the slider switches driver
• Putting it together:
Fill in the main.c file in the GXX Lab3 directory. The main function will include the header files
for both the drivers, and will send the switches state to the LEDs in an infinite while loop. The
code for this file is shown in Figure 3.
Figure 3: Code for the main.c file
Next, open the project settings and add all the driver files to the project. Compile and load the
project onto the De1-SoC computer, and run the code. The LED lights should now turn on and
off when the corresponding slider switch is toggled.
Slightly more advanced: Drivers for HEX displays and push-buttons
Now that the basic structure of the drivers has been introduced, custom data types in C will be used
to write drivers that are more readable and easier to implement. In particular, the following two
drivers will focus on using enumerations in C.
• HEX displays:
As in the previous parts, create two files HEX displays.s and HEX displays.h and place them
in the correct folders.
The code for the header file is provided in Figure 4. Notice the new datatype HEX t defined in
the form of an enumeration, where each display is given a unique value based on a one-hot
encoding scheme. This will be useful when writing to multiple displays in the same function
call.
Write the assembly code to implement the three functions listed in the header file. The HEX t
argument can be treated as an integer in the assembly code. The subroutine should check
the argument for all the displays HEX0-HEX5, and write to whichever ones have been
asserted. A loop may be useful here!
HEX clear ASM will turn off all the segments of all the HEX displays passed in the argument.
Similarly, HEX flood ASM will turn on all the segments. The final function HEX write ASM
4
Figure 4: Code for the HEX displays.h file
takes a second argument val, which is a number between 0-15. Based on this number, the
subroutine will display the corresponding hexadecimal digit (0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F)
on the display(s).
A sample program is shown in Figure 5 to demonstrate how multiple displays can be controlled
in the same function call. Since the value for each display is based on a one-hot encoding
scheme, the logical OR of all the displays will assert the bits for all the displays.
Figure 5: Sample program that uses the HEX displays driver
• Pushbuttons:
Create two files pushbuttons.s and pushbuttons.h and place them in the correct folders.
Write the assembly code to implement the functionality described in the header file, as shown
in Figure 6
• Putting it together:
Modify the main.c file to create an application that uses all of the drivers created so far. As
before, the state of the slider switches will be mapped directly to the LEDs. Additionally, the
state of the last four slider switches SW3-SW0 will be used to set the value of a number from
0-15. This number will be displayed on a HEX display when the corresponding pushbutton
is pressed. For example. pressing KEY0 will result in the number being displayed on HEX0.
Since there are no pushbuttons to correspond to HEX4 and HEX5, switch on all the segments
of these two displays. Finally, asserting slider switch SW9 should clear all the HEX displays.
5
Figure 6: Code for the pushbuttons.h file
6
3 Timers
For this part, it is necessary to refer to sections 2.4.2 (p. 4) and 3.2 (p. 20) in the De1-SoC Computer
Manual.
Brief introduction
Timers are simply hardware counters that are used to measure time and/or synchronize events.
They run on a known clock frequency that is programmable in some cases (by using a phase-locked
loop). Timers are usually (but not always) down counters, and by programming the start value, the
time-out event (when the counter reaches zero) occurs at fixed time intervals.
HPS timer drivers
For this section, the drivers have been provided. Download the two files HPS TIM.s and HPS TIM.h
from MyCourses and place them in the correct folders. The code for the header file is shown in
Figure 7.
Figure 7: Code for the HPS TIM.h file
This driver uses a new concept in C – structures. A structure is a composite datatype that allows
the grouping of several variables to be accessed by a single pointer. They are similar to arrays, except that the individual elements of a structure can be of different datatypes! This driver will help
demonstrate how structures can be useful by modifying multiple parameters easily.
7
Notice how the first subroutine HPS TIM config ASM takes a struct pointer as an argument. The
reason for this is that if a struct is passed directly to a function, the compiler unpacks the struct
elements at compile time and passes them as individual arguments to the function. Since in most
cases the number of arguments will be greater than the number of argument registers, the compiler
will place the extra arguments on the stack. This is perfectly fine if all the code is handled by the
compiler, but since this lab requires handwritten assembly drivers, it causes the programmer a lot
of extra overhead when retrieving the arguments in the assembly subroutine. By passing a struct
pointer, the individual elements can be easily accessed at the corresponding offset from the base
address passed in the pointer. For instance, the timeout element can be accessed in the assembly
subroutine via a load instruction from the address in R0 offset by 0x4.
Notice how the timeout struct element is given in microseconds. This hides the hardware specific
details of the timer from the C programmer. Since all of the HPS timers do not run on the same clock
frequency, the subroutine calculates the correct load value for the corresponding timer in order to
achieve the desired timeout value. Similarly, the last three struct elements should be set to 1 to be
enabled, and 0 to be disabled.
The second subroutine HPS TIM read INT ASM supports multiple timer instances passed in the
argument. The return value is an integer that should be interpreted as a 4-bit one-hot encoded
number, with bit 0 corresponding to the S-bit of TIM0, and so on. Thus this function can be used to
read the S-bit of just one timer, or can also be used to read the S-bit of all four timers, using just a
single function call in both cases. Similarly, the other two subroutines also support multiple timer
instances passed in the argument.
A sample program that uses the HPS timer driver is shown in Figure 8. Notice how all four HPS
timers are configured to have a 1 second timeout in the same function call. The program will count
from 0-15 on all four HEX displays at the same rate of 1 second. It is important to remember that
the configuration values in the struct are implemented at a level of abstraction above the hardware,
with the aim of providing a better hardware interface to the C programmer. How these values are
then used in the assembly driver is governed by the hardware documentation (De1-SoC computer
manual). Recreating this sample program will prove to be a useful exercise before attempting the
application in the next section.
Creating an application: Stopwatch!
Create a simple stopwatch using the HPS timers, pushbuttons, and HEX displays. The stopwatch
should be able to count in increments of 10 milliseconds. Use a single HPS timer to count time.
Display milliseconds on HEX1-0, seconds on HEX3-2, and minutes on HEX5-4.
PB0, PB1, and PB2 will be used to start, stop and reset the stopwatch respectively. Use another
HPS timer set at a faster timeout value (5 milliseconds or less) to poll the pushbutton edgecapture
register.
8
Figure 8: Sample program that uses the HPS timer driver
9
4 Interrupts
For this part, it is necessary to refer to section 3 (pp. 19-32) in the De1-SoC Computer Manual.
Additional information about the interrupt drivers that are provided can be found in ’Using the ARM
Generic Interrupt Controller’ which is available on myCourses.
Brief introduction
Interrupts are hardware or software signals that are sent to the processor to indicate that an event
has occurred that needs immediate attention. When the processor receives an interrupt, it pauses
the current code execution, handles the interrupt by executing code defined in an Interrupt Service
Routine (ISR), and then resumes normal execution.
Apart from ensuring that high priority events are given immediate attention, interrupts also
help the processor to utilize resources more efficiently. Consider the polling application from the
previous section, where the processor periodically checked the pushbuttons for a keypress event.
Asynchronous events such as this, if assigned an interrupt, can free the processors time and use it
only when required.
Using the interrupt drivers
Download the following files from myCourses:
• int setup.c
• int setup.h
• ISRs.s
• ISRs.h
• address map arm.h
Within the GXX Lab3/drivers/ directory, place C files in the src, header files in the inc, and assembly files in the asm directories. Only the ISRs.s and ISRs.h files will need to be modified in
applications. Do not modify the other files
Before attempting this section, get familiarized with the relevant documentation sections provided in the introduction. To demonstrate how to use the drivers, a simple interrupt based application using HPS TIM0 is shown.
Note: Ensure that in the memory settings menu in the project settings, the Linker Section
Presets has been changed from ‘Basic’ to ’Exceptions’!
To begin, the code for the main.c file is shown in Figure 9. The int setup() function is the only
thing needed to configure the interrupt controller and enable the desired interrupt IDs. It takes two
arguments: an integer whose value denotes the number of interrupt IDs to enable, and an integer
array containing these IDs. In this example, the only interrupt ID enabled is 199, corresponding to
HPS TIM0.
After enabling interrupts for the desired IDs, the hardware devices themselves have to be programmed to generate interrupts. This is done in the code above via the HPS timer driver. Instructions for enabling interrupts from the different hardware devices can be found in the documentation.
10
Figure 9: Interrupts example: The main.c file
Now that HPS TIM0 is able to send interrupts, ISR code is needed to handle the interrupt events.
Notice how in the while loop of the main program, the value of hps tim0 int flag is checked to see if
an interrupt has occurred. The ISR code is responsible for writing to this flag, and also for clearing
the interrupt status in HPS TIM0.
When interrupts from a device are enabled and an interrupt is received, the processor halts code
execution and branches to the appropriate subroutine in the ISRs.s file. This is where the ISR code
should be written. Figure 10 shows the ISR code for HPS TIM0. In the ISR, the interrupt status of
the timer is cleared, and the interrupt flag is asserted.
Finally, in order for the main program to use the interrupt flag, it is declared in the ISRs.h file as
shown in Figure 11.
IMPORTANT: When ISR code is being executed, the processor has halted normal execution.
Lengthy ISR code will cause the application to freeze. ISR code should be as lightweight as
possible!
Interrupt based stopwatch!
Modify the stopwatch application from the previous section to use interrupts. In particular, enable
interrupts for the HPR timer used to count time for the stopwatch. Also enable interrupts for the
pushbuttons, and determine which key was pressed when a pushbutton interrupt is received. There
is no need for the second HPS timer that was used to poll the pushbuttons in the previous section.
11
Figure 10: Interrupts example: The ISR assembly code
12
Figure 11: Interrupts example: Flag declaration in ISRs.h
13
5 Grading
The TA will ask to see the following deliverables during the demo (the corresponding portion of your
grade for each is indicated in brackets):
• Slider switches and LEDs program (10%)
• Entire basic I/O program (15%)
• Polling based stopwatch (30%)
• Interrupt based stopwatch (25%)
Full marks are awarded for a deliverable only if the program functions correctly and the TA’s questions are answered satisfactorily.
A portion of the grade is reserved for answering questions about the code, which is awarded individually to group members. All members of your group should be able to answer any questions
the TA has about any part of the deliverables, whether or not you wrote the particular part of the
code the TA asks about. Full marks are awarded for a deliverable only if the program functions
correctly and the TA’s questions are answered satisfactorily.
Finally, the remaining 20% of the grade for this Lab will go towards a report. Write up a short
(3-4) page report that gives a brief description of each part completed, the approach taken, and the
challenges faced, if any. Please don’t include the entire code in the body of the report. Save the
space for elaborating on possible improvements you made or could have made to the program.
Your final submission should be a single compressed folder that contains your report and all the
code files, correctly organized (.c, .h and .s).
This Lab will run for five weeks, from February 19th to March 23rd, along with Lab 4. There
will be no lab sessions during reading week, March 5th to March 9th. You should demo your code
within the other active weeks within your assigned lab period. The report for Lab 3 is due by
11:59 pm, March 30th.
In this lab will use the high level I/O capabilities of the DE1-SoC computer. In particular, the tasks
will:
• Use the VGA controller to display pixels and characters.
• Use the PS/2 port to accept input from a keyboard
• Use the audio controller to play generated tones
1
1 VGA
For this part, it is necessary to refer to section 4.2 (pp 40-43) of the De1-SoC Computer Manual.
Brief overview of the De1-SoC computer VGA interface
The VGA controller hardware has already been introduced in the ECSE 222 labs. The De1-SoC
computer has a built in VGA controller, and the data displayed to the screen is acquired from two
sections in the FPGA on-chip memory – the pixel buffer and the character buffer – which are described
in sufficient detail in section 4.2.1 and 4.2.3 of the De1-SoC Computer Manual. For this lab, it is not
required to make use of the double buffering feature described in the manual.
VGA driver
Create two files VGA.s and VGA.h and place them in the correct folders. The code for the header
file is shown in Figure 1.
Figure 1: Code for the VGA.h file
The subroutines VGA clear charbuff ASM and VGA clear pixelbuff ASM should clear (set to 0) all
the valid memory locations in the character buffer and pixel buffer respectively.
VGA write char ASM should write the ASCII code passed in the third argument to the screen at
the (x,y) coordinates given in the first two arguments. Essentially, the subroutine will store the value
of the third argument at the address calculated with the first two arguments The subroutine should
check that the coordinates supplied are valid (i.e. x = [0,79] and y = [0,59]).
VGA write byte ASM should write the hexadecimal representation of the value passed in the third
argument to the screen. This means that this subroutine will print two characters to the screen! (For
example, passing a value of 0xFF in byte should result in the characters ’FF’ being displayed on the
screen starting at the x,y coordinates passed in the first two arguments) Again, check that the x and
y coordinates are valid, taking into account that two characters will be displayed.
Both the above subroutines should only access the character buffer memory.
Finally, the VGA draw point ASM subroutine will draw a point on the screen with the colour as
indicated in the third argument, by accessing only the pixel buffer memory. This subroutine is very
similar to the VGA write char ASM subroutine
2
NOTE: Use suffixes ‘B’ and ‘H’ with the assembly memory access instructions in order to
read/modify bytes/half-words
Simple VGA application
Build a C based application to test the functionality of the VGA driver. Write three functions as
shown in Figure 2
Figure 2: C functions used to test the VGA driver
Use the pushbuttons and slider switches as follows:
• PB0 is pressed: if any of the slider switches is on, call the test byte() function, otherwise, call
the test char() function.
• PB1 is pressed: call the test pixel() function.
• PB3 is pressed: clear the character buffer.
• PB4 is pressed: clear the pixel buffer.
3
2 Keyboard
For this part, it is necessary to refer to section 4.5 (pp 45-46) in the De1-SoC Computer Manual.
Brief overview of the PS/2 Keyboard Protocol
For the purpose of this lab, a very high level description of the PS/2 keyboard protocol is given. A
more detailed description can be found at this link.
The PS/2 bus provides data about keystroke events by sending hexadecimal numbers called scan
codes, which for this lab will vary from 1-3 bytes in length. When a key on the PS/2 keyboard is
pressed, a unique scan code called the make code is sent, and when the key is released, another
scan code called the break code is sent. The scan code set used in this lab can be found here.
Two other important parameters involved are the typematic delay and the typematic rate. When
a key is pressed, the corresponding make code is sent, and if the key is held down, the same make
code is repeatedly sent at a constant rate after an initial delay. The make code will stop being
sent only if the key is released or another key is pressed. The initial delay between the first and
second make code is called the typematic delay, and the rate at which the make code is sent after
this is called the typematic rate. The typematic delay can range from 0.25 seconds to 1.00 second
and the typematic rate can range from 2.0 cps (characters per second) to 30.0 cps, with default
values of 500 ms and 10.9 cps respectively.
(a) Key ’a’ is pressed and released
(b) Key “a” is pressed, held down, and then released
Figure 3: Example of data received on the PS/2 bus
4
PS/2 keyboard driver
Create two files ps2 keyboard.s and ps2 keyboard.h and place them in the correct folders.
For this lab, simply implement a subroutine with the following specifications:
• Name: read PS2 data ASM
• Argument: A char pointer variable data, in which the data that is read will be stored
• Return type: Integer that denotes whether the data read is valid or not
• Description: The subroutine will check the RVALID bit in the PS/2 Data register. If it is
valid, then the data from the same register should be stored at the address in the char pointer
argument, and the subroutine should return 1 to denote valid data. If the RVALID bit is not
set, then the subroutine should simply return 0.
Simple keyboard application
Create a simple application that uses the PS/2 keyboard and VGA monitor. The application should
read raw data from the keyboard and display it to the screen if it is valid. Only the VGA write byte ASM
subroutine is needed from the VGA driver, and the input byte is simply the data read from the keyboard.
Note: In the program, keep track of the x,y coordinates where the byte is being written.
For example, write the first byte at (0,0) and the second byte at (3,0) and so on until the first
line on the screen is full, and then start writing bytes at (0,1), (3,1), (5,1) etc. A gap of 3 x
co-ordinates is given since each byte will display two characters, and one more for a space
between each byte.
3 Audio
For this part, it is necessary to refer to section 4.1 (pp 39-40) of the De1-SoC Computer Manual
Write a driver for the audio port following the same procedure introduced so far. The driver
should only have one subroutine. The subroutine should take one integer argument and write it to
both the left and the write FIFO only if there is space in both the FIFOs (Hint: Use the value of
WSLC and WSRC in the subroutine). The subroutine should return an integer value of 1 if the data
was written to the FIFOs, and return 0 otherwise.
Use the driver in an application that plays a 100 Hz square wave on the audio out port. The
frequency can be achieved by knowing the sampling rate of of the audio DAC. For example, if the
sampling rate is 100 samples per second and a 2 Hz square wave is to be played, that means there
are two complete cycles of the wave contained in 100 samples, so for 25 samples a ‘1’ should be
written to the FIFOs, and for 25 samples a ‘0’ should be written to the FIFOs.
For this lab, find the sampling rate from the manual and calculate the number of samples for
each half cycle of the square wave. Finally, write 0x00FFFFFF and 0x00000000 to the FIFO instead
of ‘1’ and ‘0’.
5
4 Grading
The TA will ask to see the following deliverables during the demo (the corresponding portion of the
grade for each is indicated in brackets):
• VGA (30%)
• P/2 Keyboard (20%)
• Audio (20%)
Full marks are awarded for a deliverable only if the program functions correctly and the TA’s questions are answered satisfactorily.
A portion of the grade is reserved for answering questions about the code, which is awarded individually to group members. All members of your group should be able to answer any questions
the TA has about any part of the deliverables, whether or not you wrote the particular part of the
code the TA asks about. Full marks are awarded for a deliverable only if the program functions
correctly and the TA’s questions are answered satisfactorily.
Finally, the remaining 20% of the grade for this Lab will go towards a report. Write up a short
(2-3) page report that gives a brief description of each part completed, the approach taken, and the
challenges faced, if any. Please don’t include the entire code in the body of the report. Save the
space for elaborating on possible improvements you made or could have made to the program.
Your final submission should be a single compressed folder that contains your report and all the
code files, correctly organized (.c, .h and .s).
This Lab will run for two weeks, from March 12th to March 23rd. Demos for Lab 4 will begin
in the week of March 19th to March 23rd, and demos will also be accepted in the week of March
26th to March 30th, within your assigned lab section. The report for Lab 4 is due by 11:59 pm,
March 30th.
In this lab, you will combine the low-level ARM programming techniques you have acquired in the course by
implementing a musical synthesizer (or “synth”).
1 Make Waves
We are providing you with drivers for writing to the audio codec, as well as a “.s” file with a wavetable
containing one period of a 1 Hz sine wave at a sampling frequency of 48000 Hz.
To play a note with frequency f using the wavetable, generate a signal using the following equations for
every sampling instant t:
index = (f ∗ t) mod 48000,
signal[t] = amplitude ∗ table[index].
If more than one note is played at the same time, compute the sample for each note and add them
together. If the index is not an integer, you can calculate table[index] by linear interpolation using the two
nearest samples. For example, table[10.73] := (1-0.73)*table[10] + 0.73*table[11].
You should write a function which takes as input f and t and returns signal[t]. Use a timer to feed the
generated samples to the audio codec periodically.
2 Control Waves
It should be possible for the user to play the synth using a PS/2 keyboard. Table 1 shows the notes your
synth should play, the key of the PS/2 keyboard each note should map to, and the frequency of the note in
Hz. You should also implement a volume control with the keyboard so that the user can turn the volume
(amplitude) up or down. This is the minimum functionality you must implement; you can also implement
other notes and features, such as different voices.
3 Display Waves
In addition to playing the waveform, you should display the waveform on the lab computer monitor, as in
Figure 1.
1
Table 1: Note Mapping
Note Key Frequency
C A 130.813 Hz
D S 146.832 Hz
E D 164.814 Hz
F F 174.614 Hz
G J 195.998 Hz
A K 220.000 Hz
B L 246.942 Hz
C ; 261.626 Hz
Figure 1: Waveform display for a single note
4 Presentation
Prepare a 10-minute presentation for the TAs and course instructor describing your synth, its software architecture, the problems you encountered during the design process, and the solutions you developed for those
problems.
5 Grading
You will be evaluated according to the following criteria:
• Audio sounds correct (12.5%)
• Note control works (12.5%)
• Volume control works (12.5%)
• Display works (12.5%)
• Does the code follow good software development principles (style, efficiency, clear commenting, appropriate use of source files, code reuse, etc.)? (25%)
• Presentation quality (25%)
The final date for demoing and submitting the final report is on the last day of classes, 16th April 2018.
A demo timetable and guidelines for submitting the report will be posted on MyCourses.
Reviews
There are no reviews yet.