A switch/case Implementation in Assembly
Often, when programming in assembly, we run across situations where we'd like to implement a programming construct from a high-level language like C. C's SWITCH statement finds many uses in embedded applications, but no such construct exists natively in the assembly language. However, it is fairly easy to recreate the functionality using the microcontroller's fundamental operations along with an infrequently used macro feature of the assembler and some good old Boolean algebra trickery that many of us have long since forgotten.
Boolean Algebra Refresher: The XOR Operator
In order to create the switch/case construct in assembly, we need to review some of the fundamental properties of the XOR operator.
Truth Table
Out | In1 | In2 |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
1 | 1 | 0 |
0 | 1 | 1 |
Algebraic Properties
Property | Notation | Description |
---|---|---|
Commutative | A ⊕ B = B ⊕ A | Operand order doesn't change the result. |
Associative | (A ⊕ B) ⊕ C = A ⊕ (B ⊕ C) | Operand grouping doesn't change the result. |
Non-Indempotency | A ⊕ A = 0 | XORing an operand with itself is zero. |
Identity | A ⊕ 0 = A | XORing an operand with zero doesn't change the operand. |
These properties are used in combination with each other to perform a little trick of logic that will be outlined in the "Step-by-Step Analysis" section. Without a working knowledge of these properties, the code won't make much sense.
The Code
In this code snippet, SWITCH is a register (variable) that contains the value we are trying to match to each of the cases. The labels CASE1, CASE2, and CASE3 are constants and are the values we are checking against the one stored in SWITCH. The labels LABEL1, LABEL2, and LABEL3 are the names of subroutines we want to jump to for each match condition.
2
3
4
5
6
7
8
9
10
xorlw CASE1
btfsc STATUS, Z ; If SWITCH = CASE1, jump to LABEL1
goto LABEL1
xorlw CASE2^CASE1
btfsc STATUS, Z ; If SWITCH = CASE2, jump to LABEL2
goto LABEL2
xorlw CASE3^CASE2
btfsc STATUS, Z ; If SWITCH = CASE3, jump to LABEL3
goto LABEL3
This code snippet takes advantage of the properties of the XOR operator and the assembler's ability to perform calculations on constants at build time (more in the "Step-by-Step Analysis" section). There are several variations floating around the net, so this is certainly not the only way to implement a switch-like construct in assembly. The previous code snippet should work on any 8-bit PIC® microcontroller. With minor modifications to the syntax, it should also work on a 16-bit PIC microcontroller and dsPIC® Digital Signal Controllers.
Step-by-Step Analysis
movwf SWITCH, w
This simply moves the value from the register labeled SWITCH into the W register. Mathematically, we can write this as:
W = SWITCH
xorlw CASE1
This performs a bitwise exclusive OR operation (at runtime) between the W register and the constant CASE1. The result is stored in W. Mathematically, this can be written as:
W = W ⊕ CASE1
Substituting for the original value of W established on line 1:
W = SWITCH ⊕ CASE1
btfsc STATUS,Z
This tests the outcome of the previous operation. If the result of SWITCH ⊕ CASE1 is zero, then the Z bit in the STATUS register will be set. So, what this line is saying is that if the Z bit is clear (the previous operation did not result in a zero), then skip the next instruction. The reason we are performing this test is based on the property of non-idempotency: A ⊕ A = 0. So, if the value in SWITCH is the same value as CASE1, then SWITCH ⊕ CASE1 = 0. If that is the case, we found our match and want to execute the next instruction.
goto LABEL1
This line takes us to a subroutine with the name LABEL1 to handle the situation when SWITCH ⊕ CASE1 = 0. If the Z bit in the STATUS register was not set above, then this instruction would be skipped and we would test the next condition.
xorlw CASE1^CASE2
This line is where most of the magic occurs. We are playing several tricks at once. The first thing to point out is that the ^ symbol is the XOR operator in the assembler's macro language. Macro operators perform calculations on the computer at build time and are never executed on the PIC microcontroller. Therefore, this operator may be used only with constants or other values that are known at build time. In this example, CASE1, CASE2, and CASE3 are all constants defined in our code and therefore known at build time. So, this line of code will XOR the value in the W register with the calculated value CASE1^CASE2. Mathematically, this can be written as:
W = W ⊕ (CASE1 ⊕ CASE2)
However, W contains the result of the previous operation SWITCH ⊕ CASE1. Substituting for the previous value of W:
W = (SWITCH ⊕ CASE1) ⊕ (CASE1 ⊕ CASE2)
At this point, we can take advantage of some of XOR's properties. First, we use the associative and commutative properties to rewrite the equation:
W = (SWITCH ⊕ CASE2) ⊕ (CASE1 ⊕ CASE1)
Next, we use the property of non-idempotency (A ⊕ A = 0):
W = (SWITCH ⊕ CASE2) ⊕ 0
And finally, we use the identity property (A ⊕ 0 = A):
W = SWITCH ⊕ CASE2
This is exactly what we want to test to see if SWITCH = CASE2! Now, this is just like what we did on line 2. From this point forward, the code just repeats itself with different values.
The code may be repeated for as many CASEn values you wish to use.