NES(FC)模拟器 PART 1 二进制位操作和NES构架讲解

Original Video ContentExpand Video

Key Points:

  • This project involves creating a NES emulator from scratch using the Pixel game engine, emphasizing the complexity of the task.
  • The series will cover key concepts such as hexadecimal notation, binary tricks, and the NES architecture.
  • Legal aspects of emulation are discussed, particularly the legality of emulating consoles versus downloading ROMs.
  • The series will explore essential components of the NES, including the CPU, PPU, and mapping circuits.
  • The goal is to provide insights into emulation while making source code available for educational purposes.

Sam. It's Sam. Ram it. Well, there's nothing else for it, I suppose. Hello. Let's make a NES emulator. I thought I'd start the series by showing you where I'm up to so far, and I'm about 90% of the way through.

So this is the emulator. It runs in the Pixel game engine. And what I want to stress here is I've not just taken somebody else's code and implemented that in the Pixel game engine. This is an emulator from scratch.

So let's get started here with a game. And as you can see, I'm using my controller. We'll start with the moon level. Everybody likes the moon level. The emulation isn't quite complete; there's an audio channel missing, and there are a few little glitches with other aspects to it, such as the graphics being a little bit sticky at the moment.

Not. The timing isn't quite right, and I've not implemented all of the available mappers. Now, that might mean nothing at all to most of you at this point, but these are all points that we will cover. Oops, died there. These are all points that we will cover in the series.

This project has been a complete passion project. I've always wanted to create a NES emulator, and because I've known I've always wanted to create a NES emulator, I've deliberately avoided looking at source code for NES emulators. I knew one day I'd want to do this, and so I've built this emulator from publicly available reverse engineering information and datasheets.

One of the nice things about building your own emulator is you can display all of the internals in real time, too. So clearly I've got the main game screen happening here. I can see live memory updates, too, and the state of the CPU. In fact, I can also see what instruction is currently being executed. But be under no illusion, this has been a complicated project and there's a lot to it.

And there's probably going to be quite a few parts to this series. In this first part, I'm not even going to start writing some code because we need to acquaint ourselves with the basics required. So I'm going to look at hexadecimal notation, binary tricks, bit fields, and the overall architecture of the NES.

Oh, that's not good. I was never any good at this game. There's also absolutely no conceivable way I can cover every single last detail of how to do this in a series of YouTube videos, probably about 45 minutes to an hour each. So I will be creating all of the source code and making it available. And I'm writing the source code in such a way that it can be used as a studying aid.

So it's deliberately verbose. I'm not trying to do any fancy programming tricks. I'm not trying to make the most optimal emulation. It's certainly not the most accurate emulation, but it's been a lot of fun doing it, and it's great to be able to finally touch and feel and hear the NES games that I enjoyed in my childhood.

Now, when it comes to emulation, there's a great big elephant in the room. How legal is it? Well, emulating the console itself is not illegal. It's simply a circuit. There's no proprietary software on there to which I could perform some sort of copyright infringement. However, the distribution of the games is illegal, so downloading the games online, that's illegal.

So I'm going to have to use my current stock of games to get the memories from in order to run them in my emulator. However, I can't just go and download the equivalent online because that would be illegal. I'm participating in the distribution of what are called ROMs. So instead, I've purchased myself one of these, and it's a ROM dumper.

I can take one of my cartridges, insert it into this little circuit board, and extract the memory contents from the cartridge. In one of the later videos of this series, I'll be talking about how this device works because it works in a very similar way to our emulator.

I have a reasonably small collection of NES games, and I thought it would be fun to show them. So here I've got the one that came with the NES, which is Mario Bros. and Duck Hunt. Then we've got Super Mario Bros. 2. Diggity Rock. A Boy and His Blob. That was a great game.

