Cpr E 211 Lab 6

Add Assembly Program

 

Lab6_add.asm

 

.export StartAsm    ; Tells the linker to export the StartAsm

                    ; label so other files can see it

       

.function "StartAsm", PPC_Start_Asm, PPC_End_Asm-PPC_Start_Asm

 

.text

 

PPC_Start_Asm:

 

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Assembly code

;

 

StartAsm:

; your code goes here

 

; Give r2 the address of the data segment defined below

 

lis  r2, DataSeg@h            ;r2 = upper halfword of address

ori  r2, r2, DataSeg@l        ;r2 = r2 | lower halfword of address

 

lwz r31,0(r2)           ; load the value from memory at address r2+0 into r31

lwz r30,4(r2)           ; load the value from memory at address r2+4 into r30

 

add r30, r30, r31       ; add r30+r31, and put the sum in r30

 

stw r30, 8(r2)          ; store the sum into memory at address r2+8

 

blr       ; Return back to the C code   

 

PPC_End_Asm:

 

DataSeg:    ;data values are words, so an offset from DataSeg is a multiple of 4

 .word $11 

 .word $22 

 .word 0

 

 

Things to Notice

A Few Details

Register-Register Operation

Read section 4.3, and refer to Table 4-1 Integer Arithmetic Instructions in your RCPU Reference Manual. One of the first instructions listed is “Add” with mnemonic add. See also Table 9-2 RTL Notation and Conventions (you may need to look up certain notations to understand the operation of assembly instructions). Notice that Figure 9-1 Instruction Description describes the format of the pages in Chapter 9.  Let’s consider an example.

Example:

add r30, r30, r31

This is a register-register operation. Look at the “Operand Syntax” in Table 4-1, and/or find the add instruction on the Quick Reference sheet. What are the operands in the example?
rD (destination register): D = 30
rA (source register A): A = 30
rB (source register B): B = 31

The operation of this instruction says: the sum (rA) + (rB) is placed into register rD. This is written as rD ß rA + rB, or r30 ß r30 + r31

Note on bit numbering in the RCPU Reference Manual: On the add page in Chapter 9 of the manual, notice the bit numbering associated with the fields in the machine instruction. Bit 0 is shown as leftmost. The bit-numbering convention in this manual is flipped. The leftmost bit is still the most-significant bit; we would number it as 31, but the manual numbers it as 0. 

 

Register-Memory, Memory-Register Operations

Read through Section 4.3 of the Notes. The addressing mode refers to how an instruction gets its operands.  For example, the add instruction above performs a register-register operation, meaning that the source and destination operands use registers. The data to be added are in registers, and the result is placed back in a register. This addressing mode is called register mode.

An instruction may also get its operand from the instruction word itself. That is, part of the instruction word is used to hold a constant, in addition to the opcode and any other fields. This addressing mode is called immediate mode. PowerPC assembly instructions that use immediate mode end in the letter “i”. For example, the “add immediate” addi instruction. Suppose we want to increment register r30.  Then the previous add instruction would be changed to the following:

addi r30, r30, 1

Look at the “Operand Syntax” in Table 4-1, and/or find the addi instruction on the Quick Reference sheet. What are the operands in this case?
rD (destination register): D = 30
rA (source register A): A = 30
SIMM (16-bit Signed integer IMMediate value):  SIMM = 1 (=  0x0001)

The source operands for addi are register r30 and immediate (constant) value 1. The destination is register r30. Thus the instruction takes the immediate value 1, adds it to the contents of register r30, and places the resulting sum back into register r30. So, register r30 is incremented. The constant is “immediately” available in the instruction; it comes with the instruction, and no other memory read is needed to get the constant.

Note that the maximum number of bits for the immediate value field is 16.

In PowerPC assembly, a single addressing mode is used as the basis for accessing any operand in memory, called indexed addressing. A load instruction that reads data from memory uses indexed addressing. A store instruction that writes data to memory also uses indexed addressing. Refer to the load and store instructions in the program:

lwz r31, 0(r2)     ; load the value from memory at address r2+0 into r31

lwz r30, 4(r2)     ; load the value from memory at address r2+4 into r30

stw r30, 8(r2)     ; store the value in r30 into memory at address r2+8

The memory operand, i.e., the operand on the right, specifies what is called an effective address. It is the address of the operand in memory, i.e., where the operand is located in memory. In indexed addressing,  the effective address is calculated as the sum of the contents of a register (called the base) and a constant contained in the instruction (called the displacement, or offset).

In the examples above, the base register is r2, and the displacements are 0, 4, 8, respectively. Thus, if r2 = 0x20000380, then the effective addresses are 0x20000380, 0x20000384, 0x20000388, respectively. So, the first load instruction reads a word from memory at address 0x20000380 and places it into register r31. The second load instruction reads a word from memory at address 0x20000384 and places it into register r30. The store instruction writes a word from register r30 to memory at address 0x20000388.

Consider the following general form of a memory operand:

constant (rN)

