Unable to generate FM sound

For anything related to sound (YM2612, PSG, Z80, PCM...)

Moderator: BigEvilCorporation

Post Reply
M-374 LX
Very interested
Posts: 61
Joined: Mon Aug 11, 2008 10:15 pm
Contact:

Unable to generate FM sound

Post by M-374 LX » Tue Jul 30, 2013 6:36 pm

Here is my FM function to write a value to an FM register (part I only, as it is just a test):

Code: Select all

void write_fm(uchar reg, uchar val)
{
	uchar* ctrl = (uchar*)0xA04000;
	uchar* data = (uchar*)0xA04001;

	while(*ctrl & 0x80);
	*ctrl = reg;

	while(*ctrl & 0x80);
	*data = val;

	while(*ctrl & 0x80);
}
And here, my attempt to generate sound with this test program:

Code: Select all

void test_fm()
{
	write_fm(0x22, 0);
	write_fm(0x27, 0);

	write_fm(0x28, 0);
	write_fm(0x28, 1);
	write_fm(0x28, 2);
	write_fm(0x28, 4);
	write_fm(0x28, 5);
	write_fm(0x28, 6);

	write_fm(0x2B, 0);
	write_fm(0x30, 0x71);
	write_fm(0x34, 0x0D);
	write_fm(0x38, 0x33);
	write_fm(0x3C, 0x01);
	write_fm(0x40, 0x23);
	write_fm(0x44, 0x2D);
	write_fm(0x48, 0x26);
	write_fm(0x4C, 0x00);
	write_fm(0x50, 0x5F);
	write_fm(0x54, 0x99);
	write_fm(0x58, 0x5F);
	write_fm(0x5C, 0x94);
	write_fm(0x60, 0x05);
	write_fm(0x64, 0x05);
	write_fm(0x68, 0x05);
	write_fm(0x6C, 0x07);
	write_fm(0x70, 0x02);
	write_fm(0x74, 0x02);
	write_fm(0x78, 0x02);
	write_fm(0x7C, 0x02);
	write_fm(0x80, 0x11);
	write_fm(0x84, 0x11);
	write_fm(0x88, 0x11);
	write_fm(0x8C, 0xA6);
	write_fm(0x90, 0x00);
	write_fm(0x94, 0x00);
	write_fm(0x98, 0x00);
	write_fm(0x9C, 0x00);
	write_fm(0xB0, 0x32);
	write_fm(0xB4, 0xC0);

	write_fm(0x28, 0x00);
	write_fm(0xA4, 0x22);
	write_fm(0xA0, 0x69);
	write_fm(0x28, 0xF0);
}
The only difference is that my function never does a "key off" after the last "key on".

Why am I not getting any sound?

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Tue Jul 30, 2013 7:15 pm

The pointers should point to volatile values, and in some circumstances, the optimization level can screw with the order of stored values. I've taken to simply ALWAYS setting the level to -O1 for any C file that has direct access to hardware in it. This is only an issue on certain CPUs - the x86 compiler doesn't have this issue, but I've been able to recreate the problem on MIPS, SuperH, and 680x0 compilers. You might google on the issue "memory barriers" and gcc - it's been an ongoing issue for more than a decade.

The idea is that you put "memory barriers" between accesses to/from hardware that is memory mapped so that they occur in the same order as the code source. For x86, merely designating the memory location as volatile is enough to create said memory barrier. For other CPU backends, you have to do more. As mentioned, I've found that changing the O level to 1 is the EASIEST thing to do. If you want more optimization, you'll need to dig into the compiler guts to determine what is and what is not an effective memory barrier for the CPU in question.

M-374 LX
Very interested
Posts: 61
Joined: Mon Aug 11, 2008 10:15 pm
Contact:

Post by M-374 LX » Tue Jul 30, 2013 8:15 pm

Do you mean I should add the volatile keyword before the declarations of the pointers (in the C files that have direct access to the hardware) and compile those files with -O1? If so, it did not work.

I had success accessing the VDP without the volatile keyword or optimizations.
Last edited by M-374 LX on Tue Sep 03, 2013 9:53 pm, edited 1 time in total.

Eke
Very interested
Posts: 884
Joined: Wed Feb 28, 2007 2:57 pm
Contact:

Post by Eke » Tue Jul 30, 2013 8:15 pm

Also you need to :

(1) cancel z80 reset (write 1 to $A11200 bit 0) to start FM chip
(2) request z80 bus (write 1 to $A11100 bit 0 and wait until same bit returns 0 on read) to access it

M-374 LX
Very interested
Posts: 61
Joined: Mon Aug 11, 2008 10:15 pm
Contact:

Post by M-374 LX » Tue Jul 30, 2013 8:42 pm

