PDA

View Full Version : Game Boy



Pages : 1 2 3 4 [5]

HyperHacker
May 27th, 2008, 10:20
You're on the right track there. IIRC they're all LS-first, and you're right with #3 and 4.

CodeSlinger
May 27th, 2008, 14:11
Wow fast reply, thanks :-)

I've been looking into how the game is loaded into memory. The game gets loaded into memory address 0 to 7FFF.

The first 0x0 - 3FFF of this memory is called "16KB ROM Bank #0" which i presume never gets switched. Now its the 4000 - 7FFF bytes im stuck on which is called "16KB switchable ROM Bank".

I know that we can determine whether MBC1 is turned on by looking at a byte in the rom header.

How does the game switch in and out of pages? Is there an opcode which specifies which page to use?

So for example, if i load the entire game into a 2d byte array like so.



BYTE m_GameMemory[NUM_PAGES][0x4000] ; // the entire game stores as pages
BYTE m_Rom[0xFFFF] ; // the game gets loaded into the first 8000 bytes



Once i have loaded in the game to m_GameMemory I then copy m_GameMemory[0] to m_Rom[0]
so bytes 0x0 - 0x3FFF in m_Rom is the first page of the game memory (this is "16KB ROM Bank #0"). I then copy m_GameMemory[1] to m_Rom[0x4000] so bytes 0x4000 - 0x7FFF in m_Rom are the same as page1 in the m_GameMemory (this is "16KB switchable ROM Bank").

Whenever the game then switches pages I then copy m_GameMemory[newPageNum] to m_Rom[0x4000] so bytes 0x4000 - 0x7FFF are the same as m_GameMemory[newPageNum]. This will always be switching "16KB switchable ROM Bank" memory and never switching "16KB ROM Bank #0".

Is this correct or am i way off?

Thanks again for your help.

Edit:

I've just read the following:

"MBC1 has two different maximum memory modes:
16Mbit ROM/8KByte RAM or 4Mbit ROM/32KByte RAM."

Does this mean the page sizes are only 8KB? If so then how does that fit into the address space 0x4000 - 0x7FFF which is 16KB.

Thanks

HyperHacker
May 27th, 2008, 21:12
Bank switching is done by writing the bank # to anywhere in the range 0x2000 to 0x3FFF. You'd want to load the entire game into a 2D array of NUM_PAGES x 0x4000, and use pointers for switching. Copying data around every time it switches is going to be very slow; some games switch several times per frame.

The memory size setting affects how many pages of ROM and RAM a cartridge can have. The mapping doesn't change.

CodeSlinger
May 28th, 2008, 09:25
Hyperhacker, you're being a great help mate, thanks!

I believe I understand now. I'll just get it confirmed with you.

So if i have a member variable m_CurrentRomBank which is set to 1 on CPU reset then if the game is using MBC1 and wishes to change rom bank then it will attempt to write a byte to a memory location between 2000 and 0x3FFF.

so my code would look something like this: (where the variable data is the byte to be written to memory)




// rom bank 0 and rom bank 1 both point to rom bank 1
if (data == 0)
data++ ;

// we are only interested in the least significant 5 bits of data
data &= 31;

m_CurrentRomBank = data ;


then i just change the pointer to point to the current rom bank?

Does this all sound correct?

I presume I change ram banks in the same way except with different memory addresses and the ram has to be enabled.

Thanks again mate

HyperHacker
May 28th, 2008, 11:50
That looks right. IIRC, RAM bank switching is writing to 4000-5FFF, but don't quote me on that. You might want to just log ROM writes to see what area it is.

I'd just look it up in Martin Korth's documentation, but the site is down. :(

CodeSlinger
May 28th, 2008, 12:07
what's the url for his docs?

Ta

synch
May 28th, 2008, 12:15
It's not down, just moved his server about a year ago or so. The documentation can be found here:

http://nocash.emubase.de/pandocs.htm

CodeSlinger
June 11th, 2008, 09:47
I've been off and on the emulator for a few weeks now (mostly off due to other commitments) . I've managed to get most of the CPU emulated, enough to emulate a few roms. However I've done no graphical emulation yet as it looked difficult so I've been avoiding it. Having a closer look at the graphical emulation it really doesnt seem that difficult at all so soon i'll be taking a stab at it but I just need a few things cleared up first.

1. I dont understand the purpose of the monochrome palette located at address 0xFF47. This register assigns shades to colour numbers for the BG and window tiles. Well whats the point in this? Surely the shades are static so this register never needs to be addressed. I understand how a colour of a pixel is determed by looking at two bytes in the Tile Data area of memory and combining the bits to form a 2 Bit colour index. Where 00 = white. 01 = light gray. 10 = dark gray. 11 = black.

So once you find out the 2 bits to represent the pixel colour you already know what the colour is so whats the point in the monochrome palette register?

2. I understand how the scrolling registers work but i just thought id make sure that the scrolling registers cannot be changed unless it is during a V Blank period. Otherwise if the emulator is half way through drawing the scan lines (so the LY register is currently 72) and the Scroll register changes, then the second half of the draw scans wont match the top half, so then there would be tearing. Is this correct?

3. The way I understand the drawing of the screen works is that every time the LY register gets incrememnted it draws 1 line of pixels (160). How do I determine where to get the information for the current pixel im drawging from? I just need to understand how to determine if the pixel is a Background pixel, a window pixel or a sprite pixel, whilst also taking into account priority.

Thanks guys.

HyperHacker
June 11th, 2008, 10:35
For perfect accuracy you'd want to draw one pixel every x cycles, but I think you'd get away with drawing one scanline at a time; possibly a few uber tech demos would break, but actual games would work. You can change scroll registers in Hblank (see wavy effects from certain attacks in Pokemon), not sure what happens if you try to change them during redraw.

For pixel colours, they aren't hardcoded like that. 00 = colour #0, 01 = colour #1, etc. FF47 sets which colours are assigned to which numbers. This lets you do colour-swap effects without having to iterate through all your graphics. (See pause effects in Metroid 2.) IIRC you can even assign the same colour to multiple indices, which Metroid 2 might also be doing.

CodeSlinger
June 11th, 2008, 11:05
Ok. I'm a bit confused by the H-Blank, im guessing it is like this:

When the gameboy is drawing a scanline H-Blank is set to false. However when it is not drawing a scanline it is set to true because if it is not drawing the scanline then it must be moving on to the start of the next scanline which is the H-Blank period. However H-Blank is never set if the gameboy is in a V-Blank?

As I said earlier I draw the scanline all in one go. Which means surely im always in a H-Blank state unless im in a V-Blank state.

Does that sound right?

HyperHacker
June 11th, 2008, 21:21
The LCD controller emulates a TV's scanning beam. HBlank is the time when the beam has finished drawing a line and is moving back to the other side of the screen. VBlank is when it's done drawing the frame and is moving back to the top. Drawing, VBlank, and HBlank periods always last a specific number of CPU cycles (documented in GBTek linked a few posts ago); most emulators just count cycles before switching modes, and draw a scanline whenever HBlank or VBlank occurs.

You do need to emulate this even if you're drawing entire scanlines at once, as some games depend on it. Video memory is not accessible during redraw periods.

Exophase
June 11th, 2008, 22:19
Felt like posting this, although I don't have anything I can actually show right now...

A week and a half ago I asked some friends of mine if they wanted to have a Gameboy emulator writing competion. At first it was actually just me and Lordus, author of jEnesisDS (Genesis emulator for Nintendo DS), but then DS scene programmer sgstair and another friend of mine joined up. sgstair was pretty confident he could get it done in 2 days so the tension was turned up about 20 notches and I was intent on giving it everything I got.

The competition started at 2:00 PM EST Sunday June 2, and the goal was to get the following five games working:

Super Mario Land
Super Mario Land 2
Kirby's Dreamland
Legend of Zelda: Link's Awakening
Tetris

For the first three games you had to be able to clear the first level, for Zelda you had to get the sword, and for Tetris you had to clear one of the "B" levels.

The sound and battery back up was supposed to work (although really with the goals listed the latter wasn't really pertinent to accomplishing them)... The sound was supposed to be "perfect." This was what ended up screwing us all over.

This is roughly how it turned out...

Me: Had the CPU/timer/div/IRQ emulation written in about 5 hours, then got rudimentary BG rendering written and spent the next 6-7 hours debugging it (using BGB to help follow along). Got first screenshot from Tetris around 13 hours in (title screen only), and had in-game some minutes later. Then spent the next 5 or so hours debugging the 5 games until they all worked correctly, so had things working at about 18 hours. Was slowing down a lot at this point; had to implement sound now (but was really tired). I figured this would just be busy work since I already emulated this for my GBA emulator. I ended up getting the first three channels done (no sweep), but for reasons that I haven't really determined it just sounded buggy. I pretty much stopped here, calling the emulator "close enough" and went to sleep (more on what I did with this later).

sgstair: Got results in the first day as well, but trailed behind my by some hours, then went to sleep. After he woke up he started doing sound and got it working for the first two channels, and it sounded more correct than my implementation (but still not perfect, and some games lacked it entirely, probably because of missing timer IRQs). Since we were roughly at about the same point and didn't feel like grinding to get the sound perfect we agreed upon a tie.

Lordus: Could only work five hours before sleeping the first day (time zone differences), after coming back the second day I believe he got some results in games. Generally had less time to work on it, but by now he does have things working in the games and sound is half done, but with some glitches.

I'm not totally sure where my other friend is, but since this is his first emulator it's more of a learning experience for him.

Anyway, I poked at my source after agreeing on the tie, but eventually decided I didn't feel like fixing the sound code so I just ripped it straight out of gpSP (my GBA emulator) and it took like an hour to fully transition and for the most part it sounded great. I did find a few bugs, some core related, some sound related, so it wasn't a total loss since it gave me an opportunity to fix some audio bugs in gpSP. I also dove into some other games and fixed CPU bugs, added battery back up, more hardware support, etc. I beat SML1 and Kirby's Dreamland in it, made it most of the way through Zelda (but I lost my playthrough of dungeon 7 because of a power outage), made it halfway through Final Fantasy Adventure before realizing that I didn't mark the hardware type as being battery backed and lost everything, and made it to the top of level 3 in Castlevania Adventure when my emulator crashed. Not trying that one again w/o savestates, but I probably fixed it by now.

EDIT: Some screenshots:

http://exophase.devzero.co.uk/gamble1.pnghttp://exophase.devzero.co.uk/gamble2.PNGhttp://exophase.devzero.co.uk/gamble3.pnghttp://exophase.devzero.co.uk/gamble4.PNG
http://exophase.devzero.co.uk/gamble5.PNGhttp://exophase.devzero.co.uk/gamble6.pnghttp://exophase.devzero.co.uk/gamble7.PNG

CodeSlinger
June 12th, 2008, 09:05
Thanks again HyperHacker.

ExoPhase great job on your GameBoy emu. It's looking really impressive.

Hopefully over the weekend i'll have my emulator displaying at least the bg tiles, if not the windows and sprites!

I havent looked at input emulation yet and as far as sound emulation goes I honestly have NO idea. Apprently it's the most difficult part.

Lordus
June 12th, 2008, 22:07
Still have a few bugs left, but the sound works pretty well now. Here are some screenshots of mine:

http://i289.photobucket.com/albums/ll208/Lordus_/Spielboy_01.pnghttp://i289.photobucket.com/albums/ll208/Lordus_/Spielboy_02.pnghttp://i289.photobucket.com/albums/ll208/Lordus_/Spielboy_03.pnghttp://i289.photobucket.com/albums/ll208/Lordus_/Spielboy_04.png

ShizZy
June 12th, 2008, 23:17
You guys suck. Next time I want in. Emulator code-offs are the best.

CodeSlinger
June 19th, 2008, 14:32
Thanks for all your help guys. I've managed to get my emulator to the state where apart from sound tetris is now completely finished.

I do seem to be having one probablem with the game bubble ghost. The emulator gets stuck servicing the following instructions:



rom: 0x394 :ld a, (C0F0)
rom: 0x397: or a
rom: ox398: jr z, 0394


which means load into register a the contents of memory addres 0xC0F0. Logically or register a with itself to set flags (the z flag is the interested flag). If the z flag is set them jump back to the first load instruction and continue until the z flag is not set (i.e. the contents of 0xC0F0 is not 0)

Now if the CPU is busy stuck in this loop then how is the contents of 0xC0F0 going to get changed?

I assumed it could be something to do with an interupt that loads something into 0xC0F0 but ive implemented all interupts and this doesnt help.

Thanks for any help

Exophase
June 19th, 2008, 22:14
Thanks for all your help guys. I've managed to get my
Now if the CPU is busy stuck in this loop then how is the contents of 0xC0F0 going to get changed?

I assumed it could be something to do with an interupt that loads something into 0xC0F0 but ive implemented all interupts and this doesnt help.

Thanks for any help

Like you said it's waiting on an interrupt to change it, this is typical form for an "idle loop." There must be a bug with when various interrupts are supposed to be triggered or something in executing the service routine itself that's preventing this from triggering. I'll check it out later today to see where that address is supposed to be written to.

CodeSlinger
June 20th, 2008, 09:55
Thanks for the reply.

It turned out to be the timer interupt. I was accidentally requesting bit 3 not bit 2. Whoops.
Bubble ghost now works up until the ghost appears at the beginning of the game. I havent implemented all opcodes yet so it breaks.

Thanks again

CodeSlinger
June 26th, 2008, 09:15
Hi Guys,

Time for a quick update.

So far my emulator has about 30% compatibility. Links awakening is probably the most advanced game it plays. Although the graphics are glitchy and the lightning at the beginning goes horizontally :) it is actually quite playable.

Im starting to bang my head at whats killing the other games though. Even games that are apparently easier to emulate than links awakening (like bubble ghost) have issues that I cant seem to resolve. Some games play up to a point and others dont even show the first screen like most of the final fantasy games.

I think my cpu is very close to being bug free with the only issues I think I have is to do with the half carry flag.

So im really stuck with what the problem could be. The only thing I think i've implemented wrong is the timers and dividers. Im guessing this could kill compat so i'll have to give it ago.

HyperHacker
June 26th, 2008, 09:58
Yeah, timers are pretty important. I'd also look at the interrupt handling, any glitches there could break all sorts of things.

What exactly does the divider do anyway? Just increment at 16384hz? Why would that be called a divider?

CodeSlinger
June 26th, 2008, 11:34
yeah thats all that register does. Dunno why it is called a divider though. One of its purposes is to supply a seed for a random number generator. As I found out the hardway with tetris, if you dont implement this then you always get given the block. Which makes the game a bit easy.

Edit:

One of the parts of the gameboy system I dont understand fully is the LCD Status register. I honestly dont think it is this that is causing the low compat problems im having but I still want to get it right.

This is how I understand it, I'll take each bit of the register at a time:

BIT 6: LYC=LY Coincidence Interrupt: This bit gets set when LYC == LY. When this bit is set it also sets the BIT 1 in the Interupt Request address (0xFF0F) which signals that an LCD interupt is required. When does this Bit 6 get reset?

BIT 5: OAM Interupt. This is set whenever the mode is set to 2. This also requests an interupt. When does it get reset?

BIT 4: V-Blank interupt. This is set during the entire V-Blank period. The thing I dont understand about this is that there is a seperate interupt for V-Blank so why would the CPU service the V-Blank interupt and then service the LCD interupt for V-Blank? They're the same thing.

BIT 3: H-Blank Interupt. This is set for the duration of H-Blank. Thing is is that there isnt a H-Blank interupt. Or does it mean request an LCD interupt and make sure BIT 3 is set?

BIT 2: This is set when LY==LYC same is Bit 6. This doesnt request an interupt. Whats the point in having bit 6 when bit 2 is the same?

BIT 1-0: Set for the current mode of the LCD. I dont know how to determine this.


One other thing. As Bit3-6 is all interupts can more than 1 be set at the same time?
Thanks for any help.

Exophase
June 26th, 2008, 23:31
yeah thats all that register does. Dunno why it is called a divider though. One of its purposes is to supply a seed for a random number generator. As I found out the hardway with tetris, if you dont implement this then you always get given the block. Which makes the game a bit easy.

It's called a divider because the clock is divided off of the main (4MHz) clock, by 256 to be exact. The timer is divided off the main clock too, but by a configurable amount.

One of the parts of the gameboy system I dont understand fully is the LCD Status register. I honestly dont think it is this that is causing the low compat problems im having but I still want to get it right.

This is how I understand it, I'll take each bit of the register at a time:

BIT 6: LYC=LY Coincidence Interrupt: This bit gets set when LYC == LY. When this bit is set it also sets the BIT 1 in the Interupt Request address (0xFF0F) which signals that an LCD interupt is required. When does this Bit 6 get reset?

BIT 5: OAM Interupt. This is set whenever the mode is set to 2. This also requests an interupt. When does it get reset?

BIT 4: V-Blank interupt. This is set during the entire V-Blank period. The thing I dont understand about this is that there is a seperate interupt for V-Blank so why would the CPU service the V-Blank interupt and then service the LCD interupt for V-Blank? They're the same thing.

BIT 3: H-Blank Interupt. This is set for the duration of H-Blank. Thing is is that there isnt a H-Blank interupt. Or does it mean request an LCD interupt and make sure BIT 3 is set?

Bits 3-6 are all interrupt enable bits. When they're on it means the LCDC IRQ, that is, the IRQ triggered in bit 1 in IF and serviced at 0x48, goes off whenever these events happen. One thing that was actually really unclear to me until I looked at the source for another GB emulator... the vblank IRQ (bit 0, serviced at 0x40) always goes off when vblank triggers when LY == 144. If bit 4 is on that means that the LCDC IRQ goes off too.

The way all exceptions work is that the IF bit that was raised is cleared as soon as the exception is raised. If for whatever reason the exception can't be raised, that is, if that interrupt is disabled (either globally through the di instruction or by the IME register) then the bit will stay 1 until it can be. Or until the user clears it manually. The CPU can tell what exception was raised by virtue of what exception handler was called. In the case of LCDC it has to look at the video mode bits because they all go to the same handler.


BIT 2: This is set when LY==LYC same is Bit 6. This doesnt request an interupt. Whats the point in having bit 6 when bit 2 is the same?

This isn't an interrupt enable bit, it's an actual status bit that's only on for the scanlines where LY == LYC.


BIT 1-0: Set for the current mode of the LCD. I dont know how to determine this.

The mode bits say what the video chip is currently doing. For 144 scanlines it's in active display and the documentation floating around lists it like this:

For ~204 cycles it's in mode 0, or hblank, and you can freely access OAM and VRAM.
Then for ~80 cycles it's in mode 2, where it is processing the sprites in preparation to draw the scanline. During this time you can't access OAM.
Then for ~172 cycles it's in mode 3, where it's drawing the scanline. During this time you can't access OAM or VRAM.

This makes up 456 cycles per scanline. The numbers given are only approximations, possibly because they can't be measured exactly with the CPU (not fine enough precision). These are in 4MHz units, by the way.

In reality what I hear is that these periods vary a lot depending on how many sprites are active in the scanline. I read VBA's source on this and couldn't really make a ton of sense of why it was doing things the way it was but it looked like every sprite drawn added 8 or 12 cycles depending on if it's an even or odd sprite out of the ones drawn. Having windows on can apparently also add 4 cycles if windows are active. These cycles come out of mode 0 for the next scanline (so the two together should still add up to the same amount always) I'd love to see some real documentation on all of this.

The cribsheet gives times in microseconds. It says 48.64 for mode 0 with no sprites, which at 4MHz (or 4194304Hz) confirms 204 cycles. It says 18.72 for 10 sprites, which is a bizarre 78.5 cycles. Following what I gave above, the odd sprites happen 5 of the time, so there should be (5 * 8) + (5 * 12) cycles taken away, or 100. Where the other 25.5 would be going is anyone's guess.

I looked at Gambatte as well, which is supposed to be the most accurate GB emulator there is. Unfortunately, the video part of the source code is next to impossible to decipher without a lot of studying (or maybe even with) - it's done in a very complex/cryptic OOP event based method that isn't really documented at all and uses a lot of nested ternary and other coding styles I find difficult to gleam from. It seems that it adds between 6 and 11 cycles per sprite, 6 for window, and something else for the current SCX value (how much off alignment it is from the tile map).

I personally wouldn't worry about getting the mode intervals right at all, but it's important to set them for at least some times because some games will poll, waiting for them to change.

Cyberman
June 27th, 2008, 01:27
Sorry about that Exophase, in some strange weird twisted event when I clicked quote I ended up editing your post. I restored it (hence it says Edited by Cyberman down there). I suppose I could do more editing, but I am kind fizzled out after explaining hardware sprite systems so I am just going to stop right here by fixing the post mostly and do less (or no more should I say) Tthat's what happens when you get an 11 year old asking you questions and playing with stuff whilst posting.

Cyb

Exophase
June 27th, 2008, 08:07
Sorry about that Exophase, in some strange weird twisted event when I clicked quote I ended up editing your post. I restored it (hence it says Edited by Cyberman down there). I suppose I could do more editing, but I am kind fizzled out after explaining hardware sprite systems so I am just going to stop right here by fixing the post mostly and do less (or no more should I say) Tthat's what happens when you get an 11 year old asking you questions and playing with stuff whilst posting.

Cyb

No problem, I've accidentally done that before on forums I was admin of too :B Fixed it up a little more.

I think GB's sprites are kinda interesting and unconventional. I don't know any other platform that has a variable hblank depending on number of sprites visible. I had a theory that PC-Engine might because we're getting really weird VDC timing flaws in various games (no emulator knows what's going on really), but that didn't pan out in the tests I got ran on hardware.

CodeSlinger
June 27th, 2008, 09:37
Thanks for the detailed reply exophase.

So during V-Blank it is possible for both interupts to be executed? The V-Blank interupt and the LCD interupt with bit 4 set? Of course this is assuming IME is enabled and so is the corresponding IF bit.

I take it the LCD interupt only occurs when one of bits 3-6 gets set from 0 to 1. And when the interupt has finished servicing the bit gets reset from 1 to 0?

I had a look at the modes and am I right in thinking it starts with mode 2 then mode 3 then mode 0. Repeat until vblank and set mode 1. Then at the end of vblank back to mode 2?

As for the LYC = LY stuff. The status bit 2 is set for the entire duration of LYC = LY. Where as the interupt bit is set as soon as LYC = LY and is reset after the interupt has been serviced. So once it has serviced the interupt bit 6 gets reset even if LYC still equals LY?

Exophase
June 27th, 2008, 19:43
Thanks for the detailed reply exophase.

So during V-Blank it is possible for both interupts to be executed? The V-Blank interupt and the LCD interupt with bit 4 set? Of course this is assuming IME is enabled and so is the corresponding IF bit.

Probably. I haven't read anything to suggest otherwise.


I take it the LCD interupt only occurs when one of bits 3-6 gets set from 0 to 1. And when the interupt has finished servicing the bit gets reset from 1 to 0?

The LCD interrupt occurs when all four of these are true:

- The particular event happens (probably only at a transition point, like the hblank IRQ event would trigger when the mode transitions from to 0)
- The bit in STAT corresponding to that event is set to 1
- The LCD IRQ is enabled in IE
- IME is set

The bits in STAT are bits that allow for an interrupt to be generated on those conditions, not bits that cause the interrupt by themselves. Setting them high probably won't cause an interrupt to happen, even if it's in the middle of the appropriate period. They won't get set back low unless the code explicitly does so.


I had a look at the modes and am I right in thinking it starts with mode 2 then mode 3 then mode 0. Repeat until vblank and set mode 1. Then at the end of vblank back to mode 2?

From the way the diagram is laid out it looks like it starts with mode 0 (hblank), then transitions to 2 (OAM access), then 3 (hrefresh), for active display scanlines. For vblank scanlines (144 to 153) the mode is 1.


As for the LYC = LY stuff. The status bit 2 is set for the entire duration of LYC = LY. Where as the interupt bit is set as soon as LYC = LY and is reset after the interupt has been serviced. So once it has serviced the interupt bit 6 gets reset even if LYC still equals LY?

If by interrupt bit you mean bit 1 in IF than yes, and keep in mind that "serviced" just means that the interrupt handler has been jumped to, not that it has completed. I haven't seen anything in the documentation to suggest anything other than that bit being high for whenever LY == LYC, so the entire scanline unless the game resets LY, or if the game changes LYC.

CodeSlinger
July 7th, 2008, 10:22
Thanks for the reply!

One thing I dont understand about the STAT register is if say BIT 4 is set then this is an interupt request and if the IF Bit 1 is also set (and IME is enabled) then it jumps to the address of the STAT interupt routine. However if it doesnt reset BIT 4 after servicing the STAT interupt then surely the next cycle will then service this interupt again and again until the mode of the LCD changes and BIT 4 is reset? I'd of thought that once servcing the STAT interupt then BIT 4 gets reset straight away so it doesnt reservice it?

Also with the timer register. I know how the timing register works. It counts up at a certain frequency and when it wraps round to 0 an interupt is requested and the timer resiger is set to the value of the timer modula.

It says in the documentation the first frequency is set to 4096 hz. Now does this mean that the timer register gets incremented at 4096hz a second, so in one second there is 16 timer interputs a second (not including when ther timer is disabled or running at a different frequency)? The reason why it would be 16 is because 4096 divided by 256 is 16 (256 being the amount of increments the register has to do before it wraps round). Or does the 4096 frequency mean there are 4096 timer interupts a second and the timer register has to be incremented at a rate that will achieve this? It says in the documentation that it is the second scenario (4096 interupts a second), but logically id say it was incorrect and would be the first scenario (16 interupts a second).

Thanks again for your help.

Exophase
July 7th, 2008, 16:18
Thanks for the reply!

One thing I dont understand about the STAT register is if say BIT 4 is set then this is an interupt request and if the IF Bit 1 is also set (and IME is enabled) then it jumps to the address of the STAT interupt routine. However if it doesnt reset BIT 4 after servicing the STAT interupt then surely the next cycle will then service this interupt again and again until the mode of the LCD changes and BIT 4 is reset? I'd of thought that once servcing the STAT interupt then BIT 4 gets reset straight away so it doesnt reservice it?

