Placeholder Image

字幕列表 影片播放

  • Hello and welcome to Part seven of my Nails emulator Siri's.

  • If you've made it this far, a big thanks to you for sticking with it for so long.

  • This video is about the mappers, specifically, the map is you're going to need to use to implement the bulk of the Nintendo catalogue of games.

  • But whilst I was planning this video, something dawned on me.

  • It's very difficult to make a video about matters, and consequently, this video is presented in a different style to how I would usually make videos.

  • Normally, I like to build up the project line by line and show you it working at various stages and provide small tests and explanations of what's going on.

  • The problem is, matters don't work that way.

  • The work or they don't and they need to exist in their entirety before you even know if they work or not.

  • And this makes them especially difficult to deep book.

  • And so instead, what I'm going to do is present.

  • A review of the map is that I've made already for this project.

  • They're the main mapas you need for most of the Nintendo games on.

  • I'll walk you through how to understand the macro specifications on how to translate that into code.

  • I also expect that given that this is Part seven, and if you have made it this far, you're probably already quite familiar with the nose architecture by now.

  • And so perhaps I don't need to go into quite a CZ much detail as I have done a staff of the Siris.

  • So let's get started with a quick refresher on what it is the map actually does.

  • Fundamentally.

  • It's a device that translates addresses from a CPU or the peopIe you into memory stored on the cartridge itself, such as the program Wrong memory onto the character.

  • Wrong memory.

  • As we'll see later in this video.

  • Some members have additional capabilities, but for now, let's assume they're just doing this simple address.

  • Translation.

  • Recall that certain locations in the CPU address space in the people you address space are expected to serve specific purposes.

  • The CPU can address 64 kilobytes, but there certainly isn't 64 kilobytes of memory on the Nintendo console.

  • On when the CPU rights to addresses that doesn't have a physical hardware implementation, it gets mirrored back to some lower number address.

  • We've covered all of this in great detail.

  • We also looked at where the programs start in the CPU address.

  • Space on it's about halfway.

  • This means there's about 32 kilobytes of addressable program memory at any given time.

  • So let's just look at a few example configurations.

  • Let's assume that's our entire program.

  • Rum on the Cartridge is, in fact, only eight kilobytes.

  • It's a small and simple game.

  • One way to approach this is to map this entire eight kilobyte memory into the 1st 8 kilobytes of our CPU address space after the halfway point.

  • And any reads two addresses beyond the eight kilobytes are simply mirrored in the rest of that space.

  • Hopefully, it's obvious that if we had a 16 kilobyte program run, we could map that twice into the sea pews, addressable space.

  • And if it was 32 kilobytes, well, then it's a perfect fit.

  • The mapping is a want or what, but let's take things to extremes for a moment.

  • Let's assume our program wrong is 100 and 28 kilobytes.

  • Clearly, this can't fit into the 32 kilobyte addressable space.

  • Instead, we must break this 128 kilobytes into 4 32 kilobyte regions on the map.

  • ER will perform that translation for us, depending on which bank we have selected.

  • So thes dotted green lines broadly can be considered bank dividers.

  • At some point, the map A is instructed which bank of program wrong is required to map into the location of the CPUs address space.

  • And in this video, we'll look at the implementation details for some of the more common mappers as they all approach this in slightly different ways.

  • This simplification that I've illustrated is a little unrealistic.

  • It surfaced from.

  • One big problem on that is swapping the entire address space in one go is, well, a bad idea.

  • And this is because as the program is running, there's a great deal of risk in just swapping the memory out as the program counter will start in the exact same location in the new bank of memory.

  • Now, unless you carefully coordinated that to be legitimate, that's really just going to make your life is a developer quite complicated?

  • So instead, there is a different solution.

  • It's convenient to think of program wrong in 16 Killer by chunks and equally think of the program Memories location in the CPU address space also in 16 Killer by chunks.

  • The map may have been configured that all of the addresses in this range are mapped into a specific bank in the program memory, but also that the addresses in this range are mapped to some other bank.

  • This means that when the program counter is operating in this range, it can configure the mapper to change the bank referenced in this range without corrupting the program.

  • And, of course, if the program counter is in this range, it can configure the bank mapped into this range.

  • This keeps things a lot more organized for the programmer.

  • The numbers I've chosen here are somewhat arbitrary on will See that the different mappers handle things in different ways.

  • The address space of the people You is mapped in a pretty much identical way.

  • The dress spaces, of course, smaller.

  • It only goes 23 f f f, the first half of which is used to store the sprite patterns.

  • Ultimately, the tiles that make up the graphics we see on the screen on the's tiles are called characters, and thus this region gets mapped onto the character wrong.

  • The character Ron can be partitioned in many ways, just as the program wrong, Waas on different regions of the pattern, tables can be mapped onto different banks of the character wrong.

  • Typically, these regions are considerably smaller because, as we've seen in previous videos, if you wanted to animate a particular Tarts set, you only want to swap in maybe a couple of rows of the pattern table, and it's possible there are several.

  • Mapping is in place at any one time.

  • The mapping for the people you is a lot more flexible than the CPU because we don't have a program counter to worry about in the purest sense.

  • It really is just mapping one memory address to a memory address somewhere on the cartridge.

  • So that's what the map it does.

  • But how does it do it as discussed?

  • Most of the addresses on the CPU boss have a purpose, but one thing we've not seen is any particular register mapped here that could potentially talk to a map.

  • Er, for example, to talk to the people you we wrote to specific addresses in the CPU address space on that cost, the people you to change its state.

  • That isn't an equivalent for configuring the map.

  • It instead, the map is take advantage of the fact that the program memory is a Rome.

  • By definition, it's read only weaken right to these locations, but nothing happens.

  • It's read only memory.

  • It doesn't change the state of our program.

  • The CPU bus is unaware what read only memory means so it can, quite happily right to a location here.

  • The map will receive this address on the data, but the data simply has nowhere to go.

  • It can't be written to the read only memory.

  • Therefore, if the map aerate detection address, which is in this addressable range where the program is expected to be read from it, can interpret that address as a command to the map for itself.

  • And it's through this mechanism that we can configure the mapper to set up the map ings between the program rahm on the cartridge and the CPU address space on the character room on the cartridge on the people you address space.

  • And even though these are two distinct systems, there is only one mapper controlling them both.

  • Now, as I mentioned at the start of the video, I'm not going to be coding up.

  • The mappers line by line instead was sort of going to review how I've implemented a certain number of mappers on since one of the earliest episodes of the series.

  • We've had a map, a base class which handles, read and write to the CP bus reading right to the people you bust on overtime.

  • It's evolved to have a few more functions, and we'll look at these as we develop the rest of this video in the cartridge CPP file, where we load the wrong image itself.

  • We determine the map.

  • Er i D.

  • According to the I Nez head of former, one of those header entries is how many banks of Program Rahm are there?

  • And it assumes that a bank of program rum is 16 kilobytes.

  • That doesn't necessarily mean the map.

  • Oh will interpret it a 16 kilobytes.

  • That's just for us to load it into, in this case, this vector the program memory on in exactly the same way.

  • We also created a vector for the character memory, and it assumes that the eight killer bite chunks.

  • In that instance, some of the mappers rely on knowing how many program banks and character banks there are.

  • So this information is passed along into the constructor of the map.

  • On will choose the appropriate derived mapper based on the map.

  • Er i d.

  • And for my emulator, I've chosen thes mappers because thes will cover the book off the games.

  • And there's one sort of curiosity mapper, although I'm not going to do them in this order and said, I'm going to do them in order of complexity.

  • The map we've been working with throughout this series so far is map a zero.

  • And as we have throughout this series, it's time to defer to the great.

  • And there's Dead Wicky.

  • So again, a big thanks to the guys that have put this together under the topic mapper.

  • We really see what the community has come together to form a standard for all of the map is, and you can see there's a whole bunch of them.

  • In fact, there's another table worth down here.

  • The book of the Nintendo Games is the most popular in the West, are covered by, say, the first half of this table, and in fact, really the bulk of the games is covered by the 1st 5 or six mappers.

  • I called mine mapper zero because it is the zero for entry in this table.

  • So let's have a look at that.

  • The nice thing about map a zero and the reason we've chosen to do it first is it doesn't really do anything at all.

  • It is not a complicated mapper, but when you're looking at a map her for the first time, it's often worth seeing which games are compatible with this mapper.

  • And you can do that by looking at this ness.

  • Can't d be another fantastic resource created by the community.

  • So here have listed all of the map a zero compatible games, and I should expected that the earlier generation games.

  • But they include titles.

  • We've been working with such a donkey cart me back to my click on Donkey Kong.

  • We can see here I nez map a zero, and it gives us information about the rooms inside here.

  • We've got the program run onto the character Ron, so by implementing mapper zero, we can see we're going to cover about 248 of the release titles now.

  • This also includes all of the slightly more obscure Japanese titles that weren't available to the rest of us.

  • But most importantly, for those at the developing emulators, your usual first goal is to get Mario Brothers up and running on Super Mario.

  • Brothers indeed, uses map a zero.

  • Now I'm going to spend a little bit of time on this first mapper, just looking at how to digest the information provided by the Wiki.

  • The first thing you'll notice is there's lots of peripheral cases.

  • Even though this is map A zero, it starts bombarding us with lots of different types of board information on the's.

  • Airil ever so slight variance off map a zero because I'm trying to keep this Siri's as simple and as accessible as I possibly can.

  • I'm not going to look at all of the specific details of every possible map of variation.

  • Unfortunately, you don't have to to get the bulk of your games working.

  • All of the map is have this little table at the top, and the important entries here are the program Rome capacity and window and character.

  • Wrong capacity Window on.

  • What this is telling us is that this particular board came with either a 16 kilobyte rahm or 32 kilobyte Romm for program memory, it says for the program.

  • Rahm Window, don't that's not applicable and this is map a zero.

  • It is the simplest mapper.

  • This means there is no mapping.

  • At the start of the slides, I showed a 16 K or 32 Kay Graham being put into the program memory, and that is exactly what we've got here.

  • It then suggests that this eight kilobytes off character Rome and again there is no applicable windowing.

  • Windowing is the technical term for mapping.

  • There are a few other tidbits of information on here, too.

  • Recall that are named tables are mirrored in a particular configuration, and on mapper zero games, this is fixed in hard work.

  • Physically, the pins are soldered in a configuration that decides whether it's a horizontal or vertical mapping.

  • There's other bits of information on here, too.

  • On we'll look at those as the map is getting more sophisticated.

  • And again, mapa zero is the simplest mapper, and the weak even highlights this forest.

  • How do we interact with it?

  • Well, we can't This has normally no mapping capability whatsoever, so let's have a look at our map.

  • A zero.

  • Well, it's a derived class that inherits from the mapper base class, and it must provide implementations for the reading right to the appropriate buses.

  • Don't forget there's no way to configure this mapper.

  • It really is very simple on its behavior could be inferred by how many program wrong banks were supplied by the cartridge here when the CPU attempts to read from the address boss in the program Memory range.

  • If the cottage only provided a 16 kilobyte rum, then we mirror the address, effectively splitting the 32 kilobyte range into two identical 16 kilobyte program memories.

  • If, however, we had two banks of program memory, remember, each one is 16 kilobytes.

  • So a total of 32 kilobytes of program memory Well, then that maps directly into the CPU addressable range, where it expects the programs to reside For the peopIe You reading right?

  • Well, there's nothing to do it all.

  • We just directly past the address through these functions, take in an input address on the respective boss of the device that's calling them, and they return a map address.

  • That's the corresponding address in the entirety of the Rome supplied on the cartridge.

  • So when Cp Reed is called the mapper turns that address on the CPU boss into a physical address into the room on the cartridge on this physical address is then used to locate the required data in the program Memory vector.

  • We created off that Rahm will come back to this bid later.

  • The same applies for the right.

  • The same applies for the peopie you read and write so thes functions in our mappers simply translate the local space address bus for the CPU and the people you into the address space of the larger rahm date of actors.

  • I decided to make my map implementation also responsible for other bits of information that the cartridge would usually supply, such as the current mirroring mode.

  • The PEOPIE, you in particular cares about the mirroring mode on needs to interrogate the cartridge to see what it is.

  • And so I've provided this mirror function which interrogates the mapper to determine the mirror mode.

  • But the map er might not always know what the mirror mode is, particularly in some simple mapper like map a zero, the mirroring modus specified in hard work.

  • If this is the case.

  • We simply return the mirroring mode that was defined in the i news file.

  • Former Header.

  • If the mirroring mode is not fixed, it is indeed software programmable.

  • Perhaps, as is the case in some of the more advanced mappers, then will defer to the map to return what the mirroring mode currently is.

  • I also included a reset function for the cartridge, which resets the mapper.

  • This doesn't set the contents of any of the rooms, but it does reset the mapper to unknown state.

  • And as we'll see, not all of the map is just get everything set to zero.

  • Now you might think that the next mapper to look at is Mapa one, but we're going to jump over that one for a minute, straight into a map to a map.

  • ITU is great because it's a very simple mapper to implement, but it is responsible for the mapping of some really cool games.

  • And again, according to the Wiki, there's 155 games in total that use this map.

  • Er, let's just interrogate the table to get some information.

  • Firstly, we can see that it can have quite a large program wrong Now.

  • Program rooms aren't just the execute herbal code that can also be all of the level data on sometimes graphical positioning data that make up the game.

  • Therefore, as the game becomes more detailed and rich and complex, we would expect it to require more program wrong.

  • We know that in the CPU addressable space, we have a total of 32 kilobytes of addressable program memory at any one time on this suggests we have 16 kilobytes, which is flexible, that can be mapped into any location within the Rome, but also 16 kilobytes.

  • That's fixed, so this will always be mapped to a specific location in the rum.

  • This is quite a useful utility tohave, because you could leave all of your system critical functions and start up code in this fixed region that never changes.

  • It's never mapped out on, for example, load in specific level data one bank a time, perhaps representing each level.

  • That's probably not how it's done, but conceptually it could be as and when it's needed.

  • Like with mapa zero, there is no facility for mapping the character wrong.

  • The eight kilobytes supplied in the room is directly mapped into the pattern tables of the peopIe you address boss map.

  • ITU allows the program memory configuration to be set up in the following way.

  • The program room can be very large.

  • It could be a few megabytes, even all split up into 16 kilobyte backs.

  • Regardless of how large this room is, The very last 16 kilobyte bank is always mapped to the top 16 kilobytes of the 32 kilobyte program address space.

  • This is always the case.

  • The lowest 16 kilobytes of the program.

  • Memory can be matched to any other bank in the program wrong, and so the map needs to be instructed.

  • Which bank is mapped into that space?

  • This introduces the concept of the map.

  • Er, having registers on, will see that most sophisticated mappers have more registers.

  • But this very simple mapper just has the one on weaken right to this register by writing to any were in the 32 kilobyte program memory address, space of the CPU address, Boss on defectively.

  • It's telling us that whatever the eight bit data value is that we write to anywhere within that range, that value corresponds to the particular bank number that were interested in.

  • And so here's my implementation of MAPA to again.

  • It just implements the interface of the base class, but it has to.

  • Internal registers on the's are going to be the offsets in banks to the program.

  • Rahm on the cartridge will start by looking at the CP right function because this could be used to to program the mapper.

  • As you can see, we're sensitive to any address in the upper 32 kilobytes on.

  • All we're going to do is take the bottom four bits of the data and assign that to the bank.

  • Select low variable.

  • In this case, Bank Select Low refers to whichever 16 kilobyte bank is in this first half of the address.

  • Space on bank Select high refers to whatever is in the upper half of this address space that we know that that's always going to be fixed to the last bank in the room.

  • The peopIe you read and write functions don't do anything.

  • This map a cannot handle the character wrong when we call the reset function, which we always do when we load a cartridge for the first time.

  • By default, the lower bank is set to bank zero, and here you can see where we've passed in.

  • The number of banks that the cartridge contains were setting the bank select high variable to the last bank of that range.

  • This means when CPU Reed is called, we can determine which half of the address space we're looking at, whether it's low or high, and then compute the relevant address in the Rome.

  • Given our bank number, we can multiply it by 16 kilobytes and add into that the offset of the address that we actually require.

  • This returned a single number, which is a direct index into the large program Wrong vector that was loaded when the cartridge object was created.

  • I do exactly the same for the upper 32 kilobytes, but this is always fixed to wherever this bank is located.

  • Mapper three is in many ways the same as mapper, too, but also the direct opposite.

  • So it's a complete paradox where his mapper, too, had a window for the program.

  • Rome apathy has a window for the character role, so this would be for very graphically intensive games, but they could be quite programmatically simple games.

  • We can see that either there is a 16 K or 32 k wrong capability.

  • So this is going to behave exactly the same way as map a zero.

  • But this potential tohave 32 kilobytes of character role in this instance, the entire pattern table is mapped at any one time.

  • We'll see in the next two mappers that you can selectively choose regions of the pattern table to be mapped so you can animate things on dhe mix and match graphics when you're compositing the scene.

  • I'm not going to go into any further detail on this map because it behaves in exactly the same way.

  • Except it's just for character Rahm rather than program rum.

  • And here is my map of three implementation.

  • We can see that this behaves exactly the same way as MAPA zero.

  • Though it's coded a little differently.

  • CP right is sensitive to anything within the program memory space on writing to that, the data value is used to determine which bank of character Ron we're going to use unless, well, in the peopIe you read function, we use that bank multiplied by eight kilobyte offset and add the address accordingly to give us the final map to dress into our vector of character.

  • Rome.

  • Nice and simple, rather oddly.

  • I'm going to jump to mappers 66 Now.

  • Mapper 66 is a very simple mapper, too, and I was interested in it because it's the Super Mario Brothers and Duck Hunt combination cartridge, and this is a simple brute force mapper.

  • We can see the wrong can have up to 128 kilobytes on.

  • We can only select 32 kilobytes of that at the time.

  • In effect, we load the entire program in one go from one of four locations.

  • The same philosophy is applied to the character wrong.

  • We can have potentially 32 kilobytes of it, but at any one time, we're going to map eight kilobytes of character.

  • Data on this format makes sense.

  • When you have multiple games on one cartridge, for example, let's assume all of duck hunt is contained within 32 kilobytes on all of Super Mario Brothers is contained within another 32 kilobytes.

  • On correspondingly, they both have all of the graphic stored within these eight kilobyte blocks.

  • Then you can easily change essentially the entire game between them.

  • You might use another block to give you a loading menu or startup screen interacting with this map.

  • ER is essentially a fusion of the two previous mappers we've looked at.

  • We're sensitive to the entire program address range.

  • On we look at the bottom two bits of the nibbles of the data that we provide to tell us which character Ron on which program Ron Bank we need for the entire range.

  • And this is my map of 66 implementation.

  • So I have two variables which tell us which bank specifically of character Roman program wrong.

  • We want when we write to the address books in the right location.

  • We can extract those numbers directly as being the bottom two bits of the data word, and I'll use those values in peopIe you read on CP.

  • You read accordingly.

  • Specifically for map of 66 you have to work in 32 killer bite chunks for program memory on eight kilobyte chunks for character memory.

  • It's quite a brute force map of this one, but useful if you're bundling multiple games into one cartridge.

  • Well, now we've got the easier map is out of the way.

  • They are simple translations from one number to another number.

  • But there are two members which are slightly different from all of the others.

  • And it's just unfortunate that these two matters make up the bulk of the catalogue of the games.

  • This is M M.

  • C one, an MMC three mapper one, formerly known as M M.

  • C.

  • One is, on the surface quite a complex map just by looking at the amount of data on the Wikipedia article could put you off, but internally, it's actually quite a simple mapper.

  • It just has a rather obscure into face, which I think makes it unique amongst the mappers.

  • However, it's important to get M.

  • M C one working correctly because it covers over half of the release Nintendo Games.

  • Examining the structure of the map.

  • Er, we can see that it's effectively split into two.

  • The program run banks can be, as we saw before, split into 2 16 kilobyte chunks where one resides in a fixed location in the room, on the other is flexible, or it could be an entire 32 kilobyte chung, and likewise the same applies to the character Ron.

  • We can have an entire eight kilobyte rum bank selected at one time, or we can split it into two meaning.

  • Each pattern table, left and right, can be mapped to an individual four kilobyte bank.

  • We also have some additional features.

  • With this mapper, we have the ability to have program round.

  • So in effect, we can enhance the amount of RAM that the Nintendo has in order to perform its calculations or store data on.

  • If there was some battery back up on the cartridge, this ram could be used to save games.

  • Also, this is the first map we've encountered where the mirroring is flexible, a necessary feature for games that can scroll in multiple directions depending on the context of the game.

  • What makes this map a rather unusual is you communicate with its serially, I just one bit at a time.

  • I imagine this was to reduce the complexity of the chip and therefore reduce the cost back in the days when producing these sort of materials was very expensive at the top level, Macro one consists of two registers on, as with the other mappers.

  • When the CP rights to an address above 8000 the map.

  • Who's interested in it?

  • If it's any point, the data that's being written by the CPU has the eighth bit set, then the map er is reset.

  • However, in all of the circumstances, the map A is only interested in the bottom bit off the data word being written to the address boss.

  • It takes five rights to anywhere within this range on serially shifting in just the bottom bit of the data to populate the load register.

  • And so fridge right, we shift in the bit on, move it along to give us a more conventional digital word.

  • But if it's any point, the data word has its most significant bit set.

  • Then the map.

  • Forget to reset to on my apologies, a quick little Hirata.

  • I'm not going to call out the control register.

  • I'm going to call that the target register.

  • Once the load register has been populated with five bits, something is goingto happen.

  • But what That something is depends on which address was used by the CPU when that fifth bit was written by only looking at the CPU address bits 13 and 14.

  • We can break this instruction region into four subregions, and so when the fifth bit has been loaded serially on DDE in combination with the address zone that's being used for the rights.

  • We can target one of four functional registers within the mapper on.

  • We can set the contents of that register to whatever the five bit data has been loaded in.

  • In this first region, we have the control register, which is used to set various status bits on the map er on, also the mirroring mode.

  • The next two zones are used to set information for the mapper to map the character wrong on the Final zone is used to configure the map er for the program.

  • Wrong.

  • Thes three registers will go and perform the function when they're written, too, and they each use information from the control register, hand information from the data that's been sequentially loaded in.

  • There isn't a fixed pattern to this relationship, but it's not complicated.

  • The real complexity of this map, er, is this serial loading.

  • Once we've got the relevant data into the map, er, performing the map ings is quite trivial.

  • Here is my map a one implementation.

  • So for the head of file, you'll notice I've now overridden the mirror function.

  • That's because the map a can determine what the mirroring mode is, but there's also more structural information inside the mapper.

  • Firstly, we have the load register.

  • This is the register that is receiving the bit sequentially on.

  • We need to keep a count of how many bits have been loaded in because when it gets to five, we want to do something on.

  • One of those things we can do is write to the control register.

  • The control register contains all sorts of bits that configure how the map of works.

  • We've also got the potential tohave ram now on this cartridge.

  • So I'm adding an additional vector to store this static ram.

  • Potentially, if we were to implement save states, you'd want to dump this RAM to a file because it effectively could contain the game saves.

  • The only other variables left are similar to what we've had in previous mappers.

  • But don't forget, we now have two distinct modes for the character and program memories.

  • Either we're working in big, chunky bank switches for character banks that was eight kilobytes, and for programs that was 32 kilobytes of swapping in the whole thing in one go or we're working with these smaller resolution bank switches of four kilobytes each for the character and 16 kilobytes each for the program on for the program memory.

  • One of these was always a fixed location, so I'll start by looking at the reset function.

  • It does reset the map er to a known state, and the control register has given a specific value.

  • And we'll look at what those bits mean as we go through the implementation.

  • All of the bank's selection pointers are effectively set to zero, except for this program, Bank 16 kilobyte high as before in a previous mapper.

  • This is set to the last available bank provided by the cartridge.

  • Starting with CP.

  • You read.

  • This is the first time we've seen the map.

  • Er, be responsible for data That's not above the midway point of the addressable range of the CPU bus, and it simply takes the address offset into that range on maps it directly into the static Graham provided on the cartridge.

  • Now I've hacked in something here to make sure that I don't then go on to effect data elsewhere in the system.

  • If I return a map to dress specifically of all ones in the cartridge class that has called this function on our map.

  • Er it is sensitive to that one particular condition.

  • This makes the rather naive assumption that we're never going to have a nez rahm with, well, four billion bytes of information.

  • Effectively, what this is doing is saying, Well, the cartridges actually provided access to that memory.

  • So don't go on to read it from else work, because the 14,000 range is typically valid.

  • It's considered Ram, but I want that ram to exist on the cartridge and not else work.

  • This is probably slightly incorrect on may be overkill, but it's working for May, So handling the static ram is quite trivial, and there is an analogous form for the CPU, right to sticking with.

  • CP.

  • Read for moment will look at how we configure the map in a short while, but what we can see is the implementation is quite trivial.

  • We're looking across the whole addressable range for the program wrong now, depending on a particular bit of the control register, were either operating in this pair of 16 kilobyte banks or a single 32 kilobyte bank.

  • In the case of the latter, we just take our bank offset pointer multiplied by 32 kilobytes and the offset provided by the right command in the first place to map it into our program Vector.

  • That's very simple.

  • And, as we saw with a previous mapper, splitting it into 2 16 killer bite chunks.

  • Well, each one of those chunks depends on the offset value stored in its corresponding variable.

  • Here.

  • PeopIe, you read acts in a very similar way.

  • There is no ram toe worry about this time, but again, a bit in the control register is used to determine whether we're operating in this large chunk mode or a smaller chunk mode on the offsets.

  • A calculated accordingly, as you'll find for many of the mapas, peopIe right doesn't do very much.

  • So the big and complicated and interesting function to look at his CP right, because this is the function will use to program the mapper.

  • We've already talked about the Ram access, but now we want to be sensitive to anything within the program memory range on at all times.

  • If the eighth bit of the data that is written toe doesn't matter what location, as long as it's within that range, we reset our mappers internals.

  • This includes our bit counter.

  • However, if that bit is not set, then we're sequentially and serially loading data into our load register.

  • So it'll take five rights to fully populate this register.

  • And each time we shifted along one on bring in the least significant bit of the data.

  • Word went to increase our bit counter.

  • So I know that when I get to five bits, it's time to do something and change the state of the mapper.

  • Recall that the address space was split into four distinct zones corresponding to some internal register on the map.

  • The first zone is used to directly set the control register, so we take the five bits of our load register on directly, implant them into the control register.

  • At this time, we can also choose the mirroring mode, which is software programmable.

  • By analyzing the bottom two bits of the control register setting the control register has now also set which operating mode our bank selection is using.

  • Is it using the big chunky banks, or is it using the smaller banks?

  • The next zone in inverted commas was used for setting the low bank of the character Rome.

  • And as usual, it depends on whether we're operating in four K mode or eight K mode.

  • The next zone was responsible for setting the high bank of the character.

  • Ron.

  • If you're setting this specifically, you must be operating in four K mode rather than a K mode.

  • The final Zone configures the program.

  • Rome mapping on the specifics of this mode could be determined by looking at two specific bits in the control register in program mod 01 We're using the Big 32 killer by Bank Select.

  • And so we take the data from the load register which will contain the bank number that we're interested in on applied directly to our 32 kilobyte offset.

  • This is where we slightly deviate from previous mappers in terms of whether fixed bank resides in programme mode to we fixed the lower 16 kilobytes of our addressable program space to the first bank provided by the cartridge on our upper 16 kilobytes of program.

  • Memory is thief flexible, one that can be moved around so we choose which specific bank that is from our load register programme mode three behaves in a very similar way, but also opposite that.

  • Our lower 16 kilobytes is flexible.

  • Andare Upper 16 kilobytes is fixed to the very last bank provided by the cartridge.

  • This is the one we've seen today in a previous mapper.

  • Once we've finished with all five bits, we reset our load register and the load register counting variable, ready for the next command, and that's it for MAPA one.

  • Once you get past this sequential loading idea, the actual implementation of the mapping is nothing more complicated than what we've seen on the simple mappers so far.

  • Okay, time to talk about the last map.

  • Wrong going to mention in this video and that is map before MM see three is its formal name on.

  • This is a very important matter to make because it's responsible for pretty much the other half of the games in the catalog of games provided for the Nintendo console.

  • In terms of complex state this map there is no more complex than mapa one.

  • Really.

  • It has a little bit more sophistication in how it can do its mapping, and it's got some additional features.

  • If we look at its capabilities, what we can see here is that the program wrong mapping can be split into two flexible eight kilobyte chunks on 1 16 kilobyte fixed now in my implementation, I found it convenience to think of It is just four eight kilobyte chunks mapped across the 32 kilobyte addressable range.

  • The character Rahm is mapped in a similar way.

  • Here it is listed as to two killer by chunks on 41 killer by chunks.

  • However, I've decided to implement.

  • This is 81 kilobyte chunks as well.

  • Mm.

  • See one.

  • The name table mirroring is software programmable, but we've also got this new addition interrupt requests and we'll look at that a little later.

  • In terms of verbosity, the Nest Dev Wiki article is an excellent resource here on like all of the previous mappers, we write to the mapper by writing to any address 8000 or above on depending on the address and the data, we sent specific internal registers on the mapping chip for this video.

  • I'm really only going to detail the program wrong mapping because the character on mapping at this point is quite analogous.

  • So if splits up my program memory into 48 killer by chunks on The first thing to note is that the Final Eight killer by chunk is always mapped to the last eight kilobyte bank on the program Wrong.

  • Fortunately, this time around, we don't need to concern ourselves with serialized behavior.

  • We can write to specific addresses and, it turns out, just as before, there are four distinct zones.

  • We set specific registers, but each one of those owns is split into an odd and even on.

  • The numbers are mirrored throughout the zone.

  • The four functional zones can be split in the following way.

  • This first region handles the mapping.

  • This second region handles mirroring on these bottom two regions.

  • Handle information regarding I are cues, and we'll look at that after we've looked at the mapping in this mapping zone.

  • When even addresses are used, we're configuring the map with how to configure the map.

  • Er, sounds a bit strange on when we use odd addresses were actually providing the data for the actual mapping itself.

  • So the address is in the program Rum.

  • So what do I really mean when we send thes even addresses?

  • Well, when we write a data bite to, let's say, address 8000 we're configuring the state of the map, er, but we're also preparing it for future data to come so specifically, this bit is going to tell us something about how we're going to handle character mapping and this bit specifically how we're goingto handle program mapping.

  • Thes bottom three bits are used as an index into an eight element array, which I'm just going to call register on.

  • So subsequently, when we write with an odd address, the full eight bit data is written into that specific register.

  • Given the index we've just received from a previous right to an even address.

  • These eight registers identify which bank in various rooms do we want to use.

  • Now.

  • M.

  • M C three is from an implementation perspective.

  • Quite simple, but from a conceptual.

  • And how do you use it?

  • Perspective Quite complicated because the map ings tend to jump around a little bit.

  • This is always the case, So really, we're looking at How do we map these other 38 kilobyte chunks?

  • Well, if this bit is set than this junk is directly mapped to the second to last bank in our program, wrong on this chunk is directly mapped toe whichever bank is identified in our array of registers element number six.

  • Regardless of whether this bit appearance set, this junk is mapped onto whichever bank is pointed by registered number seven.

  • If the bitch is not set, then these two effectively swap around.

  • This implements the fixed 16 kilobyte mapping.

  • Because this 16 kilobyte range here is always fixed to the last two banks which is also 16 kilobytes in the program run on.

  • These two remain fully flexible in a very similar tangle of spaghetti.

  • The character mapping happens the same way here.

  • I've got the peopie you address space broken up into one killer by junks.

  • If that bit in the data word is set, then this junk uses the address of the bank in register to register three.

  • And if it's not set, we see that the registers are used in different places.

  • So this one is now set by registered to on so forth.

  • Recall that in the mapping, two of these blocks are specified as to kilobytes on four of them are specified as one kilobyte.

  • Well, here we've specified the one kilobyte blocks, but we still use the same registers to get that information just so happens that these two will use registered zero on these to register one, and the same applies on this site.

  • But this, of course, implies that were specifying two different locations in the people.

  • You address space to the same bank in the room, which doesn't see my deal.

  • Well, the programmer will have anticipated this and knows that for here they need to specify to kilobyte banks.

  • And so we can implement this mapping by taking the value in registers zero here and simply adding one to it before multiplying it by one kilobyte toe workout where the offset is in the character Rahm.

  • This would, of course, assumed that the program and knows that the boundaries between these banks in the rum need to be multiples of two kilobytes.

  • But if they specified an odd bank number, this would lead to us having non boundary aligned banks being accessed so we can protect ourselves against that by simply ignoring the least significant bit.

  • I don't know if that ever happens in reality, but it's a nice little safety check to put in.

  • So what we see from this map, er, is it's actually quite flexible in how you go about mapping the various rooms to the addressable spaces of the CPU and the peopie.

  • You?

  • Yeah, it takes a little bit of thinking about it, and perhaps they had automated tools, which helped them.

  • This leaves us with two remaining functions of the M M C three mapper, and that is handling this new feature into it requests on.

  • I'll add that this isn't an interrupt request to the map, er, to go and do something.

  • This is the map for generating an interrupt request for the CPU, and so far in this series, we've not really handled any interrupt requests other than the non mask herbal interrupt issued by the P.

  • P U.

  • That particular interrupt, which used to inform the CPU that it had finished rendering a frame and therefore the game Luke could go ahead and get the inputs, do some calculations and produced the output for the next frame in many games is useful to split your screen into different regions, particularly if you've got status parts.

  • So, for example, in this region, appear you could have score information on perhaps down here, you have inventory stuff I don't know, leaving this region in the middle to have some sort of gain environment on.

  • We looked in the past that the peopie you, how it handles split screen scrolling and in games like Mario one, as the scan line is zipping across the screen, it waits for a specific pixel to be hit.

  • And that's issues a Sprite hit event to the CPU.

  • We're on on screen.

  • Spite collides with a background.

  • Spite on the CPU could use this to crudely measure were abouts in the frame.

  • The rast arise Arise book, too, and use this information to change its rendering information accordingly.

  • The problem is per frame.

  • You only get to do this once on.

  • Given that they're P, P U is an entirely deterministic system.

  • You would think it would be easy to synchronize the CPU and the peopIe.

  • But it isn't because the CPU instructions are variable in length, so you can never guarantee where things are going to line up, depending on what's happening within the game logic and what's happening on the screen on various other factors, perhaps even controller input being pressed, that frame is changing the synchronization between the two processes.

  • It also means that you're potentially sat waiting in a loop for an event to occur on.

  • This is wasted processing time.

  • A slightly more elegant solution would be to have some peripheral system actually count the scan lines for you.

  • On when that count of scan lines hits a particular number you've programmed in advance.

  • You can issue an interrupt to the CPU, and this provides a much cleaner and predictable solution.

  • The only problem is, there's nothing on the nest that will do this.

  • There's nothing in the peopie you that will do this.

  • And so this functionality has been added to the map er, and it does this in an exceptionally clever way on.

  • This is a real testament to designers at the time that had to really try and squeeze everything else of the technology they had available.

  • The only way the map Ercan know about which scan line is being operated upon is by looking at the peopIe you address, because that will scroll through the various memories at various predictable stages during the rendering.

  • And by keeping track of this address, it can detect when there's been a memory address change in such a way that would typically reflect the end of a scan line.

  • Now, if you're really hard core about making your emulators, you could go ahead and implement this logic and try and get it a cycle accurate as possible.

  • I've not done that with my emulator, so it's not strictly an option for me, but because I have written this emulator, I can cheat.

  • Therefore, I will add specifically to my mapper into face a function that is called when a scan line has been completed and the map a can accumulate thes calls on issue, and I are cute when required.

  • Now let's have a look at my map of four implementation I've now.

  • I did some additional functions to the interface toe handle.

  • The ai R Q.

  • On this is that scan line function I've just mentioned internally, I've got those eight registers, and I've also created to internal arrays to store the bank offsets for the character and program ROM's.

  • We also need some counters for our interrupt, and there's an interrupt counter reload value.

  • So this is how many scan lines need to occur before the I R.

  • Q gets issued, and I've got some status flags, toe handle the ai R Q.

  • Internally.

  • And as before, I've got some static ram, a mirroring mode and some mapper infrastructure.

  • Those two bits that set the mapping tables accordingly on the bottom.

  • Three bits of the data word, which indicate which register were writing to on the next.

  • Right.

  • So let's look at CP.

  • You read first as before we handle the static.

  • Graham.

  • Now, in the spirit of this Siri's, I've deliberately kept things were both.

  • So I'm absolutely certain there are ways you can condense this into some nice numerical trick.

  • I didn't want to hide behind too many binary tricks just so it remains visible What's going up.

  • But when we're reading from the program memory, we can see which bank offset.

  • We've got in our way for a given range, and we just go and find that in the program Rahm supplied by the cartridge very simple.

  • In exactly the same way.

  • PeopIe you read looks at the range and chooses the corresponding bank from our array of bank offsets.

  • There just happens to be quite a few of them, each one representing one kilobyte of character data.

  • The reset function defaults the map er to a known state, and by default it's in this fixed up a 16 kilobyte mode.

  • So the last two chunks of program memory are mapped to the very last two banks of program.

  • Wrong memory on the cartridge.

  • Configuring the mapper, as always, is done in CPU, right?

  • And it's much simpler this time round because we actually have specific zones, and all we're doing is checking if the address is odd, or even so in the first Zone, which is configuring the map for itself, arguably the most important one we first check.

  • Is it odd or even if it's even?

  • Then we're configuring.

  • That's target register on the two bits that control how we do the mapping later on.

  • If it's odd, then we're updating one of the eight internal registers, and then we just go on, implement our table's directly.

  • I'm not going to go through those other than to say they just set the bank offsets in the program.

  • Rome According to how our internal array of registers has been programmed, the next zone was responsible for handling the mirroring, and in my emulator, I'm only interested in even address is being used here on we specify the mirroring as being horizontal or vertical.

  • The Wiki article says There's also something to do with RAM protection, though I've not implemented that.

  • The third Zone Down was handling the Ai R.

  • Q.

  • If it's even then, we're setting the number of scan lines we want to occur before the i.

  • R.

  • Q fires.

  • If it's odd, then we're just resetting.

  • That counter on the final zone is used to enable the Ai R.

  • Q allowing it to happen or not at all, regardless of the counter value.

  • It's all that's left to mention in this map.

  • Er, is this scan line function every time it's called it just deck remains the counter on when the counter reaches zero reloads it with the value that we've set so we can get it to fire every five scan lines.

  • Every 20 scan lines, every 39 scan lines, whatever.

  • In the event that the I R Q Counter has equal zero on, we're actually interested in receiving. 00:4

Hello and welcome to Part seven of my Nails emulator Siri's.

字幕與單字

單字即點即查 點擊單字可以查詢單字解釋

B1 中級

NES模擬器第7部分:更多關於繪圖儀的內容。 (NES Emulator Part #7: More About Mappers)

  • 0 0
    林宜悉 發佈於 2021 年 01 月 14 日
影片單字