Chapter Twenty

Automating Arithmetic

The human species is often amazingly inventive and industrious but at the same time profoundly lazy. It’s very clear that we humans don’t like to work. This aversion to work is so extreme—and our ingenuity so acute—that we’re eager to devote countless hours designing and building clever devices that might shave a few minutes off our workday. Few fantasies tickle the human pleasure center more than a vision of relaxing in a hammock watching some newfangled contraption we just built mow the lawn.

I’m afraid I won’t be showing plans for an automatic lawn-mowing machine in these pages. But in this chapter, I’ll begin taking you through a progression of ever more sophisticated machines that will automate the process of adding and subtracting numbers. This hardly sounds earth-shattering, I know. But these machines will gradually become so versatile that they will be able to solve virtually any problem that makes use of addition and subtraction, as well as Boolean logic, and that includes a great many problems indeed.

Of course, with sophistication comes complexity, so some of this might be rough going. No one will blame you if you skim over the excruciating details. At times, you might rebel and promise that you’ll not seek electronic assistance for a math problem ever again. But stick with me, because eventually we’ll have invented a machine that can legitimately be called a computer.

The last adder we looked at was in Chapter 17 on page 231. That version included an 8-bit edge-triggered latch that accumulated a running total of numbers entered on one set of eight switches:

An accumulating adder consisting of an 8-bit adder going to an 8-bit edge-triggered latch whose output is routed back to the adder.

As you’ll recall, an 8-bit latch uses flip-flops to store an 8-bit value. Initially, the content of the latch is all zeros and so is the output. You use the switches to enter your first number. The adder simply adds this number to the zero output of the latch, so the result is the same number that you entered. Pressing the Add switch stores that number in the latch and turns on some lightbulbs to display it. Because this is an edge-triggered latch, the latch does not store a new value until the Add switch is released and pressed again.

Now you set up the second number on the switches. The adder adds this one to the number stored in the latch. Pressing the Add button again stores the total in the latch and displays it using the lightbulbs. In this way, you can add a whole series of numbers and display the running total. The limitation, of course, is that the eight lightbulbs can’t display a total greater than 255.

A latch that is used to accumulate a running total of numbers is often called an accumulator. But you’ll see later that an accumulator need not simply accumulate. An accumulator is often a latch that holds first one number and then that number combined arithmetically or logically with another number.

The big problem with the adding machine shown above is fairly obvious: Perhaps you have a list of 100 bytes that you want to add together. You sit down at the adding machine and doggedly enter each and every number and accumulate the sum. But when you’re finished, you discover that a couple of the numbers on the list were incorrect. And then you begin wondering if perhaps you made additional mistakes typing them all in. Now you have to do the whole thing over again.

But maybe there’s a solution. In the preceding chapter, you saw how to use flip-flops to build a RAM array containing 64 KB of memory. You also saw a control panel containing switches and lightbulbs:

A Control Panel to specify a 16-bit address, 8 data bits, with Write and Takeover switches. Eight lightbulbs show the contents of the memory at that address.

Flipping the switch labeled Takeover literally allows us to take over all the writing and reading of this RAM array, as shown here:

Twenty-six 2-to-1 selectors allow a control panel to write data into a memory array, and read data from it.

If you had typed all 100 bytes into this RAM array rather than directly into the adding machine, checking the values and making a few corrections would be a lot easier.

To simplify future diagrams in this book, the 64K × 8 RAM array will be shown by itself rather than accompanied by the control panel and the 26 selectors that are necessary to take over the reading and writing:

A 64K by 8-bit RAM array allows storing 65,536 bytes referenced with a 16-bit address.

The existence of the control panel—or something equivalent that allows us humans to write bytes into the memory array and read them back—is implied by this simpler diagram. Sometimes I won’t show the Enable signal either. You can assume that the tri-state buffers for Data Out are enabled if this signal is not shown.

Suppose we want to add 8 bytes—for example, the hexadecimal values 35h, 1Bh, 09h, 31h, 1Eh, 12h, 23h, and 0Ch. If you use the Windows or macOS calculator app in Programmer mode, you’ll discover that the sum is E9h, but let’s challenge ourselves to build some hardware that adds these numbers for us.

