Preface

Howdy, I'm Wraithan McCarroll, I greatly enjoy tinkering with things. This is my documentation for my 8-bit computer based on Ben Eater's Kit and related videos. I'm starting this documentation part of the way through the build, mostly because I took a break from working on this computer after having a few modules built, then had a hard time understanding what I'd already built.

In doing some more research for this project, I came across rolf-electronics build documentation which made me realize I should do the same, especially as I've already dabbled in deviating from the original build.

When doing builds of sorts I tend to have rules. They're arbitrary and mostly serve to keep the project interesting to me. They're not super strict but here they are:

  1. The first build of the CPU should be largely the base that Ben Eater designed.
  2. Anything that lives on the CPU is 8-bit or less.
  3. Modules can't be added to the CPU until they've been tested on their own.

Build as of 2021-03-15

The 8-Bit Delight Build

Current build state (excuse the picture was taken quickly):

Build as of 2021-03-15

Left Side:

  1. Clock
    1. Potentiometer chooses speed, left faster, right slower
    2. Momentary button steps clock if manual stepping is enabled
    3. Switch button switches between auto and manual stepping
  2. Inputs
    1. Yellow Button, instruction register output to bus
    2. Red Button, instruction register input from bus
    3. Red button, clear memory, hooked up to instruction register, and ALU register A and B
    4. Switch bank for setting bus lines. On is high, off is disconnected and relies on bus pull-down resistor
  3. Instruction Register
  4. Teensy++ 2.0 Debugger Module

Right Side:

  1. Open
  2. ALU Register A
    1. Yellow button output to bus
    2. Red button input from bus
  3. ALU Register Output
    1. Has a long yellow wire for output to bus, low is output, high is disconnected, Blue button is switches to subtract while pressed.
  4. ALU Register B
    1. Yellow button output to bus
    2. Red button input from bus

Clock

ICs

From left to right:

  • LM555 timer as an oscillator
  • LM555 timer as a monostable debouncer
  • LM555 timer as a bistable debouncer
  • SN74LS04N hex inverter used both for signal inversion and signal buffering
  • SN74LS08N quad dual input AND used for clock source selection and halt
  • SN74LS32N quad dual input OR used for clock source selection

Notes

This clock is both neat and could be stripped down a bit. I'll definitely keep the 555 used as an oscillator because I really enjoy that style, but I might switch to a simpler RC low pass filter for the debouncing. Similarly it isn't hard to rework the logic ICs down to just two of them, Ben Eater did this in the video with NAND gates.

I've altered the build from the base schematic by taking the inverted clock signal and passing it through another inverter to functionally buffer the clock signal. I did this based on the u/lordmonoxide What I Have Learned post, but reading that section on the clock section, they mention swapping the hex inverter with a schmidt trigger based inverter, which I don't believe I have any spares of in LS right now, just CMOS CD40106 which wont play well with the TTL of the LS chips. I'm considering swapping to CMOS logic eventually, but will likely be going to the 74HC series logic, not the 4000 series logic.

The clock module is a prime candidate for turning into a soldered protoboard version, both because then I can reduce noise and keep the clock more stable, as well there not being many features I can think of adding to the clock, just simplifications.

ALU / Registers

Register A and B ICs

From left to right:

  • SN74LS245N 8-bit bus transceiver that can read 8 bits off the bus or put them on.
  • SN74LS173AN 4-bit D register is for the MSB
  • SN74LS173AN 4-bit D register is for the LSB

ALU ICs

  • SN74LS245N 8-bit bus transceiver that can write 8 bits to the bus, but cannot read.
  • SN74LS283N 4-bit adder with carry used for MSB
  • SN74LS283N 4-bit adder with carry used for LSB
  • SN74LS86N Quad dual input XOR gate used for subtraction for MSB
  • SN74LS86N Quad dual input XOR gate used for subtraction for LSB

Notes

Currently this aspect of my build is a mess using a lot of stranded wire jumpers instead of cut to size solid core. Since I've been running the CPU again I've managed to find/fix all the lose wires in this section but it needs to be upgraded to cut to length. This section has the most control signals out of any part of my CPU so far, IO for both registers, output for the ALU register, and subtract mode. Before I go about cleaning up these modules, I want to have some basic tests written for the QA module that will ensure that when I replace all the wires it works at least as good.

Notably on this design of the ALU, the MSB carry bit is ignored. I know some processors have access to detect if an overflow happened during a math operation. Not sure how I'd use it or where I'd route it, but it intrigues me.

Instruction Register

ICs

From left to right:

  • SN74LS173AN 4-bit D register is for instruction to be decoded in the MSB
  • SN74LS173AN 4-bit D register is for the address in the LSB
  • SN74LS245N 8-bit bus transceiver that can read 8 bits off the bus, or put the 4 LSB on the bus.

Notes

For the initial build where I'll be sticking with the 16 instructions and bytes of RAM limitation, this likely wont get any real changes until I expand the CPU.

Quality Assurance Module

This is my way of making sure my CPU works. I'm a software engineer and throwing some software at this problem doesn't seem like the most absurd thing. The QA module is something that is not at all part of the base build. As such I selected the Teensy++ 2.0 since it uses an 8-bit Microchip AT90USB1286 AVR microcontroller. While it isn't period correct, it does stick to 8-bit or less for anything that lives on the board.

It currently only has some basic firmware for showing the value on the bus via serial, and only reading that value on the clock pulses. Eventually I'll want this to be able to take over for the clock or other control signals to be able to isolate the a given module and run unit tests on it. I'm estimating that I'll need 2 breadboards worth of space for this between shift registers and the logic gates.

I also want to add a small display to the board, for debugging purposes. A 16x2 LCD like this one from Adafruit would look more appropriate but is big and relatively low density. One of these OLED displays like the Monochrome 0.96" 128x64 or this Color 1.27" 128x96 would give me more pixel density, the second one even giving me an SD card which might be a good way to store the unit and integration tests. I have a fair selection of displays around my house so I'll play with a few soon and decide if I need to pick up a new display or can use one I already have.

The 6502 Delight Builds

Making 6502 computers with a delightful experience.

What does it mean for a 6502 computer to have a delightful experience? Well, that of course comes down to the eye of the beholder. For me it means caring about my ergonomics as I develop and eventually use these computers. For example, before I even completed building Ben Eater's kit, I'd already customized the build to have in situ ROM reprogramming and extra debug capabilties.

Breadboard 6502

Based on Ben Eater's kit, known in the 8-bit/homebrew communities as a BE6502. This project originally started as a way to have a computer that was just a step above the 8-Bit Delight build I was doing so I could write tests for the 8-Bit Delight and verify it was working after I did reworks or added modules. I found as I dove into the 6502, there was a bunch of cool stuff to explore and my 8-Bit Delight fell by the wayside. As I build this up, I have in situ ROM reprogramming and debugging via the Delightful CLI and the Delightful Debugger (hardware portion).

Backplane 6502

My first pass at a "Desktop PC" 6502. A backplane is a board with a bunch of connectors on it where the vast majority of functionality is added via cards inserted into the connectors. For my purposes this means I can break my computer up into a set of modules that are each replacable, that way if I have to change how the processor is wired up, I don't have to spin a whole new large board with expansion connectors. This will make it easier to take breadboard prototypes and turn them into soldered ones with more reliable and predictable connections.

Laptop 6502

This idea started when I replaced my Kinesis Advantage keyboard and realized it'd be fun to modify the case and build a 6502 computer into it. Toss a battery inside and include a screen and you have a mobile computer. My dream for this project is to be able to take my own homebrew computer, running my own OS and software stack, to a cafe for coffee and hacking.

Breadboard 6502

Based on Ben Eater's kit, known in the 8-bit/homebrew communities as a BE6502. This project originally started as a way to have a computer that was just a step above the 8-Bit Delight build I was doing so I could write tests for the 8-Bit Delight and verify it was working after I did reworks or added modules. I found as I dove into the 6502, there was a bunch of cool stuff to explore and my 8-Bit Delight fell by the wayside. As I build this up, I have in situ ROM reprogramming and debugging via the Delightful CLI and the Delightful Debugger (hardware portion).