We've got some old ones as well, so Excitebike. And I've got a feeling games like this may come in handy while building the emulator. Classic 1. Goonies 2. Very fun, but never finished it. Another old one here. Kung Fu. Oh. Shadowgate. Some of the best music ever in a game. Really taxing. Lots of fun completing that one.

This was a bit odd. High Speed Pinball? No, I don't rate that one. Ah. Now we've got two obviously rubbish games here. The first is The Legend of Zelda and of course its sequel, The Adventures of Link. We've also got Marble Madness, that's quite an old one.

Oh, here this is a classic. Super Mario Brothers 3. Double Dragon 3 was never really into the beat 'em up games. I've got one here that's a bit strange. I don't think we'll be able to emulate this one, but this is Micro Machines. Ah.

Now I've got the Disney Fantastic One. So I've got a chip. And Dale Rescue Rangers. DuckTales One, of course the best game ever made. And DuckTales Two, which I don't want to lose that one, that's for sure.

And I've got a few that don't have boxes, but are excellent. So this is Captain Skyhawk, and this one is Mega Man. I'm going to zoom in on here, see if we can get some of the Mega Man artwork on the cartridge, if it's not too blurry. Doesn't look anything like the in-game Mega Man and Donkey Kong's classics, which are also going to be quite useful for emulation.

And Teenage Mutant Hero Turtles. I never got past the water stage, and you can see these ones don't have boxes because I've bought them in second-hand markets. My plan is to use this device to extract the contents of the read-only memories on the cartridges. In effect, I'm making a backup of the software that I own.

But of course, I need some way of implementing this backup in order to play the games, and thus why I need to build an emulator. The very first thing I want to do, however, is point out a website called wiki.nesdev.com because creating a NES emulator would have been completely impossible without this fantastic resource.

This website has been put together, presumably combining hundreds of thousands of man-hours of dedicated enthusiasts reverse engineering the NES. And this resource has proven to be completely invaluable in creating my emulation.

But what I like about it the most is not just the sheer technical content of how the NES works. And what it does is it doesn't really include any code. It is purely a behavioral description of what's going on. And so this was perfect for my project. For example, let's take one of the audio channels here, and we'll see a lot more of this in a specific episode later in the series.

This, there is no code. It doesn't tell you how to do it. All it does is tell you what the behavior is. So it tells you what the clock frequencies are of the different components, it tells you what certain memory registers should do, and it tells you what the behavior should be. But it doesn't ever give you any code.

Wiki.nesdev.com is just a fantastically, incredibly thorough resource. It is the work of many people, incredibly dedicated people, far smarter than I am, who have had to reverse engineer this because there was no documentation released by Nintendo. So they've had to write programs, perform experiments, get some electronic gear out, get the oscilloscopes out and work out how it all works. And they've provided it for free. So thank you very, very, very much to these guys. I cannot stress that enough.

I'm going to start with a very basic concept, hexadecimal notation. Now, I know many of you will be familiar with this and use it all the time, but there will be some of you that aren't familiar with it, and if you're not, then the rest of the series is not going to make very much sense.

So we'll look at hexadecimal notation and some of the bitwise and logic operations that we'll be applying to the emulation. I'm hoping that by virtue of watching this channel, you have the most basic familiarity with the concept of binary numbers. So if I take a decimal number, let's say 65, because it's an easy one to remember, its binary equivalent is 01000001.

These two numbers are equivalent; they just have different bases. So in this case, this is 5 times 10 to the 0 plus 6 times 10 to the 1. This is what we're all familiar with. It is decimal, it is base 10 notation starting with the rightmost digit being 0, 1, 2, 3, 4, etc., etc.

Base 2 notation is no different: 0, 1, 2, 3, 4, 5, 6, 7. But because we only have two available digits to represent our number, our base is 2. So in this case, I've got 1 times 2 to the 0, and I've got 1 times 2 to the 6. Everything else in between is 0. Anything to the power of 0 is equal to 1, which means I've got 1 times 1, which is 1. 2 to the power of 6 is 64, so I've got 1 times 64.

