☰

Viktor T. Toth
  • Home
  • Contact
  • Support
  • Notable projects
  • Books and publications ►
  • Technical notes
  • Physics notes
  • AI and machine learning notes
  • ––––––––––––––––––––
  • Physics apps ►
  • AI and machine learning apps ►
  • Miscellaneous apps ►
  • Games ►
  • ✨ GitHub projects ►
  • Legacy products
  • ––––––––––––––––––––
  • Miscellaneous writings
  • My Web sites
  • Blog
  • External links
  • Site news
  1. You are here:  
  2. Home
  3. Notable projects
  4. 4-bit Processor

Support my research and science communication efforts through Patreon.

4-bit Processor

Beware of programmers who carry screwdrivers! Or soldering irons. Experimenting with electronics is a hobby I rediscovered in the late 1990s. By far my most complex project was the design and construction of a 4-bit processor using low-level logic circuits. Although this machine has little practical utility, it serves as a useful educational tool due, in part, to its relative simplicity. Yet despite its simple design, the processor demonstrates many key concepts, including the use of microcode, a two-phase clock for controlling the processor's components, and even a primitive form of instruction pipelining.

Viktor's amazing 4-bit processor - Clock generator

The clock generator circuit of my 4-bit processor uses a series of simple logic components to generate a two-phase clock signal using a crystal resonator. The diagram below show the signal as it appears on an oscilloscope screen at various test points.

vicproc_clk.gif

Viktor's amazing 4-bit processor - Architecture

The following table summarizes the operational characteristics of my 4-bit experimental processor:

Data bus 4 bits
Address bus 8 bits
Memory 256 4-bit word
Accumulator 8 bits
Program counter 8 bits
Data address register (internal) 8 bits
Instruction register (internal) 4 bits
Flags Carry, Zero, Data (external I/O)
Instruction set 16 variable length instructions

The 16 machine instructions are as follows:

Opcode Mnemonic Size Description
0 HLT 1 Halts the processor. Can be restarted manually by pressing a button.
1 LDA nn 3 Load 8-bit value from address nn into AC
2 STA nn 3 Store AC at address nn.
3 JMP nn 3 Continue execution at (i.e., set PC to) address nn.
4 SPC nn 3 Store current PC value at address nn.
5 AND nn 3 Compute logical AND of AC and nn, store result in AC. Set Z flag as appropriate.
6 OR nn 3 Compute logical OR of AC and nn, store result in AC. Set Z flag as appropriate.
7 ADD nn 3 Compute arithmetic sum of AC and nn, store result in AC. Set C and Z flags as appropriate.
8 SUB nn 3 Subtract nn from AC, store result in AC. Set C and Z flags as appropriate.
9 JNZ nn 3 Jump conditional; execute JMP nn if Z flag is not set.
A CMP nn 3 Subtract nn from AC, discard result. Set C and Z flags as appropriate.
B JND nn 3 Jump conditional; execute JMP nn if D flag is not set.
C JNC nn 3 Jump conditional; execute JMP nn if C flag is not set.
D ROL 1 Rotate AC left. Move C to least significant bit of AC. Move most significant bit to C.
E ROR 1 Rotate AC right. Move C to most significant bit of AC. Move least significant bit to C.
F CLF 1 Clear flags C, D, and Z.

The LDA, STA, and SPC instructions are the only instructions that can read, or store, data at arbitrary memory locations. These instructions always operate on two 4-bit machine words at a time.

Other instructions use immediate arguments. That is, the instruction argument consists of two 4-bit words immediately following the single 4-bit instruction word.

This instruction set is obviously Spartan, but it is sufficient to implement any algorithm, memory size permitting. Restricting the instruction set to 16 instructions also greatly simplified design, since there's no need to fetch, and decode, instructions that consist of more than one 4-bit machine word.

Viktor's amazing 4-bit processor - Assembly

Due to the complexity of the Arithmetic Logic Unit, and the way it interacts with the processor's internal registers (accumulator, data address register, and program counter) I developed a wiring diagram that shows how the components of these are interconnected. Signal names on the left are those of the decoded microcode instructions, clock signals, and data/flag lines. Signal names on the right are those of various signals internal to the ALU and registers; the names should be self-explanatory. (E.g., DA AO is the name of the signal that enables, in the data address register, the 74LS244 that places the contents of this register on the address bus.) I also used these symbols when I labelled the chips (seen on the processor's photograph), to make the assembly process less error-prone.

vicproc_wire.gif

The top half of the completed computer, as seen in the picture, contains the TTL logic chips that implement the above diagram, chips that comprise the Arithmetic Logic Unit, plus chips that make up the processor's accumulator, program counter, and data address register.

The bottom half of the computer contains the microcode ROM and microcode decoder, RAM, switches used to program the ROM and RAM, and the CPU clock. A 40-pin connector is used to connect the two halves via a short piece of ribbon cable.

Viktor's amazing 4-bit processor - Memory

The memory subsystem was the first component of my 4-bit processor that actually got built. Initially, my goal was less ambitious; I picked up some surplus 4×256 bit static RAM chips at a local electronics store, and I just wanted to see if I can build a simple circuit to store, and retrieve, data from these chips.

This didn't turn out to be a useless exercise, however. The DIP switches I used to exercise the memory chip are still used to load programs and data into my processor's memory. A planned modification to this circuit will also reuse the DIP switches and one of the LED displays to load, and display, I/O data which will allow my processor to interface with the outside world.

The memory subsystem interfaces with other components via the Data Bus and the Address Bus. The Data Bus is bidirectional; data can be both read and written. The Address Bus is unidirectional; addresses are generated by other processor components and are used by the memory subsystem to select the appropriate memory location for reading or writing.

In addition to the data and address buses, the memory subsystem also receives decoded instruction microcodes. These instruction microcodes control the operation of the subsystem. Specifically, nine instruction microcodes are recognized by this subsystem, decoded into one state and one trigger condition.

Microcodes LIR, LAL, LAH, LDL and LDH cause the subsystem to enter the MRD (Memory ReaD) state. In this state, the contents of memory appear on the Data Bus as output. The address from the Address Bus is used to select the desired memory location.

Microcodes SAL, SAH, SPL and SPH cause the subsystem to execute the MWR (Memory WRite) trigger. On the negative edge of CLK2, the memory subsystem fetches whatever data is on the Data Bus and stores it at the current address that is present on the Address Bus.

Finally, an additional signal (denoted PGM in the diagram below) indicates whether the processor is in its normal, operational state, or in the manual programming state. In the manual programming state, the memory contents can be read, and set, using the DIP switches that are part of the memory subsystem.

mem.gif

Viktor's amazing 4-bit processor - Microcode sequencer

Perhaps the most interesting question with any processor is this: how do instruction codes (i.e., code words in memory) get turned into electrical signals that in turn trigger various events in the processor, altering its internal state?

There are many solutions to this problem, one of which is the use of microcode. While machine instructions are designed with the user in mind (well, inasmuch as machine language can be code user-friendly, that is!), microcode instructions represent internal state changes of the machine itself.

In the case of my 4-bit contraption, each machine code instruction is translated into several microcode instructions. Each microcode "instruction" is really just an electrical signal that, along with the clock signals activates various components of the processor. One of the tricks is the use of a two-phase clock, whose signals look like this:

clkphase.gif

As you can see, the first clock signal transitions from a high to a low state before the second clock signal. When the first clock signal is in a low state, subsystems in the processor respond by presenting information on their outputs, but without altering their internal states. When the second clock signal transitions from a high-state to a low state, subsystems respond by latching whatever data is present on their inputs and alter their internal state.

