This document is a short introduction to programming and using the MV 950 emulator, a program that simulates a Metrovick 950-compatible computer, the first commercial computer using transistors (1956). The simulation is not perfect, but if you figure out how to use this program, you will only need a troubleshooting manual and a time machine to program and operate the real machine.
If you feel lost, please check other documentation files in this folder, as well as built-in help for the emulator.
Thanks to the latest advancement in digital electronics, Metrovick 950 is a very compact machine. It will barely take more space than a couple of average-sized wardrobes, and it only requires 2 kilowatts of power (about as much as three NVidia GTX 4090 Ti). The standard package includes a teleprinter, a tape punch, and a CRT that can display the contents of the computer's memory.
Metrovick 950 operates on 32-bit integer numbers and can store up to 4096 of them on a magnetic drum. The mean access time for a memory address is 10 milliseconds (100 Hz), but the computer allows you to access memory much more efficiently through programming. This means MV 950 is capable of reaching and even surpassing 200 operations per second.
MV 950 is a stored-program computer. This means not all 4096 words of memory are available for data; some of them will be required to store the program. However, this is actually great: if your program or data doesn't fit on the memory drum, you can offload a part of it on the paper tape, and then load it back when you need it again.
The computer is mostly operated through the control panel. The control panel shows the numbers stored in the computer's register memory. It also has Hand Switches that can be set to any 32-bit value. Hand Switches can be written into the Accumulator or Instruction registers.
The most important register of MV950 is the Instruction register. The control logic surrounding the Instruction register is also the most notable difference between magnetic drum computers and other types of computers. Computers with semiconductor or core memory often have a register called "Instruction Pointer" or "Program Counter". A computer with a PC register would read memory from the address stored in the PC register, loads the data from it into the Instruction register (completely invisible to the programmer), execute it, and increment the value of the PC register by 1. Metrovick 950 doesn't work like that.
There is no "Instruction Pointer". Instead, there is an Instruction register, directly accessible to the programmer (or the computer operator). A number stored in the Instruction register is the command that the computer will execute, not a pointer to it. In the case of Metrovick 950, the command contains four components: Function Digit, B-store Digit, address S1 and address S2.
1234, then 1234 is going to be the S1 part of the command.Note: the manual for Metrovick 950 also mentions 4 bits of "Engineering Digits", but those are not accessible to the user.
There is little benefit in using the teleprinter for small programs; it is much easier to input numbers into the computer using Hand Switches and read the output from the contents of the registers displayed on the CRT. Thus, a very useful first program is a program that loads a number from Hand Switches to a fixed address.
The program will perform only two commands:
The emulator comes with a simple "autocode" program, also known as "assembler", but in the interest of education, it is worth converting this simple program into the machine code manually. We will store the first command at the memory address 0, and the second at the address 1. You can find the FD numbers for commands the MV 950 supports in the "Report on Metrovick", "Original Specification" or the assembler manual.
| # | FD - Command | BD | S1 address | S2 address |
|---|---|---|---|---|
| 0 | Load H.Switch - 19 | 0 | 100 | Next command - 1 |
| 1 | STOP - 21 | 0 | 0 | 0 |
Naturally, the entire program needs to be converted into 32-bit words. Note: on a real physical Metrovick 950 the program words need to be entered backwards, but the MV 950 emulator simplifies this aspect of operation.
| # | FD | BD | S1 | S2 | Hexadecimal |
|---|---|---|---|---|---|
| 0 | 10011 | 000 | 0000 0110 0100 | 0000 0000 0001 | 98 064 001 |
| 1 | 10101 | 000 | 0000 0000 0000 | 0000 0000 0000 | a8 000 000 |
Now let's power up the computer, load the program and execute it. Uhm, I mean, let's start the emulator:
$ python3 emu.py
You will see the state of the computer's registers, including the Instruction register and a mnemonic of the command stored in it. "All zeroes" in the Instruction register for the computer means "Clear Accumulator, ignore B-store, the next instruction is at the address 0".
MV950 is ready. Press Enter to perform a single step, or enter a command.
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x00000000 [FD:0 BD:0 S1:0 S2:0] CLR &0 0x0 0x0
>
As you started the emulator without any arguments, its RAM will be empty. This is the normal state for the computer after its magnetic drum was formatted. Worry not, there is no need for an operating system. We can load our program from the computer's control panel. The emulator offers a built-in shell that supplants the control panel, and it is fairly simple to use.
Thanks to the Instruction register, we can input a command into the computer and execute it without the need to store the command in the RAM first. A command to store the contents of the Hand Switches in the RAM is a very useful command that be executed directly.
A command to write to memory address 0 is:
| # | FD | BD | S1 | S2 | Hexadecimal |
|---|---|---|---|---|---|
| 0 | 10011 | 000 | 0000 0000 0000 | 0000 0000 0000 | 98 000 000 |
0x98000000. It is done by typing hs 0x98000000 into the emulator.wi.> hs 0x98000000
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x00000000 [FD:0 BD:0 S1:0 S2:0] CLR &0 0x0 0x0
> wi
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x98000000 [FD:19 BD:0 S1:0 S2:0] HS &0 0x0 0x0
0x98064001> hs 0x98064001
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x98000000 [FD:19 BD:0 S1:0 S2:0] HS &0 0x0 0x0
>
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x98064001 [FD:19 BD:0 S1:100 S2:1] HS &0 0x64 0x1
You can see that the value in the Instruction register has changed to the command we just loaded. This is because we told the computer to read the next command from the address 0. But we are not done yet!
Now, let's repeat the same operations for the second command.
hs 0x98001000 on the Hand Switches - "Store Hand Switches at address 1, then jump 0"wi> hs 0x98001000
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x98064001 [FD:19 BD:0 S1:100 S2:1] HS &0 0x64 0x1
> wi
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x98001000 [FD:19 BD:0 S1:1 S2:0] HS &0 0x1 0x0
hs 0xa8000000> hs 0xa8000000
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x98001000 [FD:19 BD:0 S1:1 S2:0] HS &0 0x1 0x0
>
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x98064001 [FD:19 BD:0 S1:100 S2:1] HS &0 0x64 0x1
The computer is now ready to execute the instruction from the address 0 again.
The emulator allows you to examine any address in RAM. This is very convenient for debugging. Let's confirm that our little first program is stored correctly in the memory by typing p 0 and p 1:
> p 0
[0] = 2550546433 | HS &0 0x64 0x1
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x98064001 [FD:19 BD:0 S1:100 S2:1] HS &0 0x64 0x1
> p 1
[1] = 2818572288 | STOP &0 0x0 0x0
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x98064001 [FD:19 BD:0 S1:100 S2:1] HS &0 0x64 0x1
Now we are ready to execute our little program.
0x138d5. This number is just an example, it doesn't have any significance.Enter twice - until we see the "STOP" command executed.> hs 0x138d5
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x98064001 [FD:19 BD:0 S1:100 S2:1] HS &0 0x64 0x1
>
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0xa8000000 [FD:21 BD:0 S1:0 S2:0] STOP &0 0x0 0x0
>
[STOP command encountered]
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x98064001 [FD:19 BD:0 S1:100 S2:1] HS &0 0x64 0x1
Let's examine the contents of the memory cell 100 to confirm that the number from the Hand Switches was recorded into the computer correctly.
> p 100
[100] = 80085 | CLR &0 0x13 0x8d5
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x98064001 [FD:19 BD:0 S1:100 S2:1] HS &0 0x64 0x1
Success!
The "STOP" instruction we programmed sets S2 to 0. This means the program simply restarts after completion. This means we can set different Hand Switches, and execute the program again by pressing Enter twice. You can execute the program again and again, as many times as you need. That's a stored-program computer for you!
> hs 2989
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x98064001 [FD:19 BD:0 S1:100 S2:1] HS &0 0x64 0x1
>
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0xa8000000 [FD:21 BD:0 S1:0 S2:0] STOP &0 0x0 0x0
>
[STOP command encountered]
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x98064001 [FD:19 BD:0 S1:100 S2:1] HS &0 0x64 0x1
> p 100
[100] = 2989 | CLR &0 0x0 0xbad
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x98064001 [FD:19 BD:0 S1:100 S2:1] HS &0 0x64 0x1
This MV950 toy comes with a simple autocode system, commonly known as "assembler". It is written in Python and thus not self-hosted. The assembler implementation is very simple, and does not perform any optimisations for memory allocation. Therefore, you might want to use a more advanced autocode system on a real Metrovick 950.
Let's take a look at a simple program (001_add.asm). The Function Digits are represented through letter mnemonics. The B-digits can be omitted when unused. Addresses for S1 and S2 can be labels. S2 can be omitted altogether if you want the instructions to be executed one after the other.
;; Add two numbers
CLR ; register A = 0
ADD num1 ; A += [num1]
ADD num2 ; A += [num2]
STOL num3 ; [num3] = A (without the carry)
STOP
:num1
#DW 123
:num2
#DW 456
:num3
#DW 0
Let's compile this program. Normally, the output of the assembler is the copy of the magnetic drum needed to execute the program. For the debugging purposes, the assembler can generate a mapfile - a list of all labels used in the program. The MV950 emulator can make a use of the mapfile.
$ python3 ../host/asm.py -i 001_add.asm -o 001_add.ram -m 001_add.map
The contents of the RAM file is the list of 32-bit words on the computer's magnetic drum:
1
134238210
134242307
939552772
2818572293
123
456
0
The map file stores addresses for the labels - NUM1, NUM2 and NUM3 in our case:
{"NUM1": 5, "NUM2": 6, "NUM3": 7}
Let's execute the program in the emulator. We can type g to make the computer run until it encounters the STOP command. Please use Ctrl+C to terminate the program early if needed.
python3 ../host/emu.py -r 001_add.ram -m 001_add.map
MV950 is ready. Press Enter to perform a single step, or enter a command.
=== A: 0 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x00000001 [FD:0 BD:0 S1:0 S2:1] CLR &0 0x0 0x1
> g
Entering interactive mode. Press Ctrl+C to return to the shell
[STOP command encountered]
[Computer hit the STOP instruction]
=== A: 579 B: [0, 0, 0, 0, 0, 0, 0, 0]
=== I: 0x0000007b [FD:0 BD:0 S1:0 S2:123] CLR &0 0x0 0x7b
>
The computer has completed its calculation. Let's see how many cycles it took:
> c
Performed 5 cycles so far.
We asked the computer to execute CLR, ADD, ADD, STOL and STOP - five commands in total. Our expectation matches the actual number of instructions executed. Nice, isn't it?
Let's confirm that the computer added NUM1 to NUM2 and stored the result in NUM3:
> p NUM1
[5] = 123 | CLR &0 0x0 0x7b
...
> p NUM2
[6] = 456 | CLR &0 0x0 0x1c8
...
> p NUM3
[7] = 579 | CLR &0 0x0 0x243
Now you are ready to write your own programs for the MV 950, and you can do it with ease!
I am glad that you asked! At the beginning of the Crash Course, I mentioned that MV 950 can execute over 200 operations per second with optimal programming (285 op/s, if we were to believe the original report). For a modern computer operator, it can be difficult to put this number into perspective. Modern computer frequencies are measured in GHz, and performance in tens of billions of operations per second.
This, naturally, raises a question: Is there any practical use for a computer that can only run at 285 op/s?
To answer this question, I implemented a simple benchmark. The benchmark program 002_gcd.asm calculates the Greatest Common Denominator for two integers. MV 950 executes this program in 229 cycles. With 3.5ms for an average cycle with an optimal drum format, this program should execute in about 0.8 second. With less-than-optimal programming and 20ms per cycle, it would take about 4.5 seconds to complete.
I have implemented the same algorithm for microprocessor computers, Dragon 32 (using 6809 @ 750 KHz) and Commodore 64 (6502 @ 1 MHz), using built-in BASIC. Both computers had similar performance, with the GCD calculation taking about 0.25s. MV 950 from the mid-1950s programmed in assembly is only 4 times slower than BASIC on microprocessor computers from the early 1980s. Obviously, both Dragon and Commodore could deliver much more number-crunching per clock if programmed in assembly, but they shipped with BASIC because even with BASIC they were fast enough for practical usage.
Even with non-optimal programming, MV 950 can compete with microprocessor systems. IllinoisU BASIC for Intel 8008 (1974) reportedly performs at 20ms per step, and the authors call their BASIC "not a number-crunching giant, but more than adequate".
It probably cannot run Doom in real time, though.
VAR_A is 27776; VAR_B is 1612. The result of calculation (124), after 252 cycles, is stored in VAR_A.
$ python3 ../host/asm.py -i 002_gcd.asm -o 002_gcd.ram -m 002_gcd.map
$ python3 ../host/emu.py -r 002_gcd.ram -m 002_gcd.map
MV950 is ready. Press Enter to perform a single step, or enter a command.
...
> p VAR_A
[17] = 27776 | CLR &0 ADJUST_1 0xc80
...
> p VAR_B
[18] = 1612 | CLR &0 BEGIN 0x64c
...
> g
Entering interactive mode. Press Ctrl+C to return to the shell
...
[Computer hit the STOP instruction]
> p VAR_A
[17] = 124 | CLR &0 BEGIN 0x7c
...
> c
Performed 229 cycles so far.
The assembler language for MV 950 makes programming the computer no more difficult than programming a modern microcontroller. But there are still a few tricks that you should know about.
There is no command for loading a register directly from the command. If you want to load a number into a register, you have to define a constant:
;; Load 1234 into Accumulator
CLR ; a = 0
ADD const_1234 ; a = [const_1234]
; ...
:CONST_1234
#DW 1234
Sometimes the code calls for an unconditional jump. In assembly for modern microprocessors, you probably would use a command like this:
JMP address
MV 950 always has an address of the next instruction in the instruction word. This means you can simply put the address you need in the S2 part of the instruction:
STOL variable address ; [variable] = Accumulator; then JMP to address
The computer does not have a "flag" register; it cannot detect carry, overflows, and so on. Thus, the only check the computer can reliably perform is a "negative" check, indicated by the Most Significant Bit in the Accumulator (or B-store register) set. Thus, the only conditional jump the system can do is "Jump If Negative".
It is possible to simulate other comparisons, of course.
;; Jump if A == B
CLR ; a = 0
ADD VAR_A ; a = VAR_A
SUB VAR_B ; a = VAR_A - VAR_B
JNEG NOT_EQUAL ; if negative, B > A, early exit
SUB CONST_1 ; a = VAR_A - VAR_B - 1. There is no immediate load, so we keep a constant in RAM
JNEG EQUAL ; if negative, A==B
:NOT_EQUAL
; ... [program continues]
:CONST_1
#DW 1 ; just a number "1"
One fascinating aspect of MV 950 is its fast-access B-store memory. Five types of operations can be done with the B-store:
When you specify a non-zero B-store in an instruction (any instruction), the value of the B-store will be added to the full 32-bit value of the Instruction register. You can use it this way:
LODB &1 OFFSET ; load offset into the B-store &1
STOL &1 1 ; store the Accumulator at the address OFFSET+1
:OFFSET
#DW 4190208 ; 1023 << 12 = 4190208
Keep in mind the 32-bit instruction format (FD, BD, S1, S2) to calculate the correct offset values for the B-store.
If you are ready to embrace the MV 950 assembly, make sure to check out Notes on programming. This document will teach you about the most frustrating aspects of MV 950 machine code and thus will save you a lot of time.