Bits 3-6 in STAT are not IRQ active lines, they're IRQ enable bits. The bits mean "allow this event to cause an interrupt when it happens", they do not reflect that the event has happened. If these bits are not set then the mode transitions they represent will never cause the LCDC IRQ bit to go high in IF. If they are set, then the bit in IF will go high, but if it's serviced (because the corresponding bit in IE is set, and IME is set) then that bit in IF will immediately go low again, preventing the IRQ from retriggering again.


It says in the documentation the first frequency is set to 4096 hz. Now does this mean that the timer register gets incremented at 4096hz a second, so in one second there is 16 timer interputs a second (not including when ther timer is disabled or running at a different frequency)? The reason why it would be 16 is because 4096 divided by 256 is 16 (256 being the amount of increments the register has to do before it wraps round).

It would be at least 16, 16 only if TMA is set to 0.


Or does the 4096 frequency mean there are 4096 timer interupts a second and the timer register has to be incremented at a rate that will achieve this? It says in the documentation that it is the second scenario (4096 interupts a second), but logically id say it was incorrect and would be the first scenario (16 interupts a second).

What documentation are you reading? If it's using the 4KHz divider then that means it increments the TIMA register at a rate of 4096 times per second.

CodeSlinger
July 8th, 2008, 08:36
Thanks again for the reply :)

I think i understand the LCD status now. I just implemented it like you said and the compatibility improved. Basically Bit3-6 of the stat reg are set by the programmer not the emulator. These represent what circumstances the LCD IRQ should happen under. So if the programmer enabled bit 3 it means they are interested in the HBLANK so they will set Bit 3 and for the duration of mode 0 (HBLANK) the emulator will check Bit 3, if it is set then it requests an lcd interupt. However the interput will only get serviced if IF bit 1 is set along with IME enabled. That sound about right?

It seems to be the timers i'm having most of my problems with. This is what ive got so far:




void Emulator::DoTimers( int cycles )
{
BYTE timerAtts = m_Rom[0xFF07];



m_DividerVariable += cycles ;

// get the timer frequency

int timerVal = GetTimerFrequency(timerAtts) ;

int incrementlimit= 0 ;


//This is the part question 2 refers to
// the values incrementlimit gets set to is clock speed / frequency
switch(timerVal)
{
case 0: incrementlimit= 1025 ; break ; // 1025 is clock speed 4Mhz divided by frequency 4Khz
case 1: incrementlimit= 16; break ;
case 2: incrementlimit= 64 ;break ;
case 3: incrementlimit= 256 ;break ; // 256
default: assert(false); break ; // weird timer val
}

if(IsTimerEnabled(timerAtts))
{
m_TimerVariable += cycles ;

// time to increment the timer
if (m_TimerVariable >= incrementlimit)
{
m_TimerVariable = 0 ;
bool overflow = false ;
if (m_Rom[0xFF05] == 0xFF)
{
overflow = true ;
}
m_Rom[0xFF05]++ ;

if (overflow)
{

m_Rom[0xFF05] = m_Rom[0xFF06] ;

// request the interupt
RequestInterupt(2) ;
}
}
}
// timer not enabled. Reset timer
else
{
//This is the part question 1 refers to
m_Rom[0xFF05] = 0 ;
m_TimerVariable = 0 ;
}

// do divider register
if (m_DividerVariable >= 256)
{
m_DividerVariable = 0;
m_Rom[0xFF04]++ ;
}
}



The parts of the above code im unsure about:

1. If the timer is disabled do I reset the current timercount (TIMA) to 0 along with the timertracer (m_TimerVariable)?

2. When the clock frequency changes the emulator immediately reacts. So if the current frequency is 4KHz the timercounter (TIMA) increments every 1025 clock cycles. So if the current amount of clock cycles is at 500 so its only half way to incrementing TIMA and the frequency changes to 65Khz this means the TIMA should increment every 64 cycles. So as its at 500 and the limit is now 64 it immediately requests a timer interupt and starts counting again from the value is TMA. Is this correct or should the timer frequency only ever take effect after the next TIMA overflow?

3. What happens to the timer when it is servicing the timer interput? Is it still counting or does it stop until its finished? Is it possible for the timer to interupt the current timer service if IME and IF are both enabled?

Thanks again for your help

CodeSlinger
July 24th, 2008, 10:18
Well after a month of trying to figure out why bubble ghost wasnt working I finally cracked it.

It turns out my function for the opcode "Set Bit n in memory address pointed to by HL" was doing two reads. So it ended up setting the bit of the value pointed to by the address pointed to by HL rather setting the value of the address HL. DOH!

Compatibility is about 60% now. My main concern at the moment is why mario2 freezes at the start of the first level. Im pretty sure its to do with the lcd status and the modes because if I mess about with it I can get Mario2 working, but i know it is the wrong way of handling the stat register. Still the end is insight!

Exophase
July 25th, 2008, 11:13
Im pretty sure its to do with the lcd status and the modes because if I mess about with it I can get Mario2 working, but i know it is the wrong way of handling the stat register. Still the end is insight!

You're on the right track. Super Mario Land 2 will break if an LCD IRQ triggers while the LCD is disabled, because said IRQ while cause a bank switch without switching back.

CodeSlinger
July 25th, 2008, 13:17
Yeah it was the LCD status. It turns out that the mode cycles through 2,3,0 and 1 during vblank. Not 0,2,3. Mario 2 now plays up until about the 3rd level.

My compatibility has suddenly shot up, probably near 80% mark now, which im really happy with. However currently im stamping over memory which is causing the emulator to bomb out so i'll have to fix that first before getting an accurate compatibility rating.

I heard sound emulation for the master system is easier than gameboy so i might get the gameboy emulator running at about 95% without sound then start the master system and once learning sound for that I'll comeback to the gameboy.

givemeachance
September 13th, 2008, 23:33
Hi everyone...

Long time ago i finished my chip8 emulator(BTW, thanks for those who helped) , and now im almost ready to start coding a GB emulator.

Im looking for a list with opcodes ( showing their actual value etc) ...anyone knows where can i find them?

Thanks.


Nevermind ! found them :bouncy: ...at least wish me good luck :sombrero:

CodeSlinger
September 15th, 2008, 11:02
Good luck with your gameboy emulator. I loved coding mine and learnt a hell of a lot in the process. It is much more difficult than the chip8 but there is a lot of content on this thread to help understand it.

Im personally finding the Master System easier than the Gameboy except the Master System uses a full Z80 making it more difficult to find bugs :)

givemeachance
September 17th, 2008, 14:01
Thanks!

Its indeed harder than i thought , but i will make it ( i hope :fireman: ).

The only bad thing , is that i can't find some source code to study.
I downloaded GnUboy src , and hell ... code design is really bad and its
impossible to learn by just reading code :borg: .

Do you guys know where can i find source code so i can actually learn ?

bcrew1375
September 18th, 2008, 10:15
Ah, the nostalgia. Looks like it's been about a year and three months since I last posted in this thread. I miss the days when we were all coding our emulators together. :( Anyway, if you look way back on page 40, I posted the source to my emulator. It is not very fast, and it doesn't have great compatibility, but I tried to make the code as readable as possible. Hopefully, you can learn most of what you need from it.

givemeachance
September 18th, 2008, 12:02
Thanks! , your source is really clean!.
Even if im more familiar with C++ , i've never seen so well organized C project.

A few suggestions how to improve your emu:

1)In sdlfunc.c you can use register-type variables.It might improve the loop a little (or not at all).

2)In cpu.c use function array pointers when executing opcodes(this will give a high speed boost) , and also don't run all tasks so often. Use a timer.

Good job , and thanks!

bcrew1375
September 19th, 2008, 00:46
Yeah, that source is pretty outdated. I fixed alot of bugs after that, but I did about squat for efficiency. I messed alot of my code up a while back and I'm going through it trying to find what I've broken.

Exophase
September 20th, 2008, 15:12
1)In sdlfunc.c you can use register-type variables.It might improve the loop a little (or not at all).

In UpdateScreen? For i and j? I doubt they wouldn't be allocated to registers for x86.


2)In cpu.c use function array pointers when executing opcodes(this will give a high speed boost) ,

This is a good case of conventional advice regarding interpretive emulation that's not correct, or at least, should not be correct. Calling arrays of function pointers should actually be slower than using a big switch - granted, he's calling functions inside the switch, but if he makes them inline then it'll make this moot. Which is probably what he should do if he wants to see an immediate improvement.

Calling a function is a lot heavier than going to a switch case. The compiler will likely turn the latter into a jump table (not a call table), with the only possible negative deterrent being an additional cmp for range checking, although some compilers are smart enough to omit this. With a function, you have a register save convention barrier to hop through, which can not be omitted because the compiler won't be smart enough to deduce the class of functions you're calling. You have to go through the function prologue/epilogue which can cost a lot depending on the arch (even just the additional return can cost more). You have to either pass the parameters individually, through a struct (or this pointer, in C++), or in globals - no matter what it's going to mean accessing memory, rather than keeping things in registers. Not necessarily a huge deal for x86, but again, for other popular archs like ARM you're making extra work. If you use parameters then boiler plate to shuffle them into place has to be done for every opcode executed. Because the compiler knows nothing about the function it's calling it has to respect the caller save conventions full stop, which weakens the register allocation pool overall.

With a switch, the most common variables like PC, flags, accumulator, can be kept in registers (here the "register" keyword MAY actually help) and don't have to be shuffled around.

If the switch is slower then that probably means the compiler is doing something very bad and you should investigate.

If using GCC then the best thing you can do is probably:

- Array of label pointers
- Variable goto for this
- Do this at the end of every instruction handler, instead of a loop, to avoid a jump

This is what people tend to do with the better ASM interpreters.

Some major things that you can do to improve speed: mainly you check for a TON of stuff every instruction and you can reduce this down to almost nothing:

- First, you can determine statically how many cycles will occur until the next IRQ for the most part - sometimes this number could suddenly get changed but there are ways to handle that. So you shouldn't have to constantly check for IRQs.
- You don't need to check halted or stopped every instruction, once it gets halted you can back out into a loop that deals with it specifically instead of running instructions. That's three if's avoided.
- You can make logging a compile time variable, so you don't have to check it.
- Although this is more complex, it's possible to factor out divide and timer counters and instead have a global timestamp that's updated, and then calculate these values when they're read (but this might complicate your memory reading code)
- At least, you can fold the check for zero into one counter that's the smallest of them.
- No reason to kill lower bits of STAT all the time, you can do that when it's written to.
- For that matter, no reason to check video state every instruction, when the lines are only changing once every so many cycles. You can factor this OUTSIDE of the loop.

If you did all these things you could probably increase speed by a good order of magnitude.


and also don't run all tasks so often. Use a timer.

What do you mean exactly?

givemeachance
September 21st, 2008, 13:45
Wow ... awesome post , thanks for all the information!.



In UpdateScreen? For i and j? I doubt they wouldn't be allocated to registers for x86.

Maybe.
The best solution is to create an empty image , and write the pixels at runtime.
Then , you can just render the final image without having to check the color of each pixel.
I think its much faster...



This is a good case of conventional advice regarding interpretive emulation that's not correct, or at least, should not be correct. Calling arrays of function pointers should actually be slower than using a big switch - granted, he's calling functions inside the switch, but if he makes them inline then it'll make this moot. Which is probably what he should do if he wants to see an immediate improvement.

Hmm...im not the best coder of course , but how faster can be this:



switch(opcode)
{
case 0 ... 255
}

which translates to :

if (opcode == 0) else if ( ==1 ) else if ( ==255)


than this:


//stored at 0x00
forceinlined void OP_NOP(TVirtualMachine* vm)
{
vm->pc++;
}


then executing the opcode directly :



if( (currentOP>=0x00)&&(currentOP<= max) )CPU_FUNCS[currentOP]();


I think its much faster...


What do you mean exactly?


In games , usually we delay the update system for about ".1"ms , and it improves
alot the performance.
I think it might work well with gb emu.

HyperHacker
September 21st, 2008, 19:49
Inline functions get copied directly into the code in place of calling them, so in theory they should be as fast as if you didn't use a function at all. Hopefully compilers would be smart enough not to bother with the usual register saving and restoring when they do this.

Exophase
September 22nd, 2008, 03:34
Wow ... awesome post , thanks for all the information!.

Thanks.



Maybe.
The best solution is to create an empty image , and write the pixels at runtime.
Then , you can just render the final image without having to check the color of each pixel.
I think its much faster...

Sorry, but I don't think I understand what you mean. Can you elaborate?



Hmm...im not the best coder of course , but how faster can be this:



switch(opcode)
{
case 0 ... 255
}

which translates to :

if (opcode == 0) else if ( ==1 ) else if ( ==255)


than this:


//stored at 0x00
forceinlined void OP_NOP(TVirtualMachine* vm)
{
vm->pc++;
}


then executing the opcode directly :



if( (currentOP>=0x00)&&(currentOP<= max) )CPU_FUNCS[currentOP]();


I think its much faster...

Who taught you that switches translate to this? That's a common misconception. Translating a switch to something faster than that is a very simple compiler operation. What GCC will translate it to is this:



labels switch_table_index[] =
{
CASE_LABEL_1, CASE_LABEL_2, CASE_LABEL_3, ...
}