Designing the microcode set was only possible after I have developed the functional block diagram for the entire processor, and had more than a clue about the way its main components will work. The finalized microcode instruction set consists of 22 microcode instructions:

Opcode Instruction Mnemonic CLK1 (states) CLK2 (triggers)
00 Halt HLT HLT -
01 Load Instruction Register LIR APC+MRD+PCI+INC LIR+LPC
02 Load AC low nybble from [DA]* LAL ADA+SDA+MRD+INC LDL+LDA
03 Load AC high nybble from [DA] LAH ADA+MRD LDH
04 Load DA low nybble from PC LDL APC+MRD+PCI+INC LDL+LPC
05 Load DA high nybble from PC LDH APC+MRD+PCI+INC LDH+LPC
06 Store AC low nybble to [DA]* SAL ADA+SAL+SDA+INC MWR+LDA
07 Store AC high nybble to [DA] SAH ADA+SAH MWR
08 Store PC low nybble to [DA] SPL ADA+SPL+SDA+INC MWR+LDA
09 Store PC high nybble to [DA] SPH ADA+SPH MWR
0A Load PC from DA LPC SDA+TWO LPC
0B Load PC on No Carry PCC SDA+TWO ~C->LPC
0C Load PC on No Data PCD SDA+TWO ~D->LPC+CLD
0D Load PC on No Zero PCZ SDA+TWO ~Z->LPC
0E Store AC&DA to AC AND SAC+SDA+AND LAC+STF
0F Store AC|DA to AC OR SAC+SDA+OR LAC+STF
10 Store AC+DA to AC ADD SAC+SDA+ADD LAC+STF
11 Store AC-DA from AC SUB SAC+SDA+SUB LAC+STF
12 Compute AC-DA CMP SAC+SDA+SUB STF
13 Rotate Left ROL SAC+ROL LAC+STF
14 Rotate Right ROR SAC+ROR LAC+STF
15 Clear Flags CLF - CLF

*Use of square brackets implies indirection (e.g., store at [DA] means store in memory at the address in DA.)

The two rightmost columns tell us what happens in the various subsystems during the two clock phases. I called CLK1 events states (because they alter the output state of subsystems) and CLK2 events triggers (because they trigger an internal change upon the high-to-low transition of the clock signal.)

These states and triggers are essentially internally decoded electrical signals inside each subcomponent. They are described in detail along with the appropriate subcomponent's detailed description.

So now we can translate the 16 machine language instructions into microcode instructions:

Opcode Microcode
HLT HLT,LIR
LDA LDL,LDH,LAL,LAH,LIR
STA LDL,LDH,SAL,SAH,LIR
JMP LDL,LDH,LPC,LIR
SPC LDL,LDH,SPL,SPH,LIR
AND LDL,LDH,AND,LIR
OR LDL,LDH,OR,LIR
ADD LDL,LDH,ADD,LIR
SUB LDL,LDH,SUB,LIR
CMP LDL,LDH,CMP,LIR
JNZ LDL,LDH,PCZ,LIR
JND LDL,LDH,PCD,LIR
JNC LDL,LDH,PCC,LIR
ROL ROL,LIR
ROR ROR,LIR
CLF CLF,LIR

So what does this all mean? Let's take, for instance, the LDA instruction. In plain English, this instruction accomplishes the following:

  • Fetches the next two machine words into the internal Data Address Register, while incrementing the Program Counter
  • Loads data at the address stored in the Data Address Register into the Accumulator

It is translated into five microcode instructions, which in turn cause the following to happen:

Microcode State/
Trigger
Description
LDL APC PC places its current value on the address bus
PCI PC places its current value on ALU's input
MRD Memory places its output at current address on data bus
INC ALU places input + 1 on its output
LDL DA fetches the contents of the data bus to its lower half
LPC PC fetches the ALU output
LDH APC PC places its current value on the address bus
PCI PC places its current value on ALU's input
MRD Memory places its output at current address on data bus
INC ALU places input + 1 on its output
LDH DA fetches the contents of the data bus to its upper half
LPC PC fetches the ALU output
LAL ADA DA places its current value on the address bus
SDA DA places its current value on ALU's input
MRD Memory places its output at current address on data bus
INC ALU places address bus + 1 on its output
LDL AC fetches the contents of the data bus to its lower half
LDA DA fetches the ALU output
LAH ADA DA places its current value on the address bus
MRD Memory places its output at current address on data bus
LDH AC fetches the contents of the data bus to its upper half
LIR APC PC places its current value on the address bus
MRD Memory places its output at current address on data bus
INC ALU places address bus + 1 on its output
LIR SEQ fetches  instruction word from data bus and resets microcode counter
LPC PC fetches the ALU output

The states and triggers are decoded by logic circuits inside the individual subsystems. As you can see, there's a lot of repetitiveness here, but that's precisely the point: the goal here is to reduce relatively complex instructions into simple, repetitive operations that can then be implemented in hardware.

The table below cross-references all 22 microcode instructions with states and triggers in each subsystem.

 

Microcode Instructions

State/
Trigger

HLT LIR LAL LAH LDL LDH SAL SAH SPL SPH LPC PCC PCD PCZ AND OR ADD SUB CMP ROL ROR CLF

Accumulator

LAC/LAC                             X X X X   X X  
LAH/LAH       X                                    
LAL/LAL     X                                      
SAC/                             X X X X X X X  
SAH/               X                            
SAL/             X                              

Program Counter

APC/   X     X X                                
   /LPC   X     X X         X C D Z                
PCI/   X     X X                                
SPH/                   X                        
SPL/                 X                          

Data Address

ADA/     X X     X X X X                        
DAI/                                            
LDA/LDA     X       X   X                          
LDH/LDH           X                                
LDL/LDL         X                                  
SDA/     X       X   X   X X X X X X X X X      

ALU

ADD/                                 X          
AND/                             X              
/CLD                         X                  
/CLF                                           X
CMP/                                            
OR /                               X            
ROL/                                       X    
ROR/                                         X  
/STF                             X X X X X X X  
SUB/                                   X X      
TWO/                     X X X X                
INC/   X X X X X X   X                          
Sequencer
HLT/ X                                          
/LIR   X                                        
Memory
MRD/   X X X X X                                
/MWR             X X X X                        

An interesting side note about this design is that despite its simplicity, it actually does some parallel execution, a feature of modern, high-speed processors. Take a look at the LDL microcode instruction, for instance. Loading the DA register from memory doesn't involve using the ALU, so it is possible to exercise the ALU in parallel, using it to increment the program counter.

But the microcode sequencer isn't responsible for executing microcode instructions (except, of course, for microcode instructions that affect the sequencer itself.) It is merely responsible for generating microcode instructions in the proper sequence from machine instructions. Microcode instructions are completely decoded; i.e., there are 22 individual control lines, one for each instruction.

Microcode is stored in a Flash ROM; an oversized Flash ROM in this case, but it so happens that that's what I had in my toolbox! It is a 28C16 Flash ROM from Microchip.

The sequencer circuit (shown below) also contains a series of DIP switches that are used to program the Flash ROM. These DIP switches are not strictly necessary; you can also use an external programmer. In my case, for one thing I didn't have an appropriate programmer unit handy when I was building this circuit, and I also found it convenient to program the chip in-circuit. Hence, the switches.

So, how does this thing work? Four-bit instruction codes are fetched from the data bus by a 74LS175 quad flip-flop. The instruction word is represents the upper 4 bits of the microcode address. The lower 4 bits are generated by a 74LS93 counter, leaving room for a maximum of 16 microcode words per instruction. The counter is controlled by the clock signals, the HLT and the LIR microcode instructions, and the START line (which is pulled low by a manually operated button).

