1200 lines
33 KiB
Markdown
1200 lines
33 KiB
Markdown
# DSA Assembly Language Reference
|
|
|
|
## Overview
|
|
|
|
This document is the comprehensive reference for writing DSA assembly programs. It covers assembly syntax, pseudo-instructions, directives, the module system, calling conventions, and provides complete examples.
|
|
|
|
**Related Documents:**
|
|
- For hardware instruction details and encoding: See *DSA ISA Specification*
|
|
- For build system and toolchain: See project documentation
|
|
|
|
## Assembly Syntax
|
|
|
|
### General Rules
|
|
|
|
- **Case Insensitive:** Mnemonics can be uppercase or lowercase (`mov` = `MOV`)
|
|
- **Comments:** Use `//` for line comments or `/* */` for block comments
|
|
- **Labels:** Identifier followed by colon (e.g., `main:`, `loop:`)
|
|
- **Whitespace:** Flexible spacing between operands
|
|
- **Numbers:**
|
|
- Decimal: `100`, `255`
|
|
- Hexadecimal: `0x10`, `0xFFFF`
|
|
- Binary: `0b1010` (if supported by assembler)
|
|
|
|
### Operand Order Convention
|
|
|
|
DSA assembly uses **GAS-style syntax** (source → destination):
|
|
|
|
```asm
|
|
mov rg0, rg1 ; Copy rg0 TO rg1 (destination is last)
|
|
add rg0, rg1, rg2 ; rg2 = rg0 + rg1 (destination is last)
|
|
```
|
|
|
|
For load/store with immediates:
|
|
```asm
|
|
lli 0x1234, rg0 ; Load immediate 0x1234 INTO rg0
|
|
ldw rg0, rg1, 8 ; Load from (rg0+8) INTO rg1
|
|
stw rg0, rg1, 8 ; Store rg0 TO address (rg1+8)
|
|
```
|
|
|
|
## Registers
|
|
|
|
| Register(s) | Type | Description | Usage Notes |
|
|
|-------------|------|-------------|-------------|
|
|
| **rg0-rgf** | General | 16 general-purpose registers | Use for variables, temporaries |
|
|
| **acc** | Special | Accumulator | ⚠️ **VOLATILE** - Used as scratch by pseudo-instructions |
|
|
| **spr** | Special | Stack pointer | Points to current top of stack |
|
|
| **bpr** | Special | Base pointer | Used for stack frames |
|
|
| **ret** | Special | Return address | Holds function return addresses |
|
|
| **zero** | Read-only | Always zero | Reads return 0, writes discarded |
|
|
| **pcx** | Read-only | Program counter | Cannot be written directly |
|
|
| **idr** | Privileged | Interrupt descriptor table | Kernel mode only |
|
|
| **mmr** | Privileged | Memory map register | Kernel mode only |
|
|
| **noreg** | Placeholder | No register | Used in encoding, triggers fault if accessed |
|
|
|
|
**Alternative Names:**
|
|
- **noreg** can also be referenced as **null** in assembly
|
|
|
|
**Register Conventions:**
|
|
- **acc**: Volatile scratch register used by pseudo-instructions - **never preserved across pseudo-ops**
|
|
- **rg0-rge**: Available for general use; calling convention defines preservation rules
|
|
- **rgf**: General purpose register, available for use
|
|
|
|
**⚠️ CRITICAL:** The `acc` register is used internally by label-based memory operations and other pseudo-instructions. Do not assume its value is preserved across any pseudo-instruction!
|
|
|
|
## Hardware Instructions
|
|
|
|
This section shows assembly syntax. For encoding details, see the ISA Specification.
|
|
|
|
### Data Movement
|
|
|
|
```asm
|
|
mov src_reg, dest_reg ; Copy value from src_reg to dest_reg
|
|
movs src_reg, dest_reg ; Copy with sign extension
|
|
```
|
|
|
|
**Examples:**
|
|
```asm
|
|
mov rg0, rg1 ; rg1 = rg0
|
|
movs acc, rg2 ; rg2 = sign_extend(acc)
|
|
```
|
|
|
|
### Memory Load Instructions
|
|
|
|
```asm
|
|
ldb base_reg, dest_reg [, offset] ; Load byte (zero-extend)
|
|
ldbs base_reg, dest_reg [, offset] ; Load byte (sign-extend)
|
|
ldh base_reg, dest_reg [, offset] ; Load halfword (zero-extend)
|
|
ldhs base_reg, dest_reg [, offset] ; Load halfword (sign-extend)
|
|
ldw base_reg, dest_reg [, offset] ; Load word
|
|
```
|
|
|
|
**Offset:** Optional signed 16-bit offset (defaults to 0)
|
|
|
|
**Examples:**
|
|
```asm
|
|
ldb rg0, rg1 ; Load byte from address in rg0
|
|
ldw rg0, rg1, 8 ; Load word from (rg0 + 8)
|
|
ldhs rg2, rg3, -4 ; Load signed halfword from (rg2 - 4)
|
|
```
|
|
|
|
**Alignment Requirements:**
|
|
- `ldb/ldbs`: No alignment required
|
|
- `ldh/ldhs`: Must be 2-byte aligned
|
|
- `ldw`: Must be 4-byte aligned
|
|
|
|
### Memory Store Instructions
|
|
|
|
```asm
|
|
stb src_reg, base_reg [, offset] ; Store byte
|
|
sth src_reg, base_reg [, offset] ; Store halfword
|
|
stw src_reg, base_reg [, offset] ; Store word
|
|
```
|
|
|
|
**Examples:**
|
|
```asm
|
|
stb rg0, rg1 ; Store byte to address in rg1
|
|
stw rg0, rg1, 12 ; Store word to (rg1 + 12)
|
|
sth acc, spr, -2 ; Store halfword to (spr - 2)
|
|
```
|
|
|
|
**Alignment Requirements:** Same as loads
|
|
|
|
### Immediate Load Instructions
|
|
|
|
```asm
|
|
lli immediate, dest_reg ; Load lower 16 bits (CLEARS upper 16!)
|
|
lui immediate, dest_reg ; Load upper 16 bits (preserves lower 16)
|
|
```
|
|
|
|
**⚠️ CRITICAL:** `lli` clears the upper 16 bits! Always use `lli` before `lui`.
|
|
|
|
**Loading 32-bit Constants:**
|
|
```asm
|
|
lli 0x1234, rg0 ; rg0 = 0x00001234
|
|
lui 0xABCD, rg0 ; rg0 = 0xABCD1234
|
|
```
|
|
|
|
**Note:** The assembler may process the immediate value for `lui` - specify the upper 16 bits directly (e.g., `lui 0xABCD, rg0` to set upper bits to 0xABCD).
|
|
|
|
**Loading Addresses:** See `lwi` pseudo-instruction
|
|
|
|
### Jump and Branch Instructions
|
|
|
|
```asm
|
|
jmp offset, base_reg ; Unconditional jump to (base_reg + offset)
|
|
jeq offset, base_reg ; Jump if equal
|
|
jne offset, base_reg ; Jump if not equal
|
|
jgt offset, base_reg ; Jump if greater than
|
|
jge offset, base_reg ; Jump if greater or equal
|
|
jlt offset, base_reg ; Jump if less than
|
|
jle offset, base_reg ; Jump if less or equal
|
|
```
|
|
|
|
**Target Address Calculation:**
|
|
```
|
|
target = base_reg + sign_extend(offset)
|
|
```
|
|
|
|
**Jump Modes:**
|
|
```asm
|
|
; Absolute jump (using zero register)
|
|
jmp label, zero ; Jump to label address
|
|
|
|
; Register-based jump
|
|
jmp 0, ret ; Jump to address in ret register
|
|
jmp 4, ret ; Jump to (ret + 4)
|
|
|
|
; PC-relative (if assembler supports label resolution)
|
|
jeq loop_start, zero ; Jump to loop_start if equal flag set
|
|
```
|
|
|
|
**Conditional Jumps:** Based on flags set by `cmp` instruction
|
|
|
|
**Examples:**
|
|
```asm
|
|
jmp start, zero ; Absolute jump to 'start'
|
|
jmp 4, ret ; Jump to (ret + 4)
|
|
jeq end, zero ; Jump to 'end' if equal flag set
|
|
jgt loop, zero ; Jump to 'loop' if greater than flag set
|
|
```
|
|
|
|
### Comparison
|
|
|
|
```asm
|
|
cmp reg1, reg2 ; Compare reg1 with reg2, set flags
|
|
```
|
|
|
|
**Flags Set:**
|
|
- Equal: `reg1 == reg2`
|
|
- GreaterThan: `reg1 > reg2` (signed comparison)
|
|
- LessThan: `reg1 < reg2` (signed comparison)
|
|
- GreaterThanOrEqual: `reg1 >= reg2`
|
|
- LessThanOrEqual: `reg1 <= reg2`
|
|
|
|
**Example:**
|
|
```asm
|
|
cmp rg0, zero ; Compare rg0 with 0
|
|
jeq is_zero, zero ; Branch if rg0 == 0
|
|
jgt is_positive, zero ; Branch if rg0 > 0
|
|
jlt is_negative, zero ; Branch if rg0 < 0
|
|
```
|
|
|
|
### Arithmetic Instructions
|
|
|
|
```asm
|
|
add src1, src2, dest ; dest = src1 + src2
|
|
sub src1, src2, dest ; dest = src1 - src2
|
|
iadd src, immediate, dest ; dest = src + immediate (DEST REQUIRED!)
|
|
isub src, immediate, dest ; dest = src - immediate (DEST REQUIRED!)
|
|
inc reg ; reg = reg + 1
|
|
dec reg ; reg = reg - 1
|
|
```
|
|
|
|
**Examples:**
|
|
```asm
|
|
add rg0, rg1, rg2 ; rg2 = rg0 + rg1
|
|
sub rg0, rg1, rg2 ; rg2 = rg0 - rg1
|
|
iadd rg0, 10, rg0 ; rg0 = rg0 + 10 (in-place)
|
|
iadd rg0, 10, rg1 ; rg1 = rg0 + 10 (separate dest)
|
|
isub rg1, 5, rg2 ; rg2 = rg1 - 5
|
|
inc spr ; spr = spr + 1
|
|
dec spr ; spr = spr - 1
|
|
```
|
|
|
|
**⚠️ Note:** For `iadd`/`isub`, the destination register is **required** (not optional). For in-place operations, specify the source register as both source and destination.
|
|
|
|
### Bitwise Logical Operations
|
|
|
|
```asm
|
|
and src1, src2, dest ; dest = src1 & src2
|
|
or src1, src2, dest ; dest = src1 | src2
|
|
xor src1, src2, dest ; dest = src1 ^ src2
|
|
not src, dest ; dest = ~src
|
|
nand src1, src2, dest ; dest = ~(src1 & src2)
|
|
nor src1, src2, dest ; dest = ~(src1 | src2)
|
|
xnor src1, src2, dest ; dest = ~(src1 ^ src2)
|
|
```
|
|
|
|
**Examples:**
|
|
```asm
|
|
and rg0, rg1, rg2 ; rg2 = rg0 & rg1
|
|
or rg0, rg1, rg2 ; rg2 = rg0 | rg1
|
|
not rg0, rg1 ; rg1 = ~rg0
|
|
xor rg0, rg0, rg0 ; rg0 = 0 (XOR register with itself)
|
|
```
|
|
|
|
### Shift Operations
|
|
|
|
```asm
|
|
shl reg, shift_amount ; Shift left by amount (0-31)
|
|
shr reg, shift_amount ; Shift right by amount (0-31)
|
|
```
|
|
|
|
**Shift Amount:**
|
|
- **Literal:** `shl rg0, 2` - shift by constant 2
|
|
- **Register:** `shl rg0, rg1` - shift by value in rg1 (low 5 bits)
|
|
|
|
**⚠️ Note:** Current assembler may only support literal shift amounts. Check your assembler documentation for register shift support.
|
|
|
|
**Examples:**
|
|
```asm
|
|
shl rg0, 2 ; rg0 = rg0 << 2
|
|
shr rg1, 3 ; rg1 = rg1 >> 3
|
|
; shl rg0, rg1 ; May not be supported by assembler
|
|
```
|
|
|
|
**Note:** Shift right is logical (zero-fill), not arithmetic
|
|
|
|
### System and Control Instructions
|
|
|
|
```asm
|
|
hlt ; Halt processor
|
|
nop ; No operation
|
|
int interrupt_code ; Trigger interrupt (8-bit code)
|
|
irt ; Return from interrupt
|
|
```
|
|
|
|
**Examples:**
|
|
```asm
|
|
hlt ; Stop execution
|
|
nop ; Do nothing (timing/alignment)
|
|
int 0x21 ; Trigger interrupt 0x21
|
|
irt ; Return from interrupt handler
|
|
```
|
|
|
|
## Pseudo-Instructions
|
|
|
|
Pseudo-instructions are assembly-level constructs that expand into one or more hardware instructions.
|
|
|
|
**⚠️ IMPORTANT:** Many pseudo-instructions use the `acc` register as a scratch register. The value in `acc` is **not preserved** across these operations!
|
|
|
|
### Data Definition Directives
|
|
|
|
```asm
|
|
db label: value1 [, value2, ...] ; Define bytes
|
|
dh label: value1 [, value2, ...] ; Define halfwords (16-bit)
|
|
dw label: value1 [, value2, ...] ; Define words (32-bit)
|
|
```
|
|
|
|
**Examples:**
|
|
```asm
|
|
db message: "Hello, World!", 0 ; String with null terminator
|
|
db bytes: 0x01, 0x02, 0x03 ; Array of bytes
|
|
dh numbers: 1000, 2000, 3000 ; Array of halfwords
|
|
dw stack_base: 0x10000 ; Single word value
|
|
dw table: 0, 0, 0, 0 ; Array of 4 words
|
|
```
|
|
|
|
**String Encoding:** Strings are automatically null-terminated and encoded as byte sequences with escape sequences:
|
|
- `\n` = newline (0x0A)
|
|
- `\t` = tab (0x09)
|
|
- `\r` = carriage return (0x0D)
|
|
- `\\` = backslash
|
|
- `\"` = double quote
|
|
- `\0` = null (0x00)
|
|
|
|
**⚠️ Endianness Note:** Numeric values in data directives are stored in **big-endian** format by the assembler, unlike the little-endian used for instructions.
|
|
|
|
### Memory Reservation Directives
|
|
|
|
```asm
|
|
resb label: size ; Reserve 'size' bytes
|
|
resh label: size ; Reserve 'size' halfwords
|
|
resw label: size ; Reserve 'size' words
|
|
```
|
|
|
|
**Examples:**
|
|
```asm
|
|
resb buffer: 256 ; Reserve 256 bytes
|
|
resh array: 100 ; Reserve 100 halfwords (200 bytes)
|
|
resw heap: 1024 ; Reserve 1024 words (4096 bytes)
|
|
```
|
|
|
|
**Note:** Reserved memory is uninitialized (contents undefined).
|
|
|
|
### Stack Operations
|
|
|
|
```asm
|
|
push reg ; Push register onto stack
|
|
pop reg ; Pop stack into register
|
|
```
|
|
|
|
**⚠️ CRITICAL - Stack Direction:** The DSA stack grows **DOWNWARD** (toward lower memory addresses).
|
|
|
|
**Expansion:**
|
|
```asm
|
|
; push rg0 expands to:
|
|
subi spr, 4, spr ; spr = spr - 4 (allocate space, stack grows down)
|
|
stw rg0, spr, 0 ; Store rg0 to [spr]
|
|
|
|
; pop rg0 expands to:
|
|
ldw spr, rg0, 0 ; Load [spr] into rg0
|
|
addi spr, 4, spr ; spr = spr + 4 (deallocate space)
|
|
```
|
|
|
|
**Examples:**
|
|
```asm
|
|
push rg0 ; Save rg0 on stack
|
|
push rg1 ; Save rg1 on stack
|
|
; ... do work ...
|
|
pop rg1 ; Restore rg1
|
|
pop rg0 ; Restore rg0
|
|
```
|
|
|
|
**Stack Diagram:**
|
|
```
|
|
Higher Memory Addresses
|
|
↑
|
|
│ (old data)
|
|
├─────────────┤
|
|
│ rg0 value │ ← After first push
|
|
├─────────────┤ ← SPR after first push
|
|
│ rg1 value │ ← After second push
|
|
├─────────────┤ ← SPR after second push (stack grew down)
|
|
↓
|
|
Lower Memory Addresses
|
|
```
|
|
|
|
### Push/Pop Multiple Registers
|
|
|
|
```asm
|
|
pusha count ; Push first 'count' general registers (rg0-rgN)
|
|
popa count ; Pop first 'count' general registers
|
|
```
|
|
|
|
**Examples:**
|
|
```asm
|
|
pusha 4 ; Push rg0, rg1, rg2, rg3
|
|
; ... do work ...
|
|
popa 4 ; Pop rg3, rg2, rg1, rg0
|
|
```
|
|
|
|
**Expansion:**
|
|
```asm
|
|
; pusha 3 expands to:
|
|
subi spr, 12, spr ; Allocate space for 3 words
|
|
stw rg0, spr, 0 ; Store rg0
|
|
stw rg1, spr, 4 ; Store rg1
|
|
stw rg2, spr, 8 ; Store rg2
|
|
|
|
; popa 3 expands to:
|
|
ldw spr, rg0, 0 ; Load rg0
|
|
ldw spr, rg1, 4 ; Load rg1
|
|
ldw spr, rg2, 8 ; Load rg2
|
|
addi spr, 12, spr ; Deallocate space
|
|
```
|
|
|
|
**Note:** Registers are pushed/popped in order (rg0, rg1, rg2, ...).
|
|
|
|
### Load Address Pseudo-Instruction
|
|
|
|
```asm
|
|
lwi label, dest_reg ; Load address of label into register
|
|
```
|
|
|
|
**Expansion:**
|
|
```asm
|
|
; lwi message, rg0 expands to:
|
|
lli message, rg0 ; Load lower 16 bits of address
|
|
lui message, rg0 ; Load upper 16 bits of address
|
|
```
|
|
|
|
**Example:**
|
|
```asm
|
|
db message: "Hello!", 0
|
|
|
|
lwi message, rg0 ; rg0 = address of message
|
|
ldb rg0, rg1, 0 ; rg1 = first byte of message ('H')
|
|
```
|
|
|
|
### Memory Access with Labels
|
|
|
|
Load and store instructions can use labels directly:
|
|
|
|
```asm
|
|
ldb label, dest_reg [, offset]
|
|
ldh label, dest_reg [, offset]
|
|
ldw label, dest_reg [, offset]
|
|
stb src_reg, label [, offset]
|
|
sth src_reg, label [, offset]
|
|
stw src_reg, label [, offset]
|
|
```
|
|
|
|
**⚠️ CRITICAL:** These pseudo-instructions use `acc` as a scratch register! The value in `acc` will be overwritten.
|
|
|
|
**Expansion:**
|
|
```asm
|
|
; ldw buffer, rg2 expands to:
|
|
lli buffer, acc ; Load lower 16 bits of buffer address into acc
|
|
lui buffer, acc ; Load upper 16 bits of buffer address into acc
|
|
ldw acc, rg2, 0 ; Load word from address in acc into rg2
|
|
|
|
; stw rg1, current expands to:
|
|
lli current, acc ; Load lower 16 bits of current address into acc
|
|
lui current, acc ; Load upper 16 bits of current address into acc
|
|
stw rg1, acc, 0 ; Store word from rg1 to address in acc
|
|
```
|
|
|
|
**Examples:**
|
|
```asm
|
|
dw counter: 0
|
|
|
|
ldw counter, rg0 ; Load value of counter (clobbers acc!)
|
|
addi rg0, 1, rg0 ; Increment
|
|
stw rg0, counter ; Store back (clobbers acc!)
|
|
```
|
|
|
|
**⚠️ Warning Example - ACC Clobbering:**
|
|
```asm
|
|
lli 100, acc ; acc = 100
|
|
ldw data, rg0 ; ACC IS NOW OVERWRITTEN!
|
|
; acc now contains garbage (address calculation temp)
|
|
; The value 100 is LOST!
|
|
```
|
|
|
|
# DSA Assembly Language Reference - Part 2
|
|
|
|
## Function Call Pseudo-Instructions
|
|
|
|
```asm
|
|
call namespace::function ; Call function from included module
|
|
return ; Return from function
|
|
```
|
|
|
|
**⚠️ CRITICAL:** The calling mechanism uses the **STACK** to store return addresses, not just the `ret` register!
|
|
|
|
**CALL Expansion:**
|
|
```asm
|
|
; call print::print expands to:
|
|
subi spr, 4, spr ; Decrement stack pointer (allocate space)
|
|
stw pcx, spr, 0 ; Store return address (PCX) on stack
|
|
jmp function_addr, zero ; Jump to function address
|
|
```
|
|
|
|
**RETURN Expansion:**
|
|
```asm
|
|
; return expands to:
|
|
ldw spr, ret, 0 ; Load return address from stack into ret
|
|
addi spr, 4, spr ; Increment stack pointer (deallocate)
|
|
jmp 4, ret ; Jump to (ret + 4)
|
|
```
|
|
|
|
**Note on RETURN +4 Offset:** The implementation adds 4 to the return address. This may be to account for instruction sizing or pipeline considerations. Consult CPU documentation if modifying.
|
|
|
|
### Module System
|
|
|
|
```asm
|
|
include namespace "path/to/file.dsa"
|
|
```
|
|
|
|
**Example:**
|
|
```asm
|
|
include print "lib/print.dsa"
|
|
include math "lib/math.dsa"
|
|
|
|
; Can now call:
|
|
call print::print
|
|
call math::multiply
|
|
```
|
|
|
|
**Namespace Resolution:**
|
|
- Functions in included modules are accessible via `namespace::label`
|
|
- Namespace is the identifier before the filename
|
|
- Labels in included files are prefixed with the namespace
|
|
|
|
## Calling Convention
|
|
|
|
DSA uses a stack-based calling convention with downward-growing stack.
|
|
|
|
### Stack Frame Layout
|
|
|
|
**⚠️ CRITICAL:** Stack grows DOWNWARD (toward lower addresses)!
|
|
|
|
```
|
|
Higher Memory Addresses ↑
|
|
│
|
|
┌─────────────┐
|
|
│ Caller's │
|
|
│ Frame │
|
|
└─────────────┘
|
|
│ Arg N │ ← Caller pushed (highest address argument)
|
|
│ ... │
|
|
│ Arg 2 │
|
|
│ Arg 1 │
|
|
│ Arg 0 │ ← First argument (lowest address argument)
|
|
├─────────────┤
|
|
│ Ret Addr │ ← Return address (pushed by CALL)
|
|
├─────────────┤
|
|
│ Old BPR │ ← Saved base pointer (pushed by callee)
|
|
├─────────────┤ ← BPR (current base), SPR (current top)
|
|
│ Locals │ ← Local variables (if any)
|
|
│ ... │
|
|
└─────────────┘ ← SPR grows downward
|
|
│
|
|
Lower Memory Addresses ↓
|
|
```
|
|
|
|
**Frame Pointer Offsets** (from BPR):
|
|
```
|
|
bpr + 0 = Old BPR (saved by function prologue)
|
|
bpr + 4 = Return address (pushed by CALL)
|
|
bpr + 8 = Argument 0 (first argument)
|
|
bpr + 12 = Argument 1
|
|
bpr + 16 = Argument 2
|
|
bpr + 8 + 4*N = Argument N
|
|
```
|
|
|
|
### Calling Sequence
|
|
|
|
**Caller Responsibilities:**
|
|
|
|
1. **Push arguments in reverse order** (last argument first):
|
|
```asm
|
|
push arg2 ; Push last argument first
|
|
push arg1
|
|
push arg0 ; First argument pushed last
|
|
```
|
|
|
|
2. **Call the function:**
|
|
```asm
|
|
call namespace::function ; Pushes return address, jumps to function
|
|
```
|
|
|
|
3. **Clean up arguments** after return:
|
|
```asm
|
|
pop zero ; Discard arg0 (or pop rg0 to get return value)
|
|
pop zero ; Discard arg1
|
|
pop zero ; Discard arg2
|
|
```
|
|
|
|
**Callee Responsibilities:**
|
|
|
|
1. **Set up stack frame:**
|
|
```asm
|
|
function:
|
|
push bpr ; Save caller's base pointer
|
|
mov spr, bpr ; Establish new base pointer
|
|
```
|
|
|
|
2. **Access arguments:**
|
|
```asm
|
|
ldw bpr, rg0, 8 ; Load arg0 from bpr+8
|
|
ldw bpr, rg1, 12 ; Load arg1 from bpr+12
|
|
ldw bpr, rg2, 16 ; Load arg2 from bpr+16
|
|
```
|
|
|
|
3. **Execute function body:**
|
|
```asm
|
|
; Function logic here
|
|
add rg0, rg1, acc ; Example: acc = arg0 + arg1
|
|
```
|
|
|
|
4. **Store return value** (optional - overwrites arg0 location):
|
|
```asm
|
|
stw acc, bpr, 8 ; Store result where arg0 was
|
|
```
|
|
|
|
5. **Restore stack frame:**
|
|
```asm
|
|
mov bpr, spr ; Restore stack pointer to base
|
|
pop bpr ; Restore caller's base pointer
|
|
```
|
|
|
|
6. **Return to caller:**
|
|
```asm
|
|
return ; Pop return address and jump
|
|
```
|
|
|
|
### Stack Evolution During Call
|
|
|
|
**Before CALL:**
|
|
```
|
|
┌─────────────┐
|
|
│ Arg 2 │
|
|
│ Arg 1 │
|
|
│ Arg 0 │ ← SPR points here
|
|
└─────────────┘
|
|
```
|
|
|
|
**After CALL (before function prologue):**
|
|
```
|
|
┌─────────────┐
|
|
│ Arg 2 │
|
|
│ Arg 1 │
|
|
│ Arg 0 │
|
|
│ Ret Addr │ ← SPR points here (CALL pushed this)
|
|
└─────────────┘
|
|
```
|
|
|
|
**After Function Prologue:**
|
|
```
|
|
┌─────────────┐
|
|
│ Arg 2 │
|
|
│ Arg 1 │
|
|
│ Arg 0 │
|
|
│ Ret Addr │
|
|
│ Old BPR │ ← SPR and BPR point here
|
|
└─────────────┘
|
|
```
|
|
|
|
### Complete Calling Example
|
|
|
|
```asm
|
|
; Function: add two numbers
|
|
; Args: arg0, arg1
|
|
; Returns: sum (overwrites arg0 position)
|
|
|
|
add_function:
|
|
; Prologue
|
|
push bpr ; Save caller's base pointer
|
|
mov spr, bpr ; Set up our stack frame
|
|
|
|
; Load arguments
|
|
ldw bpr, rg0, 8 ; rg0 = arg0
|
|
ldw bpr, rg1, 12 ; rg1 = arg1
|
|
|
|
; Perform operation
|
|
add rg0, rg1, acc ; acc = arg0 + arg1
|
|
|
|
; Store return value (overwrites arg0 position)
|
|
stw acc, bpr, 8 ; Store result at bpr+8
|
|
|
|
; Epilogue
|
|
mov bpr, spr ; Restore stack pointer
|
|
pop bpr ; Restore caller's base pointer
|
|
return ; Return to caller
|
|
|
|
; Caller example:
|
|
main:
|
|
; Set up stack
|
|
lwi stack_base, bpr
|
|
mov bpr, spr
|
|
|
|
; Prepare arguments
|
|
lli 5, rg0 ; First argument = 5
|
|
lli 7, rg1 ; Second argument = 7
|
|
|
|
; Push arguments (reverse order!)
|
|
push rg1 ; Push arg1 (7) first
|
|
push rg0 ; Push arg0 (5) second
|
|
|
|
; Call function
|
|
call local::add_function
|
|
|
|
; Retrieve result and clean up
|
|
pop rg2 ; Get result (12) - was at arg0 position
|
|
pop zero ; Discard arg1 slot
|
|
|
|
hlt
|
|
|
|
dw stack_base: 0x10000
|
|
```
|
|
|
|
### Register Usage Conventions
|
|
|
|
| Register(s) | Usage | Preserved Across Calls? |
|
|
|-------------|-------|------------------------|
|
|
| **rg0-rg3** | Function arguments, temporaries | No (caller-saved) |
|
|
| **rg4-rge** | Local variables | Conditional (callee-saved if used) |
|
|
| **rgf** | General purpose | Conditional (callee-saved if used) |
|
|
| **acc** | Temporary calculations, scratch | **Never** (volatile) |
|
|
| **spr** | Stack pointer | Yes (must be restored) |
|
|
| **bpr** | Base pointer | Yes (must be restored) |
|
|
| **ret** | Return address | Managed by call/return |
|
|
|
|
**Preservation Rules:**
|
|
- **Caller-saved (rg0-rg3, acc):** Caller must save these before calling if needed after
|
|
- **Callee-saved (rg4-rgf):** Callee must save/restore if it uses them
|
|
- **Always preserved (spr, bpr):** Must be restored to original values
|
|
- **Never preserved (acc):** Assume destroyed by any operation
|
|
|
|
**Example - Callee-Saved Registers:**
|
|
```asm
|
|
my_function:
|
|
push bpr
|
|
mov spr, bpr
|
|
|
|
; We want to use rg4 and rg5 - must save them!
|
|
push rg4
|
|
push rg5
|
|
|
|
; Use rg4 and rg5 for local work
|
|
lli 100, rg4
|
|
lli 200, rg5
|
|
add rg4, rg5, acc
|
|
|
|
; Restore callee-saved registers before returning
|
|
pop rg5
|
|
pop rg4
|
|
|
|
mov bpr, spr
|
|
pop bpr
|
|
return
|
|
```
|
|
|
|
## Complete Examples
|
|
|
|
### Example 1: Multiplication Library
|
|
|
|
```asm
|
|
// multiply.dsa
|
|
// Multiplies two numbers using repeated addition
|
|
//
|
|
// Usage:
|
|
// include multiply "multiply.dsa"
|
|
// push multiplicand
|
|
// push multiplier
|
|
// call multiply::multiply
|
|
// pop result ; Result is in arg0 position
|
|
// pop zero ; Clean up arg1
|
|
|
|
multiply:
|
|
push bpr
|
|
mov spr, bpr
|
|
|
|
ldw bpr, rg0, 8 ; Load multiplier (arg0)
|
|
ldw bpr, rg1, 12 ; Load multiplicand (arg1)
|
|
|
|
lli 0, acc ; Initialize result to 0
|
|
|
|
loop_start:
|
|
add acc, rg0, acc ; acc += multiplier
|
|
dec rg1 ; multiplicand--
|
|
|
|
cmp rg1, zero
|
|
jgt loop_start, zero ; Continue if multiplicand > 0
|
|
|
|
stw acc, bpr, 8 ; Store result for caller (at arg0)
|
|
|
|
mov bpr, spr
|
|
pop bpr
|
|
return
|
|
```
|
|
|
|
### Example 2: Print Library
|
|
|
|
```asm
|
|
// print.dsa
|
|
// Prints null-terminated string to display memory
|
|
//
|
|
// Usage:
|
|
// include print "print.dsa"
|
|
//
|
|
// push string_address
|
|
// call print::print
|
|
// pop zero
|
|
//
|
|
// call print::reset ; Reset cursor (no args)
|
|
|
|
dw display: 0x20000 ; Display memory base address
|
|
dw current: 0x20000 ; Current cursor position
|
|
|
|
// Print function
|
|
print:
|
|
push bpr
|
|
mov spr, bpr
|
|
|
|
ldw bpr, rg0, 8 ; Get string address argument
|
|
ldw current, rg1 ; Get current cursor position (clobbers acc!)
|
|
|
|
print_loop:
|
|
ldb rg0, acc, 0 ; Load character
|
|
stb acc, rg1, 0 ; Store to display
|
|
|
|
addi rg0, 1, rg0 ; Advance string pointer
|
|
addi rg1, 1, rg1 ; Advance cursor
|
|
|
|
cmp acc, zero ; Check for null terminator
|
|
jne print_loop, zero ; Continue if not null
|
|
|
|
stw rg1, current ; Save cursor position (clobbers acc!)
|
|
|
|
mov bpr, spr
|
|
pop bpr
|
|
return
|
|
|
|
// Reset cursor function
|
|
reset:
|
|
push bpr
|
|
mov spr, bpr
|
|
|
|
ldw display, rg1 ; Load display base (clobbers acc!)
|
|
stw rg1, current ; Reset cursor to start (clobbers acc!)
|
|
|
|
mov bpr, spr
|
|
pop bpr
|
|
return
|
|
```
|
|
|
|
### Example 3: Main Program
|
|
|
|
```asm
|
|
// main.dsa
|
|
// Demonstrates using included libraries
|
|
|
|
include print "./print.dsa"
|
|
|
|
dw stack: 0x10000
|
|
db string: "'To confuse your enemy, you must first confuse yourself' - Probably Sun Tzu.", 0
|
|
|
|
init:
|
|
// Set up stack
|
|
ldw stack, bpr ; Load stack base (clobbers acc!)
|
|
mov bpr, spr
|
|
|
|
start:
|
|
// Load string address
|
|
lwi string, rg1 ; Load address of string
|
|
|
|
// Call print function
|
|
push rg1
|
|
call print::print
|
|
pop rg1 ; Clean up (or pop zero to discard)
|
|
|
|
hlt
|
|
```
|
|
|
|
### Example 4: Conditional Logic
|
|
|
|
```asm
|
|
// Demonstrates comparisons and branching
|
|
|
|
dw value: 42
|
|
|
|
main:
|
|
ldw value, rg0 ; Load value (clobbers acc!)
|
|
|
|
cmp rg0, zero
|
|
jeq is_zero, zero
|
|
jgt is_positive, zero
|
|
jlt is_negative, zero
|
|
|
|
is_zero:
|
|
lwi zero_msg, rg1
|
|
jmp print_and_exit, zero
|
|
|
|
is_positive:
|
|
lwi positive_msg, rg1
|
|
jmp print_and_exit, zero
|
|
|
|
is_negative:
|
|
lwi negative_msg, rg1
|
|
jmp print_and_exit, zero
|
|
|
|
print_and_exit:
|
|
push rg1
|
|
call print::print
|
|
pop zero
|
|
hlt
|
|
|
|
db zero_msg: "Value is zero", 0
|
|
db positive_msg: "Value is positive", 0
|
|
db negative_msg: "Value is negative", 0
|
|
```
|
|
|
|
### Example 5: Loop with Counter
|
|
|
|
```asm
|
|
// Count from 0 to 9
|
|
|
|
dw stack: 0x10000
|
|
|
|
main:
|
|
ldw stack, bpr ; Set up stack
|
|
mov bpr, spr
|
|
|
|
lli 0, rg0 ; Counter = 0
|
|
lli 10, rg1 ; Limit = 10
|
|
|
|
loop:
|
|
// Process counter value
|
|
push rg0
|
|
call process_value
|
|
pop zero
|
|
|
|
inc rg0 ; Counter++
|
|
cmp rg0, rg1 ; Compare with limit
|
|
jlt loop, zero ; Loop if counter < limit
|
|
|
|
hlt
|
|
|
|
process_value:
|
|
push bpr
|
|
mov spr, bpr
|
|
|
|
ldw bpr, rg0, 8 ; Get value
|
|
; Process value here...
|
|
|
|
mov bpr, spr
|
|
pop bpr
|
|
return
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### 1. Stack Management
|
|
- **Always balance push/pop operations** in the same function
|
|
- Set up stack frame (`push bpr; mov spr, bpr`) in every function
|
|
- Clean up arguments after function calls (caller's responsibility)
|
|
- Use `pop zero` to discard unwanted values
|
|
- Remember: stack grows DOWNWARD
|
|
|
|
### 2. Register Usage
|
|
- **Never rely on `acc` being preserved** across ANY operation
|
|
- Treat `acc` as write-only temporary storage
|
|
- Save caller-saved registers (rg0-rg3) before calls if needed
|
|
- Restore callee-saved registers (rg4-rgf) if modified
|
|
- Use `zero` register for zero constants
|
|
|
|
### 3. Memory Access
|
|
- Ensure proper alignment for halfword/word access
|
|
- Be aware that label-based loads/stores clobber `acc`
|
|
- Prefer register-based addressing when `acc` value matters
|
|
- Check that labels are defined before use
|
|
|
|
### 4. Function Design
|
|
- **Document calling convention** in function comments
|
|
- Always include prologue and epilogue
|
|
- Validate input arguments when appropriate
|
|
- Use consistent parameter order across related functions
|
|
- Return values via stack (overwrite arg0) or designated register
|
|
|
|
### 5. Code Organization
|
|
- Use meaningful label names (e.g., `loop_start` not `l1`)
|
|
- Comment complex operations
|
|
- Group related functions in modules
|
|
- Use includes for code reuse
|
|
- Keep functions focused and small
|
|
|
|
### 6. Performance Considerations
|
|
- Minimize memory accesses (use registers when possible)
|
|
- Avoid unnecessary comparisons
|
|
- Use shifts for multiplication/division by powers of 2:
|
|
```asm
|
|
shl rg0, 3 ; Multiply by 8 (2^3)
|
|
shr rg0, 2 ; Divide by 4 (2^2)
|
|
```
|
|
- Consider instruction pipelining if CPU supports it
|
|
|
|
### 7. ACC Register Awareness
|
|
**⚠️ CRITICAL - Common Mistakes:**
|
|
|
|
```asm
|
|
; WRONG - acc gets clobbered:
|
|
lli 100, acc
|
|
ldw data, rg0 ; acc is now GARBAGE!
|
|
add acc, rg0, rg1 ; Using garbage value!
|
|
|
|
; RIGHT - use a different register:
|
|
lli 100, rg2
|
|
ldw data, rg0 ; acc clobbered (don't care)
|
|
add rg2, rg0, rg1 ; rg2 still has 100
|
|
|
|
; WRONG - assuming acc preserved across call:
|
|
lli 42, acc
|
|
call some_function
|
|
add acc, rg0, rg1 ; acc probably destroyed!
|
|
|
|
; RIGHT - use caller-saved register:
|
|
lli 42, rg0
|
|
call some_function
|
|
add rg0, rg1, rg2 ; rg0 might be destroyed, so save it first!
|
|
```
|
|
|
|
## Common Patterns
|
|
|
|
### Loading 32-bit Constants
|
|
```asm
|
|
lli lower_16_bits, reg
|
|
lui upper_16_bits, reg
|
|
|
|
; Example:
|
|
lli 0x1234, rg0 ; rg0 = 0x00001234
|
|
lui 0xABCD, rg0 ; rg0 = 0xABCD1234
|
|
```
|
|
|
|
### Zero a Register
|
|
```asm
|
|
mov zero, reg ; Method 1 (preferred - clearest)
|
|
xor reg, reg, reg ; Method 2 (clever but less clear)
|
|
lli 0, reg ; Method 3 (works but wastes upper bits clear)
|
|
```
|
|
|
|
### Copy Memory
|
|
```asm
|
|
ldw src_addr, rg0 ; Load from source (clobbers acc)
|
|
stw rg0, dest_addr, 0 ; Store to destination (clobbers acc)
|
|
|
|
; If you need acc preserved:
|
|
lli 0x1234, acc ; Some value in acc
|
|
push acc ; Save it
|
|
ldw src_addr, rg0 ; acc clobbered
|
|
stw rg0, dest_addr, 0 ; acc clobbered again
|
|
pop acc ; Restore acc
|
|
```
|
|
|
|
### Multiply/Divide by Power of 2
|
|
```asm
|
|
shl reg, 3 ; Multiply by 8 (2^3)
|
|
shr reg, 2 ; Divide by 4 (2^2)
|
|
```
|
|
|
|
### Boolean NOT
|
|
```asm
|
|
cmp reg, zero
|
|
jeq was_zero, zero ; If reg == 0, result is 1
|
|
lli 0, reg
|
|
jmp done, zero
|
|
was_zero:
|
|
lli 1, reg
|
|
done:
|
|
```
|
|
|
|
### Min/Max
|
|
```asm
|
|
; max(rg0, rg1) -> rg2
|
|
mov rg0, rg2 ; Assume rg0 is max
|
|
cmp rg0, rg1
|
|
jge done, zero ; If rg0 >= rg1, we're done
|
|
mov rg1, rg2 ; rg1 was larger
|
|
done:
|
|
```
|
|
|
|
### Array Indexing
|
|
```asm
|
|
; Access array[i] where array is 32-bit words
|
|
; rg0 = array base address
|
|
; rg1 = index i
|
|
; Result in rg2
|
|
|
|
shl rg1, 2 ; Convert index to byte offset (i * 4)
|
|
add rg0, rg1, acc ; acc = base + offset (use acc for temp)
|
|
ldw acc, rg2, 0 ; Load array[i]
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Errors
|
|
|
|
**Alignment Fault:**
|
|
- Symptom: Exception when loading/storing halfword or word
|
|
- Cause: Address not properly aligned
|
|
- Fix: Ensure halfword addresses are even, word addresses divisible by 4
|
|
|
|
**Illegal Instruction:**
|
|
- Symptom: Unexpected halt or exception
|
|
- Cause: Invalid opcode or malformed instruction
|
|
- Fix: Check assembly syntax, verify instruction encoding
|
|
|
|
**Stack Corruption:**
|
|
- Symptom: Function returns to wrong address, registers have wrong values
|
|
- Cause: Unbalanced push/pop, incorrect stack frame management
|
|
- Fix:
|
|
- Verify push/pop balance
|
|
- Check function epilogue restores bpr
|
|
- Ensure caller cleans up arguments
|
|
- Verify stack grows downward (push decrements)
|
|
|
|
**Wrong Results / Unexpected Values:**
|
|
- Symptom: Calculations produce incorrect results
|
|
- Cause: ACC clobbering, wrong register assumptions
|
|
- Fix:
|
|
- Check if acc was assumed preserved (it never is!)
|
|
- Verify lli called before lui for 32-bit constants
|
|
- Check signed vs unsigned loads (ldb vs ldbs)
|
|
- Verify register preservation across calls
|
|
|
|
**Label Not Found:**
|
|
- Symptom: Assembler error about undefined label
|
|
- Cause: Label used before definition, typo in label name
|
|
- Fix: Define labels before use, check spelling
|
|
|
|
### Debugging Tips
|
|
|
|
1. **Add NOP markers** at key points for breakpoints:
|
|
```asm
|
|
nop ; Breakpoint here
|
|
```
|
|
|
|
2. **Print register values** using display memory:
|
|
```asm
|
|
stw rg0, debug_out ; Store to known location
|
|
```
|
|
|
|
3. **Use single-step execution** to trace program flow
|
|
|
|
4. **Verify stack pointer** values at function boundaries:
|
|
```asm
|
|
; After prologue, check:
|
|
; spr == bpr (should point to same location)
|
|
; After epilogue, check:
|
|
; spr restored to entry value
|
|
```
|
|
|
|
5. **Check label addresses** in disassembly output
|
|
|
|
6. **Trace ACC usage**:
|
|
```asm
|
|
lli 0xDEAD, acc ; Marker value
|
|
; ... your code ...
|
|
cmp acc, zero
|
|
jeq acc_was_clobbered, zero ; If acc==0, it was overwritten
|
|
```
|
|
|
|
## Appendix: Instruction Quick Reference
|
|
|
|
| Category | Instructions |
|
|
|----------|-------------|
|
|
| **Data Movement** | mov, movs |
|
|
| **Memory Load** | ldb, ldbs, ldh, ldhs, ldw |
|
|
| **Memory Store** | stb, sth, stw |
|
|
| **Immediate Load** | lli, lui |
|
|
| **Jump/Branch** | jmp, jeq, jne, jgt, jge, jlt, jle |
|
|
| **Comparison** | cmp |
|
|
| **Arithmetic** | add, sub, iadd, isub, inc, dec |
|
|
| **Logical** | and, or, xor, not, nand, nor, xnor |
|
|
| **Shift** | shl, shr |
|
|
| **System** | hlt, nop, int, irt |
|
|
| **Pseudo - Data** | db, dh, dw, resb, resh, resw |
|
|
| **Pseudo - Stack** | push, pop, pusha, popa |
|
|
| **Pseudo - Memory** | lwi, ldb/ldh/ldw with labels, stb/sth/stw with labels |
|
|
| **Pseudo - Control** | call, return |
|
|
| **Pseudo - Module** | include |
|
|
|
|
## Version History
|
|
|
|
- **v2.0** - Corrected comprehensive reference based on implementation
|
|
- **CORRECTED:** Stack grows downward (not upward)
|
|
- **CORRECTED:** CALL/RETURN use stack for return addresses
|
|
- **CORRECTED:** Pseudo-instructions use ACC as scratch (not rgf)
|
|
- **CORRECTED:** Label loads use ACC for address calculation
|
|
- **CORRECTED:** IADD/ISUB require destination (not optional)
|
|
- **ADDED:** PUSHA/POPA documentation
|
|
- **ADDED:** Detailed ACC volatility warnings throughout
|
|
- **ADDED:** Stack evolution diagrams
|
|
- **CLARIFIED:** Big-endian data directive encoding
|
|
- **CLARIFIED:** Jump instruction semantics
|
|
- All examples updated to reflect correct stack direction
|
|
- Added extensive troubleshooting for ACC-related issues
|