TTL Computer Processor Blog



When I told my dad that i'm starting this project, he said that I should keep a blog about my progress. I normally don't like to do this sort of thing but I wanted to document my progress. So here it is. Note the specs aren't updated yet. The main page has the current specs.


September 18-24,2009

Ok, so I was talking to a classmate who is taking the digital design fundamentals class (which I will be taking in spring ’10), and he was telling me that they are split up into groups that are to design a part of a computer processor from logic gates and memory.  After he told me this, I immediately remembered that Donn Stewart, who helped me build the Z80 Microcomputer, also designed and built his own processor from logic gates. So I went home, found his book and started to read about how to build the different parts of a cpu. I started to understand how a cpu runs and operates.

Since I’m already familiar with the Z80 and Atmel assembly language, I started to write down which instructions that I would like be implemented into the processor. I wrote down the usual instructions: add, sub, and, or, xor, jmp, jpz, etc. But I would also like this processor to actually be useful, not just a proof of concept. So I also added rotate left/right thru carry, add and sub thru carry, compare registers, and a few other instructions. For the flags, I wrote down Zero (Z), Carry (C), Half Carry (H), Negative (N), and Two’s Compliment Overflow (V). The biggest feature I wanted to add is a stack. From past experience, the stack is very useful and writing a program without it would be very time consuming and frustrating. The stack adds the instructions: push, pop, call, and ret. This also means that I would have to add a 16-bit stack pointer register (SP). The Stack Pointer also needs to be able to be loaded with an address, incremented, and decremented in order to function properly.

At this point, I printed out Donn’s Data Path drawing of his cpu so that I can use it as a reference for drawing my own design. I looked at his design and he has it set up to where the first 4 bits are the instruction opcode and the rest of the 12 bits are the operand to make up a 16-bit instruction word. This does allow the computer to be faster since the operand is already part of the instruction word. One less state is required since the operand doesn’t need to be fetched from memory. But there also downsides. One of the issues is that the memory needs to be 16 bit but most are 8-bit. Donn used two eproms in parallel to form the 16 bit data path. This may be ok if you write an operating system and aren’t planning on changing it very often but in my case, I would change it quite a bit so I don’t want to mess with programming two eproms (one has the opcode and the upper 4 bits of the operand, and the other has the lower 8 bits of the operand… this can get confusing). The other design limitation is that since only 4 bits are used for the instruction opcode, this allows you to implement a total of 16 instructions, which won’t be acceptable since I’m implementing more instructions. So, i’m going to use an 8-bit opcode and operands that are separate from the opcode. I’m not trying to make mine compete or be better than Donn’s, he purposely designed his to be fairly simple and mostly a proof of concept, which is ok too but for my purposes, I want mine to be mostly functional and useful in a future project. Anyways, I started to think about the Z80’s architecture. It has 8-bit registers that can be paired together to form one 16-bit register. You can perform mathematical and logical operations 16 bits at a time. This is very useful if you need a 16-bit counter/timer or are working with addresses. This also works out in my favor since I am a big fan of using multiple registers for storage of variables and other operations.  So for right now, i’m going to have at least four 8-bit general use registers to my cpu.

September 25,2009

After getting fed up with my linear algebra homework last night, I pulled out my drawing that I had started of the main data path of the processor. And after looking at it for a little while, an idea popped into my head and I got over (what I think) is a major milestone. Let me explain… Since I wanted to be able to do 16-bit arithmetic, the ALU needs to be 16 bits wide. I also wanted to have 8-bit register pairs act as one 16-bit register. I originally decided to have the registers directly feed the inputs of the multiplexors but this presents a major problem. Say I wanted to add two 16-bit numbers using register pairs AA’ and BC (two 8-bit register pairs) and store the result into register BC. Then if I wanted to do another 16-bit addition using the same registers but instead store the result in AA’, the data in BC would be lost since I had to use BC to perform the addition… not good. If I did this operation on the Z80, the data in BC would be intact. Also, registers A and B would be permanently connected to the lower half of the 16 bits and A’ & C would be permanently connected to the upper half of the 16 bits of the ALU input. This means that I could only use registers A and B for 8-bit arithmetic, which is ok but i’d rather be able to use whatever registers I want.  I realized that registers A, A’, B, and C aren’t really registers, they are more like temporary registers for only ALU operations. I changed my drawing to have temporary registers for ALU operations and decided to move all regular registers (mainly used for storage) to the 8-bit data bus. This is the milestone. After I made this change, everything started to fall into place. I knew at this point, there is no turning back, I am going to make my own computer processor from scratch! And by scratch I mean only logic gates (74LSXX series ICs) and classic UV eprom memory, nothing else! I’m planning on setting up my memory like how I did on my Z80 Microcomputer: 32Kb ROM on addresses 0000h-7FFFh and 32Kb RAM on addresses 8000h-FFFFh. Like the Z80, my cpu can only physically address a total of 64Kb of memory. As of right now, I have a rough count of how many total chips to be used: 90.