These two are added together and lo and behold, we've got 64. 1 equals 65. And when I refer to the zeroth bit, I mean the one that is furthest to the right. And typically for a byte of 8 bits, bit 7 is the one to the left.

Emulation is all about representing the hardware. And hardware consists of, well, hardwarey things like transistors and flip-flops and buses and wires. There is no fundamental hardware component that can understand a decimal number in the format of 65. So instead we use binary, a series of on and off switches.

Whilst binary is great for computers, it's terrible for humans because the numbers get large quite quickly. Here we've got eight bits, and so we can represent the range 0 to 255. That's not very large. So to store larger numbers, we need more bits.

When I want to convey to another human being this 16-bit number, ideally I'd prefer to do it in decimal, because if I was telling my friend that I wanted the number 0110-000110-01110, despite it sounding like a Flight of the Concord song, it's just not a feasible way to communicate that information.

But the problem with the decimal equivalent is it doesn't really give me any visual recognition of what the binary sequence is. My brain's just not wired that way. To see decimal numbers represented as binary, what we need is a format that lies halfway between being able to see which bits are active, but also as being a human being gives me an appreciable scale of the number's value.

And that is, of course, hexadecimal notation where we're going to group the bits into groups of four. Four binary bits give you a range between 0 and 15. And so for this first grouping here we can see we've got eight plus four plus two, so that's 14. Here we've got eight plus one, that'll be nine. Here we've just got a one, so that's still just a one. And here we've got a four and a two, so that's a six.

In this new notation, we can't have double-digit numbers, so we're going to use a different symbol. We've got 16 different symbols to store. I'm going to stick with the traditional 0 to 9 for the numeric digits. And then I'm going to have A, B, C, D, E, and F, where 0 represents 0 and F represents 15.

So my hexadecimal equivalent, just shorthanded to hex, is going to be 619E. And so after a little bit of practice, I can perceive that the binary construction of this number at some point contains this sequence. But I can also immediately tell that 619E is larger than 6190E, for example.

In the literature, you'll see different notations for numbering systems used interchangeably. But in C, to represent a binary number directly, we prefix it with 0b. Hexadecimal digits are prefixed with 0x. And in this case, we can see we've got no eights, a four, two, and a one. So that's just going to be a four, and this second grouping is just going to be a one.

But you may see in the literature other notations. So the dollar sign 41 means the same thing. Sometimes you'll see 41, and sometimes you'll just see x41. Now, if you're thinking, what is the point of all of this? Why not just use the decimal values?

Well, the answer is simply because hardware tries to be as optimal as it possibly can, and it does so in a way that doesn't always align with the friendly concepts we're used to in programming. So, for example, in C, we're used to a char being an 8-bit data type, 1 byte. But whereas you and I may look at that as being a numeric value, hardware may look at that a bit differently.

Within this one byte, there may be lots of different types of information. Perhaps these three values at the bottom are used to represent some value in the range 0 to 7. Perhaps these two bits in the middle represent some state of which there are only four possible states to exist.

Within these two next bits may only need to be simply bits. They're just control switches in the hardware. They're either on or off. And finally, this bit may simply not be required. The system only requires these seven bits of information. But on the computer, we can't store seven bits conveniently, so we use a char and simply ignore one of the bits.

Since I'm building an emulation of hardware, it's important that I know how to extract the relevant parts of the overall 8-bit number and interpret what they mean. Conversely, I might need to construct the bits required for the hardware out of information I'm storing as other data types.

To facilitate this form of data processing, we need to use bitwise operations. Let's say out of this data. All I'm interested in right now are these bottom three digits. How do I get to those? In principle, I can construct a binary mask of the digits I'm interested in. In this case, I'm not interested in these digits, but I am interested in the bottom three.

