An emulator for the 1958-designed MonrobotXI computer, written in Typescript
  • TypeScript 87.3%
  • JavaScript 12.7%
Find a file
2026-09-15 15:33:31 +10:00
examples MonrobotXI machine emulator written in Typescript 2026-09-15 15:33:31 +10:00
src MonrobotXI machine emulator written in Typescript 2026-09-15 15:33:31 +10:00
test MonrobotXI machine emulator written in Typescript 2026-09-15 15:33:31 +10:00
.gitignore MonrobotXI machine emulator written in Typescript 2026-09-15 15:33:31 +10:00
ARCHITECTURE.md MonrobotXI machine emulator written in Typescript 2026-09-15 15:33:31 +10:00
package-lock.json MonrobotXI machine emulator written in Typescript 2026-09-15 15:33:31 +10:00
package.json MonrobotXI machine emulator written in Typescript 2026-09-15 15:33:31 +10:00
README.md MonrobotXI machine emulator written in Typescript 2026-09-15 15:33:31 +10:00
tsconfig.json First pass reverse engineering the architecture and rebuilding a CPU with devices in TypeScript 2026-09-13 15:06:09 +10:00
VERIFICATION.md MonrobotXI machine emulator written in Typescript 2026-09-15 15:33:31 +10:00

Monrobot Mark XI - CPU core

This is a DOM-free TypeScript reconstruction of the CPU core, I/O dispatch, and peripheral models of the Monrobot Mark XI computer, a 1958-designed bit-serial machine which was one of the first all-transistorised small office computers - an early 2nd generation computer.

This emulator was built as a clean reference for porting the machine to other languages - specifically a C++ desktop emulator and a SystemVerilog implementation for a Cyclone IV FPGA.

Try it out!

Want to just run a tape and watch it work? src/cli.ts is a general-purpose runner (not specific to Marilyn) that streams any program's Typewriter output to stdout:

npm run print:marilyn -- | less -r

To run any tape:

npx tsx src/cli.ts --tape <any tape image>

or if you have a binary image of the tape:

npx tsx src/cli.ts --drum <any binary tape>

Pass --verbose for diagnostics to stderr, but not when using a pager like less.

npx tsx src/cli.ts --tape examples/Marilyn/MAR-P.txt --verbose >/dev/null
# Loaded 959 drum words; starting at 007.
# Ran 71501 instructions; halted=true, stopCode=XX

Use npx tsx src/cli.ts --help to get help on further options:

--start <addr> overrides the boot address

--device2 <tape file> attaches a second Tape device preloaded from a different tape, for a program that does its own tape I/O at runtime (distinct from the tape used to load it)

--max-steps <n> caps execution

--interactive reads stdin and feeds it to the Typewriter as keyboard input via Typewriter.typeText() (auto-inserting shift-toggle bytes as needed). This is untested best-effort, since no known example program has yet exercised keyboard input to verify this against

John Mann wrote the Python script examples/Marilyn/bin2ppt.py to convert a tape image into a binary tape file, and this has been translated into TypeScript as src/tooling/ut1-tape-decoder.ts. This implements the loading protocol of the original Marilyn "UT-1" loader, and has been verified byte-for-byte against that script's own output.

Contributing to Development

npm install
npm run typecheck   # tsc --noEmit
npm test            # vitest

How the machine is structured

Start with ARCHITECTURE.md. It is the language-agnostic ISA/hardware spec and is the primary artifact here - generally more authoritative than this TypeScript for a port, since it states bit-level mechanics directly rather than through any one host language's numeric quirks.

Why this exists

This leans heavily on the live emulator published by dmcnaugh and co-written as a COVID lock-down project by he and his brother Steve. Kudos to them both for sheer persistence in getting this to work! The emulator is a minified, bundled JavaScript build - no TypeScript source repository is public. This reconstruction was produced by:

  1. Prettifying and reading the entire decode/execute switch, the fetch pipeline, the I/O dispatch, and the three device classes (Tape, MonroCard, Typewriter) directly out of that bundle.
  2. Cross-checking terminology (register names, instruction names like Detract/Interchange/Binary-End-Around, the "sexadecimal" numeral system) against the original Monroe Calculating Machine Company Monrobot XI Program Manual (1963 CHM archive scan).
  3. The changelog from the production code - a rich source, documenting exact manual page citations and several corrected misunderstandings. That changelog resolved at least one place where this reconstruction's first pass had the behavior backwards (see ARCHITECTURE.md section 6.10 - OUT adds parity, OUTP does not, which is the opposite of what the mnemonics suggest).
  4. Re-organizing the result into a clean separation between CPU, I/O, and UI code. Here:
    • src/core/ - the CPU: registers, drum memory, decode/execute, the two-syllable-per-word fetch pipeline. Zero I/O or DOM dependencies.
    • src/io/ - the 3-slot device bus and the MonroCard unit contract the CPU talks to, decoupled from any concrete device.
    • src/devices/ - Tape, MonroCard, Typewriter, Empty: behavioral models (character tables, card-motion timing, punch-tape bit encoding) with no DOM.
    • src/ui/ - deliberately empty except for a note on what was left out and why (see src/ui/README.md).

