Ramses — Architecture Manual

1. Overview

Ramses is an 8-bit didactic computer, the third in the family of hypothetical machines used in UFRGS computer architecture courses (Neander → Ahmes → Ramses → Cesar). It keeps Neander’s byte-sized word and 256-byte address space but adds:

Everything in Ramses is still 8-bit: the word size, each register, the Program Counter, and memory addresses. This keeps programs small and makes register/memory state easy to show on a single screen — a useful constraint for UI design.

Source of truth for this document: fs/ArchSims.Core/Ramses.fs, fs/ArchSims.Core/Memory.fs, fs/ArchSims.Assemblers/RamsesAssembler.fs.

2. Programmer’s Model

2.1 Registers

RegisterWidthEncoding (bits 3..2)Role
RA8 bits00General-purpose
RB8 bits01General-purpose
RX8 bits10General-purpose; also used as index in Indexed mode
PC8 bits11Program Counter

PC is addressable as a register field in the encoding, but every instruction that writes through the register field simply writes whichever register the two-bit code selects. Practical programs almost always use RA, RB, RX.

The assembler accepts these names: A, B, X, PC (case-insensitive).

2.2 Instruction Register (IR)

A two-byte structure holding:

The IR is programmer-visible in the CLI output and should be visible in the UI — it is the best single explanation of “what is about to execute”.

2.3 Flags

FlagSet byCleared on reset
Negativeresult bit 7 = 1 (i.e. result > 0x7F)false
Zeroresult = 0true
CarryADD/SUB: unsigned overflow; NEG: operand was 0; SHR: LSBfalse
HaltedHLT just executedfalse

Note: Zero is true in the initial state because the accumulator-like value 0 is zero. UIs should reflect this — flag lamps start -Z-, not all off.

N and Z are updated by every register-writing instruction (LDR, ADD, OR, AND, NOT, SUB, NEG, SHR). C is touched only by ADD, SUB, NEG, SHR. STR, JMP, Jxx, JSR, NOP, HLT leave the flags alone.

2.4 Memory

The core instruments reads and writes via MemoryReadByte/MemoryWriteByte, keeping ReadCount/WriteCount counters that the CLI never prints but that a UI can display as a “memory accesses this step” indicator.

3. Instruction Encoding

Each instruction occupies one or two bytes. The first byte (“OpCode”) packs three fields:

bit  7 6 5 4   3 2   1 0
     I I I I   R R   M M
     |         |     |
     |         |     +-- Address mode
     |         +-------- Register
     +------------------ Instruction

Masks (see Ramses.fs):

Instructions that take no register (NOP, JMP, Jxx, JSR, HLT) simply ignore the R field. Instructions without an operand (NOP, NOT, NEG, SHR, HLT) occupy one byte and ignore the M field.

3.1 Instruction Table

MnemonicOpCode (hi nibble)BytesOperandWritesAffects flagsNotes
NOP0000 (0x00)1No-op
STR r0001 (0x10)2yesMEMMEM[addr] ← r
LDR r0010 (0x20)2yesrN, Zr ← MEM[addr]
ADD r0011 (0x30)2yesrN, Z, Cr ← r + MEM[addr]
OR r0100 (0x40)2yesrN, Zr ← r OR MEM[addr]
AND r0101 (0x50)2yesrN, Zr ← r AND MEM[addr]
NOT r0110 (0x60)1rN, Zr ← NOT r
SUB r0111 (0x70)2yesrN, Z, Cr ← r − MEM[addr]; C = no borrow
JMP1000 (0x80)2yesPCUnconditional
JN1001 (0x90)2yesPCIf N=1
JZ1010 (0xA0)2yesPCIf Z=1
JC1011 (0xB0)2yesPCIf C=1
JSR1100 (0xC0)2yesMEM, PCSubroutine call (see below)
NEG r1101 (0xD0)1rN, Z, Cr ← −r (two’s complement); C=1 iff original r = 0
SHR r1110 (0xE0)1rN, Z, CLogical right shift; C ← former LSB
HLT1111 (0xF0)1HaltedStops the CPU

3.2 Notes on individual instructions

4. Addressing Modes

Four modes, encoded in the two low bits of the OpCode:

ModeBitsAssembly suffixEffective addressExtra memory reads
Direct00NN0 (fetch only)
Indirect01N,IMEM[N]+1
Immediate02#NN itself (the operand byte)0
Indexed03N,X(N + RX) mod 2560

Important subtleties visible in Ramses.fs:123-128:

UI tip — when disassembling, show the effective address next to the mnemonic. The disassembler already produces e.g. LDR A 123 vs LDR A 123,I vs LDR A #123 vs LDR A 123,X. Supplementing that with → eff. addr = 146 during single-step makes indexed/indirect semantics tangible.

5. Execution Cycle

Step cpu performs Fetch then Execute. No interrupts, no cycle counting — one instruction per step.