if(index > LAST_CASE_NUMBER)
goto BREAK;
goto switch_table[index];

CASE_LABEL_1:
...

CASE_LABEL_2:
...

BREAK:
...



In games , usually we delay the update system for about ".1"ms , and it improves
alot the performance.
I think it might work well with gb emu.

You should wait for however long you need to to achieve realtime speed for Gameboy. If you can't achieve realtime speed then either the computer you're running on is 10+ years old or your emulator is way too slow. Adding waits won't make something faster though, that's kind of counter-intuitive. If it's multithreaded it'll let other things run, but there are more explicit ways to do this without delaying. That's actually not a good approach.

Exophase
September 22nd, 2008, 03:37
Inline functions get copied directly into the code in place of calling them, so in theory they should be as fast as if you didn't use a function at all. Hopefully compilers would be smart enough not to bother with the usual register saving and restoring when they do this.

Are you referring to what I said about function tables? If so, you can't inline a function that's called indirectly. Or at least, it won't be inlined where you call it, of course.

CodeSlinger
September 22nd, 2008, 09:17
Exophase is correct about the large switch being faster than an array of function pointers. As he said the compiler converts a switch statement into a jump table and not a huge if else if mess. Well saying that some compilers do compile it into a huge if else if mess but these compilers should be avoided like the plague.

Personally I prefer using functions inside the switch statement to handle the opcodes because they can be taylored to be used for more than one opcode. You can use the function Opcode8BitAdd( ) to handle Add a,b and Add a,c etc. This minimises duplicate code so one bug fix fixes all. These functions should be made inline which is something I've yet to do on my gameboy and master system emulator. I'll start optimizing my code after I've emulated sound, this way I can study exactly how much cycles im saving. I only ever optimize after I've finished coding.

givemeachance
September 22nd, 2008, 11:51
Sorry, but I don't think I understand what you mean. Can you elaborate?


I will take as example "Miracle GB's" source.

This function :


// Function name: UpdateLCD
// Variables: None
// Purpose: Update the Gameboy LCD screen.
int UpdateScreen()
{
int i;
int j;

SDL_Rect plotRectangle;

for (j = 0; j < 144; j++)
{
for (i = 0; i < 160; i++)
{
plotRectangle.x = i * 3;
plotRectangle.y = j * 3;
plotRectangle.w = 3;
plotRectangle.h = 3;

switch(screenData[(j * 160) + i])
{
case 0:
{
SDL_FillRect(screen, &plotRectangle, color_white);
}
break;
case 1:
{
SDL_FillRect(screen, &plotRectangle, color_light_grey);
}
break;
case 2:
{
SDL_FillRect(screen, &plotRectangle, color_dark_grey);
}
break;
case 3:
{
SDL_FillRect(screen, &plotRectangle, color_black);
}
break;
}
}
}

SDL_Flip(screen);
return 0;
}



Looping through all pixels , and renders every single pixel one by one.

Now , imagine having this :



static void updateScreen(const SDL_Surface*& img,const SDL_Surface*& screen)
{
renderSurface(img);
SDL_Flip(screen);
}


Then in CPU.C , you can just write the pixels in the surface at once:




...outside main : SDL_Surface* screen_surface = NULL;



Then we modify the following code:


if (IOregister_LCDC & BIT_0)
{
//----------------------------------------//
// Draw the background into the screen //
// buffer. These are always 8 x 8. //
//----------------------------------------//
while (x < 160)
{
data1 = memory[BGTileData + (tileNumber * 16) + (borderY * 2)];
data2 = memory[BGTileData + (tileNumber * 16) + (borderY * 2) + 1];

while (borderX > 0)
{
color = (data1 & borderX) ? 1 : 0;
color += (data2 & borderX) ? 2 : 0;

if (color == 3)
screenData[(y * 160) + x] = ((IOregister_BGP & BIT_7) >> 6) + ((IOregister_BGP & BIT_6) >> 6);
if (color == 2)
screenData[(y * 160) + x] = ((IOregister_BGP & BIT_5) >> 4) + ((IOregister_BGP & BIT_4) >> 4);
if (color == 1)
screenData[(y * 160) + x] = ((IOregister_BGP & BIT_3) >> 2) + ((IOregister_BGP & BIT_2) >> 2);
if (color == 0)
screenData[(y * 160) + x] = ((IOregister_BGP & BIT_1)) + ((IOregister_BGP & BIT_0));

x++;

if (x >= 160)
break;

borderX >>= 1;
}
}
}


Into this:



forcedinlined void fillScreenSurface(SDL_Surface*& s,const TVirtualMachine* vm)
{
if ( !(vm->regs.IOregister_LCDC & vm->flags.BIT_0) )return;
//----------------------------------------//
// Draw the background into the screen //
// buffer. These are always 8 x 8. //
//----------------------------------------//
for (;x < 160;x++)
{
const register data1& = vm->memory[vm->tileInfo->BGTileData + (vm->tileInfo->tileNumber * 16) + (vm->tileInfo->borderY * 2)];
const register data2& = vm->memory[vm->tileInfo->BGTileData + (vm->tileInfo->tileNumber * 16) + (vm->tileInfo->borderY * 2) + 1];

for (;borderX > 0,x<160;borderX >>=1)
{
color = (data1 & vm->tileInfo->borderX) ? 1 : 0;
color += (data2 & vm->tileInfo->borderX) ? 2 : 0;
switch(color)
{
case black :
write 0x00 , 0x00 , 0x00 to surface
etc
}
x++;
}
}


I think you get my point...

Exophase
September 22nd, 2008, 15:44
But his code scales, as far as I can tell yours doesn't. Not that his is the proper way to do it. If you mean that the surface should be scaled after it's completely written to then I would generally agree. There are a lot of other things that can improve the code though.

givemeachance
September 22nd, 2008, 18:41
But his code scales, as far as I can tell yours doesn't. Not that his is the proper way to do it. If you mean that the surface should be scaled after it's completely written to then I would generally agree. There are a lot of other things that can improve the code though.

Yeah , with scaling of course...

Can you please share some more tips? im really really really really (:nemu: ) interested!!

Thanks.

Pixman
September 26th, 2008, 20:13
Would be cool if at the end of this everyone could post their effots for other to educate themselves and get some ideas... I long for it! :)
Greets,
Pix

electrophyte
October 19th, 2008, 16:43
Hey guys