Verification against a real program

This isn't just unit-tested in isolation - see VERIFICATION.md for a cross-check against examples/Marilyn/, a real historical program tape with a captured reference run from the online emulator. Result: all 8 FA registers plus the pipeline register and stop code match exactly after 71,501 executed instructions, and the printed output matches 658/662 characters exactly (the remaining 4 are a residual, understood discrepancy in the reference material itself, unrelated to overstrike - see VERIFICATION.md, which is also honest about what this particular run does and doesn't confirm about the Typewriter's 39 backspace-overstrikes, since none of them land in the one frame the reference captured). Run it with npm run verify:marilyn.

Confidence level

  • src/core/cpu.ts: high confidence. Every opcode class was transcribed directly from the decode switch, and the trickiest part - the auto-sequencing pipeline that lets non-jump instructions still advance to the next drum word via a jump instruction "stashed" in the FA7/CR rotation for exactly two ticks - is validated by an end-to-end test (test/cpu.test.ts, "full fetch/execute pipeline") that runs a bootstrap JMP into two plain ADDs into an auto-fetched next word into a STP, with no jump instructions in the "body," and checks the final accumulator value. 17/17 tests pass; tsc --noEmit is clean.
  • src/devices/tape.ts: high confidence for the punch/feed/bit-reorder logic (transcribed 1:1, tape image text format preserved for interchange with the original's saved tapes). The original's real-time, human-in-the-loop "click Feed again" input model is simplified to "one attempt per read, then wait forever" - see the doc comment on Tape.input().
  • src/devices/monrocard.ts: medium-high confidence for the opcode-relevant read/write/eject/seek-timing state machine (transcribed from Te/Ie/Re/ie/K/V/ee). The original's deck-reload UI button behavior (Ee()) was not reproduced - it's a front-panel convenience, not opcode-driven behavior.
  • src/devices/typewriter.ts: high confidence. The character-code table (128 entries) is transcribed verbatim, and Backspace/overstrike is now fully reproduced as a lossless "X\bY" byte stream - see test/typewriter.test.ts for unit coverage of the documented hardware quirks (coalescing, top-of-form, two-per-position), and VERIFICATION.md for the honest caveat on what the Marilyn run does and doesn't confirm about overstrike specifically (it activates 39 times without derailing later output, but none of those 39 land inside the one frame the reference capture recorded).
  • Timing constants (device delay, MonroCard inter-sector timing) are the emulator author's UI-pacing choices, not attested hardware timing - see ARCHITECTURE.md section 8. Don't cite them for a cycle-accurate FPGA design.

Layout

ARCHITECTURE.md          <- the ISA spec; read this first
VERIFICATION.md          <- cross-check against a real program; how to run any tape
src/
  core/
    types.ts             <- MachineState, Registers, Drum, Opcode enum, Device/Tracer interfaces
    sexadecimal.ts        <- base-16 "S-X" numeral system (format/parse/parity)
    cpu.ts                <- the CPU: decode/execute, pipeline, autoSequence
  io/
    io-bus.ts             <- 3-slot device bus (select-bits 1/2/4)
    monrocard-unit.ts     <- MonroCard read/write/eject contract
  devices/
    empty.ts, tape.ts, monrocard.ts, typewriter.ts
  tooling/
    ut1-tape-decoder.ts   <- decode a raw UT-1-protocol tape (TS port of bin2ppt.py --ut1)
  ui/
    README.md             <- what was deliberately not reconstructed, and why
  index.ts                <- public re-exports
  cli.ts                  <- general-purpose "run any tape, stream ASCII to stdout" program
test/
  harness.ts              <- test CPU factory + instruction-word encoders
  cpu.test.ts             <- opcode-by-opcode and full-pipeline tests
  typewriter.test.ts      <- overstrike and keyboard-input tests
  ut1-tape-decoder.test.ts <- decoder verified against the pre-decoded reference
  marilyn-fixture.ts, marilyn-verify.ts <- the VERIFICATION.md cross-check
examples/Marilyn/         <- sibling directory: the real program tape used above

Using this for the C++ / SystemVerilog ports

Recommended order:

  1. Read ARCHITECTURE.md in full; it's short and is the real spec.
  2. Use src/core/cpu.ts as the reference implementation when writing the C++ core - its structure (one method per opcode class, fetchOperand/ storeOperand as the universal register-or-drum indirection) maps fairly directly to a C++ class.
  3. For the SystemVerilog/FPGA target, the most load-bearing section is ARCHITECTURE.md §5 (pipeline) and §6.3 (Jump/Jump-Mark) - the "stashed auto-jump" mechanism is exactly the kind of thing worth turning into an explicit state machine diagram before writing RTL, rather than translating the rotate-arithmetic literally.
  4. test/cpu.test.ts is a reasonable starting checklist of behaviors any port should reproduce; port the same test cases (especially the DXFR short-circuit, the STR/XCH address-6 special case, and the OUT/OUTP parity polarity) to whatever test framework the port uses.