When the instruction involves a load/store from/to memory, then the memory address is calculated by adding the constant to the contents of the register rN, which is denoted (rN): constant + (rN). If the constant is omitted (or 0), then the register contents is the address. If the register specification is omitted, then the constant is the address. For example, this load instruction (“Load Byte and Zero”) loads a byte from memory and puts it in register r6, zeroing the three higher-order bytes of r6. It reads a byte from memory address (r3)+0, i.e., Mem[(r3)+0]:

lbz r6, 0(r3)

For example, if r3 = 0x4000028A, and Mem[0x40000028A] = 0xCC, then this instruction results in r6 = 0x000000CC.

If the instruction does not calculate a memory address, then either the constant or rN is omitted. Then the operand is either the constant, i.e., immediate value, or the contents of rN.

 

Initializing a Base Register with a 32-bit Address

Refer to the end of the program, where the label “DataSeg” precedes three assembler directives. After the assembler is done translating the instructions to machine code, it sees the .word directives, which tell it to reserve three words in memory following the code and initialize the first two words. This is like a local variable space for the assembly program. The label DataSeg is equal to the starting address of the variable space, i.e., the address of the first word that is initialized to $11. Thus, each of the words can be accessed as an offset from DataSeg: DataSeg+0, DataSeg+4, DataSeg+8.

DataSeg: 

 .word $11   

 .word $22   

 .word 0

This looks like a perfect match for indexed addressing, in which a base register holds the starting address of the variable space, and displacements correspond to the offsets for each of the words. The question is, how is a base register initialized to the value of the label DataSeg, which is a 32-bit address? For example, suppose we want to initialize register r2 with the value of DataSeg, where DataSeg = 0x20000380.

Does the addi instruction work?

addi r2, 0, DataSeg

This is a special case of addi where a register operand is replaced with 0, so that r2 ß 0 + DataSeg. But what’s wrong with this instruction? Recall the constraint that an immediate value can be no larger than 16 bits. How big is DataSeg? 32 bits. An instruction cannot take a 32-bit number (either address or data) as an immediate operand.

Here’s the solution.

To work with a 32-bit immediate value, it must be handled as 2 16-bit numbers, the 16 high-order bits (upper halfword) and the 16 low-order bits (lower halfword). In addition, the instruction set includes a simpler mnemonic (name) to load an immediate value into a register: li, load immediate. For example:

li r2, 0x2000

This instruction places the value 0x00002000 (the 16-bit value left-padded with zeros) into register r2. To find this instruction in the manual, you will need to see Appendix E, Simplified Mnemonics.

Okay, so now what? To get a 32-bit value such as 0x20000380 into a register takes two steps:

1.      Get 0x2000 into the upper half of the register.

2.      Get 0x0380 into the lower half of the register.

This can be done in several ways, but this is a common pair of instructions that does the job.

lis  r2, 0x2000         ;upper half of r2 = value

ori  r2, r2, 0x0380     ;r2 = r2 | value

Or, using the label DataSeg itself, along with the “@” notation to select the upper or lower half of the number:

lis  r2, DataSeg@h           ;upper half of r2 = upper halfword of DataSeg

ori  r2, r2, DataSeg@l       ;r2 = r2 | lower halfword of DataSeg

To understand this pair of instructions, you will need to look up and familiarize yourself with the “Load Immediate Shifted” lis instruction and “Or Immediate” ori instruction. The lis instruction loads a 16-bit signed immediate value, shifted left by 16 bits, into the destination register. The ori instruction performs a logical operation, r2 ORed with the 16-bit value (with zero-padding to 32 bits).

Example:

Suppose we want to load a 32-bit address into a register, such as the address of DIP Switch 1. Then we’ll use the register as a pointer to read the switch. How is register r3 initialized with the 32-bit address, 0x4000000B, of DIP Switch 1? Two instructions are needed.

Let:
IO_DIGITAL_INPUT_DIP_1 = 0x4000000B    ; constant defined in code, address of DIP Switch 1

lis  r3, IO_DIGITAL_INPUT_DIP_1@h             ;upper half of r3 = high-order 16 bits of address of DIP switch 1

r3 ¬ (IO_DIGITAL_INPUT_DIP1@h << 16)    ; use @h to get the higher 16 bits
r3
¬ (0x4000 << 16)
r3 = 0x40000000

ori  r3, r3, IO_DIGITAL_INPUT_DIP_1@l         ; r3 = r3 | low-order 16 bits of address of DIP switch 1

r3 ¬ r3 | IO_DIGITAL_INPUT_DIP1@l            ; use @l (lower case L) to get the lower 16 bits
r3
¬ r3 | 0x000B
r3
¬ 0x40000000 | 0x000B
r3 = 0x4000000B

Once register r3 holds the switch’s address, then the “Load Byte and Zero” instruction can be used to read the switch’s value into another register, e.g., r6.

lbz  r6, 0(r3)

This puts the 8-bit switch value into the LSB of r6.

Another coding option for reading the switch input port is shown below ($ denotes a hex number). Hint: recall the indexed addressing mode.

lis  r3, $4000
lbz  r6, $000B(r3)

Suppose the lis-lbz code is followed by the “Store Byte” stb instruction:

stb r6, $0023(r3)

You can look in defines.h to identify where the switch value in r6 is being stored/written using this instruction (i.e,. which PowerBox output port is at 0x40000023).