Emulating a CPU in C++ (6502)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
so what better way to learn how cpu works than by emulating one which sounds crazy but i don't know sounds like a good idea to me i i think if you write an emulator for a cpu you do learn well you have to learn a lot about how that cpu actually works so i thought i wouldn't i'm not going to write a whole um emulator for cpu but i'll i'll start writing one and then we can see how the whole thing works and i'm going to write a emulator for a really old processor the 6502 it's like four years old now the reason i'm going to do that is because the 6502 is an incredibly simple 8-bit processor and although yes it's incredibly simple it does have a lot of the same like principles that it operates on as a modern processor so a modern processor and the 6502 are yeah they're like chalk and cheese but the principles of how they work are the same they they have memory they have code they execute code and they run through the instructions they have registers and so on and so forth so if you understand how this works then you will better understand how a modern processor works and by writing an emulator you maybe get an idea so um found this website that just goes through all the go to all the architecture of the 6502 we'll probably start there so let's have a look at architecture so it's relatively simple 8-bit cpu with a few internal registers and in fact it's only actually got three registers so register is just a small area of memory that the computer can access it can only address 64 kilobytes of memory yeah so that means it's got a 16-bit address bus so there's 16 pins coming out processor that can address the memory processor is little endian which is convenient because i'm little ending as well on my modern pc so um i did a video previously about ending this and why you might care and so for my case i'm probably not going to do anything with that because i'm little endian and 65 of 2 is little endian so the co have something called the zero page in the 6502 and zero page is just a fancy way of saying the first 256 bytes of memory and the reason why that's important as you'll see later on is that back in the day when memory was very expensive and the computer didn't have very much because of that reason so the the cpu has a bunch of special addressing modes on most of the instructions that allow you to access stuff that's in that first 256 bytes one cycle quicker than you can do with anywhere else that's actually quite important because it basically means that this first 256 bytes becomes like it actually behaves like a bunch of registers like you've got lots of instructions that you can do there so this was quite an important concept of the 6502 this was one of the things that made this processor like quite fast is that lots and lots of things could like skip a clock cycle by doing this and when you didn't have that many clock cycles per tick well you had to save as many as you could uh second the second page of memory the next 256 bytes is actually the stack memory so you've got a 256 byte stack memory which is amazing to think about that nowadays stack memory on a modern just on a single thread of a modern program is like way more memory than you had on this whole computer and there's lots of other reserved memory addresses that do various things and we'll get into some of those because one of them is the reset vector and that's the place where it reads from initially when the computer is booted up it had memory what they called memory mapped io so hardware devices were mapped to regions of the memory in order to exchange data so that was weird because it meant that even though you had 64k of ram um certain bits of ram weren't really accessible because they were mapped to certain hardware things so that was a bit weird but that's the way it worked and it was very simple and again this is this computer was old so or this process result i'm thinking mostly about the commodore 64 which had this cpu in it which is the computer i had but it was in so many computers like um the bbc and so um loads of others the apple had them in l2 whatever we could have a look at what registers the cpu has and then we need to emulate all of these registers and there aren't many so that's quite a good thing yeah 65.2 had a small number of registers first one being the program counter now this is something that you will get on a modern processor and what that is is it's the pointer to the address that the cpu is executing the code for so whatever address is in there that's going to be the next instruction that's going to be executed so that's the first thing we're going to need so again straight off the bat we've initially found a parallel between a modern 50 billion core computer and a 40 year old cpu so to start with i am going to create a little struct i'm going to call it cpu and i'm going to start creating these registered now the first thing you've got to note here is this is a the program counter is a 16 bit register because that's the 16 bits is the biggest address we could register so it's got a register that large so i'm going to need to define just a couple of things here so i can actually know like what is a 16 bit i basically need to define a 16 bit and an 8 bit type in here to use so i'm just going to call one byte let's do it uh let's do it like that so this is where you have to know exactly the size of a certain type on your platform or on your compiler that is a buy so in my case i'm going to use unsigned so we're going to assume that spite and we also need something for the 16-bit one i'll call that word so we're gonna have byte for eight bits and word for 16 bits so i'm just defining my little types for the um let's stick those stick those in there so we've got a cpu byte and a cpu word so we kind of know like that's our types for this so the first thing we need is we need the program counter and that is a word so i'll just call that pc i think it's traditional to call that pc so we've got a program counter now so we're already starting to emulate this this um cpu we need this stack pointer again this the stack pointer is an address it is the address of the current part of the stack that the program is using because so all modern cpus have the concept of a stack which is a small piece of reusable memory and you know when you're executing code you're pushing things onto the stack and you're popping things off the stack so the stack in fact is just a small piece of memory that just gets reused and again you can see that even 40 years ago that was a concept okay it was only single there was only a single core a single thread and it was only 256 bytes but the same principles applied back then just like they do now that you have the concept of a program pointer program counter and a stack pointer and now we've got the three all three registers that the 6502 had and these are 8-bit registers so yeah so you can see like why this really was an 8-bit computer the register's rate bits because it only had an 8-bit data bus the accumulator we just traditionally got i think that's traditionally called a index register x that's traditionally just called x and we can do this like this and y so those are our registers oh registers and the next thing is we have processor status now this is a bunch of bits i think this is one single byte so the processor status is a single byte with a bunch of bits in it that have flags in it that gets set depending on what certain instructions happen and you get the same thing on a modern processor but different processors have different flags and they're all used for different things so we've got carry zero interrupt disable decimal mode break command overflow and negative and the depending on what instruction gets called these flags will be set or cleared um depending on what's going on so there you go so overflow flag will set will be set during arithmetic operations if if the result revealed it revealed an invalid complement so you know you overflowed um a sum and this flag will get set so we need to define those and i'm going to define those as a we could just do it as processor status like that like we could have a byte like that but um because we're going to access all these via bits i'm just going to do it as uh a bit field like this so c plus sql supports bit fields as well so traditionally i think it's just called carry is c uh zero is essent um interrupt disable i'll just call it i uh decimal mode d uh break command i'll call it b uh overflow flag is i think strategy called v and the negative is called n so so these are our status flags there we go so you can see how simple this is like literally i think for the internal data for the cpu i think this is literally it like that that's its internal data structure which is so super simple compared to a modern processor but it's enough and it's enough to get code executing we've got our cpu now we can put our cpu here and there you go we've made our cpu and it doesn't do anything it has no code it has no memory now keep in mind that the 65.2 can't really operate without memory because it needs the it needs the stack pointer to point to somewhere and it also needs the a lot of instructions need the zero page if you're gonna execute anything like it to actually execute any code you at least need the zero page and the stack pointer and then you and the stack memory and you'd need some more memory on top of that to actually do anything so first thing i want to do probably is i want to reset the cpu now we don't really know how that's going to work yet but somehow the cpu needs to get reset into a state like as if you turn the computer on which isn't a real computer but uh we somehow need to be able to turn this thing on what i'm going to do is i'm going to initialize some of these values to something like like when a 652 booted up there is a boot up sequence i'm not sure if i've got it on here there's a reset process here so you'd have this reset vector which is the address in memory where it would start looking for and then that would jump to a kernel routine in the rom and that would execute some code like this that would do some things initialize memory and stuff like that but basically because we're emulating we can just uh we can just fake this for now um so what we could do is we could assume that firstly the program counter we'll we'll initialize that to the value that the reset vector would be fffc like you can see in in here this is the kind of things like this is what the c64 would do it would initialize memory uh it would initialize ao set the i o vectors more initializing um and then it would actually jump to the the basic rom and it would execute the basic rom and then the basic would boot up and you'd have your ready prompt so we're not going to do any of that we're just going to um uh like you can see here it clears the decimal flag so like that's our d flag so we would we would clear that that'll be one thing we do on boot up so i think for now i'm going to um set the stack pointer i mean this is really what this this boot up sequence here would be doing the stack pointer is getting set to um f f i believe what i think's actually happening is is it does transfer this value this ff to the to the to the stack but at some point here that that would be in the zero page that wouldn't be the first address of the stack looks like here the first stack access happens at 100 which is what i would have expected um so i'm just going to for now i'm going to initialize my stack pointer to 100 because something like i mean i'm not trying to accurately emulate this accurately at the moment i'm just trying to get something working and if you want to go through this and accurately emulate what happens here then you'd have to do all this and you'd have to load the colonel ramen and do all this stuff but i just want to get a cpu that boots it won't necessarily be an actual commodore 64. it will be a computer that's like a commodore 64. i think i'll also initialize the the a x and and y registers to zero again i'm not sure if that's exactly what we want would want to happen but i'm gonna do that anyway and i'm going to initialize like it said it initialized the decimal flag it cleared the decimal flag but i think i'm just going to clear all the flags for now and that's going to be my reset routine for now it's not necessarily accurate but it's it's similar to it's it's getting my processor into a state where i think i'm ready to execute one of the things this does do though is it initializes memory you can see that here so um it jumps to a subroutine that can initialize the memory so i'm assuming that might actually um zero all the memory which is possible so what i might do is that would be the next thing we'd want we need some memory for this computer so i'm going to make a struct uh i'm going to make a strip called mem and in here this is where i need the actually i'll take these out of here now because actually the memory needs to access these and the cpu needs to access these so let's have um this is going to be our data here for that it's the actual memory um um that needs to be a let's also define so i'm just going to define this u32 for this is kind of like the bite and the word are for the actual like emulated computer but the e32 is for me so i'm just using that in there so const expert y you've got a problem with context but because you're not static there we go so we've got a little bit of memory so we've got some memory and let's put that here as well so we've got our memory we've got our cpu so the reset probably needs access to the memory as well because it needs to initialize the memory so let's pass in the memory to the reset and in here we will initialize so we'll can we kind of want to do what that thing's doing i i think this is just going to zero this out but again it's not very clear you'd have to go and look at what that kernel routine did on this particular computer so we're doing a bit of both here we're trying to emulate the cpu but we're also maybe emulating slightly um what this c64 would have done there you go so that's uh it's our initialization wow that's really lo-fi isn't it um so yes we've got cpu we've got memory we've got a cpu and we've done a reset on it and we're technically in a position now where if we started executing the program counter would want to start reading from this address and start executing the code that's in there but of course at the moment we don't have any way of executing any code in this so that's all pretty cool so far so i think next what we want to do then is we actually want a piece of code that can execute an instruction or we want to execute instructions so what we want there is we want execute and we need to take in it needs to take in the memory because well the instructions come out of memory we want to execute instructions we've got to do them from the memory somehow so let's execute in fact this is going to have exactly the same well very similar to this so this execute function at the moment we we've told it to execute but we haven't told it how much to execute because we probably want it to um execute a certain number of instructions and then maybe stop while we did a redraw of video graphics or something like that so the cpu basically has a clock on that those clock the clock ticks and each cycle or each tick of the clock um the the cpu would be able to execute an instruction it might fetch one byte from memory or and then the next execution might have to fetch another byte and then it might have to do something else so what we really want to do here is we want to pass in uh the number of of cycles that we want to execute the number of like ticks of the clock so i'm just going to pass in two for now but um really what's going to happen is uh so we want this to be it's like the number of ticks let's call it um basically what we're going to say is we want to execute some instructions at memory and we want to execute instructions for this number of ticks so if we have load accumulator like you see i've looked at this one before this is the one i'm going to do actually so load and store operations basically this instruction can take some data and put it into the a register the accumulator and um if i hit that loads a byte of memory into the accumulator setting zero and negative flags as appropriate so that's what this does uh and you can see where i put the v for the overflow and everything before so there's all the the bits the status registers that are effective these are the addressing modes so these are all the different ways you can call this instruction as you can see at the end of the day what this means is this this one instruction that you write in assembly language actually amounts to one two three four five six there's actually eight different versions of it and this would be the code that the machine would actually read so that is the machine code that it would read this is how many bytes the total instruction needs to take up so it's one byte for the op code and there's another byte for the actual data that this one might require and this is the number of cycles that it takes so actually we'll call this cycles because that's what they're calling it here you can see here that if we want to execute load accumulator in immediate mode we're going to need two cycles to do that i've actually passed in two there and the reason you can see why is because like each because it's an 8-bit processor each time it goes to the access to the 8-bit bus a cycle needs to happen so it has to do one cycle it reads the bus another circle it reads the bus and because this immediate mode one this load accumulator is one by for the instruction and then another buy for the data that that you want to load immediate just means like a literal piece of data that you've in lined into the machine code that you want to load into the accumulator so you'll need a cycle to read the op code and another cycle to read the immediate value so it's two cycles to read um to load the accumulator and it needs two bytes of code but basically to execute an extraction we need to be able to say how many cycles do we want to execute before we stop on our simulated processor what we need to do then is let's say well cycles uh so we're gonna decrement we're gonna say we want to do these number cycles we'll keep decrementing these cycles until there's nothing left to do and then we stop executing and return from this function we need to fetch the next instruction from memory so where is the next instruction in memory it's wherever the program counter told us it was so you can see now why we need the program counter because what we want to do is we want to fetch an instruction um and we don't know that it's instruction we just want to fetch when we say fetch we're going to fetch the next thing that's pointed to by the program counter that needs to take in the memory and it also wants to take in the number of cycles because this fetch is going to take a cycle like we'll define how many it takes so this should be um the instruction so we're going to fetch the next instructions fit that on the screen there we go so what we want to do is fetch the next instruction from memory so we know what to execute so let's write that up here in this case actually uh because we know we're fetching an instruction we actually want to fetch uh we actually want to fetch a byte don't we so let's do let's call this effect byte wherever the program counter tells us uh we want to take the memory and we want to fetch the next byte from wherever the program counter told us it was and that would be uh that'll be our data that we got from memory and then we want to increment the program counter because every time we fetch from every time we do this fetch the program character moves on to the next step that's the whole point of the program camera we also know from well if we looked up the data for this processor we'd know that the cycles is decremented well we we used a cycle to do that in fact that needs to be passed in by reference so that we know that when it comes out it's decremented number of cycles and then we can return the data so that's us fetching one byte from memory i don't think that's going to compile because we don't have yeah we don't have an operator for that so let's just put in um let's just put in an operator to get a buy out of memory so that will just return us this is basically like a read by function and we'd probably assert here you'd actually put an cert in there to make sure you're accessing the memory correctly yeah so that's it so i've just got an easy way of reading a byte there and that should compile now maybe uh down here except fetch byte so the cpu starts up we reset it and which includes in fact um yeah we've reset it and then we start execute we've just told it we can execute two instructions from memory and all that does is goes in here and it fetches by fetches the instruction and then that will decrement the number of cycles and we keep doing that until the cycles is done and then we stop executing so in reality here we need to keep executing more and more and more but for now i just want to exit i just want to execute two clock cycles at the moment i'm just trying to get this thing like booted up and working so i wonder if we can get to here without crashing at the moment um so we haven't used this so it's complaining because i haven't used this variable yet so i think we can just do this yeah we just cheated into thinking that it's been referenced i'm just getting it to compile there that i'm going to take this out in a minute so i want to find out i can get to the end of this without this crashing and the answer is yes and we can have a quick look there's our program cat or our stack point has been initialized and there's a lot of zeros so we've so we've got something here but it doesn't do anything yet uh and really this is where we want to start like looking at this um instruction and seeing if we can actually create an instruction like this and actually run it so let's do that so let's so what we want uh we want to at least get one of these like be able to handle one of these instructions so i'm going to try load accumulator immediate mode because that's really the simplest one i could probably do let's just put this here for now uh so these are and up codes are all bytes on this cpu and i'll just say it's an instruction it's load accumulator and it's immediate mode and that has a specific op code here which is a9 there we go so then so now we should be able to switch so now we should be able to switch on that instruction that we pulled and and actually deal and actually deal with actually running that instruction now all we have to do is like do what it says load up load a byte of memory into the accumulator setting the zero and negative flags as appropriate well let's do that then uh there's no example of this here but immediate mode means basically the first first byte is the op code the second byte is the um is the actual value to load so what we want to do is we want to fetch another byte from memory so if we got to here i'm going to fetch another buy from memory uh and this is the value this is you can see here that we are expecting that after we got this particular instruction the next byte is the actual like is right there in in inlined in the machine code is the next value that we actually want to put into uh put the register and then we can just set the a register with this value so that's most of it but also setting the zero and negative flags as appropriate uh zero flag is set if a equals zero so we can do that so the zero flag is z and we wanna set that if a equals zero so we do that and uh the negative flag set if bit seven of a is set so we've got to do that as well so that's the negative flag so if the a so if bit seven of the accumulator is set then we set the negative like so we don't even really have to know what this means we just need to know that this is what the instruction is supposed to do so you can see here that this is our basically our two clock cycles will execute we'll come in we say okay we want to execute two clock cycles we fetch the instruction we look at the instruction if it has to be one of these we fetch a bike that took another clock cycle and then we set the appropriate things and then when we came around again to loop this again the number of cycles will be zero so we'll break out and then our program will stop executing so we've got the world's crappiest 6502 there is it's a 65.2 and it can only execute one instruction specifically and that's all it can do um so if we run this what's really going to happen now is probably nothing because our memory is just initialized to zero so we try that in the debug if this actually compiles let's go in we'll execute and in fact this uh yes it's going to get the first instruction it's zero and it won't execute that it's going to try for another one that's also zero and then it exits so it didn't do anything so that's uh that's a bit of a shame so we really need to get a program into memory here that can actually execute something and actually do what we wanted it to do we should probably also have a default uh here uh do you need to put break after default i know i was never quite sure and now we have basically almost like a debugging thing where where we actually um we're going to get a printout for if a certain instruction just isn't going to be allowed we should also really put that asserting because we're probably going to send the program counter past the end of the stack uh past the end of memory and it's all going to go wrong but that's if you want to do this properly robust i'm just like trying to get something working at the moment after we've reset the memory let's cheat a little bit um we've reset the cpu let's cheat a little bit and let's put an actual program into memory that can execute so we know we're going to execute from fffc which is the reset vector we've actually got enough room right at the end of memory just to put just to put our little program so we want an a9 which is going to be our instruction to it's going to be in fact we can we've actually got a thing for that haven't we we're going to inline a little problem here so this is quite often something you'll do when you're trying to get something working is you write you write a bit of like code that's just in line this would be loaded from disk or do something else um and then we want to put a um i think that's cpu isn't it and then we could do a thing where we load we load some value we're going to load hex 42 into memory there we don't have any way of actually writing into here at the moment so let's put that in there we go so that will allow us to actually actually returns as a reference to that part in memory that we can actually write to so now we've got a little program here so in effect if i just break point down here let's have a look at memory memory data um memory data plus fffc there it is there's our little program in memory there a9 42. so we've actually hard coded a little program um into memory there um so hopefully this this is our now we've actually written machine code inline virtual machine code inline in this program so let's just go in now and let's see if we can actually execute that instruction remember i've said i only want to execute two cycles but i already know this only takes two cycles so we're going to go in we are going to fetch a bite and that is where is our watch there there it is a9 it's quite hard to read that so we actually we did actually fetch so we're at the right place in memory uh and then we're gonna fetch a value and that value is 42 that's the value we set that into the a register and then we're going to set these values appropriately z uh it should be cert if a is zero which is not and if the negative flag is set then we set negative and it's not so we come out of there and that's our cycle done because we only wanted to do two and there you go there's our cpu and we loaded our 42 into the a register and you can see the program counter has moved on and we're we're about to go off the end of memory here so that's going to be the first problem with this is that we're already about to we haven't got any way of jumping around in memory so at the moment we're just going to keep executing memory till we go off the end and then we'll just crash or we'll we'll start writing memory in our virtual machine so that's actually pretty cool so far so really when it comes down to writing one of these uh when it comes down to writing one of these programs one of these emulators for a cpu i mean firstly you're gonna have to do a lot more than this because there's probably like a hundred or so instructions and you've got to do a case for each one of these and you've got to handle exactly what it does um you know it's aromatic instructions there's instructions that jumping around there's all there's another seven versions of just this alone i mean let's try another one anyway let's try let's see if we can do a a zero page um we could do the zero page one as well um and we could see how that one works so let's do that and now we add another case so what have we got to do for this one i think for the zero page one um the the next buy i mean i'm doing this from memory but the next buy after the op code is the address in the xero page so this ends up being the same thing to start with is that we fetch the buy but this byte is now the zero page address but then we want to be able to we want to use a clock cycle to read that value from memory but we don't want to use this fetch byte thing because this is actually incrementing the program counter so in fact what we want is a read by instruction but the read byte won't affect the program counter but it will take a clock cycle so actually i think what we want is instruction here that can just we call it going to call this read byte um so this is the cpu not executing code as such but just reading a value from memory and it's going to be very similar to this except it doesn't increment the program counter because we're not we're not executing a piece of code here we're just actually reading a piece of memory so this ends up being pretty much the same so let's go in down here so we got our xero page address now we want to read it by read the byte from the zero page address from memory and that is going to be uh in fact that can go straight into the a register uh so you can see why that took three clock cycles now so this this zero page takes three clock cycles because it's one clock cycle effects that fetch the instruction a clock cycle to fetch the immediate value to fetch the zero page address that's stored in the machine code and then there is the read by function which takes a milliclock cycle to just get that into the a register and then the next part again this bit's always the same so we always do this bit afterwards so this is probably where we want to extract this setting the zero flag and the negative flag out into a function so every time we execute um an lda instruction we're just going to do that afterwards what's wrong with you oh zero page address needs to take got this wrong so it needs to take the number of cycles there we go so yeah so this read bite takes needs to take cycles the address and the memory into account here so we actually need yeah so it needs to take the cycles the address and the memory had that wrong before there we go so this re read one by yeah needs the cycles it needs the uh oh the address is not the program camera i have that totally wrong it's the address there there we go that's better that makes total more sense so in effect is that correct that our load accumulator from zero page can now fetch the byte read the byte and then sets the states registers are we compiling yes we are and let's change our let's change our little program to load from the zero page is that what i called it and we're going to load from the zero page address 42 and what we'll actually do is at the xero page address 42 we're actually going to stick an actual piece of data in and this data will be 84. so in fact what this should do is is take the value 84 and put it into the accumulator so let's oh we're going to need to execute three cycles to get this one working so let's see if that works so first cycle we get our instruction which is a5 that's the correct instruction so it's going to switch the correct one here we are going to load the zero page address which is 42 and then we're going to read from the zero page address um from that memory and load it to the a register and there it is the value 84 then we set our flags and we come out and uh there we have it and 84 got into our a register so we've now implemented two instructions we've got load address immediate and load address from the xero page um so you can see how this goes now is that this i'm just implementing one i think there's 22 or something instructions in the 65 or 2 but with all the addressing modes um there's actually quite a lot more basically so there's another one here there's um uh in fact we could have looked at the zero page thing there yeah so this this website actually tells you um about how these kind of things work so zero page x so the uh there's another one here the address to be accessed in instruction is indexed using the zero page uh by taking the eight bit zero page address from the instruction and adding the current value of the x register to it so there we go so it actually tells you how to do that so let's do that one as well because that one's that's that's that can be done um so you can see why that needs four clock cycles now we need one to fetch the instruction the op code one to fetch the zero page address one to uh increment uh with the add the x value onto it and another one to actually read the byte so that's that four o'clock cycles so this is all starting to make loads of sense and you can also see that why is there a zero page why there is no zero page y there's only zero page x on this one and then you can go through and like do all these basically load x register um like y register and so on and so forth you could probably find a lot of repeated code in here and start actually like i did with this lda set stats there's probably like loads of things here where you keep doing this but essentially that's how you start writing a process the only other thing you've got to do then is actually load a program um into memory i think i suppose one of the other instructions that we're going to need before we really do anything in fact it was right there is jsr jump to subroutine jump to subroutine we can't actually move around the program counter um so that would be another one we need we would need to be able to take the current program counter we want to be able to um go to an absolute address in memory push things onto the stack pop things off the stack so on and so forth uh yeah json instruction pushes the address minus one of the return point onto the stack and then sets the program counter to the target memory wrist maybe we should have a go at that one because that way we'll actually have to use this stack and we'll actually have to use a 16-bit address as well yeah okay so this is so firstly this is absolute addressing mode which we didn't do before but absolute dressing mode uh instructions using absolute address men contain a full 16-bit address to identify the target location in fact their example there is one of them is jsr so the first thing i'm going to need to do is read a 16-bit address from the from the program so we need a fetch word that's what we're going to need is so so we want fetch word and we want to fetch it from where the program account is so it's quite similar it's going to be quite similar to this except basically basically this is this is the bit where we now need to know that 6502 is little endian so that means that the first byte that we read first byte that we read is going to be the least significant byte of the data so actually we can just read the first byte and stick it in there and decrement a cycle and for the second byte that's going to be the high byte uh it's going to be something very similar it's going to be very similar actually we're going to read again except this time we are going to shift it up by 8 bytes and we're going to pour it there we go so in fact what we can do here is we can turn this into just cycles plus equals two i'm just having two minus minuses because we don't really care too much until that's done at the end 65 2's little ending my platform is little endian and uh this is where uh if you wanted to handle endingness you would have to swap back here so you probably have a something like uh on the indian then you would stop swap bytes inward data yeah so that would be how you'd handle if you wanted your emulator to compile on any platform you'd have to know whether your platform is big endian or not and you'd have to swap them i'm not going to bother here because i'm little endy and the 6502 is little endian so actually um this all works out in the wash but again what i'm doing here is i'm just making the i'm reading it out and then i'm making the assumption that my platform is a little ending and then i'm just swapping at the other end so the other way of doing it would be to just do this in the other order and write the code differently but i think this way is nicer because you always handle it in one way and then you swap it um if you're big ending i don't know that might be good it might be bad but i'm not going to bother handling it here so we are going to implement our thing now we can fetch a word um so we fetched our instruction and we are going to fetch the word so that's the jump address uh subroutine address is going to be this workflows and push the address minus one of the return point onto the stack ah okay so this is where we need our stack pointer we've got a stack pointer here and we've got our memory uh push the instruction pushes the address minus one of the return point yeah so the return point is going to be where we are now minus one and i suppose the reason for that is that when you execute the last instruction at the end the program counter will have to read this back off the stack and then increment the program calculus one so we're actually going to so this is what it's telling us to do is push the return point which is the where the program counter is right now remember the cycles that takes a cycle to do that and then set the program counter to the target memory address so then the program counter becomes the subroutine address and that takes another cycle and it doesn't affect any flags and there we go and i think that's our six cycles is that one cycle to get the instruction two cycles to get the address from memory that's three another cycle to set the uh oh no actually there's gonna need to be two cycles for this yeah because that's only five actually we've got this wrong because now what we want to be able to do this i've actually only written one byte and here i need to write two bytes so this is the next um this is the next thing i'm going to need i need to be able to write a word i want to be able to write a word in here um the least significant by goes in first the most significant by goes in second and we can write a word in there what's wrong with that what is wrong with you oh because it needs to be anything wrong with that oh we can't have data there we go so now we can write what now we can write two bytes while we're at it why don't we take in the cycles for that as well and we will take two cycles off because we took one cycle to do that and one cycle to do that um i don't know what order to put these in this is going to be a very confusing function to call here we go so the return address minus one and we don't need that anymore because we're subtracting our cycles we do the value the address which is the stack pointer and the number of cycles that we that this takes so we can increment it did we actually we actually take that in by value we didn't whoops cycles has to be by value because we're changing it now we're doing what it says i think yeah read the instruction one cycle fetch the address that we want to go to that takes two cycles then we want to write the address minus one of the return point which is currently the program count minus one onto the stack it takes another two cycles that's five and then lastly um move the program counter on to where we wanna jump to so that is our jump to subroutine function and there's why it takes six cycles uh and you can see why it takes three bytes because we actually read three bytes so this all kind of comes out in the wash this is really good and maybe we should see if uh should we see if we can actually do a jsr we can hack the program now uh e so remember a little by little bit is first right let's just jump to let's not jump to zero zero let's jump to well let's jump to 42 42 so in effect this should read the reset vector which can have a jump to subroutine which is going to take us to 4242 and it's also going to push the return value onto the stack and then in effect if we actually did put something at 42 42 oh if this works this will be awesome we'll put in uh we'll put in a load immediate function uh and we're going to load the value 84 into the a register and what do we need to do this six nine nine instructions to do this so in effect what this will do is it'll start the program and we'll end up with just 84 in the a register and i'm going to run it out for the best here goes nothing and what did we get we did we got it we got 84 in the a register um and we didn't really we didn't really uh return from our subroutine because we haven't written that yet but that's that's pretty cool we're actually so we actually got a program that can actually branch we're not really branch but it can jump around in memory now uh hence the random access part of it so i actually think like considering the amount of time this is taking me um that is actually uh that's actually pretty cool that i've got this much this much actual like of a see of an emulated cpu running in this amount of time you can see why people write these because they i suppose this is kind of like fun if you know what i mean like working out how a cpu works and how can you actually emulate one but i'm actually quite happy with that so i'm gonna leave it there but you could see that if you wanted to carry this on you just gotta keep putting these instructions in and getting them to work and you've got all the ads and ads with carries and all that kind of nonsense so i'm not going to do all that here because it's just a lot of work but this is the beginnings of uh an incredibly simple cpu and the other cool thing to notice is have you noticed how the entire memory and cpu this entire program didn't allocate any heat memory it's all actually on the stack in my uh on my machine so the memory is on the stack and the cpu is all on the stack so the entire thing is totally like it didn't allocate any memory to access the whole thing the whole computer can run on there you can also see that this is one of the other points about this is that when you do actually create because you'd have to like if you want to do this properly you'd have to create debugging and all kinds of nonsense um when you actually create one of these emulated cpus that once you've emulated the whole cpu you'll realize that you can't actually do anything with this the cpu can you know it can read and write its memory and it can read and write registers and so on and so forth but until you put some input and output on it you will never be able to see any output from this cpu so really you can see why a cpu on its own can't do anything it's the central processing unit but you can't see anything you can't hear anything it can't write to any serial buses it can just affect the memory that's in the computer so really what you need to make a full emulated machine is you need to start interfacing with for instance a video chip and that's why you can see like when you get a computer it's the other chips around the cpu that are really making the computer work so if you put a video chip in here that have some memory and some registers if the cpu could actually access those memories and registers it could then start displaying things onto the screen but this this emulated cpu here can't do that but you'd still have to write all this to get to the point where you were actually showing stuff on the screen so this can't write to a terminal i can't do anything but in effect it's starting to work you could also see how maybe if you were writing your own scripting language that this is kind of how a scripting language would work if you could if you get a scripting language and you could compile it into a bunch of very simple instructions they could be executed like this and you could basically have um a compiled language the problem you get then is that really all the works in the compiler it's not actually in the machine itself it's in the compiler and how does the compiler how does this um interact with the rest of the program so i hope you learned something about how an actual like cpu might work and how you might emulate one and how you might go through all these and start implementing all these and you can see none of them would be very hard to implement subtract would carry uh decimal flags store accumulator yeah we did this we did the um did we do store accumulator now we did load accumulator so this one takes the accumulator and puts it back into memory that's slightly opposite so very cool hope you learned something from that because um i'll put this on github and if maybe we'll continue at some point and start adding more and more stuff to it but this is super super basic but you could see that in in a day or two well maybe even less actually you'd probably be able to get a whole machine working and also shows where you'd need to do a lot of testing to make sure this thing's actually working you could write a test for each one of these instructions because it's so simple and you could get a fully working 6502 a 40 year old processor emulated on a zero year old processor
Info
Channel: Dave Poo
Views: 336,498
Rating: 4.9605927 out of 5
Keywords: c64, 6502, emulator, retro, 8-bit, 8bit
Id: qJgsuQoy9bc
Channel Id: undefined
Length: 52min 27sec (3147 seconds)
Published: Sun Aug 23 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.