Memory Map

This memory map is inherited from the BE6052. It prioritizes ease of implementation on a breadboard with logic gates, while still being simple to conceptualize. One of my next iterations will include a GAL or PLD to be able to keep the number of ICs down (and thus have 1 place to about propegation delays) while being able to reprogram it as I adjust the memory map, this was also a prevailing technique on home computers that used the NMOS 6502.

Important pins:

  • A0-A3: No block is smaller than 16bits, so these are shared by all blocks.
  • A4-A13: If A14 is high and A15 is low, IO Device select, only 1 pin should be high in that case. Very wasteful but easy to wire up.
  • A14: If A15 is low, RAM / IO selector, low = RAM, high = IO
  • A15: everything else / ROM selector, low = everything else, high = ROM
BinaryHexDescription
0000000000000000 - 00000000111111110000 - 00ffRAM - Zero page
0000000100000000 - 00000001111111110100 - 01ffRAM - Stack
0000001000000000 - 00111111111111110200 - 3fffRAM - Free Usable RAM
0100000000000000 - 01000000000011114000 - 400fIO 00
0100000000010000 - 01000000000111114010 - 401fIO 01
0100000000100000 - 01000000001011114020 - 402fIO 02
0100000001000000 - 01000000010011114040 - 404fIO 03
0100000010000000 - 01000000100011114080 - 408fIO 04
0100000100000000 - 01000001000011114100 - 410fIO 05
0100001000000000 - 01000010000011114200 - 420fIO 06
0100010000000000 - 01000100000011114400 - 440fIO 07
0100100000000000 - 01001000000011114800 - 480fIO 08
0101000000000000 - 01010000000011115000 - 500fIO 09 - ACIA for external UART
0110000000000000 - 01100000000011116000 - 600fIO 10 - VIA for LCD
1000000000000000 - 11111111111111118000 - ffffROM

Visual version of the memory map, top is page-wise based on first 16bit block, second is bit-wise with a page (256 bits) per row. This really shows how ineffcient the current memory map is.

Visual version of the memory map

Backplane 6502 Build

My first pass at a "Desktop PC" 6502. A backplane is a board with a bunch of connectors on it where the vast majority of functionality is added via cards inserted into the connectors. For my purposes this means I can break my computer up into a set of modules that are each replacable, that way if I have to change how the processor is wired up, I don't have to spin a whole new large board with expansion connectors. This will make it easier to take breadboard prototypes and turn them into soldered ones with more reliable and predictable connections.

Goals

Have a soldered together 6502 computer that is connected to the RA8875 display, as well as a serial connection. Input via PS/2 keyboard and SEGA controller. Oh and definitely has blinkenlights.

This is meant both to be my desktop 6502 that will live on my bench, but also the platform for me to prototype the peripherials and software for my Laptop 6502.

Pin Map

Still working this out... there is a symbol in the schematics/ directory of one potential layout.

Currently thinking 2 card edge connectors, 50 pins, 25 on each side for the first one, 30 pins, 15 on each side for the second. It's a lot of pins but it will allow me to expand over time without worrying that I'll have conflicts.

Laptop 6502

This idea started when I replaced my Kinesis Advantage keyboard and realized it'd be fun to modify the case and build a 6502 computer into it. Toss a battery inside and include a screen and you have a mobile computer. My dream for this project is to be able to take my own homebrew computer, running my own OS and software stack, to a cafe for coffee and hacking.

Design Notes

Keyboard

I picked the AT89S52 MCU because it has an MCS-51 or 8051 core. That core came out in the mid 80s, a bit after early late 70s early 80s adoption of the 6502 and derived variants. The MCS-48 is a bit more contemporaneous with the 6502, and was used in keyboards and other IO devices to provide serial (more common for detachable keyboards, speaking something like PS/2) or parallel (more common for integrated keyboards mapped directly to an internal IO port). Unfortunately for hobby folks ICs with that core are a bit less common, but the 8051 core which is very similar, and was used for teaching assembly in some universities, has a wide variety of ICs that implement it with some extra features, like a built serial interface. Another big problem is a lot of MCS-48 core ICs are One Time Programable or OTP.