September 26,2009

I started to think about an assembler for my processor. An assembler is extremely useful since you don’t need to keep track of label addresses and opcodes; the assembler does it all for you.  But the problem is I don’t know how to make an assembler. I remember when working with the Z80, I used TASM a few times. TASM is a table assembler for DOS. What’s nice is that it allows you to make a text file with all of the possible opcodes and combinations and the instruction mnemonics. This allows me to implement my own instruction set by entering in my own mnemonics and opcodes. All I have to do is write a program in a text file and run the DOS batch file to assemble the program. The output is placed in another file with the machine code in hex. After looking at Z80 opcodes, I noticed that instructions involving one register like ADD B, the register B is embedded into the opcode. That’s a good idea. It will save another processor state since the register doesn’t need to be fetched from memory. I’m thinking of just using the Z80 opcodes and maybe the mnemonics just to keep things simple. It’s easy sometimes to get confused when switching from programming in Z80 assembly and Atmel assembly or vice versa. This also means that I can keep most of the entries in the TASM Z80 instruction table; just delete the instructions that don’t apply to my processor.

Oh man, I just realized that this is going to be ALOT of work; mainly implementing all of the instructions into microcode! There are at least 12 different opcode combinations for each instruction! If I want to implement 23 instructions, then there are 276 individual addresses for each of the three microcode eproms! And that’s also not including the2-6 possible states for each instruction which adds 2-6 times the number of bytes to be programmed by hand (1656 to 4968 bytes)! This won’t be economically feasible unless there is some semi-automated way to produce the microcode bits! I’m going to have to find a way to reduce this. I knew that there was going to be some sort of consequence because of the increased instructions, registers, and overall complexity. Apparently this is it.

September 27,2009

I found out a way to reduce the microcode programming: I have one eprom that is dedicated to determine how many states a certain instruction needs. The current state, 8-bit opcode, and carry & zero flags are the inputs to the address inputs. Since I have between 32 and 40 individual control lines, I will need 5 more eproms in parallel that contain microcode. Since state 0 is always needed regardless of the instruction, the opcode is disconnected from the address inputs so that I don’t have to place the same microinstruction for every single instruction. Once state one is released, the opcodes are connected to the address inputs. All of the 5 eproms have an 8-bit opcode and the state at its address inputs. The opcode determines the microinstruction and the state determines which state the current instruction is performing. All instructions will have between 2 and 5 states but most will have 2. So I really only have to program one microinstruction (into the 5 eproms) for most instructions. That’s not so bad. It will still take a while if each instruction has up to 12 different combinations but it won’t be as bad as I thought. At least this way I will have a fully functional processor with multiple registers and an indirect addressing mode.
After learning the concepts of how the internals of a microprocessor work, I realized a few things:

  1. The process that goes on in really isn’t very different than writing an assembly program for say a Z80 to control I/O ports and memory. What you are really doing when designing a processor is writing a microprogram that consists of microinstructions to control all of the registers, memory, and data flow elements. This is very similar to writing a program that consists of instructions for a microprocessor. The concepts are the same, just a little more complicated. I don’t know why but I never really thought that it would work like this.
  2. A high level function represents many instructions as to an instruction represents a few microinstructions.

October 19, 2009

