@@ -58,16 +58,176 @@ enum enumHIRAM_States
5858
5959extern volatile uint8_t doReset ;
6060
61+ //HIRAM sense: how KernalCheck finds out, after a write to $00/$01, whether the KERNAL is banked in.
62+ // (The 6510 doesn't put the value of a $00/$01 write on the external data bus, so it can't be tracked from the writes.)
63+ // Skoe: on the first $E000+ read, drive A14 low with /EXROM+/GAME asserted (16K mode) and look at ROMH.
64+ // This needs the PLA to see the address the TR drives over the CPU's. On a C128 in C64 mode it doesn't: the
65+ // expansion port's address lines come through a transceiver that drives towards the port outside DMA, so ROMH never
66+ // answers, every sense reads "RAM", and the replacement image is never served.
67+ // DMA: right after the write, hold the CPU with /DMA for one cycle and, as bus master, read $A000 with /EXROM+/GAME
68+ // asserted: the PLA selects ROMH there only with HIRAM set. The cycle after a store to $00/$01 is always the next
69+ // opcode fetch, a read, so the CPU halts on it. Costs the CPU one cycle per write to $00/$01 (more when the VIC
70+ // holds the bus). Used on a C128.
71+ enum enumHIRAMSenseModes
72+ {
73+ KSense_Skoe = 0 ,
74+ KSense_DMA = 2 ,
75+ KSense_Auto = 9 , //DMA on a C128 (the menu's machine report), Skoe otherwise
76+ };
77+ uint32_t KSenseMode = KSense_Auto ; //serial tx# (Dbg_SerTimChg)
78+ uint8_t KSenseEff = KSense_Skoe ; //the method in effect
79+
80+ FLASHMEM void KSenseApplyMode ()
81+ {
82+ #ifdef Fab04_FullDMACapable
83+ KSenseEff = (KSenseMode == KSense_Auto ) ? ((IO1 [wRegVid_TOD_Clks ] & rvtcC128 ) ? KSense_DMA : KSense_Skoe ) :
84+ (KSenseMode == KSense_DMA ) ? KSense_DMA : KSense_Skoe ;
85+ #else
86+ KSenseEff = KSense_Skoe ;
87+ #endif
88+ if (KSenseEff == KSense_DMA ) Serial .println (PSTR ("Kernal HIRAM sense: DMA" ));
89+ else Serial .println (PSTR ("Kernal HIRAM sense: Skoe" ));
90+ }
91+
92+ #ifdef Fab04_FullDMACapable
93+ enum enumKDS_States
94+ {
95+ KDS_Idle ,
96+ KDS_Skip , //the sense cycle's rising-edge interrupt arrives late (in Phi2 low): drop it. It always arrives: the
97+ // Teensy core clears the GPIO interrupt flag before it calls isrPHI2, so the edge during the sense sets it again.
98+ KDS_WaitBus , //DMA asserted, BA was low: try the sense read on the next cycle
99+ KDS_AfterRead01 , //a write to $00/$01 right after a read of $00/$01: the first of a read-modify-write's two
100+ // writes, or a store after an indexed dummy read. The next cycle tells which.
101+ };
102+ volatile uint8_t KDS_State = KDS_Idle ;
103+ uint8_t KDS_Guess = HIRAM_State_ROM ; //last verdict, for a KERNAL read no sense could precede
104+ uint32_t KDS_Read01Cyc = 0 ; //ARM_DWT_CYCCNT of the last CPU read of $00/$01
105+ bool KDS_Read01Valid = false;
106+
107+ //Phi2 low (StartCycCnt = falling), /DMA asserted: one DMA read of $A000 with /GAME+/EXROM, address timing as DMAByte's.
108+ //false: bus not available (badline, sprite fetch), try again on the next cycle
109+ FASTRUN bool KDS_SenseCycle ()
110+ {
111+ WaitUntil_nS_fine (nS_DMABAWait );
112+ if (!GP9_BA (ReadGPIO9 )) return false;
113+
114+ uint32_t RegAddrBits = (0xA000 << 16 );
115+ CORE_PIN19_PORTSET = RegAddrBits ; //set address port value to be ready for output drive
116+ CORE_PIN19_PORTCLEAR = ~RegAddrBits & GP6_AddrMask ;
117+ WaitUntil_nS (nS_DMASetup ); //just ahead of Phi2 rising
118+ StartCycCnt = ARM_DWT_CYCCNT ;
119+ SetAddrBufsOut ;
120+ SetAddrPortDirOut ;
121+ SetExROMAssert ;
122+ SetGameAssert ;
123+
124+ WaitUntil_nS_fine (nS_KSenseSample );
125+ HIRAM_State = KDS_Guess = (GP9_ROMH (ReadGPIO9 )== 0 ? HIRAM_State_ROM : HIRAM_State_RAM );
126+
127+ WaitUntil_nS_fine (nS_DMADataSetup ); //keep the address valid as long as a DMA read does
128+ SetGameDeassert ;
129+ SetExROMDeassert ;
130+ SetAddrPortDirIn ;
131+ SetAddrBufsIn ;
132+ return true;
133+ }
134+
135+ //Phi2 low, /DMA asserted, StartCycCnt = falling
136+ FASTRUN void KDS_Run ()
137+ {
138+ if (!KDS_SenseCycle ())
139+ {
140+ KDS_State = KDS_WaitBus ;
141+ return ;
142+ }
143+ while (GP6_Phi2 (ReadGPIO6 )); //Phi2 falling after the sense cycle
144+ SetDMADeassert ; //release in Phi2 low, as isrPHI2's DMA_S_StartDisable does
145+ KDS_State = KDS_Skip ;
146+ }
147+
148+ //Phi2 high of the CPU's write to $00/$01
149+ FASTRUN void KDS_Begin ()
150+ {
151+ if (DMA_State != DMA_S_DisableReady )
152+ { //another DMA user (REU, remote memory access) owns the line
153+ HIRAM_State = KDS_Guess ;
154+ return ;
155+ }
156+ while (GP6_Phi2 (ReadGPIO6 )); //Phi2 falling
157+ StartCycCnt = ARM_DWT_CYCCNT ;
158+ WaitUntil_nS (nS_DMAAssert );
159+ SetDMAAssert ;
160+ KDS_Run ();
161+ }
162+
163+ //The DMA sense's work for a cycle off the common path: a sense pending, or a $00/$01 access.
164+ //true ends the cycle: nothing else may answer it
165+ FASTRUN bool KDS_Slow (uint16_t Address , bool R_Wn )
166+ {
167+ switch (KDS_State )
168+ {
169+ case KDS_Skip :
170+ KDS_State = KDS_Idle ;
171+ return true;
172+ case KDS_WaitBus :
173+ while (!GP6_Phi2 (ReadGPIO6 )); //a late call (in Phi2 low) waits for the next cycle
174+ while (GP6_Phi2 (ReadGPIO6 ));
175+ StartCycCnt = ARM_DWT_CYCCNT ;
176+ KDS_Run ();
177+ return true;
178+ }
179+ if ((Address & 0xfffe ) == 0 )
180+ {
181+ if (R_Wn )
182+ {
183+ KDS_Read01Cyc = ARM_DWT_CYCCNT ;
184+ KDS_Read01Valid = true;
185+ }
186+ else
187+ { //write to $00/$01
188+ HIRAM_State = HIRAM_State_Unknown ;
189+ bool AfterRead = KDS_Read01Valid && (ARM_DWT_CYCCNT - KDS_Read01Cyc ) < nSToCyc (1500 ); //the previous cycle
190+ KDS_Read01Valid = false;
191+ if (AfterRead && KDS_State != KDS_AfterRead01 )
192+ {
193+ KDS_State = KDS_AfterRead01 ;
194+ return true;
195+ }
196+ KDS_State = KDS_Idle ;
197+ KDS_Begin ();
198+ return true;
199+ }
200+ }
201+ if (KDS_State == KDS_AfterRead01 ) KDS_State = KDS_Idle ; //it was a store after a dummy read: this read is served on KDS_Guess
202+ return false;
203+ }
204+ #endif
205+
61206//______________________________________________________________________________________________
62207
63208FASTRUN bool KernalCheck (uint16_t Address , bool R_Wn )
64209{
210+ #ifdef Fab04_FullDMACapable
211+ if (KSenseEff == KSense_DMA )
212+ { //this runs ahead of every IO1/IO2 answer, so the common cycle costs only these tests: work added here answers IO late
213+ if ((KDS_State != KDS_Idle || (Address & 0xfffe ) == 0 ) && KDS_Slow (Address , R_Wn )) return true;
214+ }
215+ else
216+ #endif
65217 //monitor port $0001 for changes
66218 if (!R_Wn && (Address & 0xfffe ) == 0 ) HIRAM_State = HIRAM_State_Unknown ; //write to addr 0 or 1 causes re-evaluation of HIRAM
67219
68220 if (!R_Wn || (Address & 0xe000 ) != 0xe000 || !GP9_BA (ReadGPIO9 )) return false; //continue with cycle processing
69221
70222 // CPU read access from address $E000..$FFFF
223+ #ifdef Fab04_FullDMACapable
224+ if (HIRAM_State == HIRAM_State_Unknown && KSenseEff == KSense_DMA ) HIRAM_State = KDS_Guess ; //no sense could precede this read
225+ #endif
226+ #ifndef KERNAL_MONITOR_ONLY
227+ //HIRAM known to be RAM (no write to $00/$01 since it was sensed): the C64's own RAM answers, so leave /GAME and the
228+ //data bus alone. Asserting /GAME (Ultimax) here takes the read away from the RAM.
229+ if (HIRAM_State == HIRAM_State_RAM ) return true;
230+ #endif
71231
72232 //prepare modified Address for Skoe HIRAM check (if needed)
73233 uint32_t RegAddrBits = ((Address & 0xbfff ) << 16 ); //drive A14 low
@@ -206,6 +366,12 @@ FLASHMEM void InitHndlr_KERNALReplace_PreStart() //not called automatically via
206366 //Serial.printf("Read %lu Bytes in %lumS\n", CharNum, millis()-StartmS);
207367 Serial .printf ("Read %lu Bytes\nKernal replace Enabled\n" , CharNum );
208368 LoadFile .close ();
369+ KSenseApplyMode ();
370+ #ifdef Fab04_FullDMACapable
371+ KDS_State = KDS_Idle ;
372+ KDS_Read01Valid = false;
373+ KDS_Guess = HIRAM_State_ROM ;
374+ #endif
209375 delay (250 );
210376
211377 //hook kernal checks: runs before BASIC init, well before any PRG-load handshake (see
@@ -229,6 +395,10 @@ FLASHMEM void InitHndlr_KernalReplace()
229395 //hooked there, KernalCheck reads an unloaded KernalBin and drives GAME/EXROM over the cart's own.
230396 if (fBusSnoop != & HandshakeSnoop || KernalBin == NULL ) return ;
231397 HIRAM_State = HIRAM_State_Unknown ;
398+ #ifdef Fab04_FullDMACapable
399+ KDS_State = KDS_Idle ; //KernalCheck is unhooked during the handshake: no sense can be in flight
400+ KDS_Read01Valid = false;
401+ #endif
232402 PendingfBusSnoop = & KernalCheck ; //not fBusSnoop directly -- may still be mid PRG-load handshake, see HandshakeSnoop
233403#endif
234404}
0 commit comments