Subroutines, aka functions, are pretty simple.
1. The parameters go in W0-W7. If there's one parameter, put it in W0. If there's two, put the first one in W0, the second in W1, and etc.
1a. If a parameter is 32 bits, it takes two registers. If it's 8 bits, it takes a whole register.
2. You can only have one return value. If it's 8- or 16-bit, it goes in W0. If it's 32-bit, it goes in W1:W0.
That's it, we're ready for every Exam 2 subroutine question that doesn't involve pointers!
This problem is simpler because it's a subroutine. You don't have to move_i32_a because it's already in w1:w0. (You also don't have to return any values - it's void, and you don't even have to call return:
; w1:w0
;; void y2021_spring_exam_2_problem_01(int32_t i32_a) {
; w1:w0 w3:w2
;; if (i32_a > 0x3E41DCE0) {
mov #0xdce0, w2
mov #0x3e41, w3
cp w0, w2
cpb w1, w3
bra le, ELSE
;; if_body_func();
rcall _if_body_func
bra END
;; } else {
ELSE:
;; else_body_func();
rcall _else_body_func
END:
This one is a little harder bc of register management, but being unsigned helps:
w1:w0 w2 w3
;; uint32_t y2019_summer_exam_2_problem_05(uint32_t u32_a, uint16_t u16_b, uint8_t u8_c) {
; w1:w0 w1:w0 w2>w5:w4 w3>w3:w2
;; return (u32_a - u16_b) | u8_c;
; input - make everything 32-bit, unsigned
; u16_b
mov w2, w4
clr w5
; u8_c
mov w3, w2
ze w2, w2
clr w3
; process (and output)
sub w0, w4, w4
subb w1, w5, w5
ior w4, w2, w0
ior w5, w3, w1
The three "32b/8b big sub" problems are variations of the same program that's at the end of the Chapter 6 lecture notes, If you're a Lab 4 veteran, you should have no trouble.
; w1:w0
;; uint8_t problem_07(uint32_t u32_value) {
; w2
;; uint8_t u8_cnt = 0;
clr.b w2
; w3
;; uint8_t u8_len = 0;
clr.b w3
WHILE:
; w3 w4
;; while (u8_len < 32) {
mov.b #32, w4
cp.b w3, w4
bra GEU, END
; w1:w0>w7:w6
;; if (!(u32_value & 0x00000001)) {
and w0, #1, w6
and w1, #0, w7
cp w6, #0
cpb w7, #0
bra NZ, ENDIF
; w2
;; u8_cnt++;
inc.b w2, w2
;; }
ENDIF:
; w3
;; u8_len++;
inc.b w3, w3
; w1:w0 w1:w0
;; u32_value = u32_value >> 1;
lsr w1, w1
rrc w0, w0
;; }
bra WHILE
END:
; w2>w0
;; return u8_cnt;
mov.b w2, w0
return
I made the initial mistake of overwriting u32_value with u32_value & 0x00000001, and used call _print_regs to help me figure out where I was going wrong.
That took a little while to solve. And it was the 7th question of the test - I feel bad for the Micro students of 2019!
There are only 3 types of pointers questions on Exam2. Since each type is so similar, we'll go over an example of each, and then just give some code for other situations. Unlike Exam 1, where copying massive programs onto your cheatsheet is folly, the amount of code in these questions is so minimal, it is probably worthwhile.
These are the rarest of the bunch, though they have appeared on the last two exams. They're just straight-up pointer questions with no subroutines, and are given this name because the single line of code you have to translate starts with either *pu or *pi.
The major trick to these is how to get to the right place in the array. Here we put _pi16_b into W1, and then move [W1 + 2*2] into W3. One 2 is because it's 16 bits (2 bytes) and the other is because the number in the brackets is 2.
; [w0] [w1]>w3 w2
;; *pi16_a = pi16_b[2] + i8_c;
mov.b _i8_c, wreg
se w0, w2
mov _pi16_a, w0
mov _pi16_b, w1
mov [w1 + 2*2], w3
add w2, w3, w3
mov w3, [w0]
If instead of pi16_b[2], it were pi8_b[3], you'd mov [w1 + 3*1] (You can just do [w1 + 3].)
If instead of pi16_b[2], it were pi32_b[2], you'd mov [w1 + 2*4] and then you'd have to mov [w1 + 2*4 + 2] into another register for the MSW.
*pu*pi++
This variant has appeared on two recent exams. Here's how you code it:
mov _pu16_a, w0
mov #0x629a, w1
mov w1, [w0++]
mov w0, _pu16_a
interestingly, you can also just do this:
mov _pu16_a, w0
mov #0x629a, w1
mov w1, [w0] ; you can also mov w1, [w0++] - it doesn't matter because the register is incremented after the move
inc2 _pu16_a
These questions sometimes have register assignments, which you can safely ignore, unless, like in the bolded questions, it specifically asks for the contents of the registers.
Here's the code, using the registers you're "supposed to":
; w0 w1
;; uint16_t problem_05(uint16_t* pu16_a, uint8_t* pu8_b) {
; [w0]>w2 [w1]>w3
;; return pu16_a[2] + *pu8_b;
mov [W0 + 2*2], W2
mov.b [W1], W3
ze W3, W3
add W2, W3, W0
Some notes:
Whether the parameters say uint8_t*, uint16_t*, or uint32_t*, these are pointers (addresses) so they all take up 16 bits, and hence, one register apiece.
In the body, however, when we see *pu8_b this means the actual thing at the address, which is in this case is 8 bits. (And so we have to use mov.b and zero extend.) *pu16_a[2] is 16 bits. If we had *pu32_whatever, it would be 32 bits.
we deal with arrays as shown above in the *pu*pi section
* in the body means [ ]. Even if they were both 16 bits, we couldn't do something like add [W0 + 2*2], [w1], w0, so we move them into registers (here W2 and W3) before operating on them. That's what the cryptic ---I>W2-- type notation is supposed to mean.
Here's one more example.
; w0
;; uint32_t problem_03(uint32_t* pu32_a) {
; [w0]>w3:w2
;; return pu32_a[3];
mov [w0 + 3*4], w2
mov [w0 + 3*4 + 2], w3
mov w2, w0
mov w3, w1
In this one, we have to move the answer variable back into w1:w0, so it can be returned.
Sometimes they want you to declare (make a label) for the function, make it global, and return it yourself. That would just look like this:
_problem_03:
.global _problem_03
; ... the above code goes here ...
return
These questions are named "a_func" because most of them involve a function with this name. (Exception: In the ones that ask about register contents, the function will be named "problem_05" or something.) They're pretty easy - you just have to pass in the parameters and call the function. We'll solve two that between them, cover all the expressions that have been tested except for one, which hasn't appeared since Summer 20, and then we'll go over that, which will complete your pointer training.
& translates into #, as does "a"... we'll make a cheatsheet when we're done.
; w0 w0 w1 w2
;; i16_a = a_func(&i16_a, pi16_b, au16_c[1]);
mov #_i16_a, w0
mov _pi16_b, w1
mov #_au16_c, w2
mov [w2 + 2*1], w2
rcall _a_func
mov w0, _i16_a
This one is even easier:
; w0 w0 w1 w2
;; pu32_d = a_func(&u8_e, pu16_f, au32_g);
mov #_u8_e, w0
mov _pu16_f, w1
mov #_au32_g, w2
rcall _a_func
mov w0, _pu32_d
This technically wasn't a registers question, but a previous one was, so it's named after that instead of being a_func. Notice that it's a little different than if it were au16+b[1]
; w0 w0 w1
;; u16_a = y2018_summer_exam_2_problem_01(&u16_a, au16_b + 1);
mov #_u16_a, w0
mov #_au16_b + 2*1, w1
rcall _y2018_summer_exam_2_problem_01
mov w0, _u16_a
Here is the promised cheatsheet. Instead of 1/2/4, you can write Z/8 if you like: