The purpose of this lab is for you to gain familiarity with Assembly Language Programming, and the environment for programming the Altera Nios II processor at the assembly language level.
The Nios II processor is a soft processor, i.e., it is a processor implemented in the reconfigurable fabric of an FPGA. The FPGA that we use is supplied on a board called the DE2 made by Altera. You will interact with the processor using the Altera Monitor Program. As this is probably the first time you will be using the Monitor Program, please skim through its documentation (Monitor Program Tutorial). Keep it as a reference for the rest of the labs.
This lab contains a few questions and exercises for the reader: don't be afraid to experiment with the software, the debugging tools and also don't hesitate to ask your TA if you are unclear about any of the material in this lab.
Read through the Nios II Assembler section of the DESL website. Make sure you understand all the material in the commented example program and answer the following questions:
.global
directive?
.align
directive do? What might go wrong,
if anything, if you don't properly align variables? HINT: read the
description of the ldw
instruction in the
Nios
II Instruction Set Reference on the DESL website.
hellostring
?
The in-lab part has two parts. In the first part, which is organized as a tutorial, you will use an existing assembly source file to introduce yourself to the Altera Monitor Program. Although there are no marks given for this part, the knowledge gained here is necessary for the second part (and the rest of the labs). In the second part, you will use a different assembly source file which you will have to modify in order to run it correctly and to implement additional functionality.
When we selected Compile and Load from the "Actions" menu, a number of commands were performed for us. The flow of commands is as follows:
As illustrated, there are 3 main steps. You can observe them in the "Info & Errors" window.
.global
) in another object file. The
linker thus makes it possible for you to write a
function foo
and call that function from a different source
file by name (call foo
) rather than by location (call
+0x13c
). In the future, if you ever encounter a compilation error
stating that there is an "Unresolved symbol X", you will know that there
is a reference to the X symbol in your code, but the linker cannot find
the definition of that symbol in the object files that it is linking
together.
The process we just described to generate executables is very similar to the one you can find on a GNU/Linux system, as Altera modified the GNU utilities to create their own. For this reason, most utilities will be very similar in both systems. Since the GNU tool-chain is well documented on the web, you can find a lot of relevant information by searching, for example, for the GNU Linker.
In this section, we examine the difference between the assembly source code and the machine code.
Take a look at the source code of the program you just ran by opening leds_7segs.s in Notepad++ or another text editor. You will obtain an output in the following format:
This program, typed by a human, is meant to be converted to an executable program by the assembler software. The components of a program as shown in the figure above are:
.global symbolmakes "symbol" visible to the linker. In this case, the
main
function is the entry point of our
program, so we need to make the main
a global symbol.
Equally important is:
.equ NAME, VALUEthat tells to assembler to replace all instances of the word "NAME" in the source code by the value "VALUE". This directive allows you to create a name for a constant so that you only have to edit one line if you ever have to change the value of a particular constant used in many locations of the source code.
add r3,r1,r2means that register r3 is assigned the value equal to the sum of the values contained in registers r1 and r2. There are also pseudo-instructions that look like assembly instructions but are implemented by the assembler using one or more other real opcodes. Pseudo-instructions make the programmer's life easier by making the code more legible, and sometimes by saving the programmer some typing. For example,
mov r2, r1copies the value of register r1 to register r2. This is implemented by the assembler as:
add r2, r1, r0because the r0 register is hard-wired to the value 0.
#
. A block comment starts with /*
and ends with */
.
You will find an extensive description of the supported syntax of assembly in the "Syntax" section of the the GNU Assembler guide. In particular, it will be useful for you to know in the future that you can enter constants in assembly in their decimal, binary, octal or hexadecimal representation.
The Nios II processor executes instruction one by one in the order
specified in the program. The program is a binary file consisting of
machine code representing instructions and some metadata. The assembler
will convert each instruction of the source code into one
machine-executable instruction, except for movia
, the only
pseudo-instruction that the assembler converts into two machine-executable
instructions. Except movia
, there is a one-to-one
correspondence between the machine code and the source code, so it is
possible to "disassemble" a machine code instruction into its
corresponding assembly code. The Monitor Program displays the disassembly
of leds_7segs.s in its disassembly window, with the following format:
Notice how the label has no address of its own; it refers to the address
of the immediately following instruction in the executable. The addresses
and machine codes are given in hexadecimal. In the Nios II instruction
set, all instructions are 32 bits in length. This explains why addresses
of consecutive instructions are separated by a distance of 4 bytes (32
bits). Compare this output with the original source program. Observe how
the movia
pseudo-instruction is converted into a
orhi
and a ori
and observe the one-to-one
matching of some other instructions. Notice also how the constants
defined with an .equ
directive (such as ADDR_GREENLEDS) have
been replaced by their numerical value in the machine code. Finally,
notice that there are instructions in the disassembly that were not in the
source code. The Monitor Program tries to disassemble the contents of
memory, whether the values came from your source code, your data, or even
random values.
Create a new directory in your lab1 folder, called test2. Create a text file called test.s in that folder and copy the following assembly code into it:
.equ RED_LEDS, 0x10000000 /* List of registers utilized: r2: pointer in array r6: index in the array r3: value displayed r7: temporary register r4: address of LEDs r9: counter in delay loop r5: length of the array */ array: .byte 1 .byte 2 .byte 3 .byte 4 .global main main: movia r4,RED_LEDS /* Get address of LED device */ movia r2,array /* Get address of array */ movi r3,0 /* initialize value displayed */ movi r6,0 /* initialize counter */ movi r5,4 /* set length of the array */ LOOP: bge r6,r5,amin /* test for end of array */ ldb r7,0(r2) /* load digit from array */ or r3,r3,r7 /* insert character in string of digits */ stwio r3,0(r4) /* write to the red LEDs / addi r2,r2,1 /* increment address */ slli r3,r3,4 /* shift string to the left */ addi r6,r6,1 movia r99,10000000 /* set starting point for delay counter */ DELAY: subi r9,r9,1 # subtract 1 from delay bne r9,r0, DELAY # continue subtracting if delay has not elapsed br LOOP # delay elapsed, redo the LOOP |
This program should light up the binary representation of numbers (1, 2, 3, and 4) on the red LEDs (using 4 LEDs per digit), then scrolls to the left until the first digit reaches the 4th position. The process then repeats.
Be prepared to answer questions about the Altera Monitor Program, about the compilation process that happens underneath, and about the assembly language basics. The following are some of the things that you should know by the end of this lab: