Try it right now: set the switches to c700
(type it on your keyboard if you can't convert hexadecimal to binary in your head), then press the RUN switch or AltR. Read on for more details. Read on for more details.
You've seen all those images of the CFT front-panel (Coming Real Soon Now). You've seen screenshots of unit tests running, Forth executing exotic code, even the microcode. You've seen screenshots of the CFT emulator. But so far, you've never had a chance to play with a CFT processor yourself. It's time to remedy this, so here's a CFT emulator and Front Panel trainer rolled into one, with a bonus ROM full (for very, very sparse values of ‘full’) of goodies. What's best, this emulator isn't the C-based one. Oh no. This one runs in your browser.
Very, very slowly. On my computer, using the latest version of Chrome, it runs at about 250 Hz. The CFT is intended to run at 4 MHz, so the emulator runs 15,000 times slower than the actual processor. But this is a good thing because now you can see all those blinkenlights1 at proper Hollywood-style speeds.
This emulator requires a large-ish screen to use. It probably won't work on mobile devices or old browsers. It was tested on Chrome 24 and 25 (and above), Firefox 19 (and above) and Opera 12, but will work with any reasonable version of these browsers with varying degrees of eye candy. It does not currently work on Internet Explorer and I'm not even working on it. Drop me a comment below if you need it to run on Explorer. I reserve the right to laugh at you, though.
1. Obligatory Bullet Points — We Have Them
Here's a list of the features available:
- Full CFT processor emulation at the microcode level.
- 48 kWords of RAM, 16 kWords of ROM.
- Seven panel-oriented test programs already in ROM, or toggle in your own!
- Full programmer's control panel, except the power and panel lock switches.
- Debugging board (partial emulation, to capture processor output and state).
- Various light decoding aids.
And here's what we don't have.
- No Memory Banking Unit.
- No Interrupt Controller Unit.
- No serial ports, printers, graphics, sound, bells or whistles.
In short, you're getting the glorious CFT processor as the Elder Gods intended: unadulterated, unexpanded, without bloat or feature creep. You'll find it's still pretty capable.
2. Future Versions
This version of the emulator is meant to demonstrate the microcode and front panel, and for that we need to be graphics-intensive and run at the microcode level, clock tick by clock tick. A better approach would be to implement instruction-level emulation, which will easily be a couple of orders of magnitude faster, approaching the speed of the real thing. It won't have a front panel, but it will have a serial console and may be able to run ROM Forth.
3. Programmer's Front Panel Crash Course
All computers used to have these once upon a time. These days, some explanation is required. You can use the Programmer's Front Panel (PFP) to operate, inspect, program and debug the machine. The CFT's PFP shows pretty much all of the computer's internal state from the microcode level up.
3.1. Lights
There are loads. If you're overwhelmed, set the ‘LTS’ switch to ‘OFF’ to extinguish the less used lights. A bunch of these are permanently off because the hardware that would drive them isn't emulated. Others are off and unlabelled because they haven't been assigned functions yet. Don't worry about them. Here are the really important ones.
- Program Counter
- Shows the address of the next instruction to be executed.
- Accumulator
Shows the value of the Accumulator (AC register), the main register of the machine. All the magic happens here.
- Output Register
- Shows the value of the Front Panel's Output Register, which is used to output results to the user. To display this register, set the white ‘OR/DR/µADDR’ switch to the ‘OR’ position (up — this is the default).
- Run/Stop
- Shows whether the computer is running or stopped. Many panel switches only work with the computer stopped. The computer only works when it's running, of course.
- Fetch/Execute
- Next to the Run/Stop lights are the Fetch/Exec lights. When Fetch is lit, the computer is reading a new instruction from memory. When Exec is lit, it's executing the last instruction it fetched.
3.2. Switches
There are three groups of these. Computer and panel control switches on the left, the Switch Register in the middle, and programming switches on the right. Here's a quick description of the important ones.
- Reset/Start
- Push up (or press Alt+Shift+R) to reset the computer, push down (or press Alt+Shift+S) to reset it and start it if it was stopped (this is the same behaviour as the ‘reset’ button on PCs).
- Run/Stop
- Push up (or press Alt+R) to start the computer, push down (or press Alt+S) to stop it. The computer can stop itself by operating the panel programmatically, and you can also stop it to examine its state. You can use the panel to program and debug the computer when it's stopped.
- Fast/Slow/Creep
- Sets the computer's clock speed. This switch behaves differently to the real thing: in fast mode, the lights don't update as often, and the processor runs at around 250–900 Hz (the real CFT runs at 4 MHz). In slow and creep modes, the lights update all the time. Slow is good for watching code execute, creep is good for watching microcode execute.
- Light Switch
- The LTS ON/OFF switch disables most of the panel's lights, which is useful when not debugging. This emulator also runs slightly faster in LTS OFF mode.
- OR/DR/µADDR
- Controls what's displayed on the Multi-Function Display, the middle of the fourth row of lights.
- The Switch Register
- The 16 switches in the middle of the front panel are used to input values to the computer. The panel uses them for programming, and the computer reads them to find out the user's intentions. When a switch is up, the corresponding bit value is
1
, otherwise it's0
. You can enter values using the hexadecimal keys 0–9 and A–F. Each key press sets four switches at once (highlighted in red). You can use Backspace to go back, but the focus wraps around to the leftmost digit after the rightmost digit is entered. - SR→PC
- With the computer stopped, push down (or press Shift+:) to set the Program Counter to the value of the Switch Register.
- →AC
- With the computer stopped, push up or down (or press =) to set the Accumulator from the Switch Register.
- MEM W/W NEXT
- With the computer stopped, push up to store the write the Switch Register to the memory location indicated by the Program Counter lights. Push down (or press Shift+Enter) to do the same and increment the PC, ready for the next value to be deposited. This is the switch most used when storing programs or data in memory. Note there are another three switches for reading memory and writing and reading from I/O space that work in exactly the same way.
3.3. Visual Aids
Many lights and switches are decoded to help you enter values. The decoding appears next to the register name in dark grey (hover over the register to make it brighter). Values like 0000
are in hexadecimal, sometimes followed by a CFT machine code disassembly of the same value. Decimal values may be shown as something like (42)
after that.
4. Playing with the Emulator
When you first load the emulator in your browser, it will run briefly then halt with all Accumulator lights off and all Output Register lights on. I use this as a prompt. Set the switch register to an address by toggling it in manually or pressing, e.g. C700and press ‘RUN’ (Alt+R). The address identifies one of the programs in the ROM. Each program behaves differently.
4.1. Address C000: Reboot
This is the code that shows the prompt and waits for an entry address. You can get there by simply pressing ‘START’ (Alt+Shift+S) at any time. The program halts, then (when the computer starts again), reads the value of the Switch Register and jumps to it.
4.2. Address C100: Counter
Counts up from zero to the value of the Switch Register, which can be changed while the program is running. The Output Register displays the current count. When the count is reached, the computer stops. Resuming by pressing the ‘RUN’ switch (Alt+R) starts counting all over again.
4.3. Address C200: Adding Machine
This is a simple adding machine with printout (on the Debug Terminal, shown below the front panel). Key in a number using the Switch Register and press the ‘RUN’ switch (Alt+R) to add it to the running total. The running total is displayed in the Output Register and printed out on the Debug Terminal. The total is displayed modulo 65,536 since the CFT is a 16-bit machine.
4.4. Address C300: Echo
The Accumulator and Output Register follow the value set on the Switch Register in real time. This program never stops.
4.5. Address C400: Rolling Lights
Upon entry, the program halts, waiting for a bit pattern to be set on the Switch Register. Try 1111
. Once this is done, press the ‘RUN’ switch (Alt+R) and the Accumulator and Output Register will roll this pattern to the left. The gap in the pattern is due to the ‘L’ register participating in the rolls as a 17th bit.
4.6. Address C500: Fibonacci Sequence
This program prints out (and displays on the Output Register) the first numbers of the Fibonacci Sequence: 0, 1, 1, 2, 3, 5, etc. Only numbers that fit in 16 bits are displayed, so the calculation will stop with 46,368.
4.7. Address C600: Eratosthenes' (Prime Number) Sieve
This program prints out prime numbers calculated using Eratosthenes' Sieve, an ancient algorithm, but very effective for generating shortish lists of primes. Upon entry, the program prints out (on a line of its own) the maximum integer to be tested, which is 30. Then, prime numbers are printed out one by one, as they are found, separated by spaces.
The maximum number of primes can be set manually by modifying the value at location 0017
(hexadecimal), and then entering the algorithm at address C602
. To do this:
- Press ‘STOP’ (Alt+S). The machine stops (if it wasn't already).
- Key in
0017
on the Switch Register (manually, or by pressing 0017 — ensure the first digit focused is the right one). - Press ‘→PC’ (Shift+:). The PC is set to
0017
. - On the Switch Register, toggle in the maximum number to test. Remember that the number is entered in hexadecimal.
- Press ‘MEM W’ or ‘MEM W NEXT’ (Shift+Enter). The memory location
0017
is set to the value you toggled in. - Press ‘START’ (Alt+Shift+S).
- Key in
C602
on the Switch Register (manually, or by pressing C602 — ensure the first digit focused is the right one). - Press ‘RUN’ (Alt+R). The calculation starts.
You can generate any number of primes up to a maximum of 45,056 (the beginning of the ROM). Unless you have a lot of free time, I recommend sticking to numbers less than a few hundred. Remember, this simulator runs at around 250 Hz.
4.8. Address C700: Hello World
The Standard Program. No input is expected. Output is sent to the Debug Terminal, but the ASCII/UCS character codepoints are also shown on the Output Register as they're printed. The program halts on termination, but can be restarted by pressing ‘Run’ (Alt+R).
5. Questions I'll Never Have to Read
In time honoured fashion, here's a NAQ (Never Asked Questions) list:
5.1. Why are so many lights off?
Some have never been designated a function. The version 4 front panel is smaller by 2U than the version 3, but it has space for a good 48 more lights, left there for future expansion and debugging of the actual beast. These lights are usually in the peripheral section, to the right of the register display and above the programming toggles.
Some other lights are supposed to be driven by hardware that isn't present. The Memory Bank Unit (MBU) isn't included here. It's part of the processor boards as constructed, but it's not necessary for the processor to function and we have more memory than we'll ever need for a panel-programmed computer. This includes the ‘MBEN’ light, which is on when the MBU is enabled and remapping addresses.
Likewise, the Interrupt Controller Unit is absent, and its lights are undriven and off. In fact, there's no hardware that generates interrupts on the Javascript emulator.
5.2. What do the Rightmost Two Toggles Do?
Nothing in this version.
The second switch from the right controls the memory map of the computer at boot time, before the MBU is enabled. In the ‘RAM BNK’ position, all 64 kWords of memory are RAM. In the ‘ROM BNK’ position, the upper 32 kWords are ROM. This is all done by the MBU, which isn't installed in this virtual computer. So the switch does nothing and the mapping is hardwired: 48 kWords of RAM, 16 kWords of ROM.
The rightmost switch issues Interrupt Requests at levels 1 and 6 when pressed. This is meant to input simple asynchronous signals to the processor via the front panel, but it requires the Interrupt Controller Unit which (shock!) isn't installed. So the switch does nothing.
5.3. In Fast Mode, the Lights Don't Make Sense. What Gives?
Well spotted. In Fast mode, there are 10 processor ticks for every update of the front panel lights, so some status changes aren't shown. If you have a really quick eye, you can see the PC jumping around where there are no jump instructions. Switch to a slower mode and the panel updates every tick.
5.4. I Need to Know More! Where Do I Start?
The top of the CFT project is right here. There's also an regularly updated design and built log which outlines the hundreds of 180° turns I've made so far (more to come).
For proper documentation, you might try the CFT Book, which contains more information about the hardware and software than you'll ever need (or want) to know. Even if you're me. This is a draft, and full of typesetting mistakes, typos, information in need of review, and the like. But it should be informative nonetheless.
6. Assembly Listing of the ROM
Here's the Standard Assembly listing of the ROM used in the emulator right now:
;;; A test program for the Javascript CFT Emulator.
;;;
;;; Copyright © 2013–2015 Alexios Chouchoulas.
;;;
;;; This program is free software; you can redistribute it and/or modify
;;; it under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 2, or (at your option)
;;; any later version.
;;;
;;; This program is distributed in the hope that it will be useful,
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with this program; if not, write to the Free Software Foundation,
;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
.include "testing.asm" ; Debugging board definitions
.include "dfp.asm" ; Panel register definitions
.include "macro-generic.asm" ; Load some basic helper macros
;;; Fill both RAM and ROM parts of the image with SENTINEL instructions, which
;;; cause the emulator to terminate. This is so we can catch addresses which
;;; don't contain valid code. On the real CFT, the DFP will also log the failure
;;; to any testing computer attached to the serial port.
&0000:
.fill 65535 SENTINEL
;;; Simplify calling puts using a macro
.equ PUTS R &100
.macro puts(addr)
LIA %addr ; Load address
JSR I PUTS ; And jump to subroutine
.end
;;; C000: Entry point. Halts, then jumps to the address toggled in the Switch
;;; Register.
&c000:
start:
.scope
LOAD _aputs ; Make puts available globally
STORE PUTS
puts(msg) ; Print out the boot message
LOAD minus1 ; We're done. AC = &ffff
SOR ; Set all the OR lights on
LI 0 ; Set all the AC lights off
HALT ; Halt and wait for user.
LSR ; Load the switch register
STORE R 1 ; Store it
JMP I R 1 ; And jump to that address
minus1: .word &ffff
msg: .str "Ready. Set addr & press Run.\n" 0
_aputs: .word _puts
.endscope
;;; Note: for the rest of the code, we avoid macros so that the inner workings
;;; of CFT assembly are made obvious.
;;; C100: Counter. Counts up from zero to the value of the Switch Register
;;; (which can be changed while the program is running). The Output
;;; Register displays the count.
&c100:
.scope
count: LI 0 ; mem[&10] = 0
STORE R 10
acloop: LOAD R 10 ; mem[&10]++
INC
STORE R 10
SOR ; Set the OR lights
LSR ; Read the panel switches
XOR R 10 ; Compare to the count (mem[&10])?
SZA ; Different?
JMP acloop ; Yes. Increment again.
SUCCESS ; They're equal. Log success.
HALT ; And halt.
JMP count ; When we continue, run again.
.endscope
;;; C200: Adding machine with printout. Toggle in value to add, then actuate
;;; the Run switch. The running total is shown on the Output Register
;;; and printed out to the debugging terminal.
&c200:
.scope
puts(msg) ; Print out prompt.
LI 0 ; Mem[&10] holds the sum. Reset it to 0.
STORE R 10
SOR ; Clear the lights
loop: HALT ; Wait for user input.
LSR ; Read the panel switches
ADD R 10 ; Add them to mem[&10]
STORE R 10 ; Store it back
SOR ; Set the OR lights to the result
PRINTD ; Print out the result to the DFP.
PRINTNL ; Print a newline
JMP loop ; Go again.
msg: .str "Adder. Set value to sum, press Run.\n" 0
.endscope
;;; C300: Echo. The Accumulator and Output Register follow the value set on the
;;; switches. Extremely simplistic.
&c300:
.scope
loop: LSR ; Read the switches
SOR ; Set the lights to the same value
JMP loop ; Go again.
.endscope
;;; C400: Rolling lights. The program halts. Set a starting light pattern using
;;; the Switch Register and actuate the Run switch. The Accumulator and
;;; Output Register display a rolling lights display using that pattern.
;;; The added 17th bit (seen as a gap) is because the L register also
;;; participates in the rolls, but isn't set from the initial pattern.
&c400:
.scope
puts(msg) ; Print out prompt
CLA CLL ; Clear both A and L PDP-8 style
SOR ; Set the OR lights
HALT ; Halt & wait the user to set the pattern
LSR ; Load initial pattern
roll: RBL ; Roll left through L & AC lights
SOR ; Set OR lights too
JMP roll ; Keep rolling
msg: .str "Set initial pattern, press Run.\n" 0
.endscope
;;; C500: Fibonacci sequence. The Output Register shows the Fibonacci sequence.
;;; The sequence is also printed out on the debug terminal.
&c500:
.scope
.equ n1 R &10 ; First number
.equ n2 R &11 ; Second number
.equ tmp0 R &12 ; Temporary
again: CLA CLL ; Clear AC and L, PDP-8 style.
STORE n1 ; n1 = 0
LI 1
STORE n2 ; n2 = 1
loop: LOAD n2
STORE tmp0
ADD n1
SCL ; Is L set?
JMP done ; Yes. We've run out of bits, so stop.
SOR ; Set the OR lights to the current value
PRINTD ; Print it out to the DFP console
PRINTSP
STORE n2 ; Store it for the next iteration
LOAD tmp0
STORE n1
JMP loop ; And keep going
done: SUCCESS
HALT
JMP again ; Keep running as long as the user wants.
.endscope
;;; C600: Eratosthenes' Sieve. Prime number generator. Shows primes < 30.
;;; The maximum number (30) by default is first printed out on the debug
;;; terminal, on a line of its own. The primes are then printed out as
;;; they are found, separated by spaces. They are also shown on the
;;; Output Register. On the Javascript emulator, the calculation is slow
;;; enough that the numbers can be read out from the OR.
;;;
;;; To calculate a different range of primes, write the maximum integer
;;; needed to location &0017 and start the program from location &C602.
&c600:
.scope
.equ ONE R &0F ; Constant 1
.equ I0 R &080 ; Autoincrement register
.equ x R &010 ; Count register
.equ pos R &011 ; Current position
.equ posptr R &012 ; Pointer to value at current position
.equ prime R &013 ; Last prime found
.equ neglimit R &014 ; The last number (negated, for subtracting)
.equ tmp R &015 ; Temporary register
.equ tmp2 R &016 ; Temporary register
.equ count R &017 ;
eratosthenes:
;; Prepare the pad
LOAD sieve_cnt ; Size of working memory
STORE count
puts(msg)
LOAD count
PRINTD
PRINTNL
NEG
STORE x ; Prepare for clearing the working memory
LOAD sieve_start ; Autoindex pointer
STORE I0
LOAD count ; Used for limit checking
NEG
STORE neglimit
LI 1
STORE ONE
;; The first prime we report, 2, is found at initialisation time.
;; Since it's the prime that takes the most to mark (half of the
;; pad has to be marked!), we join this step and the pad init
;; step and start looking for primes at 3.
LI 2 ; Report 2
SOR
PRINTD
PRINTSP
sieve_clear:
LOAD x
AND ONE ; Heh. EBM reference.
XOR ONE ; Thus we initialise the entire table for prime=2
STORE I I0
ISZ x
JMP sieve_clear
sieve_init:
LI 2 ; pos = 2 (to be incremented soon)
STORE pos
ADD sieve_start
STORE posptr ; posptr = &pad[pos]
sieve_next_prime:
ISZ pos ; Next position
JMP @+1
ISZ posptr
JMP @+1
LOAD pos ; Past the end of the working memory?
ADD neglimit
SNA ; pos + neglimit < 0?
JMP sieve_done ; The algorithm is done!
LOAD I posptr ; Consider pad[pos].
SNZ ; pad[pos] == 0?
JMP sieve_got_prime ; Yes. We found a prime.
JMP sieve_next_prime ; No. Loop again.
sieve_got_prime:
LOAD pos
SOR
PRINTD
PRINTSP
sieve_mark_mult:
ADD pos ; Next multiple of pos
STORE tmp
ADD sieve_start
STORE tmp2
LOAD tmp
ADD neglimit
SNA ; pos + neglimit < 0?
JMP sieve_next_prime ; No. Done with the pad, find next prime.
;; Within limits. Mark as non-prime and loop.
LI 1 ; pad[x] = 1
STORE I tmp2
LOAD tmp
JMP sieve_mark_mult
sieve_done:
SUCCESS
HALT
JMP eratosthenes ;; If the user continues here, restart.
sieve_start:
.word &1000
sieve_cnt:
.word 200
msg: .str "Prime numbers up to " 0
.endscope
;;; C700: Hello world program. Prints out The Standard Message on the debug
;;; terminal.
&c700:
.scope
hello: LIA msg
STORE R &80
loop: LOAD I R &80
SOR
SNZ
JMP done
PRINTC
JMP loop
done: SUCCESS
HALT
JMP hello
msg: .str "Hello, world!\n"
.str "This is a microcode-level CFT emulator written in Javascript! "
.str "It's a lot slower than \nthe real thing, but it works."
.str "The emulator contains a small ROM of sample programs."
.str "Read the description below \nfor a list of programs and "
.str "instruction on how to use them. The source of the ROM "
.str "is also available below.\n"
.str 0
.endscope
;;; A basic subroutine to print out a message.
;;;
;;; AC: address of message.
;;;
;;; Side-effects: Clobbers index register &00FF.
_puts:
.scope
STORE R &ff ; Store the string address
loop: LOAD I R &ff ; Load next character
SNZ ; Is it zero?
JMP done ; Then we're done.
PRINTC ; Print it to the debugging terminal
JMP loop ; And go again
done: RET ; Return
.endscope
;;; System vectors
&fff0:
JMP I boot
boot: .word start
;;; Just in case interrupts are enabled by mistake (and an interrupt somehow
;;; triggered), the ISR simply disables interrupts again.
&fff8: RTI ; Interrupts do nothing but disable interrupts
;;; End of file.