Using the control panel, you can enter these 8 bytes into the RAM array beginning at the address 0000h. When you’re finished, the contents of the RAM array can be symbolized like this:

A block of random access memory showing eight bytes stored in the first eight addresses and another address awaiting the sum.

This is how I’ll be showing a section of memory from now on. The boxes represent the contents of the memory. Each byte of memory is in a box. The address of that box is at the left. Not every address needs to be indicated, because the addresses are sequential and you can always figure out what address applies to a particular box. At the right are some comments about this memory. This particular comment suggests that we want to build something that adds up the first 8 bytes and then writes the sum to the first memory location that contains a byte of 00h, which in this case is at address 0008h.

Of course, you’re not limited to storing just eight numbers. If you have 100 numbers, you’ll store these numbers at addresses 0000h through 0063h. Now we face the challenge of connecting the RAM array to the accumulating adder from Chapter 17, which once again looks like this:

An accumulating adder consisting of an 8-bit adder going to an 8-bit edge-triggered latch whose output is routed back to the adder.

Those switches and lightbulbs are no longer needed because we have switches and lightbulbs on the control panel that is connected to the memory array. We can replace the switches to the adder with the Data Out signals from the RAM array. And instead of the output from the latch lighting up lightbulbs, we can route that output to the Data In inputs of the RAM:

A block diagram of an accumulating adder attached to random access memory.

Of course, this is missing a few pieces. It doesn’t show you what’s connected to the Clock signal on the latch, which is essential for storing the accumulated sum. It also doesn’t show what’s connected to a Write signal on the RAM, which is essential for storing the final result. The RAM is also missing a 16-bit address necessary for accessing the contents.

The Address input to the RAM must increase sequentially, beginning at 0000h, then 0001h, 0002h, 0003, and so forth. This is a job for a counter built from a row of cascading flip-flops, such as the one you saw on page 237 of Chapter 17:

A 16-bit counter.

Notice that the data path from the counter output is a little wider to symbolize 16 bits rather than just 8.

This counter provides the Address input for the RAM:

A counter is added to the accumulating adder to provide an address for the random access memory array.

I call this machine the Automated Accumulating Adder.

Of course, by adding a counter to provide the address for the RAM, we’ve introduced another missing Clock signal that’s necessary to increase the value of the counter. But we’re on our way. All the main 8-bit and 16-bit data paths have been defined. Now all we need are three signals:

· The Clock input for the counter

· The Clock input for the latch

· The Write input for the random-access memory

Signals of this sort are sometimes known collectively as control signals, and they often turn out to be the most complex part of a circuit such as this. These three signals must be coordinated and synchronized.

The Clock input for the counter causes the counter to increment to the next address, from 0000h to 0001h, and then from 0001h to 0002h, and so on. That address accesses a particular byte of memory, which goes into the adder along with the output of the latch. The Clock input on the latch must then store that new sum. In real life, the memory access and the addition take a little bit of time, which means that the Clock input on the latch must occur sometime after the Clock signal on the counter, and similarly, the next Clock signal on the counter must occur sometime after the Clock signal on the latch.

To accomplish this, let’s wire up two flip-flops like this:

Two flip-flops wired to generate control signals for the Automated Accumulating Adder.

The oscillator at the far left is just something that alternates between 0 and 1. It can be very fast, such as a crystal oscillator used in clocks and computers, or it could be as simple as a switch or button that you press with your finger.

The first flip-flop is wired to divide that frequency in half, such as you saw toward the end of Chapter 17. The Q output of that flip-flop becomes the Clock input to the counter, which increments the counter’s value on every transition from 0 to 1. Here’s the timing diagram for the first flip-flop:

Timing diagram for the first flip-flop to generate the Clock input to the counter in the automated adder.

The bottom row of the timing diagram symbolizes how the counter output changes.

The Clock input to the second flip-flop is opposite that of the first flip-flop, and the D input is the Q output from the first flip-flop, which means that the Q output from the second flip-flop is offset one cycle from the Q output of the first one. For purposes of comparison, the following diagram includes the counter output from the previous diagram:

Timing diagram for the second flip-flop to generate the Clock input to the latch in the automated adder.

The AND gate combines the Q output from the first flip-flop and the Q output from the second flip-flop. I will be calling that output from the AND gate the Pulse.

This Pulse signal becomes the Clock input of the latch:

The 8-bit latch in the Automated Accumulating Adder is clocked by the Pulse signal from the flip-flops.

We want to make sure that enough time is available for the value from the counter to address the memory, and for the data from the memory to be added to the previous sum before it is saved in the latch. The objective is to ensure that everything is stable before the latch saves the new sum. In other words, we want to avoid glitches. This has been accomplished: The counter output remains constant when the Pulse signal is 1.

The other signal that’s required in the Automated Accumulating Adder is the memory Write signal. I mentioned earlier that we want to write the accumulated sum to the first memory location that has a value of 00h. That memory location can be detected by tapping into the Data Out signals from the RAM and running them into an 8-bit NOR gate. The output of this NOR gate is 1 if all the individual Data Out values are 0. That output can then be combined with the Pulse output from the flip-flop configuration:

An 8-input NOR gate detects when the contents of the memory is all zeroes, which is used to generate a Write signal to the memory.

Images

An interactive version of the complete Automated Accumulating Adder is available on the website CodeHiddenLanguage.com.

No provision has been made for stopping the Automated Accumulating Adder from continuing forever. As long as the oscillator keeps generating a signal that alternates between 0 and 1, the counter will continue to access the memory. If the other bytes in the memory are equal to 00h, the circuitry will write the completed sum into those locations.

Eventually, if the oscillator keeps going, the counter will reach FFFFh, and then it will roll over to the value 0000h and start the accumulated addition again. But this time, it will add all the values in memory to the sum already calculated.

To have some control over this process, you’ll probably want to add a button or switch labeled Clear. The counter providing the memory address is built from edge-triggered flip-flops, so it probably has a Clear input. The latch is built from edge-triggered flip-flops as well, and edge-triggered flip-flops have been used for generating the control signals. This Clear button can clear the counter and the latch and stop the pair of flip-flops from working. You can then enter new values into memory and start the addition process again.

But the biggest problem with the Automated Accumulating Adder is that it’s limited to adding mere bytes, and bytes can only range in value from 00h to FFh, or 255 in decimal.

The example job that I described for this Automated Accumulating Adder was an addition of the 8 bytes 35h, 1Bh, 09h, 31h, 1Eh, 12h, 23h, and 0Ch, which sum up to E9h, or 233 in decimal. But suppose there were a ninth byte of 20h. The sum would then have been 109h. But that’s no longer a 1-byte value. The output of the 8-bit adder would be just 09h, and that’s what would be stored in memory. The Carry Out signal of the adder would indicate that the sum exceeds FFh, but the Automated Accumulating Adder isn’t doing anything with that signal.

Suppose you want to use the Automated Accumulating Adder to verify deposits in your checking account. In the United States, money is reckoned using dollars and cents—for example, $1.25—and many other countries have similar systems. To store that value in a byte, you’ll need to convert it to a whole number by multiplying by 100 to get 125 cents, which is 7Dh in hexadecimal.

That means that if you want to use bytes for storing money amounts, you are limited to values up to FFh, or 255 in decimal, or a mere $2.55.

You’ll need to use more bytes for larger amounts. How about two bytes? Two-byte values can range from 0000h to FFFFh, or 65,535 in decimal, or $655.35.

That’s much better, but you’ll probably also want to represent negative amounts of money as well as positive amounts—for example, when your checking account is overdrawn. That means using two’s complement, which I discussed in Chapter 16. With two’s complement, the maximum positive 16-bit value is 7FFFh, or 32,767 in decimal, and the minimum negative value is 8000h, which is –32,768. That would allow money values between –$327.68 and $327.67.

Let’s try 3 bytes. Using two’s complement, 3-byte values can range from 800000h to 7FFFFFh, or from –8,388,608 to 8,388,607 in decimal, which translates into money values from –$83,886.08 to $83,886.07. I suspect that is a much safer range for most people’s checking accounts, so let’s go with it.

