Systems Lecture FRI Sep 11th

Assembly Code Formatting and Syntax Rules

  • Label Placement and Margins:

    • Labels must be positioned flush against the far-left margin (Column 1).

    • The only elements allowed on the far-left margin are labels and comments.

    • All pseudo-ops (directives) and assembly instructions must be indented using tabs.

  • Label Uniqueness and Scoping:

    • Labels act like global variable names and are visible across the entire program.

    • Every label must map to a unique memory address; a single label name cannot point to multiple addresses.

    • Multiple distinct labels can point to the exact same address.

    • Labels cannot be reused (e.g., if an IF statement uses an exit label, it must be uniquely designated such as IF_END_1 or FN_1 so it does not conflict with subsequent logic blocks).

  • Instruction and Operand Spacing:

    • Instructions and pseudo-ops must be tabbed over from the left margin.

    • Operands must be separated by exactly one space.

    • Commas must immediately follow an operand without preceding spaces (e.g., ADD R1, R2).

  • Comment Formatting:

    • Comments may begin on the left margin, be tabbed over, or be placed anywhere on a line.

    • Improper tabbing and formatting lead to point deductions on examinations.

Code Structure and Commenting Best Practices

  • Program Directives:

    • Programs begin with an origin directive, such as .ORIG x3000 (or value 30003000), to set the starting memory address.

    • The program terminates with the .END directive at the bottom of the file.

  • Program Entry Point:

    • A MAIN label is commonly placed at the start of the executable code block to indicate the program entry point.

  • High-Level Commenting Philosophy:

    • Comments should describe the intent and high-level logic of what the code achieves rather than simply restating the low-level assembly instruction.

    • Bad comment example: Stating that a line "puts 6565 into address 30033003".

    • Good comment example: Explaining that 6565 represents an ASCII capital character 'A' (or a negative capital 'A' used for character verification).

    • Good comment example: For a sequence involving NOT and ADD instructions, the comment should state R0 = R1 - R2 to clearly identify the block as a subtraction operation.

    • Explaining complex high-level structures (such as IF statements or WHILE loops) keeps the programmer from losing their train of thought during debugging.

LC-3 Assembler Workflow and File Types

  • Supported File Extensions:

    • Assembly source code must be saved with a .asm file extension (e.g., test.asm).

    • Operating system settings that hide file extensions can inadvertently create invalid files such as test.asm.txt, which will fail to open in the assembler.

  • Assembly Process and Error Checking:

    • Opening a .asm file in the assembler and clicking Reload and Assemble compiles the code.

    • The assembler status line must display Assembly Complete along with a timestamp to confirm success. If an error message appears, the output files will not be generated properly and the code must be corrected.

    • The timestamp in the assembler updates once per second upon reloading.

    • Programmers must ensure they are editing and assembling the exact same file rather than editing a copy in a different directory.

  • Generated Output Files:

    • .asm File: The raw assembly source text file.

    • .hex File: An ASCII text representation of hex machine code required by the simulator.

    • .obj File: A true binary machine code file storing actual integer values directly executable by the processor.

    • .sym File (Symbol Table): Contains mappings of all defined labels to their designated 16-bit memory addresses.

  • Debugging with Symbol Tables and Simulator Integration:

    • Inspecting the .sym file helps identify syntax typos. For example, typing JSRx instead of JSR x causes the assembler to treat JSRx as an unused label assigned to an address.

    • When loading a .hex file into the LC-3 simulator, if the corresponding .sym file is present in the same directory, the simulator replaces raw numerical addresses with human-readable label names.

    • The optimal troubleshooting workflow cycle: Edit .asm source file →\rightarrow Save →\rightarrow Click Reload and Assemble in assembler →\rightarrow Click Reset in simulator to reload updated machine code.

Subroutines: JSR and JSRR Instructions

  • Subroutine Overview:

    • Subroutines function like methods or functions in high-level programming languages.

    • Subroutines are located between the main program block (after the HALT instruction) and the .END directive.

  • JSR vs. JSRR Instructions:

    • JSR (Jump to Subroutine): Uses an 11-bit PC-relative offset embedded inside the instruction. It is limited in jump range relative to the current Program Counter.

    • JSRR (Jump to Subroutine Register): Obtains the target address directly from a 16-bit register. Because a register holds a full 16-bit address, JSRR can jump anywhere within the entire 2162^{16} (6553665536) address space.

  • Control Flow Constraints:

    • Enter a subroutine exclusively via JSR or JSRR instructions.

    • Exit a subroutine exclusively via the RET (Return) instruction.

    • Never use branch (BR) instructions to branch out of a subroutine or to branch back into MAIN from inside a subroutine. Branching must strictly remain within the internal scope of the subroutine.