If I perform a logical AND between this word and this word, I get the following: 0 and 0 is 0, 1 and 0 is 0. Now this time I've got 0 and 1. That's still a 0, but I've got 1 and 1 twice. So we can see that performing the bitwise logical AND has given me a byte that I can work with in software that represents just these bottom three bits.

In a similar way, of course, I can extract any of the bits in my original word. So let's have a look at these two bits representing some state. This time my mask is applied to the two bits that I'm interested in looking at, performing a bitwise logical AND, and of course extracts those two bits for me.

But to begin with, we made the assumption that these two bits represent some value 0, 1, 2, or 3. Clearly, these values are not the same as this value. In effect, we need to shift this value three bits to the right, and we can do that with the bitwise shift operator.

So to shift right, it's two angle brackets facing right. Let's say my variable was X. I want to shift it right three bits. Well, that's the operation. Of course, I can also shift left, too. Shifting right effectively divides by two each bit, and shifting left multiplies by two each bit.

Now let's take a look at our switches. These are either on or off. I've created a mask that has the switch position. And again, if I perform a bitwise AND, well, in this case, it looks just like my mask. If I wanted to test if this switch was on, that is not zero, I can simply take whatever the decimal equivalent this is and test it to see if it's larger than zero.

And in C, in many cases, I don't even need to do that because an if will branch based on the condition of true or false, i.e., is it zero or not zero. So making a decision based on a single bit in a word could look something like this. Let's say X is my word and I want to see if a single bit of it is active.

I could simply test it with, say, the hexadecimal equivalent in this case, which would be 20. Don't forget 8, 4, 2, 0. Alternatively, I could test it with a constant we construct at compile time. I want to take a 1 and shift it 5 bits. 1, 2, 3, 4, 5. My point is, there are multiple ways to achieve the same goal, and you'll choose one based on what is the most readable in that given situation.

This notation, to me, immediately says I'm testing to see if bit five in a word has been set. So we've seen that we can use the bitwise AND operator to extract information from a word. We'll use the bitwise OR operator or vertical bar to set parts of the word.

So let's take a blank 8-bit word, and it is my intention to set these three bits with a value between 0 and 7. I'm going to say 5, which we know can be represented as binary as 1 0 1. Firstly, I'm going to want to shift these bits into the correct location. I want to shift everything by two to the left. This will give me 0 0 0101 0 0.

So the three bits are now in the right place. To set the bits in X, I'm going to use the bitwise OR operator. I'm going to use it on X itself. So X is equal to X OR this binary word. But hopefully, you can see with some simple arithmetic and bitwise operations, we can certainly set individual bits in groups in a word.

Setting an individual bit in a word is very simple. How do you unset a bit? How do you set a particular bit to be equal to zero? Let's say I want to clear this specific bit out of this 8-bit word. I can construct a mask easily enough using the bitwise OR operator and some bit shifting.

And if I use the invert operator, which is the tilde symbol, to invert all of those bits, I can now use the AND operator with the inverted word and my original word to clear that bit. Because now we've got 1 and 1, it's 1, so that's original 0 and 1 is 0. So that's the same as the original.

This is the bit we're clearing, which is now 1 and 0, which is 0 and the rest follows. The two words are identical except for the bit that we've changed. So we can clear a specific bit in the following way. And it goes without saying that we can clear groups of bits in the same way too.

There's one more common use case, and that is the ability to toggle a specific bit. So it might be a 1 and I want it to become a 0, or it might be a 0 and I want it to become a 1. I don't know ahead of time what situation will be. And we've looked at the operations of AND and inversion and OR; there is another one which is exclusive OR, which is the hat symbol.

So I've got a bit highlighted here. Let's say that's the bit I want to toggle. I can construct my mask again that identifies which specific bit in the word I want to toggle. And I can take the exclusive OR between these two words. So 1 and 0 exclusively ORed together is 1, 0 and 0 is 0. 1 and 1 is 0. 1 and 0 is 1, 1 and 0 is 1, 0 is 0. Etc., etc.