How can the automated adder be enhanced to add 3-byte values rather than just 1-byte values?

The easy answer is to expand the memory to store 24-bit values, and to build 24-bit adders and latches.

But maybe that’s not practical. Maybe you’ve already invested in creating a 64K × 8 RAM array, and you already have an 8-bit adder, and these can’t be easily replaced.

If we stick with 8-bit memory, 24-bit values can be stored by splitting them into three consecutive memory locations. But the crucial question is: What direction?

And what do I mean by that?

Suppose you want to store the value $10,000.00. That’s 1,000,000 cents or 0F4240h in hexadecimal, which is the 3 bytes 0Fh, 42h, and 40h. These 3 bytes can be referred to as the “high,” “middle,” and “low” bytes. But they can be stored in memory in one of two ways. Do we store the 3 bytes in this order?

Three bytes stored in memory in the order 0F, 42, 40.

Or in this order?

Three bytes stored in memory in the order 40, 42, 0F.

You might ask: What’s the customary way of doing it? Or: What’s the time-honored industry-standard way of doing it? Unfortunately, the answer to those questions is: Both. Some computers do it one way; others do it the other way.

These two methods for storing multibyte values are known as big-endian and little-endian. I mentioned this difference in Chapter 13 when discussing Unicode. The terms come from Jonathan Swift’s satirical novel Gulliver’s Travels (Part I, Chapters 4 and later), which describes how the people of Lilliput had been embroiled in a long controversy over whether to break an egg at the smaller or larger end. In the computer industry, it is not so much a controversy as a basic difference that everyone has learned to live with.

At first glance, the big-endian approach seems more sensible because it’s the same order in which we would normally write the bytes. The little-endian approach looks backwards because it begins with the least significant byte.

And yet, if you were reading multibyte values from memory for purposes of addition, you’d want to begin with the least significant byte. The addition of the least significant byte might generate a carry that is used in the addition of the next more significant byte.

For that reason, I will store bytes in the little-endian format—least significant byte first. But that only applies to the order when stored in memory. When showing hexadecimal values otherwise, I’ll continue to show the most significant byte first.

In describing what this new machine does, I’ll be speaking in terms of “deposits” and “withdrawals” as if it were calculating a balance for a bank account. But it could just as well apply to expenses and income when running a small business, or assets and liabilities.

Let’s begin with two deposits of $450.00 and $350.00. In hexadecimal these are added like so:

images

As each pair of bytes is added starting at the right, a carry is generated that affects the next pair of bytes.

Now let’s withdraw $500.00 from that sum, which is 00C350h in hexadecimal:

images

In Chapter 16 I described how binary numbers are subtracted. You first convert the number being subtracted to two’s complement and then add. To find the two’s complement of 00C350h, invert all the bits (0 bits become 1 and 1 bits become 0) to get FF3CAFh, and then add 1 to get FF3CB0h. Now add that:

images

In decimal, that sum is 30,000, or $300. Now let’s withdraw another $500:

images

The result is a negative number. Our balance has dropped below zero! To determine that negative value, again invert all the bits to get 004E1Fh and add 1 to get 004E20h, or 20,000 in decimal. The balance is –$200.00.

Fortunately, we have more money coming in. This time the deposit is a whopping $2000.00, or 030D40h. Add that to the previous negative result:

images

And that, I’m pleased to say, is 180,000 in decimal, or $1800.00.

That’s the type of job I want this new machine to do. I want it to add and subtract 3-byte values that are stored in memory, and I want it to write the result back into memory.

And I do want it to subtract. I want a withdrawal of $500 to be stored as the 3 bytes 00, C3, and 50, and not in two’s complement. I want the machine to do the work for us in calculating the two’s complement.

But if all the numbers are stored in memory as positive values, then deposits and withdrawals look the same. How can they be distinguished?