I started to program my gameboy emulator at the beginning of this year. I already have implemented very much of the system. The only thing which I have to do now, is to implement sound.
I intended to do this with DirectSound but I have no idea how to create sounds with it. I think I have to create a rectangular wave in a buffer and then let it play, but how should I do this. (I guess I can't allocate memory for each new tone). Can anyone help me?

KickTheChair
November 22nd, 2008, 23:07
Hi! I've been testing my emulator recently and I found that something weird happens while emulating Tetris game. If anyone has that rom, check opcode at address 0x02F0. For some reason when I load it with my app and with hex editor it shows me opcode 0x28 (JR Z), however when I load it in no$gmb debugger it shows 0x76 (halt). :o Can anyone tell me why does this happen?

HyperHacker
November 23rd, 2008, 04:29
Your app and the hex editor both show the same thing, but it works fine in No$GMB? Is that address actually being executed?

KickTheChair
November 23rd, 2008, 10:47
Yes it is, when I run it in no$gmb everything works fine, it goes into halt state, but when I run it in my emu it goes into loop, because instead of HALT I've got JR Z opcode.

Edit:
I changed 0x28 to 0x76 in rom and it works ok now. Although I still wonder why it no$gmb changed that opcode by itself.

Exophase
November 23rd, 2008, 19:50
It didn't work okay before? It's supposed to be 0x28.

My first guess was that the transformation that No$ made is an example of "idle loop elimination." The little loop that is entered can only be exited if the byte at 0xFF85 is zero. Because this is HRAM, only software can set this location, and because of that it can only happen when an interrupt occurs.

However, it seems that it starts this way as soon as the game is loaded, so it could just be a speed hack done for Tetris. I tried changing the ROM so it has backup RAM in the hopes that I'd fool it but that halt is still there. So I don't really know what the deal is.

HyperHacker
November 23rd, 2008, 21:00
I think No$GMB allows you to disable that. If not, try changing the ROM name in the header.

KickTheChair
November 24th, 2008, 17:32
Well, there's one thing I still don't understand: if it changes 0x28 to 0x76 then why does it leave the next byte unchanged. After JR Z opcode comes value that's added to PC. For some reason next byte is left as 0xFB which in this case is EI opcode so it doesn't really matter, because interrupts are already enabled by the program gets to this part of code. Although it might as well could have been some other value that would mess with execution of the game.

Aire83
January 18th, 2009, 22:03
hi guys im trying to emulate the cpu, but i don't know when to increase the Program Counter and the amount to increase

CodeSlinger
January 19th, 2009, 08:50
The instructions are only 8bit so you'll only need to increment the program counter the once. It is up to you if you want to increase the program counter before or after executing the instruction. I believe the original hardware does it after the execution of the instruction but I chose to do it before as I found it easier to work with and have had no issues.

However remember that the gameboy z80 has the CB opcode prefix so you'll also need to increment the program counter when reading a CB opcode.

Aire83
January 24th, 2009, 05:51
i've coded the cpu but im still confused with the paired registers.

to emulate these registers do u make a variable for them like BC,DE...?

for example opcode 0x01:
LD n , nn

do u guys load nn into register BC ? or B and C separately then & the two later.

some how my emulator excecute opcode ( ignore the line number )

Line Number : 1 Executing Opcode 0
Line Number : 2 Executing Opcode C3
Line Number : 3 Executing Opcode F3
Line Number : 4 Executing Opcode 31
Line Number : 5 Executing Opcode 21
Line Number : 6 Executing Opcode 1
Line Number : 7 Executing Opcode AF
Line Number : 8 Executing Opcode CD
Line Number : 9 Executing Opcode 57
Line Number : 10 Executing Opcode 7A
Line Number : 11 Executing Opcode 22
Line Number : 12 Executing Opcode B
Line Number : 13 Executing Opcode 79
Line Number : 14 Executing Opcode B0
Line Number : 15 Executing Opcode 20
Line Number : 16 Executing Opcode 7A
Line Number : 17 Executing Opcode 22
Line Number : 18 Executing Opcode B
Line Number : 19 Executing Opcode 79
Line Number : 20 Executing Opcode B0
Line Number : 21 Executing Opcode 20
Line Number : 22 Executing Opcode 7A
Line Number : 23 Executing Opcode 22
Line Number : 24 Executing Opcode B
Line Number : 25 Executing Opcode 79
Line Number : 26 Executing Opcode B0
Line Number : 27 Executing Opcode 20
Line Number : 28 Executing Opcode 7A
Line Number : 29 Executing Opcode 22
Line Number : 30 Executing Opcode B
...

it just keep on repeating Line
Number : 18 Executing Opcode B
Line Number : 19 Executing Opcode 79
Line Number : 20 Executing Opcode B0
Line Number : 21 Executing Opcode 20
Line Number : 22 Executing Opcode 7A
Line Number : 23 Executing Opcode 22

when it reaches opcode 0x20 to do condition relative jump
Zflag is always 1 so it keeps on jumping

CodeSlinger
January 27th, 2009, 09:00
The pair registers and the singular registers should not be treated seperately. If you really wanted to do it that way then you can but you'll be in for a world of debugging hurt. The pair registers are just the singular registers combined together to double their size. If you change one of the singular registers value then it will modify the pair register. If you change the pair register value it will change both the singular register value.

The best way to emulate this (assuming you are using c++ or c) is with unions. A union is like a structure but the difference is each of its elements share the same memory space. This is perfect for what you need to emulate the reigsters.




union Register
{
unsigned short reg ;
struct
{
unsigned char lo ;
unsigned char hi ;
};
};

Register AF;



The unsigned short vairable labelled "reg" takes up 2 bytes of memory (which is exactly the same as the pair register) and each of the "lo" and "hi" variables take up 1 byte of memory, however this memory is shared with the "reg" variable.

This means if you change the value of "reg" it will also change the values of both "hi" and "lo". However if you change the value of "hi" it wont change the value of "lo" but it will change the value of the hi byte in "reg".

This way whenever you need to work with register A you can refer to it as AF.hi. Whenever you want to work with register F you can refer to it as AF.lo and whenever you want to work with pair register AF you can refer to it as AF.reg

This is the best way (imo) to emulate the Z80 registers.

Good luck and have fun

ShizZy
February 17th, 2009, 00:05
So it's been a while since I have posted here. Basically, I found out that a software engineering course I am taking next term was java based, and I had never written a line of java before in my life. So I decided to write a quick GB emu in it to pick up the language. Here's about 2 days worth of work:

http://img156.imageshack.us/img156/6444/ineedalifegl0.th.jpg (http://img156.imageshack.us/my.php?image=ineedalifegl0.jpg)

I'm hoping to give it another day or two and at least get a few commercial games working. Sadly, I think I've learned all the Java I can from this project, and the rest is just tedious debugging :-(

(Oh and... after having to 2's complement signed values from mem by hand its safe to say I hate Java)

Cyberman
February 17th, 2009, 01:48
I've noticed a lot of Comp Sci programs have been over emphasizing Java I've noticed. It's rather pathetic since this is mostly due to sheer laziness on the teachers part. They stopped teaching many 'older' languages in favor of 'newer' languages. This is quite flawed especially since the majority of computers today aren't PC's and aren't part of a internet based system. Java may be seen on some platforms with Jazel acceleration or other forms but in general it's not in more common things such as joysticks USB drivers and such (yes those are all computers these days).

Erstwhile looks like you've made progress for 2 days. So .. you don't like Java I take it? Well if anything being able to use it is important for some things. I prefer ye olde C/C++ (although I don't like windows programming admitedly). I'm use to building something from the ground up (like a game boy advance game would be). Having a bios is a bit novel to me even.

So back to the game boy. What do you plan to do with the final results? I wonder if one could make a web based emulator that stored game scores etc in a sort of competition type thing. IE GB tomb raider game etc. :D

Cyb

ShizZy
February 21st, 2009, 20:36
I agree... And yes, not a fan of Java haha. I made a few fixes with Big Scroller Demo but am not too motivated to mess around with it too much more... I'll post the source if anyone wants to take a look, Java is soo sloppy haha. Who knows tho.. just really need to work on my other projects :)

Stig
February 24th, 2009, 01:09
Hi I'm developing a Gameboy Emulator and I've started doing a Blog to log all the work. I just wanted to share in case is useful to anyone.

As I read this post I saw a couple of questions that might be answered in my blog. So I'd like to help if possible.

I have some work done already, but I've only created the blog today. I've already posted 5 topics and expect to be very active adding details of the rest that has been done and then I'll continue with regular work logs as I improve the emulator.

It's important to know that this is the first emulator I've ever started from scratch, so many things could be done in better ways. I think, however, that the information could help anyone who is just starting, so they have a place to start and to look for those special details that nobody talks about that keeps you stucked for a while when starting.

The link is: http://emulearn.blogspot.com/

Thanks for reading,

Stig.

CodeSlinger
February 24th, 2009, 08:47
Hi Stig,

I agree that it is incredibly difficult for a newcomer to get to grips with emulator programming. I also had the idea of starting writing development documentation for specific consoles to help with the learning curve.

My site can be found here http://www.codeslinger.co.uk and shows how to emulate the chip8, gameboy and sega master system.

It also goes through sound emulation for the master system. The site isnt finished yet, but shouldnt be too far off. I am by no means an emulation wizard and my site will also have information that could be improved on. Infact as you read the tutorials it can be easily seen how much I have learnt from when I wrote my first one (chip8) to the last (master system).

Hopefully both our sites can help in the emu scene.

Stig
February 24th, 2009, 17:22
Hi CodeSlinger,

Thanks for sharing! It would have been awesome to have that like two weeks ago!! But nevertheless it will help me out with the stuff I still have left to do. Like the sound emulation.. I've never done any program to create sound so I really have no clue as how to do that, yet. But well.. I didn't knew a lot of things a while ago and they are working like a charm now :).

Anyway.. I gave quick look and it seems quite good. A lot more organized than my blog, probably because I didn't want to use my time setting up a site and wanted to start writting right away. I will look at it in more detail other day as I have my final presentation and report for my work as summer student tomorrow (I'm from Chile so it's summer now).

Bye!

olejl
June 5th, 2009, 05:22
I'm also writing a Gamboy emulator. Manly to learn about emulators and programming. For now I have only one question.

http://gbx-emu.googlecode.com/svn/wiki/bug.png

This is from a PD ROM called test.pd. I have compared my output from the output of other emulators, and there is a difference when running the Text Demo. In text line 8 and 9, my emulator shows the numbers 234 and 490. Other emulators show 234 on both lines. Does anyone know what could cause that? Is there a description of this ROM anywhere, which explains what it is testing?

ShizZy
June 9th, 2009, 03:13
CPU bug........ check your instructions.

olejl
June 9th, 2009, 09:51
It was a bug in my ADC A,n routine. I was updating the C flag before adding it :blush:

srastol
June 28th, 2009, 15:41
Hey guys, having some trouble here with DAA opcode. The point is, what's the problem with doing something like,

F &= NF;
F |= ( A > 99 )? CF : 0;

if( !( F & NF ) ) // Positive
A = ( ( A / 10 ) % 10 ) << 4 | ( A % 10 );
else
// ...

F |= ( A == 0 )? ZF : 0;

Optimization discussions apart (divisions do not come for free), this seems to do the trick. None of the pieces of code I've seen along this thread seem to work properly at all. For instance, let's take aprentice's one for the case of A being +16 (0001 0000). Here "(regA&0x0F)>9" does not hold, so "regA+=0x06" does not apply. Similarly, "((regA&0xF0)>0x90)" does not hold, so at the end A keeps being 16 (0001 0000), that is, the BCD result turns to be 10, which is obviously wrong.

I'm still missing what the role of H flag is in this process. All implementations for this opcode seem to have it into account, but I cannot understand why. Even more, what would be the result of the process if flag C were set? Would you mind exposing the whole process to the details here?

By the way, does anyone have a copy of the official LR35902 (GBx CPU) datasheet? It seems to have disappeared from the net.. I cannot find a working link on the net..

Thanks!

icepir8
June 29th, 2009, 06:46
Hey guys, having some trouble here with DAA opcode. The point is, what's the problem with doing something like,


The DAA opcode can be hard to understand.


Optimization discussions apart (divisions do not come for free), this seems to do the trick. None of the pieces of code I've seen along this thread seem to work properly at all. For instance, let's take aprentice's one for the case of A being +16 (0001 0000). Here "(regA&0x0F)>9" does not hold, so "regA+=0x06" does not apply. Similarly, "((regA&0xF0)>0x90)" does not hold, so at the end A keeps being 16 (0001 0000), that is, the BCD result turns to be 10, which is obviously wrong.

I'm still missing what the role of H flag is in this process. All implementations for this opcode seem to have it into account, but I cannot understand why. Even more, what would be the result of the process if flag C were set? Would you mind exposing the whole process to the details here?


The H flag is the half-carry set/cleared by Addtion or Subtraction operations on the A register.

example: adding 0x09 to 0x06 results in a value of 0x0f and the H flag is clear.
and adding 0x09 to 0x07 results in a value of 0x10 and the H flag set.

Now the DAA operation looks at the H flag and if it is set adds 0x06 to the value.
so the final result is 0x16 instead of 0x10.

I hope this helps.

Cheers!
Icepir8

srastol
June 29th, 2009, 11:06
example: adding 0x09 to 0x06 results in a value of 0x0f and the H flag is clear.
and adding 0x09 to 0x07 results in a value of 0x10 and the H flag set.

Now the DAA operation looks at the H flag and if it is set adds 0x06 to the value.
so the final result is 0x16 instead of 0x10.

Now, if I just load a value into A and immediately after request a DAA to turn it into BCD, being that H has not been changed with the load, would I get proper results?

S.

icepir8
June 29th, 2009, 13:08
Now, if I just load a value into A and immediately after request a DAA to turn it into BCD, being that H has not been changed with the load, would I get proper results?

S.

not everytime. only when the value of a is in the range of 0x00 to 0x0f.

the DAA only works correctly after a Addition or Subtraction from A reg.

btw if the C flag is set DAA adds 0x60 to A.

srastol
June 29th, 2009, 13:47
not everytime. only when the value of a is in the range of 0x00 to 0x0f.

the DAA only works correctly after a Addition or Subtraction from A reg.

Huh.. that's weird. If DAA is suposed to turn A into a BCD number, it should do it always, not only under certain conditions. I mean, that's not a serious design..

Just imagine you have a score counter that you must render each frame. You turn that value into BCD, for some reason, and then output it. Now, that value may not change every frame, so for some cases you'll have to do something like,

toBCD( score-1+1 )

to get a proper value. That sounds weird to me, or I'm just missing the purpose of the opcode itself. Is current knowledge about DAA based on official documentation? Or just on test-error testing? Has this behavior been observed and tested on the real thing?

Thanks.

S.

icepir8
June 29th, 2009, 17:31
The DAA operation is not to convert the value in A to BCD but to addjust the results so that you can do BCD math. If you didn'r have the DAA opcode you would have to do the math 4bits at a time.

srastol
June 29th, 2009, 18:21
The DAA operation is not to convert the value in A to BCD but to addjust the results so that you can do BCD math. If you didn'r have the DAA opcode you would have to do the math 4bits at a time.

Seen this way it makes sense. I've seen this same observation on a z80 tech site a few minutes ago, so I guess the doubt's been cleared.

Thanks dude!

icepir8
June 29th, 2009, 18:59
No Problem


Detailed info DAA
Instruction Format:
OPCODE CYCLES
--------------------------------
27h 4


Description:
This instruction conditionally adjusts the accumulator for BCD addition
and subtraction operations. For addition (ADD, ADC, INC) or subtraction
(SUB, SBC, DEC, NEC), the following table indicates the operation performed:

--------------------------------------------------------------------------------
| | C Flag | HEX value in | H Flag | HEX value in | Number | C flag|
| Operation | Before | upper digit | Before | lower digit | added | After |
| | DAA | (bit 7-4) | DAA | (bit 3-0) | to byte | DAA |
|------------------------------------------------------------------------------|
| | 0 | 0-9 | 0 | 0-9 | 00 | 0 |
| ADD | 0 | 0-8 | 0 | A-F | 06 | 0 |
| | 0 | 0-9 | 1 | 0-3 | 06 | 0 |
| ADC | 0 | A-F | 0 | 0-9 | 60 | 1 |
| | 0 | 9-F | 0 | A-F | 66 | 1 |
| INC | 0 | A-F | 1 | 0-3 | 66 | 1 |
| | 1 | 0-2 | 0 | 0-9 | 60 | 1 |
| | 1 | 0-2 | 0 | A-F | 66 | 1 |
| | 1 | 0-3 | 1 | 0-3 | 66 | 1 |
|------------------------------------------------------------------------------|
| SUB | 0 | 0-9 | 0 | 0-9 | 00 | 0 |
| SBC | 0 | 0-8 | 1 | 6-F | FA | 0 |
| DEC | 1 | 7-F | 0 | 0-9 | A0 | 1 |
| NEG | 1 | 6-F | 1 | 6-F | 9A | 1 |
|------------------------------------------------------------------------------|


Flags:
C: See instruction.
N: Unaffected.
P/V: Set if Acc. is even parity after operation, reset otherwise.
H: See instruction.
Z: Set if Acc. is Zero after operation, reset otherwise.
S: Set if most significant bit of Acc. is 1 after operation, reset otherwise.

Example:

If an addition operation is performed between 15 (BCD) and 27 (BCD), simple decimal
arithmetic gives this result:

15
+27
----
42

But when the binary representations are added in the Accumulator according to
standard binary arithmetic:

0001 0101 15
+0010 0111 27
---------------
0011 1100 3C

The sum is ambiguous. The DAA instruction adjusts this result so that correct
BCD representation is obtained:

0011 1100 3C result
+0000 0110 06 +error
---------------
0100 0010 42 Correct BCD!

initech
July 2nd, 2009, 15:19
Quick question about the GameBoy CPU manual. I just want to confirm that the "#" parameter is a signed immediate byte. Is that correct?

Also, for operations like ADD which accept a signed value, should the carry flags be set on both borrow and overflow conditions?

I'm currently writing an emulator in C. I have the CPU almost completed (except those signed operations, which will really make things messy). I have cobbled together an interrupt system for the three main interrupts (VBLANK, TIMR and DIV). Most of the ROMs I run through it spin on the higher registers while waiting for the display to become ready (which I haven't programmed yet).

icepir8
July 2nd, 2009, 15:55
The parity/overflow flag - which has two uses; the parity part we'll see later. In dealing with addition and subtraction an overflow occurs if an operation causes a result larger than can be represented in twos complement, i.e. something less than -128 or more than 127. So 127 + 1 or -128 - 1 would both cause overflows.

In other word if the carry out from bit 7 is not the same as the carry into 7 then the overflow flag is set.

I hope that this helps you understand what the overflow flag does.

initech
July 5th, 2009, 13:44
Thanks icepir8.

I spent a while debugging today only to find out that when a word is pushed to the stack, the most significant byte must be pushed before the least significant. I was doing it the other way around which caused problems. After figuring that out my emulator is showing signs of life:

http://i43.tinypic.com/qz28f8.gif

What ROMs do you suggest for testing and debugging? I am using Tetris, the big scroller, and the PD test ROM.

srastol
July 5th, 2009, 14:44
Hey icepir8

I may be missing something, but I think aprentice's code is still not doing right. Just look at this case, being all flags clear and A=0x0A. According to the table you posted, "numer added to byte" should be 0x06, but for aprentice's code,


void op0x27() //DAA
{
if(regF&0x40) //Negative Flag Set
{
if((regA&0x0F)>0x09 || regF&0x20)
{
regA-=0x06; //Half borrow: (0-1) = (0xF-0x6) = 9
if((regA&0xF0)==0xF0) regF|=0x10; else regF&=~0x10;
}

if((regA&0xF0)>0x90 || regF&0x10) regA-=0x60;
}
else
{
if((regA&0x0F)>9 || regF&0x20)
{
regA+=0x06; //Half carry: (9+1) = (0xA+0x6) = 10
if((regA&0xF0)==0) regF|=0x10; else regF&=~0x10;
}

if((regA&0xF0)>0x90 || regF&0x10) regA+=0x60;
}

if(regA==0) regF|=0x80; else regF&=~0x80;
}

it turns to be 0x66. The problem I see on his code is that flags are being updated while results are still being computed. As far as I understand the opcode, flags should be updated after the amount to be added/substracted has been dedided.

Just wanted to know what you think about that.

By the way, I've got absolutely nothing against aprentice :P I'm just testing his code cause it seems several of you are using it in your emus.

Thanks again.

S.

icepir8
July 8th, 2009, 18:30
The code looks correct.


Hey icepir8

I may be missing something, but I think aprentice's code is still not doing right. Just look at this case, being all flags clear and A=0x0A. According to the table you posted, "numer added to byte" should be 0x06, but for aprentice's code,


void op0x27() //DAA
{
if(regF&0x40) //Negative Flag Set
{
if((regA&0x0F)>0x09 || regF&0x20)
{
regA-=0x06; //Half borrow: (0-1) = (0xF-0x6) = 9
if((regA&0xF0)==0xF0) regF|=0x10; else regF&=~0x10;
}

if((regA&0xF0)>0x90 || regF&0x10) regA-=0x60;
}
else
{
if((regA&0x0F)>9 || regF&0x20) // regA&0x0F == 0x0A so this is true
{
regA+=0x06; //Half carry: (9+1) = (0xA+0x6) = 10 // regA == 0x10 now
if((regA&0xF0)==0) regF|=0x10; else regF&=~0x10; // regA & 0xF0 == 0x10 now so false and carry flag is cleared.
}

if((regA&0xF0)>0x90 || regF&0x10) regA+=0x60; // both conditions false so do not add 0x60 to regA.
}

if(regA==0) regF|=0x80; else regF&=~0x80;
}

it turns to be 0x66. The problem I see on his code is that flags are being updated while results are still being computed. As far as I understand the opcode, flags should be updated after the amount to be added/substracted has been dedided.

Just wanted to know what you think about that.

By the way, I've got absolutely nothing against aprentice :P I'm just testing his code cause it seems several of you are using it in your emus.

Thanks again.

S.

I hope that makes sense to you. If this is not happening then look for a type in the code you are compiling.

btw I think there may be a problem with clearing the carry flag during the opperation.

srastol
July 11th, 2009, 00:59
If this is not happening then look for a type in the code you are compiling.

Uhm, you're right, got a typo in aprentice's code when copying it into my workspace. That's my fault. But now I've got a new question regarding input value 0x9A. If you don't mind, let me expose my point to the details, and see if we can find where the problem is. Let's have A register hold value 0x9A (regA = 0x9A), all flags being clear (regF = 0x00). According to the table "Detailed info DAA" (post #1072), our case is the 5th listed,


--------------------------------------------------------------------------------
| | C Flag | HEX value in | H Flag | HEX value in | Number | C flag|
| Operation | Before | upper digit | Before | lower digit | added | After |
| | DAA | (bit 7-4) | DAA | (bit 3-0) | to byte | DAA |
|------------------------------------------------------------------------------|
| | 0 | 0-9 | 0 | 0-9 | 00 | 0 |
| ADD | 0 | 0-8 | 0 | A-F | 06 | 0 |
| | 0 | 0-9 | 1 | 0-3 | 06 | 0 |
| ADC | 0 | A-F | 0 | 0-9 | 60 | 1 |
| | 0 | 9-F | 0 | A-F | 66 | 1 | <-- our case

Let's put some numbers to aprentice's code,


00: void op0x27() //DAA
01: {
02: if(regF&0x40) //Negative Flag Set
03: {
04: if((regA&0x0F)>0x09 || regF&0x20)
05: {
06: regA-=0x06; //Half borrow: (0-1) = (0xF-0x6) = 9
07: if((regA&0xF0)==0xF0) regF|=0x10; else regF&=~0x10;
08: }
09:
10: if((regA&0xF0)>0x90 || regF&0x10) regA-=0x60;
11: }
12: else
13: {
14: if((regA&0x0F)>9 || regF&0x20)
15: {
16: regA+=0x06; //Half carry: (9+1) = (0xA+0x6) = 10
17: if((regA&0xF0)==0) regF|=0x10; else regF&=~0x10;
18: }
19:
20: if((regA&0xF0)>0x90 || regF&0x10) regA+=0x60;
21: }
22:
23: if(regA==0) regF|=0x80; else regF&=~0x80;
24: }

Now, the logic, as I see it, flows like this:

1.- Line 02 condition is not satisfied, our number is positive. Jump to line 14.
2.- "(regA&0x0F)>9" is satisfied. Move to 16.
3.- 0x06 is added to regA, regA turns into 0xA0. Move to 17.
4.- Condition is not satisfied, carry C flag is cleared, regF keeps being 0x00. Move to line 20.
5.- Condition holds, 0x60 is added to regA, regA turns into 0x00 (0x100 actually). Move to line 23.
6.- Condition holds, zero Z flag is set, regF turns into 0x80. End of function.

See the problem? At the end we've added 0x66 to regA, that's correct, but at step 5 carry C flag has not been set on regA's overflow. Back to the table, our case is expected to set C flag. So again, I may be too tired to spot where my fault is (it's been a long day), or actually a bug in the code.

I just hope me not being too much a pain for you, heh..

S.

icepir8
July 12th, 2009, 04:57
I made a few changes to the code I believe this is correct now.



#defing CarryMask 0x10
#define HalfCarryMask 0x20
#define NegFlagMask 0x40

00: void op0x27() //DAA
01: {
02: if(regF & NegFlagMask) //Negative Flag Set
03: {
04: if((regA & 0x0F)>0x09 || regF & HalfCarryMask)
05: {
06: regA-=0x06; //Half borrow: (0-1) = (0xF-0x6) = 9
07: if((regA & 0xF0) == 0xF0) regF |= CarryMask;
// We don't clear the carry/barrow flag in the DAA
//else regF &= ~CarryMask;
08: }
09:
10: if((regA & 0xF0) > 0x90 || regF & CarryMask)
{
regA-=0x60;
regF |= CarryMask;
}
11: }
12: else
13: {
14: if((regA & 0x0F)>9 || regF & HalfCarryMask)
15: {
16: regA += 0x06; //Half carry: (9+1) = (0xA+0x6) = 10
17: // never happens in BCD math
//if((regA & 0xF0)==0) regF |= CarryMask;
// We don't clear the carry/barrow flag in the DAA
//else regF &= ~CarryMask;
18: }
19:
20: if((regA&0xF0)>0x90 || regF & 0x10)
{
regA+=0x60;
regF |= CarryMask;
}
21: }
22:
23: if(regA==0) regF|=0x80; else regF&=~0x80;
24: }

fenixagua
August 14th, 2009, 16:54
Hi, guys!

I am developing an emulator for Game Boy, but I have no information on the HuC1, HuC3, MBC4, MBC5, MBC7, among others, and the pan doc not have and did not think so.

Could you help me?

Exophase
August 14th, 2009, 19:18
You must be pretty far along to be interested in such things.

Here's an MBC5 document: http://www.ziegler.desaign.de/cgbmbc5.pdf
HuC1 is allegedly MBC1 with infrared support. Unless you plan on emulating that you shouldn't have to do anything special for it.

I can't find information on the other mappers, so you probably want to look at source of emulators like Gambatte to get an idea. I imagine that they have core functionality that's similar or identical to one of the other mappers. MBC4 might just be an MBC5 that doesn't support double speed mode.

fenixagua
August 15th, 2009, 15:11
You must be pretty far along to be interested in such things.
Oh, yes, almost all the opcodes listed in GB Cribsheet are made and I have done the basics with the timers and special registers (LY, etc.). And runs games smoothly MBC1.

Here's an MBC5 document: http://www.ziegler.desaign.de/cgbmbc5.pdf
HuC1 is allegedly MBC1 with infrared support. Unless you plan on emulating that you shouldn't have to do anything special for it.

Thank you for this information!



Know how it is done to activate the infrared HuC1?

Exophase
August 17th, 2009, 22:14
Hey fenixagua, congratulations on making what must be a pretty complete GB emulator by now. How is sound working? A lot of people doing GB emulators seem to have difficulty with sound, at least relative to everything else.

I wish I could tell you more about the obscure mappers, but unfortunately the best I could do would be to personally dig through what open source emulators there are and try to glean that info. I'll leave that task to you, since I probably wouldn't be able to find anything better.

MicBeaudoin
December 30th, 2009, 03:48
Hello,

I'm making a GB emulator right now, but there is something that bugs me. Knowing that the GB cpu runs at 4 Mhz, shouldn't it run 4 millions cycles per second?

In the docs, it's said that 4096 cycles should be run each second(not sure about this):

4. FF04 (DIV)
Name - DIV
Contents - Divider Register (R/W)
This register is incremented 16384(16384/4=4096)
(~16779 on SGB) times a second. Writing
any value sets it to $00.

The DIV register is the cycle count right? So let's say I run the NOP opcode, the DIV reg should be +=4?

I'll give you screenshots when it'll be working, I just need to make a GUI using Qt and the code of the rendering part and it should work(for cartridges without MBC of course)

Thanks

MicBeaudoin
January 3rd, 2010, 09:16
Nevermind, I just read the whole topic and my questions were already answered. I'll send screenshots here when it's showing some pixels.

MicBeaudoin
January 13th, 2010, 07:22
Hello,

Here are some screenshots of my emu running some demos(scroller and "test")


http://bayimg.com/iaJLaaaCk
http://bayimg.com/hajlnAaCk


Unfortunately, Tetris is not working because I'm stuck into an infinite loop:

opcode_0x20()
opcode_0xF0()
opcode_0xFE()

It looks like it's looking for a particular state of the LY register, even though it is incremented as stated in the docs.
All I get is a blank screen with a small black line.
http://bayimg.com/iaJlFAAcK

MicBeaudoin
April 4th, 2010, 16:02
Here is an update of my emu running Dr Mario with messed up sprites(but playable!)

http://img109.imageshack.us/img109/5171/drmario.png

Tetris menus work too, but the rom restarts just before the playing screen :sombrero:

CHR15x94
August 4th, 2010, 03:47
Hey there. Sure is quiet around here...

Well, I started my GameBoy emulator a few months ago, and worked on it for a bit, then took a break from it for a while. About a week ago I started working on it again and have ironed out some bugs. Unfortunately, I still have quite a few issues to sort out.

My emulator is done in SDL, and made in the Code::Blocks (10.05) IDE, compiled using MinGW.

Here's a few screenshots of my emulator running a few ROMs (commercial and PD).

**SNIP**

Also, thanks to CodeSlinger. I'm currently using his tile drawing code and it seems to work quite well. :satisfied My drawing code never worked 100%, so I used his to help my emulator progress a bit faster. Once I've debugged my emulator some more, I'll write my own drawing routines.

Thanks, and any suggestions, questions or whatever are welcome. :D


EDIT: Worked on my emulator's MBC1 and MBC3 code, as well as fixed JoyPad emulation. Tetris worked after fixing JoyPad code (goes to menu and in game, no graphics errors other than no sprites (not implemented)). Super Mario Land works 100%, (again, other than no sprites), Super Mario Land 2 goes to menu and file/save select screen but crashes when the save is loaded. Pokemon Red/Blue display distorted graphics for the Pokemon sprites/tiles (on the title screen, etc) and crashes upon going in game and crashes if you don't mash the buttons at the cutscene at the start. On the other hand, Pokemon Silver/Gold seem to play 100% (haven't tested everything yet though). Kirby's Dream Land plays alright other than Kirby falls through the floor (I assume anyways, can't see Kirby, no sprites). Boxxle also seems to work fine, other than the odd graphics error or two. Link's Awakening also seems to work OK, but I think the ROM crashes upon leaving the first house. Altered Space hangs at the copyright screen, Prehistorik Man displays garbage which is messed around by some SCX/SCY effect, then crashes.

Anyways, I'm going to finish up what's left of MBC1 and MBC3, as well as add MBC2 support, and add a rough implementation of sprites, then I'll release the source of my emulator, as well as a build of it (Windows only).


EDIT 2: Here it is. My emulator and it's source code, all in one ZIP file. And yes, I know my emulator isn't that great. I probably won't be working on it for too much longer either (hopefully). This release includes some changes:


Fixed some bugs in rotate instructions (fixes graphics errors in Pokemon Red/Blue, and allows it to go ingame, fixes a bug that stops the user from choosing a starter Pokemon in Pokemon Silver, fixes some graphics errors in Wave Race, fixes collisions in Kirby's Dreamland)
Added basic sprite support. (Basic drawing, X/Y axis flipping. No priorities, etc.)
Finished MBC1 and MBC2 support. MBC3 support is OK as well, but no RTC emulation is present.


And still TODO...:

Finish sprite emulation
Emulate other MBCs.
Emulate STOP, HALT and DAA instructions.
Fix remaining CPU bugs (timing, instruction, etc.)
Sound


If you decide to take a peek at my emulator, I'd appreciate it if you could take a look over my source code. I know, it's a mess, but I'm running out of places to look for bugs. I know I'm missing some important instructions (as mentioned above) and I need to finish filling in the cycle count table that tracks how many cycles pass for each opcode. If you notice anything that isn't those, please tell me about them. Thanks. :)

I'll probably release a newer build in a few days if I fix anything.

Current download:
http://host-a.net/Chris94/newBoy.zip/link.png (http://host-a.net/Chris94/newBoy.zip)

I also noticed something with sprite drawing. The PanDocs say sprites are drawn at the Y position specified in the OAM - 16. This doesn't work for the games I tried (Super Mario Land 1/2, Pokemon, etc), so instead, I draw them at Y - Y axis size (in the LCDC register). Most of the more complex games still don't draw correctly, like Kirby and Zelda (sprites are chopped and mashed, often drawn in the wrong position), so I'm still not sure if this is the way to do it or not.

- CHR15x94

keithodulaigh
September 3rd, 2010, 17:01
Hello!

Can someone explain to me how video works on the GB? I've read the "Pan document" but it has left me a bit confused...

He says the background tile map contains numbers of the tiles to be displayed and those tiles are located in the tile map at either 0x8000-8fff or 0x8800-97ff. So where on the memory map is the background tile map located?

My interpretation is that the tile map is like a "spreadsheet of tiles" and the background tile map chooses which tiles to pick from the "spreadsheet" (i.e. the numbers of the tiles).

Thanks.

keithodulaigh
September 7th, 2010, 15:19
Okay I found the answer to my question.

The answer depends on the value set in the LCDC register (i.e. 0xFF40)

Bit 6 determines which address the window tile numbers are at. (These are just tile numbers, not the tile data.)

Bit 3 detemines which address the background tile number are at. (again just tile numbers, not data.)

The tile numbers are like indices for an array. You use the index to look up the data. The address of where the data lies is determined by bit 3 of the LCDC register.

For more detailed information refer to page 52 of the "Gameboy CPU manual" by Pan et al.

tinmanjo
September 7th, 2010, 21:27
Hi, im going through my CPU code and something which I cant seem to get right is the Xor,Or,And etc..

for instance AF: Xor a, what is 'a' refering to.

Does this refer to RegA, RegAF so iss this correct :-

RegA=RegA Xor RegA.

Basically the tetris game I load it and get to this op, the AF=1B0, which A=1 and F=B0.

But when I debugged the same in N$gb and step AF opcode, RegAF becomes 80, how come because RegA being = 1, xor (1,1) =0.

Im confused.:(

Oldrey
September 19th, 2010, 11:25
Hi, I'm writing an emulator and I have seen different implementations of some parts of the game boy emulation so I have some questions:

1. In MBC2, when writing to address < 0x2000, the rom is trying to enable/disable external ram memory access. What I don't know is how does it work exactly: is it like MBC1, where (address & 0xF == 0xA) enables and otherwise disables, or do I have to toggle the ram enabled status (ram_enabled = !ram_enabled)? Also, in the pandocs.htm it says that bit 8 must be zero, but I have seen some emulators where they don't do this check, and some that even check that the bit is set! What's the right way of doing it?

2. In MBC2, when writing to address >= 0x2000 and address < 0x2000, the rom is trying to change the rom bank with the lower nibble of the data. I would like to know what is the behaviour if the lower nibble is zero. Do I have to change the nibble to 1 (like if the 5 lower bits are 0 in MBC1, so banks 0x00, 0x20, 0x40, 0x60 can't be used) or do I have to select bank 0, which is already in address >= 0x0000 && address <= 0x4000? Also, the pandocs.htm says that bit 8 must be 1, but again there are emulators where they ignore it or do it the other way. Which one is the correct one?

3. I'm not sure what the correct behaviour would be when ram is disabled. When the rom tries to write, should I ignore it? What does the original Game Boy do here? Also, when reading what should I return? Should I return the byte at the requested address even if ram is disabled?

4. When the rom changes rom/ram banks, should I check that they don't go out of range? And if so, what should I set them to if they are greater that the maximum number of banks?

5. The zero flag is set when the result of an arithmetic operation is 0. But suppose I'm doing an 8 bit add, for example 128 + 128. Here, the result will be 256, but when I store it in the destination register, it will be 0 (and carry flag would be set). In this case, should I set the zero flag? If not, can't the zero flag be reset unconditionally in this case, since I'm adding 2 unsigned values?

6. In the LHDL SP,n instruction, pandocs.htm says I have to set carry and half-carry flag accordingly. I'm doing an addition (sp + n) so I should set the carry flag if the result is > 0xFFFF. But since n is signed, the result could be < 0x0000. Should I check for this and set the carry flag if this condition is true? If so, I guess I should do the same for the half-carry flag? Also, I've seen here some emulators looking at the lower nibble to check for half-carry, but since sp is a 16bit word, shouldn't I be checking for the lower byte?

Thanks for your help

Edit: n is signed, not unsigned, sorry

pradipna
July 16th, 2011, 16:21
Quite here isn't it? lol

Anyway I started making my GB emulator some days ago and after some extensive coding and debugging the emulator finally shows some thing:

http://i2.photobucket.com/albums/y6/pradipna/ohBoy.jpg

That is the only ROM that works because I haven't handle any interrupts yet. :P Other ROMs just give white screen or show rubbish. I have so far have taken care of all opcodes (maybe some bugs, probably lol), taken care of STAT and LY registers, made a debug dumper (which basically dumps all the debug code to one big log file :P), made the frontend and yeah that's all. I should be able to finish interrupts soon and see if other roms work or not.

pradipna
July 19th, 2011, 04:20
Finally, I emulated VBlank Interrupt and P1 JoyPad register and first commercial game shows something. It's Tetris:

http://i2.photobucket.com/albums/y6/pradipna/ohboy2.jpg
http://i2.photobucket.com/albums/y6/pradipna/ohboy3.jpg

But if I try to play the game, it goes to High Score. LOL Here are screens:

http://i2.photobucket.com/albums/y6/pradipna/ohboy4.jpg
http://i2.photobucket.com/albums/y6/pradipna/ohboy5.jpg

Hmmm... maybe because I haven't taken care of Timer and DIV register...

pradipna
July 25th, 2011, 18:57
Thought I would post a quick update on my emu. Well, most of the 32 KB ROMs are now playable. I haven't implemented MBCs yet, and also 8*16 sprite is yet to be taken care of. I had thought of implementing MBCs after I am able to load all 32 KB ROMs perfectly but I am getting bored of bug hunting and maybe will implement MBCs soon (Hopefully Legends of Zelda: Link's Awakening will be playable soon. ;) ). Ok, here are some latest screenshots of my emu:

Tetris:

This game is now fully playable. :)

http://i2.photobucket.com/albums/y6/pradipna/OhBoy-Tetris.jpg

Amida:

This game is also fully playable.

http://i2.photobucket.com/albums/y6/pradipna/OhBoy-Amida1.jpg
http://i2.photobucket.com/albums/y6/pradipna/OhBoy-Amida2.jpg
http://i2.photobucket.com/albums/y6/pradipna/OhBoy-Amida3.jpg

Boxxle:

Another fully playable game. :P

http://i2.photobucket.com/albums/y6/pradipna/OhBoy-Boxxle1.jpg
http://i2.photobucket.com/albums/y6/pradipna/OhBoy-Boxxle2.jpg

Asteriods:

The title image is scrambled. :getlost:

http://i2.photobucket.com/albums/y6/pradipna/OhBoy-Asteroids1.jpg

But playable nevertheless.

http://i2.photobucket.com/albums/y6/pradipna/OhBoy-Asteroids2.jpg

Motocross Maniacs:

This game has the most weird problem of all. It loads up just fine. But when I try to play it, after 2 seconds or so it says 'Time Up'. Maybe the timer implementation has some bugs? I have see.

http://i2.photobucket.com/albums/y6/pradipna/OhBoy-MotocrossManiacs1.jpg
http://i2.photobucket.com/albums/y6/pradipna/OhBoy-MotocrossManiacs2.jpg

Castelian:

This game is not playable. :getlost: It loads up the title fine.

http://i2.photobucket.com/albums/y6/pradipna/OhBoy-Castelian1.jpg

But inside the game, it's just a mess.

http://i2.photobucket.com/albums/y6/pradipna/OhBoy-Castelian2.jpg

MelvoPR
July 31st, 2011, 21:50
About the DMA Register...

I've seen some emulators that use a function between every access to memory with the purpose of intercepting where the GB wants to write to, that is, monitor when the register 0xFF46 wants to be written to.
This results in a significant overhead, due to every write, it needs to access a function. This is not necessary, you can initialize the DMA to an invalid value, that is, 0xF1 < DMA <= 0xFF. Now, when DMA changes value, it is obvious that the GB has written to it.

In other words, when DMA changes from a value less than 0xF2, we need to copy from memory[memory[0xFF46] << 8] to memory[0xFE00] (OAM) 0xA0 bytes.
As this function is supposed to take approximately 160 microseconds, we need to check for this register every ~672 cycles, assuming clock is 4.194304 MHz GB.

Implementation of above:

On initialization function:


BYTE oldDMA = 0xFF; // invalid value
mem[0xFF46] = 0xFF; // invalid value

DMA Register Check:


if(oldDMA != mem[0xFF46])
{
oldDMA = mem[0xFF46] = 0xFF; // reset DMA reg again
memcpy(&mem[0xFE00], &mem[(mem[0xFF46] << 8)], 0xA0); // DMA Transfer (672 cycles aprox.)
}
Note: DMA is not supposed to be read, that is, GB cannot read contents of location 0xFF46, so, this is safe to do.

pradipna
August 1st, 2011, 08:04
Write function does need to have some overhead, you need to check if it wants to write to ROM bank registers, RAM bank registers, LY, STAT, P1, DIV, Echo etc etc.

MelvoPR
August 1st, 2011, 22:29
If you read my post again, you will notice I'm refering to intercepting every write that the cpu performs.

Example:
Opcode 0x77: Write contents of A to location in memory[(H << 8 )+ L]:
Now, you don't need an intermediary function for writing to a location in memory:

void WriteToMem(ushort location)
{
// let's check where it wants to write:
if(location == 0xFF44) // it wants to write to LY
{
// do stuff to handle a LY write
} else if(location == 0xFF41) // it wants to write to STAT
{
// do stuff to handle a STAT write
}
// and so on, you get the idea....
}
This would create unnecesary overhead to each write and/or read performed.
You could simply write directly to a location, and then have a function that updates all necessary registers after a set number of cycles...

Hope you understand now. :)

Now, this doesn't really matter that much (Also, you'll not gain a lot of speed), I mean, most GB emu's are done as a learning experience, and don't care or focus about speed, so, this is just something to have in mind for future projects, since I've seen this on some emulators.

pradipna
August 2nd, 2011, 04:23
Yeah I understand what you say. :) But like you said, it won't have much performance impact. Also some register like, let's say LY register, needs to be reset as soon as it is written to, so waiting for some cycles and then checking won't be an option. Same goes for ROM and RAM bank registers, and most others.

Are you making an emulator too? Or have you already made one? I am doing lot's of debugging right now to increase the compatibility of my emu.

MelvoPR
August 2nd, 2011, 05:54
As far as emulators are concerned, I've only done CHIP8 and Space Invaders interpreter(similar CPU).
Then moved on to implementing a dynarec, since I've too, made them for the sole purpose of learning.

When you finish your GB emu, you can take the challenge of doing a dynarec too :)
I mean, moving from GB to NES/Master System, you will probably find you'll not learn much from what you already did with GB.
Heck, some people think Master System is easier than GB.


