Video summary
In the fourteenth part of the series on building a 6502 emulator in Python, the creator addresses viewer feedback regarding missing flag implementations and code refactoring. The primary focus of this episode is implementing the Negative (N) and Zero (Z) flags for various CPU instructions, while intentionally skipping the Overflow flag due to its complexity. A significant portion of the video involves incorporating suggestions from a viewer named Ray Bellis, who pointed out that many load instructions were failing to set these flags correctly. Ray also recommended simplifying the codebase by creating separate functions for reading memory and handling values outside the standard byte range, though the creator decides to prioritize fixing the flag logic first before tackling those structural changes later.
To implement the new functionality, the developer adopts a helper function suggested by Ray that efficiently sets the Z flag based on whether a value is zero and the N flag based on the state of the leftmost bit (bit 7). The code is updated to handle both register operations and memory locations consistently by using a generic "value" variable instead of hardcoding references to specific registers like the accumulator. This approach ensures that instructions which operate on memory rather than just the accumulator still correctly update the status flags. The creator writes extensive test cases, such as loading zero or specific negative values like 128, to verify that the emulator accurately reflects the internal state of the CPU after each operation.
As the implementation progresses, the developer systematically goes through the instruction set, adding the necessary flag-setting logic to commands like ADC, SBC, CMP, and various load and store operations. During this process, several mistakes are made where instructions were initially thought to affect flags but do not, or vice versa, such as with stack transfer instructions like TXS which does affect flags while its counterpart does not. The creator learns from these errors by carefully checking the official 6502 specification for each instruction, realizing that some operations modify registers without changing flags, while others alter the processor status register directly. By the end of the session, most of the missing flag logic has been added through a combination of copy-pasting the new helper function and making specific adjustments for instructions that operate on different memory addresses or registers.
The video concludes with the creator expressing satisfaction at how quickly the bulk of the work was completed after integrating the community suggestions, though he acknowledges that proper testing is still needed to ensure absolute correctness, especially for rotation functions and edge cases. He thanks Ray and another viewer, Garib 85, for their detailed reviews and code contributions, admitting that relying on external feedback has accelerated his learning process significantly. With the core flag implementation largely finished for this episode, the creator wraps up by promising to address further refactoring ideas in future videos before signing off with an encouragement for viewers to keep coding.
Read the full video transcript
Welcome to 6502 emulator in Python part
14. Uh we are getting up there. So in
today uh today's video we're going to
take a look at some viewer suggestions.
I've gotten a few suggestions from a
couple different viewers. Uh some
suggestions on refactoring the code uh
and some you know issue rays were
missing flags. And that's actually what
I'm going to actually start working on
today uh is setting the negative and the
zero flags. And so you'll see what what
you know these people you know wrote on
to my GitHub and uh we'll take a look at
some of that and then we'll implement at
least some of that today but not all of
it cuz only so much time in the day. So
anyway let's take a look. We are working
on our 6502 emulator. So I'm going to go
back to here and basically what we're
trying to implement is flags. So we we
talked about that earlier in in the
series. Um there are these flags in the
CPU. So, you got the negative, overflow,
zero, and carry flags. Now, as I've been
coding, I've been I think I've been
setting the carry flag correctly. Um, so
for the commands that need that, uh, but
today, what we're do is we're going to
be working on the N, which is the
negative flag, and the Z, which is the
zero flag. Uh, we're going to skip
overflow for now. I think there's only
three commands that use that, and that's
a bit more complicated, so we'll come
back to that some other time. Uh, so I'm
just going to go down through and code
wherever I see NZ, but let's take a look
at I sorry I should have had this open.
Uh,
I not sure why I closed it, but here you
go. Um, so this is the GitHub and you'll
see here I have issues. So this is where
username Ray Bellis uh, so thanks to Ray
for from England which is very awesome.
Uh so thanks to Ray for taking a look
and taking the time to read this and
watch and make some suggestions and also
uh got 85
uh talked about the LDA command and
actually it applies to all the commands.
Let's take a look real quick at what Ry
wrote. Um, so Ray wrote about how I can
massively simplify my code and just he
talked about certain things here and I I
think you know he's absolutely correct
especially here with this read and read
16 cuz basically what I have here is
something that we don't like to have
which is let me just go down here
somewhere is we have a bunch of where is
that at?
Um we have a bunch of repeated code
here. So you see this lsb msb lok um you
just don't want that. So you know he
recommends like rewriting that as as a
separate function and he's absolutely
correct. I will do that at some point.
Um so he has read which is going to read
an 8 byt or yeah an 8 bit memory
location read 16 which will read a two
byt memory location. So that's great. Um
he has writes and this is good because
it deals with values outside the range
the proper range. I don't have any
checking for that, at least very little
here and there. Um, so yeah, he has he
has a lot of really good ideas here and
I will implement some of those later.
Um, you he talks about different ways to
do the uh program counter incrementing
and I'm not 100% sure I'm going to make
that change. Um, I did it a different
way. I think it's okay, but I'm sure
actually I'm sure his version is much
better. Um, so yeah, so we're going to
look at that. Well, I'm not going to do
that today, but that's one piece of
feedback which I thought was really
good. So, thank you again, Ray. And then
the second one, this is one I'm actually
going to use today. Um, so again, he
talks about wrapping here and just
different ways to do it. Um, he thinks
the branch functions are off. I think
they're okay. Um, it's just the way I
think it's just implemented a little bit
differently. So, I think even here he
says, "It looks like your branch
extensions are actually okay, but only
because of the use of increments." Um,
again, if I hadn't used increments, you
know, there's different ways to do
things. Uh but as he mentions here
properly, I don't make sure that any of
my branches or anything are in the
proper range of uh 0 to FFF, which is
65,535.
Um so again, thanks Ray. Um where's the
thing you wrote? Or was it where's the
other person here? Let's take a look
here. And then this one says here, he
says that Oh, it's here. So thanks, Ray.
Um so Gibb 85 says LDA is missing set
flags ZN. and he's correct. That's what
we're going to do today. Um, I'm not
setting any flags uh except for carry
here and there. But, uh, so Ray says,
"Yeah, when you do fix this, note that
several other instructions, LDX, maybe I
should make that bigger. LDX and LDY
have the same behavior." So, he
recommends the set NZ self regge and
self.z equals regge equals zero, self.n
n equals bool regge and 0x80.
Um, so
that's a new one. I don't actually know
what that means. Um, so I'm probably
have to look that one up. Um, so he
says, "Yeah, your functions have a
similar bug. They do not set Z." Um, in
fact, all of your load instructions are
missing N and Z. I know that. Um, you
know, if you go back to the start page,
I have that listed down here into be
implemented setting flags. So, let's go
ahead and
take a look again at Ray's code. I think
I'm just going to use that instead of
writing my own cuz you know what?
Somebody did it for me. So, again,
thanks again, Ray. And anything else?
I'm just I'm just going to copy it. What
the heck? I'm copy and give it a shot.
So we're basically what we're doing is
we're setting the negative and zero
flags based on the value of some. Now he
has register here but I think it also
applies to uh not just registers but I
think it applies to memory locations as
well. So I'm just going to change that
because just to keep it consistent with
the rest of my code. So, I'm going to go
back to here and I am just going to plop
that in here and down with the rest of
the kind of the helper code, I think. Or
is that up at the top?
Let's see here. Where we throw that in
here? Where do we have wrap at? Okay, so
we'll put it in here. Okay, we'll put it
here before all the other these other
things. It's got to go in that order
anyway. So, I'm going to go ahead and
copy that. I'm going to That was dumb.
Tab
tab that over.
All righty. So, I'm going to change this
to value because I don't want to use
regge. Uh I'm going to change that to
value just to keep it consistent with
what I wrote up here. Value. And I'm
actually going to Google real quick. I
don't know. I'm not familiar with that.
Say bool command in Python. I have an
idea, but we'll see what happens. Uh
convert a value of boolean for
um
you know what I'm just going to trust
Ray and see how it goes. We'll I have
some testing code. We we'll see what
happens. If it doesn't I'll fix it
later. Okay. So what this is supposed to
do is if the value is zero
then Z is going to be set to true. If
the value is not zero, Z will be set to
false. Now again, for my students, if
any of my students are watching this,
this is just a nice short way of doing
if value equals zero, self.z equals uh
true uh capitals true, and then else,
you know, self.z equals false. But this
will do the exact same thing. So this is
just a nice nice little shorthand way of
doing things. And I'm presuming that
this will do the same thing. And the
reason that negative he's you know
anding the value with 0x80x80
is 128. So that is our leftmost bit. 0 0
0.
Oops. Uh one more. And I've explained
this I think in a different video. So if
this bet is set to one, it is considered
negative. So that gives us a range I
think of 127. Noative 127.
Yeah. or noative 128 to 127 or something
like that. I forget which video it was
in but we talked about that. So
basically we're just checking to see if
the value is zero or not and if the
leftmost bit is set. So I'm going to I'm
going to trust Ray that this is this
code is correct cuz I again I haven't
seen this before. Uh I did something
similar in a different video but I had
to do an if statement. So this is how
I'm learning too. So it's pretty
exciting. So I do again I really do
appreciate that. So, what I'm going to
have to do is I think most of the
commands are in the same order. I hope.
No, they're not in the same order. Um,
so what I'm going to do is I'm going to
go through I'm going to go ahead open
this up. I'm going to go here and
I'm just going to scroll down through.
This is probably was going to get boring
for you, but so we see add with carry
and we do set the N and Z flags. So I'm
going to go ahead and do
add carry.
So again there is a value uh add the
value to the accumulator. So u and then
we're going to do
set n.
And so I'm going do self set uh nz and
it's going to be self and it's going to
be self a because that it's the value of
the accumulator that is
working. Actually before I do that I'm
going to actually test this. So uh I did
some testing code for lda.
Okay. So I'm going to go ahead and just
plop that in there.
And so what I did was
basically I'm going to LDA the value of
0 0. So this should set the zero flag
because the value is zero. So let's go
ahead and run it. Oops. Are we not
running again? Yep. Close that for you
and do that again. There we go. Save and
run.
Self set NZ takes two positional
arguments but only one of course um 230
LDA self set
Z ah it's not necessary that is from
doing too much job I believe
there we go so the accumulator is zero
so the zero flag is set to one so that
I'm with that so far so that looks like
it's working let's set it to one so the
zero flag should be set to false.
So this is one. The zero flag is set to
false. So far so good. So let's set it
to the first negative value which is 080
and compile it or compile it. We'll run
it.
And it keeps locking up like that's
really annoying. Um I think it only does
this when I have OBS running.
We'll live with it. Uh,
so we have 128 and that gives us the one
which is negative. So I'm pretty happy
with that. I'm just going to test one
more value FF cuz I know the leftmost
bit is set FF. So let's try that. And we
have the negative. So now it's not
possible to have negative and zero at
the same time for obvious reasons. Um,
but yeah, this I'm pretty confident that
this is correct.
So, that is good news. So, I'm going to
go back to here. I'm going to copy that
out
and I'm going to go to ADC because that
has N and Z. Again, I've already set the
carry so I don't have to worry about
that. Um, overflow will do another day.
So, I'm going to set this properly. So,
really now all I have to do is go
through and copy and paste
this everywhere where N and Z are set.
So, this is probably going to get very
boring for you. So, this will be and and
I'm going to go ahead and
oops, set that.
And we got a S and L. So, this is also N
and Z.
Ooh, actually, you know what? I got to
think about what I'm doing. That I was I
got a little overconfident. Um, so right
now, what I'm copying is add carry. So,
this is with the accumulator. So I got
to be careful. This is also accumulator.
Um and arithmetic shift left is
does not always do the accumulator.
Okay. Okay. This is good. And does not
always do the accumulator. Add with
carry doesn't always do accumulator. All
righty. Awesome. Okay. So I do need to
make some changes here. So this is this
is a good learning experience. So I'm
going to go back with add with carry.
So add carry actually no it does add to
the accumulator. Sorry about that. um
bitwise
and with accumulator so this is okay but
as L
ah okay it depends if it's accumulator
mode
then value equals self a
okay so
all I need do is update that there. So,
I'm going to go ahead and do that.
Instead of self a, I'm going to do value
because it might not go into the
accumulator. Um, so what happens here is
if it's the accumulator, self. A goes
into the value. Then we do all this blah
blah blah blah stuff. And then this is
where I could have done bool just, you
know, bool blah blah blah. Um, check the
leftmost bit shift blah blah blah. But
anyway, value is is going to hold that
value. Now, if it's accumulator mode, we
put that value into a. Uh, if it's not
accumulator mode, we put that into the
memory location. So, what I'm going to
do here is put value. So, that's going
to work for that, I presume. Um, next is
BCC.
So, oh, actually, no. ASL.
ASL.
I just I just did ASL, right? Yeah. Um,
so the next one should be I didn't do
bit yet. Um,
see branch instructions don't affect
that.
Break affects B. We haven't done break
yet either. So let's go to compare
cmp.
Did I not do compare?
Oh, there it is. Okay, take that. Um, so
compare to accumulator.
And
so then this is also going to be a
cumulator. So we're going to go ahead
and paste that
set n
and it's going to be self
a. All righty. And so cmp compare x. All
right. So this is what ray was talking
about. So if we're doing x the x
register. So,
CPX.
Did I not do Yeah, I think I missed that
one. Yeah, I missed CPX. That's not
good.
Okay,
I'm just going to make a note of that
for now. And I'm going to go back to
going to Pierce, right? Okay.
CP to do
CPX.
Come on. Probably CPY.
This is good. This is a good exercise.
Um, CPM. Yeah, I forgot to do this. CPY.
DEC. I think I did that one.
D E. Nope. How did I miss all these?
This happens when you talk and don't
listen. Um, decrement memory. Dec.
God, tell me I did this one. Um, E or
Yosh. All right. So
and we e or with the accumulator.
So this is going to be accumulator set n
for the accumulator
self a
copy. Um okay we already done clear
carry and all this kind of stuff so we
don't have to worry about that
increment.
Increment
I didn't do this one either. Jeez. Oh
man. All right. Well, I mean, they're
going to be easy to do because they're
just basically copies of other stuff,
but um yeah. Well, did miss a few.
Didn't feel too easy. Um jump, no flags
affected. JSR, no flags affected. LDA, I
think that was the first one we did, but
let me just make sure. Yeah, LDA set NZ
self A. Good. LDX. There we go. This
will make Ray happy. That's what he was
talking about. So, this should be
self.x.
Oops.
Um,
uh, LDY.
So, that should be
selfy.
Okay. Now, we're back to lsr.
And this is going to be similar to ASL.
So it's again it's going to be the
value.
Um
so down here we're just going to go
ahead and do
value because again that value may go
into the accumulator. It may go into a
memory location. Again as Ray mentioned
you know he he had an idea of instead of
doing it like this you know do a
function to do that which is a great
idea. That was a good idea for
refactoring. Um wraparound. I'm going to
skip that for now. We're going to go
ahead. No operation, no flags. O a
is bitwise with accumulator. So, oops.
So, that's self a
tax. I didn't realize those were going
to affect that. So, T ax.
So, transfer the accumulator to X. So
just to make it so the value of x this
is going to be tx a should be self a t a
y should be oops selfy
t y a should be
self a right
yeah I can't believe I didn't do this uh
annex etc. Um, dy didn't do those.
Did I? I did do a dx and dy. All right.
Awesome.
D x.
So it should be xde
y.
So y
did I do I just do those
dx dy in x?
Okay
should be x i in y should be y
and
yeah I think I did all those. All right
I think we're good there. Um, dx dy.
Yep. TX.
All right, I'm pretty happy with that.
U, we got to go to Rol. Again, if I miss
something, you know, please comment. Um,
I'm doing this kind of fast. So again,
RO is just like we saw earlier because
it may go to the accumulator. It may go
into memory. So this is just going to be
the value.
And R O R makes sense. Same thing
value. Yeah.
Um RTI, we haven't done interrupts yet.
Uh that's definitely on the list. Um
then RTS, but no flags affected.
Subtract with carry SBC. So this is
subtracting again. Sub subtraction
operates on the accumulator. So that's
going to be self a
store accumulator.
So, does not affect any flags, which is
interesting because we only we only
affect flags when there's a change to
the value. In this case, the
accumulator, but since we're storing um
that doesn't change stack instructions
um
do not affect looks like they do not
affect
well,
but transfer X to stack pointer. So X
doesn't change, but transfer stack
pointer to X.
Push accumulator pull accumulator. That
doesn't say anything about affecting the
um
doesn't say anything about affecting the
flags. Let me just check the other one.
So TXS control find TXS
transfer
stack pointer to tsx transfer stack
pointer to x. You see here it does say n
and zero are affected. So I'm going to
go ahead and trust this. Um this just
makes sense. So you see here transfer x
to stack register doesn't affect these
but transferring stack pointer to x does
affect those. So I'm I'm going to I'm
going to assume that I'm correct. So
transfer stack pointer to x I'm going to
say tsx.
And
so anytime there's a change to one of
those registers, we have to update the
flags. Um push accumulator and pull
accumulator. So control find PHA
push accumulator.
So push accumulator doesn't change
because the accumulator doesn't change
but pull accumulator. So accumulator
does change. So we do need to do that.
So
PLA. So let me go back to here. So PLA
pull accumulator
and we got push processor status and
pull processor status. Now just by
definition that it's going to affect the
processor status but we've already taken
care of that. Okay. Store X no flags.
Store Y no flags. And we are at the
bottom. Oh my gosh. Okay. So, again, I
could go through and just test all this,
but you know what I'm going to do? I'm
going to assume that's correct. And
compile it to make sure there's no
compilation errors. Going to run this
and make sure see if that's running. So,
255 gives us a negative and non zero.
Um, so since we copy and pasted, we'll
assume that everything is working as
expected. So yeah, that was pretty
quick. Um, quicker than I thought it was
going to be. But let's go back to the
coding concepts.
So again, thank you to the viewer
suggestions. Uh, was it Ray and
um,
Gar Garib 85. So I have set the Z and N
flags as far as I know for every almost
everything. Um, and then Ray again,
thank you. I used your code. just made a
slight change because it's not always a
register. Um, so because it could be a
memory location or a value and
and so your rotation function do have a
similar bug set
Z. I think the rotation functions are
similar.
Um, I think the rotation functions are
okay. Or did I just do those? Um, R O R.
I did I just did those. So, RO L and R O
R. So, yeah. So, I think we're okay with
that now. Again, I would need to do
proper testing to make sure. Oh, sorry.
I was looking at the code there. I think
we would need to do proper testing if
that were the case. But, uh, again,
I'm going to call this a win. So, again,
thanks to Gare, thank you to Ray. I
appreciate your help. Ray, I will get to
some of the other, you know, refactoring
suggestions you made later, but uh for
now, I think we're going to call this a
night. I got a big day tomorrow.
Everyone,
thanks for watching. And as I love to
say, keep on coding. Take care.