Submind YouTube summaries
Thumbnail for How to Code a 6502 Emulator in Python Part 14

How to Code a 6502 Emulator in Python Part 14

Watch on YouTube

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.