BTW, you can check my x86 Space Invaders dynarec in this thread:
http://www.emutalk.net/threads/52849-Space-Invaders-Recompiler

Exophase gave me nice info for further improvements, and I plan to implement some of them on my next dynarec (dead flag elimination is a must!)

I've made some improvements quickly after I released it, but didn't get to implement the complex features due to being useless effort on doing so on a core which an interpreter can emulate full speed even on very old systems...


Check the thread for further info :)

BTW, Good job getting that far already!

Hope to hear some sound from your emu soon :P

pradipna
August 2nd, 2011, 13:41
Oh yeah the sound. :P I haven't started it yet but I will start after I implement the CGB functions.

And thank you for the dynarec implementation. Exophase knows what he is talking about, his GBA emulator for PSP was the best. I will definitely learn dynarec after I complete this project.
You should start a GB emu with dynamic recompilation. :P That should be fun. :)

MelvoPR
August 3rd, 2011, 19:50
Yup, I agree. Exophase is a pretty good coder, even though, he sometimes do some strange things (Ex: abuse of #define), but what programmer doesn't do some erratic things anyway?

While a dynarec for GB wouldn't be bad, it would be more of the same...
So, I think I'm moving to something different than x86, would like to experiment with other architectures, maybe MIPS.

pradipna
August 4th, 2011, 10:15
MIPS eh? How about PSP emulator then? :P There are non that can play commercial properly at the moment as far as I know.

MelvoPR
August 6th, 2011, 05:37
As far as I know, JPCSP is the most compatible one, and can run a lot of commercial games.
It lacks in speed, obviously.

I don't think it's possible to make a full speed emulator with decent compatibility with

today's computers. Even with the most optimized code, it's just too expensive.


How about PSP emulator then? :P

Nice joke lol, I don't posess enough skills for such a project, not to mention you can't do

it on your own, there are a lot of things on PSP. And even if I could start such a project,

I'm not really interested on it at the moment, given that it's very time consuming...
Yeah, PSP is definitely out of question.

Now, for a real project, it could be a GP32 :)

