8
\$\begingroup\$

I'm currently a junior in highschool, and I've been interested in computer/electrical engineering, specifically microprocessor design. I've read Code by Charles Petzold, , and have begun reading the Microprocessor Design Wikibook (which seems to be incomplete.) Through reading Code, I understand the basic logic behind a CPU, and have begun building one in LogiSim. Chapter 17 in Code details the CPU I want to build, but the circuits lack key components -- clock signals, and instruction decoding. Some of the clock signals seem to be pretty obvious (the PC seems to need a steady clock signal) but others (like how to latch RAM values) I have had to think through and try to get working.

I can build a working accumulator (it can't be accurately called an ALU, I think, because it lacks the L part) that switches between addition and subtraction with a single input, and I understand this is all I need for the arithmetic part -- once I get jump opcodes working, I can implement multiplication and division in code. The part I'm struggling with is the instruction decoding. Through some google searches, I see that each opcode needs to be interpreted as multiple microinstructions, but I'm lost as to how I need this to work. Currently, my instruction decoder is just a combinational analysis circuit with a single binary output for each opcode -- 13 in all.

The way the code works is it has one 8 bit code value (I only use the low-end byte), and then two separate 8 bit address values that I then combine to be the 16 bit address input to the RAM. In order to latch the values, I have a separate counter that counts up to 10b and then resets to 00b. It is the clock input for each latch in turn (for the three latches, there's a, b, and c. the second clocks have a be 1 while b & c are 0, then b is 1 and 1 & c are 0, then c is one and 1 & b are 0, then it resets). But on instructions such as ADD 000Ah, the PC jumps to 000AH...which is supposed to be added into the accumulator, but actually gets latched into the code latch, and is then interpreted as the next opcode, which makes the whole thing go crazy.

I feel like I'm missing some big information regarding instruction decoding and how I need to do clock signals...

Here are the LogiSim .circ files: https://dl.dropboxusercontent.com/u/61676438/PetzoldMk5/8BitAdder.circ https://dl.dropboxusercontent.com/u/61676438/PetzoldMk5/8BitAdderSubtractor.circ https://dl.dropboxusercontent.com/u/61676438/PetzoldMk5/8BitInverter.circ https://dl.dropboxusercontent.com/u/61676438/PetzoldMk5/8BitLatch.circ https://dl.dropboxusercontent.com/u/61676438/PetzoldMk5/ID.circ https://dl.dropboxusercontent.com/u/61676438/PetzoldMk5/PetzoldMk5.circ

PetzoldMk5 is the main CPU, relying on the other files to be imported as libraries.

Here's a list of opcodes (all binary):

Load                 0001
Add                  0010
Add w/ Carry         0011
Sub                  0100
Sub w/ Borrow        0101
Jump                 0110
Jump w/ Carry        0111
Jump W/ 0            1000
Jump w/o C           1001
Jump W/o 0           1010
Store                1011
Halt                 1100
Reset                1101
\$\endgroup\$
  • 1
    \$\begingroup\$ Are you willing to share your existing Logisim CPU .circ file? And just to make life a little more complicated, you should carefully read and understand the Logisim help, under the heading of "Value Propagation." If you haven't, already. (You may have, given all the work you've already applied. Very glad to hear you take this on!) \$\endgroup\$ – jonk Nov 22 '16 at 1:40
  • \$\begingroup\$ @jonk thanks for the nice words. Editing the post with the associated .circ files. \$\endgroup\$ – Will Hodges Nov 22 '16 at 20:14
  • \$\begingroup\$ Is that 6 files, altogether, then? (I assume this is for LogiSim 2.7.1, 2011?) \$\endgroup\$ – jonk Nov 22 '16 at 20:19
  • \$\begingroup\$ @jonk yes, it is, on both questions \$\endgroup\$ – Will Hodges Nov 22 '16 at 20:21
  • \$\begingroup\$ How are you handling subtraction? Do you use the /Q from an input latch to one of the A/B inputs of your ALU and modify the carry-in? Or some other method? \$\endgroup\$ – jonk Nov 22 '16 at 20:22
3
\$\begingroup\$

Hate to post a "link only" like answer, but I think you should be made aware of Warren Toomey's work with CPU's in Logisim, since it's probably exactly what you are looking for.

He has a couple tutorials building up to a reasonably simple CPU here...

http://minnie.tuhs.org/CompArch/Tutes/

And if that doesn't float your boat, he has a more sophisticated CPU here...

http://minnie.tuhs.org/Programs/UcodeCPU/

... All of which is well explained, and has downloads to .circ files.


Another great, and arguably more functional, DIY CPU/computer is Magic-1 found at http://www.homebrewcpu.com/. Though it's not done in Logisim, it is quite well documented, including pictures, schematic, description. It is also more than just a CPU in a simulator. It has a ANSI C compiler, an OS, and some software. It's also has the distinct advantage of actually having been built in hardware. In fact, it's currently up and running and serving web pages!


Finally The Elements of Computing Systems and the associated site nand2tetris.org comes in as the #1 recommended information resource for building your own computer from the ground up every time I look into it. Much (All?) content is free of charge I believe. YouTube would agree; many people have made projects starting from this one source.

\$\endgroup\$
  • 1
    \$\begingroup\$ Checking out Ben Eater's Youtbue video on building an 8bit bread board computer is very helpful too! \$\endgroup\$ – Francis Cugler Aug 7 at 23:09
1
\$\begingroup\$

I think you're missing a key aspect of how ALUs work. Usually each bit of the accumulator is connected to each of the various function blocks via a demultiplexer. Using a command byte, the function is selected and each bit of the accumulator is connected to the proper input of the function block. The size of the demultiplexer determines how many functions the ALU can handle. In my very crude example shown below, an ALU with a 4 bit input could reference 16 different functions using the demultiplexer:

Connecting the accumulator to various function blocks within an ALU

Note that in most CPUs, this design is optimized into a mess of gates to reduce the transistor count.

More functions can be used by having a larger command register, but this would also require more clock cycles to load the command.

I highly recommend reading the following book if you would like to learn more about digital design: Fundamentals of Logic Design 7th ed.

\$\endgroup\$
  • \$\begingroup\$ This answer kind of confuses instructions with ALU operations. Specifically this demultiplexer would just be used to select which arithmetic operation to pick for an arithmetic instruction. You would feed the demultiplexer with, say, 3 bits out of an 8-bit instruction, to pick which arithmetic operation. You need an entirely separate mechanism to decode and sequence instructions in general. As an aside, most of the CPUs I've looked at don't use a demultiplexer like this in the ALU, but use an optimized mess of gates to perform the right operation. \$\endgroup\$ – Ken Shirriff Jan 9 '17 at 21:55
  • 1
    \$\begingroup\$ @KenShirriff I made some changes for clarity. Feel free to make some edits as you see fit. \$\endgroup\$ – Takide Jan 10 '17 at 5:16
1
\$\begingroup\$

It seems like your are on the right path.

When you are planning your micro-instructions you will need to clearly define in your own mind the "traffic" pattern for your data movement through your various blocks. Performing an ADD will require multiple steps. If you have holding registers for your two ALU operands then those will need to be loaded from RAM, or some register or bus. If you have a shared internal bus then you may need to load one operand at a time. Once you know what bytes (address, immediate literal, RAM data, pointer) need to move where, plan the order of the bytes' movement through the bus(es) into and out of the various registers. You may get stuck and need to add an internal holding register because some intermediate value must be preserved until the next micro-instruction step.

I think the thing you suspect is missing is the complexity required for the logic to take the instruction or opcode and translate it into multiple states inside your CPU. It can be complex, but it is possible to create a somewhat simple state machine then expand on it with basic logic concepts.

For example let's say you are creating the micro-instruction steps for a MOVE operation. That could be loosely described by 3 steps : 1) Assert source register contents onto internal bus, 2) strobe the write clock of the target register, 3) De-assert source register contents from internal bus. Steps 1) through 3) describe timing for a register output enable (OE) and a register write enable (WE) which could be selectively distributed to any source register and any target register connected to the shared internal bus with demultiplexers.

If you don't have much practice creating Finite State Machines, it might be useful to look at the different approaches to give you another building block toward generating the control signals from your micro-sequencer.

Stick with it. The amount of knowledge you acquire will be phenomenal. And hopefully you'll have a lot of fun.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.