NES Emulator Part #3: Buses, RAMs, ROMs & Mappers

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

This series is fantastic! Can’t wait for the PPU episode.

πŸ‘οΈŽ︎ 13 πŸ‘€οΈŽ︎ u/justindarc πŸ“…οΈŽ︎ Sep 07 2019 πŸ—«︎ replies

An actual full guide and modern

πŸ‘οΈŽ︎ 9 πŸ‘€οΈŽ︎ u/sarkie πŸ“…οΈŽ︎ Sep 07 2019 πŸ—«︎ replies

Damn this channel is a gold mine, never heard of it before.

πŸ‘οΈŽ︎ 8 πŸ‘€οΈŽ︎ u/memeita πŸ“…οΈŽ︎ Sep 07 2019 πŸ—«︎ replies

This is the best 40 min video I ever saw. I don't know how to program anything but this videos are really easy to follow and to understand the logic behind it. To me the most important part is that I'll finally understand why mame color pallet are so off for NES emulation in the next video, I can't thank this guy enough!

πŸ‘οΈŽ︎ 7 πŸ‘€οΈŽ︎ u/cd4053b πŸ“…οΈŽ︎ Sep 07 2019 πŸ—«︎ replies

I start later with part #1, thanks for sharing !

πŸ‘οΈŽ︎ 2 πŸ‘€οΈŽ︎ u/wudini1911 πŸ“…οΈŽ︎ Sep 07 2019 πŸ—«︎ replies

I'd have given up one of my kidneys back in the 90s for that kind of information. I'm still amazed at how much the Internet has changed the world.

πŸ‘οΈŽ︎ 2 πŸ‘€οΈŽ︎ u/RealNC πŸ“…οΈŽ︎ Sep 07 2019 πŸ—«︎ replies

Are these just videos, or are there equivalent accompanying articles?

πŸ‘οΈŽ︎ 2 πŸ‘€οΈŽ︎ u/MT4K πŸ“…οΈŽ︎ Sep 12 2019 πŸ—«︎ replies
Captions
hello and welcome to part three of miners emulation series this video directly follows on from the previous video so if you've not watched the early ones in this series I suggest you do so now and if you have watched the last video then you may have picked up that I suggested this video would be about the PPU the picture processing unit but I've had a slight change of plan because I started to make that video and realize there's actually still quite a lot of fundamental stuff we need to understand before we can get that far and so this video is specifically about the glue logic that binds all of the different elements of the nest together so let's get started we finished the last video with an implementation of the 6502 processor and a small demonstration program we could use to analyze it that video implied that the CPU in isolation doesn't do very much it needs to be connected to a bus and in that demonstration we used a very simple bus that the CPU could write to and read from previously I treated the entire addressable range of the CPU as a ram while on the nez there isn't 64 kilobytes of RAM those in fact only two kilobytes of RAM and this Ram is accessible from the range 0 to 1 FFF now some of you will already have noticed that that's actually an 8 kilobyte range so how can that range be mapped on to the 2 kilobytes of RAM that's available well the nez uses something called mirroring and this is something we'll use a lot of throughout this series the underlying principle of mirroring is that ranges of addresses repeat so even though the addressable range of the RAM is from 0 to 8 kilobytes the first 2 kilobytes of that is mapped onto the hardware each 2 kilobyte segment of this addressable range effectively Maps on to that original 2 kilobyte section ie these ranges mirror the first range so if I write to this location in RAM I effectively also write conceptually to these locations naturally the same applies when reading from the memory too I just want to emphasize there really is only 2 kilobytes of RAM it's just in this instance each location of that Ram has four different addresses that can access it and as I've already mentioned we'll see this quite a lot on the Ness fortunately implementing mirroring is very simple indeed suppose I want to write to the address 0 X 1 2 3 4 this clearly goes beyond the 2 kilobyte allowance in decimal this is 4 6 6 0 I can perform a bitwise logic and with the value that reflects the realistic maximum addressable range in this case it's 0 7f f which conveniently is 2047 are 2 kilobytes this leaves me with just the 2 3 and the 4 which in decimal is 564 which is within our 2 kilobyte range and this makes sense because effectively we've done 4 6 6 0 mod 2 kilobytes which will always yield a valid address within the realistic addressable range we know from the first video there is also another device connected to the CPUs bus it is the PPU the picture processing unit that handles the graphics and they also mentioned that the PPU has its own bus attached to this bus is an 8 kilobyte addressable range that reflects the pattern memory this is the memory that stores what the graphics look like the sprites and like the CPU this goes from 0 to 1 ffff there is an additional dedicated Ram here from 2002 to FFF and this stores what's called the name tables these are effectively two-dimensional arrays which store the IDS of which patterns to show in the background finally the PPU has a small Ram attached to it which stores the pallet information this describes which colors should be displayed on the screen when you combine the sprites and the background and that goes from 3 F 0 0 to the end of the addressable range the PPU bus doesn't have the same addressable range as the CPU bus it's much smaller now you may notice something is missing here where is the per stored well the answer is obvious it's stored on the cartridge that's what makes the games different and the program ROM is addressed via the cpu bus and basically fills up the entire second half range of the CPUs addressable range the cartridge doesn't just contain the program it also contains all of the graphical information required to render the game so the pattern tables are also stored on the cartridge for simplicity I'm going to assume that name tables are stored on the Nintendo system itself this isn't strictly true certainly there is a two kilobyte RAM available to store name table information but some cartridges extends the feature set of the PPU bus and some of the name table can be stored on the cartridge we also saw in the first video that sometimes the amount of program data required exceeds the addressable range of the CPU and so different banks of the cartridges Rong's need to be connected to the CPU bus as required and this is handled by a mapping circuit which exists on the cartridge mappers are typically addressed in the same range as the program wrong because roms are only ever read from but the mapper can be written to and since the bus is capable of differentiating reads from writes we can split the CPUs intent accordingly to either reading an instruction or some data from the ROM or configuring the mapping circuit just to complete this diagram also attached to the CPU bus is the audio processing unit access to the i/o and the controllers and some just bits and pieces of stuff which will see fleshed out in future videos and so today I really want to build on the system architecture that allows us to define these buses and connect these peripheral devices to them I'm also going to talk about how we can load in a cartridge and look at a system for flexibly mapping the addresses from the roms on the cartridge to both the CPU and the PPU one thing to note is that all of this stuff in the middle here on the cartridge can be read and written from by the CPU and read written from by the PPU our existing bus class is really going to start representing what the nez is so over the course of this episode we will see the bus mutate into being a representation of the console even though the PPU has its own bus it's a much simpler bus and so my PP u will contain internally the things that it needs I'm going to work with the code that I made in the last video I just showed that the system architecture is broadly split into three sections the bus and the 6502 processor which is what we have here the PPU and the cartridge so I'm going to create two classes one to represent the PPU and want to represent the cartridge but the PPU I'm going to call it Oh Elsie to co2 which was the chips designation and for the cartridge I'm simply going to call it cartridge potentially we have lots of devices reading and writing the CPU can read and write and the PPU can read and write so wherever I've got a read and write in my system I'm going to make them quite distinctive only the CPU reads and writes to the bus so I'm going to change the write and read functions to CPU write and CPU read the book contains the devices that are connected to it and as we can see the CPU is already there and we have a ram I'm just going to call this CPU RAM and change the size of that Ram to two kilobytes connected to this bus is another device the PPU I'm going to add a temporary variable called data to the CPU read function on the bus and I'm going to adjust this range which was nonsense in the last video to something more suitable in this case it's the 8 kilobyte range within which the RAM is addressed however we need to mirror certain Ram locations in this case every 2 kilobytes so I'm going to bitwise logic and with the value 0 X 7ff as described in the slides before naturally the write function looks quite similar except from reading from the RAM we're writing to the RAM so the PPU header I'm going to add four methods and hopefully these should look very familiar these to connect the PPU to the CPU bus and these to connect the PPU to its own boss and I'll just create some bodies for these the main system boss is the target of all CPU rights and here we've checked already to see if the right was targeting a ram but perhaps it was targeting a different address range let's assume is targeting the PPU which only has eight addressable elements within that address range however the range one can write to to access the PPU is much larger and so yet again we're using mirroring to reduce this very large address range into just the eight entries that we need and so I'll transform the address with a logic bitwise and and write the data to the PPU directly and naturally what we'll do for right we'll also have some symmetry in read so far the devices are connected to the bus exist in these fixed locations they cannot be relocated by a mapper in a similar way to the PPU our cartridge class will also consist of four methods because conceptually the cartridge is connected to both buses here are the four empty functions in the PP use body and as we had before I'm going to create a temporary data variable and return it the CPU can only address eight different locations on the PPU and in this video I'm not going to detail what these locations do but I'm taking the address passing it through a small switch case statement and for now I'm going to leave it blank they'll do nothing but it's ready for later likewise when the CPU writes to the PPU we'd have another switch case statement but leave out the functionality for now it may be tempting to think why not just use eight different variables and read and write to those but that's not quite how the transaction works in reality because the data read back from the PPU in one location is not necessarily as the last item of data that was written to that location and it turns out that for the PPU just the act of reading and writing to the chip will change its state so even though there are only eight locations that can be transacted with the CPU these transactions are actually quite complex but all of that will be in the next video and since this video isn't about the PPU when it tries to read and write with its own bus it's going to do nothing so I'm just going to put in some placeholders for now I'm masking the address just in case the PPU ever tries to write to its bus in a location beyond its addressable range I mentioned before that the bus is going to mutate into being the core of the emulation it will be the class that represents the nez and then there's very coarsely has three ways of interacting with it we can insert a cartridge which is a function that takes a shirred pointer to a cartridge object the idea being is you load the cartridge into memory before inserting it into the net we have a reset button on the front of the nez and I'm providing a clock function which is what we'll call to perform one system tick of the emulation and I'll just get rid of this little red wiggly by including the cartridge class up here I'm going to add one internal variable to the bus class and this is simply going to accumulate how many times the clock function has been called when the bus is reset we can call the reset function on the CPU and I'm going to set my counter to zero the PPU also has access to the cartridge so I'm going to store a local pointer to a cartridge object and as I did with the bus I'm going to create a small interface want to connect the cartridge to the PPU and again a clock function the cartridge itself is effectively another device connected to the main bus and when the buses insert cartridge function is called I want to connect the bus to the cartridge and I want to connect the PPU to the cartridge for now this ppu Connect cartridge function is very simple we just connect its local Carteret variable to the cartridge pointer we pass in as we have in the main bus class the devices that are connected to it the PPU also needs devices that are connected to its bus but in this case it's just memories the first memory is the vram used to hold the name table information this was two kilobytes but I'm splitting this into to one kilobyte chunks because a full named is one kilobyte and the nest has the capability to store two whole name tables however it does have the ability to address four named tables and we'll see that there's some trickery involved in that when we talk about the PPU in the next video there was a ram connected to the PPU bus that stored the palace information there are 32 entries now I'm going to add something completely unnecessary because I'm a little strange I'm going to add two four kilobyte arrays that represent our pattern memory this memory exists on the cartridge in normal Ness systems and normal Ness emulators will do just that and in fact if you load up a nez game into this emulator this memory will never be touched however because I'm a little strange and I think in the future I'm going to tinker with the inner workings of the nares because I think it will be quite a fun project to develop my own mapper and develop some games that will only run in my emulator yet can leverage abilities that the original nez never had so I'm going to flag this with a little Javid future reminder so just to recap the PPU physically has some vram for the name tables and it physically has a ram to represent the pallets this is not necessary but I'm keeping it in because I might want to do some modifications in the future now let's consider the cartridge the majority of the pins on the cartridge or game pack are really just the data pins and address pins for both the CPU and the PPU and naively we can assume there are some direct connections to some program memory and some pattern memory pattern memory is otherwise known as character memory and I'm assuming the name character memory is a throwback to the days of primitive dot matrix displays where individual characters were small bitmaps in a memory somewhere the nez operates on a similar principle except a sprite can be made up of several bitmaps in this case it's a two by two sprite of 8x8 pixel tiles and so I'm assuming the name just stuck since the pattern memory has a three-letter name so does the program memory P R G the cartridge can be a lot more sophisticated than this firstly it may consist of many different memories and physically different memory chips or it may consist of one large memory but only reference sections of it at a time it is the job of the mapper to take the incoming addresses from both buses and transform them to the correct memory location on the cartridge this way the CPU is oblivious to the data that it's reading and writing as is the PPU larger memories may be split into banks let's assume the CPU is addressing 0 X 8000 the mapper takes that 8,000 and converts it into a suitable address to read from this ROM so it could be that the 8000 really begins here or it could be it begins here the CPU will have pre-configured the mapper to choose which bank represents that address range exactly the same principle applies to the PPU the ROM on the cartridge may be partitioned up into several different banks and potentially all of the sprites for level 1 exist here and all of the sprites for level 4 exist here the program running on the CPU will configure the mapper to effectively fool the PPU into believing it's always reading from the address range 0 X 0 0 0 0 2 1 F F F F yet the cartridge may return different data depending on how the mapper has been configured we'll see as we progress through the series and I add more mappers that there are a variety of different ones with different degrees of sophistication some added additional co processing functionalities others did interesting things with sound effects but I'm only going to stick to the most basic mappers because that covers the bulk of the library of nests games available and this image we've built up here is not dissimilar to how the ROM file that stores the nest game content is constructed the file has some header information that tells it how much character wrong there is and how much program ROM there is it also contains a few other bits and pieces that the nez needs to configure its hardware so just a quick recap the CPU configures the mapper and the mapper translates the addresses from either the CPU bus or the PPU bus in order to read and write data to the memories contained on the cartridge and just an aside this is why there were really no loading times on the older consoles today we expect games to load all of the graphics for level 1 and then move on loads all of the graphics for level 2 etc etc on this system there's literally nothing to load all we do is map the address line to somewhere else in this memory and using digital electronics that's very quick and simple to do both the CPU and ppyou are completely oblivious to the fact that that has happened behind the mapper so to the cartridge class I'm going to add some memory and I'm going to represent this memory in the form of vectors because I don't know how large the memories need to be until I've read the file fortunately for us as the nez emulation community is matured its converged on a standard for representing the files that contain the information of the games and my little rom dumping device creates one of these files I'm going to add to the constructor a string that represents the path to the file and as well as my two memories for the program and character ROM I'm also going to add three simple numbers one which mapper are we using and the other to store how many banks of the respective memories that are there is a fantastic fan resource online called the nez cart database hero found the hardware information for one of my favorite games Disney's DuckTales and it tells you information about the wrong including the size and types of chips that made up the cartridge it even has box artwork but it also lists other interesting information we need for our emulation it tells us which mapping circuit was adopted for this game and don't forget the mapper was a physical piece of hardware which translated the addresses I'm now going to fill in the constructor for my cartridge class the first thing I want to do is create a structure that represents the header as defined on the fantastic Ness dev wiki and I'm going to open the file in binary mode and immediately read in the header the specification of the file says the next 512 bytes are used for training information I just see this as junk so I'm going to ignore them using the header that I've just read in I can extract which mapper the ROM is using it's a little bit convoluted but it's entirely detailed in the file format there are actually three types of inez file and further now all I'm interested in is type 1 but I'll leave type 0 and type 2 as placeholders for later but in type 1 all I'm going to do is read in how many banks of memory are in the ROM for the program memory and we're to resize my vector to that size and then I'm going to read the data from the file directly into the vector and I do that for the program memory and then I do it for the character memory the file format standard specifies at a single bank of program memory is 16 kilobytes and that a single bank of character memory is 8 kilobytes these are not necessarily the same banks that the mapper will see however they give me the correct size for the memory that I need to read it with the memory loaded I'll close the file if you're paying attention earlier you may have noticed that my CPU read and write and PPU read and write functions for the cartridge return a boolean whereas all of the other reads have returned the data they read and I'm going to use this boolean to tell the calling system whether the cartridge is handling that read or write let's go back to the main system bus and examine the CPU write function right now it assumes nothing will interfere with this address but for my emulator I'm going to give the cartridge of first dibs on all read and writes so here when the bus is written to by the CPU the cartridge gets to decide well hang on that was for me and if that returns true then the RAM doesn't get updated or the PPU doesn't get written to in effect this allows the cartridge to veto what's happening on the bus now I expect in almost all cases this is irrelevant why would the cartridge want to interfere with the CPU reading and writing to the CPU Ram it wouldn't but as before I'm going to leave this here because it allows me to create a flexible extension to the emulator in the future where by just changing the mapping circuit on the cartridge I can really change the behavior of minez emulator but also if the CPU is interacting with a cartridge in a way that does matter this returns true and it doesn't bother doing any of the other checks so there's a small optimization to be had as well and as before what we do for right we typically also do for read I've now added the Glu logic to connect the CPU bus to the cartridge I'll do something very similar for the PPU even though right now our ppyou doesn't do anything i'm going to give it the option of reading from the cartridge first or writing to the cartridge first so now I have a framework where all reads and writes to both buses are intercepted by the cartridge and if the cartridge isn't interested well they just carry on as normal to the other devices on those buses but if the cartridge is interested then we need to get the data from the correct location and don't forget we don't access that data on the cartridge directly we go through a mapper the nez emulation community have come together and standardized the mappers because they're a fantastic bunch and as you can see there's a whole bunch of them but the ones that were really interested are these in the top left in fact for this video I'm only going to implement mapper 0 and the wiki will even specify how that mapper works amongst all sorts of other technical information what we can see here is that this ROM comes in a 16 kilobyte or 32 kilobyte format and there's only ever 8 kilobytes of character ROM it's around about now that we're starting to get into the real technical details of implementation specifics and these sort of things do take some digestion if the ROM was specified as 32 K then we can see that this range represents the first half of that and this range represents the second half of that if it was only a 16 K ROM then this second half is a mirror of the first half mapper zero is quite nice because the mapper has really no circuitry at all but for my needs today it will establish us with an excellent template for adding as many mappers as we need I'm going to add a class called mapper when a mapper is constructed I'm going to pass in two it's the number of program ROM banks and character ROM banks and I'll store those locally and then going to add four virtual functions because mapper is an abstract base class the purpose of these functions is to take the input address from the respective CPU or ppyou bus and transform it into an address through which I can index the roms as I've read in from the file if the address is successfully mapped it returns true the internals of the construction are terribly boring and because this is an abstract class there is nothing left to add to it for all of the mappers I intend to implement I'm going to derive a class from this class so for map of 0 I'm going to add a class called mapper well 0 triple 0 in this case and I'm going to set the base class to just be matter and I'll add overrides for the four functions and give those some bodies so don't forget the purpose of a mapper is not to actually provide any data it is just to translate the address when the CPU writes to its system bus the cartridge is only interested if the address is within the range 8000 to well at the end of the range those locations are not satisfied by anything else in the nares they have to come from the cartridge so it's going to return true if the address is within that range the same rules apply to the CPU writing in fact very similar rules apply to the PPU reading and writing except the maximum range of the address is slightly smaller in this case encompassing the pattern tables which was 0 to 1 triple F one of the reasons we have the mappers in these separate classes is because we can have additional variables and rules local to this specific mapper implementation here we are really using object-oriented program to encapsulate functionality now don't forget for mappers 0 we change the location of where we read in from our rom file depending on how many banks are supplied in the ROM file and here I'm doing just that this variable contains how many 16 kilobyte chunks were loaded as program wrong so if there's more than one of those then we know we're working with the 32k ROM the 32k maps into exactly half the addressable range of the 16-bit address but we don't want to read it starting at 8,000 because in the ROM file it starts at 0 so if this is a 32 K ROM and the address is in the allowable range I'm going to mask the address to ensure that we are offsetting from 0 and so I set my mapped address to that transformed input address however if we've only got one Bank of program ROM then this is a 16 K ROM and as we saw in the specification we mirror the 16 K ROM within that address range and so just by changing the binary mask as we have done earlier we can mirror that address and return a singular mapped address to the correct location map a 0 is really simple and for the PPU there is no bank switching at all there is nothing to do so the transformed address just becomes the actual address what do we do for PPU map right well the clues in the name it's a character ROM that we're writing to so we don't do anything it doesn't make any sense so in this instance the cartridge is not interested it's just going to return false it could well be the case of course that the cartridge doesn't just have ROM at that location it may have RAM in which case we would want to write to the pattern memory that's an obscure tangent of a thought so for now I'm just going to assume ROM is ROM so now we're getting to the crux of the application the cartridge has the right to intercept all reads and writes on both buses so let's handle a CPU read I'm going to include the mapper zero header file in my cartridge as I develop more mappers I'll be including them all and I'm also going to add a pointer to the mapper class once I've loaded my vectors to represent my program in character memories I then need to load up the appropriate mapper based on the mapper ID and so in this case I'm using polymorphism to selectively choose which mapper class I want to use this now leaves us with just the for read and write functions to the cartridge and don't forget these have the right to veto any transactions on both buses I'll start by creating a temporary variable that'll hold the transformed address but the address will only be transformed if the corresponding mapper routine for that particular bus says that the information needs to come from the cartridge if it does the mapped address variable now stores the offset into the ROM data I've read from the file so I can access that data directly from my vector and return true to indicate that the cartridge has handled that particular address if the cartridge isn't interested I'm just going to return false unsurprisingly we have very similar routines for the other functions except the PPU read and write functions care about the character memory rather than the program memory now the contents i've shown in this episode doesn't have much scope for demonstration so I thought I'd spend the last few minutes showing how this architecture is instantiated and connected together and lay the foundations for building the PPU in the next episode so this is the code from the last video that displayed a memory dump and to the processor status I'm going to keep all of that but I'm going to remove the contents from on user create I've added two variables a pointer to the cartridge object and an object of type bus which now represents Arnez firstly I'm going to load the cartridge file secondly I'm going to insert the cartridge into the nest which will connect the cartridge object to the PPU and the cpu via the bus because I'm still interested in the inner workings I'm going to extract the disassembly over the addressable range and finally I'm going to press the reset button on the Ness I want to display the application a little differently to the user after clearing the display I'm going to check for some user input in this case if they press the C key it's going to execute one whole instruction and if they pass the R key it's going to reset the Ness I'm then going to draw the state of the CPU and draw some of the disassembled code as before but I want to start getting things visual to the PPU header file I'm going to include the pixel game engine because inside this class I'm going to store some intermediate states that allow me to debug it and visualize its inner workings specifically I'm going to add an array that will store all of the colors that the Ness was capable of displaying I've added three sprites here one that's going to represent the full screen output called sprite screen one that's going to represent a graphical depiction of both the name tables and one which represents the depiction of both the pattern tables these have been sized accordingly to their layout in memory and we'll cover that in the next episode the Ness was only capable of generating so many colors and it adhered to a particular palette so that palette is stored in this array pal screen in order to access these sprites I'm going to add some functions which return a reference to them now you might be thinking why not just access these spice directly well that's because when these functions are called I'm going to need to construct these sprites in a particular way in order to visualize them accurately and I don't want to do that unless I have to I'm also going to throw in a boolean that represents when a frame is complete and finally I'll add in two variables scanline and cycle and for now all I want you to think of these of are the scanline represents which row on the screen and cycle represent which column of the screen in the body of the PPU class we'll fill in some of these obvious ones as I've just mentioned these functions do nothing except to return the appropriate Oh Elsie sprite but the one function we've not done anything with yet on the PPU is its clock function the clock never stops an every clock cycle we increase our cycle count now these numbers may seem a little hard-coded but these are the physical boundaries of how the nares works if the cycle gets to 341 we reset the cycle and increase our scan line count if our scan line gets to 261 we set our scan line to minus one and our frame complete flag is going to be set to true why we set this to minus one will become clear when we start detailing the PPU but for now all I want you to think of this is we're scrolling across the screen in the x direction and the y direction because at each location on the screen we want to plot a pixel and we're going to do that every time the clock function is called so I'm just using the set pixel function of OLC sprite using cycle and scanline as the x and y coordinates I'm using the palette array that we've created earlier and randomly choosing either a black or white pixel to look like old-fashioned static noise but from this dev they've provided a set of RGB values which sufficiently represents the palette entries given a particular index I know this all seems a little fast compared to what we've just been doing but all of this will become clear but I have used this color palette information for my emulation every time there is a system tic the PPU needs to do something so it gets clocked the cpu clock runs three times slower than the PPU clock so I'm going to use my system clock counter variable to keep track of when I need to clock the CPU relative to clocking the PPU my intention here is to keep all of my clocking information and there's a bit more to come yet so I can just provide a single system tick at a fast frequency and all of the devices that care about that tick at that particular time will do their thing but right now it's enough to say that the CPU clock is three times slower than the PPU clock going back to our visualization I'm going to draw the nez ppyou output each frame and whereas I have pressing the C key to execute a single instruction of the CPU I'm going to have the F key to represent an entire frames worth of instructions being implemented by the CPU and the PPU note that for both of these I'm clocking the bus and not the device directly and I'm almost done now but I'm going to add two more variables one is emulation run which is a boolean and residual time emulation run if true is going to try and run the emulation in real time so stepping through things doesn't apply I'm going to be sensitive to the space bar being pressed to toggle the value of that boolean if I am in running mode then I'm going to do something or I'm going to be in stepping mode and I'm going to implement time very crudely fundamentally I want to run as fast as I can so I'm going to sit here in a loop clocking the system until a frame is complete this will give me a very high frame rate emulation but it'll be burly playable it'll be too fast given that I'm following the nez dev wiki way of doing things and they primarily aim for NTSC based systems and that means the frame rate of the nez should be approximately 60 Hertz 60 frames per second so I only want to do one complete frame of emulation every 60th of a second this way it doesn't really matter how fast my emulation is the update rate and the user interaction will be at the correct speed and so I'm going to use my residual time variable to govern this frame rate let's assume residual time is set to the duration of one frame in real time one sixtieth of a second each time on user update is called I simply decrease that value by the eff elapsed time the emulation goes dormant but once the residual time variable goes below zero I add another whole frames worth of time to it minus the time of the previous frame just to keep it a little bit accurate and then I emulate my whole frame again this means most of the time my emulator is not doing anything and it does kind of assume that the emulation will be running faster than real time so let's take a look so now the simulation shows the output of the PPU on the left and a small breakdown of the disassembly and in this case it's loaded the nez test nez rom which is used for testing the 6502 CPU pressing the C key allows me to step through one instruction at a time and we'll see we don't get very far because the instruction is getting stuck on is load the accumulator with the data at address 2002 which happens to be mapped into the PPU status register so it's waiting for the PPU to get to a particular state and since we don't have a p pu implementation that does anything it's going to be waiting a long time you'll notice that a cluster of pixels which look like noise for now because that's what we told it to do a generated per instruction I press the F key we generate a whole frame in one hit and if I press the space bar at the risk of the YouTube compression algorithm going nuts it looks like an old television now the eagle-eyed amongst you in the previous video noticed that there was a little bug in the disassembly output regarding the relative address of a branching condition here you can see that really we know that it's sitting in a loop waiting for the value in the accumulator to hit a certain value but it's saying that the address is going to jump to is somewhere way down here it really should be jumping back to C double zero 9 so let's just quickly fix that I'll go to the disassembler which is at the bottom of the 6502 CPP file and all the problem was is this value was unsigned and I'm going to make it signed so let me just double check that that works that looks a lot better can now see the relative values make sense and so there we have it I appreciate not a fantastically visual video this time around but that's all going to change in the next episode when we start looking at how the nares renders its graphics I did feel that most of the stuff in this video was necessary before we get that far though as always I'll upload a snapshot of this particular video code to the github and there's a link below for that if you've enjoyed this video a thumbs up please have a think about subscribing come and have a chat on the discord server we've opened up a dedicated emulation chat there now and it's quite active and finally thanks to all of the new subscribers I've been getting lots and lots recently and thanks to my patreon stew anyway I'll see you next time take care
Info
Channel: javidx9
Views: 102,149
Rating: 4.9809914 out of 5
Keywords: one lone coder, onelonecoder, learning, programming, tutorial, c++, beginner, olcconsolegameengine, command prompt, ascii, game, game engine, pixelgameengine, olc::pixelgameengine, NES emulation
Id: xdzOvpYPmGE
Channel Id: undefined
Length: 40min 36sec (2436 seconds)
Published: Fri Sep 06 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.