pradipna
August 6th, 2011, 06:05
Haha.. Yeah, PSP would be very difficult. I've never heard of GP32 before.

Anyway, small update on my GB emu. I have made some progress and now most of the games (non color) work well. I still haven't implemented CGB feature since I am too busy increasing compatibility of my emu which is fairly good at this moment - more than 90%. :)

Here's what I've already done:

1. All CPU opcodes are implemented.
2. Non CGB graphics completed, except some priority issues which I haven't given much focus on.
3. All interrupts except Serial Interrupt has been implemented.
4. All I/O registers except sound registers and serial registers are taken care of.
5. MBC1, MBC2, MBC3 (no RTC yet) and MBC5 has been implemented.
6. Battery feature has been implemented which now means you can save game progress.


Here's a todo list:

1. CGB feature.
2. Real Time Clock (RTC) for MBC3
3. Sound registers
4. SGB feature (not sure if I will bother adding this feature though)


Here are some latest screenshots:

Legend of Zelda: Link's Awakening:

http://i2.photobucket.com/albums/y6/pradipna/OhBoy-Zelda1.jpg
http://i2.photobucket.com/albums/y6/pradipna/OhBoy-Zelda2.jpg

Pokemon Red: :P

http://i2.photobucket.com/albums/y6/pradipna/OhBoy-PokeRed2.jpg
http://i2.photobucket.com/albums/y6/pradipna/OhBoy-PokeRed1.jpg

