DMC

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

Post Reply
proxy
Posts: 68
Joined: Tue Mar 08, 2011 9:45 am
Contact:

DMC

Post by proxy » Tue Apr 23, 2013 9:37 am

Hey guys, sorry if this has been covered, but I'm a little stumped on my DMC implementation and wanted to see if any of you guys have any insight. I'm trying to get the APU "just right", and seem unable to get my DMC to pass even the "basic operations" test, so obviously I'm misunderstanding something. Here's what I believe happens:

* The frequency is how many CPU cycles between DMC "ticks"
* Every time the DMC "ticks", if there are "bytes remaining" in the sample, then a DMA read might happen
* A DMA read happens if there are bytes remaining and the sample buffer is empty (AKA, every 8 ticks)
* After a read, bytes remaining is decremented, if it hits 0, we either STOP, IRQ, or LOOP.

Based on this, here's my current attempt at implementing the rules above, this function is called every CPU cycle:

Code: Select all

void DMC::tick() {

	if(enabled()) {
		if(frequency_ != 0) {
			if(--frequency_ == 0) {			
				frequency_ = reload_frequency_;				

				if(bytes_remaining_ != 0) {
				
					// read the 8-bit sample
					if(bit_ == 0) {
						// TODO: hijack the CPU for appropriate number of cycles
						sample_buffer_ = nes::cpu.read(current_address_);
						if(current_address_ == 0xffff) {
							current_address_ = 0x8000;
						} else {
							++current_address_;					
						}
					}
					
					// TODO: grab appropriate bit from sample_buffer and update
					bit_ = (bit_ + 1) % 8;
					
				
					if(--bytes_remaining_ == 0) {
						if(loop_) {
							bytes_remaining_ = sample_length_;
							current_address_ = sample_address_;
						} else if(irq_enabled_) {
							nes::cpu.irq(CPU::APU_IRQ);
							nes::apu.status_ |= APU::STATUS_DMC_IRQ;
						}
					}
				}
			}
		}
	}
}
Any thoughts on where I am going wrong?

User avatar
rainwarrior
Posts: 8000
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: DMC

Post by rainwarrior » Tue Apr 23, 2013 11:52 am

Could you describe what basic operations it's failing? What do you expect it to do when you don't even make use of your sample_buffer at all?

proxy
Posts: 68
Joined: Tue Mar 08, 2011 9:45 am
Contact:

Re: DMC

Post by proxy » Tue Apr 23, 2013 12:19 pm

I am trying to run it against blargg's "7-dmc_basics.nes". It was failing the first test with the message:
DMC isn't working well enough to test further
.

However, I added some tracing and it seems that the test was expecting things to happen exactly 8 times slower than I was doing it. So i made one change, when the frequency is set, I multiply it by 8.

Now it passes some of the tests in that ROM (but not all).

Why do I need to multiply it by 8? I thought the frequency was the number of CPU cycles between DMC "ticks" (what is the right word there, "clocks"?). When the DMC's timer decides it's time to fire, does it not reset until all 8 bits of the sample byte have been loaded?

User avatar
rainwarrior
Posts: 8000
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: DMC

Post by rainwarrior » Tue Apr 23, 2013 12:51 pm

I presume you're looking up the DPCM frequency from the appropriate table: Wiki

Yes, the DPCM unit should tick once every n CPU cycles, where n is a value looked up from that table. During this tick it should consume one bit of data from the sample buffer. If the sample buffer is empty it will need to reload a new byte.

Multiplying your frequency by 8 does not sound like the correct thing to do. It's probably because you're decrementing bytes_remaining_ every time, even though you're only fetching a new sample when bit_ is 0.

proxy
Posts: 68
Joined: Tue Mar 08, 2011 9:45 am
Contact:

Re: DMC

Post by proxy » Tue Apr 23, 2013 1:07 pm

Ah, good call, that was precisely it. I am using the frequency table of course and things are closer to passing the test suite now :-).

For reference, here is the new code which doesn't depend on me multiplying the frequency from the table by 8:

Code: Select all

void DMC::tick() {
	if(enabled()) {
		if(frequency_ != 0) {
			if(--frequency_ == 0) {			
				frequency_ = reload_frequency_;				

				if(bytes_remaining_ != 0) {
				
					// read the 8-bit sample
					if(bit_ == 0) {					
						// TODO: hijack the CPU for appropriate number of cycles, not hardcoded to 4
						nes::cpu.burn(4);
						sample_buffer_ = nes::cpu.read(current_address_);
						if(current_address_ == 0xffff) {
							current_address_ = 0x8000;
						} else {
							++current_address_;					
						}
					}
					
					// TODO: grab appropriate bit from sample_buffer and update
					
					bit_ = (bit_ + 1) % 8;
					if(bit_ == 0) {
						if(--bytes_remaining_ == 0) {
							if(loop_) {
								bytes_remaining_ = sample_length_;
								current_address_ = sample_address_;
							} else if(irq_enabled_) {
								nes::cpu.irq(CPU::APU_IRQ);
								nes::apu.status_ |= APU::STATUS_DMC_IRQ;
							}
						}
					}
				}
			}
		}
	}
}
Now it passes all but the last of the DMC basics test
#19 - There should be a one-byte buffer that's filled immediately if empty
. I am honestly not sure what I am doing wrong as far as that last test.

User avatar
rainwarrior
Posts: 8000
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: DMC

