- TypeScript 87.3%
- JavaScript 12.7%
| examples | ||
| src | ||
| test | ||
| .gitignore | ||
| ARCHITECTURE.md | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| VERIFICATION.md | ||
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:
- 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.
- 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).
- 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 -
OUTadds parity,OUTPdoes not, which is the opposite of what the mnemonics suggest). - 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 (seesrc/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 --noEmitis 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 onTape.input().src/devices/monrocard.ts: medium-high confidence for the opcode-relevant read/write/eject/seek-timing state machine (transcribed fromTe/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 - seetest/typewriter.test.tsfor 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:
- Read
ARCHITECTURE.mdin full; it's short and is the real spec. - Use
src/core/cpu.tsas the reference implementation when writing the C++ core - its structure (one method per opcode class,fetchOperand/storeOperandas the universal register-or-drum indirection) maps fairly directly to a C++ class. - 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.
test/cpu.test.tsis 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.