Page 1 of 1

Unable to generate FM sound

Posted: Tue Jul 30, 2013 6:36 pm
by M-374 LX
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?

Posted: Tue Jul 30, 2013 7:15 pm
by Chilly Willy
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.

Posted: Tue Jul 30, 2013 8:15 pm
by M-374 LX
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.

Posted: Tue Jul 30, 2013 8:15 pm
by Eke
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

Posted: Tue Jul 30, 2013 8:42 pm
by M-374 LX
Thanks, Eke. It works now.
Optimizations were not needed, Chilly Willy.

Posted: Wed Jul 31, 2013 12:33 am
by Chilly Willy
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:

Posted: Wed Jul 31, 2013 8:06 am
by Stef
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.

Posted: Wed Jul 31, 2013 6:27 pm
by Chilly Willy
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.

Posted: Wed Jul 31, 2013 9:08 pm
by Mask of Destiny
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.

Posted: Thu Aug 01, 2013 8:10 pm
by Chilly Willy
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.

Posted: Thu Sep 05, 2013 1:07 am
by M-374 LX
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)?

Posted: Thu Sep 05, 2013 7:52 am
by Stef
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.