I built a simple computer and wrote a
program to print hello world on the
screen but the program is a bit of a
mess right now it's over 150 lines of
assembly language just to print hello
world and see printing character by
character here you know it's very
repetitive because I just copy and
pasted the code to print each character
over and over again and then if we go
all the way back up to the top I'm doing
the same thing at the top here just to
send the instructions to get there's a
display module set up in the first place
so let's simplify this by moving this
code here for sending an instruction
into a subroutine that we could just
call with a single line of code each
time we need to so I'm gonna copy all of
this and paste it way down here at the
bottom of the program so this is all the
way at the bottom I'll paste that same
code and then I'll add a label here
called LCD instruction and except I'll
spell it right and I'm calling an LCD
instruction because this is the code
that sends an instruction or whatever
instructions in the a register to the
LCD module so back up at the top here
here's here we're sending some
instructions and we're loading the
instruction into the a register and then
instead of having all this code here we
just say jsr which has jumped to
subroutine LCD instruction so this will
load the instruction into the a register
and then jump down to the bottom here to
this LCD instruction section here and
execute all this code here which
actually sends that instruction to the
LCD then at the end of this we can say
RTS which means return from subroutine
which will exit the subroutine and then
jump back up wherever we came from so if
we go back to the top here that'll jump
up here too to where we just finish this
this jump subroutine instruction and the
nice thing is that we can reuse that
subroutine for each of the instructions
we need to configure the LCD so instead
of doing all this we get rid of that and
say jsr i'll see the instruction and
then same thing here this is another
instruction so we can get rid of all
that and add another jump to subroutine
LCD instruction it's now it's easier to
follow this code because because here
you know this is all the code to
initialize the LCD module and you can
check out my last video where I go into
what each of these instructions do and
what each of these bits are for but if
you watched that video and know what
these bits mean then hopefully this code
is pretty clear about what it's doing
and of course you know I've also got the
comments over here which should help
assuming they're correct and then of
course we can do the same thing for
actually printing the letters
so all of this code here prints the
letter H from the a register so I can
copy all that down into a subroutine
down here at the bottom as well and I
can call that print character and it's
pretty similar to the subroutine for the
LCD instruction it's just we've got this
register select bit set here but again
at the end we need the return from
subroutine instruction to exit the
subroutine and go back up to the main
program and so now if we go back up here
we can replace all of this with just a
jump subroutine print character and I'll
go ahead and do that for each letter in
the entire message and you can see this
is gonna significantly reduce the amount
of code we need to send each letter
since most of the code we started with
was just repeating those same commands
to send a letter to the display and now
that's been factored out into that
subroutine so now this is what we're
left with and it's an it's a lot shorter
and also I think a lot more clear about
what it does and a thing with assembly
language is there's a direct correlation
between the assembly code that you write
and the machine code that runs so let's
save this and if we look at the machine
code we started with yeah this is all
the assembled machine code you can see
there's a fair amount of it now let's
run the assembler on our modified code
and now if we look at the new code that
we generated see this is on his is a lot
shorter that means it's also going to
take up a lot less room in the ROM or
wherever we're running it from now there
I guess there is a trade-off in the
sense that jumping to and returning from
the subroutines will take a few extra
clock cycles to execute so technically
this code will run slightly slower than
we had before but that you know there's
a huge improvement in size so you know I
think that's a fine trade-off to make
now I've got the EEPROM in the
programmer and this is just writing the
new code to it and it's done so let's
put the EEPROM back in the circuit and
it should work the same as before
because we haven't changed the
functionality at all we've just factored
out that repetitive functionality into
into those subroutines so I'll power
this back up and then I'll reset it here
it should work just the same as before
and we should see it print hello world
on the screen although it looks like
maybe it's not working I try resetting
it again and yeah it's not working so
what's going on here what what we can do
is is step through the program
see and try to see what's going on and
in order to see exactly what's happening
I'll connect up a bunch of pins from
this Arduino mega that I've used in some
of the previous videos and so here I'll
connect a 16 pins from the Arduino to 16
address lines which will let us use the
Arduino to see exactly what value is on
the address lines at all times and then
I'll connect another 8 pins up to the 8
data lines on the bus so we can also see
what data is on the bus at any point in
time and then I'll hook up one more
signal here to the read/write pin to see
whether the processor is reading from or
writing to the bus and then finally I'll
hook up a signal to the clock pin
because you know I've got the Arduino
program to read all these other pins
just at that moment when the clock
signal pulses so we can follow each
clock cycle and then of course we'll
need a common ground connection between
the computer and the Arduino so let's
plug the Arduino in and then power up
the computer and this is the same
Arduino code from the first video in
this series if you want to know more
about how it works but if I pop open the
serial monitor here we can see yeah
there goes so it's definitely doing
something and this first column here
tells us what address the address lines
are on so this is the first 16 wires
that I hooked up there and shows us the
address bus the next 8 bits here the
second column shows us the data bus and
then we've got the address bus here just
in hexadecimal and over on the right
this is the data data bus in in
hexadecimal and then this column here is
either R or W to show whether the
processor is reading or writing to the
data bus so let's hold down reset now
and then to stop the clock and then I
can clear the output here and then it
takes 7 clock cycles for the processor
to initialize so I'll step through 7
clock cycles and incidentally when I
made this circuit to do the single
stepping I think I may have made the
pulse with a little bit too short so it
still does sometimes bounce when you let
go of the switch a quick hacky
workaround is just to put a point 1
micro farad capacitor right across the
switch which I did here and that seems
to help things a little bit but in any
event after that initialization it then
reads the reset vector and gets eight
zero zero zero so it's going to jump to
address eight zero zero zero which is
the start of our program if you didn't
follow all this you know that's that's
fine I cover all this in the first few
if you want to go back and watch that
but in any rate at this point we're at
address eight zero zero zero and we're
reading a nine which is the first load a
instruction because you know a nine is
the opcode for load a so if you keep
going here we get load a ffs and then
eight D is store a and so that's a store
a six zero zero two and that's this
store a DDR B and so that on the next
clock cycle we see it writing that FF to
address six zero zero two and if we keep
going we get load a easy row and then
store a six zero zero three so that's
the story a DDR a and now we can see it
writing that easy row to address six
zero zero three the next we get load a
thirty eight and that's our our first
instruction that we want to send to the
LCD so so far so good and now we're
reading to zero and that should be the
the jump to subroutine and and you know
if we look at the data sheet that that
is indeed correct to zero is is the
opcode for jump to subroutine ok so then
next we expect it to read the address to
to jump so it's reading five D and then
five T again but this is this is sort of
interesting so for some reason it's now
at address zero one two four and it's
reading and it's I don't know where it's
getting this five D from but after this
jump to subroutine I would expect it to
read two bytes I would expect it to read
a byte from 8 0 0 D + 8 0 0 e because
that's where the address to to jump to
is but instead it's going to this 0 1 to
4 which which is sort of weird so so
let's keep going so wait a minute now
what's it doing so now it's it's still
at that address 0 1 2 4 but now it's
writing and it's writing 8 0 and if we
go one more clock cycle now we see it's
you know address 0 1 2 3 and it's
writing 0 E so so what's up with this
you know we have this jump subroutine
instruction and then it seems to have
read half of the address to jump to but
then it's going into the these other
addresses and it's you know looks like
it's writing something to those
addresses so so what's up with that well
let's let's actually keep going to a
couple more steps so now if I go one
more step it actually seems like it's
maybe back on track here right because
we had 8 0 0 see it's reading the opcode
for the jump to subroutine then 8 0 0 d
it's reading 5 D and then 8 0 0
eventually after all this weirdness it's
reading 8-0 and that part seems right
because we have a jump subroutine and
then if you look at this 5 D and this 8
0 that's the address that's jumping too
and those are the next two bytes after
the instruction so so that sort of makes
sense jump subroutine to 8:05 D that
sounds right and in fact if we go one
more step here we end up at address 8:05
D which is which is the X at the
beginning of our subroutine so this this
is doing the right thing but it's doing
something else kind of weird here with
this address 0 1 2 4 and it's writing
some data there so what's going on with
that and what it's doing here
particularly these 2 clock cycles here
is is actually really important you know
because we're jumping to a subroutine
and the way we expect this to work if I
if I go back to the code here you know
we are at this point here so we're doing
this jump subroutine to LCD instruction
and LCD instruction apparently the
address for that is this 8 0 5 D and we
have our eventually reading that right
so we're reading the the jump subroutine
instruction so that's the jump
subroutine here and then the the label
or the address to jump to is LCD
instruction and it's going to read that
you know the instruction was at a 0 0 C
so it's going to read 8 0 0 D and then
eventually 8 0 0 e to get that address 8
0 5 D and then it jumps to 8 0 5 d and
then 8 0 5 D is LCD instruction so
that's all the way down here so this LCD
instruction the first instruction there
should be store a and so if we look here
it's 0 5 D we're reading an 8 D and 8 D
is in fact the opcode for a store a but
you know what's going on here what's
going on here in the middle here what is
this writing
what is this 0 1 2 4 0 1 2 3 what is all
that nonsense well what's going on is
once we get down to this LCD instruction
we've jumped down here we're going to
start executing all this stuff and then
eventually we're going to get to this
return from subroutine and we're going
to need to somehow find our way back up
to where we started up here when we
called that subroutine so that we can
continue executing our program and so
what address is it need to jump back to
that's the that's the question because
the the return from subroutine doesn't
it doesn't give an address it just says
return from wherever you came from so
the processor actually has to keep track
of where we came from
and that's what it's doing with these
extra clock cycles here and specifically
what it's doing is it's writing it's
writing this this 8 0 0 e which is the
return address that we're going to have
to return to because this 8 0 0 e is
actually the last address that we that
we came from so you saw what really what
the processor is doing is it's taking a
couple extra clock cycles here to save
that address so that we can return back
to it and so it's it's it's writing this
8 0 0 e somewhere in memory so that we
can get back to it so let's keep
stepping through the code here so at
this point we're in the subroutine so we
read a store a 6 0 0 0 which is port B
and we see it write the instructions who
address 6 0 0 0 we have a load a 0 and a
store a 6 0 0 1 which is port a and
there's the 0 being written 2 6 0 0 1
we have load a 8 0 which is the enable
bit and a store a 2 6 0 0 1 again to set
the enable bit and there it goes we see
the top bit here getting set to address
6 0 0 1 then a load a 0 again and
another store a 6 0 0 1 and there we see
the 0 going out to six zero zero one
again clearing that enable bit and
that's the whole sort of dance we need
to go through to send an instruction to
the LCD like I talked about in the last
video and so that all seemed to work
just fine and so here we are at the the
next address you know 8 0 6 F where
we're reading 6 0 hex and 6 0 is the
instruction for return from subroutine
so what do we expect this to do well it
should jump back to that return address
that we saved up here this 8 0 0 e so
we'd actually expect it to jump to 8000
F which would be the next instruction to
execute after the the subroutine so it
should somehow jump back to this 8 0 0 e
so let's keep stepping forward and see
what it does so if we step forward and
actually he doesn't seem to do anything
it just kind of advances to the next you
know after 6 F it goes to 7 0 which is
just the next memory location and it's
reading you know this 8 D which I guess
is whatever instruction is there I don't
think it's actually reading this to
execute that instruction I think the
processor is actually is taking a couple
extra clock cycles here you know to do
some internal stuff so I don't think
that just because we see this on the bus
that it's it's actually going to try to
execute that and if we keep going we see
now
it actually is reading from address zero
one two two the next clock cycle it
reads from zero one two three and then 0
1 2 4 and if you remember that 0 1 2 3 0
1 2 4 that's where this return address
was saved so up here when we actually
jumped into the subroutine the processor
saved that return address at address 0 1
2 4 and 0 1 2 3 so I save that 8 0 0 ii
and so now down here it seems like it's
trying to get that return address back
so that it can return which makes a ton
of sense but we've got a problem
and the problem is that it's not reading
that same address back that it's saved
up here so it saved this 8 0 0 e by
writing it to those two addresses but
down here when it when it reads from
those addresses it sits risk reading
this ad and and this is our problem this
is why our program doesn't work and the
problem is that we actually don't have
any hardware that responds to address 0
1 2 3 or 0 1 2 4 there's nothing at this
address so when the processor writes to
address 0 1 2 4 0 1 2 3 up here it's not
actually writing to anything it's just
sort of throwing it out on the bus but
nothing there's no hardware responding
to it there's no no there's no memory
there's nothing that's that's doing
anything with that and so then when it
tries to read it back down here is not
actually reading from anything it's just
getting whatever happens to be on the
bus and that's why our program doesn't
work you know the probably the processor
is trying to save the return address but
our computer doesn't actually have any
RAM in it yet so so it doesn't work and
so it gets this far but when it tries to
return from the subroutine with this
return 6 0 instruction it tries to read
the return address and it gets this 8 D
8 D and if we step once more you can see
it jumps to 8 D 8 D so it thinks it's
jumping back to where it should but it's
it's not it's jumping to some other just
sort of arbitrary address is definitely
not going to be our program anymore and
so if we keep going it's just going to
do all kinds of other weird weird and
wild things but none of this is anything
that we want so one question you might
be wondering is why is it trying to
write the return address to 0 1 to 4 and
0 1 2 3 you know where did arrow 1 to 4
come from where does ero 1 2 3 come from
where do these addresses come from
well the datasheet for the processor it
says the stack may use memory from 0 1 0
0 through 0 1 FF and this is something
that's just hard-coded into the way that
the 6502 microprocessor works is these
particular addresses are reserved for
this thing called the stack and the way
that a stack works is you've got these
256 addresses from 0 1 0 0 through 0 1
FF in memory and then you've also got a
processor register called the stack
pointer that is pointing to one of these
addresses so this is actually going to
be an 8-bit pointer but if it has the
value of 2 4 for example then it would
be pointing to this address 0 1 2 4
because you know all of the stack is
going to be 0 1 something so if the
stack pointer is is 2 4 then it's going
to be pointing to 0 1 2 4 in memory and
then when we do that jump to subroutine
it pushes the return address on to the
stack so we're going to put the return
address of 8 0 0 e onto the stack that's
a 0 but then also each time it pushes a
value onto the stack it decrements this
stack pointer so now there our stack
pointer is pointing up there that way if
we're inside one subroutine and we want
to call another subroutine we can you
know push another return address onto
the stack and kind of keep going and
keep calling all sorts of subroutines we
can even push our own data onto the
stack you know as long as we're careful
to pull it off the stack in the opposite
order that we push it on the stack can
actually be a really handy place to
temporarily store data so for example
with our subroutine here to send an
instruction to the LCD the subroutine
starts by we actually load something
into the a register and then call the
subroutine and if we look in the
subroutine you know it's going to use
what's in the a register flips this one
here but it's also going to overwrite
what's in the a register in order to
toggle the enable bit here so something
to be aware of is that up here where we
call this subroutine we're loading
something in the a register and then
we're calling the subroutine well when
that subroutine is done executing this
value is not going to be in the a
register anymore because the subroutine
modifies it and in our case that's fine
you know we're putting something else in
a register right away anyway so it
doesn't doesn't really matter but if we
wanted to down here in the subroutine
what we could do is push the value in a
register onto the stack and then what
that would do is it would put that value
I think it was 3/8 or something whatever
that value was it would put that
on the stack and then move the and then
decrement the stack pointer again and so
we have this extra value for what our a
register was when we called that
subroutine then of course we have to be
very careful that before we return from
the subroutine we need to pull that
value off the stack and put it back into
the a register and so what this pull a
will do is after we've used the a
register to toggle the enable flag here
it'll pull a value off the stack which
in this case is going to be this 38 it's
going to pull that off the stack and
it's going to increment the stack
pointer and then that 38 will get put
back into the a register here so by
pushing the a register onto the stack at
the beginning of the subroutine and
pulling it off the stack at the end of
the subroutine that means that when we
call the subroutine up here when we when
we do this jump to subroutine when we
get to the next statement here in our in
our program that a register will have
the same thing that it had before we
call the subroutine if we cared about
that you know in our case we don't
actually really care about that so I'm
gonna go ahead and delete both of these
lines because they do add a couple extra
clock cycles to the the program and you
know there's just no need if we push a
onto the stack and don't pull it off
before the return here that would be bad
because instead of returning to the
correct address you know eight zero zero
e if the stack pointer is off you know
because we didn't pull it top value off
the stack then the return instruction
would think we need to return to you
know zero e 3 eight so we'd end up
jumping to some other random memory
address you know the 0 3 e 3 8 or
whatever and of course that wouldn't be
good because who knows what's at that
address so we always have to be careful
about not pushing stuff onto the stack
that we don't pull off later in the in
the reverse order we also have to be
careful because on the 6502 like I said
the stack is always starts at you know
address 0 1 0 0 and goes through 0 1 FF
so it's just 256 bytes so if we end up
you know pushing more than 256 bytes
onto the stack you know if we're calling
more functions so you know we call this
function maybe we push this value on the
stack and maybe within this function we
call another function and so we have to
you know push another address onto the
stack and maybe that function calls
another function you know if we if we
got convoluted enough or maybe we had
some recursion or something we could get
in a situation where you know we keep
pushing more and more stuff onto the
stack and of course we get up to 0 1 0 0
and then if we were to decrement the
stack point
we just wrap around 2ff and so we'd end
up down here at 0 1 FF and we could keep
pushing stuff onto the stack and all of
this would be fine up until the point
where the stack pointer wraps around and
we're about to push something onto the
stack that overwrites stuff we already
had on the stack if that happens that's
not good that's what we call a stack
overflow because here you know if we
ended up pushing something else on to
the stack maybe there's another function
call you would con gone down the rabbit
hole here and we have another function
call we pushed that return address onto
the stack it would overwrite this return
address and everything would seem to be
working fine because we would return
from that function and we would you know
return from the right address and we
return from whatever called it and we'd
pull off whatever else we've had on the
stack and it would just kind of keep
wrapping around till we got to FF and
then when it incrementing when we pulled
something else off the stack it would
wrap back around to 0 0 and we could
keep pulling stuff off the stack all of
this would work fine until we get to
this point where we've overwritten stuff
from that very first function call and
so when we return from that very first
function call we wouldn't have the
correct return address here and that
would be bad and you know who knows what
would happen so we definitely wanna be
careful to avoid a stack overflow
situation like this because they can be
very difficult to to troubleshoot now
you might also be wondering you know
we've got this stack that goes from 0 1
0 0 through 0 1 FF you know why do we
start pushing data at 0 1 2 4 you know
we're you know why would the stack
pointer start here and not you know
anywhere else well it's because the
stack pointer just happened to start out
at 2 4 it just happened to be
initialized with that value it's it's
just completely arbitrary when the CPU
powers on what value is gonna be in that
stack pointer so maybe a good thing to
do would be at the beginning of our
program or you know right when the CPU
powers up and initializes you know to
initialize the stack point or to FF here
so it only starts out at the top of the
address range and then the nice thing
about that is that as we push values
onto the stack you know we always look
at the stack pointer and know like oh if
it's pointing to you know if the stack
pointer is 2 6 it's pointing here then
that means that we would have you know
26 or not 26 but 2 6 hex however however
many bytes that is we would know how
many bytes we have left in our stack
whereas if it's down here and you know F
D we'd know we have you know 254 bytes
available above that in our state
so how do we go about putting ffs into
the stack pointer to initialize it like
that well if we look at our instructions
you know there's a load a instruction
there's a load X instruction there's a
load Y instruction but unfortunately
there's no load stack or load stack
pointer instruction however there is if
we flip over here there are some
transfer instructions that work with the
stack pointer so for example we have
transfer where is it transfer X transfer
X to stack this will let us put a value
in the stack pointer from the X register
which we can load so basically this has
to be a two-step operation now first we
can load X with FF and then transfer X
to s and this will end up initializing
the stack pointer to FF because we're
loading that into X and then we're
transferring that to this the stack
pointer
that'll initialize the stack pointer to
FF meaning the stack pointer now starts
at the end here and then as data gets
pushed onto the stack the stack pointer
moves up like this although usually to
say the stack grows downward because the
the numbers are actually getting smaller
so you know maybe I drew this upside
down but you get the idea but anyway you
know the reason our program isn't
working is because it's trying to use
this stack but our computer doesn't
actually have any RAM in it at address
you know 0 1 2 3 0 1 2 4 or you know 0 1
FF or anywhere and that's what I plan to
fix in the next video I'll add some
memory to the computer by wiring up this
Ram chip and you know right now of
course we have the ROM which is
read-only memory and that's where our
program is stored but so far we haven't
added any RAM that we can read from and
also right - yeah and so that's what we
need to do so in the next video I'll add
the RAM and make sure part of it is
accessible at those address you know 0 1
0 0 through 0 1 FF and then our computer
will have memory we can write 2 which
means it'll have a stack which means
that subroutine calls will work in the
meantime you can check out my website at
either net 6502 for more information
schematics and kits which include all
the parts that i've used in these videos
in case you're interested in following
along yourself and as always thanks to
all my patrons for your support your
contributions on patreon really helped
me continue making this content so thank
you