A two-pass assembler for a modified SIC/XE architecture, with support for program blocks. Written in Python for a Systems Programming course, December 2023.
Modi-SIC/XE keeps the original SIC/XE instruction set, addressing modes, and BYTE/WORD/RESB/RESW storage directives, and adds two things.
A 4-byte instruction format that makes execution conditional on a processor flag, removing the need for separate branch instructions.
6 bits 4 bits 2 bits 20 bits
opcode | register | cond flag | address
The condition flag selects which processor flag gates the instruction: 00 for Zero, 01 for Negative, 10 for Carry, 11 for Overflow. If the flag isn't set, the instruction is skipped.
Immediate and indirect addressing are not available in this format.
Written in source as:
CADD A, BUFFER, Z
The 4F instruction set is CADD (BC), CSUB (8C), CLOAD (E4), CSTORE (FC) and CJUMP (CC). These occupy opcode slots left free by dropping the floating-point instructions and STT/STSW, which are not implemented.
Unlike standard SIC/XE, where blocks are user-named, modi-SIC/XE defines four fixed blocks, each with a designated purpose:
- DEFAULT: format 1 and 2 instructions
- DEFAULTB: format 3, 4 and 4F instructions
- CDATA: small data
- CBLKS: large memory reservations
DEFAULT and DEFAULTB are enforced by format. CDATA and CBLKS are distinguished by convention rather than by the assembler.
A program can use one, two, three or all four. The USE directive switches the active block.
A preliminary parse strips comments and line numbers into an intermediate file. Pass 1 then reads the source and tracks a separate location counter per block. It builds the symbol table and collects literals, flushing them at LTORG or END.
After Pass 1, a fixup loop works out where each block actually lands in the final memory layout and adds that offset to every symbol and literal address. Without it addresses collide, since every block's location counter starts at zero.
Pass 2 generates object code, selecting the instruction format and addressing mode (immediate, indirect, indexed, PC-relative, base-relative, extended), and writes the object program as H/T/M/E records.
Error handling covers unrecognised block names and undefined symbols.
Python 3, pandas (see requirements.txt).
python sicxe_assembler.py
This assembles examples/proga_multiblock.asm. The input path is hardcoded in two places, line 197 and line 834, so assembling a different file means editing both. Changing only one leaves Pass 1 and the preliminary parse reading different files.
Output lands in the repo root:
- HTME.txt: the object program
- symbTable.txt, literalTable.txt, blockTable.txt
- intermediate_file.txt
- out_pass1.txt: location counter per line
- out_pass2.txt: object code per line
examples/proga_multiblock.asm uses all four blocks, BASE, format 4, literals, immediate/indirect/indexed addressing, and the 4F conditional instructions.
examples/jlt_test.asm is a small conditional-jump test.
- EQU is not supported, and it fails silently. An EQU line never appends to the location counter list, so every instruction after it is paired with the wrong address. Nothing looks wrong until Pass 2 eventually throws an IndexError at an unrelated instruction further down.
- USE with no block name raises a KeyError instead of falling back to the default block.
- No macro processor. Input is assumed macro-expanded.
- The input path is hardcoded rather than taken as an argument.
- Floating-point instructions and STT/STSW are out of scope, as described above.