[Music]
hey everybody
and welcome to the third class of the
pie pico
uh deep dive course um as usual
i will start by sharing my screen and
okay so cool so uh that was class two
that was last week now we are in class
three
and just like last time we have the
agenda and we are
going to go over those subjects so you
have it here for your uh
reference and i will add the resources
here as we go
and just like last time it's going to be
a bit intense we have a lot to
cover um so let's
uh go right into this
so the first thing uh just a very quick
review of
what we did last week uh or the last
thing we did last week uh
we started working on uh avr assembly
and we learned about uh a few basic
instructions
uh move and all these uh arithmetic and
bitwise instructions
uh i added the list here so uh you can
see it here and we also talked about
those registers
r1 to r12 which is are just like local
variables
and we have seen this like cool um
emulator shortcut where you can uh just
dump the values of those registers in
your
browser console using this bkbt42
so a cool debugging tools tool we can
use
for uh studying assembly all right so
um other than uh those uh
instructions uh we also have like uh
beat shift instructions so uh we can uh
shift
uh stuff like uh uh so
uh for instance we can uh shift
uh r0 by uh so
uh logical shift left uh left lsl
would uh shift those um
this value by eight bits yeah eight bits
let's run it and see
and we will be able to see that uh r0
is uh like it's this two but it's
shifted
uh two places to the left so that's
eighth bit
shift and similarly we have um
lsr which is uh if we shift it by eight
then it will be zero but we can shift uh
by one
and that would be uh just uh one you can
see here
and there is also like uh rotate right
ror so if we rotate right by eight bits
it will let's remove this comment it's
no longer relevant
so uh yeah it fails uh yeah we can't
rotate
uh by a given amount that's my bad but
we can have the amount of bits to rotate
in a register
so uh if you wanted to rotate uh this
one by eight bits then we would
uh just uh use this uh piece of code and
then you can see that
this two form uh that we had here
moved here and this is super useful
because
when you are using this move instruction
you can't really load values that are
greater than
255. like if i try to load 1000 for
instance
then you will see i get an error this
value is out of range 1000
so if i want to get larger values uh the
best way to do it
is just by loading a smaller value
and then i can shift it and i can load
another
value and bitwise or so i can sort of uh
build the value from smaller
values depending on the exact value uh
and we will see another way to load
a 32-bit value into a register
uh soon um one more thing there is
also asr which is arithmetic shift right
and it's a very simple similar to
um to lsr so lsr
just both shift the bits right
but asr uh is copying the
uh leftmost bits when it shifts bit in
so if the leftmost bit was zero it will
be the same
as lsr lsr always
shifts zero from uh uh
sorry rsr so rsr always shifts
zero into the uh left bits and then
asr just copies the left
bit left most uh that's the msb when
shifting
um so we could see the difference if we
loaded
uh this value we
we want to load this into our zero so
uh this value
[Music]
but obviously it's yeah
we can't load it because it's too big
but we can load this value
and then shift it first by uh
i think uh we can shift it by
24 if i'm unmistaken let's see
and then we can see the difference
when we shift it with r star versus
yeah rsr by
8 for instance versus um
yeah well what doesn't it oh you need uh
here uh this pound sign
uh yeah
oh sorry uh asr
our star arithmetic shift so that would
shift once
yeah uh moment
bkpd42 so we can see if
yeah so uh yeah i think we shifted
too much so we needed to shift by
probably 20 to get it
right
um or
yeah wait a second
we'll figure this one out um
and then oh that's not 80 hexa that's
the problem
so yeah i want to load 80 hexa and then
i want to uh shift it
by uh i think 20 is the
24 is the right one to get it like to be
this
and again i forgot this hashtag
um okay cool
uh run it again yeah so now i got this
uh value in r4 using those two
instructions
and now you can see that if i shift it
with asr
we get uh f so it adds
once on the left because the leftmost
bit is one
but if i use a logical shift right lsr
it will shift zero so you can see
this is like the output it's just zero
zero eight so that's the difference
between
a star arithmetic and logical uh
right shift shift right okay so that was
it with the arithmetic instructions
um and uh one more thing i wanted to
mention
and i think uh i'm not sure if i
mentioned it last week
uh arm has two uh encodings for the
instruction set there is like
uh thumbing coding and arm encoding
and of course it's confusing so
uh there is this uh stack overflow
answer that explains it all
but uh what you need to remember is that
uh with
the cortex-m the uh mobile uh
the microprocessor uh version of the arm
we are all only using the thumb encoding
so
the arm encoding isn't relevant let me
just
put this link here i think we had it
also last time but uh
yeah so if you encounter like those
uh terms you can now uh know that uh
thumb is just one of the encodings for
the instructions
it's a 60-bit encoding some instructions
are also encoded as 32-bits
and arm is another encoding which is not
used in this microprocessor
cool and the next thing i want to show
you so we
we talked about those registers r2 to
through
r0 through r12 and i want to
talk to you another and about another
important register that's
uh pc pc is the program counter
and you can see that i have two
consecutive bkpt instructions so you can
see
a pc is incremented by two so
the size of each bkpt instruction is two
bytes
and pc is just pointing to the address
of the
next instruction or depending on the
context it might point to the
instruction after the next instruction
but what i want you to see here is how
it increments whenever i have
uh more instructions so uh if i have
those four
then you will see it uh it jumps uh four
times so
from here uh until here so
um that's the pc register
and um once we know that uh
uh there is a register that points to
the next instruction
uh we can ask ourselves okay what can we
do with it
so the first thing we can do with it we
can uh
give it a different value so instead of
like
pointing to the next instruction for
instance we can go
tell it uh you know what we want you to
change the program counter
to go back for instance uh
let's do this example so i can tell it
to
so b is branch it tells it to set a new
value to the
pc and i will define a label let's call
it uh
uh i turn niti and then i can tell
uh the assembler to uh branch to
eternity so uh it will set pc
to that the address of the
that same instruction and then uh the
nice thing about it
is that uh if we run it we basically
created an
infinite loop so it will never get to
this big apt
because you can see the simulation is
running but
uh we are just uh going uh going in
circles
and um that's not very useful
um but it starts to become useful if
um we want to for instance
create some um some logics to create
start creating some
control flow and for that we need a way
to to do things for instance
um first of all uh before we do that uh
i can show you another nice demo
so uh we can actually instead of going
uh back just uh one instruction
let's uh get rid of this we don't need
it right now
so instead of going back one instruction
we can actually branch
back to setup so uh now we can
uh serial println hello
and then delay 1 1000
and uh yeah now we do need a simulation
to see the output
and um oh that's serial one of course
i would i told you i would fall for that
and now
at this point we basically created a uh
infinite loop but it does something in
this loop it just
prints hello and then it goes back to
the beginning of setup
so that's already useful in a way but if
we want it to be really useful
then we need to support some way of
setting a conditional jump conditional
branch so
instead of always going to set up let's
say we only want to do something
uh 10 times and for that we have
a set of instructions called conditional
branches
and those conditional branches um
they they uh they work by looking
at uh some uh cpu flags
so those flags are basically uh
they are like uh four flags uh i think
the most useful one is the zero flag
uh which tell you if the result of the
last calculation was uh
zero and let's see how it works and then
we'll see how to combine it with
the uh conditional branches so for
instance
if i'm uh i have the value
[Music]
0 in r0 and then i'm
adding their ad r0
5 then obviously the result won't be
zero
and uh if we take a look at the flags
which should
appear in the console you can see these
are reflects
and uh the all of them are right now
grayed out it means that none of these
flags are set but
if we uh add zero to r0
which means the result will be zero we
can see that
uh now this zero flag is on
so basically that means that uh the
result is now
uh zero and this can be useful because
um uh if we like have
uh one in r1 and then we subtract
one from uh in r0 and then we sub
subtract one from it
we can check if the result is zero you
can see that now we have
this zero flag set and um
we can take advantage of that by uh
initializing for instance
r0 to 10 and then every time
we are going to uh subtract one
and then uh if it's and then
like print the result and then we want
to go back only if
our zero isn't zero so uh for that we
are going to use a conditional branch
let's define a label here uh let's call
it uh
we can actually uh just go back to the
beginning of
now let's go back here uh let's
call it label one just uh
one name and um
the thing i'm going to do now is show
you
this uh conditional execution it's
uh in the arm v6 architecture reference
manual that
i shared a link last time and it shows
you all these
uh conditional branches and they always
start with the b
plus some uh plus two letters that
specified a
type of condition so for instance i want
to
uh if i want to branch um
based on this zero flag i write b eq
branch equal and then uh it will branch
if
the zero flag is um
[Music]
is set to one and i can use b and e
which will
branch if the zero flag is set to zero
so in this example i could just wrench
equals
so that would uh be uh branch
if um if
zero is true and go back to
label one and now when i run this demo
i should see the loop running uh yeah
why did i mess up oh yeah it doesn't
like this form of comment probably
and now you should be seeing
yeah interesting um oh yes
sorry i want to branch if zero is false
so i want to branch as long as the
result is not 0. so
instead of using baq i actually want to
use b and e
if 0 is not set so now
i will probably get the correct result
um and yeah you can see that we have
like
uh r0 going um
yeah interesting oh yeah
my bad i create an infinite loop
uh and now you can see like my browser
is struggling
let's close this so yeah i should have
initialized r0 outside a loop
um let's retry that i think i may have
to
uh yeah open a new window
if that doesn't work let's try that
again now
uh initializing r0 oh yeah
okay so now i got the correct result you
can see r0 is going down
uh four three two one
and after it's zero uh this uh
you can see that that in all those uh
cases the zero flag is not set
and then the zero flag is set um
here when uh it it reaches zero
and uh the branch is not taken and we
finish this loop
so that's a very simple uh for loop that
goes from
uh from uh 10 to zero or from nine to
one depend on when you sample
the value of r zero in our case it goes
from
uh nine to one because uh we assemble it
here
and what if we wanted to do the opposite
for instance
uh counting from uh zero or from one to
ten
so obviously we could have uh
done this like adding uh one to
r0 but then how would we write the
branch
so uh one way to do it is just
uh to do something like subtracting
10 from r0 and if r0 is equal 10
then the result will be equal to
10. the result will be equal to 0 and 0
will be true
and we won't uh go back we won't
continue looping but then we will also
lose the value of
r10 so there is another instruction
called
called cmp which is compare which
basically does
uh r0 minus 10 and then
only updates the flag so this is like uh
like sub uh but doesn't
about throws away the result
and then that's uh another way we can
uh do this so we can see now that r0
goes
uh from one uh in the first invocation
and then
two three four five and you can see that
all those flags are grey
until we get um
yeah until we get to this oh
yeah we need to call uh bkpt after this
instruction to see the uh flex uh
after this comparison and before the
branch
so yeah so now we can see that uh for
all
those invocations uh at the
before the loop when r 0 is less than 10
we can see that only the n n is the
negative flag
negative flag so the only the n flag
is set and that's because uh five or in
this case three minus ten is negative so
uh the negative flag is set and then as
soon as
we eat ten the zero flag is set and
also the carry flag is set because uh in
subtraction the carry
is set if uh the the second operand
is uh uh less than or equal to the first
opera
um and you don't have to really remember
all those rules because there is like
this uh
very useful table uh which said
uh which explains uh in which case
uh like uh if uh it's sign are greater
than unequal
signed less than uh unsigned uh higher
lower same it explains uh when comparing
uh two values uh uh r0
and ten or two registers uh
when do we take the the branch so uh in
this case
we take it if they are equal in this
case if they are not equal
um and then um
if uh the left one is higher
um if we look at them as unsigned then
uh we will use if we want to test for
this condition we'll use this
be high etc so you have this
very useful table um it's uh
in a63 there is also like a reference
here
so you can know where it is
um cool so uh
we already know how to sort of uh
a basic control flow we can implement
loops this way but what if we
actually want to call functions
and you know what before we do that i
want to show you one more thing about
those
branches um so
uh let's say i didn't want to
uh i wasn't very creative and i just
gave it this
loop name and i would try to run the
code
and oops it fails it says
loop is already defined so that's one
thing you should
know whenever you define some label here
it's actually uh globally defined for
the entire file
and in this case it collides with the
loop definition
so there is a nice way to sort of use
local labels so you don't have to worry
about those collisions
i mean you could probably use a
convention of using something like that
for inner labels inside functions
but there is a nice shortcut you can
just give them numbers
uh like uh number one and then when you
branch you can
tell uh go to number one and then you
need to tell it where to look for it so
b means uh look backwards look for the
uh the closest label one that
is before this instruction and then you
also have one
f which tells it to look down so usually
when you write your own assembly code
it's just
uh i think it's the best practice to use
um
a number for the numbers for the labels
and then just
one b or one f or any number bf or
f to look for them and if i'm not
mistaken you can use any number from
one to nine and if you have like
multiple ones
it will just look for let's do this
actually
it will just go to the closest one
looking backwards so
even though i defined one multiple times
this still works
and you can see like uh this from the
previous execution
but you can see uh i got a correct
result the loop
executed 10 times it didn't go here it
went here
so that's how you define uh really local
labels in assembly
okay so um now we want
to uh to have the ability to call
our functions and um
one way to do that um if we wanted to
uh to to know how function could work
would be to try to define our own
function let's call it uh for instance
uh my function and then
uh try to call it from assembly for
instance so
uh we can have yeah so
uh
yeah that multi-line assembly i don't
know why i deleted it
and we still need it so yeah so um
yep now the syntax is correct uh so
one way to do this uh would just be you
know to branch to my function
uh that that would work like any other
branch
um and then in this function i can do uh
serial. uh i can actually just
print the registers for for for a
beginning
so uh i will do that and
uh i mean why did it fail
oh yeah the reason it failed is that
uh my function is actually a c plus plus
function so
uh its name is change it's called
name mangling and i can avoid that by
using this uh extern c syntax
it just tells the compiler to call it uh
my function without any uh name
decorations
and you can read more about name angling
here
if you are interested and if you want i
can
talk more about that in the q and a
session
but for now all you need to remember is
if you define a function that you want
to call from assembly
it's probably the best to just define it
with this extrinsic prefix
um cool so uh bkpt
42 and
now we have a function that calls
another function
and we can see that um the
registers have changed and
in i can also call it here from
c and then it should be invoked twice
and what i want you to see now or what
i want us to explore together is
how we um how we find the values of the
arguments for this function so for
instance we can
uh we can pass here 15
and let's see what happens inside this
function
so yeah oh yeah we just need to say here
that it takes
a number otherwise that wouldn't
probably work and
uh yeah so uh if we were
looking for this 15 um the the truth is
uh you could go for hours looking for
this 15 but you would never
find it and the reason for that is that
this function isn't actually
created as a function instead what
happens
is that the compiler ignites this
function so
it basically copies this code here and
here
and it ignores this number because
nobody is using this
number um i could probably
try to trick the compiler and
i don't know maybe if i have like uh a
serial printer
and just you know print the number that
would work let's try that
not sure but it's it's worth trying that
to see if that would uh hello
yeah if that would uh fix this uh issue
where the the function is inlined
and uh actually it did uh i i can
also print this uh number here so we
will see what happens when we call it
from assembly
so let's do that again and
you will now observe that it prints this
value 15
and then when we call it from assembly
it prints this value
4 so it's as if it was called with 4 as
an
argument and if we now look at the
registers we can see where this argument
is coming from
in the first invocation it r0 had the
value of
f and in the second invocation it had a
value of four
so basically um
we we we can guess that uh i mean
r1 also had these values so we can guess
it's either
r0 or r1 which holds the value of the
argument
and what we can do is just test which
one is the
right one so for instance if we
set r0 to 22 before calling this
function
then we will see uh something
interesting
we will see that uh it now prints 22.
so basically the way to pass arguments
to functions
is just to put them in to put the
argument the value of the argument
in r0 so that's how we
uh um yeah
um how we pass values to functions
and i want to show you uh something else
now uh what if we want to call this
function
again i mean this function uh
it's a function so uh it's supposed to
somehow
uh be able to return but right now if we
call it again like with 33
you will see that it's not called again
something happens this function doesn't
return and the reason it doesn't return
is that we didn't tell it where to
return
and to show you that i'm going to add
another
breakpoint here just before calling it
from uh from assembly so that would be
the second print
and i want you to notice something so we
have this pc which is the
uh program counter and um
you can see that uh pc
in this second time is set to this value
and then um when we go to the function
so this the second pkpd inside a
function we can see that the pc is set
to a different value a little bit lower
by about 20 exa
and um the thing is uh
the the function when it returns it
needs to know
where it should return and it uses
another register for that so there is
this
uh lr link register which tells the
function where to return
and right now when we are doing this
branch we are not setting this link
register so the function
uh instead of returning to the address
after
uh this one uh to the next uh
instruction
it just returns here which is probably
where
setup should return and we can fix that
by doing
instead of using b we can use uh bl
which is branch link
it means jump to this function and also
update this lr
link register with the value
with the return address of the function
so that would be uh
the pc of the address of this next
instruction
and now if we comment this out for a
moment so we don't get too many debug
prints
you will see that now uh after the
uh um the
in the second invocation so the third uh
debug print
that comes from here you can see that lr
is set to be
a bit after this pc so about a bit after
this point
actually uh if i change my uh print to
be here
you will see that lr points at this
instruction so uh this
print now goes here so you can see this
is the
lr the link register where
[Music]
this function will return and you can
see that
uh the pc this is actually the value of
pc
after the pkpt it's just uh this one
plus one
so uh the the the lr
always have the has the least
significant bit set
and that's that has to do with the fact
we are running uh in the thumb mode
uh but you shouldn't care about it too
much what you need to remember is that
other than this
uh least significant bit it just points
always points to the
address of that instruction the
instruction uh next to the bl
um and now we can probably uh you know
do this one more time just bl 2 my
function
and uh yeah now we can see that uh
it it executed three times correctly if
we
run it again with the debug prints you
can also see like those
register values um so yeah
so basically bl is how we call functions
and um then uh we'll see in a moment
how we uh return from functions right
now the compiler is doing it for us
um but uh what i wanted you to know
is that or what i wanted to show you is
how we
uh sort of figure out ourselves uh which
way the parameters are passed
and uh r0 is indeed how we pass
parameters
and we could go on and like add more
parameters and try to figure that out
but the truth is there is like this um
yeah this uh procedure called standard
from the arm architecture
i will just put a link in the document
so you'll also have it
and this document basically defines how
function calls work in this architecture
so there is like the standard base
procedure called
the base processor called standard and
it has this section about the registers
and then it tells you what
what each register does inside a
function
so for instance you can see that
r0 to r3 are used to pass arguments
r0 to r1 are used for the result so
that's the return function
value of the functions and then there
are also scratch registers which means
that
the function can modify their values
without preserving them
but then there are like all those
registers which are variable registers
and this means that
when you write a function you can use
these
registers to store values and you can be
sure that
any function that you call won't modify
them
or if it modifies them it will preserve
the value so
if you call another function and it
modifies r8
uh you don't see it it it has to restore
the value of r8 at the end so these are
like
all those uh registers from uh r4 to r11
are variable registers they are
um they are guaranteed to be preserved
when you call a function
unlike those four registers which may
change
and you also use them to pass arguments
and their return
values and
then there is uh like this r12 which is
also scratch register so you can modify
it as you like
uh it's not guaranteed to be preserved
inside functions and
somebody is asking if bl is similar to
call in x86 assembly so
it's similar in the way in a way it
doesn't do anything with the stack and
we'll talk about the stack in a second
so it just uh uh bl just copies uh
the value of pc after the instruction of
the next instruction into link register
and then just uh assigns the address of
my function to pc
um so that's about the register so we
already know there are like those
registers where we
pass the arguments and the result and
you probably ask yourself what happens
if we have more than like
four arguments or we have very big
arguments
and for that we have like um a bunch of
um
algorithms that explain how
how this works so for instance uh let's
start with
the return value because it's a bit
simpler it says that uh
there is like an algorithm that says for
instance
a data type that is smaller than 4 bytes
is
0 or sign extended and returned in r0
and then if it's a word size so 4 bytes
then it's returning
r0 and if it's a double forward size
like
long double uh etc it returns in those
two registers
and then it goes on um and then for
parameter passing there is a very
very long algorithm that you can read if
you like
uh it's pretty boring uh in a nutshell
uh what it says uh you basically
pass the uh the arguments um
in those registers are 0 to r4 r3
and then when you are out of registers
you use the stack for the remaining
registers
for the remaining arguments and um
we we will talk about the stock in a
moment but uh
for the most part those registers should
suffice for
uh most use cases um so
um one more thing we
uh can take a look at here is this
subroutine calls
so uh this just explains
how uh how uh
what we have just seen here how this bl
works and that it sets the uh
the copies the return address into lr
and this is like
uh sort of like uh how to
implement uh your own um
your own variant of bl
um anyway this is just uh if you want to
know more you can look into this
document it's very detailed i think
too much too detailed but for the most
part you just need to remember there is
like
bl and then there is like
r0 for the arguments also for the return
value
and um what we are going to do now
instead of uh implementing this function
in c we we're going to do it in pure
assembly so
let's change it to max 32 and
move the implementation out of this file
so uh
num1 num2 we want to create a function
and it returns an int a function
that will uh take two numbers and return
the maximum
so let's start by uh writing the tests
for this function so
uh max 32 let's say s surf 10 and
15 and 15 and 10
and we expect both to be 15.
so how do we do this the first thing we
do
is just create a new file let's call it
hackaday.s that's assembly source file
and now we are going to define this
function on our own without
any help from the compiler so
that's the max 32 and we know the args
are in r0 and r1 and the return value
should be in r0 so the first thing we
need to do
uh before we even uh implement the logic
is just
uh to return and returning in uh
assembly is super simple we just
bx bx is branch uh from uh
and read the address through for the
branch from a register lr
basically this uh this is the equivalent
of
move pclr it just copies the
address of the address the return
address that we have in lr
into pc and that how that's how we
return from functions
and now that we have this return uh
basically if we don't do anything
uh if we just run it won't compile it
says
uh yeah that's yeah that's
not a valid comment uh yeah but
it won't compile because uh it doesn't
know
about mac32 it says like this symbol
is not defined and that's because we
didn't say it's global so in assembly
code we should
everything is local to the file by
default if you want to
export any kind of symbol we need to
make it
global so um
making it global and then now when we
run it we can see it compiles but
it still doesn't do what it's supposed
to do it just returns the first argument
and that's because the return value is
always expected in
r0 so uh in order to fix that let's
implement some logic we are going to
compare r0 with
r1 so that's the cmp instruction
and by the way the syntax highlighting
still uh doesn't work very well
uh in in this editor but
i'm working on it probably should work
better for next week
and then after we compare them uh i want
to
uh if i want if our zero let's say we
want to do it
unsigned so i will consume that number
table
so if r0 is higher so b high
if r0 is higher higher than r1
then i want to just go to
so if it's higher then i go to label
number
one uh forward so that will be returning
otherwise i just uh if our zero
is not higher than a high one run i just
copy uh
the value from r1 to r0 so it will
return the higher value
let's run it and see if it works
hopefully so
and yeah now you can see it always
returns the higher value
let's just copy this and i will
probably organize this later but for now
you have this
code example here and
so that's cool now we know how to
implement to write functions in assembly
but um yeah
there is a great question on the chat
what happens if i want to use the other
registers like
r4 and r5 and for that we are going to
implement
another function in assembly and you
probably know this function
is the fibonacci function and with the
fibonacci
we are going to um
have like n and it will return the end
number in the fibonacci sequence and
let's create some
test code i goes from 0 to 16
and serial or let's just use printf
because it's more convenient in this
case
that's not standard arduino but we can
still do this so
i and fibo i and
just the scaffold for fibo let's do it
in assemblies
we have this fibo
global fibo and then
fibo is just if we do just this just
returning
then uh it will just uh return the
argument
and you can see it prints uh the value
that it
got and in order to implement fibo we
need to do a few things
first of all we need to uh check
if 001 then return
so if it's 0 returns zero or we can say
probably
if zero returns zero same goes for one
else return fibo of
n minus one uh plus fibo of
n minus 2. so the first two conditions
are pretty easy so we can say c and p
are zero zero and um
then if it's uh equal
if it's zero let's define a label here
just go to
one f go to one below and same logic for
one
and we already finished that uh two
these two um
so these two conditions but now we have
to do
this so we need to call fibo with
n minus one so obviously we could uh
subtract
uh one from r0 and then
uh branch link to fibo
but then uh we will have a fibo of n
minus one now in r zero but
we no longer have this value of r zero
um so it would be nice if we could
somehow preserve this value
and uh so we could call fibo with n
minus two
and one way to do it would be just to
use one of those
uh higher registers that are guaranteed
to be saved across calls so
um we we can do something like uh
at the beginning we could copy this
value so
move r4 which is one of the saved
registers
zero and now we
could probably uh assume that uh
like we could move uh our first of all
we want to
save the the return value of fibo it's
now in r0 so we
don't want to override it so we can
let's say save it inside r5
and now um
we we can restore the argument from
r4 and we can uh
subtract two so uh from the result
and now branch link to fibo and now we
have the result
for let's so that's
fibo and minus one and the other one is
fibo n minus two cool
and now we have the uh the result in r0
so we can
end the previous result in r5 and we can
uh
add our five
r5 r0 r5 so now r0 is just
uh just the result of uh
fibo it's actually fibo n minus two plus
fibons
minus one and yeah
that would hopefully work but
as you can probably see in a moment
it doesn't really never it never
finishes running we just we we get
we never guessed past one and the reason
for that
is um those registers
aren't saved automatically for us we
have to take care of that so
when we write a function if we do
something like this if we modify any of
those registers
other than r0 to r3 or r12
we need to save them and the easiest way
would be to save them to the stack
and uh the stack is basically there is
another
register called the stack pointer i will
probably print it in a moment but for
now bear with me we are just going to
push
those value r4 and r5 so here we are
saving them to the stack
and before returning we are popping them
from the stack
uh and you would imagine that would
probably fix the issue but there is
another thing it still doesn't work
and the reason it still doesn't work and
we get all those
errors in the console is that um
when we call uh bl fibo again
we also override the link register
so whenever we call another function
in our code we also need to store the
link register
so uh we can also push it and now we can
probably
uh pop it but this instruction actually
doesn't exist
in the the instruction set of the pi
pico there is a reason for that usually
you don't really want to
pop lr and i mean usually when you want
to restore
r is just because you want to return so
do this next so you could obviously do
something like
pop r6 and then uh it would override r6
so that would be another problem
but there is a nice shortcut you can
just pop directly into pc
and then you don't have to do uh this
bxlr so basically whenever we enter this
function we push
r4 r5 and lr into the stack
and then at the end we restore them and
instead of restoring
lr we just you know copy the value to pc
because that's what we
care about and now if we run the code
this will hopefully
give us the fibonacci sequence yes you
can see now it works
so uh this is a good example of how to
use the stack to store
to store these registers and the call
save registers versus those
uh scratch registers that you don't need
to care about
and let's edit here and later i will
organize it
more nicely um but uh
justin is asking whether i don't have to
okay
i think our experiments kill this small
console
whether i don't have to uh pop in the
opposite order
of pushing and yeah that's true the
stack is like
always uh pushing it's going to be down
so whenever i push
the stack pointer uh goes uh uh
one one way lower let's actually uh
demonstrate that really quick so
let's ditch the fibo for a moment and
just open
uh an assembly section
and now i can just pick apt um
and then i can push r0
uh move r0 55
now let's print the registers again and
then i can
pop r0 and do this one again
and now you should be what did a mess up
missing semicolon and now you should be
seeing how the stack works
so you can see r0 has this value that
comes from somewhere
and then when i after i pushed r0
uh you can see uh and i set it to a
different value you can see it's
actually let's do it in hexa so it will
be easier to see that
but uh you can see that uh it changes
the value
and when i popped it it restored the
original value
but the more interesting thing is that
we have this sp that's the stack pointer
and you can see that here it's it has
this 660 but
when i uh pushed a value it got uh
two or four places lower so it pushed uh
four bytes and now it uh it went uh four
places back we can actually see that if
we uh
just subtract those two numbers we can
see it's four
and then um when i pop you can see
as p goes back to the original value so
that's
how the uh the uh
push and pop work and internally the
implementation
uh if i'm not mistaken uh you can see it
in a data sheet like
uh how it pops and pushes in which order
but
it does it in the reverse order so uh
they don't get mixed up
so if we go to the alphabetical list and
we take a look at
pop uh yeah we can see that in pop it
just goes from zero to seven
and then it stores this in
or that's it loads it from the memory
address
um
[Music]
at address and
yeah and where does uh where is the push
push so
uh yeah actually here it increments the
address
uh and then in pop it
probably uh
yeah it also increments the address uh
yeah so uh it's basically uh
so it just uh in in push what happens
is it first of all uh
goes sp and goes back so
uh if you push four registers it goes
back
four entries 16 bytes and then it loads
the registers from there
and in pop what happens it just
loads the registers from the current
stack pointer and
advances it in four at a time so
basically
uh it's doing
um doing this is
more or less the equivalent of doing uh
let's write it
in a comment so this is basically
uh the equivalent of
equals push r4
uh push r5
push lr and then pop lr
oh r4 pop r5
r4 r5 and r4 so yeah so i hope this uh
answers the uh uh number
um yeah
so uh yeah poppy c right that's
that's correct so yeah so um that was
about
push and pop and i see there are a few
questions in the chat so
uh we can get to them in the q a session
because
uh before we finish i want to show you
uh one or two more things so uh
right now we only work with registers
and we know we can use push and pop to
save them to the stack so
we can use them as local variables but
what happens
when we want to use something like a
global variable so
in that case we have some work we need
to do first of all there are like
instructions that can help with that
there are like
uh two instructions uh ldm
and uh stm so ldr and str load and
store register and uh to demonstrate
that
these instructions i'm going to create
another function
let's call it counter
and counter is just going to have a
global variable
a global value that it increments every
time
you call it so the first thing we want
to do
is just of course return from counter
and let's also define it here just you
know
counter that gets an n
oh it doesn't get anything but it still
returns an int
and then uh we can have this um
let's say just we print here instead of
printing i
we are just printing uh the value of
counter which should
increment by one every time yeah we can
probably ditch this
all right and uh obviously now uh
if we run it i don't know what will
happen it will probably print some
random values yeah
always these values and then uh always
2 for some reason but we wanted
to store some state and increment it and
for that we would need to declare a
variable
and a variable is just a label so
counter
var and we are we want to put there the
values zero
and the thing is if we declared a
variable like this
then um it's declared inside the code
section and we won't be able to write to
it
if we want to tell the compiler that
this is actually data
we need to write uh data and
uh then the text to return back to the
code so this
this uh counter is put into the code
section and not data section
not into the rom this is this goes into
the flash
this goes into the ram so uh
ideally we would want to uh load
something from counter var and
um then increment that value
save it and return it but the thing is
uh the address of counter var
is probably too big to load into a
register so we can do something like
that like move the the address of
counter bar into
r0 because it's too big so there is a
trick
that we do we can define another label
let's call it one and in this label
we have we store the address of a
counter var
so now we have this address and then we
can
load this value into r0 so we are using
this new instruction
ldr r0 and we load
actually into r1 because we are going to
use r0 next
and we are going to load uh the value
from
uh 1f i think that's the
syntax not sure we'll figure it in a
moment
and then once we have the address of
this uh variable
in r1 we can load uh the value from
there using
another ldr instruction so load r1
from the address pointed by a node r0
from the address pointed by r1
add r01 and finally we can store
the new value of r0 that's the store
instruction
into the address pointed by r1 now this
may compile and may not let's see yeah
the the problem is that all the load and
the store instruction
they need to write to align the
addresses so addresses that are
on the four byte boundary and this
variable
just not doesn't happen to be uh
on such boundary so we can tell the
compiler uh
a line four which tells it uh make sure
the next uh piece of data is aligned on
the four byte pound
byte pound byte boundary so it will just
uh pad it with zero
so this uh lies on a four byte boundary
and if all works well now i will see
this counter in action yes
you can see it worked so there is
a bit of work we need to do to uh work
with variables because we need to sort
of
uh first of all uh load our address
uh into a register and then load the
value from that address so that's
a two-step thing but that's the way you
work with
uh global variables um or with the data
section
of course you can also get the address
uh in one of these
parameters so let's copy this as well
and i will later
organize it in one nice
example like we have here from last time
and um basically uh
these are the basic instructions that
you need to know about
there are a few more if you want i can
show you them in the q and a like
uh ldm and stm that are just like
ldr and sdr but they are storing
multiple values at once so you can store
like
r1 and r2 and it will store them
consequently one after each other
um and uh same for
stm will storm so ldm will load multiple
values stm will store multiple values
and yeah i think
we pretty much covered most of the
instruction set already there are a few
missing instructions
and it's sometimes a bit
hard to remember all those instructions
so i really looked hard for like
references that can help and i found
this
uh nice uh reference card that you can
use
uh with all the instructions i will
share the link
where is that in the document so you
will have it as well
you can download it from here um
it's not perfect but i think it's the
best reference that i found so
far there is also like this file with
all the instructions
but it it it's pretty messy because you
have like instructions
for uh arm of v7 and we are using v6m
so there are like many irrelevant
instructions and when you go
like let's go to ldr where is that
ldr yeah when you go to ldr there are so
many of them but
when you go to one of those instructions
you can see like there are like specific
notes
for uh like uh um
the different uh architecture uh
i think maybe ldm as uh yeah
yeah you consider like 16-bit
instructions is like time so
in 60-bit instructions there are
specific registers
i'll copy this one as well but i think
uh
it's it's probably more confusing
because it has
um yeah let's make it a link
it has a lot of um
instructions or options for the
instructions that aren't available
and this one is more like relevant to
our uh
thumb in arm v6 and not the
other uh more advanced version of the
instruction set
and um before we move on to the
q and a section uh a few pieces of
homework that you can use to practice
uh what you learned today in the class
and
also one one uh one more fun
instruction so uh so the
this homework is going to be a part of
the final project you can do it for next
week or
which is probably a good idea or you can
do it when you start working on the
final project and we'll have uh some
more
uh uh coming in the next few session
uh so um the first thing you need to do
is to implement uh gcd which
is just a greater common uh
greatest common divisor and there is
like
a few algorithms that you can implement
you probably want to do
this implement because this algorithm
because it doesn't include division
and there is no instruction to divide
numbers
but if you do want to challenge yourself
and
let me just share this link with the
explanation if you do want to challenge
yourself there is like i don't have it
open now but there
is uh in the rp-2040 data sheet there is
like this
uh system description
uh seo there is like uh
a hardware integer uh divider it's
in here yeah there is somewhere here you
can find
information so you can try to like uh
read this um there is like a set of
registers that you can use
and then you can try to use this integer
divider
uh in the sao let me copy down this
section number
in case you want to do this with
division um
that's two two three one five
so if you want to really uh uh shine
then
uh you can also
use this integer divider in
this section uh otherwise you can just
implement like this algorithm which
doesn't need any division or any
remainders
uh and not worry about it too much
and then um another function i would
love you to implement
is a function that is called square and
it takes
an array of numbers uh as an
argument and a second argument is the
size of this
array how many numbers we have there and
it just
updates all the numbers in this array
are two are squares so
multiplies each number in this array by
itself
uh modifying uh the the the array
so that's your homework and of course
you need to implement
these functions in arm assembly that's
the
the idea to practice um
cool so uh i think with that we can
probably move on to
the q and a until then i hope you have a
great week and see you again next next
week where we'll talk about reverse
engineering disassembly and
hopefully some uh somewhere about the
multi-core
operation on the pipe eco