Kenbak-uino an Arduino-based emulation of the "KENBAK-1" by Mark Wilson (kiwimew at gmail dot com) 1 Introduction -------------- Not long after discovering the Arduino it seemed to me it could be a fun project to re-create an early computer, one with just LEDs and switches. I looked at things like the Altair 8800 (1975) but it has 30+ LEDs and 20+ switches and seemed like too much work. Then I stumbled on the KENBAK-1 (1971). Perfect! Only a dozen LEDs and 17 switches. As a bonus it was the 30th anniversary of it's introduction. I found a reasonable amount of information online, starting at the Wikipedia article, http://en.wikipedia.org/wiki/Kenbak-1. This is a software emulation of the KENBAK-1's CPU, and method of operation, together with a basic recreation of the hardware. 2 Operation ----------- The basic operation is to enter values with the 8 bit switches on the left. CLR clears them all. The bits toggle by default. Pressing (ADDR) SET sets the address register to the displayed value. Pressing STOR writes the displayed value into the memory at the address, and increments the address. In this way programs can be entered. The KENBAK as only 256 bytes of memory and no registers as such (location 0 is A, location 4 is the program counter etc). DISP displays the current address. READ reads the memory at the address. STRT starts the program running, STOP halts it. STOP+RUN single-steps. For more details refer to the online information, for example the scans of the original manuals. 3 The Build ----------- I went with an over-all scale in keeping with small LEDs and small buttons. I dispensed with the power and lock toggle switches. I found a suitable enclosure in keeping with the over-all scale. I prototyped the circuits for driving the LEDs with 74HC595's and the switches with 74HC165's. I also prototyped the "daughter" board that would hold the LEDs and switches at right angles to the main board. To keep the proportions right, the LEDs and switches needed to be quite close together so in the end I used two parallel boards to squeeze the components together and as an aid to keeping everything, particularly the LEDs, "square". I aimed to keep the design fairly "modular", the main board and LED/Switch boards are separate and connect via a header strip. At this early stage I did very little coding, other than the emulation of the CPU. Once I was convinced I had the hardware right, I wired it all together (a lot of work, 500+ solder joints), then squeezed it into the enclosure and tested all the buttons and LEDs, amazingly, everything worked first time. Then I spend a lot of time on the software, particularly the MCP which controls the CPU and implements the extensions, like sample programs, listed below. The samples were very challenging too, it's been a long time since I worked in assembly language! An ASCII schematic of the circuit can be found in PINS.H of the sketch. 4 Extensions ------------ I've added a few extensions to the basic KENBAK-1 behaviour. Extension #1 SysInfo Instruction --------------------------------------------- The KENBAK-1 instruction set includes 3 NOOP (no-operation) op-codes: 02Q0, 03Q0 and 031R+[Second Byte] (Q=0..7, R=3..7). I've chosen a particular value of the latter, 0360, to implement an "operating system" SysInfo extension rather than no-operation. (I use 0300 when a real NOOP is required.) The execution of any of the NOOPs is handled by the virtual method virtual bool OnNOOPExtension(byte Op); on the CPU class. On the base class it does nothing (just returns true to indicate execution can continue). On the Sketch's derived ExtendedCPU class it traps the 0360 NOOP and execute the SysInfo function as follows: The value in the A register sets the operation: if the high bit is set, the operation is a "write" otherwise it is a "read". The remaining 7 bits provide the Index of the item. The argument for a write comes from the B register. The result of a read is placed in the B register. Note that some writes actually perform an *action* and the corresponding reads do nothing. Executing the SysInfo instruction resets the CPU speed. The first 8 values for the Index read/write the DS1307 RTC registers (see http://datasheets.maxim-ic.com/en/ds/DS1307.pdf). Numbers are BCD. 000: Seconds (00..59) 001: Minutes (00..59) 002: Hours (00..23) (always 24-hr, no matter how the RTC is configured) 003: Day (01..07) 004: Date (01..31) 005: Month (01..12) 006: Year (00..99) 007: Control No validation is performed. The next 8 values read/write bytes to the subsequent 8 bytes of "user" RAM in the DS1307: 010: Flags controlling the Kenbak-uino. Currently only 1, if b0 is set, pressing one of the Data switches *toggles* the bit, otherwise it only sets it (as per the KENBAK-1). 011: EEPROM Page Map See Extension #5 EEPROM. The value of this byte defines how the 1k of EEPROM is partitioned into 8 pages starting with #0 of 256 bytes. Bits in the Map indicate if subsequent pages should be half the size, a 1 means halve, a 0 leaves the size as-is. The least significant bit applies to page #1 -- if it should be half the size of #0 (i.e. 128 bytes). Thus for example, a Map of 012 creates the following page sizes: 256, 256, 128, 128, 64, 64, 64 000 creates 4 full-size pages (higher pages are ignored): 256, 256, 256, 256 012: User #1 013: User #2 014: User #3 015: User #4 016: User #5 017: User #6 These 6 bytes are available for reading and writing non-volatile values. The final 6 Index values act as follows: 020: Control LEDs Reading does nothing. Writing sets the control LEDs as follows: b0:INP b1:ADDR b2:MEM b3:RUN The upper 4 bits control the intensity of the RUN LED, 0000 = max brightness (PWM=255), 1111 = min (PWM=16). 021: Random A read returns are random byte, 0..255 in B. Writing a 0 seeds the random number generator using the time etc. Writing a non-0 value uses that as the seed. 022: Program delay milliseconds Reading does nothing. Writing delays execution for the given duration. This is separate from the "CPU Speed" (Extension #7 below.) 023: Serial Reads or writes a byte from the Serial port (@38400baud) 0177: Reading this special value simply returns 0 in Register A, indicating that extensions are enabled. Writing does nothing. The remaining extensions use multiple button-presses to perform special actions when a program is not running. Note that STOP+RUN single-steps, as per the KENBAK-1. Extension #2 Blank ----------------------------------------------------------- Pressing STOP+CLR (i.e. Press STOP and without releasing it press CLR) turns off all LEDs. Extension #3 Erase ----------------------------------------------------------- Pressing CLR+STOR sets all memory to 0 *except* 03 (P register) which is set to 04. The address register is set to 04, ready to enter a program. The CPU speed (BitN-STOP, Extension #7 below) is set to 0. Extension #4 Library --------------------------------------------------------- Pressing STOP+BitN loads one of eight pre-defined programs: N Description -- ----------- 0: Simple counter 1: Pattern 2: Counting Clock 3: BCD Clock 4: Binary Clock 5: Das Blinken Lights (random pattern) 6: Sieve of Eratosthenes 7: Set RTC See 5 Programs below. Extension #5 EEPROM ---------------------------------------------------------- Pressing BitN+STOR writes program memory to EEPROM at "page N". Pressing BitN+READ reads program memory from EEPROM at page N. The ATmega328 has 1k of EEPROM, by default this is divided into 8 "pages" of various sizes: N Size -- ---- 0: 256 1: 256 2: 128 3: 128 4: 64 5: 64 6: 64 7: 64 Program memory is always read from or written to starting at address 000. Thus Bit7+STOR writes the *first* 64 bytes (addresses 000 through to 077) of program memory to EEPROM address 960. Bit0+READ reads all 256 bytes of program space (addresses 000 through to 0377) from EEPROM address 0. Note that EEPROM memory which has not been written to will be read as 0377 (Unconditional JUMP AND MARK INDIRECT). The page sizes can be adjusted using SysInfo Index 011 EEPROM Page Map. Extension #6 SystemInfo ------------------------------------------------------ Pressing STOP+READ executes a SysInfo read. The Index is taken from the Address register, the result is placed in the Output register (0200). Pressing STOP+STOR executes a SysInfo write. The Index is taken from the Address register (there is no need to set b7), the argument is taken from the Input register (0377). All SysInfo calls are available programatically and from the front panel but some make more sense that others (for example Delay from the front panel). Extension #7 CPU Speed ------------------------------------------------------- Pressing BitN+STOP sets the "CPU speed". It sets the delay in milliseconds added after each CPU cycle, equal to 2^N ms. Thus b0+STOP sets the delay to 1ms. b7+STOP sets it to 128ms. The delay is set to 1 at power on and on CLR+STOR (Extension #3 Erase above) of if a program executes the SysInfo instruction, 0360. Not done --------------------------------------------------------------------- It would be really useful to move the Input and Flag bytes (0200-0203) up alongside the Output byte (0377) to provide a larger contiguous block of memory. See for example the Sieve program which has to work around this. Deprecations: I don't do the "dim" display of entry values while running No need to Start/Stop at power on. Memory is zero'd at power on. 5 Programs ---------- "Classic programs", these don't use any extensions: ---Count: Simply increments the OUTPUT register. Will be a blur unless slowed-down with BitN+STOP. ---Pattern: Cylon-style single LED moving left-right-left etc. Will be a blur unless slowed-down with BitN+STOP. ---Sieve: Builds a table of the odd numbers up to 255 and applies the Sieve of Eratosthenes to them. Then display the primes, HALTing after each one (hit START to proceed to the next one). "Clocks", these use System extensions to try to do something useful with the Kenbak-uino. Note that they all display the time in 12-hour format with no AM-PM indicator: ---Count Clock: Alternates between showing the hours and the minutes. The Control LEDs are turned off. The hours are displayed thus P_ HHH HHH The number of H's is the hour, plus 6 if P is lit. Thus (where '*' denotes a lit LED & '_' unlit) -- --* *** is 4 O'Clock *- --- *** is 9 O'Clock etc The minutes are displayed thus B_ MMM MMM The number of M's is the minutes times 10. If the leftmost lit M is blinking, it means +5 minutes. B is always blinking. Thus (where '!' denotes a blinking LED) !- --* *** is 40 minutes after the hour !- --- !** is 25 minutes after the hour This is the easiest clock to actually read the time. ---BCD Clock Scrolls back and forth between showing the hours and the minutes in BCD. The minutes display has the leftmost (b7) LED blinking. The Control LEDs are turned off. Thus -- --- *-- is 4 O'Clock -- -*- -*- is 12 O'Clock !- *-- --* is 21 minutes after the hour !* -*- *-* is 55 minutes after the hour ---Binary Clock Shows the time in binary. The minutes are shown on the Data LEDs with the leftmost (b7) LED blinking. The hours are shown on the Conrol LEDs. Thus !- -*- *-* * - - * is 9:21 !- **- *** * * - - is 12:55 ---Set Clock This sets the time: the hours (BCD, 24-hour format) are in A, the minutes (BCD) are in B. --Das Blinken Lights Just blinks all the LEDs, old-school.