Notably the Kinesis Advantage 1 that I'm starting with had an AT89S8253, which is similar to the chip I'm using but more advanced.

FeatureAT89S52AT89S8253
Programable Memory8KB12KB
EEPROM (Runtime Storage)02KB
InterfacesUART (simple)UART (fancy), SPI
Clock0-33MHz0-24MHz

Additionally the AT89S8253 has a few more interrupts features, lower power sleep mode features, and a wider electrical range. The program will hopefully not be a problem, I have a hard time imagining using all 8KB, even if I use sparse data tables for lookups, the keyboard is a 15x7 matrix, which if padded out to 16x8 is just 128 bytes. The loss of an EEPROM is the most tangible, having it built in and mapped into the address space makes it easy to store configration. The 8051s do support external data or program memory, but that is much more complex to wire up, likely needing to use a few bus tranceivers to swap between EEPROM access and normal operation. I may need some subset of that tranceiver setup anyway to support ISP (In System Programing) so depending on how far that goes, adding EEPROM may not be a challenge.

Years ago, originally giving me the idea to mod my own kinesis, was a blog from David Whetstone Hacking the Kinesis Contoured Keyboard. He documented that it used a 15x8 matrix, then provided an optimized matrix that doesn't require rewiring any of the individual keypads bringing it to 15x7, just how they're combined together for the MCU to read. My theory on the 15x8 is that they dedicated a whole "port" of the AT89S8253 for reading from the keyboard so optimizing the matrix a bit better had little benefit.

In tearing down my Kinesis I noticed it used two address decoders, SN74LS138, they take in 3 address pins, using them to drive 8 output pins. The address pins are basically interpeted like binary, selecting one of the 8 output pins to be driven low, while the rest are held high. They even have extra enable/diable lines that make it easy to chain them together without extra logic, so you can combine 2 of them to make a 4 to 16 decoder. Meaning you can use 4 pins to scan through all 15 rows of the matrix, and still have a spare. Using this technique I end up with 4 pins to drive row selection, 7 pins to read the column of buttons or a minimum of 11 pins to read the 15x7 matrix. I'll almost certainly follow this same address decoder path but swap in 74HC138, since the majority of my build is already CMOS.

The 16th row will be available to me whether I want to use it or not, and reading from a port at a time is how you do things in 8051, so I'll program my firmware for the AT89S52 to scan all 16 rows and read in 8 wide columns. What will I do with this embarassment of riches that is 15 + 8 = 23 extra inputs? Extra momentary buttons are easy, switches get more complex because I'll either need to periodically send the value, regardless of change state, or send it as part of a handshake with the OS. A rotary encoder is also tempting, but that means implementing quadrature encoding. Paul Stoffregen has an optimized teensy library that has nice documentation around Quadrature Encoding including an interactable diagram. Similarly a potentiometer would be cool but requires analog reading which is a completely different ballgame than digital stuff mentioned so far.

Why optimize down the number of pins used for the keyboard matrix? Firstly for fun! After that it should make it easier for me to leave the pins needed to do ISP exposed without extra logic. Additionally, since I'll already be using 2 address decoders that have an enable line, putting a bus tranceiver in front of the column reader port would make it easy to fully disconnect the keyboard matrix from the MCU using a single signal pin. I'll need to backhaul data back to the computer itself, more pins I can leave free, the more options I leave myself.

Since this is for a laptop build, a parallel interface seems nice, I don't have to worry about the size or fragility (physical or electrical) of a cable with a ton of wires in it, Using 8-10 pins still leaves me with 8-10 pins for other output like LEDs or to make ISP easy by leaving all the pins for it free. 8 pins for the data bus, possibly 1 for rW so the host can indicate it wants to write to the device. Possibly another to generate an interrupt that a byte is ready to be read.

