LC3 Assembly Language

More abstract, with additional powers:

  1. Labels
  2. Instruction and Register names
  3. Assembler Directives
    • .ORIG - location to store code/data
    • .END - end assembly process
    • .FILL - Value for this memory location
    • .BLKW - Block of Words
    • .STRINGZ - Initialize memory with ASCII values, 0-terminated
  4. Shorthand for using decimal and hexadecimal numbers

New: when you write in assembly code, the memory dumps will be disassembled automatically for you. That is, they will be converted from bits to instructions.

BUG: Always provide a LABEL for .BLKW

Format of assembly code

This is completely up to you! Some like to line up items in columns:

LABEL:    OPCODE       OPERANDS    ; COMMENTS
          DIRECTIVE

OPCODE and OPERANDS are mandatory. All spacing is optional, but lines are meaningful.

Colon after the LABEL is optional with the Calysto LC3 Assembler. Develop your own style!

Jupyter Magics - metacommands

Review, and some new ones:

 %bp [clear | SUSPENDHEX]           - show, clear, or set breakpoints
 %cont                              - continue running
 %dis [STARTHEX [STOPHEX]]          - dump memory as program
 %dump [STARTHEX [STOPHEX]]         - list memory in hex
 %exe                               - execute the program
 %mem HEXLOCATION HEXVALUE          - set memory
 %pc HEXVALUE                       - set PC
 %reg REG HEXVALUE                  - set register REG to HEXVALUE
 %regs                              - show registers
 %reset                             - reset LC3 to start state
 %step                              - execute the next instruction, increment PC
 %labels                            - show the labels after first pass

Example

In [1]:
; Example from book, Figure 7.1, page 179
; Program to multiply an integer by the constant 6.
; Before execution, an integer must be stored in 
; NUMBER.

        .ORIG x3050
        LD R1, SIX
        LD R2, NUMBER
        AND R3, R3, #0 ;; clear R3; will contain product

;; Loop

AGAIN:   ADD R3, R3, R2
        ADD R1, R1, #-1
        BRp AGAIN
        
        HALT
        
NUMBER: .BLKW 1
SIX:    .FILL x0006

        .END
============================================================
Memory disassembled:
============================================================
           x3050: x2207  LD R1, SIX                                [line: 1]
           x3051: x2405  LD R2, NUMBER                             [line: 2]
           x3052: x56E0  AND R3, R3, #0                            [line: 3]
AGAIN:     x3053: x16C2  ADD R3, R3, R2                            [line: 7]
           x3054: x127F  ADD R1, R1, #-1                           [line: 8]
           x3055: x03FD  BRp AGAIN                                 [line: 9]
           x3056: xF025  HALT                                      [line: 11]
NUMBER:    x3057: x0000  BR SIX (or 0)                             [line: 13]
SIX:       x3058: x0006  BR x305F (or 6)                           [line: 14]
           x3059: x0000  BR x305A (or 0)                           [line: 15]

============================================================
Registers:
============================================================
PC: x3059
N: 0 Z: 1 P: 0 
R0: x0000 R1: x0000 R2: x0000 R3: x0000 
R4: x0000 R5: x0000 R6: x0000 R7: x0000 
In [2]:
; Example from book, Figure 7.1, page 179
; Program to multiply an integer by the constant 6.
; Before execution, an integer must be stored in 
; NUMBER.

.ORIG x3050
        LD R1, SIX
        LD R2, NUMBER
        AND R3, R3, #0 ;; clear R3; will contain product

;; Loop

AGAIN:  ADD R3, R3, R2
        ADD R1, R1, #-1
        BRp AGAIN
        
        HALT
        
NUMBER: .BLKW 1
SIX:    .FILL x0006

.END
============================================================
Memory disassembled:
============================================================
           x3050: x2207  LD R1, SIX                                [line: 1]
           x3051: x2405  LD R2, NUMBER                             [line: 2]
           x3052: x56E0  AND R3, R3, #0                            [line: 3]
AGAIN:     x3053: x16C2  ADD R3, R3, R2                            [line: 7]
           x3054: x127F  ADD R1, R1, #-1                           [line: 8]
           x3055: x03FD  BRp AGAIN                                 [line: 9]
           x3056: xF025  HALT                                      [line: 11]
NUMBER:    x3057: x0000  BR SIX (or 0)                             [line: 13]
SIX:       x3058: x0006  BR x305F (or 6)                           [line: 14]
           x3059: x0000  BR x305A (or 0)                           [line: 15]

