So far in ESE101, I’ve introduced a fictional microcontroller that has three instructions: ADD, SUB, and MOVE. The microcontroller uses three simple steps to run each instruction from memory and then move on to the next sequential instruction in memory.
Is this all a microcontroller can do? Just grab instruction at address 0, then address 2, then address 4, and so on until the power goes out? It can only run the same instructions every time? Of course not. That’s crazy talk. Why would you even suggest such a thing? Oh, don’t go accusing me of bringing this up, you’re the one you who asked if that’s all a microcontroller can do. [EDIT: I am actually considering leaving this in, it just seemed funny when I typed it, so I’m going to let you read my stupidity and tell me if this belongs.]
A program that runs the exact same instructions every time is not terribly useful. The interesting part of most programs is where decisions are made, for instance:
- If a temperature value is greater than 78 degrees Fahrenheit, then turn on the air conditioning.
- If a car’s sensors detect an impending collision, then slam on the brakes.
- If a password == “Zero Cool”, then allow a user to login.
With ADD, SUB, and MOVE we can’t compare a temperature value to 78, much less do anything about it. Let’s add a few new instructions that will let us do just that!
At a high level we’ll need:
- a way to compare two numbers,
- a place to store the results of that comparison, and
- a way to conditionally do something based on that comparison’s result.
We’ll start with #2 by defining a place to store the results of comparisons. That place is a new register for our microcontroller, called the Status Register, or SR.
The Status Register is a special register. It doesn't hold regular values like R0-R3, or even PC. Instead, the Status Register has a few different bits that instructions can use to check the status of previous operations. For our fictional microcontroller, SR has 2 bits:
- Negative bit or SR.N: This bit is set to 1 when the result of an operation is negative and set to 0 when the result is positive.
- Zero bit or SR.Z: This bit is set to 1 when the result of an operation is 0 and set to 0 when the result is not 0.
When I write “SR.Z”, I mean "the Zero (or Z) bit in the SR register", and “SR.N” means “the Negative (or N) bit in the SR register.”
Now that we have somewhere to store the results of a comparison, let’s add a new instruction that does comparisons. We’ll call it the compare instruction, abbreviated as CMP. CMP is sort of like subtraction, with an extra side effect. Our microcontroller defines CMP as:
CMP input1, input2 input1 can be either a register or immediate value. input2 must be a register. The CMP instruction subtracts the value in input1 from the value in register input2 and sets the N and Z bits in SR based on that subtraction. Note that the input1 and input2 registers are not changed by CMP. Only the SR register is changed by the CMP instruction, nothing else is changed
CMP is a little strange at first, so let’s work through a couple of examples. Here are the registers with some data I just made up. The bits in SR are initially set to 0.
Let’s say the next instruction to run is:
CMP R1, R3
CMP does the subtraction operation “R3 - R1”, but it does not change R1 or R3! The only thing the CMP instruction changes are the bits in SR. Let’s do the math:
result = R3 - R1 = 37 - 37 = 0
The result of subtraction is 0; what happens to the SR.N and SR.Z bits? Let’s look at each in turn:
- SR.N: Is the result negative? No: SR.N = 0
- SR.Z: Is the result zero? Yes: SR.Z = 1
After the CMP instruction runs (remember the PC is incremented by 2 after each instruction), the registers are now (values that changed are shown in red):
Let’s try another example:
CMP #50, R0
This CMP instruction subtracts the immediate value 50 from the value in R0, only changes the SR bits. Remember, the CMP instruction does not change its input registers! The math is:
result = R0 - 50 = 42 - 50 = -8
The result of subtraction is -8; let’s look at what happens to the SR bits:
- SR.N: Is the result negative? Yes: SR.N = 1
- SR.Z: Is the result zero? No: SR.Z = 0
After this CMP instruction runs, the registers are:
We’ve seen how the CMP instruction compares two numbers and stores information about the result in SR. The final piece of today’s puzzle is a couple of new instructions that do different things based on SR’s value.
Conditional Jump Instructions
The new instructions are called conditional jump instructions. A conditional jump instruction looks at the SR and, depending on what’s in SR, either runs the next sequential instruction in memory or jumps to a different, non-sequential instruction in memory. A conditional jump is sometimes called a branch or a conditional branch.
Conditional jump instructions change the three steps that the microcontroller uses to run instructions. Remember the old three steps were:
- Read the instruction at the memory address in the PC register.
- Run the instruction.
- Update the PC register to point to the next instruction in memory.
With conditional jump instructions added to the mix the microcontroller uses these three steps instead:
- Read the instruction at the memory address in the PC register.
- Run the instruction, which may change the PC.
- If the instruction didn’t change the PC, then update the PC register to point to the next sequential instruction in memory.
Since these new conditional jump instructions can change the PC, the microcontroller doesn’t always blindly increment PC by 2 anymore; it only does PC=PC+2 if the instruction didn’t already change the PC.
Our microcontroller defines conditional jumps as as:
JN newPC JZ newPC newPC is an immediate value. The JN conditional branch instruction (“jump if SR.N==1”) looks at the SR.N bit, and if SR.N is 1 then the PC is set to newPC. If SR.N is 0 then JN does nothing. JZ (“jump if SR.Z==1”) is the same as JN, except that JZ uses the SR.Z bit instead of SR.N. Note that JN and JZ do not change anything other than (possibly) PC. SR is unchanged.
Let’s see an example of conditional jumps starting with the registers we already have, and a 16 byte memory that looks like this:
|0||CMP R1, R3|
|2||CMP #50, R0|
|6||SUB R1, R2|
|8||ADD #5, R0|
|12||ADD R1, R2|
|14||MOVE R2, R3|
Since the PC is 4, the microcontroller reads the instruction a memory location 4:
JN (“jump if SR.N==1”) looks at the N bit in SR, and:
- If SR.N == 1 then the PC is set to JN’s argument, which is 10.
- If SR.N == 0 then JN does nothing.
The microcontroller sees that SR.N == 1, and so the PC is set to 10 to find the next instruction. The microcontroller jumps over and ignores the instructions at memory locations 6 and 8, and the next instruction to run is at memory location 10. The microcontroller does not do its normal “PC = PC+2” step here since the JN instruction already changed the PC.
Here are the registers after JN is done:
Note that the only thing that JN changes is the PC register; no other register or memory location is affected. Even the SR is not changed, only CMP affects the SR.
Since PC=10, the microcontroller reads the next instruction from memory location 10 and finds:
JZ (“jump if SR.Z==1”, not Jay Z), looks at the Z bit in SR, and:
- If SR.Z == 1 then the PC is set to JZ’s argument, which is 14.
- If SR.Z == 0 then JZ does nothing.
The microcontroller looks at SR.Z, sees that it’s 0, and the JZ instruction is done: it does nothing. Since the JZ instruction did not change the PC, the microcontroller increments the PC by 2 and the next sequential instruction will be run. The new register values look like this:
Now that our microcontroller can compare numbers and conditionally do different things based on those comparisons, we have a microcontroller that can run any conceivable program.
Next week we’ll move beyond our simple, fictional microcontroller and introduce the real microcontroller and hardware evaluation platform used for the rest of the ESE101 series.
We discussed conditional jump instructions today, which conditionally change the PC based on the bits in SR. Most microcontrollers also have an unconditional jump instruction which always changes the PC whenever the instruction is run. It might look like "JMP 50," meaning when the JMP 50 instruction is run the microcontroller unconditionally (always) changes the PC to 50 and continues execution from the instruction at memory location 50.
How Many Instructions Are Actually Needed?
Random aside: It turns out that it’s possible that a microcontroller with only a single instruction can execute any conceivable program. Check out the wikipedia page for One instruction set computer for more info. It's more of an academic exercise than a computer you'd actually like to work on, but it's an interesting idea.
This post is part of a series. Check out the complete Embedded Software Engineering 101 series here.