Over the last month, life has been very busy. Between dealing with the things that go along with a family and kids and getting a much needed vacation in northern Wisconsin, there wasn't really much time for documenting what's been going on.
Here's a TLDR:
- I added 2 MUX modules using a pair of 74HC138's which control the input and output of the registers; these each have a display of LED's and a 74HC14. (additionally 2 signals are displayed using a NAND as in inverter.
- I added a flag's register that writes to the bus as register 3 on the mux, and has it's own reset signal that is XOR'd with the main reset signal when written two on 3 (any value). Having the XOR package then gave me 3 more XOR gates, I use in the place of traditional OR gates to glue some of the control lines together in situations where a single pulse on one or the other control lies is assured.
- rebuilt the clock module, fixing very silly design mistakes, I also tightened up my reset circuit by using a 555 timer the main slow clock is still built on an inverter tree but the reality of trying to optimize something that should not exist reared it's ugly head so for simplicity sake I went with a very jelly bean 555 reset circuit using a spare inverter.
- added some glue logic to fix some signaling issues brought on by using dissimilar outputs together on a common signal line (I know this is a bad Idea now... I figured it out the hard way).
- added RS232 and a traditional serial port which is driven by the soft uart in the micro-controller
- re-wrote some of the ALU logic to include a carry out bit on the flags register whenever a shift operation is going to overflow, this required removing and re-programming all three chips.
- started and finished a "helper" ATTINY88 Micro-controller that acts as:
- a very tiny stack
- a soft uart
- a few 16 bit static registers I think i will need
- a machine language monitor.
- this chip only provides these functions, and will not do any computation; aside from the stack pointer management (add and subtract 1)
- set up a bus transceiver and select logic to allow the micro-controller to read and write to the bus using its 8 bit wide port, and reside on the bus in tri-state when not active.
- I drew up a vintage looking block diagram of what (generally) the finished CPU will look like.
This was all done in 1-2 hour spurts in my afternoons and during baby naps on my days off from work.
First off, Lets address the monkey in the room:
The attiny88, and what it is doing in a discrete logic CPU build. I thought this over a ton.... The ALU and its support registers + the control logic for the whole CPU is 8 modules (not counting the two side boards for serial and power.) The program counter, address bus management module, and the step counter module along with the state-machine(s) for the rest of the opcodes, not to mention the 32k of ram I am adding is most likely going to (I would like it to) fit on another 8 modules. I could add a discrete UART chip and a clock to control it but this is 2022. We have tiny micros that can be programmed for single task activities to do the same thing in much much less space. Why not use them? the way this micro is programmed it can not do anything save manage the UART, run the monitor and load the registers and ram. The computation will still be done on the CPU itself. There's a little space left over in the micro so I added some register capabilities to shuffle some data around and make the serial interface far easier to use. All of these functions would require more complexity and considerably more time / energy / resources to build. When I set out to build something based off a old chip, I never made it a goal to keep the design language free from modern conveniences, and having a micro-controller to test the data-path at speed is worth wile to me, and so I keep it. So thats that, I guess if that "wreaks" the design aesthetic for you, hopefully I can turn you around to my way of thinking via results later on.
The memory map of the internal "register space" for the MCU is:
- Byte 0 - 00- Future use 8 byte static register
- Bytes 1 - 50 - Stack space, when in this space the register behaves like a stack
- example: if you set the stack counter to 1, and write to it 1 = the data, the stack counter will roll up to the next available space.
- example: if you read from a byte in this range, the counter will decrement by one and the value in the space will zero out.
- Bytes 51 - 52 - 16 bit HL register, behaves exactly like a static 16 byte register, and the pointer must be moved manually to access the register cell.
- Bytes 53 - 54- Jump Register will be used by the microcode state, machine to store / reference the location of the next jump.
- bytes 55 - 56- return register, this space is used by the microcode state machine to store / reference the return address.
- bytes 57 - 59- UART values : 57 - outputs this value next write to terminal action, 58 - next available incoming ascii value or null, 59 - character ready = 1 if yes 0 if no.
This memory range is accessed by referencing register 4, and the stack pointer is read and referenced at register 7. The control unit may use some spare control lines on the MCU to quickly change the stack pointer before address bus reads and writes to make addressable jumps work better, but that has yet to be decided. As it stands I have 4 unused GPIO's on the MCU so there is room to add features if needed.
Additionally the MH-ET dev boards that I am using have a 6 pin ISP port for programming, and powering the build for quick testing. These little things though a bit dated, and sporting a practically useless micro-USB port due to basically non-existent modern OS Driver support, actually are supported by Arduino under Tiny Core. And can be programmed using a mini USB programmer like a USBTinyASP programmer. All of this means I can work on the firmware without popping the chip out to re-program it. This has made programming the firmware substantially easy, even more so than programming the EEPROMS for the ALU.
Clock and reset module enhancements:
The previous design for a reset line driver involving a Schmidt trigger inverter and an RC junction + a Zener diode for some sort of brownout / voltage drop detection, was basically for a lack of a better description poorly thought out. Though this worked through the onset there were massive issues with noise, bread board capacitance. If I had found the issues and done a better job sorting out the other problems with the build of the module Vs. the actual schematics I believe I would have had a much better time with it... but from the onset the Zener was never going to work as I had envisioned.
I wound up using the commonly used 555 timer arrangement in mono-stable mode, triggering the reset with an RC junction at the trigger pin of the timer. Then this signal is hooked to a inverter to buffer the reset line. As it has worked before for countless others (including the developers of the C64). This worked well in my build as well. However, I think if I build another CPU, or looking towards the CPLD version the design will designate / recommend a proper discrete reset circuit with a brownout detection there are many of these livable with / without a watchdog timer. I think that that is probably the best course to take.
The MUX modules:
I used the same inverter / mux arrangement that Ben Eater and others have in order to get 3 byte input / output. this gets combined onto a 6 bit control bus (with two lines left over from a full 8 bytes, one for the ALU enable signal, and another for future use). This allows the input and output of the registers to be controlled with a single port which is a nibble and a half. I used some of the spare space on the module boards for glue logic and the flags register.
The registers addressing are as follows:
- Data bus register
- Control register input / ALU output
- Flags reset (on any write, flags are updated from the ALU), Flags output to bus
- MCU D-Port (various internal memory ranges and registers)
- Low byte address bus parallel load / read ( not built yet)
- High byte address bus parallel load / read (not built yet)
- Stack Pointer (managed by MCU)
The micro-controller has 4 ports, one of them has a nibble and a half exposed to the pins on the dev-board right in a row. this makes it perfectly accessible to use IO register manipulation to control the MUXes from within the bootstrap environment. This allows the micro-controller to take full control of the RAM (once built) and the control bus in order to load the ram, and kick off execution once the ram is staged with code.
The Glue logic / RS232 additions:
The simplest solution is often the correct course of action. As i am actually quite a novice to CMOS logic, I am learning as I go. One of the most recent hurdles I encountered was the effects dis-similar outputs can have on each other, when they share a data line. This is where Glue logic was essential to solving these issues. I amused incorrectly that an output is an output. (this is very far from the truth). Essentially I learned that trying to isolate outputs (with diodes or not at all) rarely works well. Unless all the outputs have some sort of tri-state cut off, you can't share a control line even if you try to isolate it with a diode. Doing so usually means an unwanted floating input at the very least, all the way up to wired oscillation / noise issues that can be very hard to troubleshoot. The solution in this case was simply to add glue logic where i need to control one input with two outputs. In most cases this was with an OR gate, since I needed a way to pass the /RES signal through to the flags register, and also provide a way of driving it low pragmatically, I used an exclusive or gate, so that when the register reset comes in from the inverter passed the input mux, the reset line for only the flags register is driven low. The remaining or operations are pretty much just assured "one or the other" signals, so there is zero likelihood that the XOR will drive low when both inputs are high.
I added RS232 support to simply isolate the RX & TX pins on the micro-controller from a direct connection to source voltages through the GPIO in reverse (which with this dev board ends up with half of a powered on state. This could be for a couple of reasons but i suspect that it has to do with how the GPIO pins are gated. Since the ATTINY88 does not support UART as native. Because of this I had to use a soft UART, which means that the RX/TX pins on the micro are just two GPIO pins which support a pin interrupt, (only a few on the ATTINY88 support pin interrupts but I picked 14 and 13. When linked with a MAX232, I simply get this isolation, and there is no path for the legs to source current backwards; so in this regard the problem is solved. Its also nice to be able to output a more normal serial signal, and I like the thought that even a very old terminal would have no problem loading code to this CPU.
ALU logic re-designs:
I added a few gates in logism (pictured is only the high word 1XXX rom, the low word 1XXX rom is slightly different to catch the LSB and carry out to the high word rom) to add carry out to the logical shift operations. The end result of this is we can now write code that will allow for shifts on binary numbers larger than 8 bits, without having to do extra store and XOR routines to come up with the bits manually. I think this will make the ALU more useful, and since the ALU only does single bit shifts, without this key component it would take massively longer to manually do this operation just using regular logic operations. In order to do this I had to make changes to each one of the truth tables for all three chips. TSV2HEX makes short work of the truth tables but pulling EEPROMs out of a fully populated breadboard was quite a task....
Whats next and conclusion:
I need to work on a test program that runs within the Arduino to test the ALU data path at the very least at 1kHz (the maximum clock frequency). At some point I'd like to build a 1-3MHz fast clock, I don't think this will get done until I have the test program up and working. I will do some simple arithmetic programs and something more complex such as generating prime numbers, or a psudorandom number generation using a user provided seed and a XOR and Shift algorithm.
I really appreciate the feedback I am getting on this project. I like to believe we are all learners and, this community (be it Facebook or hack a day), is a valuable resource for each of us to learn AND teach. Thanks for taking the time to check out my project. I usually have most of my updates on minimalist computing on Facebook, generally under the #UFS4057ES hashtag.
as always if im way off let me know, drop a line here or wherever else I am, have a wonderful week everyone!