Another option is to go with a serial interface anyway. The 8051s pretty much all have serial IO, the AT89S52 has 4 serial modes, the 0th mode is a shift register mode that clocks a bit per 12 clock cycles the processor goes through, meaning it takes 96 clock cycles to send a byte. The 6522 VIA I use in my 6502 build for IO has a serial interface as well, which I can configure to interrupt the processor after 8 shifts aka a whole byte has been read or written. The shift register setup only needs 2 pins, data and clock. This is close to what PS/2 keyboards do, which use 9 bits per message, the last one being a parity bit to ensure the byte was transfered correctly, since I'm not doing long wiring, skipping the parity bit seems safe enough. To take another notion from PS/2, the keyboard will drive the clock typically, but the host can request to send a message by asserting the clock line.

If I go with serial, and my other choices before that, I'll have 4 pins to drive the matrix, 8 pins to read from it, 2 to talk with the host, which is 14 pins, out of 32 available to me. If I didn't want to have output LEDs, I could probably cut down to the AT89C4051s I picked up which only have 15 IO pins instead of the 32 of the AT89S52, but instead I'll have 18 pins for LEDs and other stuff. Supposing I have the spare program space, some of the above uses of the spare entries in the keyboard matrix that seemed a little unreasonable there could be implemented with direct access to pins, such as rotary encoders or an ADC so I can read a potentiometer. EEPROM for configuration, like remapping keys, is also a thing I could use my spare pins for. For now I'll just enjoy the space I've created and look forward to actually getting my shipment with AT89S53 in it and starting to program them.

Custom Tools

Throughout the build I'll of course be building my own tool chain to support my 6502 Delight Computer and 8-Bit Delight CPU

Delightful CLI

This is the Command Line Interface that talks with the Delightful Debugger I've written to run on a Teensy++ 2.0

Delightful Debugger

Delightful Debug Protocol

This is the protocol used over USB Serial between the Delightful CLI and the Delightful Debugger. Typically home grown USB Serial wire formats are newline delimited text streams. But, since I didn't want to have to Base64 encode binary payloads, especially from the debugger to the client, I went with a custom binary protocol using CBOR as the payload data format.

Currently a packet is laid out as such:

  • First 2 bytes are a u16 that describes the length of the payload of the message
  • Payload of the message serialized as CBOR

Assuming we know what byte to start on, this lets us read the first two bytes, get the length to read from the buffer, then keep reading until that many bytes come out. When messages come in too fast (and so more than one in a chunk) or are broken up across multiple chunks, being able to tell how much of a stream to read lets us handle it.

Unfortunately this assumes:

  • data will always be aligned exactly correctly, with no spurious data on the line.
    • This is already disproven because for some reason, sometimes after boot the first byte the teensy gets is a 0, then it starts reading the data I sent, causing it to misread the length of the first message and not be able to find the proper alignment.
  • the contents of the message never has transmission errors
    • Need to understand how much assurance about the data I get from USB Serial
    • If there is a protocol error, even if due to developer bug, restart of the serial connection is required.

If there are transmission errors, retransmit is one way to handle it. Also trying to find the start to another message because all messages that exist currently start their CBOR with 0xa1 (no body) or 0xa2 (has body), and the first key of that body should be "kind", but that assumes a few more things (like never nesting messages or using "kind" in other ways in the data model). In an acknowledged world, where the a side wants to be sure one operation has finished before trying to send the next command, messages need to be able to be distinguishable. One way to do that is to keep a message counter that should increase for every message sent. When an acknowledgement is required, just that sequence number is needed. Also then when a number is skipped, whether a transmission error was detected or not, those missing messages can be requested.

For retransmit to work, this means holding onto the sent messages for some period of time, and resending them as requested. If messages were out of order somehow, we might get a request for a message that is already in a buffer to be sent and we'll send it again. If clients hold onto the last message number they processed they can ignore ones from before that as already processed, within some range in order to handle rollover.

CBOR Array base

A potential future for the protocol is not putting that length outside of the message, but instead requiring messages to start with 0x84 which 0x80 is "array" and the 0x04 of it means 4 elements. First element would be an unsigned number that is the length of the message. Realistically that'll be 0x18 aka u8 or 0x19 aka u16, maybe in the most extreme case 0x1a aka u32, but I don't plan to sweep more than 50% of the 16bit address space in a given send, given the Teensy only has 8192 bytes of RAM I'd have to be streaming the response. It isn't hard to manually parse this much of a CBOR message to decide how much to buffer before trying to decode the rest of the message. Also for generation I have to include a placeholder, I could just make that placeholder 0xffff which means it'll always be [0x84 0x19 ...] to start the message.

Next element of the array would be the message counter, limiting that to a u16 means it'd start with 0x18 aka u8 and 0x19 aka u16 then reroll over when it hit the cap. Again this could be manually parsed, then we could more eagerly attempt to request a retransmit, but I'm not sure that's really that useful.

The last element of the array would be the CBOR Pair aka Order Map object that holds "kind" and "body" mentioned above.

Another feature I need is whether this message needs acknowledgement, I'll reserve a boolean for this.

[
    0x001c, // length
    0x1283, // message number
    true,   // needs acknowledgement
    {
        "kind": "Monitor",
        "body": false // optional/any type
    }
]

Turns into the following CBOR

84                      # array(4)
   19 001c              # unsigned(28)
   19 1283              # unsigned(4739)
   F5                   # primitive(21) aka true
   A2                   # map(2)
      64                # text(4)
         6B696E64       # "kind"
      67                # text(7)
         4D6F6E69746F72 # "Monitor"
      64                # text(4)
         626F6479       # "body"
      F4                # primitive(20) aka false

Resources

All of these resources I've gone through and meaningful changes happened to my CPU and I take inspiration from.

Ben Eater

The man, myth, legend that started the process for me. I watched his video The world's worst video card? and was hooked by the idea of building up from logic ICs. I'd watched a few of the 8-bit breadboard computer series before, but the VGA card really sparked my desire to also build some of this stuff.

  • 8-bit breadboard computer playlist has the full SAP-1 build using TTL logic via the 7400 LS series logic. My build is almost entirely built off this guide so far.
  • 6502 based computer playlist I built this over the weekend of March 27th, 2021, then proceeded to start modding it with in situ EEPROM programming. You can find code related to this in the 8-bit-delight repo. I'll add writing about it soon.

r/beneater

This is the subreddit for those work toward building the various projects that Ben Eater has put on his channel, then extending them. Lots a great resources available here and seems pretty welcoming.

Other builds

  • rolf-electronics The-8-bit-SAP-3
  • Wilson Mines Co. has a whole 6502 Primer section
    • The Circuit Potpourri Base of this and the Ben Eater builds are functionally identical. Goes through various ways to extend it, where I understood using the 6522's shift register capabilities for some display logic I needed.

Tools

  • Volker Barthelmann vasm Assembler I've been using for 6502 stuff. Recommended by Ben Eater in his video series.

CBOR

CBOR is the binary serialization used by the Delightful Debug Protocol and this could be useful if doing your own dev with CBOR or are just curious.

  • YACL is a C++ Arduino platform CBOR (de)serializer. Used by Delightful Debugger.
  • serde_cbor is a Rust crate for using CBOR with serde it has a longer history than others, and a variety of active contributors but is undergoing a significant rewrite. Until that rewrite completes I decided to use ciborium.
  • ciborium is another Rust crate for using CBOR with serde, this is a newer crate that didn't want to wait for the serde_cbor rewrite to complete along with some differing takes on CBOR Tag support. Delightful CLI currently uses this as it's CBOR (de)serializer.
  • CBOR Playground is an online tool that takes "Diagnostic notation" and hex binary notation of CBOR data and translates between. Has been invaluable when debugging my own code and understanding exactly what the libraries were outputting.
  • CBOR RFC 8949 the spec for CBOR rendered as HTML helpful for understanding the exact data model, as well as considerations to make when serializing or creating protocols based on the data format.

Future

Resources that look useful down the line, whether that's more computers I want to build, upgrades I think look neat but haven't dove into the details, or programming related assembly and related tools.