I'm preparing the sequel to my book, Sticks, Volts, Monochips. This time I'm going to cover the design of osmibit computers. The book should be published in 2019 again in the CZ.NIC edition.
> When I was thinking about how to structure the book, it was clear that a dry exposition would not be enough. I mean, it would have been enough, but I'm not a fan of theoretical textbooks with no overlap into practice. So it was clear to me that I wanted some kind of construction.
Yes, I could take a real osmibit and show how things are done on it, maybe even entice the reader to build a replica, but I didn't really want to go that route. I figure that on my own construction, which will be as simple as it can be, I can illustrate the required things much better.
So I chose the path of constructing completely simple computers that have to satisfy several points:
- They will use parts that can be bought commercially.
- They will not follow the orthodoxy of 1980s designs – i.e., if I want to use memory, I will use modern, say 128x8bit static RAM, which is one chip, and I will not construct a circuit of eight chips of dynamic RAM and three more IOs all around.
- They will be in the spirit of the old 8-bit days, i.e. simple peripherals – keyboard, display, speaker – and minimal software.
- It will be easy to write software for them.
- I'll happily skip the old processor itself and emulate the necessary functions in a modern single chip. Or use a single chip instead of peripherals. No stress.
So, I sat down and prepared some designs that are suitable for beginners and which explain the principles of building systems with 8-bit microprocessors.
I now offer you the first construction, and as a demonstration I will gradually release a few abbreviated chapters from the manuscript. And if you want to get a head start, check out list of parts for the build.
8080/8085 processor computer design
All you need is a non-soldering contact field, interconnecting wires, a power device and a few components: a processor, memory and some peripherals.
Of course, I'll be honest: in the case of the 8080 processor, this doesn't apply so much. I mean, not that it can't, of course it can, but the problem is that the 8080 processor requires three power supply voltages and also some support circuitry.
For various technological reasons, the 8080 processor does not have some important components integrated on-chip, most notably bus control and clock pulse generation. A pair of external circuits, the 8224 and 8228, are used for this purpose, and although it is possible to dispense with them and build your own alternatives, I do not recommend this approach.
The 8224 circuit is used to generate the clock frequency, in the correct phase and with the correct period ratios, and to synchronize RESET and some other signals. The circuit takes the connected frequency and divides it by nine. This is then the working frequency of the processor. If the connected generator has a frequency of 18 MHz, the 8080 will operate at 2 MHz.
The 8228 circuit generates the full control signals /MEMR, /MEMW, /IOR, and /IOW from the so-called status word that the processor sends over the data bus and several control signals. In addition, it can function as a simple interrupt controller - it has only one level and sends an RST 7 instruction to the bus when a request occurs (i.e., a jump to address 0038h, as we will discuss later).
It is only this holy trinity, as these three circuits are sometimes called, that makes up the "8080 microprocessor" itself.
In the simplest of systems, where there is no need to deal with interrupts or suspend or bus takeover, just connect the unused HOLD and INT inputs to the log. 0, the RDYIN input to log 1 and ignore the INTE, WAIT, HLDA and INTA outputs. The entire processor then communicates with the rest of the system using the four control signals /MEMR, /MEMW, /IOR and /IOW, which tell the processor to read from memory (MEMory Read), write to memory (MEMory Write), or read or write to/from the input/output circuits (Input/Output Read, Input/Output Write).
Your own computer? What might it look like?
Although the 8080 was a widely used "classic", I wouldn't recommend it for a new design - at least because of the three power supplies. I'd reach for the slightly faster and improved 8085 processor, or its CMOS version, the 80C85 (produced by several manufacturers). The 8085 processor makes do with just one supply voltage (standard +5 volts) and does not need special clock generation circuitry (just a simple clock pulse, or even just connecting a crystal with capacitors). In addition, it has two pins for serial input and output and a total of five interrupt inputs - in addition to INTR, a TRAP input (non-maskable interrupt) and three RST inputs (RST5.5, RST6.5 and RST7.5).
Even the 8085 cannot work completely without external circuits: the designers resorted to so-called multiplexed addressing due to the need to fit it into a 40-pin case. The processor has eight higher bits of the address (A8-A15) and the lower 8 bits are routed over the data bus (pins AD0-AD7). The processor sends the address twice. The ALE (Address Latch Enable) signal is used for control. If it is in log 1, it means that the processor sends the lower 8 bits of the address via AD0-AD7. If it is in log. 0, these pins function as a data bus.
Therefore, the system designer must provide an external circuit (8-bit latch) that takes care of capturing the lower 8 bits of the address. Typically, for example, a 74573 circuit can be used (older designs used two 7475 circuits, for example).
Internal structure 8085
At first glance, it does not differ much from the 8080A - even the 8085 has a set of registers B, C, D, E, H, L, accumulator A, Stack pointer SP, program counter PC, ALU, control circuits, controller... In addition, compared to the 8080, the interrupt control part is extended (by RST and TRAP inputs), part of the serial output (SID, SOD) and built-in clock generator.
Below you can see the mentioned split of the address bus into two parts - separate upper 8 bits A15-A8, and multiplex for the lower 8 bits of address / 8 bits of data (AD7-AD0).
The control signals have the following functions:
|A8-A15 (output, three-state)||Top of address. During HOLD, HALT and RESET states, it is in a high impedance state|
|AD0-AD7 (input/output, three-state)||Lower eight bits of the address, or sat bus|
|ALE (output)||Address Latch Enable. Indicates that the lower part of the address (1) is on pins AD0-AD7. If 0, it means that AD0-AD7 is functioning as a data bus|
|S0, S1 (outputs)||Status information. Along with the IO/M signal, it tells if the processor is reading an instruction, if it is reading/writing data to memory, if it is communicating with the peripheral, if it is servicing an interrupt, or if it is stopped. They are not used in simple designs.|
|IO/M (output)||Indicates whether the processor intends to communicate with memory (0) or peripherals (1)|
|/RD (output, three-state)||If 0, the processor reads data from an external device (memory, port). The external device must send the required data to the data bus.|
|/WR (output, three-state)||If 0, the processor wants to write the data it sent on the data bus to the port or memory.|
|READY (input)||In the days when memories and peripherals were slower than the processor, they could send a "wait for data ready" signal during a read or write cycle. The READY input is used to do just that. If it is 1, the processor is running at full power. If it is 0, the processor, after setting the /RD or /WR signal, waits until READY is 1 again before continuing. We'll just plug it in at 1 and trust that modern memories are fast enough...|
|HOLD (input)||Normally 0. When set to 1, the processor completes the most necessary operation (read or write cycle) and disconnects its data bus, address bus, and /RD, /WR, and IO/M signals. By disconnect, I mean switching to the Z (high impedance) state. Once the processor is disconnected, it will acknowledge that it is done with the HLDA signal. Simple systems do not use this signal, you can connect it to the log. 0|
|HLDA (output)||In normal operation 0. If a HOLD request has been made, this signal is set to log 1 after the necessary operations are completed and the buses are disconnected.|
|INTR (input)||Interrupt request. The processor tests the state of this signal at the end of each instruction and also in the HALT (after the HLT instruction) and HOLD states. If it is 0, nothing happens, if it is 1, the interrupt handler process starts. The processor reads the value from the data bus and treats it as an instruction to execute. Most commonly, single-byte RST instructions are sent, but it is also possible to send a three-byte CALL instruction. The sending of the instruction must be taken care of by external circuitry, the so-called interrupt controllers. It is possible to disable the interrupt triggered by this signal with a program.|
|/INTA (output)||Interrupt Acknowledge. When an INTR interrupt request arrives, the processor uses the /INTA output to fetch the above instruction (instead of the /RD signal)|
|RST5.5, RST 6.5, RST 7.5 (inputs)||Direct interrupt inputs. They work similarly to the INTR signal, but don't require all the hassle of fetching an instruction using /INTA. Instead, they internally arrange themselves to jump to the appropriate locations. Like INTR, they can "wake up" the processor from the HALT and HOLD states. The programmer can ignore these signals using a special instruction ("interrupt masking")|
|TRAP (input)||TRAP is similar to the RST x.5 signals, except that this interrupt cannot be masked by the programmer.|
|/RESET IN (input, Schmitt F-F)||Reset input. Upon system startup or crash, external circuitry should switch this signal, which is normally in log 1, to log. 0. This starts the internal initialization process: the processor disconnects the buses, sets the program counter to address 0000h, terminates any possible wait cycles and starts working "from scratch". This input is equipped with a Schmitt flip-flop circuit, which means that it is possible to connect directly to it a popular circuit with a resistor and a capacitor, which will take care of RESET when the supply voltage is switched on.|
|RESET OUT (output)||Indicates that the processor is in the RESET state. This signal is synchronized with the clock and can be used as a system RESET for the rest of the system.|
|X1, X2 (inputs)||Used to connect the clock crystal. Its frequency is internally divided by two and the result gives the system clock. The X1 input can also be used as a clock signal input from an external circuit.|
|CLK (output)||System Clock. Its frequency is half the frequency of the X1 input (i.e. the frequency of the connected crystal)|
|SID (input)||Serial input. The value on this pin is loaded into the high bit of the accumulator by the RIM instruction.|
|SOD (output)||Serial output. The value on this pin is set or reset by the SIM instruction.|
|Vcc||Power supply voltage. Nominally 5 volts, can be anywhere between 3V and 6V for CMOS versions.|
In the datasheet (there are many different ones from different manufacturers, I would recommend the OKI datasheet, their chip was called MSM80C85AH) you will find a very important graph that shows the waveforms on the bus. Let's take a look at it together:
The first line shows the clock signal. M1 is the first machine cycle, T1 to T4 are its clock cycles. Thus, when fetching an instruction, the processor controls the outputs as follows:
- T1: Sets the upper part of the instruction address to outputs A8-A15, the lower part to outputs AD0-AD7, and also activates the ALE (Address Latch Enable) signal. This signal controls the aforementioned buffer outside the processor, whose job is to remember the lower part of the address. It also sets the IO/M signal to 0, i.e. "memory communication". The /RD and /WR signals are inactive, we do not need to worry about the status signals. With the falling edge of the clock at time T1, the ALE signal is deactivated and the lower part of the address remains captured in the buffer.
- T2: The processor switches the AD0-AD7 signals to data input and activates the /RD signal, which informs the surrounding system that the processor intends to read (/RD) from memory (IO/M is at log 0). The memory is connected to the bus and the address is selected.
- T3: With the rising edge of the clock, the processor reads the state on the data bus and starts evaluating it as an instruction. It then deactivates the /RD signal so that the memory can be disconnected again.
- T4: The processor decodes the instruction.
Suppose the instruction needs one eight-bit parameter. This is stored, as usual, at the following address. This is followed by an M2 machine cycle, during which data is read from memory. If you look at the schematic again, you can see that it is almost identical to the M1 cycle, except that it lasts only three clock cycles - the "decode" clock is removed.
If an event occurs in the system that requires the processor to attend to it immediately (a fire breaks out, a user presses a button, new data arrives, ...), it triggers a so-called interrupt. In the 8085, three mechanisms are used to do this.
The first is identical to the 8080 - an external device sets the INTR signal, the processor reads an instruction from the interrupt controller and executes it. Unsurprisingly, this is a subroutine jump instruction (for programmers used to higher-level languages: something like a handler invocation, function call, etc.) External circuitry, usually specialized circuitry called interrupt controllers, must take care of sending the instruction correctly.
In the 8080, this complexity could be simplified by using the 8228 circuit - by connecting one of the pins (INTA) via a 1K resistor to a 12 volt voltage, this circuit became a simple "interrupt controller" that, when an interrupt request arrived, sent the value FFh to the bus, which is the value the processor decodes as an RST 7 instruction. (RST 7 stores the current address on the stack and does a bounce to address 0038h, but more on that later). The 8085 doesn't have this facility, so you either use an interrupt controller or make do with the internal RSTx and TRAP inputs.
The second way is to use the RSTx inputs. Their function is similar to what I described for the 8228 circuit a paragraph above - they generate the jump instruction themselves. It's not quite literal, because the mechanism is slightly different, but the principle is the same.
The RST 5.5 and RST 6.5 inputs (I guess we can safely call them five and a half and six and a half) are, like the INTR input, periodically checked, and if they are in state 1, the processor raises an interrupt.
The RST 7.5 input, unlike the previous ones, has a built-in internal flip-flop circuit that responds to the rising edge. The advantage is that only a short signal is needed and the processor will "hold" the interrupt until it tests the interrupt inputs.
The third method is the TRAP signal. All of the previous interrupt methods can be disabled by the programmer, "masked" by the DI (Disable Interrupt) instruction, and enabled by the EI (Enable Interrupt) instruction. If interrupts are disabled (DI), the processor ignores the INTR and RST signals. TRAP is an exception; this signal will trigger interrupts even if they are disabled. This is why it is also called a non-maskable interrupt.
What happens if multiple interrupt requests come in at once? The processor has a simple priority mechanism built in, see the following table, and executes a handler for the request with the highest priority.
|Signal||Priority||Directory where the handler is stored||Called by...|
|TRAP||Highest (1)||0024h||Rising edge or log 1 during testing|
|RST 7.5||2||003Ch||Rising edge (delayed)|
|RST 6.5||3||0034h||Log 1 during testing|
|RST 5.5||4||002Ch||Log. 1 during testing|
|INTR||Lowest (5)||According to the instruction sent||Log 1 during testing|
8085 processor wiring
This is what the heart of the computer will look like. Let me just make a few comments:
In the role of the address buffer, the IC2 is a 74573 type circuit (whether in HC, HCT or ALS version). Functionally, it is identical to the 74373, except for one difference: the 373 has the pins swapped, the 573 nicely has all the inputs on one side of the case and the outputs on the other. The clock input is connected to the ALE pin, I hardwired the OE enable input to ground. I could connect it to the HLDA pin to provide address bus disconnect in the HOLD state, but our computer won't use that state.
We won't use HOLD, READY, or interrupt, so these inputs are connected to neutral (READY is 1 - still ready, HOLD and interrupt to 0). Of the outputs, I did not use status S0, S1, RESET OUT, and logically neither HLDA nor INTA. Likewise, the serial interface remained unconnected - the SID input is connected to ground, the SOD output is free. You can connect an LED to it via a resistor and try to blink, for example...
There are two circuits to the left of the processor. Below is a clock pulse generator with a crystal and two capacitors. I chose a crystal with a frequency of 3.6864 MHz. Why? Why not 4 or, I wonder, 27?
First, the frequency must not be less than 1 MHz. It says so in the datasheet. Second, it shouldn't be higher than 10 MHz. That's what it says. Somewhere around 4 MHz is ideal, because the processor then runs at a pleasantly slow frequency of around 2 MHz, roughly how the original 8080A ran.
But why 3.6864 MHz? Well, because then the processor will run at 1.8432 MHz, i.e. half as fast.
Okay, but why not 4 MHz?
Well, because 1843200 Hz / 96 = 19200 and 1843200 Hz / 192 = 9600. Is it getting light? Right, 19200 and 9600 are standard frequencies for serial communications. We can easily derive them from 1.8432 MHz by dividing by an integer. If we wanted to get these frequencies from, say, 4 MHz, we would have to divide the clock by, say, 208.3333 or 416.6666, and there would be errors in the transmission. So I'd rather spare some power here, knowing that it will make further wiring easier.
And one last thing: capacitors at the crystal! They are important to keep the crystal oscillating at the correct frequency. You can either calculate their capacitance or read them from the datasheet. I read 56 pF from the datasheet, which seemed like a huge capacitance. I put in 33 pF, and even that was too much. In the end, I left only one 33 pF capacitor at the X2 input. Then it turned out that the 56 pF was from the datasheet of the CMOS version and I had used NMOS, where capacitances of around 7 pF are recommended at these frequencies.
There is a circuit for /RESET above the clock generator. Using a capacitor to ground and a resistor to supply voltage, when the power is turned on the /RESET IN input is a logic 0 until the capacitor slowly charges. You can either calculate or experimentally test the appropriate ratio of resistance to capacitance. I don't think you can go wrong with a 10K resistor and a 10M capacitor. Give the system some time to start, as the voltage still fluctuates for a while after the power is turned on. Thanks to this trick, the processor is still in the RESET state at that time, and when it boots up, the voltage is already stable.
Even further to the left is the button that connects the input to ground. It's the magic RESET button that brings our computer to its senses if it ever accidentally forgets itself and ends up in an endless loop, for example. Thanks to the capacitor, even the kinks are filtered out...
So the processor is taken care of. It communicates with the outside world using the address bus, data bus, IO/M, /RD, /WR signals and the CLK clock signal. That's really it, that's all you need for a simple computer.
Comments powered by Talkyard.