Sponsored By

Reincarnated Return of the HRRG 4-Bit Computer (Part 3)

As part of the HRRG’s evolution over the past few years, the instruction set has experienced some interesting changes.

11 Min Read
HRRG max-0025-hrrg-featured-image.png
Image courtesy of Clive "Max" Maxfield, Joe Farr, and Nils van den Heuvel

In Part 1 of this mega-mini-series, I introduced Joe Farr and we revisited the concept of the Heath Robinson Rube Goldberg (HRRG) 4-bit computer, which has been on the drawing board for longer than I care to remember. In Part 2, I introduced Nils van den Heuvel and we discussed how the registers in the HRRG’s CPU have evolved over the years (you may wish to bounce over to Part 2 and refresh your mind regarding these registers before proceeding further).

The next thing for us to consider is the HRRG’s instruction set, which has also seen more than a few changes since it was first conceived deep in the mists of time.

The HRRG’s Latest and Greatest Instruction Set

The first thing to remember is that we wanted to be able to use a single 4-bit nybble to specify which instruction we were talking about. Obviously, this limited us to only 16 instructions. There’s a lot to wrap one’s brain around here, and I don’t intend to go into too much detail, but there are a couple of points that are well worth noting.

First, we decided to implement an ADDC (“add with carry”) instruction rather than an ADD (“add without carry”) because these are necessary to implement multi-nybble operations, and it’s a lot easier to make an ADDC work like an ADD by setting the carry flag to 0 before performing the operation than it is to make an ADD work like an ADDC. Similarly, we opted for a SUBB (“subtract with borrow”) rather than a SUB (“subtract without borrow”) because it’s easier to make a SUBB work like a SUB by setting the carry flag to 1 before performing the operation than it is to make an SUB work like a SUBB.

The same thing applies to the ROLC (“rotate left through carry”) and RORC (“rotate right through carry”). It’s easier to use these as starting points to implement alternative rotate and shift operations than it is to go the other way round.

As we see in the illustration below, the instruction opcode is followed by additional nybble operands that are used to specify the (source of data), if any, and   (the target or destination of the result), if any. These may be accompanied by additional and operands if required (“aop” stands for “additional operand”).

Originally, we had opted for a approach, but once Nils commenced his FPGA implementation, he said that presenting things in the order would make his life easier, so that’s what we did.

One thing that’s worth emphasizing is that both the source and target can be a CPU register or a memory location. For example, we can add a nybble in a CPU register to a nybble in memory and store the result in the register or in the memory depending on which we define as the source and which we define as the target.