We need something to accompany the numbers in memory to identify what we want to do with them. After considering this problem—perhaps overnight if you need to—you might have the brilliant idea of preceding each number in memory with a code of some sort. One code could mean “add the following 3-byte value,” and another code could mean “subtract the following 3-byte value.” Here’s how it might look in memory for the example problem I just discussed:

Memory contents to add and subtract five three-byte values.

I chose a code of 02h to indicate that the next 3-byte value is to be added to the running total, and a code of 03h to indicate subtraction. These codes are somewhat arbitrary, but not entirely. (You’ll soon see what I mean.)

Codes such as these are sometimes called instruction codes or operation codes or opcodes. They instruct a machine reading the memory what to do, and the machine responds by performing certain operations such as addition or subtraction.

The contents of the memory can now be differentiated as code and data. In this example, each code byte precedes 3 data bytes.

Now that we have codes to add and subtract values, let’s devise another code to store the running total in memory immediately following the code, and another code to halt the machine so it doesn’t continue running with nothing to do:

Memory contents to store a running total and halt a machine.

Let’s call the machine that performs this wondrous feat the Triple-Byte Accumulator. Like the Automated Accumulating Adder, it will continue to access a 64K × 8 memory array, and it will accumulate the running total using an 8-bit adder. But the number of latches must be increased to four—one to store the instruction code and the other three to store the running total. Here are all the major components and data paths:

The Triple Byte Accumulator has a latch to store an instruction and three latches to accumulate the three bytes of the running sum.

To avoid overwhelming the diagram, none of the numerous control signals is shown. I’ll spend much of the rest of the chapter showing you these control signals. Also not shown in this already complex diagram are the inputs to these various boxes that the control signals are attached to—for example, the Clock inputs to the counter and the latches, the Enable signals for the tri-state buffers, and the Write signal for the RAM.

Just as with the Automated Accumulating Adder, the Triple-Byte Accumulator has a 16-bit counter that provides an address for the random-access memory. The Clock input for this counter comes from the same configuration of two flip-flops shown earlier.

The Triple-Byte Accumulator has four latches: The first is labeled “Inst. Latch,” which stands for “instruction latch.” This is used to hold the instruction code from memory addresses 0000h, 0004h, 0008h, and so forth.

The other three latches are labeled “High,” “Mid,” and “Low.” These are used to store the 3 bytes of the running total. The input to these three latches is the Sum output of the adder. The outputs go into three boxes labeled “Tri-State,” which are tri-state buffers, described in the previous chapter. These boxes have Enable signals not shown in the illustration. As with any tri-state buffer, if the Enable signal is 1, then the Outputs are the same as the Inputs—0 if the input is 0, and 1 if the input is 1. But if the Enable signal is 0, then the Outputs are neither 0 nor 1—neither a voltage nor a ground, but instead nothing. That’s the third state.

Each of the three tri-state buffers shown in the Triple-Byte Accumulator has its own Enable signal. At any time, only one of these three Enable signals is set to 1. This allows the outputs of the three tri-state buffers to be connected to each other without conflicts between voltages and grounds. The Enable signals control which of the three latch outputs goes to the RAM Data In and to the B input to the adder.

These three Enable signals depend on the two least significant bits of the memory address generated by the counter. The bytes in memory have been stored in a very methodical manner: First an instruction byte and then the low, middle, and high bytes of a 3-byte number. The roles of these 4 bytes correspond to the two least significant bits of the memory address. These 2 bits increase from 00 to 01 to 10 to 11 over and over again so that each value corresponds to a particular type of byte in memory:

· If the lower 2 bits of the address are 00, then the byte at that address is an instruction code.

· If the lower 2 bits of the address are 01, then the byte at that address is the least significant byte (the low byte) of the number to be added or subtracted.

· Similarly, if the lower 2 bits are 10, then it’s the middle byte.

· If the lower 2 bits are 11, then it’s the high byte.

The three Enable signals for the tri-state buffers can be generated by a 2-to-4 decoder using the two least significant bits of the memory address, identified here as A0 and A1:

A 2-to-4 decoder uses the lowest two bits of the RAM address to generate enable signals for the three bytes, and the instruction latch clock.

