Phasor: support SSI263 speech for Mockingboard mode (#777)

. Improved DEVICE_SELECT' I/O support (including Echo+).
. SSI263_Read() only for Phasor in Phasor mode.
. save-state: v6 (Phasor unit): phasor mode extended.
SSI263 (MB/Phasor) bug fix for when RESET/Power-cycle during phoneme playback.
Logging:
. improved for SSI263
. in CPU.cpp, added 'IRQ' / 'ISR-end'
This commit is contained in:
tomcw 2020-04-19 21:00:37 +01:00
parent 22806c6c59
commit 9f49820a8d
2 changed files with 143 additions and 41 deletions

View File

@ -98,6 +98,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#endif
#include "Video.h"
#include "NTSC.h"
#include "Log.h"
#include "z80emu.h"
#include "Z80VICE/z80.h"
@ -105,6 +106,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "YamlHelper.h"
#define LOG_IRQ_TAKEN_AND_RTI 0
// 6502 Accumulator Bit Flags
#define AF_SIGN 0x80
#define AF_OVERFLOW 0x40
@ -243,6 +246,10 @@ static __forceinline void DoIrqProfiling(DWORD uCycles)
if(regs.ps & AF_INTERRUPT)
return; // Still in Apple's ROM
#if LOG_IRQ_TAKEN_AND_RTI
LogOutput("ISR-end\n\n");
#endif
g_nCycleIrqEnd = g_nCumulativeCycles + uCycles;
g_nCycleIrqTime = (UINT) (g_nCycleIrqEnd - g_nCycleIrqStart);
@ -442,6 +449,9 @@ static __forceinline void IRQ(ULONG& uExecutedCycles, BOOL& flagc, BOOL& flagn,
regs.pc = * (WORD*) (mem+0xFFFE);
UINT uExtraCycles = 0; // Needed for CYC(a) macro
CYC(7)
#if defined(_DEBUG) && LOG_IRQ_TAKEN_AND_RTI
LogOutput("IRQ\n");
#endif
}
g_irqOnLastOpcodeCycle = false;

View File

@ -187,7 +187,8 @@ static bool g_bMBAvailable = false;
static SS_CARDTYPE g_SoundcardType = CT_Empty; // Use CT_Empty to mean: no soundcard
static bool g_bPhasorEnable = false;
static BYTE g_nPhasorMode = 0; // 0=Mockingboard emulation, 1=Phasor native
enum PHASOR_MODE {PH_Mockingboard=0, PH_UNDEF1, PH_UNDEF2, PH_UNDEF3, PH_UNDEF4, PH_Phasor/*=5*/, PH_UNDEF6, PH_EchoPlus/*=7*/};
static PHASOR_MODE g_phasorMode = PH_Mockingboard;
static UINT g_PhasorClockScaleFactor = 1; // for save-state only
//-------------------------------------
@ -363,8 +364,7 @@ static void UpdateIFR(SY6522_AY8910* pMB, BYTE clr_ifr, BYTE set_ifr=0)
// NB. Mockingboard generates IRQ on both 6522s:
// . SSI263's IRQ (A/!R) is routed via the 2nd 6522 (at $Cx80) and must generate a 6502 IRQ (not NMI)
// . SC-01's IRQ (A/!R) is also routed via a (2nd?) 6522
// Phasor's SSI263 appears to be wired directly to the 6502's IRQ (ie. not via a 6522)
// . I assume Phasor's 6522s just generate 6502 IRQs (not NMIs)
// Phasor's SSI263 IRQ (A/!R) line is *also* wired directly to the 6502's IRQ (as well as the 6522's CA1)
if (bIRQ)
CpuIrqAssert(IS_6522);
@ -394,7 +394,7 @@ static void SY6522_Write(BYTE nDevice, BYTE nReg, BYTE nValue)
if(g_bPhasorEnable)
{
int nAY_CS = (g_nPhasorMode & 1) ? (~(nValue >> 3) & 3) : 1;
int nAY_CS = (g_phasorMode == PH_Phasor) ? (~(nValue >> 3) & 3) : 1;
if(nAY_CS & 1)
AY8910_Write(nDevice, nReg, nValue, 0);
@ -590,14 +590,14 @@ const BYTE CONTROL_MASK = 0x80;
const BYTE ARTICULATION_MASK = 0x70;
const BYTE AMPLITUDE_MASK = 0x0F;
static BYTE SSI263_Read(BYTE nDevice, BYTE nReg)
static BYTE SSI263_Read(BYTE nDevice, ULONG nExecutedCycles)
{
SY6522_AY8910* pMB = &g_MB[nDevice];
// Regardless of register, just return inverted A/!R in bit7
// . A/!R is low for IRQ
return pMB->SpeechChip.CurrentMode << 7;
return MemReadFloatingBus(pMB->SpeechChip.CurrentMode & 1, nExecutedCycles);
}
static void SSI263_Write(BYTE nDevice, BYTE nReg, BYTE nValue)
@ -609,12 +609,22 @@ static void SSI263_Write(BYTE nDevice, BYTE nReg, BYTE nValue)
case SSI_DURPHON:
#if LOG_SSI263
if(g_fh) fprintf(g_fh, "DUR = 0x%02X, PHON = 0x%02X\n\n", nValue>>6, nValue&PHONEME_MASK);
LogOutput("DUR = %d, PHON = 0x%02X\n", nValue>>6, nValue&PHONEME_MASK);
#endif
// Datasheet is not clear, but a write to DURPHON must clear the IRQ
if (pMB->sy6522.PCR == 0x0C)
UpdateIFR(pMB, IxR_PERIPHERAL);
else // Phasor's SSI263.IRQ line appears to be wired directly to IRQ (Bypassing the 6522)
// Notes:
// . Phasor's text-to-speech playback has no CTL H->L
// - ISR just writes CTL=0 (and new ART+AMP values), and writes DUR=x (and new PHON)
// - since no CTL H->L, then DUR value doesn't take affect (so continue using previous)
// - so the write to DURPHON must clear the IRQ
// . Does a write of CTL=0 clear IRQ? (ie. CTL 0->0)
// . Does a write of CTL=1 clear IRQ? (ie. CTL 0->1)
// - SSI263 datasheet says: "Setting the Control bit (CTL) to a logic one puts the device into Power Down mode..."
// . Does phoneme output only happen when CTL=0? (Otherwise device is in PD mode)
// SSI263 datasheet is not clear, but a write to DURPHON must clear the IRQ.
// NB. For Mockingboard, A/!R is ack'ed by 6522's PCR handshake.
if (g_bPhasorEnable && g_phasorMode == PH_Phasor)
CpuIrqDeassert(IS_SPEECH);
pMB->SpeechChip.CurrentMode &= ~1; // Clear SSI263's D7 pin
@ -623,7 +633,6 @@ static void SSI263_Write(BYTE nDevice, BYTE nReg, BYTE nValue)
g_nSSI263Device = nDevice;
// Phoneme output not dependent on CONTROL bit
SSI263_Play(nValue & PHONEME_MASK);
break;
case SSI_INFLECT:
@ -632,6 +641,7 @@ static void SSI263_Write(BYTE nDevice, BYTE nReg, BYTE nValue)
#endif
pMB->SpeechChip.Inflection = nValue;
break;
case SSI_RATEINF:
#if LOG_SSI263
if(g_fh) fprintf(g_fh, "RATE = 0x%02X, INF = 0x%02X\n", nValue>>4, nValue&0x0F);
@ -641,19 +651,40 @@ static void SSI263_Write(BYTE nDevice, BYTE nReg, BYTE nValue)
case SSI_CTTRAMP:
#if LOG_SSI263
if(g_fh) fprintf(g_fh, "CTRL = %d, ART = 0x%02X, AMP=0x%02X\n", nValue>>7, (nValue&ARTICULATION_MASK)>>4, nValue&AMPLITUDE_MASK);
//
{
bool H2L = (pMB->SpeechChip.CtrlArtAmp & CONTROL_MASK) && !(nValue & CONTROL_MASK);
char newMode[20];
sprintf_s(newMode, sizeof(newMode), "(new mode=%d)", pMB->SpeechChip.DurationPhoneme>>6);
LogOutput("CTRL = %d->%d, ART = 0x%02X, AMP=0x%02X %s\n", pMB->SpeechChip.CtrlArtAmp>>7, nValue>>7, (nValue&ARTICULATION_MASK)>>4, nValue&AMPLITUDE_MASK, H2L?newMode:"");
}
#endif
if((pMB->SpeechChip.CtrlArtAmp & CONTROL_MASK) && !(nValue & CONTROL_MASK)) // H->L
{
pMB->SpeechChip.CurrentMode = pMB->SpeechChip.DurationPhoneme & DURATION_MODE_MASK;
if (pMB->SpeechChip.CurrentMode == MODE_IRQ_DISABLED)
{
// "Disables A/!R output only; does not change previous A/!R response" (SSI263 datasheet)
// CpuIrqDeassert(IS_SPEECH);
}
}
pMB->SpeechChip.CtrlArtAmp = nValue;
// "Setting the Control bit (CTL) to a logic one puts the device into Power Down mode..." (SSI263 datasheet)
if (pMB->SpeechChip.CtrlArtAmp & CONTROL_MASK)
{
// CpuIrqDeassert(IS_SPEECH);
// pMB->SpeechChip.CurrentMode &= ~1; // Clear SSI263's D7 pin
}
break;
case SSI_FILFREQ:
case SSI_FILFREQ: // RegAddr.b2=1 (b1 & b0 are: don't care)
default:
#if LOG_SSI263
if(g_fh) fprintf(g_fh, "FFREQ = 0x%02X\n", nValue);
#endif
pMB->SpeechChip.FilterFreq = nValue;
break;
default:
break;
}
}
@ -990,20 +1021,34 @@ static DWORD WINAPI SSI263Thread(LPVOID lpParameter)
//if(g_fh) fprintf(g_fh, "IRQ: Phoneme complete (0x%02X)\n\n", g_nCurrentActivePhoneme);
#endif
if (g_nCurrentActivePhoneme < 0)
continue; // On CTRL+RESET or power-cycle (during phoneme playback): ResetState() is called, which set g_nCurrentActivePhoneme=-1
SSI263Voice[g_nCurrentActivePhoneme].bActive = false;
g_nCurrentActivePhoneme = -1;
// Phoneme complete, so generate IRQ if necessary
SY6522_AY8910* pMB = &g_MB[g_nSSI263Device];
if (!g_bVotraxPhoneme && pMB->SpeechChip.CurrentMode != MODE_IRQ_DISABLED)
if (!g_bVotraxPhoneme)
{
pMB->SpeechChip.CurrentMode |= 1; // Set SSI263's D7 pin
// Always set SSI263's D7 pin regardless of SSI263 mode (DR1:0), including MODE_IRQ_DISABLED
pMB->SpeechChip.CurrentMode |= 1; // Set SSI263's D7 pin
if (pMB->sy6522.PCR == 0x0C)
if (pMB->SpeechChip.CurrentMode != MODE_IRQ_DISABLED)
{
if (pMB->sy6522.PCR == 0x0C && (!g_bPhasorEnable || (g_bPhasorEnable && g_phasorMode == PH_Mockingboard)))
{
UpdateIFR(pMB, 0, IxR_PERIPHERAL);
else // Phasor's SSI263.IRQ line appears to be wired directly to IRQ (Bypassing the 6522)
pMB->SpeechChip.CurrentMode &= ~1; // Clear SSI263's D7 pin (cleared by 6522's PCR CA1/CA2 handshake)
// NB. Don't set CTL=1, as Mockingboard(SMS) speech doesn't work (sets MODE_IRQ_DISABLED mode)
//pMB->SpeechChip.CtrlArtAmp |= CONTROL_MASK; // 6522's CA2 sets Power Down mode (pin 18), which sets Control bit
}
else if (g_bPhasorEnable && g_phasorMode == PH_Phasor) // Phasor's SSI263 IRQ (A/!R) line is *also* wired directly to the 6502's IRQ (as well as the 6522's CA1)
{
CpuIrqAssert(IS_SPEECH);
}
}
}
//
@ -1470,7 +1515,7 @@ static void ResetState()
g_bMB_RegAccessedFlag = false;
g_bMB_Active = false;
g_nPhasorMode = 0;
g_phasorMode = PH_Mockingboard;
g_PhasorClockScaleFactor = 1;
g_uLastMBUpdateCycle = 0;
@ -1499,6 +1544,8 @@ void MB_Reset() // CTRL+RESET or power-cycle
//-----------------------------------------------------------------------------
// Echo+ mode - Phasor's 2nd 6522 is mapped to every 16-byte offset in $Cnxx (Echo+ has a single 6522 controlling two AY-3-8913's)
static BYTE __stdcall MB_Read(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, ULONG nExecutedCycles)
{
if (g_bFullSpeed)
@ -1526,11 +1573,13 @@ static BYTE __stdcall MB_Read(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, ULO
if(nMB != 0) // Slot4 only
return MemReadFloatingBus(nExecutedCycles);
int CS;
if(g_nPhasorMode & 1)
CS = ( ( nAddr & 0x80 ) >> 6 ) | ( ( nAddr & 0x10 ) >> 4 ); // 0, 1, 2 or 3
else // Mockingboard Mode
int CS = 0;
if (g_phasorMode == PH_Mockingboard)
CS = ( ( nAddr & 0x80 ) >> 7 ) + 1; // 1 or 2
else if (g_phasorMode == PH_Phasor)
CS = ( ( nAddr & 0x80 ) >> 6 ) | ( ( nAddr & 0x10 ) >> 4 ); // 0, 1, 2 or 3
else if (g_phasorMode == PH_EchoPlus)
CS = 2;
BYTE nRes = 0;
@ -1542,9 +1591,10 @@ static BYTE __stdcall MB_Read(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, ULO
bool bAccessedDevice = (CS & 3) ? true : false;
if((nOffset >= SSI263_Offset) && (nOffset <= (SSI263_Offset+0x05)))
if ((g_phasorMode == PH_Phasor) && ((nAddr & 0xD0) == 0x40)) // $Cn4x and $Cn6x (Mockingboard mode: SSI263.bit7 not readable)
{
nRes |= SSI263_Read(nMB, nAddr&0xf);
_ASSERT(!bAccessedDevice);
nRes = SSI263_Read(nMB*2+1, nExecutedCycles); // SSI263 only drives bit7
bAccessedDevice = true;
}
@ -1555,8 +1605,8 @@ static BYTE __stdcall MB_Read(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, ULO
return SY6522_Read(nMB*NUM_DEVS_PER_MB + SY6522_DEVICE_A, nAddr&0xf);
else if((nOffset >= SY6522B_Offset) && (nOffset <= (SY6522B_Offset+0x0F)))
return SY6522_Read(nMB*NUM_DEVS_PER_MB + SY6522_DEVICE_B, nAddr&0xf);
else if((nOffset >= SSI263_Offset) && (nOffset <= (SSI263_Offset+0x05)))
return SSI263_Read(nMB, nAddr&0xf);
// else if((nOffset >= SSI263_Offset) && (nOffset <= (SSI263_Offset+0x07)))
// return SSI263_Read(nMB*2+1, nExecutedCycles); // SSI263 only drives bit7 -- TODO confirm with real MB h/w
else
return MemReadFloatingBus(nExecutedCycles);
}
@ -1592,10 +1642,12 @@ static BYTE __stdcall MB_Write(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, UL
int CS;
if(g_nPhasorMode & 1)
CS = ( ( nAddr & 0x80 ) >> 6 ) | ( ( nAddr & 0x10 ) >> 4 ); // 0, 1, 2 or 3
else // Mockingboard Mode
if (g_phasorMode == PH_Mockingboard)
CS = ( ( nAddr & 0x80 ) >> 7 ) + 1; // 1 or 2
else if (g_phasorMode == PH_Phasor)
CS = ( ( nAddr & 0x80 ) >> 6 ) | ( ( nAddr & 0x10 ) >> 4 ); // 0, 1, 2 or 3
else if (g_phasorMode == PH_EchoPlus)
CS = 2;
if(CS & 1)
SY6522_Write(nMB*NUM_DEVS_PER_MB + SY6522_DEVICE_A, nAddr&0xf, nValue);
@ -1603,8 +1655,16 @@ static BYTE __stdcall MB_Write(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, UL
if(CS & 2)
SY6522_Write(nMB*NUM_DEVS_PER_MB + SY6522_DEVICE_B, nAddr&0xf, nValue);
if((nOffset >= SSI263_Offset) && (nOffset <= (SSI263_Offset+0x05)))
SSI263_Write(nMB*2+1, nAddr&0xf, nValue); // Second 6522 is used for speech chip
int CS_SSI263 = (g_phasorMode == PH_Mockingboard) ? (nAddr & 0xE0) == 0x40 // Mockingboard: $Cn4x
: (g_phasorMode == PH_Phasor) ? (nAddr & 0xC0) == 0x40 // Phasor: $Cn4x and $Cn6x
: 0; // Echo+
if (CS_SSI263)
{
// NB. Mockingboard mode: writes to $Cn4x/SSI263 also get written to 1st 6522 (have confirmed on real Phasor h/w)
_ASSERT( (g_phasorMode == PH_Mockingboard && (CS==0 || CS==1)) || (g_phasorMode == PH_Phasor && (CS==0)) );
SSI263_Write(nMB*2+1, nAddr&0x7, nValue); // Second 6522 is used for speech chip
}
return 0;
}
@ -1613,23 +1673,45 @@ static BYTE __stdcall MB_Write(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, UL
SY6522_Write(nMB*NUM_DEVS_PER_MB + SY6522_DEVICE_A, nAddr&0xf, nValue);
else if((nOffset >= SY6522B_Offset) && (nOffset <= (SY6522B_Offset+0x0F)))
SY6522_Write(nMB*NUM_DEVS_PER_MB + SY6522_DEVICE_B, nAddr&0xf, nValue);
else if((nOffset >= SSI263_Offset) && (nOffset <= (SSI263_Offset+0x05)))
SSI263_Write(nMB*2+1, nAddr&0xf, nValue); // Second 6522 is used for speech chip
else if((nOffset >= SSI263_Offset) && (nOffset <= (SSI263_Offset+0x07)))
SSI263_Write(nMB*2+1, nAddr&0x7, nValue); // Second 6522 is used for speech chip -- TODO confirm with real MB h/w that writes go to 1st 6522
return 0;
}
//-----------------------------------------------------------------------------
// Phasor's DEVICE SELECT' logic:
// . if addr.[b3]==1, then clear the card's mode bits b2:b0
// . if any of addr.[b2:b0] are a logic 1, then set these bits in the card's mode
//
// Example DEVICE SELECT' accesses for Phasor in slot-4: (from empirical observations on real Phasor h/w)
// 1)
// . RESET -> Mockingboard mode (b#000)
// . $C0C5 -> Phasor mode (b#101)
// 2)
// . RESET -> Mockingboard mode (b#000)
// . $C0C1, then $C0C4 (or $C0C4, then $C0C1) -> Phasor mode (b#101)
// . $C0C2 -> Echo+ mode (b#111)
// . $C0C5 -> remaing in Echo+ mode (b#111)
// So $C0C5 seemingly results in 2 different modes.
//
static BYTE __stdcall PhasorIO(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, ULONG nExecutedCycles)
{
if(!g_bPhasorEnable)
if (!g_bPhasorEnable)
return MemReadFloatingBus(nExecutedCycles);
if(g_nPhasorMode < 2)
g_nPhasorMode = nAddr & 1;
UINT bits = (UINT) g_phasorMode;
if (nAddr & 8)
bits = 0;
bits |= (nAddr & 7);
g_phasorMode = (PHASOR_MODE) bits;
g_PhasorClockScaleFactor = (nAddr & 4) ? 2 : 1;
if (g_phasorMode == PH_Mockingboard || g_phasorMode == PH_EchoPlus)
g_PhasorClockScaleFactor = 1;
else if (g_phasorMode == PH_Phasor)
g_PhasorClockScaleFactor = 2;
AY8910_InitClock((int)(Get6502BaseClock() * g_PhasorClockScaleFactor));
@ -1972,7 +2054,8 @@ void MB_GetSnapshot_v1(SS_CARD_MOCKINGBOARD_v1* const pSS, const DWORD dwSlot)
// 3: Added: Unit state - GH#320
// 4: Added: 6522 timerIrqDelay - GH#652
// 5: Added: Unit state-B (Phasor only) - GH#659
const UINT kUNIT_VERSION = 5;
// 6: Changed SS_YAML_KEY_PHASOR_MODE from (0,1) to (0,5,7)
const UINT kUNIT_VERSION = 6;
const UINT NUM_MB_UNITS = 2;
const UINT NUM_PHASOR_UNITS = 2;
@ -2235,7 +2318,7 @@ void Phasor_SaveSnapshot(YamlSaveHelper& yamlSaveHelper, const UINT uSlot)
YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE);
yamlSaveHelper.SaveUint(SS_YAML_KEY_PHASOR_CLOCK_SCALE_FACTOR, g_PhasorClockScaleFactor);
yamlSaveHelper.SaveUint(SS_YAML_KEY_PHASOR_MODE, g_nPhasorMode);
yamlSaveHelper.SaveUint(SS_YAML_KEY_PHASOR_MODE, g_phasorMode);
for(UINT i=0; i<NUM_PHASOR_UNITS; i++)
{
@ -2269,7 +2352,16 @@ bool Phasor_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version
throw std::string("Card: wrong version");
g_PhasorClockScaleFactor = yamlLoadHelper.LoadUint(SS_YAML_KEY_PHASOR_CLOCK_SCALE_FACTOR);
g_nPhasorMode = yamlLoadHelper.LoadUint(SS_YAML_KEY_PHASOR_MODE);
UINT phasorMode = yamlLoadHelper.LoadUint(SS_YAML_KEY_PHASOR_MODE);
if (version < 6)
{
if (phasorMode == 0)
phasorMode = PH_Mockingboard;
else
phasorMode = PH_Phasor;
}
g_phasorMode = (PHASOR_MODE) phasorMode;
AY8910UpdateSetCycles();