HRRG max-0025-01-hrrg-instruction-set.png

  • #1 If the source is a 4-bit register or a 12-bit register, then there won’t be a (additional operand).

  • #2 If the source is a memory location (as indicated using the MD, MI, MX, or MR virtual registers), then the will be a 3-nybble address.

  • #3 If the target is a 4-bit or 12-bit register, then there won’t be a (additional operand).

  • #4 If the target is a memory location (as indicated using the MD, MI, MX, or MR virtual registers), then the will be a 3-nybble address.

  • #5 If the source is a 4-bit quantity (a CV4 or a 4-bit register), the target will typically be a 4-bit register or memory location. If the source is a 4-bit register and the target is one of the 12-bit registers (PC, SP, IX, GP), then the contents of the 4-bit register will be moved (copied) into the least-significant nybble (LSN) of the target register. If the source is a 12-bit register, the target will typically be a 12-bit register or a memory location (see note #6); if the source is a 12-bit register and the target is a 4-bit register, then the contents of the LSN of the 12-bit register will be copied into the 4-bit register.

  • #6 If the contents of a 12-bit register are copied into memory, the target operand will be the least-significant address of a 3-nybble field. If the contents of memory are copied into a 12-bit register, the source operand will be the least-significant address of a 3-nybble field.

  • #7 If the source is a 4-bit quantity (a CV4, a 4-bit register, or a memory location), then a 1-nybble value will be pushed onto the stack and SP = SP + 1. If the source is a 12-bit quantity (a CV12 or a 12-bit register), then a 3-nybble value will be pushed onto the stack and SP = SP + 3. (The stack pointer will be incremented after the PUSH.)

  • #8 If the target is a 4-bit register or a memory location, then SP = SP – 1 and a 1-nybble value will be popped off the stack (the stack pointer will be decremented before the POP). If the target is a 12-bit register, then a 3-nybble value will be popped off the stack (ending up with SP = SP – 3).

  • #9 There are no RTS (“return from subroutine”) or RTI (“return from interrupt”) instructions – the same effect is achieved by using a POP instruction to retrieve the return address off the stack and load it into the PC.

  • #10 The JMP and JSR opcodes (instructions) are followed by a control nybble, a nybble, and (possibly) a in the form of a 3-nybble address. The control nybble is used to perform unconditional jumps or conditional jumps. The least-significant three bits of the control nibble point to the bit to be tested in the 8-bit status register formed from S1 and S0; this will be in the range 000 to 111 (0 to 7). Assuming a 0 in the most-significant bit of the control nibble, then a value of 1 in the selected status bit will cause a jump; for example, JMP %0001 is equivalent to a “Jump if Zero”. By comparison, a 1 in the most-significant bit of the control nibble will invert the operation of the jump; for example, JMP %1001 is equivalent to a “Jump if Not Zero”. Note that status bit 7 [bit 3 in S1] is a hard-wired 1, which is equivalent to an unconditional jump.

  • #11 As part of its execution, the JSR instruction will cause the return address (the 3-nybble address of the next opcode) to be automatically pushed onto the top of the stack and SP = SP + 3. Similarly, when an interrupt occurs (assuming the interrupt status flag is enabled), the CPU will cause the return address (the 3-nybble address of the next opcode) to be automatically pushed onto the top of the stack and SP = SP + 3 prior to handing control over to the interrupt service routine (ISR), where the interrupt vector (IV) pointing to the first instruction in the ISR is stored in the three most-significant nybbles of the main memory (addresses $FFD, $FFE, and $FFF).

 

What’s Your Status?

Before we proceed, we really need to consider the HRRG’s status register, which is where we find the status bits (or flags) that reflect the results from an operation. In the case of the HRRG, we have two 4-bit status registers in the CPU that we call S0 and S1.

max-0025-02-status-registers.png

Since the HRRG has only 16 instructions, we don’t have any special instructions dedicated to clearing (to 0) or setting (to 1) the status bits. However, we can achieve the same effect using AND and OR operations. For example, if we wanted to clear the C flag, we could do so using the following instruction: 

 

AND %1011, S0

 

The corresponding machine code would be as follows:

 

$7 $A $B $4

 

Where $7 specifies the AND operation, $A says the source (the ) is a CV4 (constant 4-bit value), $B is the constant value itself (the ), and $4 says that the target (the ) is register S0 (since the target is a CPU register, there is no ).

As a somewhat related point, we don’t have special instructions to enable or disable interrupts. However, we can achieve the same effect by setting or clearing bit 0 in status register S1.

Most processors have an unconditional JMP (“jump”) instruction along with a suite of jumps based on the state of the status flags, like JZ (“jump if zero”), JNZ (“jump if not zero”), JN (“jump if negative”), JNN (“jump if not negative”), and so forth. We do things a little bit differently because we only have a JMP instruction. However, as discussed in point #10, this instruction is followed by a control nybble, which is used to perform both unconditional and conditional jumps. Also, even though we have only a JMP instruction at the CPU level, we can have JMP, JZ, HNZ, JN, JNN... etc. mnemonics in our assembly language because our assembler can translate these into the appropriate control codes.

Also of interest is the fact that our JSR (“jump to subroutine”) instruction has the same capabilities with regard to jumping depending on the values of status bits as does our JMP instruction.

And, as one more related point, we don’t have special “return from subroutine” or “return from interrupt” instructions. In this case, the same effect is achieved by popping the return address off the top of the stack into the PC.

When Instructions and Status Bits Collide