============================================================
Registers:
============================================================
PC: x3059
N: 0 Z: 1 P: 0 
R0: x0000 R1: x0000 R2: x0000 R3: x0000 
R4: x0000 R5: x0000 R6: x0000 R7: x0000 
In [3]:
%mem x3057 x1
============================================================
Memory disassembled:
============================================================
NUMBER:    x3057: x0001  BR x3059 (or 1)                           [line: 13]
In [4]:
%exe
============================================================
Computation completed
============================================================
Instructions: 22
Cycles: 143 (0.000072 milliseconds)

============================================================
Registers:
============================================================
PC: x048E
N: 0 Z: 1 P: 0 
R0: x0000 R1: x0000 R2: x0001 R3: x0006 
R4: x0000 R5: x0000 R6: x0000 R7: x3057 

Labels

We used to have to figure out PC-offsets by hand, but now the assembler will figure that out for you.

Recall, for something like:

In [5]:
.ORIG x4000
        LD R1, SIX
        HALT
SIX:    .FILL #23
.END
============================================================
Memory disassembled:
============================================================
           x4000: x2201  LD R1, SIX                                [line: 1]
           x4001: xF025  HALT                                      [line: 2]
SIX:       x4002: x0017  BR x401A (or 23)                          [line: 3]

============================================================
Registers:
============================================================
PC: x4003
N: 0 Z: 1 P: 0 
R0: x0000 R1: x0000 R2: x0000 R3: x0000 
R4: x0000 R5: x0000 R6: x0000 R7: x0000 

You would have to compute the value for SIX in line 2 manually. No more!

But how would the assembler do this?

Two-pass assembler

The assembler first goes through the source code collecting labels, and their locations. During the second pass, it can substitute the used label in the operands with the label location minus instruction location - 1.

.ORIG x4000
x4000            LD R1, SIX
x4001            HALT
x4002    SIX:    .FILL #23
         .END

So, the PC-offset for SIX is x4002 - x4000 - 1 = 1. Verify that this is correct.

Warning: you still only have 9 bits to represent the distance between label and instruction!

How far away can the label be?

  • You can represent 512 in 9 bits: 111111111. But that is a negative number
  • You can represent 255 in 8 bits: 011111111.

So, this is ok:

In [2]:
.ORIG x4000
        LD R1, SIX
        HALT
BLOCK:  .BLKW #254
SIX:    .FILL #23
.END
============================================================
Memory disassembled:
============================================================
           x4000: x22FF  LD R1, SIX                                [line: 1]
           x4001: xF025  HALT                                      [line: 2]
BLOCK:     x4002: x0000  BR x4003 (or 0)                           [line: 3]
           x4003: x0000 - \0
           x4004: x0000 - \0
           x4005: x0000 - \0
           x4006: x0000 - \0
           x4007: x0000 - \0
           x4008: x0000 - \0
           x4009: x0000 - \0
           x400A: x0000 - \0
           x400B: x0000 - \0
           x400C: x0000 - \0
           x400D: x0000 - \0
           x400E: x0000 - \0
           x400F: x0000 - \0
           x4010: x0000 - \0
           x4011: x0000 - \0
           x4012: x0000 - \0
           x4013: x0000 - \0
           x4014: x0000 - \0
           x4015: x0000 - \0
           x4016: x0000 - \0
           x4017: x0000 - \0
           x4018: x0000 - \0
           x4019: x0000 - \0
           x401A: x0000 - \0
           x401B: x0000 - \0
           x401C: x0000 - \0
           x401D: x0000 - \0
           x401E: x0000 - \0
           x401F: x0000 - \0
           x4020: x0000 - \0
           x4021: x0000 - \0
           x4022: x0000 - \0
           x4023: x0000 - \0
           x4024: x0000 - \0
           x4025: x0000 - \0
           x4026: x0000 - \0
           x4027: x0000 - \0
           x4028: x0000 - \0
           x4029: x0000 - \0
           x402A: x0000 - \0
           x402B: x0000 - \0
           x402C: x0000 - \0
           x402D: x0000 - \0
           x402E: x0000 - \0
           x402F: x0000 - \0
           x4030: x0000 - \0
           x4031: x0000 - \0
           x4032: x0000 - \0
           x4033: x0000 - \0
           x4034: x0000 - \0
           x4035: x0000 - \0
           x4036: x0000 - \0
           x4037: x0000 - \0
           x4038: x0000 - \0
           x4039: x0000 - \0
           x403A: x0000 - \0
           x403B: x0000 - \0
           x403C: x0000 - \0
           x403D: x0000 - \0
           x403E: x0000 - \0
           x403F: x0000 - \0
           x4040: x0000 - \0
           x4041: x0000 - \0
           x4042: x0000 - \0
           x4043: x0000 - \0
           x4044: x0000 - \0
           x4045: x0000 - \0
           x4046: x0000 - \0
           x4047: x0000 - \0
           x4048: x0000 - \0
           x4049: x0000 - \0
           x404A: x0000 - \0
           x404B: x0000 - \0
           x404C: x0000 - \0
           x404D: x0000 - \0
           x404E: x0000 - \0
           x404F: x0000 - \0
           x4050: x0000 - \0
           x4051: x0000 - \0
           x4052: x0000 - \0
           x4053: x0000 - \0
           x4054: x0000 - \0
           x4055: x0000 - \0
           x4056: x0000 - \0
           x4057: x0000 - \0
           x4058: x0000 - \0
           x4059: x0000 - \0
           x405A: x0000 - \0
           x405B: x0000 - \0
           x405C: x0000 - \0
           x405D: x0000 - \0
           x405E: x0000 - \0
           x405F: x0000 - \0
           x4060: x0000 - \0
           x4061: x0000 - \0
           x4062: x0000 - \0
           x4063: x0000 - \0