pradipna
August 7th, 2011, 20:30
Some games are just plain weird. They try to access memory bank that doesn't even exist. For example Final Fantasy Legends 2, when PC is $491A (I guess in ROM bank $E), it tries to write $8e to rom bank register at $4195. But it only has 16 banks...

Finished debugging my emu for today. Every game that I have with me in my computer are now playable in my emu. YAY!!! Now I can focus on CGB feature...

pradipna
August 11th, 2011, 21:30
Finally got the CGB feature implemented. There are some glitches and I am sure my graphics code isn't 100% accurate. I haven't tested a whole lot of color games but out of those I have tested, some don't load at all. I have to check them tomorrow, it's almost 2:30am so I should get some sleep. Here are some latest picture from Legend of Zelda: Link's Awakening DX:

http://i2.photobucket.com/albums/y6/pradipna/OhBoy-Link1.jpg
http://i2.photobucket.com/albums/y6/pradipna/OhBoy-Link2.jpg
http://i2.photobucket.com/albums/y6/pradipna/OhBoy-Link3.jpg
http://i2.photobucket.com/albums/y6/pradipna/OhBoy-Link4.jpg

pradipna
September 13th, 2011, 06:51
Just wanted to say that I have released my emulator as a beta version: http://www.pradsprojects.com/dinoboy.html
There is no sound emulation yet but I will emulate sound in next version...

WingsORC
September 24th, 2011, 16:35
hey guys just wanted to say that i started my GB emulator a few days ago.
Got nearly all CPU instructions by now and im trying to implement the interrupts now.
(its entierly written in java)

pradipna
September 26th, 2011, 05:07
Good luck with your emulator.
I am doing sound emulation right now. Channel 2 is done and it works quite well but has some bugs and channel 1 is almost completed.

MelvoPR
September 27th, 2011, 06:19
Just wanted to say that I have released my emulator as a beta version: http://www.pradsprojects.com/dinoboy.html
There is no sound emulation yet but I will emulate sound in next version...

Hi, nice to see you are still working on your emu!

I've took a quick glance over your code and this is what I've found you could very easily improve:

You should #define special memory locations. (Ex: #define MEM_LY 0x1F44)
So, it looks more readable.

Some sanity check on input.dat won't hurt.

Also, you can extract instruction registers from the opcode itself, so, your code doesn't look so bloated like on main.cpp

that is, to reduce redundancy:
Instead of this:

case 0x34:
SWAP(H);
case 0x35:
SWAP(L);
Write this:

case 0x34: case 0x35: /* etc... */
SWAP(opcode >> offset); // you get the idea...

Also, it is good practice to use ++x instead of x++ when the temporary value is not needed. While this doesn't really matter for default data types, it does for custom data types (ex: objects).

There are other optimizations skipped that have been discussed earlier, but other than that, it looks good!

Keep up the good work :D

pradipna
September 27th, 2011, 08:24
You should #define special memory locations. (Ex: #define MEM_LY 0x1F44)
So, it looks more readable.

Oh yeah, that should definitely be done. I was just being lazy before I guess.


Also, you can extract instruction registers from the opcode itself, so, your code doesn't look so bloated like on main.cpp

that is, to reduce redundancy:
Instead of this:

case 0x34:
SWAP(H);
case 0x35:
SWAP(L);
Write this:

case 0x34: case 0x35: /* etc... */
SWAP(opcode >> offset); // you get the idea...


Ok but wouldn't extracting the register from the opcode have some performance issue. I mean, if I want to extract the registers from opcode of LD r, r', I would have extract the register r by "r = (opcode & 0x38) >> 3;" and then extract register r' by "r' = opcode & 7".
Then I would have point it to register something like this for both:



switch (r) {
case 0:
R = &B;
break;
case 1:
R = &C;

and so on...

}


Or I could put all registers in array instead of using switch case but still I think it will have some performance issue...


Also, it is good practice to use ++x instead of x++ when the temporary value is not needed. While this doesn't really matter for default data types, it does for custom data types (ex: objects).


Since I started programming I have always been using x++ so it kinda of hard to break that habit. *lame excuse :P* But I will try to use ++x more. haha

Thank you for your advice. :)

MelvoPR
September 27th, 2011, 21:40
Yes, you could put registers on an array.
Performance impact would be very minimal (extracting the registers). However, code will be reduced significantly and look cleaner.
(Also, the primary aim of your emu isn't speed, as far as I know.)

Good luck on sound!

pradipna
September 28th, 2011, 05:23
Sound is going well, I have finished emulating channel 1 and 2 and in some games it sounds almost perfect while in others it is very buggy. I am going to emulate channel 3 today...

WingsORC
October 7th, 2011, 01:15
Hello guys, got a question:

i checked out a few gb emulators and saw that writing 0x03 to the LCD control register(FF40) seems to reset LY (Scanline thingy). Didnt find anything in the docs with which i could explaint that.

edit: its in the tetris ROM at PC 239 to 23B

pradipna
October 7th, 2011, 04:06
Yes, writing 0x03 should indeed reset the value of LY register and it should also reset the value of Mode Flag in STAT register too. Actually writing any value with bit 7 turned off should do that since LCD stops working after that until it is restarted again by writing any value equal to or greater than 0x80 to LCDC Register...

ShizZy
October 20th, 2011, 05:17
pradipna: Nice work!
I hope you (and WingsORC) keep your projects up, its nice to see people working on new emulation projects & keeping the programming community active. Looking at this thread brings me back..

pradipna
October 22nd, 2011, 05:02
Thank you Shizzy. :) And Good Luck with your projects that you are currently working on...

WingsORC
October 28th, 2011, 11:07
Thanks too. My emulator shows only a glitchy asteroids intro so far :) but well the progress is steady!

Drenn
October 30th, 2011, 17:28
Hi, I've been slowly plugging away on a gameboy emulator since june. I've made a lot of progress but sound emulation is a very new thing to me. I've emulated the first 2 channels and they sound something like a piano player who always hits the adjacent key... I used SDL_audiospec while following codeslinger's master system sound code and I ended up with this:

updateBufferCount += cycles;
if (updateBufferCount >= updateBufferLimit)
{
if (bufferPosition < BUFFERSIZE)
playbackBuffer[bufferPosition] = tone*3000;

bufferPosition++;
updateBufferCount = updateBufferLimit - updateBufferCount ;
}

where playbackBuffer is copied into the audio buffer in my SDL_audiospec's callback routine, and updateBufferLimit is the interval measured in clock cycles between the times that the tone is stored into playbackBuffer. The value calculated for updateBufferLimit should make playbackBuffer completely filled by the time SDL requests its audio buffer to be refilled, but it tends to vary. When bufferPosition < BUFFERSIZE equals false, the tone doesn't get put into the playbackBuffer since the array has been filled already, and this seems to happen too often, which I believe is what makes the music sound sloppy.
If there is any better way to program this, I'd love to know, since I've never done anything like this before. Maybe something other than SDL would work better, or I could just be doing it wrong...

pradipna
October 31st, 2011, 10:43
I don't have any experience with SDL so I can't really help you on that. I am using xAudio2. The way I am emulating channel 1 and 2 is like this, I have created 4 buffers with different wave duty (12.5%, 25%, 50% and 75%). When game wants to play sound from channel 1 or 2, I just copy the required wave duty buffer to xAudio source buffer and loop it infinitely at given frequency. And stop it according to sound length register, or sweep frequency overflows etc etc...

ShizZy
October 31st, 2011, 19:25
Drenn,

Have you checked out:

http://slack.net/~ant/libs/audio.html#Blip_Buffer

(http://slack.net/~ant/libs/audio.html#Blip_Buffer)
Blip_Buffer implements an efficient band-limited sound buffer for high-quality emulation of sound chips. After setting the source clock rate and output sample rate, sound waves are made by specifying the time points where amplitude changes occur.

It's written using SDL, and really helpful for audio emulation of older systems. Blargg also has a really simple example of the Game Boy PAPU sound chip emulator, written in C/SDL (if you're feeling less adventurous).

Drenn
October 31st, 2011, 20:56
Thanks for replying :)
As it turns out,

updateBufferCount = updateBufferLimit - updateBufferCount ;
should have been

updateBufferCount = updateBufferCount - updateBufferLimit;
An honest mistake :)
It sounds loads better now, just a little... how to say... unclean? Not as crisp as it should be. Blip Buffer sounds perfect for an emulator, I'll probably use that when I'm feeling less lazy. For now I'm happy with the result, I just need to emulate sweep and noise. I may be back to ask about the details of sweep, since my previous attempt at emulating that ended badly.

Edit - gah, now the frequency for channel 3 sounds too low in some cases, like the intro in mario deluxe. Screw it, I'll use blip_buffer!

Drenn
November 4th, 2011, 04:15
I am SO close... it sounds nearly perfect, like a real gameboy... except that channel 3 has an annoying tendancy to be a little bit off-tune sometimes. It completely wrecks a few songs :(

Weirdly, for channel 3 the pandocs say:

Frequency = 65536/(2048-x) Hz

but the value that works for me is


frequency = (65536/(2048-x)*32) Hz

Does anyone else have this? Could this be screwing up the frequency just slightly? It's driving me crazy!

Edit - Ohh, the 32 is for the 32 samples. Still, the off-tune channel 3 remains at large... here's my code:

// Channel 3
if (chanOn[2])
{
static double polarity[] = { -1, -0.8667, -0.7334, -0.6, -0.4668, -0.3335, -0.2, -0.067, 0.0664, 0.2, 0.333, 0.4668, 0.6, 0.7334, 0.8667, 1 } ;
if (chanVol[2] != 0)
{
// Read the sample from ram
int wavTone = ioRam[0x30+(chan3WavPos/2)];
wavTone = chan3WavPos%2? wavTone&0xF : wavTone>>4;

// Add the sample to the output
if (chanToOut1[2])
toneSO1 += (polarity[wavTone])*(0xF >> (chanVol[2]-1));
if (chanToOut2[2])
toneSO2 += (polarity[wavTone])*(0xF >> (chanVol[2]-1));
}
chanPolarityCounter[2] -= cycles;
// Update polarity
if (chanPolarityCounter[2] <= 0)
{
chanPolarityCounter[2] = clockSpeed/(65536/(2048-chanFreq[2])*32);

chan3WavPos++;
if (chan3WavPos >= 32)
chan3WavPos = 0;
}


// Check that it hasn't timed out
if (chanUseLen[2])
{
chanLenCounter[2] -= cycles;
if (chanLenCounter[2] <= 0)
{
chanOn[2] = 0;
chanPolarityCounter[2] = 0;
clearChan3();
}
}
}


Sorry for the mess. It's possible the error's not in there, but unlikely. It's the only channel giving me problems.

pradipna
December 4th, 2011, 17:53
Ok, I have finally decided to release the new version of my emulator. Here's a feature list:

Features:
---------------------
-> Can emulate GameBoy and GameBoy Color hardware.
-> Sound Emulation (excluding Vin output)
-> Keyboard and JoyPad input support.
-> MBC1, MBC2, MBC3 and MBC5 support.
-> Real Time Clock emulated.
-> Battery Pack support. Save format is compatible with VBA-M.
-> Save State
-> VisualBoy inspired Turbo Button. Press Space to speed up the game.

I have increased the compatibility too. I am going to keep this project aside for now and work on a new one.
If anyone's interested, download it from here: http://www.pradsprojects.com/dinoboy.html