The following table illustrates which of the O (Overflow), C (Carry), Z (Zero) and N (Negative) status bits are affected by the various instructions. 

max-0025-03-hrrg-status-register-vs-instructions.png

  • #1 As part of rotating the target value one bit to the left, the most-significant bit (MSB) of the target value is copied into the C flag; at the same time, the original contents of the C flag are copied into the least-significant bit (LSB) of the target value.

  • #2 As part of rotating the target value one bit to the right, the LSB of the target value is copied into the C (carry) flag; at the same time, the original contents of the C flag are copied into the MSB of the target value.

  • #3 The Z flag is set to 1 if the values being compared are equal, otherwise it’s cleared to 0.

  • #4 The values being compared are considered to be 4-bit unsigned integers. The C flag is set to 1 if the value in the is greater than the value in ; otherwise, it’s cleared to 0.

  • #5 If the target is status register S1, then its flags will be loaded from the stack and the contents of S0 will not be affected; if the target is S0, then its flags will be loaded from the stack; if the target is any other 4-bit location (register or memory), then the N and Z flags will behave as usual.

  • #6 These flags work as usual for a 4-bit target with one exception — if the target is one of the status registers S0 or S1, then the flags in S0 will not be automatically updated; if the source is a 12-bit register and the target is a 4-bit register, then the Z and N flags will be based on the contents of the least-significant nybble of the 12-bit register (i.e., the only bits to be copied); if the source is a 12-bit register and the target is memory, then Z flag will be set based on the contents of the entire 12-bit register and the N flag will be set based on the contents of the MSB of the most-significant nybble (MSN) of the 12-bit register.

  • #7 The Z flag will be set based on the contents of the entire 12-bit register and the N flag will be set based on the contents of the MSB of the MSN of the 12-bit register.

A Matrix of Possibilities

Given the fact that we have a 4-bit data bus, a 12-bit memory space, and both 4-bit and 12-bit CPU registers, things can become a tad confusing when transferring data back and forth between the 4-bit and 12-bit domains.

In the early days, Joe and I tried to tie the various possibilities down and capture our thoughts in the form of notes, but we were never 100% sure that we’d covered every conceivable combination and corner case. You can only imagine our chagrin when Nils came onboard and whipped up the following handy-dandy source-to-target data width mappings graphic.

HRRG max-0025-04-hrrg-source-vs-target-matrix.png

As soon as I saw this chart, I thought (a) “That’s brilliant” and (b) “Why on Earth didn’t I think of that myself?”

So, that’s the current state of play. You are now up to date with where we are at with respect to the HRRG 4-bit computer. Nils is working away furiously on his gate/register-level FPGA-based implementation, about which I will blog further in the future. In the meantime, as always, I welcome your comments, questions, and suggestions.

About the Author(s)

Clive 'Max' Maxfield

Clive "Max" Maxfield is a freelance technical consultant and writer. Max received his BSc in Control Engineering in 1980 from Sheffield Hallam University, England and began his career as a designer of central processing units (CPUs) for mainframe computers. Over the years, Max has designed everything from silicon chips to circuit boards and from brainwave amplifiers to Steampunk Prognostication Engines (don't ask). He has also been at the forefront of Electronic Design Automation (EDA) for more than 35 years.

Well-known throughout the embedded, electronics, semiconductor, and EDA industries, Max has presented papers at numerous technical conferences around the world, including North and South America, Europe, India, China, Korea, and Taiwan. He has given keynote presentations at the PCB West conference in the USA and the FPGA Forum in Norway. He's also been invited to give guest lectures at several universities in the US and at Oslo University in Norway. In 2001, Max "shared the stage" at a conference in Hawaii with former Speaker of the House, "Newt" Gingrich.

Max is the author and/or co-author of a number of books, including Designus Maximus Unleashed (banned in Alabama), Bebop to the Boolean Boogie (An Unconventional Guide to Electronics), EDA: Where Electronics Begins, FPGAs: Instant Access, and How Computers Do Math.

Sign up for the Design News Daily newsletter.

You May Also Like