Fetch (Ramses.fs:95):

  1. Read byte at PC, store in IR.OpCode, increment PC.
  2. If the instruction has an operand, compute the effective address according to the mode and store it in IR.OperandAddress. This may consume one more byte from PC.

Execute (Ramses.fs:131):

  1. Decode register and instruction from IR.OpCode.
  2. Perform the operation, update the target register/memory and the flags as per the instruction table.
  3. Set Halted iff the instruction was HLT.

The reference implementation never throws on “invalid” opcodes — any byte whose high-nibble is not a recognized instruction is treated as NOP/HLT. The CLI’s RunningForever stop reason after 1000 instructions (fs/ArchSims.Core/Debugger.fs) guards against infinite loops.

6. Assembly Language

Parser: FParsec-based, defined in RamsesAssembler.fs.

6.1 Lexical rules

6.2 Directives / pseudo-ops

SyntaxMeaning
@NSet assembly cursor to address N (0..255)
:LabelDefine a label at the current cursor
NA bare unsigned byte is emitted as a data constant

6.3 Instruction syntax

NOP | HLT
NOT r | NEG r | SHR r                        r ∈ {A, B, X, PC}
JMP op | JN op | JZ op | JC op | JSR op
STR r, op | LDR r, op | ADD r, op
OR  r, op | AND r, op | SUB r, op

Operand op is one of:

FormModeExample
NDirectLDR A 10
N,IIndirectLDR A 10,I
#NImmediateLDR A #10
N,XIndexedLDR A 10,X
:LabelDirectJMP :Loop
:Label,I, :Label,X, #:Labelas above, with the label’s address as N

Forward label references are resolved in a single deferred pass (RamsesAssembler.fs:114). Duplicate or unresolved labels raise a Portuguese error message (Label duplicado, Label indefinido).

6.4 Example — BitShift.Ramses.txt

:StartRA
    LDR A #128
:RepeatRA
    SHR A
    JZ :StartRB
    JMP :RepeatRA
:StartRB
    LDR B #128
:RepeatRB
    SHR B
    JZ :StartRX
    JMP :RepeatRB
:StartRX
    LDR X #128
:RepeatRX
    SHR X
    JZ :Finish
    JMP :RepeatRX

:Finish
    HLT

Loads 0x80 into each register in turn and shifts right until zero — a visually obvious progression that makes a good first-launch demo for any UI.

7. Running a Program

CLI entry point: fs/ArchSims.CmdLine/Main.fs dispatches to RamsesCmdLine.fs.

ArchSims.CmdLine.exe BitShift.Ramses.txt -Cpu Ramses -Mode Interactive -Output Decimal -Speed 10

Output line format (decimal):

Ra:  0 Rb:  0 Rx:  0 PC:  2 IR: [LDR A 128  ] -Z-

Save mode produces a binary file with prefix 0x03 'R' 'M' 'S' followed by the 256 bytes of memory — compatible with the historical Ramses .mem format.

8. UI Design Suggestions

A few recommendations that follow directly from the architecture:

  1. Fit on one screen. 256 bytes fit comfortably in a 16×16 hex grid. Highlight the current PC, last read, last written (three distinct colors). The core already exposes ReadCount/WriteCount per step if you want a “changed cells” animation.
  2. Register panel. Show RA, RB, RX, PC as four small tiles. Render each value in three bases simultaneously (hex, decimal, binary) — the architecture is small enough that showing all three costs nothing.
  3. Flag lamps. Three flags plus Halted. Model them as LEDs. Remember Z starts lit.
  4. Instruction register view. The IR holds both the opcode byte and the effective address. Decode it visually: split the opcode byte into IIII / RR / MM fields, color each bit-group, and show the mnemonic + effective operand underneath. This single widget explains the encoding to a student in a way a table cannot.
  5. Disassembly column. DisassembleInstructions (in Ramses.fs) already walks a byte list and returns (text, size) pairs. Use it to render an “assembly view” alongside the hex grid and mark which bytes belong to the instruction currently in the IR.
  6. Step vs Run. The debugger API (DebuggerStep, DebuggerRun, DebuggerSetBreakpoint, DebuggerClearBreakpoint) matches a typical UI triad of Step / Run / Break. The RunningForever sentinel after N steps gives you a natural “slow mode” progress indicator.
  7. Addressing-mode explainer. When the user hovers an instruction with ,I, #, or ,X, draw an arrow from the instruction’s operand byte to the effective memory cell. This is the single hardest concept in the Neander → Ramses step and a visual arrow teaches it in seconds.
  8. Assemble-on-the-fly. AssembleInstruction accepts a single line and returns the bytes. A UI could expose this as “type a mnemonic, see its bytes appear” — a great exploration tool for the encoding table.
  9. No stack means no call tree. JSR self-modifies memory. Visualizing it as an arrow that writes a return byte into the called routine (rather than “pushes”) is honest and instructive.
  10. Speed control. The CLI exposes Speed as instructions-per-second; keep that for parity and expose 1, 10, 100, 1000 presets plus a “as fast as possible” toggle.