jjpeerless wrote:
Isn't it safe to assume games won't do this,
NO!
Most mappers put their write-only regs at $8000-FFFF, so therefore the game will write there every time it wants to swap PRG or CHR (several times per frame). Other games are malicious in that they check for antipiracy purposes (to see if the ROM is being run on a copier, which might allow overwriting ROM).
Assume games can and will do everything. Because they can. And they do.
Quote:
Anyways, so would a better approach be to have read_memory(16 bit address) and write_memory(addr) functions? in the write_memory I could add checks to the addr to make sure its not in read-only memory? is that the idea?
Generally yeah that's the idea.
A common variant is instead of one overall read and write function group, you split it up so that you have 16 function pointers (one for each $1000 bytes of addressing space) and assign the function pointers to different read/write functions. ie, ReadFunc[0] and ReadFunc[1] would point to ReadRAM, ReadFunc[2],3 would point to ReadPPU, Read[8]+ would point to ReadROM, etc. This allows for easy mapper intervention, as well (just change a pointer)... which is nice because you'll need to catch reads and writes for various mappers.
Blargg propsed a hybrid solution where you have an overall read/write function first which deals with system RAM (0x0000-1FFF) and then does the 16 callbacks method if the address is outside that range. The idea is that this is benefitial because RAM reads/writes occur very frequently.
Quote:
As I have very limited knowledge of what I am doing, I was just going at this project on a 'as-i-go' basis,
Heh. Yeah we've all been there. It's hard to plan ahead for eveyrthing when you don't know what "everything" is. Just prepare to do a lot of rewrites, or hit a few snags/walls. Because it's bound to happen. Don't let it discourage you though!
Quote:
I've only been working on implementing the cpu. I am not even sure what/why bankswitching refers to?
Since there's only 32k of PRG space available (addresses $8000-FFFF) for games to have more than this, they employ bankswapping to "swaps out" different pages of PRG allowing for the overall game to be larger. This is where you get into mappers and stuff. Right now you're probably just dealing with mapper 0 games which have no swapping.
Quote:
Yeah, I noticed that when looking at the memory map. How do people handle these mirrorings?
Code:
// to read RAM (assume addr is between 0-0x1FFF)
v = RAM[ addr & 0x07FF ];
Quote:
Im guessing you are talking about I/O registers? Haven't really thought about these things yet. :/
Yup. This is a very important part. After all you can't make games if you can't output anything.
Quote:
Ok so if I am going to restructure my memory implementation Id like to do it now before I get too far into this.
The only other way I could think to implement memory would be to do something like this:
Allocate bytes for each section of memory to their own byte pointers?
[snip]
this just seems really messy, would also have to store the start addresses for each section.
That is typically what is done. And it's actually much more organized (not messy) because each block of memory is clearly labelled (ie: you have RAM, ROM buffers instead of just a big "memory" buffer which could be anything or nothing). Keep in mind that when you get into bankswitching and stuff, the "big 64k clump" just isn't practical and you're much better off with separate buffers.
You might want to look at some bankswitching info before you go further. Once you see how it works you'll better understand what I'm talking about.
For an example -- let's take a simple "mapper 2"-ish example:
- PRG-ROM is 128K (0x20000 bytes)
- this is broken up into 8 "pages", each 16K (0x4000) in size.
- $C000-FFFF is mapped to the last page (page 7: 0x1C000-0x1FFFF)
- $8000-BFFF is swappable, so it can reflect any of the 8 pages in the ROM.
Basically, when the games "swaps" PRG, it's changing which page is "visible" in that slot. IE:
Code:
JSR SwapToPage_0
LDA $8000 ; reads from PRG offset 0x00000
JSR SwapToPage_2
LDA $8000 ; reads from PRG offset 0x08000
For this to work with the "big 64K clump" you'd need to copy the full 16K page to your memory block every time a swap occurs. Since swaps occur repeatedly and rapidly (several times per frame) this is very wasteful.
The general approach to this is to have pointers. Like one pointer for each $1000 bytes. These pointers would then point to different areas in the main PRG buffer. That way to swap all you have to do is change where the pointer(s) point(s).
Quote:
Also, if I dont use my memory implementation then it seems like reads/writes will be messier, requiring lots of checks of where the address is to find out which pointers to use, instead of just indexing right into the spot?
Hence why function pointers are a common solution. They make it sort of like a jump table.
Code:
v = ReadFunc[ addr>>12 ](addr);
Quote:
Why does the order matter?
It doesn't really. All that matters is that you emulate the desired output behavior. I was just saying why the 6502 does it the way it does. Your emu need not do it that way.
It also depends on the level of accuracy you want. If you want your CPU core to be cycle accurate, then you'd want to do the reads and writes in the exact right order and have them spaced out one cycle apart.