Thanks, Eke. It works now.
Optimizations were not needed, Chilly Willy.

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Wed Jul 31, 2013 12:33 am

M-374 LX wrote:Thanks, Eke. It works now.
Optimizations were not needed, Chilly Willy.
Were? Or weren't? If the subroutine isn't inlined, that would also help with the in-order business. Hardware access can be tricky... just when you think you have it worked out, it bites you in the ass! :lol:

Stef
Very interested
Posts: 3131
Joined: Thu Nov 30, 2006 9:46 pm
Location: France - Sevres
Contact:

Post by Stef » Wed Jul 31, 2013 8:06 am

M-374 LX wrote:Do you mean I should add the volatile keyword before the declarations of the pointers (in the C files that have direct access to the hardware) and compile those files with -O1? If so, it did not work.

I had success accessing the VDP without the volatile keyword or optimizations.
Actually you need to use the volatile keyword when accessing hardware port as soon you enable optimization on compiler, if you do not, the compiler while assume your variable value don't change (code optimization) and that kind of code:

Code: Select all

while(*ctrl & 0x80);
will loop forever...
Looks at the code of SGDK, every hardware port are accessed through volatile field.

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Wed Jul 31, 2013 6:27 pm

Stef wrote:
M-374 LX wrote:Do you mean I should add the volatile keyword before the declarations of the pointers (in the C files that have direct access to the hardware) and compile those files with -O1? If so, it did not work.

I had success accessing the VDP without the volatile keyword or optimizations.
Actually you need to use the volatile keyword when accessing hardware port as soon you enable optimization on compiler, if you do not, the compiler while assume your variable value don't change (code optimization) and that kind of code:

Code: Select all

while(*ctrl & 0x80);
will loop forever...
Looks at the code of SGDK, every hardware port are accessed through volatile field.
Yes, volatile takes care of the "must read or write the location EVERY time" issue, but beyond O1 there is a more subtle issue concerning the order of execution that can arise that volatile doesn't fix. For example, we've seen that while loops can run backwards for best speed on the 68000, but if you're accessing hardware in that loop, you may be counting of the loop running FORWARDS. Another thing I've seen is that the compiler will generate code that defers when a value is written/read until later, perhaps to save on register usage. That may also cause hardware accesses to be done in the wrong order. It doesn't ALWAYS do it - it's a fickle optimization based thing that can change with every change of code. It's why I tend to try to keep hardware code by itself and always compile it at -O1.

Mask of Destiny
Very interested
Posts: 615
Joined: Thu Nov 30, 2006 6:30 am

Post by Mask of Destiny » Wed Jul 31, 2013 9:08 pm

Chilly Willy wrote:Yes, volatile takes care of the "must read or write the location EVERY time" issue, but beyond O1 there is a more subtle issue concerning the order of execution that can arise that volatile doesn't fix.
Technically, the ordering of volatile accesses separated by a sequence point should be guaranteed according to the standard, even at higher optimization levels. The ordering of non-volatile accesses with respect to volatile access is not guaranteed nor are volatile accesses that are not separated by a sequence point. So you should be able to get away with higher optimization levels if you're careful to only depend on what the standard guarantees, but there's a fair amount of room to screw it up. The definition of an "access" to a volatile object is unfortunately implementation defined in C; however, GCC uses a fairly sane definition (see here for details).

Volatile does not guarantee that accesses are atomic though, nor does it deal with re-ordering done at the CPU level, but I wouldn't normally expect those kind of things to crop up on the Genesis.

I suppose it's possible that GCC's 68K backend is just buggy though. I would imagine it's pretty unloved these days given that ColdFire hasn't exactly taken the world by storm. Do you have an example of a piece of code that didn't work at optimization levels about -O1? I'd be curious to see the code it produced.

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Thu Aug 01, 2013 8:10 pm

We went over this in a different thread last year (or was it the year before?). Just search on memory barriers and you should be able to find it.

M-374 LX
Very interested
Posts: 61
Joined: Mon Aug 11, 2008 10:15 pm
Contact:

Post by M-374 LX » Thu Sep 05, 2013 1:07 am

What should one do if the 68000 needs to write to the YM2612 while the Z80 runs a DAC sample driver (which seems to be the case of the games that use the 68000 version of the SMPS sound engine)?

Stef
Very interested
Posts: 3131
Joined: Thu Nov 30, 2006 9:46 pm
Location: France - Sevres
Contact:

Post by Stef » Thu Sep 05, 2013 7:52 am

You need to take Z80 bus and so interrupt it... which is never good when Z80 is used to play samples through DAC. Try to interrupt it for the shortest possible period as it may affect the sample playback quality.

Post Reply