So we can see that I've got a new word which is the same as my previous word. But the one bit that I've identified has been toggled. And if you don't believe me, if I apply the exclusive OR bitwise between these two words, I get 0 1, which is a 1, a 0, I've got 1 OR 0 again, 1 1, 1, 01 1. So you can see that is the same as the very first word.

And so there you have a very brief summary of the six primary bitwise logic: AND, OR, NOT or inversion, XOR which can be used for toggling, shifting left which multiplies by 2, and shifting right which divides by 2. And even though I've shown these operations being applied to binary words, they can of course be applied to decimal numbers and hexadecimal numbers.

You just need to think about what you're doing if you know you're going to be working with bitwise operations a lot, and in emulation we will be. Sometimes it's a lot more convenient to create a structure that represents a binary word that's already broken apart into convenient properties. C provides a facility for this.

It's called bitfields. It is possible to tell the C compiler to construct a data structure where it only allocates specific bits to a particular word. So let's replicate the example we had before. The bottom three bits represented some value between 0 and 8, but I only wanted to allocate three bits of accuracy to that value.

Well, in a struct I can declare how many bits I want by using the colon and say three bits, please. The next one was a two-bit value to represent a state. Then I had a couple of values of individual bits to represent switches. So I'll call that SW1. We'll give that one bit, put some semicolons in and SW2, and that's also one bit. We also had one that was unused, so I'm going to include that too.

And what's very important here is that these numbers add up to something that can be represented in C. So in total, we've got eight bits. So let's say I had a variable called A that was the same type as this structure. I can represent the individual bits as simply a value and treat it like any other component of a C expression.

So I can assign numbers to it and I can read from it. And of course, I can represent the other properties of it too. So let's say I wanted to set switch 2 to 0. So by doing this, I don't need to do all of the bitwise operations.

We'll see throughout this series that in some situations this is an applicable solution, and in other situations handling the binary mechanically and by hand is the correct solution. But in this scenario, how would I go about setting all of the bits to zero simultaneously? That might be something that's required during a reset.

Well, the simplest option is to create a union between this struct and another variable that has a type of the same number of bits. And we know we've got eight bits. We know a char is eight bits. So I'm just going to create a variable in there, char 8-bit reg.

And so now if I have type A of this union, I can simply say a reg 0x00 which will set all of the bits to 0, and I can still access the individual bits as required. And it's up to you how far you go with this. You can create quite intricate nested structures and unions to best represent elaborate data structures.

But as I mentioned before, it's entirely situation dependent. What's the best approach? And I always try to go for readability. Compared to modern machines, the NES may seem quite simple, but don't be fooled; it's a rich and complicated machine in its own right.

In this series, I'm going to break the NES down into its fundamental components and talk about them individually in separate videos. In isolation, these videos aren't going to make very much sense. So I'm going to give a brief overview of how the NES is constructed and how the various components talk to each other.

I'm not going to go into any code in this video. This is purely a very high-level look at how the NES is constructed. Like most computer systems, the NES has a CPU, central processing unit, and its designation is 2A03. That's the type of chip that the CPU is made of.

It's actually a variant of what is called the 6502, which was really prolific in that era as the microcontroller of choice. In fact, it is just a 6502. The only thing that makes a difference is it has got inbuilt audio circuitry which the 6502 didn't have. So for my emulation, I'm going to just simply implement a 6502 processor because that gives me the opportunity to use it in other things in the future.

The CPU has no internal memory, and so it must be connected to some via a bus. Other things are connected to this bus. And even though I'm going to draw them as discrete objects, they might not necessarily be. They could be combined together. CPUs typically need some memory, and the NES has a whopping 2 kilobytes of memory.

Fundamentally, the CPU is an 8-bit device. Its internal registers are only capable of storing 8 bits of information. However, it can address a 16-bit range. So potentially, it could interface to 64 KB of memory on this bus. The memory is mapped to locations 0000 to 07F F, that's 2 kilobytes in hex.

So when the CPU puts an address on the bus that lies within that range, this particular object is going to respond. And so, even though the CPU can address 64 kilobytes of memory, most of those addresses will resolve to nothing. And we'll come back to that in a minute.

There could be other devices attached to the bus. Let's take, for example, the APU, which is the audio processing unit responsible for generating the sound. In reality, the APU is actually part of the CPU, but conceptually it's more convenient to think of it in terms of being a separate peripheral attached to the bus.

There aren't many addresses mapped to the APU, but it starts at 4000 and goes to 4017 at this stage. This is an incredibly high-level look at the architecture. There's a lot more detail in each of these units, and we'll look at them in individual videos.

There's really only a couple more things attached to the bus connected to the CPU. We'll come back to a major one in a minute, but let's just finish this one off. The big one attached here is the cartridge itself. So this is the device that contains the memories that typically have the programs that the CPU runs.

And the cartridge may typically be mapped from 4020 all the way to the end of the addressable range FFF. So what we're starting to see already is we can't just represent the giant address space as an array, depending on the address that the CPU is interested in reading or writing to.

We need to respond differently. There is an additional device connected to the bus, and it's arguably the most important one and certainly the most complicated one. And that is the Picture Processing Unit, otherwise known as PPU, sometimes called the Pixel Processing Unit by me accidentally.

And this only has a very small mapping. Given its importance and its relationship to the CPU, it suggests the range 2000 to 2007. And you may notice I'm already starting to do some hexadecimal slip-ups. Of course, this is a hexadecimal 2007, not a decimal 2007.

The PPU is a completely separate device. Its official designation is A2CO2. These devices are clocks. So every time a clock ticks, the CPU does something, or the PPU does something, and in particular, the PPU. Every clock tick outputs a pixel to the screen.

The CPU and PPU actually run at different clock speeds, and we'll go into that later. In many ways, you can think of the PPU as being one of the earliest graphics cards. The CPU is not really responsible at all for drawing anything to the screen. Instead, it structures the data within the PPU in such a way that the PPU can compose a picture that we see on the screen.

As such, we can think of the PPU as being a parallel processing unit, just to add to the ppuness confusion. And it's a sophisticated little device. It has a bus of its own. And whereas the CPU had a 64KB addressable range, the PPU has a 16KB addressable range.

But the bus works in exactly the same way as the CPU bus. It has different things connected to it. Early on, on the bus, it has some memory mapped to graphics. This is the actual pixel data, the sprites, or the tiles that make up the scenes in the games. There are 8KB of available graphics memory.

However, this memory doesn't necessarily exist on the NES. In fact, it exists on the cartridge. The next component is a small VRAM, a video memory, and in total, there are 2KB of this memory. Typically, this memory is used to store identifiers that represent which tiles to draw in the background.

This is stored between 2027FF. When we start talking about the PPU in detail later in the series, we'll see that there's actually some additional complication to how this unit is addressed. The final unit connected to the PPU bus controls the palettes, and these exist between 3F00 and the end of the addressable range.

Unlike the graphics we're familiar with today, where things are stored as bitmaps or JPEGs or PNGs, on the NES, it had its own proprietary format. And again, later in the series, we'll look at this in some detail. But effectively, all of the graphics on the NES were 2 bits per pixel, and those 2 bits were indexed into a palette to choose all of the available colors that we saw on the screen.

The PPU has one final additional thing connected to it which is not available via any bus. And it's a small memory called the object attribute memory. And this memory is used to store the locations of the sprites that we see on the screen. The CPU effectively only has eight letterboxes through which to deposit information and exchange information with the PPU.

That doesn't seem like very many, given the complexity of the tasks the PPU has to perform. Sprites and things moving around on the screen are clearly positioned by the CPU. The PPU just does drawing; it doesn't implement any physics or movement. So the CPU needs to populate this OAM memory every frame.