Register Parameter Passing and Multiplication Algorithm

  • Parameter and Return Value Conventions:

    • Parameters (arguments) are passed into subroutines via designated data registers (e.g., R1 and R2).

    • Return values are returned to the caller via a designated return register (e.g., R0).

    • Built-in system subroutines (TRAPs) follow this exact protocol: GETC reads a keypress from the keyboard and returns the character in R0; OUT takes a character stored in R0 and prints it to the console.

  • LC-3 Multiplication Subroutine (MULT) Implementation:

    • High-Level Call Specification: Pass multiplier and multiplicand in R1 and R2; return product in R0 (e.g., computing 5×3=155 \times 3 = 15).

    • Assembly Setup Code:

    • Load inputs: LDR R1, A and LDR R2, B (where A stores 55 and B stores 33).

    • Execute call: JSR MULT.

    • Algorithm Logic:

    1. Clear R0 to 00 (ADD R0, R0, #0 or AND R0, R0, #0).

    2. Repeatedly add R1 to R0 while decrementing R2 by 11 until R2 reaches 00

    3. Return execution to caller with R0 containing 1515

Register Preservation and Callee-Save Protocol

  • The Register Modification Problem:

    • In high-level languages like Java, passing variables a and b into c = mult(a, b) does not alter a and b in the calling scope.

    • In assembly, internal execution of MULT decrements R2 down to 00. If the main program depends on the original value of R2 after calling MULT, data corruption occurs.

  • Callee-Save Protocol:

    • The subroutine (callee) is responsible for saving any registers it modifies internally to dedicated memory locations at entry, and restoring them prior to returning.

    • Step 1 (Entry): Store modified registers to memory (e.g., ST R2, SAVE_R2).

    • Step 2 (Body): Execute subroutine computational logic.

    • Step 3 (Exit): Load saved values back into registers from memory (e.g., LD R2, SAVE_R2).

    • Step 4 (Return): Execute RET.

    • From the perspective of MAIN, register contents remain unchanged across the subroutine invocation.

  • Selective Preservation Rules:

    • Only save and restore registers that are explicitly modified inside the subroutine.

    • Do not save registers that are only read and never altered (e.g., if R1 is unchanged throughout execution, saving it is unnecessary).

    • CRITICAL EXCEPTION: Never save or restore the designated return register (R0). Restoring R0 at exit overwrites the calculated output with the old input value, breaking the subroutine's return mechanism.

  • Performance and Efficiency Impact:

    • Indiscriminately saving all registers (e.g., 66 stores and 66 loads = 1212 extra instructions per call) introduces severe overhead.

    • If a subroutine is called inside a loop executing 10001000 times, saving all registers unnecessarily adds 12×1000=1200012 \times 1000 = 12000 unnecessary instructions to program execution.

  • Handling Nested Subroutines and TRAP Calls (R7 Preservation):

    • The JSR instruction and system TRAP calls (such as GETC or OUT) write the return address into register R7.

    • If Subroutine A calls Subroutine B or executes a TRAP call within its body, the original return address in R7 needed to return to MAIN is overwritten.

    • Any subroutine containing nested JSR or TRAP calls must save R7 to memory at entry and restore R7 before executing its own RET instruction.

  • Register Storage vs. Stack Architecture:

    • Passing arguments via fixed memory labels limits subroutines from being called recursively, as subsequent invocations overwrite the static label memory locations.

    • Dynamic languages utilize a stack frame where arguments and return addresses are pushed and popped per call layer to support recursion.

Audience Interaction & Class Logistics

  • Student Attendance:

    • Student Alex Kider was absent due to COVID-19 quarantine and will provide a doctor's note.

  • Dormitory Housing Discussion:

    • Students discussed transfer housing arrangements featuring suite-style rooms with private/shared connected bathrooms compared to standard dorm layouts.