Initialization (you don't need to add Input Output Process or do Register assignments for this section, but if you do, do them right)
;; i32_a = 0x12345678
mov 0x5678, W0
mov 0x1234, W1
mov W0, _i32_a
mov W1, _i32_a + 2
; also correct
mov 0x5678, W0
mov W0, _i32_a
mov 0x1234, W0
mov W0, _i32_a + 2
Sign extend W0 16->32 bits
; w1: w0
;; (int32) i16_d
clr W1
btsc W0, #15
setm W1
Always put the MSW (+2) in the odd, higher register!
Input
mov _i32_a, w0
mov _i32_a+2, w1
mov _i32_b, w2
mov _i32_b+2, w3
Add with carry
; w5:w4 = w1:w0 + w3:w2
add w0, w2, w4
addc w1, w3, w5
Subtract with borrow
; w5:w4 = w1:w0 - w3:w2
sub w0, w2, w4
subb w1, w3, w5
32-bit compare
; w1:w0 w3:w2
;; if (i32_a > i32_b)
cp w0, w2
cpb w1, w3
bra LE, ELSE
32-bit shift left
; W1:0 = W1:0 << 1
sl W0, W0
rlc W1, W1
32-bit signed shift right
; W1:0 = W1:0 >> 1;
asr W1, W1
rrc W0, W0
Output
mov w0, _i32_a
mov w1, _i32_a+2
To help you get started. Spring 25 and later - don't forget the underscores.
; Sign extension
se W1, W1 ; makes 8-bit signed number into 16-bit signed number
; Negate
neg(.b) W0, W0 ; W0 = -W0
; Shift right (signed)
asr(.b) W1, #3, W1
; Multiply (signed)
mul.ss W1, W2, W6 ; have to put the answer in an even register
; Divide (signed)
repeat #17
div.s W3, W4 ; quotient goes in W0, remainder in W1
; Signed branching
bra GT, label ; >
bra LT, label ; <
bra GE, label ; >=
bra LE, label ; <=
; 32-bit values
; W1:W0 W1:W0
;; i32_a = 0x87654321;
mov #0x4321, W0
mov #0x8765, W1
mov W0, _i32_a
mov W1, _i32_a + 2
; 32-bit bitwise
; w5:4 w1:0 w3:2
;; i32c = i32_a & ~i32b;
mov _i32_a, W0
mov _i32_a + 2, W1
mov _i32_b, W2
mov _i32_b + 2, W3
com w2, w2
com w3, w3
and w0, w2, w4
and w1, w3, w5
mov w4, _i32_c
mov w5, _i32_c+2
; add with carry
add w0, w2, w4
addc w1, w3, w5
; subtract with borrow
sub w0, w2, w4
subb w1, w3, w5
; 32-bit branching
; W1:0 W3:2
;; if (i32_a > i32b) {
cp w0, w2
cpb w1, w3
bra LE, label
; 32-bit non-zero test
; W1:0
;; if (u32_a) {
;; if_body_func();
mov _u32_a, W0
mov _u32_a+2, W1
cp W0, #0
cpb W1, #0
bra Z, END
rcall _if_body_func
END:
; 32-bit zero test
; W1:0
;; if (!u32_a) {
;; if_body_func();
mov _u32_a, W0
mov _u32_a+2, W1
cp W0, #0
cpb W1, #0
bra NZ, END
rcall _if_body_func
END:
; 32-bit increment
;; _u32_a++;
clr W0
inc _u32_a
addc _u32_a+2
; 32-bit shift left
; W1:0 W1:0
;; u32_a = u32_a << 2;
mov _u32_a, W0
mov _u32_a+2, W1
sl W0, W0
rlc W1, W1
sl W0, W0
rlc W1, W1
mov W0, _u32_a
mov W1, _u32_a+2
;32-bit unsigned shift right
; W1:0 W1:0
;; u32_a = u32_a >> 2;
mov _u32_a, W0
mov _u32_a+2, W1
lsr W1, W1
rrc W0, W0
lsr W1, W1
rrc W0, W0
mov W0, _u32_a
mov W1, _u32_a+2
; 32-bit signed shift right
; W1:0 W1:0
;; i32_a = i32_a >> 1;
mov _i32_a, W0
mov _i32_a+2, W1
asr W1, W1
rrc W0, W0
mov W0, _i32_a
mov W1, _i32_a+2
; Zero extend unsigned 16-bit value to 32 bits.
; W1:W0 W0
;; u32_a = (uint32_t) u16_a;
mov _u16_a, W0
clr W1
mov W0, _u32_a
mov W1, _u32_a+2
; Sign extend signed 16-bit value to 32 bits.
; W1:W0 W0
;; i32_a = (int32_t) i16_a;
mov _i16_a, W0
clr W1
btsc W0, #15
setm W1
mov W0, _i32_a
mov W1, _i32_a+2
_i16_x - signed 16-bit variable int16_t
_i8_x - signed 8-bit variable int8_t
You can make a literal negative with a minus sign.
#-1492 ; is -1492
Most arithmetic and bitwise operations are the same as with unsigned numbers, but there are a few different ones:
neg(.b) W0, W0 ; W0 = -W0
We have to use this right shift with signed numbers. Instead of always sticking a 0 on the left, it puts an 1 on the left if the number is negative.
asr W1, #6, W1
Before, W1 contained 0x80FF, a negative number (if it's signed). After the operation, it is now 0xFE03 (and still negative).
These work the same way as the unsigned versions.
Signed 16-bit multiplication
mul.ss W1, W2, W6 ; have to put the answer in an even register
Signed 16-bit division
repeat #17
div.s W3, W4 ; quotient goes in W0, remainder in W1
Be careful to use the signed form of the branching instructions. They're the same as the unsigned branching instructions, just without the 'U':
You must not zero-extend signed numbers.
;; int8_t i8_a
;; i8_a = -1 // 0xFF in hex
;; i16_a = (int16_t) i8_a
mov.b _i8_a, WREG
ze W0, W0 // this makes 0x00FF, which in 2's complement is 255!
Instead use sign extension. This puts 0xFF in the MSB if the LSB starts with 8-F, and 0x00 in the MSB if the LSB starts with 0-7, thereby preserving polarity:
se W0, W0 // would make 0xFFFF, which is still -1
void y2023_spring_exam_1_problem_03(void) {
i16_b = (i8_a >> 2) - i16_b;
} // End.
; w1 w0 w1
; i16_b = (i8_a >> 2) - i16_b;
I like to do sign/zero extensions (and complement) as part of the Input step, so I don't forget. Personal preference.
mov.b _i8_a, WREG
se W0, W0
mov _i16_b, w1
Don't forget it's asr not lsr!
asr w0, #2, w0
sub w0, w1, w1
mov w1, _i16_b
This is the problem worth the most points ever (15) on an Exam 1. (Tied with Summer 20, Exam #1, Problem #2, which was identical.)
We'll also put in our function:
; --------w0--------w1-------w2----
; while (!i8_a || (i16_b < -12567)) {
rcall _while_body_func
We'll put in our labels here. We need 3 of them:
top:
mov.b _i8_a, wreg
se w0, w0
mov _i16_b, w1
mov #-12567, w2
; check first condition, bra while if true
; check second condition, bra end if false
while:
rcall _while_body_func
; branch back to while
end:
Remember for signed numbers, it's GE, not GEU
...
; check first condition, bra while if true
cp0 w0
bra z, while
; check second condition, bra end if false
cp w1, w2
bra GE, end
...
; branch back to top
bra top
No output, so here's the complete solution:
; --------w0--------w1-------w2----
; while (!i8_a || (i16_b < -12567)) {
top:
mov.b _i8_a, wreg
se w0, w0
mov _i16_b, w1
mov #-12567, w2
; check first condition, bra while if true
cp0 w0
bra z, while
; check second condition, bra end if false
cp w1, w2
bra GE, end
while:
rcall _while_body_func
; branch back to top
bra top
end:
u32_x - unsigned 32-bit variable uint32_t
i32_x - signed 32-bit variable int32_t
A 32-bit value takes up two registers on the PIC24. You need to put the LSW in an even-numbered register, and the MSW in the odd-numbered register one higher. So you can use W7:W6, W5:W4, W3:W2, or W1:W0. You write the higher number first so that it lines up with the data. Note that if you want to keep a 32-bit number in W1:W0 you have to coordinate it with using WREG for 8-bit values.
; W1:W0 W1:W0
;; i32_a = 0x87654321;
mov #0x4321, W0
mov #0x8765, W1
It takes two moves to get a 32-bit value into a variable. First, mov the LSW (even) register into the variable, then the MSW (odd) register into the variable+2:
mov W0, _i32_a
mov W1, _i32_a + 2
Good news - for ior, and, xor, and com, you just have to do the 16-bit version twice. Order doesn't matter, but make sure you align the MSWs and LSWs. A good way to quickly check for errors is to make sure the operands are either all odd or all even.
; w5:4 w1:0 w3:2
;; i32_c = i32_a & ~i32_b;
mov _i32_a, W0
mov _i32_a + 2, W1
mov _i32_b, W2
mov _i32_b + 2, W3
com w2, w2
com w3, w3
and w0, w2, w4 // all even
and w1, w3, w5 // all odd - everything looks good
mov w4, _i32_c
mov w5, _i32_c+2
Bad news, now you've got to learn 2 more instructions, and order does matter. Just like in regular arithmetic, where you add the ones place first, then carry to the tens place, etc; you've got to do the even registers (LSWs) first with the original instruction, and then addc or subb with the odd registers.
; W5:4 W1:0 W3:2
;; i32c = i32_a + i32b;
mov _i32_a, W0
mov _i32_a + 2, W1
mov _i32_b, W2
mov _i32_b + 2, W3
add w0, w2, w4
addc w1, w3, w5
mov w4, _i32_c
mov w5, _i32_c+2
; W5:4 W1:0 W3:2
;; i32c = i32_a + i32b;
mov _i32_a, W0
mov _i32_a + 2, W1
mov _i32_b, W2
mov _i32_b + 2, W3
sub w0, w2, w4
subb w1, w3, w5
mov w4, _i32_c
mov w5, _i32_c+2
Actually, the branching is the same, it's the comparing that's different. You have to compare the LSW with cp, and then the MSW with cpb.
; W1:0 W3:2
;; if (i32_a > i32b) {
; assume they're in those registers
cp w0, w2
cpb w1, w3
bra LE, label
There is no cp0b or cpb0 instruction. So how do you check if a 32-bit value is zero or not zero? There are several methods. Choose whichever you prefer/understand/is shortest. (BTW, the ebook method is "Comparing to #0" and this is what is given in the Quick Reference above.) For non-zero tests, i.e. if (u32_a), in declining number of lines of code:
Using cp0 twice (for non-zero test)
; W1:0
;; if (u32_a) {
;; if_body_func();
mov _u32_a, W0
mov _u32_a+2, W1
cp0 W0
bra NZ, IF
cp0 W1
bra Z, END
IF:
rcall _if_body_func
END:
Comparing to #0 (for non-zero test)
; W1:0
;; if (u32_a) {
;; if_body_func();
mov _u32_a, W0
mov _u32_a+2, W1
cp W0, #0
cpb W1, #0
bra Z, END
rcall _if_body_func
END:
Comparing to W0 (for non-zero test)
;; if (u32_a) {
;; if_body_func();
clr W0
cp _u32_a
cpb _u32_a+2
bra Z, END
rcall _if_body_func
END:
For zero tests, i.e. if (!u32_a), in declining number of lines of code:
Using cp0 twice (for zero test)
; W1:0
;; if (!u32_a) {
;; if_body_func();
mov _u32_a, W0
mov _u32_a + 2, W1
cp0 W0
bra NZ, END
cp0 W1
bra NZ, END
rcall _if_body_func
END:
Comparing to #0 (for zero test)
; W1:0
;; if (!u32_a) {
;; if_body_func();
mov _u32_a, W0
mov _u32_a+2, W1
cp W0, #0
cpb W1, #0
bra NZ, END
rcall _if_body_func
END:
Comparing to W0 (for zero test)
;; if (!u32_a) {
;; if_body_func();
clr W0
cp _u32_a
cpb _u32_a+2
bra NZ, END
rcall _if_body_func
END:
You are now able to tackle even the most advanced problem ever to appear on an Exam 1:
void y2022_fall_exam_1_problem_04(void) {
while (u8_a && !u32_b) {
while_body_func();
}
} // End.
; w0 w3:w2
;; while (u8_a && !u32_b) {
rcall _while_body_func
top:
mov.b _u8_a, wreg
mov _u32_b, w2
mov _u32_b+2, w3
; check w0, if zero bra end
; check w2, if not zero bra end
; check w3, if not zero bra end
rcall _while_body_func
bra top
end:
; check w0, if zero bra end
cp0.b w0
bra Z, end
; check w2, if not zero bra end
cp0 w2
bra NZ, end
; check w3, if not zero bra end
cp0 w3
bra NZ, end
; w0 w3:w2
;; while (u8_a && !u32_b) {
top:
mov.b _u8_a, wreg
mov _u32_b, w2
mov _u32_b+2, w3
; check w0, if zero bra end
cp0.b w0
bra z, end
; check w2, if not zero bra end
cp0 w2
bra nz, end
; check w3, if not zero bra end
cp0 w3
bra nz, end
rcall _while_body_func
bra top
end:
Increments and decrements, which take two characters in C, and one instruction in PIC24 assembly for 8- and 16-bit variables, become a whole production with 32-bit numbers. You might as well just move them into the registers, add 1, and carry:
; W1:0
;; u32_a++;
mov _u32_a, W0
mov _u32_a+2, W1
add W0, #1, W0
addc W1, #0, W1
mov W0, _u32_a
mov W1, _u32_a+2
However, here is a quicker method:
;; u32_a++;
clr W0
inc _u32_a
addc _u32_a+2
Another operation that becomes exponentially more annoying with 32-bit operands is shifting. This is the hardest part of Lab 4. You cannot use the sl W0, #3, W0 type command twice. Instead you have to:
learn two new commands, rrc and rlc
use them in the proper order
For shift left: sl on the LSW, then rlc on the MSW (+2)
For unsigned shift right: lsr on the MSW (+2), then rrc on the LSW
For signed shift right: asr on the MSW (+2), then rrc on the LSW
repeat both commands for every bit you want to shift
Here are some examples:
32-bit left shift
; W1:0 W1:0
;; u32_a = u32_a << 2;
mov _u32_a, W0
mov _u32_a+2, W1
sl W0, W0
rlc W1, W1
sl W0, W0
rlc W1, W1
mov W0, _u32_a
mov W1, _u32_a+2
32-bit unsigned shift right
; W1:0 W1:0
;; u32_a = u32_a >> 3;
mov _u32_a, W0
mov _u32_a+2, W1
lsr W1, W1
rrc W0, W0
lsr W1, W1
rrc W0, W0
lsr W1, W1
rrc W0, W0
mov W0, _u32_a
mov W1, _u32_a+2
32-bit signed shift right
; W1:0 W1:0
;; i32_a = i32_a >> 1;
mov _i32_a, W0
mov _i32_a+2, W1
asr W1, W1
rrc W0, W0
mov W0, _i32_a
mov W1, _i32_a+2
You can also just do them directly to variables in memory if you like:
32-bit signed shift right (no registers)
;; i32_a = i32_a >> 2;
asr _i32_a+2
rrc _i32_a
asr _i32_a+2
rrc _i32_a
Note that this doesn't work if the variables are different, for example on the ebook HW question:
void shifts_ex_1(void) {
u32_a = u32_b >> 3;
} // End.
This would be like if the code was
y = x + 2;
You're expected to "Make y whatever x+2 is," not, "Add 2 to x, then make y what x is now."
Zero extending unsigned 16-bit values into unsigned 32-bit values is easy. You do have to be sure:
The 16-bit value is in an even register
Nothing important is in the odd register one higher
Then you just make the odd register = 0.
; W1:W0 W0
;; u32_a = (uint32_t) u16_a;
mov _u16_a, W0
clr W1
mov W0, _u32_a
mov W1, _u32_a+2
You could also save a step (and a register), and just move zero into the MSW:
; W0
;; u32_a = (uint32_t) u16_a;
mov _u16_a, W0
mov W0, _u32_a
clr _u32_a+2
Sign extension, however, is kind of a pain. Sticking zeros into the MSW only works if the number is positive. This is another common Lab 4 error which will cause your code to get passes until the 16-bit variable has a negative number in it. One way to solve this is to compare the number to zero - if it's less, put 0xFFFF in the MSW, otherwise put 0x0000:
; W1:W0 W0
;; i32_a = (int32_t) i16_a;
mov _i16_a, W0
cp0 W0
bra LT, NEG
clr W1
bra SE_DONE
NEG:
mov #0xFFFF, W1
SE_DONE:
mov W0, _i32_a
mov W1, _i32_a+2
A more concise method is found in the textbook (B.A. Jones, R. Reese, and JW Bruce, Microcontrollers: From Assembly Language to C Using the PIC24 Family, 2nd ed. Cengage Learning, 2015): We clear the register to use for the MSW. Then we check the most significant bit of the number. (In two's complement, negative numbers always have a 1 as their MSb, and positive numbers start with 0.) If the number starts with a 1 (negative), we make all the bits in the MSW = 1 (0xFFFF).
; W1:W0 W0
;; i32_a = (int32_t) i16_a;
mov _i16_a, W0
clr W1 ; assume it's positive
btsc W0, #15 ; test the most significant bit of the number, skip next instruction if it's 0
setm W1 ; makes all the bits of W1 = 1 (0xFFFF)
mov W0, _i32_a
mov W1, _i32_a+2
This requires knowing two new instructions, btsc (Bit Test Ws, Skip if Clear) and setm (Set Ws), but besides being shorter, it has another advantage - if you have to do multiple sign extensions in one program, you don't need to make labels like SE_DONE4.
You're now ready to attack about 20% of Exam 2 programming problems.
void problem_02(void) {
i32_b = (i32_a >> 1) ^ ((i32_a << 1) + i8_c);
} // End.
Since there's only four 32-bit registers, we have to be a little more thrifty. However, this is plenty for this problem. Remember to save one for i8_c to evolve into 32-bit form.
; w7:6 w3:2 w5:4 (w1):w0
; i32_b = (i32_a >> 1) ^ ((i32_a << 1) + i8_c);
mov.b _i8_c, wreg
mov _i32_a, w2
mov _i32_a+2, w3
mov w2, w4
mov w3, w5
8 -> 16 -> 32 bits in 4 lines of code:
se w0, w0
clr w1
btsc w0, #15
setm w1
32-bit shifts should be on everyone's cheatsheet.
asr w3, w3
rrc w2, w2
sl w4, w4
rlc w5, w5
add w4,w0, w0
addc w5,w1,w1
xor w3,w1,w7
xor w2,w0,w6
mov w6, _i32_b
mov w7, _i32_b+2
This is everything you need to know for Lab 4.
Part 3 of the Assembly Primer covers pointers, functions, as well as pushing and popping the stack!