The complete 8-bit microcode address is fed to the 28C16 via a 74LS244 octal buffer. The output of the 28C16 is then decoded into 22 individual lines using three 74LS138 3-to-8 demultiplexers. LEDs are also used to show the current state of the 22 control lines; I found this very helpful while testing/debugging the circuit.

vicproc_seq.gif

Lastly, here's the actual microcode table, used to program the 28C16:

Opcode   Address   Microcode words (binary)   Hex   Mnemonic
0 HLT (00) 00000000 00000 00001 00000 00000 00000 00 01 HLT,LIR
1 LDA (10) 00010000 00100 00101 00010 00011 00001 04 05 02 03 01 LDL,LDH,LAL,LAH,LIR
2 STA (20) 00100000 00100 00101 00110 00111 00001 04 05 06 07 01 LDL,LDH,SAL,SAH,LIR
3 JMP (30) 00110000 00100 00101 01010 00001 00000 04 05 0A 01 LDL,LDH,LPC,LIR
4 SPC (40) 01000000 00100 00101 01000 01001 00001 04 05 08 09 01 LDL,LDH,SPL,SPH,LIR
5 AND (50) 01010000 00100 00101 01110 00001 00000 04 05 0E 01 LDL,LDH,AND,LIR
6 OR (60) 01100000 00100 00101 01111 00001 00000 04 05 0F 01 LDL,LDH,OR ,LIR
7 ADD (70) 01110000 00100 00101 10000 00001 00000 04 05 10 01 LDL,LDH,ADD,LIR
8 SUB (80) 10000000 00100 00101 10000 00001 00000 04 05 11 01 LDL,LDH,SUB,LIR
9 JNZ (90) 10010000 00100 00101 01101 00001 00000 04 05 0D 01 LDL,LDH,PCZ,LIR
A CMP (A0) 10100000 00100 00101 10010 00001 00000 04 05 12 01 LDL,LDH,CMD,LIR
B JND (B0) 10110000 00100 00101 01100 00001 00000 04 05 0C 01 LDL,LDH,PCD,LIR
C JNC (C0) 11000000 00100 00101 01011 00001 00000 04 05 0B 01 LDL,LDH,PCC,LIR
D ROL (D0) 11010000 10011 00001 00000 00000 00000 13 01 ROL,LIR
E ROR (E0) 11100000 10100 00001 00000 00000 00000 14 01 ROR,LIR
F CLF (F0) 11110000 10101 00001 00000 00000 00000 15 01 CLF,LIR

Only the bits shown here need to be programmed. Other bits are ignored. These include the top 3 bits of each microcode word, as well as unused words, such as the words at address 00010005 to 0001000F (the LDA instruction only uses five microcode words, at address 00010000-00010004.) This microcode table could also have been implemented as a diode ROM instead of wasting so much useful space in my 28C16; an address decode logic could have been used in conjunction with approx. 90 diodes.

Viktor's amazing 4-bit processor - Program counter

The program counter of my 4-bit processor consists of a pair of 74LS175 circuits to hold 8 bits of data, plus several 74LS244 circuits to control input and output to and from this register. The decoder logic ensures that the program counter latches onto data, or emits data on the correct bus at the appropriate times during instruction cycles. In terms of its function, the program counter is very similar to the accumulator.

vicproc_pc.gif

Viktor's amazing 4-bit processor - Data address register

The data address register of my 4-bit processor consists of a pair of 74LS175 circuits to hold 8 bits of data, plus several 74LS244 circuits to control input and output to and from this register. The decoder logic ensures that the register latches onto data, or emits data on the correct bus at the appropriate times during instruction cycles. In terms of its function, this register is very similar to the accumulator.

vicproc_da.gif

Viktor's amazing 4-bit processor - Accumulator

The accumulator of my 4-bit processor consists of a pair of 74LS175 circuits to hold 8 bits of data, plus several 74LS244 circuits to control input and output to and from this register. The decoder logic ensures that the accumulator latches onto data, or emits data on the correct bus at the appropriate times during instruction cycles.

