10
\$\begingroup\$

As a project I am building a small 8-bit RISC processor out of discrete ICs. I have 17 instructions and cannot fit all information into instructions that are only one byte, so I have been thinking about using a 16-bit address bus, instruction set and program counter. This has got me thinking as to how real processors actually implement this.

My guess is that there is separate memory for instructions and data, so the instruction memory will be 16 bits wide, and the data memory will be 8 bits wide. My initial design was to have a single memory chip for both instructions and data which both share an 8-bit bus.

I suppose I could keep the 8-bit bus for shared data and instructions and a single memory chip. Then every time instead of fetching one byte from memory, I could fetch two bytes (one for the instruction opcode and the other for addressing,( but this is very slow and wasteful of clock cycles.

Is my guess how it is done or how is this architecture type implemented?

\$\endgroup\$
15
  • 2
    \$\begingroup\$ David, when the 8080 came out (IMSAI -- I own one; MITS Altair -- I own two) they were limited to 64k address space but, quickly, there were needs for still more. Early options were to add an I/O port where you could write an 8-bit (or 4-bit) latch with a value that would extend the address. Tricky to use -- often requiring overlapping addresses in practice -- but it wasn't in any way slower to use this wider address. That's external. The internal methods (the 8088, for example) using segment registers, were still fast. It doesn't have to be slower. It's just more complex, is all. \$\endgroup\$
    – jonk
    Sep 17, 2022 at 22:12
  • 2
    \$\begingroup\$ ARM Thumb uses 'literal pools' for 32 bit loads of registers. Thumb instructions are 16 bits wide, so no hope of getting 32 bits in one instruction. Instead the instruction has an offset from the program counter where it loads a 32 bit value from the effective address which is the literal pool. eg: PC[offset] -> reg. Otherwise, there's the common technique of having instructions to load to low and high bytes of the register. The problem is you need two fetches if your instructions are 8 bit, one for the instruction, the next for the operand. Decidely non- RISC. \$\endgroup\$
    – Kartman
    Sep 18, 2022 at 5:55
  • 3
    \$\begingroup\$ If you run out of instruction encodings then you should increase instruction width (to 12 or 16 bit). Adding address lines or register width doesn't help a bit. \$\endgroup\$ Sep 18, 2022 at 11:46
  • 2
    \$\begingroup\$ EM73P361A is a MCU with 12 bit address space, 4-bit ALU/accumulator and 8/12/16 bit instructions. \$\endgroup\$ Sep 18, 2022 at 11:57
  • 2
    \$\begingroup\$ @David777 I'm just really glad to see you working on a CPU. Brings me back to days of tears and heartache when I designed my first 8-bit one using 7400 series parts in 1974. I didn't know crap about how to design them but worked it out entirely on my own, slowly bit by bit. Finally got it working. Since then I've done many such and worked at Intel on their BX chipset. I love the stuff. Doesn't mean I'm any good at helping you with your design. But who can say? Maybe, \$\endgroup\$
    – jonk
    Sep 20, 2022 at 1:05

7 Answers 7

12
\$\begingroup\$

Different processors handle it in different ways. Since you mentioned 6502 in a comment I'll tell you how that one works.

The 6502 has a 64k address space. To address 64k you need 16 bits, or two 8 bit bytes. The address space is shared by code and data.

The 6502 instructions can be followed by zero, one or two bytes and have a number of addressing modes, some of them are:

  • Absolute - The opcode is followed by two bytes specifying the target address, LSB first.
  • Zero Page - The opcode is followed by one byte representing an address between $00 and $FF (0 and 255). These locations are known as Zero Page and have special addressing modes that take advantage of them only needing one byte to specify the address.
  • Relative - The opcode is followed by one byte representing an offset from the current Program Counter value, it is a signed value so gives an offset of between -127 and +128. This is used for branching instructions.
  • Indexed Indirect - The opcode is followed by one byte specifying a value between $00 and $FF, the X register is added to the value and the result used as a Zero Page address, the processor fetches the byte at that address and the following byte and uses them as the target address of the instruction.

There are some others addressing modes, but those are the ones most pertinent to your question.

The number of clock cycles need to execute an instruction depends partially on how many bytes the processor has to fetch. This is why Zero Page is preferred for storing performance sensitive data.

One other important thing with the 6502 is how it knows at what address to start executing code. Two bytes, $FFFC and $FFFD are called the RESET vector. When RESET is asserted, the program counter will be loaded with the values from these two locations and execution will begin at the resulting address. Because of this, 6502 computers usually have their firmware stored in ROM at the top of memory and these addresses point to the code to be run at bootup.

\$\endgroup\$
0
10
\$\begingroup\$

The 8080, Z80 and other 8 bit microprocessors used an 8 bit data bus with a 16 bit address bus. Instructions could be 1, 2, or 3 bytes long. In some cases, the second byte contained an offset from the present program counter address or from a register. If the instruction required a full 16 bit address, it would use three bytes, one for the instuction op code, and the other two for the address.

Have a look for 8080 or Z80 insruction sets and other documentation to see how they did things.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ I was looking at the 6502 in particular as I know it has a 16-bit address bus and 8-bit data bus. So when the instructions are required to be 2 or 3 bytes long, does this mean the fetching process will just take more clock cycles? \$\endgroup\$
    – David777
    Sep 17, 2022 at 20:55
  • 7
    \$\begingroup\$ Yes. Longer operands generate multiple bus accesses to complete the load or store. \$\endgroup\$ Sep 18, 2022 at 4:16
  • 1
    \$\begingroup\$ The 6502 also has special "zero-page" versions of most instructions, which execute (usually) in 1 clock cycle less than the normal instruction, because it only has to fetch 1 address byte (the high byte of the address is forced to 00) instead of 2 bytes. \$\endgroup\$
    – cjm
    Sep 18, 2022 at 6:43
  • 1
    \$\begingroup\$ The original 8051 architecture, for example, has twelve clock cycles per machine cycle. To some degree, I suspect you have to choose between transistor count, flexibility, and performance. \$\endgroup\$ Sep 18, 2022 at 8:13
  • 1
    \$\begingroup\$ The Z80 mainly uses full 16 bit addresses from either two direct bytes or a register pair (mostly the pair hl). The index registers ix and iy have an additional 8 bit offset applied but using this is slow. Also the jump relative instructions use a relative offset, so that code is easily relocatable. \$\endgroup\$
    – Ian Bland
    Sep 19, 2022 at 15:02
6
\$\begingroup\$

Almost all microcontrollers and microprocessors have addresses of at least 16 bits: it’s very hard to do anything useful with just 8 bits of address.

As you think about your own architecture, bear in mind that your data bus size doesn’t necessarily limit your address size. In fact, your data bus can be any size you wish, so long as your data I/O is smart enough to deal with variable transaction sizes.

To do this your instruction opcode set needs to support multiple sizes. Your instruction decoder and bus transaction units uses the opcode information to direct loading and storing of 8 bit, 16 bit or possibly longer values.

It’s also possible to perform multi-byte load/stores using successive load/store instructions. But beware: an interrupt in the middle of a multi-byte load/store can have unwanted side effects. Operations on data values need to be atomic, that is, uninterruptible. At the very least you’d surround a multi-byte load/store with an interrupt disable/enable. Program fetches should likewise be uninterruptible.

While you’re investing the effort in upgrading your opcode set to handle multi-byte operands, I would think that you want to handle addresses of least 24 bits. 32 bits would be even better, and more architecturally consistent with data types used in programs.

If you’re concerned about ‘wasted’ cycles to deal with longer address operands, most architectures use relative addressing modes to help with this. Examples include pc-relative short jumps, indexed addressing, and stack push/pops. None of these require fetching a long address, except for the first time to initialize the PC, index register or stack pointer, respectively.

Finally, yes, there are systems that use separate data and program memories. They’re called ‘Harvard’ architectures. The 8051 is an example; many modern RISC architectures are also internally constructed as Harvard using separate data and program buffers.

\$\endgroup\$
3
  • \$\begingroup\$ Thanks for your answer, it's very helpful. I see what you mean, the architecture can really be designed any way a designer wants. I don't plan on having a stack or anything fancy other than one very simple interrupt capability. What I still don't understand is how the memory then works in the architecture. Is it best to have 8-bit RAM for instruction and data, or go 16-bit and force the most significant byte as all zeros as the data bus is 8 bits wide? \$\endgroup\$
    – David777
    Sep 18, 2022 at 19:52
  • 1
    \$\begingroup\$ @David777 All the common 8-bit processors only have 8 bit data buses, so there's no point in using 16-bit RAM. They have completely separate Address and Data pins. You set a 16-bit address to read or write, then read or write the 8-bit data at tthat address. Multi-byte instructions require the processor to read the bytes one at a time. \$\endgroup\$
    – Simon B
    Sep 18, 2022 at 21:02
  • \$\begingroup\$ Thanks Simon, that makes sense! \$\endgroup\$
    – David777
    Sep 18, 2022 at 21:06
3
\$\begingroup\$

In your question, you say:

I have 17 instructions and cannot fit all information into instructions that are only a byte.

This represents one of the first critical architectural choices. How do you expand the number of "instruction encodings." Many options exist.

  • Add additional information to the processor state, and interpret instructions differently based on the state. Add instructions to change the state. In effect, have instruction prefix bits set in one instruction that affect the execution of following instructions. In the most common case, this may be a condition code (think, "equal zero") that changes the behavior of the next jump instruction. Or, a "skip next instruction if equal zero" instruction. Problems are that additional processor state needs to be saved and restored in interrupt contexts, and must be managed by code generators.

  • Use a form of Huffman coding, say, for registers in the instruction. Have one register be the preferred source and perhaps a different one for destination. If code can use the preferred registers much of the time, have a 1-bit register address encoding where 0->use the standard, and 1->do something else. The something else could involve consuming another byte to contain register addresses.

  • Combine the first and second option, where one instruction sets of selections (addressing modes, register, short offsets, longer offsets with extended instruction byte(s)), and the active instructions select from a small set of selections for their arguments.

  • So many other ways exist, all to encode the high probability choices in small instructions, with lower probability choices increasing the instruction length.

  • Or, you can go old school (like, say, the PDP-10) and have all instructions be fixed length, with plenty of bits to define all the options. The PDP-10 instruction set is remarkably symmetrical and elegant.

So I have been thinking about using a 16-bit address bus, instruction set and program counter, [...]

The size of the address bus, the data bus, the instruction set, and the program counter are completely independent of each other.

For discreet component CPUs, chip count is often most important. If so, the choices of data bus width may be driven by the number of memory chips and their access times. The width of the address bus may be determined by the chips required to buffer and latch addresses.

As CPUs became integrated, the cost of chip pins was higher than chip silicon, so adding many equivalent TTL devices to make bus width smaller such as multiplexing several functions onto a group of pins, became worthwhile. Your choices will depend on factors including what, if any, standard busses you may want to interface with.

The details of your instruction set, including how it is encoded, fixed or variable length, et. al. are not limited by the width of your busses.

\$\endgroup\$
2
\$\begingroup\$

Take the 8088 as an example.

It's a 16-bit CPU like 8086, but with an 8-bit bus.

Instructions are up to 6 bytes long so they obviously have to be read in one byte at a time. Just like the 8086 that can fetch two bytes at a time over the 16-bit bus.

To make code execution faster, they have a prefetch queue, basically a FIFO for opcodes to execute. As the instructions take multiple bus cycles to execute, the bus would be idle during opcode execution, so the idle time is used to read more opcodes into the prefetch queue when possible. This means that when executing typical code, the queue always has opcodes to execute so CPU does not need to wait until opcodes are read directly from memory. Except after jump/call instructions which clear the queue.

\$\endgroup\$
3
  • \$\begingroup\$ I have a somewhat unrelated question; your answer got me thinking. Regarding pipelining processor architectures like you are taking about above, can pipelining only be done in architectures with a separate address bus? If a shared bus is used for address and data, when the instructions are being executed, the bus is in use. If the address bus is separate, then it is free for fetching the next instruction? Is pipelining possible in single bus architectures? \$\endgroup\$
    – David777
    Sep 18, 2022 at 20:17
  • 1
    \$\begingroup\$ @David777 A bus cycle to access memory needs both address and data bused anyway, whether or not pipelining is used. So a shared bus does not prevent, nor a sepatate bus does not enable pipelining. \$\endgroup\$
    – Justme
    Sep 19, 2022 at 4:20
  • \$\begingroup\$ Yeah actually I understand, that was a silly question. I struggle to see how a simple architecture could be pipelined. Say for example the basic fetch cycle had 2 main steps: 1- Put program counter in bus and clock into the memory address register. 2- Access RAM and place instruction onto data bus, store in memory buffer register or another register and increment program counter. Then the actual instruction is performed. How can this process that from my understanding is the principle of processor fetch and execution cycle be pipelined? \$\endgroup\$
    – David777
    Sep 19, 2022 at 19:31
2
\$\begingroup\$

The 6502 is one of the nicest examples of using a 16-bit address bus on an 8-bit CPU. In addition to the named registers available to the programmer, the 6502 has half of the program counter along with a secondary register attached to each half of the address bus, each half of which can, during each cycle, be loaded from either the data bus or the output from the internal ALU, or hold its content. In each cycle, the address bus can either output the contents of the secondary registers, the program counter, or (for the upper address byte) the special values $00, $01, or $FF [6502 convention is to precede hex values with a dollar sign].

A typical 6502 instruction that illustrates how nicely things fit together is opcode $5D, "EOR abs,X". This instruction takes a 16-bit address which is placed immediately after the instruction LSB first, adds the 8-bit value of the X register, reads a value from the resulting address, exclusive ORs that with the accumulator, and latches the result back to the accumulator.

During the cycle after the 6502 fetches an opcode $5D the 6502 will output the program counter on the address bus and prepare to read an operand byte into the ALU input register (something it does after fetching every opcode, while the instruction decoder settles). By the time the operand-fetch cycle completes, the decoder will have determined that the instruction is using abs,X addressing mode, which will require reading a third byte from the instruction stream.

During the next cycle (during which the MSB of the address is being read), the program counter will be output to the address bus while the ALU receives the ALU data register (which holds the just-read LSB of the specified address), the contents of the X register, and an indication it should add the two values together, without any carry input.

During the cycle after that, the upper half of the address register will latch the value that was just received from memory while the lower half will output the value from the ALU. If there was no carry during the previous ALU operation, this address will be the correct address. If there was a carry from the ALU operation, this address will be incorrect but the 6502 will issue a memory read request to it anyway. In either case, however, the ALU will be fed the newly-fetched byte of memory along with a constant zero and an "add values plus one" command, but while it does that the 6502 will prepare to do one of two things depending upon whether there was a carry.

If there was no carry, the 6502 will output the program counter on the address bus while it places the newly-read value into the ALU along with the contents of the accumulator; the ALU's output will then be fed back to the accumulator on the cycle after that (while the 6502 is doing its unconditional operand fetch).

If there was a carry, the 6502 will latch the ALU output into the top half of the address bus while the lower half keeps outputting its present value, and another read cycle will be issued. Once this is done, the 6502 will process the second read value the same way as it would have processed the first in the absence of a carry.

An important thing to note is that while the 6502 behaves as though it computes an address by adding a 16-bit value to an 8-bit value, there is no 16-bit arithmetic hardware outside the program counter (whose top half can be incremented while the bottom half advances from $FF to $00). What looks like 16-bit addition is done as two 8-bit operations. The first half is performed while the upper half of the address is being fetched, and the second half is performed speculatively while the system performs a speculative read. Either the results of the read or the upper-address computation will be useless; the system will discard one of the results at the end of the cycle.

\$\endgroup\$
1
  • \$\begingroup\$ This is an excellent breakdown of the step-by-step processing of one of the more involved 6502 instructions. It's well worth reading the original MCS6500 Microcomputer Family Programming Manual; in particular, Appendix E ("Summary of the Addressing Modes") gives cycle-by-cycle breakdowns of how instructions run that, with a bit of thought, will illuminate how the processor implements its variable-byte instructions. \$\endgroup\$
    – cjs
    Dec 23, 2022 at 11:40
1
\$\begingroup\$

You can use an 8-bit latch, but you need to modify the control unit. In one cycle you issue the most significant bits into the latch, enable the latch with a signal like Address Latch Enable (ALE). Then, you issue the least significant bits directly to the memory. The two group of signals (output of latch and the LSB from the processor) are the address lines to the memory. This was used in Intel 8031 microcontroller.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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