Post by rainwarrior » Tue Apr 23, 2013 1:11 pm

Could you link to the source code for the test you're using?

proxy
Posts: 68
Joined: Tue Mar 08, 2011 9:45 am
Contact:

Re: DMC

Post by proxy » Tue Apr 23, 2013 1:21 pm

sure thing, i'm gonna include the whole test suite for context. The test in particular's source code is:

apu_test/source/7-dmc_basics.s

Thanks for taking a look.
Attachments
apu_test.zip
(55.34 KiB) Downloaded 312 times

User avatar
rainwarrior
Posts: 8000
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: DMC

Post by rainwarrior » Wed Apr 24, 2013 1:31 am

The test is seeing if $4015 reports that the DPCM unit has read the last byte of the sample with correct timing.

The DPCM flag in $4015 (bit 4) clears immediately when the last byte of the sample is read. It will still play all the bits of the sample (so it will be affecting sound for 7 more ticks, as it empties the bits from its 1-byte buffer), but the flag in $4015 is based on bytes read, not bits.

So, what this test does is first empties the DPCM unit (i.e. turns it off, and delays long enough for the bits to finish), then it triggers a 1-byte sample to play, expecting bit 4 of $4015 to immediately clear, since that one byte will be read on the first tick, and it's the last byte of the sample.

You should also note that setting bit 4 of $4015 doesn't discard any bits remaining in the sample buffer. If another sample still had bits in there, they will finish playing before it loads the first byte of the new sample. I don't think there is a test for this in blargg's tests here, but it's still something to be aware of.

beannaich
Posts: 207
Joined: Wed Mar 31, 2010 12:40 pm

Re: DMC

Post by beannaich » Wed Apr 24, 2013 6:12 am

This test was failing me because I was doing things in the wrong order on writes to $4015, had nothing to do with my DMC operation at all.

Code: Select all

        private void Poke4015(uint address, byte data)
        {
            cpu.Interrupt(dmc.IrqPending = false);

            sq1.Enabled = (data & 0x01) != 0;
            sq2.Enabled = (data & 0x02) != 0;
            tri.Enabled = (data & 0x04) != 0;
            noi.Enabled = (data & 0x08) != 0;
            dmc.Enabled = (data & 0x10) != 0; // this property will set IRQ on blargg's test, so it must be AFTER clearing the DMC IRQ flag.
        }

proxy
Posts: 68
Joined: Tue Mar 08, 2011 9:45 am
Contact:

Re: DMC

Post by proxy » Thu Apr 25, 2013 8:38 am

Awesome info guys. With your help, I was able to get my DMC code to pass the basic operations tests :-). Turns out my issue was two fold:

1. As the test states, enabling the DMC should process a 1 byte sample immediately if the sample buffer is empty.
2. (This was the tricky part). I was only draining the sample buffer if there was at least one byte remaining! Once I figured that out, the rest was easy :-).

For reference of anyone who reads this later, the final (I believe correct code) ended up looking like this:

Code: Select all

        // timer_.tick() will return true every N ticks where N is the frequency of the channel
	if(enabled() && timer_.tick()) {
		if(bytes_remaining_ != 0) {
		
			// read the 8-bit sample
			if(sample_shift_counter_ == 0) {
				sample_shift_counter_ = 8;

				// TODO: hijack the CPU for appropriate number of cycles, 
				//       not hardcoded to 4
				nes::cpu.burn(4);
				sample_buffer_ = nes::cpu.read(sample_pointer_);

				sample_pointer_ = ((sample_pointer_ + 1) & 0xffff) | 0x8000;

				if(--bytes_remaining_ == 0) {
					if(loop_) {
						bytes_remaining_ = sample_length_;
						sample_pointer_ = sample_address_;
					} else if(irq_enabled_) {
						nes::cpu.irq(CPU::APU_IRQ);
						nes::apu.status_ |= APU::STATUS_DMC_IRQ;
					}
				}
			}
		}
		
		if(sample_shift_counter_ != 0) {
			// TODO: process a bit from the sample
			--sample_shift_counter_;
		}
	}
Now I have to figure out the next DMC test ;-).

Thanks!

User avatar
ulfalizer
Posts: 349
Joined: Fri Mar 08, 2013 9:55 pm
Location: Linköping, Sweden

Re: DMC

Post by ulfalizer » Thu Apr 25, 2013 9:29 am

Haven't followed the topic closely, but it would be nice to have the details of this stuff on the wiki if it isn't there already. (Feel free to disregard this as a misplaced semi-rant if it's already there. :))

Seems some info is hidden away on the forums or in test roms, which imo isn't ideal (lots of "rediscovery" needed by newcomers).

User avatar
rainwarrior
Posts: 8000
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: DMC

Post by rainwarrior » Thu Apr 25, 2013 9:45 am

Well, if you think something from this thread is missing from the wiki, tell me what it is and I'll add it, or you can add it if you have a wiki account.

User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Re: DMC

Post by blargg » Thu Apr 25, 2013 9:48 am

I'm pretty sure I put all this in my APU doc and on the Wiki.

User avatar
ulfalizer
Posts: 349
Joined: Fri Mar 08, 2013 9:55 pm
Location: Linköping, Sweden

Re: DMC

Post by ulfalizer » Thu Apr 25, 2013 9:51 am

Ok, no worries then. :)

Post Reply