vicproc_acc.gif

Viktor's amazing 4-bit processor - Arithmetic logic unit

The arithmetic and logic unit, or ALU, is the most complex subsystem of this processor. Its purpose can be simply defined, however: depending on the current instruction microcode cycle, it is to take the data on one or both of its inputs, compute the desired result, and place the result on its output.

Components of the ALU perform computations in parallel. A selection logic decides which computation's output will appear on the ALU's output bus. Further selection logic is used to modify the results of computations; e.g., the difference between an addition and a subtraction is that during a subtraction, the two's complement of the second argument is computed before the two arguments are fed to the adder circuit.

vicproc_alu.gif

Viktor's amazing 4-bit processor - Functional block diagram

The diagram at the bottom of this page provides an overview of my 4-bit contraption. You can also click on any of the subsystem rectangles to link directly to the respective page, or review some assembly instructions.

I began construction of this processor with its memory, by building a simple circuit to drive a 256×4 bit static RAM chip (2112) that I picked up at a local electronics store. At the time, it wasn't yet clear exactly where I was heading with this project; so I installed a set of microswitches to set the memory address and data, and two 7-segment LED displays to read my data switch settings and show the data at the current address in RAM.

Once I realized that this is going to be more than just a toy circuit to play with a RAM chip, the next step was to develop the operational concepts for my processor.

Once I had my instruction set, microcode instruction set, and register design, I was able to proceed with designing what really is the heart of the processor: a microcode instruction sequencer. This circuit turns instruction codewords into control signals that activate other processor components which in turn perform operations.

The simplest subsystems are the processor's internal registers: its accumulator, data address register, and program counter. Still, these are subsystems on their own right as they respond, and perform a variety of functions, in response to control signals.

Actual computations in this processor are done by its arithmetic and logic unit, or ALU. Most operations either use, or alter the contents of the processor's main internal register, its accumulator. Another internal register, the data address register, is used to facilitate access to memory for fetching, or writing, data words. The program counter register, on the other hand, is used to fetch instruction words; it is also incremented automatically as instructions are executed. Its contents can also be altered directly by conditional or unconditional jump instructions.

Not shown in the logic diagram, but vital to the operation of the computer, is its clock generator. The computer uses a two-phase clock. The idea is simple; on the first clock phase, internal states are preset in accordance with the current microcode instruction, and on the second phase, register values are fetched. For instance, if the current microcode instruction is to increment the program counter, the first clock phase configures the ALU to emit PC+1 on its output, while the second clock phase instructs the PC to fetch this new value from the ALU.

Finally, all the components of the processor are connected to each other via a variety of buses, some bidirectional, some unidirectional:

The memory address bus is an 8-bit bus that carries address information from the DA or PC to the memory module.

The memory data bus is a 4-bit bidirectional bus. It carries data from the AC or PC to memory; or, from memory to the AC or DA.

The instruction microcode bus carries the 22 microcode signals from the sequencer to all other system components.

The ALU communicates with other components via three 8-bit buses. Two input buses are used to send data to the ALU from the accumulator, DA, and PC. The ALU output is used to send results to the accumulator, DA, or PC.

Lastly, ALU flags (namely, the Carry, Data, and Zero flags) are sent from the ALU to the PC; the PC uses these flag results during the execution of conditional jump instructions.

Interface Summary

Bus

Functional Unit

ACC PC SEQ DA MEM ALU
MA - W - W R -
MD R/W W R R R/W -
A1 W - - - - R
A2 - W - W - R
AO R R - R - W
AF - R - - - R/W
IM R R R/W R R R

 

 
 
     
 
 

You haven't lived until you've died in MUD2!
Copyright © 1995-2026 Viktor T. Toth. All Rights Reserved.
Joomla! is Free Software released under the GNU General Public License.