Also shown is the Clock input for the instruction latch. This occurs when the lowest 2 bits of the address are 00 and the Pulse signal from the dual flip-flops configuration is 1. The instruction remains in that latch while the next 3 bytes are accessed. The Clock signals for the other three latches are a little more complicated, but I’ll show them shortly.

Here’s how the Triple-Byte Accumulator works. Let’s assume that all the latches are initially cleared and hold no values: The counter outputs a RAM address of 0000h. The byte at that address (02h in this example) is latched in the instruction latch.

The counter outputs a RAM address of 0001h. That’s the location in memory of the low byte of the first number. That byte goes into the adder. (Ignore the box labeled “1s’ Comp.” for now; assume it does nothing, which is true when an addition is occurring.) The lower 2 bits of the address are 01, so the low-byte tri-state buffer is selected. But because the low-byte latch has been cleared, the B input of the adder is 00h. The Sum output of the adder is the low byte of the first number. That value is latched in the low-byte latch.

The counter outputs a RAM address of 0002h. That’s the middle byte of the first number. That goes into the adder along with the value in the middle latch, which is 00h. The Sum output is the same as the middle byte from memory, and that’s latched in the middle-byte latch.

The counter outputs a RAM address of 0003h. That’s the high byte, and it goes through the adder and is latched in the high-byte latch.

The counter outputs a RAM address of 0004h. That’s an instruction code of 02h, which means to add.

The counter outputs a RAM address of 0005h. That’s the low byte of the second number. It goes into the A input of the adder. The low-byte tri-state buffer is enabled. Because the latch contains the low byte of the first number, that becomes the B input of the adder. Those two bytes are added and then latched in the low-byte latch.

This process continues with the middle and high bytes and then the next number.

When designing the Triple-Byte Accumulator, I defined four instruction codes:

· 02h for adding the following 3-byte number

· 03h for subtracting the following 3-byte number

· 04h for writing the 3-byte running total into memory

· 08h for halting the machine

Only 4 bits need to be stored in the instruction latch. Here’s how those bits correspond to the four instructions:

The instruction latch stores just four bits to identify the four operation codes.

The instruction code remains in the latch while the next 3 bytes are read into the machine. The bits in the instruction code are used to control other parts of the Triple-Byte Accumulator.

For example, the Q1 bit is 1 if the next 3 bytes are to be either added or subtracted from the running total. This means that this bit can be used to help determine the Clock inputs on the latches for the 3 data bytes. Here’s the 2-to-4 decoder again to show how those outputs are combined with the Q1 instruction bit and the Pulse signal from the flip-flops:

The 2-to-4 decoder outputs are combined with the Q-one output from the instruction latch and the pulse signal from the flip-flops to generate signals to latch the three data bytes.

As you saw earlier, when the lower 2 bits of the address are 00, the instruction byte is latched. That instruction byte can then be used when these 2 address bits are 01, 10, and 11, as the 3 data bytes are accessed. If the instruction is an Add or Subtract, then the Q1 output of the instruction latch will be 1, and these three AND gates generate Clock signals to successively latch the 3 bytes.

It may have seemed odd that I originally defined the Add instruction to be 02h and the Subtract instruction to be 03h. Why not 01h and 02h? Or 23h and 7Ch? I did it this way so that the Add and Subtract instruction codes shared a bit that could be used to control the Clock signals for the latches.

The Q0 bit of the instruction is 1 only if the number in the next 3 bytes is being subtracted. If that is the case, then the two’s complement of the number must be taken. The two’s complement is calculated by first finding the ones’ complement and then adding 1. The ones’ complement is simply all the bits inverted from 0 to 1 and from 1 to 0. You saw a circuit to do this in Chapter 16:

A circuit using eight XOR gates to invert or not invert eight bits.

The inputs come from the RAM Data Out. The outputs go to the A input of the 8-bit adder. The Invert signal can come directly from the Q0 output of the instruction latch. That’s the bit that indicates that a number is being subtracted rather than added.

The two’s complement is the ones’ complement plus 1. The addition of 1 can be accomplished by setting the Carry In input to the adder to 1, but only for the first of the 3 bytes:

The Q-zero bit from the instruction latch is used to compute the ones’ complement of the value from memory, and to set the Carry In of the adder to 1.

Whenever the lowest bit from the instruction latch is 1, a subtraction is taking place. All the data bytes coming from the RAM must be inverted. That’s the purpose of the box labeled “Ones’ Comp.” Also, the Carry In input of the adder must be set to 1, but only for the first byte. That’s the purpose of the AND gate. The 01 output of the decoder will be 1 only for the first data byte.

The OR gate is present because the Carry In to the adder might also need to be set when adding or subtracting the second and third data bytes. To keep the diagrams simple, I have totally ignored the carry problem, but now we must be brave and confront it directly.

The Triple-Byte Accumulator contains three latches and three tri-state buffers to store and access the 3 bytes of the running total:

The adder, data latches, and buffers of the Triple-Byte Accumulator.

But not exactly. To accommodate the carry bit, two of these latches need to store 9 bits: the 8 bits of the sum as well as the Carry Out from the adder. Two of the tri-state buffers also need to handle nine bits so that a carry from the low-byte addition can be used in the middle-byte addition, and a carry from the middle-byte addition can be used when adding the high byte:

The adder, latches, and tri-state buffers of the Triple-Byte Accumulator with circuitry added to handle carries.

Notice that the Carry Out signal from the adder is stored in the low-byte and mid-byte latches, but these values then go into the mid-byte and high-byte tri-state buffers, which I know looks very odd. Here’s why it’s like that:

When the low bytes are added, the Carry In input of the adder is 0 for an addition and 1 for a subtraction. (That’s the other input to the OR gate.) That addition might result in a carry. The Carry Out output of the adder is saved by the low-byte latch. However, that carry bit must be used when adding the middle byte. That’s why the value of the carry bit in the low latch is another input to the tri-state buffer of the middle byte. Similarly, a carry resulting from the addition of the middle bytes must be used when adding the high bytes.

We’re in the home stretch now. All the addition and subtraction are now being handled. What hasn’t been handled yet is the instruction code 04h. That’s for writing the 3 bytes into memory. This circuit uses the Q2 bit stored in the instruction latch:

The 2-to-4 decoder of the Triple-Byte Accumulator with an inverter and AND gate to generate a RAM Write signal.

The RAM Write signal is generated only when the lower 2 bits of the RAM address are 01, 10, or 11, corresponding to the 3 data bytes. That’s the purpose of the inverter. When those address bits are 01, 10, or 11, the three successive tri-state buffers are enabled. If the Q2 bit is 1 (indicating a Write instruction) and the Pulse signal from the flip-flop configuration is 1, then the 3 bytes are successively written into memory.

The final instruction is 08h, meaning Halt. This is an easy one. When the Q3 bit from the instruction latch is 1, we essentially want the oscillator that has been running the whole show to stop working:

The Halt bit from the instruction latch is used to control the oscillator input to the flip-flop configuration.

It would also be convenient to add a Clear button, which clears the contents of the flip-flops, the counter, and all the latches in preparation for accumulating another running total.

An interactive version of the complete Triple-Byte Accumulator is available on the website CodeHiddenLanguage.com.

Images

By now it should be obvious why I defined the four instruction codes the way I did. I wanted to use the bits directly in the circuit. I wanted one bit to indicate an addition or subtraction, another bit to indicate subtraction, another for writing the result into memory, and another to halt. If I had just used the numbers 01h, 02h, 03h, and 04h, additional circuitry would have been necessary to decode those values into separate signals.

It should also be obvious why I decided to use 3 bytes for storing each number rather than 4. This allowed using the A0 and A1 bits of the memory address to control the various latches in a very direct manner. If I had used 4 bytes for each number, then the operation codes would have been stored at memory address 0000h, 0005h, 000Ah, 000Fh, 0012h, and so forth, which would have made it more difficult to store everything in the correct latches.