The PPU is clocked at three times the frequency of the CPU. So the CPU updating the object attribute memory manually would be far too slow. So the NES provides a dedicated peripheral just for doing this. And it's a primitive form of DMA direct memory access, which temporarily suspends the CPU and transfers memory directly into the object attribute memory.

This way, the CPU can prepare a region of its own address space with where the sprite should be. But it doesn't need to bog itself down with manually transferring that information through this limited space. Those of you watching with a keen eye may have noticed a bit of a limitation with this setup.

Firstly, 8KB of graphics doesn't sound like very much. When the NES was first released and the games were quite simple, it was an adequate amount. The graphics are low resolution after all, and they were simplistic. But over time it grew in its sophistication, and its audience became more demanding for more sophisticated graphics.

The designers at Nintendo were clearly very forward-thinking about this, and they knew that extending the capabilities of the NES would be vitally important to its success as a long-term device. So to that end, they included additional circuitry on the cartridges, and of course, that made them expensive.

This circuitry is called a mapper, and there are a whole host of different types of mappers. In total, I think there are around 700 licensed Nintendo games for this console. But fortunately for my needs, most of those games use just three or four different configurations of mapping circuits.

So as part of our emulation, we're also going to have to emulate this device. And this device was responsible for what is known as bank switching. And this is where the CPU can configure the mapper on the cartridge to give different data for the same address ranges.

So in one instance, let's say bank A contains the graphics for level one. When you reach level two, the cartridge is programmed so that all of the graphics map onto bank B, so you get different graphics for that level. And the nice thing is nothing has to be loaded or transacted along the bus.

We're simply changing the mapping of the addresses to different memory locations on the cartridge so the PPU and CPU addressing those locations on the buses see no difference; they don't know anything has changed. But the mapper delivers different data.

And it's through this that we can have significantly larger memories devoted to graphics and programs. The program data is obviously stored on the cartridge too, as is all of the information for all of the different level layouts and configurations.

It may well be that all of the memory required for a specific game far exceeds the 64KB allowance that the addressable range of the CPU allows. And so again, the CPU can configure this mapping circuit to change what memory banks are connected to the bus.

And as we've seen with the PPU, that can change the graphics, with the CPU, that can change the running program. As I've said several times already, it's my intention to make several videos in this series. The first is going to be dedicated to the CPU and its bus.

This will be a nice self-contained module that we can use in other things. In Part two, we'll look at half of the PPU, and we'll be looking at how it handles the background rendering on the screen. In Part three, we'll look at the second half of the PPU, how we handle the sprites.

In Part four, we'll look at the mapping circuits, and hopefully finally in Part five, we'll look at how it generates audio. I don't currently plan on there being a Part six. It would be a very long series if there was.

However, this is the type of project that, because for me, it's so personal and I'm so pleased to be doing it, it's never really going to be finished until I'm absolutely happy with it. However, as you've seen already, what I've currently got is a working NES emulation, and I hope to deliver enough content in this series that you can get one working on your own.

But you will need some of the information available on NES dev. It is not my intention in this series to go through byte by byte how to create a Nintendo emulator. Instead, what I'm trying to provide is some insight into how do you go about emulating systems in general.

And I'm going to try and deliver a fairly deep understanding into the inner workings of the NES in particular, because it's a fascinating machine, and I love it to bits. I'm personally really pleased to have started this series. It's something I've wanted to do for so long, and now that it has been started, I can at least start showing things and uploading clips to the sister channel Javid X9 Extra.

There won't be any source code immediately, simply because if I deliver it in chunks, people might start wanting to try using it straight away, and I'll try and make sure that the source code is relevant to the video at the time. I appreciate this video in particular has been a bit light on technical content and code, but it was a necessary step before embarking on a journey like this.

So if you've enjoyed it, please give me a big thumbs up. Have a think about subscribing, and I very much hope I'll see you next time. Take care.