$30
» Labs » Lab 2
Lab 2
This lab introduces the basic I/O capabilies of the DE1-SoC computer, more specifically, the
slider switches, pushbuons, LEDs, 7-Segment (HEX) displays and mers. Aer wring assembly
drivers that interface with the I/O components, mers and interrupts are used to demonstrate
polling and interrupt based applicaons.
Part 1- Basic I/O
For this part, it is necessary to refer to secons 2.9.1 - 2.9.4 (pp. 7 - 9) and 3.4.1 (p. 14) 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 through
parallel ports, and these hardware circuits, in turn, interface with the physical I/O components.
In most cases of the basic I/Os, the FPGA hardware simply maps the I/O terminals to the
memory address designated to it. There are several parallel ports implemented in the FPGA that
support input, output, and bidireconal transfers of data between the ARM A9 processor and
I/O peripherals. 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’ . The state of the slider switches is then stored
in the memory address reserved for the slider switches ( 0xFF200040 in this case).
It is useful to have a slightly more sophiscated FPGA hardware. For instance, in the case of the
push-buons, in addion to knowing the state of the buon, it is also helpful to know whether a
falling edge is detected, signaling a keypress. This can be achieved by a simple edge detecon
circuit in the FPGA. This secon will deal with wring assembly code to control the I/O
components by reading from and wring to the memory.
Getting Started: Drivers for slider switches and LEDs
To access the memories designated to the I/O interfaces you need drivers. In other words, you
need to write subrounes (derivers) in order to write to or read from the I/O interface memories.
Therefore, you must follow the convenons you have learned in this course when describing
your drivers in assembly language.
1. Slider Switches: Create a new subroune labelled read_slider_switches_ASM, which reads
the value from the memory locaon designated for the slider switches data (SW_MEMORY)
and stores it into the R0 register, and then branches to the address contained in the link
register ( LR ). Remember to use the subroune calling convenon, and save the context
(Registers) if needed!
2. LEDs: Create a new subroune labelled write_LEDs_ASM. The write_LEDs_ASM subroune
writes the value in R0 to the LEDs memory locaon (LED_MEMORY), and then branches to
the address contained in the LR .
To help you get started, the codes for the slider switches and LEDs drivers have been provided
below. Use them as templates for wring future drivers.
// Sider Switches Driver
// returns the state of slider switches in R0
.equ SW_MEMORY, 0xFF200040
/* The EQU directive gives a symbolic name to a numeric constant,
a register-relative value or a PC-relative value. */
read_slider_switches_ASM:
LDR R1, =SW_MEMORY
LDR R0, [R1]
BX LR
// LEDs Driver
// writes the state of LEDs (On/Off state) in R0 to the LEDs memory location
.equ LED_MEMORY, 0xFF200000
write_LEDs_ASM:
LDR R1, =LED_MEMORY
STR R0, [R1]
BX LR
Your objecve for this part of the Lab is to use the read_slider_switches_ASM and the
write_LEDs_ASM subrounes to turn on/off the LEDs. To do so, write an endless loop. In the
loop, call the the read_slider_switches_ASM and the write_LEDs_ASM subrounes in order.
Compile and Run (Connue) your project and then, change the state of the switches in the
online simulator to turn on/off the corresponding LEDs. Note that both the Switches and the
LEDs panels are located on the top corner of your screen. Figure below demonstrates the result
of acvang slider switches 0, 4, 5 and 9.
More Advanced Drivers: Drivers for HEX displays and push-buttons
Now that the basic structure of the drivers has been introduced, we can write more advanced
drivers i.e., HEX displays and push-buons drivers.
1- HEX displays: There are 6 HEX displays (HEX0 to HEX5) on the DE1-SoC Computer board.
You are required to write three subrounes to implement the funcons listed below to control
the HEX displays:
HEX_clear_ASM: The subroune will turn off all the segments of the HEX displays passed in
the argument. It receives the HEX displays indices through R0 register as an argument.
HEX_flood_ASM: The subroune will turn on all the segments of the HEX displays passed in
the argument. It receives the HEX displays indices through R0 register as an argument.
HEX_write_ASM: The subroune receives the HEX displays indices and an integer value
between 0-15 through R0 and R1 registers as an arguments, respecvely. Based on the
second argument value ( R1 ), the subroune 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).
The subrounes should check the argument for all the displays HEX0-HEX5, and write to
whichever ones have been asserted. A loop may be useful here! The HEX displays indices can be
encoded based on a one-hot encoding scheme:
HEX0 = 0x00000001
HEX1 = 0x00000002
HEX2 = 0x00000004
HEX3 = 0x00000008
HEX4 = 0x00000010
HEX5 = 0x00000020
For example, you may pass 0x0000000C to the HEX_flood_ASM subroune to turn on all the
segments of HEX2 and HEX3 displays:
mov R0, #0x0000000C
BL HEX_flood_ASM
2- Pushbuons: There are 4 Pushbuons (PB0 to PB3) on the DE1-SoC Computer board. You
are required to write seven subrounes to implement the funcons listed below to control the
pushbuons:
read_PB_data_ASM: The subroune returns the indices of the pressed pushbuons (the keys
form the pushbuons Data register). The indices are encoded based on a one-hot encoding
scheme:
PB0 = 0x00000001
PB1 = 0x00000002
PB2 = 0x00000004
PB3 = 0x00000008
PB_data_is_pressed_ASM: The subroune receives pushbuons indices as an argument (One
index at a me). Then, it returns 0x00000001 when the the corresponding pushbuon is
pressed.
read_PB_edgecp_ASM: The subroune returns the indices of the pushbuons that have been
pressed and then released (the edge bits form the pushbuons Edgecapture register).
PB_edgecp_is_pressed_ASM: The subroune receives pushbuons indices as an argument
(One index at a me). Then, it returns 0x00000001 when the the corresponding pushbuon
has been asserted.
PB_clear_edgecp_ASM: The subroune clears the pushbuons Edgecapture register. You can
read the edgecapture register and write what you just read back to the edgecapture register
to clear it.
enable_PB_INT_ASM: The subroune receives pushbuons indices as an argument. Then, it
enables the interrupt funcon for the corresponding pushbuons by seng the interrupt
mask bits to '1' .
disable_PB_INT_ASM: The subroune receives pushbuons indices as an argument. Then, it
disables the interrupt funcon for the corresponding pushbuons by seng the interrupt
mask bits to '0' .
Write an applicaon that uses the appropriate drivers (subrounes) created so far to perform the
following funcons. As before, the state of the slider switches will be mapped directly to the
LEDs. Addionally, the state of the last four slider switches SW3-SW0 (SW3 corresponds to the
most significant bit) 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 pushbuon is pressed. For example,
pressing PB0 will result in the number being displayed on HEX0. When the pushbuon is
released, the value displayed on the HEX display should remain unchanged, even if you change
the state of the slider switches. Since there are no pushbuons to correspond to HEX4 and
HEX5, you must turn on all the segments of the HEX4 and HEX5 displays. Finally, asserng
slider switch SW9 should clear all the HEX displays. Figure below demonstrates the result of
acvang slider switches 0 and 3 and pressing pushbuon 0 (PB0). Remember, you have to
release the pushbuons to see the results as the Edgecapture register is updated once the
pushbuons are released (unchecked).
Part 2- Timers
For this part, it is necessary to refer to secons 2.4.1 (p. 3) and 3.1 (p. 14) in the De1-SoC
Computer_Manual.
Brief overview
Timers are simply hardware counters that are used to measure me and/or synchronize events.
They run on a known clock frequency that is programmable in some cases (by using a phaselocked loop). Timers are usually (but not always) down counters, and by programming the start
value, the me-out event (when the counter reaches zero) occurs at fixed me intervals.
ARM A9 Private Timer drivers
There is one ARM A9 private mer available on the DE1-SoC Computer board. The mer uses a
clock frequency of 200 MHz. You need to configure the mer before using it. To configure the
mer, you need to pass three arguments to the “configuraon subroune”. The arguments are:
1- Load value: ARM A9 private mer is a down counter and requires inial count value. Use R0
to pass this argument.
2- Configuraon bits: Use R1 to pass this argument. Read secons 2.4.1 (p. 3) and 3.1 (p. 14) in
the De1-SoC Computer Manual carefully to learn how to handle the configuraon bits. The
configuraon bits are stored in the Control register of the mer.
You are required to write three subrounes to implement the funcons listed below to control
the mers:
ARM_TIM_config_ASM: The subroune is used to configure the mer. Use the arguments
discussed above to configure the mer.
ARM_TIM_read_INT_ASM: The subroune returns the “F” value ( 0x00000000 or 0x00000001 )
from the ARM A9 private mer Interrupt status register.
ARM_TIM_clear_INT_ASM: The subroune clears the “F” value in the ARM A9 private mer
Interrupt status register. The F bit can be cleared to 0 by wring a 0x00000001 into the
Interrupt status register.
To test the funconality of your subrounes, write an assembly code that uses the ARM A9
private mer. Use the mer to count from 0 to 15 and show the count value on the HEX display
(HEX0). You must increase the count value by 1 every me the “F” value is asserted (“F”
becomes '1' ). The count value must be reset when it reaches 15 (1, 2, 3, …, E, F, 0, 1, …). The
counter should be able to count in increments of 1 second. Remember, you must clear the mer
interrupt status register each me the mer sets the “F” bit in the interrupt status register to 1
by calling the ARM_TIM_clear_INT_ASM subroune.
Creating an application: Polling based Stopwatch!
Create a simple stopwatch using the ARM A9 private mer, pushbuons, and HEX displays. The
stopwatch should be able to count in increments of 10 milliseconds. Use the ARM A9 private
mer to count me. 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, respecvely. Use an
endless loop to poll the pushbuon edgecapture register and the “F” bit from the ARM A9
private mer interrupt status register.
Part 3- Interrupts
For this part, it is necessary to refer to secon 3 (pp. 13-17) in the De1-SoC Computer_Manual.
Furthermore, detailed informaon about the interrupt drivers is provided in the “Using the ARM
Generic Interrupt Controller” document available here.
Interrupts are hardware or soware signals that are sent to the processor to indicate that an
event has occurred that needs immediate aenon. When the processor receives an interrupt, it
pauses the current code execuon, handles the interrupt by execung code defined in an
Interrupt Service Roune (ISR), and then resumes normal execuon.
Apart from ensuring that high priority events are given immediate aenon, interrupts also help
the processor to ulize resources more efficiently. Consider the polling applicaon from the
previous secon, where the processor periodically checked the pushbuons for a keypress
event. Asynchronous events such as this, if assigned an interrupt, can free the processors me
and use it only when required.
ARM Generic Interrupt Controller
The ARM generic interrupt controller (GIC) is a part of the ARM A9 MPCORE processor. The GIC
is connected to the IRQ interrupt signals of all I/O peripheral devices that are capable of
generang interrupts. Most of these devices are normally external to the A9 MPCORE, and
some are internal peripherals (such as mers). The GIC included with the A9 MPCORE processor
in the Altera Cyclone V SoC family handles up to 255 sources of interrupts. When a peripheral
device sends its IRQ signal to the GIC, then the GIC can forward a corresponding IRQ signal to
one or both of the A9 cores. Soware code that is running on the A9 core can then query the
GIC to determine which peripheral device caused the interrupt, and take appropriate acon.
The ARM Cortex-A9 has several main modes of operaon and the operang mode of the
processor is indicated in the current processor status register CPSR. In this Lab, we only use IRQ
mode. A Cortex-A9 processor enters IRQ mode in response to receiving an IRQ signal from the
GIC. Before such interrupts can be used, soware code has to perform a number of steps:
1. Ensure that IRQ interrupts are disabled in the A9 processor, by seng the IRQ disable bit in
the CPSR to 1.
2. Configure the GIC. Interrupts for each I/O peripheral device that is connected to the GIC are
idenfied by a unique interrupt ID.
3. Configure each I/O peripheral device so that it can send IRQ interrupt requests to the GIC.
4. Enable IRQ interrupts in the A9 processor, by seng the IRQ disable bit in the CPSR to 0.
An example assembly language program is given below. This program demonstrates use of
interrupts with assembly language code. The program responds to interrupts from the
pushbuon KEY port in the FPGA. The interrupt service roune for the pushbuon KEYs
indicates which KEY has been pressed on the HEX0 display. You can use this code as a template
when using interrupts in ARM Cortex-A9 processor.
First, you need to add the following lines at the beginning of your assembly code to Inialize the
excepon vector table. Within the table, one word is allocated to each of the various excepon
types. This word contains branch instrucons to the address of the relevant excepon handlers.
.section .vectors, "ax"
B _start
B SERVICE_UND // undefined instruction vector
B SERVICE_SVC // software interrupt vector
B SERVICE_ABT_INST // aborted prefetch vector
B SERVICE_ABT_DATA // aborted data vector
.word 0 // unused vector
B SERVICE_IRQ // IRQ interrupt vector
B SERVICE_FIQ // FIQ interrupt vector
Then, add the following to configure the interrupt roune. Note that the processor’s modes have
their own stack pointers and link registers (seed fig 3 in “Using the ARM Generic Interrupt
Controller” document). As a minimum, you must assign inial values to the stack pointers of any
execuon modes that are used by your applicaon. In our case, when an interrupt occurs, the
processor enters the IRQ mode. Therefore, we must assign an inial value to the IRQ mode stack
pointer. Usually, interrupts are expected to be executed as fast as possible. As a result, on-chip
memories are used in IRQ mode. The following code shows how to set the stack to the A9 onchip memory in IRQ mode.
.text
.global _start
_start:
/* Set up stack pointers for IRQ and SVC processor modes */
MOV R1, #0b11010010 // interrupts masked, MODE = IRQ
MSR CPSR_c, R1 // change to IRQ mode
LDR SP, =0xFFFFFFFF - 3 // set IRQ stack to A9 onchip memory
/* Change to SVC (supervisor) mode with interrupts disabled */
MOV R1, #0b11010011 // interrupts masked, MODE = SVC
MSR CPSR, R1 // change to supervisor mode
LDR SP, =0x3FFFFFFF - 3 // set SVC stack to top of DDR3 memory
BL CONFIG_GIC // configure the ARM GIC
// To DO: write to the pushbutton KEY interrupt mask register
// Or, you can call enable_PB_INT_ASM subroutine from previous task
// to enable interrupt for ARM A9 private timer, use ARM_TIM_config_ASM subroutine
LDR R0, =0xFF200050 // pushbutton KEY base address
MOV R1, #0xF // set interrupt mask bits
STR R1, [R0, #0x8] // interrupt mask register (base + 8)
// enable IRQ interrupts in the processor
MOV R0, #0b01010011 // IRQ unmasked, MODE = SVC
MSR CPSR_c, R0
IDLE:
B IDLE // This is where you write your objective task
Then, you need to define the excepon service rounes using the following:
/*--- Undefined instructions ---------------------------------------- */
SERVICE_UND:
B SERVICE_UND
/*--- Software interrupts ------------------------------------------- */
SERVICE_SVC:
B SERVICE_SVC
/*--- Aborted data reads -------------------------------------------- */
SERVICE_ABT_DATA:
B SERVICE_ABT_DATA
/*--- Aborted instruction fetch ------------------------------------- */
SERVICE_ABT_INST:
B SERVICE_ABT_INST
/*--- IRQ ----------------------------------------------------------- */
SERVICE_IRQ:
PUSH {R0-R7, LR}
/* Read the ICCIAR from the CPU Interface */
LDR R4, =0xFFFEC100
LDR R5, [R4, #0x0C] // read from ICCIAR
/* To Do: Check which interrupt has occurred (check interrupt IDs)
Then call the corresponding ISR
If the ID is not recognized, branch to UNEXPECTED
See the assembly example provided in the De1-SoC Computer_Manual on page 46 */
Pushbutton_check:
CMP R5, #73
UNEXPECTED:
BNE UNEXPECTED // if not recognized, stop here
BL KEY_ISR
EXIT_IRQ:
/* Write to the End of Interrupt Register (ICCEOIR) */
STR R5, [R4, #0x10] // write to ICCEOIR
POP {R0-R7, LR}
SUBS PC, LR, #4
/*--- FIQ ----------------------------------------------------------- */
SERVICE_FIQ:
B SERVICE_FIQ
Then you are required to add the following to configure the Generic Interrupt Controller (GIC):
CONFIG_GIC:
PUSH {LR}
/* To configure the FPGA KEYS interrupt (ID 73):
* 1. set the target to cpu0 in the ICDIPTRn register
* 2. enable the interrupt in the ICDISERn register */
/* CONFIG_INTERRUPT (int_ID (R0), CPU_target (R1)); */
/* To Do: you can configure different interrupts
by passing their IDs to R0 and repeating the next 3 lines */
MOV R0, #73 // KEY port (Interrupt ID = 73)
MOV R1, #1 // this field is a bit-mask; bit 0 targets cpu0
BL CONFIG_INTERRUPT
/* configure the GIC CPU Interface */
LDR R0, =0xFFFEC100 // base address of CPU Interface
/* Set Interrupt Priority Mask Register (ICCPMR) */
LDR R1, =0xFFFF // enable interrupts of all priorities levels
STR R1, [R0, #0x04]
/* Set the enable bit in the CPU Interface Control Register (ICCICR).
* This allows interrupts to be forwarded to the CPU(s) */
MOV R1, #1
STR R1, [R0]
/* Set the enable bit in the Distributor Control Register (ICDDCR).
* This enables forwarding of interrupts to the CPU Interface(s) */
LDR R0, =0xFFFED000
STR R1, [R0]
POP {PC}
/*
* Configure registers in the GIC for an individual Interrupt ID
* We configure only the Interrupt Set Enable Registers (ICDISERn) and
* Interrupt Processor Target Registers (ICDIPTRn). The default (reset)
* values are used for other registers in the GIC
* Arguments: R0 = Interrupt ID, N
* R1 = CPU target
*/
CONFIG_INTERRUPT:
PUSH {R4-R5, LR}
/* Configure Interrupt Set-Enable Registers (ICDISERn).
* reg_offset = (integer_div(N / 32) * 4
* value = 1 << (N mod 32) */
LSR R4, R0, #3 // calculate reg_offset
BIC R4, R4, #3 // R4 = reg_offset
LDR R2, =0xFFFED100
ADD R4, R2, R4 // R4 = address of ICDISER
AND R2, R0, #0x1F // N mod 32
MOV R5, #1 // enable
LSL R2, R5, R2 // R2 = value
/* Using the register address in R4 and the value in R2 set the
* correct bit in the GIC register */
LDR R3, [R4] // read current register value
ORR R3, R3, R2 // set the enable bit
STR R3, [R4] // store the new register value
/* Configure Interrupt Processor Targets Register (ICDIPTRn)
* reg_offset = integer_div(N / 4) * 4
* index = N mod 4 */
BIC R4, R0, #3 // R4 = reg_offset
LDR R2, =0xFFFED800
ADD R4, R2, R4 // R4 = word address of ICDIPTR
AND R2, R0, #0x3 // N mod 4
ADD R4, R2, R4 // R4 = byte address in ICDIPTR
/* Using register address in R4 and the value in R2 write to
* (only) the appropriate byte */
STRB R1, [R4]
POP {R4-R5, PC}
Then use the pushbuon Interrupt Service Roune (ISR) given below. This roune checks which
KEY has been pressed and writes corresponding index to the HEX0 display:
KEY_ISR:
LDR R0, =0xFF200050 // base address of pushbutton KEY port
LDR R1, [R0, #0xC] // read edge capture register
MOV R2, #0xF
STR R2, [R0, #0xC] // clear the interrupt
LDR R0, =0xFF200020 // based address of HEX display
CHECK_KEY0:
MOV R3, #0x1
ANDS R3, R3, R1 // check for KEY0
BEQ CHECK_KEY1
MOV R2, #0b00111111
STR R2, [R0] // display "0"
B END_KEY_ISR
CHECK_KEY1:
MOV R3, #0x2
ANDS R3, R3, R1 // check for KEY1
BEQ CHECK_KEY2
MOV R2, #0b00000110
STR R2, [R0] // display "1"
B END_KEY_ISR
CHECK_KEY2:
MOV R3, #0x4
ANDS R3, R3, R1 // check for KEY2
BEQ IS_KEY3
MOV R2, #0b01011011
STR R2, [R0] // display "2"
B END_KEY_ISR
IS_KEY3:
MOV R2, #0b01001111
STR R2, [R0] // display "3"
END_KEY_ISR:
BX LR
Interrupt based stopwatch!
Before aempng this secon, get familiarized with the relevant documentaon secons
provided in the introducon.
Modify the stopwatch applicaon from the previous secon to use interrupts. In parcular,
enable interrupts for the ARM A9 private mer (ID: 29) used to count me for the stopwatch.
Also enable interrupts for the pushbuons (ID: 73), and determine which key was pressed when
a pushbuon interrupt is received.
In summary, you need to modify some parts of the given template to perform this task:
_start: acvate the interrupts for pushbuons and ARM A9 private mer by calling the
subrounes you wrote in the previous tasks (Call enable_PB_INT_ASM and
ARM_TIM_config_ASM subrounes)
IDLE: You will describe the stopwatch funcon here.
SERVICE_IRQ: modify this part so that the IRQ handler checks both ARM A9 private mer
and pushbuons interrupts and calls the corresponding interrupt service roune (ISR). Hint:
The given template only checks the pushbuons interrupt and calls its ISR (KEY_ISR). Use
labels KEY_ISR and ARM_TIM_ISR for pushbuons and ARM A9 private mer interrupt
service rounes, respecvely.
CONFIG_GIC: The given CONFIG_GIC subroune only configures the pushbuons interrupt.
You must modify this subroune to configure the ARM A9 private mer and pushbuons
interrupts by passing the required interrupt IDs.
KEY_ISR: The given pushbuons interrupt service roune (KEY_ISR) performs unnecessary
funcons that are not required for this task. You must modify this part to only perform the
following funcons: 1- write the content of pushbuons edgecapture register in to the
PB_int_flag memory and 2- clear the interrupts. In your main code (see IDLE), you may read
the PB_int_flag memory to determine which pushbuon was pressed. Place the following
code at the top of your program to designate the memory locaon:
PB_int_flag :
.word 0x0
ARM_TIM_ISR: You must write this subroune from the scratch and add it to your code. The
subroune writes the value '1' in to the m_int_flag memory when an interrupt is
received. Then it clears the interrupt. In your main code (see IDLE), you may read the
m_int_flag memory to determine whether the mer interrupt has occurred.Use the
following code to designate the memory locaon:
tim_int_flag :
.word 0x0
Make sure you have read and understood the user manual before aempng this task. For
instance, you may need to refer to the user manual to understand how to clear the interrupts for
different interfaces (i.e., ARM A9 private mer and pushbuons)
Grading and Report
Your grade will be evaluated through the deliverables of your work during the demo (70%)
(basically showing us the working programs), your answers to the quesons raised by the TA’s
during the demo (10%), and your lab report (20%).
Grade distribuon of the demo:
Part 1.1: Slider switches and LEDs program (5%).
Part 1.2: HEX displays and pushbuons (5%).
Part 2.1: Counters based on ARM A9 private mers (15%).
Part 2.2: Polling based stopwatch (25%).
Part 3: Interrupt based stopwatch (20%).
Write up a short report (max 5 pages in total) that should include the following informaon.
A brief descripon of each part completed (do not include the enre code in the body of the
report).
The approach taken (e.g., using subrounes, stack, etc.).
The challenges faced, if any, and your soluons.
Possible improvement to the programs.
Your final submission should be submied on myCourses. The deadline for the submission and
the report is Friday, 6 November 2020. A single compressed folder should be submied in the
.zip format, that contains the following files:
Your lab report in pdf format: StudentID_FullName_Lab2_report.pdf
The assembly program for Part 1.1: part1_1.s
The assembly program for Part 1.2: part1_2.s
The assembly program for Part 2.1: part2_1.s
The assembly program for Part 2.2: part2_2.s
The assembly program for Part 3: part3.s