# 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