; move 16-bit stuff into registers
mov _u16_a, W2
mov #0xD38F, W0
mov #1728, W1
; move 8-bit stuff into registers
mov.b _u8_a, WREG
mov.b #0xD8, W0
mov.b #23, W3
; move from register to register
mov W1, W2
mov.b W0, W1
; move from register to variable
mov W1, _u16_a
mov.b WREG, _u8_b
; zero-extend (make an 8-bit number 16 bits)
ze W0, W0
; clear a register (or the LSB) i.e. make it 0
clr W1
clr.b W1
; add
add W0, W1, W2 ; W0 + W1 = W2
add.b W0, W1, W2 ; LSB of W0 + LSB of W1 = W2
; subtract
sub w2, w1, w0 ; w2 - w1 = w0
sub.b w2, w1, w0 ; LSB of w2 - LSB of W1 = LSB of w0
; increment
inc W2 ; W2 ++
inc.b W3 ; LSB of W3 ++
; decrement
dec W2 ; W2 --
dec.b W2 ; LSB of W2 --
; can also inc2 and dec2
; bitwise AND
and W0, W1, W2 ; W2 = W0 & W1
and.b W0, w1, w2 ; lsb of w2 = lsb of w0 & lsb of w1
; bitwise OR
ior w0, w1, w2 ; w2 = w0 | w1
ior.b w0, w1, w2 ; lsb of w2 = lsb of w0 | lsb of w1
; bitwise XOR
xor w0, w1, w2 ; w2 = w0 ^ w1
xor.b w0, w1, w2 ; lsb of w2 = lsb of w0 ^ lsb of w1
; bitwise complement
com W0, W0 ; W0 = ~W0
com.b W1, W1 ; lsb of W1 = ~lsb of W1
; left shift
sl w1, #3, w1 ; w1 = w1 << 3
; right shift
lsr w1, #2, w1 ; w1 = w1 >> 2
; exam & ebook only: print all registers
rcall _print_regs
; branch unconditionally
bra LABEL
; label
LABEL:
; compare two registers
cp w0, w1
cp.b w0, w1 ; compare lsb of w0 to lsb of w1
; compare to zero
cp0 w1
cp0.b w0
; > (Greater Than, Unsigned)
BRA GTU, LABEL
; < (Less Than, Unsigned)
BRA LTU, LABEL
; >= (Greater than or Equal, Unsigned)
BRA GEU, LABEL
; <= (Less than or Equal, Unsigned)
BRA LEU, LABEL
; == (W1 - W2 = Zero)
BRA Z, LABEL
; != (W1 - W2 is Not Zero)
BRA NZ, LABEL
; WHILE
; ---------w0------
;; while (u16_b > 3) {
while:
mov _u16_b, w0
cp w0, #3
bra LEU, end_while
; do stuff
bra while
end_while:
; more code
; DO WHILE
do:
; do stuff
; ---------w0------
;; while (u16_e > 3)
mov _u16_e, w0
cp w0, #3
bra GTU, do
; more code
; IF
; ------w0------
;; if (u16_f > 3) {
mov _u16_f, w0
cp w0, #3
bra LEU, end_if
; then do stuff
end_if:
; more code
; IF-ELSE
; ------w0------
;; if (u16_g > 3) {
mov _u16_g, w0
cp w0, #3
bra LEU, else
; then do stuff
bra end_if
else:
; otherwise do other stuff
end_if:
; more code
; && AND
; both conditions must be true
; if 1st is false, jump to "false" code
; if 2nd is false, jump to "false" code
; after "true" code, jump PAST "false" code
; ----------w0--------------w1-------
;; while ((u16_b > 3) && (u16_c == 0)) {
while:
mov _u16_b, w0
mov _u16_c, w1
cp w0, #3
bra LEU, end_while
cp0 w1
bra NZ, end_while
; do stuff
bra while
end_while:
; more code
; || OR
; one condition must be true
; if 1st is true, jump to "true" code
; if 2nd is false, jump to "false" code
; after "true" code, jump PAST "false" code
; ----------w0--------------w1-------
;; while ((u16_b > 3) || (u16_c == 0)) {
while:
mov _u16_b, w0
mov _u16_c, w1
cp w0, #3
bra GTU, do_stuff
cp0 w1
bra NZ, end_while
do_stuff:
; do stuff
bra while
end_while:
; more code
; ! NOT
; the condition must = 0
; ----------w0------
;; while (!u16_d) {
while:
mov _u16_b, w0
cp0 w0
bra NZ, end_while
; do stuff
bra while
end_while:
; more code
; multiply
mul.uu W0, W1, W2 ; w3:w2 = w0 x w1 (destination register has to be even)
; divide
repeat #17
div.u w2, w3
; w1 contains the remainder (%)
; w0 contains the quotient (/)
_u16_x - unsigned 16-bit variable uint16_t
_u8_x - unsigned 8-bit variable uint8_t
32-bit, signed, pointer, and array variables will be discussed later.
The PIC24 has 16 working registers called W0-W15. They are basically places to stick numbers, like the memory on a calculator.
Let's talk about registers:
W0 is sometimes called WREG, and we will discuss it shortly, as it is key to dealing with 8-bit values, and causes a lot of confusion.
The registers are 16 bits, so if we want to store a 32-bit value, we'll need to use two registers. That's a Lab 4 / Exam 2 topic, so it will be covered later on.
W15 is the stack pointer. Don't ever use that unless you're messing with the stack, or your code won't compile!
According to the ebook, "if your code modifies W8-W14, it must restore the original value before returning" [1]. In practical terms, don't use these registers! Stick to W0-W7.
As stated earlier, you're not judged on readability or commenting. However for your own sake, I would copy the line of C you're trying to translate into the answer box, make it a comment, and then do your register assignments. To make a comment, prefix the line with a semicolon (;).
The provided solution goes further, showing where the output variable is placed, and giving intermediate register assignments:
But I just follow these two rules:
If you do something to a variable (e.g. here we are complementing, then left shifting u16_j), just put the results back into the same register (W2).
If you do an operation with two registers (e.g. bitwise ANDing W2 and W3) store the results in the lower-numbered register (W2).
If a variable appears more than once, it's often easier to just put a copy into a different register. For example in Spring 24 #5:
I put u16_i in w0. Later I need to add 0x15 to it. I put another copy of u16_i into w5. That one gets left shifted.
A literal is a number. Precede literals with a # sign. If you forget the #, the compiler will think you're referring to a memory address and cause problems.
#42 ; 42 (decimal)
#0x2A ; 42 (hex)
#052 ; 42 (octal)
#0b101010 ; 42 (binary)
Some PIC assembly instructions allow you to use a literal as an operand. For example:
ADD W1, #7, W2
This does:
W2 = W1 + 7
The problem is that the instructions limit the size of the literal you can use, and these size limits are different depending on the instruction! You need to look at the "PIC Instruction Set Summary" to see whether the instruction you're trying to use allows 4, 5, 8, 10, 11, or 14 bit literals. If you don't have access to the Summary or don't feel like getting it out, it's better to just put the literal into a register, unless it's really small (-15 <= n <= 15).
To call a C function:
use rcall
remove parenthesis
precede with underscore
So in C: my_function()
becomes in assembly: rcall _my_function
(As you are hopefully aware, MOV isn't really moving anything, it's copying it. However everyone just calls it "move", so we will too.)
The first part of every problem is moving the contents of variables and literals into registers.
move 16-bit literal into register
mov #0xB390, w1
(don't forget the # sign)
move 16-bit variable into register
mov _u16_j, w2
(don't forget to precede the variable name with an underscore)
If we just put u16_j in w2 and wanted a copy in w4, we could do it like this:
move register to register
mov w2, w4
At the end of every problem we have to put our answer into the answer variable
move 16-bit variable into register
mov W0, _u16_a
This is one of the major sources of confusion and/or errors in the early part of the course.
If you're doing anything with 8-bit variables (u8_something), you need to be using .b after your instructions.
If you're using byte-sized quantities and a variable in the same instruction it's WREG, otherwise it's W0.
; 8-bit operation - use WREG
mov.b _u8_i, WREG
; 16-bit operation - call it W0
mov W0, _u16_i
; This is okay - we can use other registers with 16-bit variables
mov _u16_i, W3
; This may run, but don't do it
mov _u16_j, WREG
; This is an error - if W0 interacts with a variable in byte mode we must call it WREG
mov.b _u8_i, W0
; This is an error - registers W1 through W15 do not support byte mode when copying to data memory locations (variables)
mov.b W2, _u8_j
; This is fine, it copies the LSB of W0 to the LSB of W1. We're not doing anything with variables.
mov.b W0, W1
; All good, we're not doing anything with variables.
mov.b #23, W3
; No variables here either:
mov.b #0x23, W0
Some other important notes:
If you have multiple 8-bit variables, you'll have to move them one at a time into WREG and then put them somewhere else (except for the last one).
mov.b _u8_i, WREG
mov.b W0, W1
mov.b _u8_j, WREG
mov.b W0, W2
mov.b _u8_k, WREG
; Now u8_i is in W1, u8_j is in W2, and u8_k is in W0.
Do not interact variables/numbers of different bit lengths.
mov.b _u8_i, WREG
mov _u16_j, W1
add W0, W1, W2
This results in a situation like the following (using decimals instead of binary for clarity):
If the MSB of W0 is all zeros, everything will work as expected. 04 + 73 = 77. But if ??? contains data, perhaps from previous use of this register, the code will run but give you crazy results.
Often you will have an 8-bit variable, but everything else in the problem is 16 bits. Use zero extension to make an UNSIGNED 8-bit variable into an UNSIGNED 16 bit variable by filling the MSB with 0s. This prevents the aforementioned problem of commingling them.
mov.b _u8_i, WREG
ze W0, W0
The value in W0 is now 16 bits, and we don't need to use .b anymore. This also works with literals:
mov.b #0xe1, WREG
ze W0, W0
W0 now contains #0x00e1.
For signed variables, you have to use sign extension, which will be covered later.
This very helpful instruction does not get covered in the lectures.
CLR W1
W1 is now 0x0000
It has a .b mode too
CLR.B W1
W1 is now 0x??00
You could use it instead of MOV #0, if you want to put a zero somewhere.
;; u16_a = 0
clr W0
mov W0, _u16_a
The lecture slides and ebook cover a lot of different versions of these instructions. All you need for writing code for Exam 1 is the basic, boring 3-operand version of each one:
Add register to register
; + =
add W0, W1, W2
Adds W0 and W1, puts the result in W2 (W2 = W0 + W1)
Subtract register from register
; - =
sub w2, w1, w0
Subtracts W1 from W2, puts the result in W0 (W0 = W2 - W1)
Of course, you need to use the .b version of each if you're working with 8-bit quantities:
; --w0----w1------w0
;; u8_k = u8_i + u8_j
mov.b _u8_i, WREG
mov.b W0, W1
mov.b _u8_j, WREG
add.b W0, W1, W0
mov.b WREG, _u8_k
According to the ebook, "Because the add{.b} Wb, Ws, Wd and sub{.b} Wb, Ws, Wd instructions only allow two input operands, you must rewrite arithmetic expressions into groups of two-operand expressions" [2]. In other words, you can only add two things together at a time, as demonstrated by this example from the ebook:
; ------------------W5-----------------W6-----
; W0----- W1------ W2----- W3------ W4
;; u16_c = u16_a + u16_b + 0x5F32 - u16_c;
mov _u16_a, W1
mov _u16_b, W2
mov #0x5F32, W3
mov _u16_c, W4
add W1, W2, W5
sub W3, W4, W6
add W5, W6, W0
mov W0, _u16_c
In order for your assembly code to get correct results, you need to perform the calculations in the same order as the C code you are translating. The bad news is that C has a complex order of operations:
The good news is that in this class you should be fine following the simple PEMDAS rule you learned in grade school math. Thus, if you are translating something like this...
;; u16_a = 3 * (u16_b + u16_c)
... clearly you should add u16_b and u16_c before multiplying by 3.
If you want to increment or decrement, you can do it directly to the variable:
;; u16_c++
inc _u16_c
;; u8_a--
dec.b _u8_a
You can also increment or decrement a quantity in a register (make sure you specify a destination register as well):
inc w0, w1
; w1 = w0 +1
dec W0, W0
; just subtracts 1 from W0
There are even increment and decrement by two instructions:
;; u16_c += 2
inc2 _u16_c
;; u16_d = u16_d - 2
dec2 _u16_d
The four bitwise operators (&, |, ^, ~) work differently than the logical operators you are familiar with from Boolean algebra. Bitwise operators, as their name implies, fiddle with individual bits. Each of them has a direct PIC24 translations. The logical operators in C (&&, ||, !) do not have assembly counterparts and we have to deal with them differently, which we'll discuss in a later section.
There are a lot of different forms of the bitwise instructions. For example, the Chapter 4 Lecture notes has a whole page of them just for bitwise AND:
However, do not be alarmed. When we are writing code for the programming problems and in the labs, one basic form of each will suffice. (As long as you are using the correct 8 or 16 - and later, 32-bit - version.)
Bitwise AND
and W0, W1, W2
; compares each bit of W0 and W1,
; if the bits are both 1, makes that bit of W2 a 1, otherwise makes that bit of W2 a 0.
Bitwise OR (8-bit version)
ior.b W0, W1, W2
; compares each bit of the LSB of W0 and W1,
; if either of the bits are 1, makes that bit of W2 a 1, otherwise makes that bit of W2 a 0.
Bitwise XOR
xor W0, W1, W2
; compares each bit of W0 and W1,
; if the bits are different, makes that bit of W2 a 1, otherwise makes that bit of W2 a 0.
Bitwise complement
com W0, W1
; puts the opposite bits of W0 into W1
We can use bitwise AND to clear certain bits that we want to be 0. Bitwise OR can be used to set bits to 1. Bitwise XOR can be used to toggle bits to their opposite. For example, let's say we have 1111, and we want the first three bits to be 0. We bitwise AND it with 0001. Boom! The result is 0001. This is summarized in the below table, and will be discussed more in the Exam review as it is an Exam 1 topic.
For the sake of clarity, pretend the registers are only 4 bits.
It's easy to shift unsigned 8- and 16-bit numbers left and right.
Shift left (<<)
sl W0, #5, W1
Puts W0, shoved over to the left by 5 zeros, into W1.
Logical Shift Right (>>)
lsr W0, #3, W1
Yes, it is very confusing that the command for shifting right starts with an "L", but the command for shifting left doesn't.
You can shift a register by up to 15 bits (#15). If you leave out the literal, it will just shift 1 bit.
There's no .b mode for this type of shifting, "shifting with short literals". If you want to right-shift an 8-bit variable, zero-extend it. (Or clr the register first.) The reason for this is that if there is already data in the MSB of the register, you'll shift that crud into your number instead of zeroes.
YOU NEED TO KNOW THIS MUCH ASSEMBLY TO DO LAB 2.
We now know enough assembly to solve the first programming problem of Exam 1 from Spring 2024.
void problem_01(void) {
u16_a = (u8_i | 0xB390) + ((~u16_j << 4) & 0x042C);
} // End.
Copy the C you want to translate into the box, and put a semicolon in front of it to make it a comment. Then make another comment and do your register assignments. Again, this is not required on exams but it's highly recommended.
; W0/REG W1 W2 W3
; u16_a = (u8_i | 0xB390) + ((~u16_j << 4) & 0x042C)
If you want, you could expend a few more keystrokes to note where you're putting subexpressions and the output:
; --W0------ -------W2---------
; W0 W0/REG W1 W2 W3
; u16_a = (u8_i | 0xB390) + ((~u16_j << 4) & 0x042C)
Next, move the variables into registers. Remember:
Put an underscore before the variables.
Put a # before literals
Move the 8-bit variable with mov.b, and put it into WREG
mov.b _u8_i, WREG
mov #0xB390, W1
mov _u16_j, W2
mov #0x042C, W3
Do the deepest parenthesis first:
com W2, W2
sl W2, #4, W2
Then just go left to right. Since 0xB390 is a 16-bit number, we have to make u8_i 16 bits, which we can do by zero extending:
ze W0, W0
ior W0, W1, W0
and W2, W3, W2
Finally, add everything together:
add W0, W2, W0
And move the answer to the output register:
mov W0, _u16_a
All together, the solution would look like:
; W0/REG W1 W2 W3
; u16_a = (u8_i | 0xB390) + ((~u16_j << 4) & 0x042C)
mov _u8_i, W0
mov #0xB390, W1
mov _u16_j, W2
mov #0x042C, W3
com W2, W2
sl W2, #4, W2
ze W0, W0
ior W0, W1, W0
and W2, W3, W2
add W0, W2, W0
mov W0, _u16_a
I like to click the "Save and run" button every line or two. This has two benefits:
Lets you see if you've made a syntax error before you go any further.
Saves your work in case Honorlock, the ebook, the internet in the lab, and/or your laptop are conspiring against you.
If you want to see what's in the registers aka why your code won't pass, include the line rcall _print_regs
That produces output like this, which appears somewhat cryptic, but is actually quite helpful.
W0 = 0x5AF4 (23284, +23284) .b: (244, -12)
W1 = 0x0001 ( 1, +1) .b: ( 1, +1)
W2 = 0x15CD ( 5581, +5581) .b: (205, -51)
W3 = 0x4798 (18328, +18328) .b: (152, -104)
W4 = 0x3E03 (15875, +15875) .b: ( 3, +3)
W5 = 0x631A (25370, +25370) .b: ( 26, +26)
W6 = 0xCE86 (52870, -12666) .b: (134, -122)
W7 = 0x49A7 (18855, +18855) .b: (167, -89)
W1:W0 = 0x00015AF4 ( 88820, +88820)
W3:W2 = 0x479815CD (1201149389, +1201149389)
W5:W4 = 0x631A3E03 (1662664195, +1662664195)
W7:W6 = 0x49A7CE86 (1235734150, +1235734150)
Let's examine the W0 line to see what it's giving us:
W0 = 0x5AF4 (23284, +23284) .b: (244, -12)
0x5AF4 - 16 bit hex value
23284 - 16 bit decimal value (unsigned)
+23284 - 16 bit decimal value (if it's signed)
244 - 8 bit decimal value of the LSB (i.e. F4, if it's unsigned)
-12 - 8 bit decimal value of the LSB (signed)
W1:W0 = 0x00015AF4 ( 88820, +88820)
These lines just give the values of two registers together if you're using them to hold 32-bit quantities.
To solve the other programming problems from Spring 24 Exam 1, you need to use conditionals - while, do...while, if, and if...else. (There are no for loops in the exams, ebook, or labs, but if you ever need to translate one, you can use the same tactic as a while loop and just use a register as a counter variable.) Up until now, translating C to assembly has been fairly straightforward, but conditionals require a little thought. They all involve labels, comparisons, branching, and usually, logical operators, so we will discuss those topics first.
Labels label places in our program that we might like to jump to. To make a label, you just put it on a line by itself and stick a colon (:) after it.
Label
dog:
The labels have to be one word, but you can use underscores, e.g. my_dog:
If we want to jump to "dog:" we don't use the colon. We also don't call it a jump, because this isn't the NSC, but an "unconditional branch"
Jump / Unconditional Branch
BRA dog
Tips:
Labels are not assembly language. They are case-sensitive.
A common cause of syntax errors is putting a semicolon after a label instead of a colon. Another one is leaving the colon in the command e.g. BRA dog: .
Be sparing with your use of labels. You should only need one or two on exam questions, and maybe 3-5 for the larger lab problems. Having labels strewn all over the place is guaranteed to make your program fail and be annoying to debug. Use labels only to mark places that your program actually jumps to, and take out the ones you don't need. If you want to label sections of your program to keep them straight, use comments instead. Use as many comments as you like.
There's only two kinds of comparisons you need to use for Exam 1:
compare two registers
cp W1, W2
; same as W1 - W2, but doesn't store the result anywhere
compare a register to zero
cp0 W1
; same as W1 - 0, but doesn't store the result anywhere
Of course, you will have to use cp.b and cp0.b if you are comparing 8-bit quantities.
We use the results of the comparison to determine where we branch...
For the next six examples, we're using unsigned quantities, we've just compared W1 and W2, and we want to jump to dog: if W1 __?__ W2
> (Greater Than, Unsigned)
BRA GTU, dog
< (Less Than, Unsigned)
BRA LTU, dog
>= (Greater than or Equal, Unsigned)
BRA GEU, dog
<= (Less than or Equal, Unsigned)
BRA LEU, dog
== (W1 - W2 = Zero)
BRA Z, dog
!= (W1 - W2 is Not Zero)
BRA NZ, dog
These commands would all be the same if we had just used cp0 to compare W1 and 0 and wanted to branch based on the results of W1 __?__ 0.
You can also BRA: C, NC, N, and NN, but I've never had to use these. You can look them up if you are curious.
A very important thing to remember:
Many times you actually want to branch when the opposite of the condition in the C code is true.
For example, let's say we have the following C code:
if (u16_a > 0) {
// do some stuff
}
else {
// do other stuff
}
Do we want to branch when a > 0? No! We want to continue on our merry way to "do some stuff". We only want to skip to "do other stuff" when a is not greater than 0, in other words, when a <= 0. We will give examples of how to code this when we cover if...else below.
Here's a simple while loop in C:
while (u16_b > 3) {
// do stuff
}
// more code
Hopefully, something in "do stuff" decrements u16_b or we'll be stuck in an endless loop, but that's not your problem, you just want to get a 100 on the exam. So here's how you code that:
; ---------w0------
;; while (u16_b > 3) {
while:
mov _u16_b, w0
cp w0, #3
bra LEU, end_while
; do stuff
bra while
end_while:
; more code
Some things to notice:
The while: label goes above moving the variable into the register. Why? We put u16_b into w0, so aren't they the same thing? Sometimes they are, but often code (which you may not have written, or worse, can't even see) may have altered u16_b or used w0 for something else in the interim, and your code will fail. The only way to guarantee that u16_b is in w0 is to move it there right before you do the compare.
We branch on LEU, not GTU, even though there's a > sign in the C code. That's because if u16_b > 3, we want to keep going on to "do stuff". We only want to jump to end_while: when u16_b > 3 is false, i.e. u16_b <= 3.
We don't need two conditional branches:
...
bra LEU, end_while
bra GTU, do_stuff
do_stuff:
; do stuff
...
Doing this is pointless and just gives you more chances to make errors.
We jump back to the test after running the code that's in the while loop with the line bra while. Forgetting to do this is a very common mistake (since in C and other programming languages it happens automatically) and will lead to your while loop running a maximum of once.
Unfortunately, the while loops and other conditional problems on the exams are not so simple. They use logical operators to combine multiple conditions.
The conditional is true only if both sides of the AND are true. So test the first one. If it's false, you don't need to bother to test the second; go ahead and jump to the "failure branch". Here's a slightly more complex version of the above while loop (with the new code highlighted) to illustrate that:
; ----------w0--------------w1-------
;; while ((u16_b > 3) && (u16_c == 0)) {
while:
mov _u16_b, w0
mov _u16_c, w1
cp w0, #3
bra LEU, end_while
cp0 w1
bra NZ, end_while
; do stuff
bra while
end_while:
; more code
Note that once again, we branch when the opposite of the condition in the C code is true, i.e. when u16_c != 0.
This conditional is true if either of the sides of the OR are true. We need to change things up a bit. Now we test the first condition and branch to the while loop if it is true. The second condition still jumps to the end_while: if it is false.
; ----------w0--------------w1-------
;; while ((u16_b > 3) || (u16_c == 0)) {
while:
mov _u16_b, w0
mov _u16_c, w1
cp w0, #3
bra GTU, do_stuff
cp0 w1
bra NZ, end_while
do_stuff:
; do stuff
bra while
end_while:
; more code
In C code, you'll often see something like this...
while (u16_d)
...which is short for:
while (u16_d != 0)
Thus...
while (!u16_d)
means:
while (u16_d == 0)
Now that you know this, coding it in assembly is relatively simple.
; ----------w0------
;; while (!u16_d) {
while:
mov _u16_b, w0
cp0 w0
bra NZ, end_while
; do stuff
bra while
end_while:
; more code
Once again, we branch when the condition (w0 == 0) is false.
void problem_03(void) {
while (!u16_i && (u16_j <= 0x028A)) {
while_body_func();
}
} // End.
; while (!u16_i && (u16_j <= 0x028A)) {
; w0 w1 w2
rcall _while_body_func
I went ahead and stuck the function call in already since I know I'll need it in there somewhere.
mov _u16_i, w0
mov _u16_j, w1
mov #0x028A, w2
This is a while loop with an AND in it, so we check the first condition and if it fails, go to the end. If it passes, we check the second condition, and again, if it fails, we go to the end. If both pass, we do the while_body_func and then have to remember to go back to the top. I'll put my labels in first to keep things straight. The comments don't hurt either:
; while (!u16_i && (u16_j <= 0x028A)) {
; w0 w1 w2
while:
mov _u16_i, w0
mov _u16_j, w1
mov #0x028A, w2
; first cp bra end if fail
; 2nd cp bra end if fail
rcall _while_body_func
bra while
end:
Now I write the first condition:
cp0 w0
bra nz, end
And the second:
cp w1, w2
bra gtu, end
There is no output - I don't have to put anything back into a variable. We're done. Here's what the whole program looks like:
; while (!u16_i && (u16_j <= 0x028A)) {
; w0 w1 w2
while:
mov _u16_i, w0
mov _u16_j, w1
mov #0x028A, w2
cp0 w0
bra nz, end
cp w1, w2
bra gtu, end
rcall _while_body_func
bra while
end:
Do...while loops are almost the same as while loops, except the loop is guaranteed to execute at least once. In C, they look like this:
do {
// do stuff
}
while (u16_e > 3) // do stuff again if this is true
// more code
Here's how that is coded in assembly:
do:
; do stuff
; ---------w0------
;; while (u16_e > 3)
mov _u16_e, w0
cp w0, #3
bra GTU, do
; more code
They're actually even easier to code than while loops because for a basic do...while, you only need one label and you branch when the condition is true.
void problem_02(void) {
do {
do_while_func();
} while ((u16_i > 105) || (u16_j != u16_k));
} // End.
This while loop has an OR in it, so if either condition is true, we jump back to do:. I'll put my labels and comments in now as well.
do:
rcall _do_while_func
; while ((u16_i > 105) || (u16_j != u16_k));
; W0 W1 w2 w3
; first cp, bra do if true
; second cp, bra do if true
mov _u16_i, w0
mov #105, w1
mov _u16_j, w2
mov _u16_k, w3
cp W0, W1
bra GTU, do
sub w2, w3, w4
bra NZ, do
Once again, there's no output. The whole solution looks like this:
do:
rcall _do_while_func
; while ((u16_i > 105) || (u16_j != u16_k));
; W0 W1 w2 w3
mov _u16_i, w0
mov #105, w1
mov _u16_j, w2
mov _u16_k, w3
cp W0, W1
bra GTU, do
sub w2, w3, w4
bra NZ, do
An if statement can be thought of as an if...then...
In C:
if (u16_f > 3) {
// then do stuff
}
// more code
In assembly:
; ------w0------
;; if (u16_f > 3) {
mov _u16_f, w0
cp w0, #3
bra LEU, end_if
; then do stuff
end_if:
; more code
So if the condition is false, we branch, skipping the then do stuff code.
If...Else in C:
if (u16_g > 3) {
// then do stuff
} else {
// otherwise do other stuff
}
// more code
In assembly:
; ------w0------
;; if (u16_g > 3) {
mov _u16_g, w0
cp w0, #3
bra LEU, else
; then do stuff
bra end_if
else:
; otherwise do other stuff
end_if:
; more code
This is trickier than a plain if. You need two branches and two labels. A common mistake is to omit the bra end_if. Then you'll do both the if and the else, which is never what you want.
void problem_04(void) {
if (u8_i + (~u16_j >> 2)) {
if_body_func();
} else {
else_body_func();
}
} // End.
Here's an if...else problem. There's actually only one condition, we just have to do arithmetic to see if it's true or not.
; if (u8_i + (~u16_j >> 2)) {
; w0 ----- w1
; if condition false, bra else
rcall _if_body_func
bra end_if
else:
rcall _else_body_func
end_if:
mov.b _u8_i, wreg
mov _u16_j, w1
Don't forget to use WREG for the 8-bit variable...
...and to zero extend it so that we can use it with 16-bit variables.
ze w0, w0
com w1, w1
lsr w1, #2, w1
add w0, w1, w0
cp0 w0
bra z, else
No output - here's the solution:
; if (u8_i + (~u16_j >> 2)) {
; w0 ----- w1
mov.b _u8_i, wreg
mov _u16_j, w1
ze w0, w0
com w1, w1
lsr w1, #2, w1
add w0, w1, w0
cp0 w0
bra z, else
rcall _if_body_func
bra end_if
else:
rcall _else_body_func
end_if:
Multiplication has never been tested, and is not in any labs.
Multiply unsigned, 16 bits
mul.uu W0, W1, W2 ; w3:w2 = w0 x w1
Multiplying two 16-bit numbers gives a 32-bit answer, so the destination register has to be even, so the upper word of the answer can go in the next higher register.
Division is rarely tested, and is not in any labs.
Divide unsigned, 16 bits
repeat #17
div.u w2, w3
; w1 contains the remainder aka %
; w0 contains the quotient aka answer
void problem_03(void) {
u16_b = (u16_a/0x12CA) - (u16_a%0x12CA);
} // End.
This is pretty straightforward.
; w3--------w2----w3---------w2------w3
; u16_b = (u16_a/0x12CA) - (u16_a%0x12CA);
mov _u16_a, w2
mov #0x12ca, w3
repeat #17
div.u w2, w3
sub w1, w0, w3
mov w3, u16_b