I finally remembered to create another entry. I’ve been busy lately with school and only made some real progress this past week. I decided that it is time to draw up the schematics. I’ve been dreading on learning on how to use Eagle, but it wasn’t as bad as I thought. I started the schematics on Wednesday the 14th and finished on Sunday the 18th. Most of it was easy to figure out as most of the registers and multiplexors are bussed together. Some of the more challenging parts were: how to figure out when the status flags should be written, how to select only one of the four different shift register functions and when to write the carry flag, and how to implement prioritized vectored interrupts. For the interrupts, there are 8 inputs with #0 having the highest priority. This is accomplished by using an 8 to 3 priority encoder. The three encoded bits are fed into flip flops to save it right after the interrupt is found at the 8-3 encoder. This is so the interrupt cannot be changed if another one comes right after it that has a higher priority. Then the saved encoded interrupt number is fed into a rom (an old chip from 1980 with a purple ceramic package and gold pins), which holds the respective interrupt vector. When an interrupt arrives, the interrupt pending pin goes high. The microcode (state) sequencer checks that pin when state 0 begins (so that an interrupt can only be recognized right after the previous instruction is completed). State 0 is the instruction fetch state. If an interrupt is found, the instruction fetch is diverted before it executes and immediately goes into state 2 and then the process goes like this: the PC is saved onto the stack, the 8-bit interrupt vector is fetched and written to the PC, and finally a jump is made to the corresponding vector. At the vector, there will be a jump instruction to the ISR. After the ISR is completed, a RETI (Return from Interrupt) instruction is executed, which pops the address located in the stack back to the PC and clears the interrupt pending flip flop in order to accept another interrupt. Interrupts are only enabled if the interrupt enable flip flop is a logic 1. The EI (Enable Interrupt) and DI instructions write to the interrupt enable flip flop.

I made a little change to the microcode sequencer. I ended up with less control lines than I thought so I only need 5 eproms total. I’m going to use flash chips instead since the data can easily be erased and reprogrammed and they act just like the eproms. They cost about the same too. I am able to use the flash chips since I found a used eprom chip programmer on Craigslist a few weeks ago. It is called ChipMax made by EETools. It practically programs everything including eproms, eeproms, flash, gals, and a few other types with the exception of fused proms. It’s a pretty nice programmer but the only down side is that it only uses the parallel port. Since I don’t have one on my desktop, I pulled out an old computer and installed windows 2000 on it. I set it up to where I can remote desktop into it so that I don’t have to switch monitors, keyboard, and mouse. It’s stashed under my computer desk so that the parallel port is easily accessible. It works great. Now that I can program flash chips, I decided to use those in my processor. I found out that I will need to split up the memory and i/o read and write cycles into two clock cycles in order to not be limited by the speed of the memory. I should be able to achieve a clock speed of 4 Mhz. I might be able to get it up to 5 or 6 but it really depends on the speed of the alu. After more thought, more states will be needed than initially thought. Most instructions will be around 5-7 states.


November 11, 2009

I found an issue with the way the overflow and the carry flag gets written to. I originally thought that the Overflow (V) flag is for regular addition/subtraction overflow detection and the Carry flag was for only adding/subtracting with carry. I found out that the Overflow flag is for detecting overflow when working with signed numbers. So, I added Overflow detection logic (from here) for addition/subtraction and changed when the Carry flag gets written to. I believe everything is correct now (according to the flag description in the Z80 manual). I normally don’t use these flags so that’s why I wasn’t exactly sure how/when they should be used. You may ask “Why am I taking the time to add those flags in if I’m not going to use them”? Because I don’t want to find out later that I wished that I added those in. I basically want to “future proof” it for future projects.

I also forgot to mention that I added a BCD correction circuit for 8-bit addition/subtraction in BCD. This could be done in software but in my past clock-based projects I’ve used BCD arithmetic.  I got the basic concepts and circuit idea from Dieter’s site.

Yes!!! I found a suitable microassembler! It’s called AS Macroassembler. It’s actually not meant to be used as a microassembler but has a few main functions that allows it to be one. While looking at the Overflow detection circuit on Dieter’s site, I saw that he had a page on microassembly for rom-based finite state machines. I wrote a microprogram for one instruction to start off with and it works great under the LogicWorks simulator (I just quickly setup the state rom and the flip flop)! This microassembler will greatly reduce errors and is easier to debug the microprogram. Thanks to Dieter for his useful information and Alfred Arnold for the (freeware) AS Macroassembler.

Now the main thing holding me back from starting to build is money. I am a nearly broke college student that has no job (wouldn’t have time for one anyways). So I will have to wait until I get enough money to purchase the parts.


December 17, 2009

The whole design has been sucessfully simulated with Proteus VSM and has no known problems (for now). I've been able to get the interrupt system to work correctly along with the Call and Return instructions. The total chip count is a little over 200. I've written a "test" instruction which represents all of the other variants. I did this to quickly test the basic functions without writing the full microcode. For now, I haven't written the full microcode since it will be at least a few months (maybe more) until I can get enough money to build the processor. That way if I decide to change something down the road, I don't have to change the whole microcode. Unfortunately, I won't be getting any money anytime soon since I don't have a job. But for now, I'm calling the design complete since all of its functions appear to work correctly!




Last Updated December 2009

Contact: Tai Oliphant

Copyright © Tai Oliphant 2009