============================================================
Registers:
============================================================
PC: x4101
N: 0 Z: 1 P: 0 
R0: x0000 R1: x0000 R2: x0000 R3: x0000 
R4: x0000 R5: x0000 R6: x0000 R7: x0000 
In [7]:
%exe
============================================================
Computation completed
============================================================
Instructions: 2
Cycles: 19 (0.000010 milliseconds)

============================================================
Registers:
============================================================
PC: x048E
N: 0 Z: 0 P: 1 
R0: x0000 R1: x0017 R2: x0000 R3: x0000 
R4: x0000 R5: x0000 R6: x0000 R7: x4002 

But this is not ok:

In [3]:
.ORIG x4000
        LD R1, SIX
        HALT
BLOCK:  .BLKW #255
SIX:    .FILL #23
.END
x4102

Lesson: labels have to be within 255 instructions of where they are used.

There is a way around this limitation. What is it?

Will be able to do this:

SIX:    .FILL DATA

Bug: But it doesn't work yet.

High-Level Programming Concepts

Declaration of Variables

High-level LC3
int a; // simple variable (uninitialized) a .BLKW 1 ; simple variable (or .FILL 0)
int b = 2014; // simple initialized variable b .FILL #2014 ; simple initialized variable
int c[10]; // array of 10 (uninitialized) c .BLKW 10 ; array of ten ints (initialized to 0)
int *d = &e; // address of e d .FILL e ; store address of e in variable d

Assignment of values

High-level LC3
b = a; LD R0, a ; load from memory to a register
ST R0, b ; store from register to memory
b = a + 1; LD R0, a ; load from memory to a register
ADD R0, R0, #1 ; increment value
ST R0, b ; store from register to memory

Assignment using pointers

High-level LC3
pa = &a; LEA R0, a ; get the address of the variable
ST R0, pa ; store it in the pointer variable
b = *pa; LDI R0, pa ; get the value at the address stored in pa
ST R0, b ; store it in b
*pa = b; LD R0, b ; load the value of b
STI R0, pa ; store it at the address stored in pa

Comparison: if

High-level LC3
if (a < b) { LD R0, a ; load a
// do something LD R1, b ; load b
} NOT R1, R1 ; begin 2's complement of b
ADD R1, R1, #1 ; R1 now has -b
ADD R0, R0, R1 ; R0 = a + (-b)
; condition code now set
BRzp SKIP ; if false, skip over code
; code to do something (the then clause)
SKIP ; remainder of code after if

Comparison: if/else

High-level

if (a < b) {
  // do something
}
else {
  // do something else
}

LC3

LD  R0, a       ; load a
     LD  R1, b       ; load b
     NOT R1, R1      ; begin 2's complement of b
     ADD R1, R1, #1  ; R1 now has -b
     ADD R0, R0, R1  ; R0 = a + (-b)
                     ; condition code now set
     BRzp ELSE       ; if false, skip over code

                     ; code to do something (the then clause)

     BR   END_ELSE   ; don't execute else code

ELSE                 ; code for else clause here

END_ELSE             ; remainder of code after else