|
|
1.1 root 1: /*
1.1.1.4 root 2: Hatari - psg.c
1.1 root 3:
1.1.1.15 root 4: This file is distributed under the GNU General Public License, version 2
5: or at your option any later version. Read the file gpl.txt for details.
1.1.1.3 root 6:
1.1.1.4 root 7: Programmable Sound Generator (YM-2149) - PSG
1.1.1.3 root 8:
1.1.1.4 root 9: Also used for the printer (centronics) port emulation (PSG Port B, Register 15)
1.1 root 10: */
1.1.1.7 root 11:
12:
13: /* 2007/04/14 [NP] First approximation to get cycle accurate accesses to ff8800/02 */
14: /* by cumulating wait state of 1 cycle and rounding the final */
15: /* result to 4. */
16: /* 2007/04/29 [NP] Functions PSG_Void_WriteByte and PSG_Void_ReadByte to handle */
17: /* accesses to $ff8801/03. These adresses have no effect, but they */
18: /* must give a 1 cycle wait state (e.g. move.l d0,ff8800). */
19: /* 2007/09/29 [NP] Replace printf by calls to HATARI_TRACE. */
20: /* 2007/10/23 [NP] In PSG_Void_WriteByte, add a wait state only if no wait state */
21: /* were added so far (hack, but gives good result). */
22: /* 2007/11/18 [NP] In PSG_DataRegister_WriteByte, set unused bit to 0, in case */
23: /* the data reg is read later (fix Mindbomb Demo / BBC). */
1.1.1.9 root 24: /* 2008/04/20 [NP] In PSG_DataRegister_WriteByte, set unused bit to 0 for register */
25: /* 6 too (noise period). */
26: /* 2008/07/27 [NP] Better separation between accesses to the YM hardware registers */
27: /* and the sound rendering routines. Use Sound_WriteReg() to pass */
28: /* all writes to the sound rendering functions. This allows to */
1.1.1.15 root 29: /* have sound.c independent of psg.c (to ease replacement of */
1.1.1.9 root 30: /* sound.c by another rendering method). */
31: /* 2008/08/11 [NP] Set drive leds. */
32: /* 2008/10/16 [NP] When writing to $ff8800, register select should not be masked */
33: /* with 0xf, it's a real 8 bits register where all bits are */
34: /* significant. This means only value <16 should be considered as */
35: /* valid register selection. When reg select is >= 16, all writes */
36: /* and reads in $ff8802 should be ignored. */
37: /* (fix European Demo Intro, which sets addr reg to 0x10 when */
38: /* sample playback is disabled). */
1.1.1.10 root 39: /* 2008/12/21 [NP] After testing different cases on a real STF, rewrite registers */
40: /* handling. As only pins BC1 and BDIR are used in an Atari to */
41: /* address the YM2149, this means only 1 bit is necessary to access*/
42: /* select/data registers. Other variations of the $ff88xx addresses*/
43: /* will point to either $ff8800 or $ff8802. Only bit 1 of $ff88xx */
44: /* is useful to know which register is accessed in the YM2149. */
45: /* So, it's possible to access the YM2149 with $ff8801 and $ff8803 */
46: /* but under conditions : the write to a shadow address (bit 0=1) */
47: /* can't be made by an instruction that writes to the same address */
48: /* with bit 0=0 at the same time (.W or .L access). */
49: /* In that case, only the address with bit 0=0 is taken into */
50: /* account. This means a write to $ff8801/03 will succeed only if */
51: /* the access size is .B (byte) or the opcode is a movep (because */
52: /* in that case we won't access the same register with 2 different */
53: /* addresses) (fix the game X-Out, which uses movep.w to write to */
54: /* $ff8801/03). */
55: /* Refactorize some code for cleaner handling of these accesses. */
56: /* Only reads to $ff8800 will return a data, reads to $ff8801/02/03*/
57: /* always return 0xff (tested on STF). */
58: /* When PSGRegisterSelect > 15, reads to $ff8800 also return 0xff. */
59: /* 2009/01/24 [NP] Remove redundant test, as movep implies SIZE_BYTE access. */
1.1.1.13 root 60: /* 2011/10/30 [NP] There's a special case when reading a register from $ff8800 : */
61: /* if the register number was not changed since the last write, */
62: /* then we must return the value that was written to $ff8802 */
63: /* without masking the unused bit (fix the game Murders In Venice, */
64: /* which expects to read $10 from reg 3). */
1.1.1.18! root 65: /* 2015/10/15 [NP] Better handling of the wait states when accessing YM2149 regs. */
! 66: /* Replace M68000_WaitState(1) by PSG_WaitState() which adds */
! 67: /* 4 cycles every 4th access. Previous method worked because all */
! 68: /* cycles were rounded to 4, but it was not how real HW works and */
! 69: /* would not work in cycle exact mode where cycles are not rounded.*/
1.1.1.7 root 70:
71:
72: /* Emulating wait states when accessing $ff8800/01/02/03 with different 'move' variants */
73: /* is a complex task. So far, adding 1 cycle wait state to each access and rounding the */
1.1.1.18! root 74: /* final number to 4 gave some good results, but this is certainly not the way it's */
1.1.1.7 root 75: /* working for real in the ST. */
1.1.1.18! root 76: /* Also in Hatari it only works when the cpu rounds all cycles to the next multiple */
! 77: /* of 4, but it will not work when running in cycle exact mode. This means we must */
! 78: /* add 4 cycles at a time, but not on every register access, see below. */
! 79: /* */
1.1.1.7 root 80: /* The following examples show some verified wait states for different accesses : */
81: /* lea $ffff8800,a1 */
82: /* lea $ffff8802,a2 */
83: /* lea $ffff8801,a3 */
84: /* */
85: /* movep.w d1,(a1) ; 20 16+4 (ventura loader) */
86: /* movep.l d1,(a1) ; 28 24+4 (ventura loader, ulm loader) */
87: /* */
88: /* movep.l d6,0(a5) ; 28 24+4 (SNY I, TCB) */
89: /* movep.w d5,0(a5) ; 20 16+4 (SNY I, TCB) */
90: /* */
91: /* move.b d1,(a1) ; 12 8+4 */
92: /* move.b d1,(a2) ; 12 8+4 */
93: /* move.b d1,(a3) ; 12 8+4 (crickey ulm hidden) */
94: /* */
95: /* move.w d1,(a1) ; 12 8+4 */
96: /* move.w d1,(a2) ; 12 8+4 */
97: /* move.l d1,(a1) ; 16 12+4 (ulm loader) */
98: /* */
99: /* movem.l d1,(a1) ; 20 16+4 */
100: /* movem.l d1-d2,(a1) ; 28 24+4 */
101: /* movem.l d1-d3,(a1) ; 40 32+4+4 */
102: /* movem.l d1-d4,(a1) ; 48 40+4+4 */
103: /* movem.l d1-d5,(a1) ; 60 48+4+4+4 */
104: /* movem.l d1-d6,(a1) ; 68 56+4+4+4 */
105: /* movem.l d1-d7,(a1) ; 80 64+4+4+4+4 */
106: /* movem.l d0-d7,(a1) ; 88 72+4+4+4+4 */
107: /* */
1.1.1.10 root 108: /* movep.w d0,(a3) (X-Out) */
109: /* */
1.1.1.18! root 110: /* clr.b (a1) ; 20 12+8 (4 for read + 4 for write) */
! 111: /* tas (a1) ; 16 (no waitstate ?) */
! 112: /* */
! 113: /* */
1.1.1.7 root 114: /* This gives the following "model" : */
1.1.1.18! root 115: /* - each instruction accessing a valid YM2149 register gets an initial 4 cycle */
! 116: /* wait state for the 1st access (whether it accesses just 1 reg (eg move.b) */
! 117: /* or up to 4 regs (movep.l)). */
! 118: /* - susbequent accesses made by the same instruction don't add more wait state */
! 119: /* (except if the instruction is a MOVEM). */
! 120: /* - MOVEM can access more than 4 regs (up to 15) : in that case we add 4 extra */
! 121: /* cycles each time we access a 4th register (eg : regs 4,8,12, ...) */
1.1.1.10 root 122: /* - accesses to $ff8801 or $ff8803 are considered "valid" only if we don't access */
1.1.1.18! root 123: /* the corresponding "non shadow" addresses $ff8800/02 at the same time (ie with */
! 124: /* the same instruction). */
1.1.1.10 root 125: /* This means only .B size (move.b for example) or movep opcode will work. */
1.1.1.18! root 126: /* If the access is valid, add 4 cycle wait state when necessary, else ignore */
! 127: /* the write and don't add any cycle. */
1.1.1.7 root 128:
129:
130:
1.1.1.11 root 131: const char PSG_fileid[] = "Hatari psg.c : " __DATE__ " " __TIME__;
1.1 root 132:
133: #include "main.h"
1.1.1.3 root 134: #include "configuration.h"
1.1.1.4 root 135: #include "ioMem.h"
1.1.1.5 root 136: #include "joy.h"
1.1.1.7 root 137: #include "log.h"
1.1.1.4 root 138: #include "m68000.h"
1.1 root 139: #include "memorySnapShot.h"
140: #include "sound.h"
1.1.1.4 root 141: #include "printer.h" /* because Printer I/O goes through PSG Register 15 */
1.1.1.3 root 142: #include "psg.h"
1.1.1.7 root 143: #if ENABLE_DSP_EMU
144: #include "falcon/dsp.h"
145: #endif
1.1.1.12 root 146: #include "screen.h"
1.1.1.7 root 147: #include "video.h"
1.1.1.9 root 148: #include "statusbar.h"
149: #include "mfp.h"
1.1.1.16 root 150: #include "fdc.h"
1.1.1.9 root 151:
1.1 root 152:
1.1.1.17 root 153: static Uint8 PSGRegisterSelect; /* Write to 0xff8800 sets the register number used in read/write accesses */
154: static Uint8 PSGRegisterReadData; /* Value returned when reading from 0xff8800 */
155: Uint8 PSGRegisters[MAX_PSG_REGISTERS]; /* Registers in PSG, see PSG_REG_xxxx */
1.1.1.4 root 156:
1.1.1.9 root 157: static unsigned int LastStrobe=0; /* Falling edge of Strobe used for printer */
1.1 root 158:
159:
160: /*-----------------------------------------------------------------------*/
1.1.1.7 root 161: /**
162: * Reset variables used in PSG
163: */
1.1 root 164: void PSG_Reset(void)
165: {
1.1.1.13 root 166: int i;
167:
1.1.1.14 root 168: if (LOG_TRACE_LEVEL(TRACE_PSG_WRITE))
169: {
170: int FrameCycles, HblCounterVideo, LineCycles;
171: Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
172: LOG_TRACE_PRINT("ym reset video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
173: FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
174: }
175:
1.1.1.4 root 176: PSGRegisterSelect = 0;
1.1.1.13 root 177: PSGRegisterReadData = 0;
1.1.1.4 root 178: memset(PSGRegisters, 0, sizeof(PSGRegisters));
1.1.1.14 root 179: PSGRegisters[PSG_REG_IO_PORTA] = 0xff; /* no drive selected + side 0 after a reset */
180:
181: /* Update sound's emulation registers */
182: for ( i=0 ; i < NUM_PSG_SOUND_REGISTERS; i++ )
1.1.1.13 root 183: Sound_WriteReg ( i , 0 );
184:
1.1.1.9 root 185: LastStrobe=0;
1.1 root 186: }
187:
1.1.1.2 root 188:
189: /*-----------------------------------------------------------------------*/
1.1.1.7 root 190: /**
191: * Save/Restore snapshot of local variables ('MemorySnapShot_Store' handles type)
192: */
1.1.1.9 root 193: void PSG_MemorySnapShot_Capture(bool bSave)
1.1 root 194: {
1.1.1.4 root 195: /* Save/Restore details */
196: MemorySnapShot_Store(&PSGRegisterSelect, sizeof(PSGRegisterSelect));
1.1.1.13 root 197: MemorySnapShot_Store(&PSGRegisterReadData, sizeof(PSGRegisterReadData));
1.1.1.4 root 198: MemorySnapShot_Store(PSGRegisters, sizeof(PSGRegisters));
1.1.1.9 root 199: MemorySnapShot_Store(&LastStrobe, sizeof(LastStrobe));
1.1 root 200: }
201:
1.1.1.2 root 202:
203: /*-----------------------------------------------------------------------*/
1.1.1.7 root 204: /**
1.1.1.10 root 205: * Write byte to the YM address register (usually 0xff8800). This is used
206: * as a selector for when we read/write the YM data register (0xff8802).
1.1.1.7 root 207: */
1.1.1.10 root 208: void PSG_Set_SelectRegister(Uint8 val)
1.1 root 209: {
1.1.1.9 root 210: /* Store register used to read/write in $ff8802. This register */
211: /* is 8 bits on the YM2149, this means it should not be masked */
212: /* with 0xf. Instead, we keep the 8 bits, but we must ignore */
213: /* read/write to ff8802 when PSGRegisterSelect >= 16 */
1.1.1.10 root 214: PSGRegisterSelect = val;
1.1.1.7 root 215:
1.1.1.13 root 216: /* When address register is changed, a read from $ff8800 should */
217: /* return the masked value of the register. We set the value here */
218: /* to be returned in case PSG_Get_DataRegister is called */
219: PSGRegisterReadData = PSGRegisters[PSGRegisterSelect];
220:
1.1.1.11 root 221: if (LOG_TRACE_LEVEL(TRACE_PSG_WRITE))
222: {
223: int FrameCycles, HblCounterVideo, LineCycles;
224: Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
225: LOG_TRACE_PRINT("ym write reg=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
226: PSGRegisterSelect, FrameCycles, LineCycles, HblCounterVideo,
227: M68000_GetPC(), CurrentInstrCycles);
228: }
1.1 root 229: }
230:
1.1.1.2 root 231:
232: /*-----------------------------------------------------------------------*/
1.1.1.7 root 233: /**
1.1.1.10 root 234: * Read byte from 0xff8800, return PSG data
1.1.1.7 root 235: */
1.1.1.10 root 236: Uint8 PSG_Get_DataRegister(void)
1.1 root 237: {
1.1.1.9 root 238: /* Is a valid PSG register currently selected ? */
1.1.1.14 root 239: if ( PSGRegisterSelect >= MAX_PSG_REGISTERS )
1.1.1.10 root 240: return 0xff; /* not valid, return 0xff */
1.1.1.9 root 241:
1.1.1.14 root 242: if (PSGRegisterSelect == PSG_REG_IO_PORTA)
1.1.1.5 root 243: {
244: /* Second parallel port joystick uses centronics strobe bit as fire button: */
245: if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT2].nJoystickMode != JOYSTICK_DISABLED)
246: {
247: if (Joy_GetStickData(JOYID_PARPORT2) & 0x80)
1.1.1.14 root 248: PSGRegisters[PSG_REG_IO_PORTA] &= ~32;
1.1.1.5 root 249: else
1.1.1.14 root 250: PSGRegisters[PSG_REG_IO_PORTA] |= 32;
1.1.1.5 root 251: }
252: }
1.1.1.14 root 253: else if (PSGRegisterSelect == PSG_REG_IO_PORTB)
1.1.1.5 root 254: {
255: /* PSG register 15 is parallel port data register - used by parallel port joysticks: */
256: if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT1].nJoystickMode != JOYSTICK_DISABLED)
257: {
1.1.1.14 root 258: PSGRegisters[PSG_REG_IO_PORTB] &= 0x0f;
259: PSGRegisters[PSG_REG_IO_PORTB] |= ~Joy_GetStickData(JOYID_PARPORT1) << 4;
1.1.1.5 root 260: }
261: if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT2].nJoystickMode != JOYSTICK_DISABLED)
262: {
1.1.1.14 root 263: PSGRegisters[PSG_REG_IO_PORTB] &= 0xf0;
264: PSGRegisters[PSG_REG_IO_PORTB] |= ~Joy_GetStickData(JOYID_PARPORT2) & 0x0f;
1.1.1.5 root 265: }
266: }
267:
1.1.1.4 root 268: /* Read data last selected by register */
1.1.1.13 root 269: return PSGRegisterReadData;
1.1 root 270: }
271:
1.1.1.2 root 272:
273: /*-----------------------------------------------------------------------*/
1.1.1.7 root 274: /**
1.1.1.10 root 275: * Write byte to YM's register (0xff8802), store according to PSG select register (0xff8800)
1.1.1.7 root 276: */
1.1.1.10 root 277: void PSG_Set_DataRegister(Uint8 val)
1.1.1.4 root 278: {
1.1.1.16 root 279: Uint8 val_old;
280:
1.1.1.11 root 281: if (LOG_TRACE_LEVEL(TRACE_PSG_WRITE))
282: {
283: int FrameCycles, HblCounterVideo, LineCycles;
284: Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
285: LOG_TRACE_PRINT("ym write data reg=0x%x val=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
286: PSGRegisterSelect, val, FrameCycles, LineCycles,
287: HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
288: }
1.1.1.9 root 289:
290: /* Is a valid PSG register currently selected ? */
1.1.1.14 root 291: if ( PSGRegisterSelect >= MAX_PSG_REGISTERS )
1.1.1.9 root 292: return; /* not valid, ignore write and do nothing */
293:
1.1.1.8 root 294: /* Create samples up until this point with current values */
1.1.1.12 root 295: Sound_Update(false);
1.1.1.8 root 296:
1.1.1.13 root 297: /* When a read is made from $ff8800 without changing PSGRegisterSelect, we should return */
298: /* the non masked value. */
299: PSGRegisterReadData = val; /* store non masked value for PSG_Get_DataRegister */
300:
1.1.1.16 root 301: /* Read previous content */
302: val_old = PSGRegisters[PSGRegisterSelect];
303:
1.1.1.10 root 304: /* Copy value to PSGRegisters[] */
305: PSGRegisters[PSGRegisterSelect] = val;
1.1.1.4 root 306:
1.1.1.9 root 307: /* Clear unused bits for some regs */
1.1.1.7 root 308: if ( ( PSGRegisterSelect == PSG_REG_CHANNEL_A_COARSE ) || ( PSGRegisterSelect == PSG_REG_CHANNEL_B_COARSE )
309: || ( PSGRegisterSelect == PSG_REG_CHANNEL_C_COARSE ) || ( PSGRegisterSelect == PSG_REG_ENV_SHAPE ) )
310: PSGRegisters[PSGRegisterSelect] &= 0x0f; /* only keep bits 0 - 3 */
311:
312: else if ( ( PSGRegisterSelect == PSG_REG_CHANNEL_A_AMP ) || ( PSGRegisterSelect == PSG_REG_CHANNEL_B_AMP )
1.1.1.9 root 313: || ( PSGRegisterSelect == PSG_REG_CHANNEL_C_AMP ) || ( PSGRegisterSelect == PSG_REG_NOISE_GENERATOR ) )
1.1.1.7 root 314: PSGRegisters[PSGRegisterSelect] &= 0x1f; /* only keep bits 0 - 4 */
315:
316:
1.1.1.9 root 317: if ( PSGRegisterSelect < NUM_PSG_SOUND_REGISTERS )
1.1.1.4 root 318: {
1.1.1.9 root 319: /* Copy sound related registers 0..13 to the sound module's internal buffer */
320: Sound_WriteReg ( PSGRegisterSelect , PSGRegisters[PSGRegisterSelect] );
321: }
1.1.1.4 root 322:
1.1.1.9 root 323: else if ( PSGRegisterSelect == PSG_REG_IO_PORTA )
324: {
325: /*
326: * FIXME: This is only a prelimary dirty hack!
327: * Port B (Printer port) - writing here needs to be dispatched to the printer
328: * STROBE (Port A bit5) does a short LOW and back to HIGH when the char is valid
329: * To print you need to write the character byte to IOB and you need to toggle STROBE
330: * (like EmuTOS does).
331: */
1.1.1.4 root 332: /* Printer dispatching only when printing is activated */
333: if (ConfigureParams.Printer.bEnablePrinting)
334: {
1.1.1.9 root 335: /* Bit 5 - Centronics strobe? If STROBE is low and the LastStrobe was high,
336: then print/transfer to the emulated Centronics port.
1.1.1.7 root 337: */
1.1.1.9 root 338: if (LastStrobe && ( (PSGRegisters[PSG_REG_IO_PORTA]&(1<<5)) == 0 ))
1.1.1.4 root 339: {
340: /* Seems like we want to print something... */
1.1.1.9 root 341: Printer_TransferByteTo(PSGRegisters[PSG_REG_IO_PORTB]);
342: /* Initiate a possible GPIP0 Printer BUSY interrupt */
1.1.1.15 root 343: MFP_InputOnChannel ( MFP_INT_GPIP0 , 0 );
1.1.1.9 root 344: /* Initiate a possible GPIP1 Falcon ACK interrupt */
1.1.1.18! root 345: if (Config_IsMachineFalcon())
1.1.1.15 root 346: MFP_InputOnChannel ( MFP_INT_GPIP1 , 0 );
1.1.1.4 root 347: }
348: }
1.1.1.9 root 349: LastStrobe = PSGRegisters[PSG_REG_IO_PORTA]&(1<<5);
350:
351: /* Bit 0-2 : side and drive select */
352: if ( (PSGRegisters[PSG_REG_IO_PORTA]&(1<<1)) == 0 )
353: {
354: /* floppy drive A is ON */
1.1.1.15 root 355: Statusbar_SetFloppyLed(DRIVE_LED_A, LED_STATE_ON);
1.1.1.9 root 356: }
357: else
358: {
1.1.1.15 root 359: Statusbar_SetFloppyLed(DRIVE_LED_A, LED_STATE_OFF);
1.1.1.9 root 360: }
361: if ( (PSGRegisters[PSG_REG_IO_PORTA]&(1<<2)) == 0 )
362: {
363: /* floppy drive B is ON */
1.1.1.15 root 364: Statusbar_SetFloppyLed(DRIVE_LED_B, LED_STATE_ON);
1.1.1.9 root 365: }
366: else
367: {
1.1.1.15 root 368: Statusbar_SetFloppyLed(DRIVE_LED_B, LED_STATE_OFF);
1.1.1.9 root 369: }
370:
1.1.1.16 root 371: /* Report a possible drive/side change */
372: FDC_SetDriveSide ( val_old & 7 , PSGRegisters[PSG_REG_IO_PORTA] & 7 );
373:
1.1.1.7 root 374: /* handle Falcon specific bits in PORTA of the PSG */
1.1.1.18! root 375: if (Config_IsMachineFalcon())
1.1.1.7 root 376: {
1.1.1.17 root 377: /* Bit 3 - centronics port SELIN line (pin 17) */
378: /*
379: if (PSGRegisters[PSG_REG_IO_PORTA] & (1 << 3))
380: {
381: // not emulated yet
382: }
383: */
384:
1.1.1.7 root 385: /* Bit 4 - DSP reset? */
386: if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<4))
387: {
388: Log_Printf(LOG_DEBUG, "Calling DSP_Reset?\n");
389: #if ENABLE_DSP_EMU
390: if (ConfigureParams.System.nDSPType == DSP_TYPE_EMU) {
391: DSP_Reset();
392: }
393: #endif
394: }
395: /* Bit 6 - Internal Speaker control */
396: if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<6))
397: {
398: /*Log_Printf(LOG_DEBUG, "Falcon: Internal Speaker state\n");*/
399: /* FIXME: add code to handle? (if we want to emulate the speaker at all? */
400: }
401: /* Bit 7 - Reset IDE? */
402: if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<7))
403: {
404: Log_Printf(LOG_DEBUG, "Falcon: Reset IDE subsystem\n");
405: /* FIXME: add code to handle IDE reset */
406: }
407: }
1.1.1.16 root 408:
1.1.1.4 root 409: }
1.1 root 410: }
411:
1.1.1.2 root 412:
413: /*-----------------------------------------------------------------------*/
1.1.1.7 root 414: /**
1.1.1.18! root 415: * Handle wait state when accessing YM2149 registers
! 416: * - each instruction accessing YM2149 gets an initial 4 cycle wait state
! 417: * for the 1st access (whether it accesses just 1 reg (eg move.b) or up to 4 regs (movep.l))
! 418: * - special case for movem which can access more than 4 regs (up to 15) :
! 419: * we add 4 extra cycles each time we access a 4th reg (eg : regs 4,8,12, ...)
! 420: *
! 421: * See top of this file for several examples measured on real STF
! 422: */
! 423: static void PSG_WaitState(void)
! 424: {
! 425: #if 0
! 426: M68000_WaitState(1); /* [NP] FIXME not 100% accurate, but gives good results */
! 427: #else
! 428: static Uint64 PSG_InstrPrevClock;
! 429: static int NbrAccesses;
! 430:
! 431: if ( PSG_InstrPrevClock != CyclesGlobalClockCounter ) /* New instruction accessing YM2149 : add 4 cycles */
! 432: {
! 433: M68000_WaitState ( 4 );
! 434: PSG_InstrPrevClock = CyclesGlobalClockCounter;
! 435: NbrAccesses = 0;
! 436: }
! 437:
! 438: else /* Same instruction doing several accesses : only movem can add more cycles */
! 439: {
! 440: if ( ( OpcodeFamily == i_MVMEL ) || ( OpcodeFamily == i_MVMLE ) )
! 441: {
! 442: NbrAccesses += 1;
! 443: if ( NbrAccesses % 4 == 0 ) /* Add 4 extra cycles every 4th access */
! 444: M68000_WaitState ( 4 );
! 445: }
! 446: }
! 447:
! 448: #endif
! 449: }
! 450:
! 451:
! 452: /*-----------------------------------------------------------------------*/
! 453: /**
1.1.1.10 root 454: * Read byte from 0xff8800. Return current content of data register
1.1.1.7 root 455: */
1.1.1.10 root 456: void PSG_ff8800_ReadByte(void)
1.1 root 457: {
1.1.1.18! root 458: PSG_WaitState();
1.1.1.10 root 459:
460: IoMem[IoAccessCurrentAddress] = PSG_Get_DataRegister();
461:
1.1.1.11 root 462: if (LOG_TRACE_LEVEL(TRACE_PSG_READ))
463: {
464: int FrameCycles, HblCounterVideo, LineCycles;
465: Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
466: LOG_TRACE_PRINT("ym read data %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
467: IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress],
468: FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
469: }
1.1.1.10 root 470: }
471:
472:
473: /*-----------------------------------------------------------------------*/
474: /**
475: * Read byte from 0xff8801/02/03. Return 0xff.
476: */
477: void PSG_ff880x_ReadByte(void)
478: {
1.1.1.18! root 479: PSG_WaitState();
1.1.1.4 root 480:
1.1.1.8 root 481: IoMem[IoAccessCurrentAddress] = 0xff;
1.1.1.10 root 482:
1.1.1.11 root 483: if (LOG_TRACE_LEVEL(TRACE_PSG_READ))
484: {
485: int FrameCycles, HblCounterVideo, LineCycles;
486: Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
487: LOG_TRACE_PRINT("ym read void %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
488: IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress],
489: FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
490: }
1.1 root 491: }
1.1.1.7 root 492:
493:
494:
495: /*-----------------------------------------------------------------------*/
496: /**
1.1.1.10 root 497: * Write byte to 0xff8800. Set content of YM's address register.
1.1.1.7 root 498: */
1.1.1.10 root 499: void PSG_ff8800_WriteByte(void)
1.1.1.7 root 500: {
1.1.1.18! root 501: PSG_WaitState();
1.1.1.7 root 502:
1.1.1.11 root 503: if (LOG_TRACE_LEVEL(TRACE_PSG_WRITE))
504: {
505: int FrameCycles, HblCounterVideo, LineCycles;
506: Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
507: LOG_TRACE_PRINT("ym write %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
508: IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress],
509: FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
510: }
1.1.1.10 root 511:
512: PSG_Set_SelectRegister ( IoMem[IoAccessCurrentAddress] );
513: }
514:
515:
516: /*-----------------------------------------------------------------------*/
517: /**
518: * Write byte to 0xff8801. Set content of YM's address register under conditions.
519: * Address 0xff8801 is a shadow version of 0xff8800, so both addresses can't be written
520: * at the same time by the same instruction. This means only a .B access or
521: * a movep will have a valid effect, other accesses are ignored.
522: */
523: void PSG_ff8801_WriteByte(void)
524: {
525: if ( nIoMemAccessSize == SIZE_BYTE ) /* byte access or movep */
526: {
1.1.1.18! root 527: PSG_WaitState();
1.1.1.10 root 528:
1.1.1.11 root 529: if (LOG_TRACE_LEVEL(TRACE_PSG_WRITE))
530: {
531: int FrameCycles, HblCounterVideo, LineCycles;
532: Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
533: LOG_TRACE_PRINT("ym write %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
534: IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress],
535: FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
536: }
1.1.1.10 root 537:
538: PSG_Set_SelectRegister ( IoMem[IoAccessCurrentAddress] );
539: }
540:
541: else
542: { /* do nothing, just a trace if needed */
1.1.1.11 root 543: if (LOG_TRACE_LEVEL(TRACE_PSG_WRITE))
544: {
545: int FrameCycles, HblCounterVideo, LineCycles;
546: Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
547: LOG_TRACE_PRINT("ym write ignored %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
548: IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress],
549: FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
550: }
1.1.1.10 root 551: }
1.1.1.7 root 552: }
553:
554:
1.1.1.10 root 555: /*-----------------------------------------------------------------------*/
556: /**
557: * Write byte to 0xff8802. Set content of YM's data register.
558: */
559: void PSG_ff8802_WriteByte(void)
560: {
1.1.1.18! root 561: PSG_WaitState();
1.1.1.10 root 562:
1.1.1.11 root 563: if (LOG_TRACE_LEVEL(TRACE_PSG_WRITE))
564: {
565: int FrameCycles, HblCounterVideo, LineCycles;
566: Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
567: LOG_TRACE_PRINT("ym write %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
568: IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress],
569: FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
570: }
1.1.1.10 root 571:
572: PSG_Set_DataRegister ( IoMem[IoAccessCurrentAddress] );
573: }
574:
1.1.1.7 root 575:
576: /*-----------------------------------------------------------------------*/
577: /**
1.1.1.10 root 578: * Write byte to 0xff8803. Set content of YM's data register under conditions.
579: * Address 0xff8803 is a shadow version of 0xff8802, so both addresses can't be written
580: * at the same time by the same instruction. This means only a .B access or
581: * a movep will have a valid effect, other accesses are ignored.
1.1.1.7 root 582: */
1.1.1.10 root 583: void PSG_ff8803_WriteByte(void)
1.1.1.7 root 584: {
1.1.1.10 root 585: if ( nIoMemAccessSize == SIZE_BYTE ) /* byte access or movep */
586: {
1.1.1.18! root 587: PSG_WaitState();
1.1.1.10 root 588:
1.1.1.11 root 589: if (LOG_TRACE_LEVEL(TRACE_PSG_WRITE))
590: {
591: int FrameCycles, HblCounterVideo, LineCycles;
592: Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
593: LOG_TRACE_PRINT("ym write %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
594: IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress],
595: FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
596: }
1.1.1.10 root 597:
598: PSG_Set_DataRegister ( IoMem[IoAccessCurrentAddress] );
599: }
600:
601: else
602: { /* do nothing, just a trace if needed */
1.1.1.11 root 603: if (LOG_TRACE_LEVEL(TRACE_PSG_WRITE))
604: {
605: int FrameCycles, HblCounterVideo, LineCycles;
606: Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
607: LOG_TRACE_PRINT("ym write ignored %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
608: IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress],
609: FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
610: }
1.1.1.10 root 611: }
1.1.1.7 root 612: }
1.1.1.16 root 613:
614:
615: /* ------------------------------------------------------------------
616: * YM-2149 register content dump (for debugger info command)
617: */
1.1.1.17 root 618: void PSG_Info(FILE *fp, Uint32 dummy)
1.1.1.16 root 619: {
620: int i;
1.1.1.18! root 621: for(i = 0; i < ARRAY_SIZE(PSGRegisters); i++)
1.1.1.16 root 622: {
1.1.1.17 root 623: fprintf(fp, "Reg $%02X : $%02X\n", i, PSGRegisters[i]);
1.1.1.16 root 624: }
625: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.