I am now ready to define two words that appear in the title of this book: hardware and software. The Triple-Byte Accumulator clearly illustrates the distinction: The hardware is all the circuitry, while the software consists of the codes and data stored in memory. They are called “soft” because they are easy to change. If you mistype one of the numbers, or mix up the codes for addition and subtraction, you can easily change the values. Meanwhile, the circuitry is much harder to change. That is true even if the circuitry is only simulated, as on the CodeHiddenLanguage.com website, rather than consisting of real-world wires and transistors.

Yet the Triple-Byte Accumulator demonstrates a very intimate connection between hardware and software. The codes and numbers are stored in flip-flops in memory, and the bits that make up these values become signals that integrate with the rest of the hardware. On the most basic level, both hardware and software are just electrical signals interacting with logic gates.

If you were to build your own Triple-Byte Accumulator and use it to keep track of your small-business finances, you might be justifiably nervous if the business begins succeeding more than you anticipated. You might easily encounter an income or expense that exceeds the machine’s 3-byte capacity. This machine is not easily expandable. The 3-byte limit is built into the hardware.

I’m afraid we must regard the Triple-Byte Accumulator as a dead end. Fortunately, nothing that we’ve learned in building it will go to waste. We have in fact discovered something very important.

We’ve made the amazing revelation that a machine can be built that responds to codes stored in memory. The Triple-Byte Accumulator used just four codes, but if instruction codes are stored as bytes, it’s possible to define as many as 256 different codes for doing all sorts of tasks. These 256 different tasks might be quite simple, but conceivably they can be versatile enough to be combined into more complex tasks.

The key here is that the simpler tasks are implemented in hardware, while the more complex tasks are implemented in software as combinations of instruction codes.

If you had decided to build such a versatile machine in the year 1970, you would have had a big job ahead of you. But by the year 1980, you wouldn’t need to build it at all! You could buy a chip called a microprocessor that could access 64 KB of memory and interpret nearly 256 different instruction codes.

The first “computer on a chip” became available in November 1971. It was built by Intel and called the 4004. This was a 4-bit processor that contained 2,250 transistors and could access 4KB of memory. By mid-1972, Intel had released their first 8-bit microprocessor, the 8008, which could access 16 KB of memory.

These chips didn’t have quite enough versatility and memory capacity to be fashioned into personal computers with keyboards and displays. They were mostly designed for embedded systems, where they would work in concert with other digital logic, perhaps to control some machinery or perform dedicated tasks.

Then in April 1974 the Intel 8080 arrived. This was an 8-bit processor with about 4,500 transistors that could access 64 KB of memory. The Intel 8080 was packaged in a 40-pin chip:

A 40-pin chip such as those used for the Intel 8080.

The Intel 8080 was ready for the big time. This is the microprocessor used in the first home computer, the Altair 8800 shown on the cover of Popular Electronics at the end of Chapter 19, and it was the granddaddy of the Intel 16-bit microprocessors used in the first IBM Personal Computer (PC), released in August 1981.

Meanwhile, Motorola was also making microprocessors. The Motorola 6800, also available in 1974, was another 8-bit microprocessor that could access 64 KB of memory. A simplified version of the 6800 was released by MOS Technology in 1975 and called the MOS 6502. This was the chip that Steve Wozniak (born 1950) used in the influential Apple II computer, released in June 1977.

Although the Intel 8080 and Motorola 6800 were similar in some ways—they were both 8-bit microprocessors packaged in 40-pin chips that could access 64 KB of memory—the instruction codes that they implemented were completely different. They also differed in in another fundamental way: Earlier I discussed the big-endian and little-endian approaches to storing multibyte numbers. The Motorola 6800 was a big-endian microprocessor, storing multibyte values with the most significant byte first. The Intel 8080 was little-endian, storing the least significant byte first.

Beginning in the next chapter, I’m going to take you inside the Intel 8080 by attempting to build one. I won’t be using anything except basic components that you’ve already seen, such as logic gates, flip-flops, adders, latches, and tri-state buffers.

I won’t finish this ambitious project. My version of the Intel 8080 won’t be as powerful as the real thing. But I’ll get far enough that by the time I’m done, you’ll have an exceptionally deep understanding of what’s going on inside a computer.

If you find an error or have any questions, please email us at admin@erenow.org. Thank you!