diff --git a/Source/Teensy/DMAControl.ino b/Source/Teensy/DMAControl.ino index 6966aebd..c0e320bb 100644 --- a/Source/Teensy/DMAControl.ino +++ b/Source/Teensy/DMAControl.ino @@ -10,8 +10,9 @@ uint8_t *DMA_Buffer; //These assume Fab04_DataBufAlwaysEnabled __attribute__((always_inline)) inline uint8_t DataPortWaitReadDMA() { // for "normal" (non-VIC) C64 write cycles - WaitUntil_nS(390); // nS_DataSetup=220 //takes a little longer for read data DMA - //too soon = bad reads + //takes a little longer than the nS_DataSetup=220 non-DMA case, too soon = bad reads + uint32_t Cyc_DMADataSetup = nSToCyc(nS_DMADataSetup); //convert outside the spin, nSToCyc() on a variable is a divide per pass + while((ARM_DWT_CYCCNT-StartCycCnt) < Cyc_DMADataSetup); uint32_t DataIn = ReadGPIO7; return ((DataIn & 0x0F) | ((DataIn >> 12) & 0xF0)); } @@ -25,7 +26,8 @@ __attribute__((always_inline)) inline void DataPortWriteWaitDMA(uint8_t Data) CORE_PIN10_PORTSET = RegBits; CORE_PIN10_PORTCLEAR = ~RegBits & GP7_DataMask; - WaitUntil_nS(430); // nS_DataHold = 390 (err), 470 OK, 430 OK(?) + uint32_t Cyc_DMADataHold = nSToCyc(nS_DMADataHold); //convert outside the spin, see DataPortWaitReadDMA + while((ARM_DWT_CYCCNT-StartCycCnt) < Cyc_DMADataHold); //not checking Phi2 state due to tight timing and early in cycle call can cause early exit SetDataPortDirIn; //set data ports back to inputs/default diff --git a/Source/Teensy/MinimalBoot/Common/Common_Defs.h b/Source/Teensy/MinimalBoot/Common/Common_Defs.h index 0f6fc817..e4d64d12 100644 --- a/Source/Teensy/MinimalBoot/Common/Common_Defs.h +++ b/Source/Teensy/MinimalBoot/Common/Common_Defs.h @@ -346,13 +346,32 @@ const uint8_t OutputPins[] = { #define Def_nS_DMASetupPAL 440 //400 delay from Phi2 falling to RW/Addr setup (just before rising edge) #define Def_nS_DMASetupNTSC 430 //380 too early will mess up VIC cycle (screen noise), too late will not set up R/W & addr lines fast enough (Write error) // 5/18/26: 440 not working for Rat NTSC for remote mem, reduced to 430 + +//Both are measured from StartCycCnt, which DMAByte() re-latches nS_DMASetup after Phi2 falling, not from Phi2 rising +#define Def_nS_DMADataSetupPAL 390 //delay to latching the data bus on a DMA read, too soon = bad reads +#define Def_nS_DMADataSetupNTSC 390 // 9/9/26: swept 310-450 on a flat NTSC C128 + TR+, clean at 350+, left at 390 +#define Def_nS_DMADataHoldPAL 430 //delay to releasing the data bus on a DMA write. 390 (err), 470 OK, 430 OK(?) +#define Def_nS_DMADataHoldNTSC 410 // 9/9/26: same rig, 455+ overruns Phi2 falling and collapses, 430-450 gives + // intermittent partial-byte errors, <=425 clean. PAL collapses at 485 (9/10/26, + // C64+Kawari in PAL, Ultimate agrees) and swept 385-475 clean over 12MB, but that + // board won't repro the 390 err above - it's forgiving, so PAL's partial-byte + // mode is untested rather than absent. //Other critical Timing #define Def_Cyc_KernProp 35 // Propagation delay for Kernal replace to sample ROMH to determine if HIRAM is asserted //C64 long bd/PAL: 10 fails (occasional misdetect of ram on rom cycle) 11 passes //C64c/PAL: 19 fails (occasional misdetect of ram on rom cycle) 20 passes //was set to 21, but testing on another C64c NTSC (short) was marginal after warmup. -uint32_t nS_MaxAdj = Def_nS_MaxAdjPAL; //default to PAL, updated on main menu load (wRegVid_TOD_Clks write) +//PAL-biased until the main menu's first wRegVid_TOD_Clks write. MaxAdj has to stay PAL here: NTSC's +// 993 is under PAL's 1015nS cycle, so a PAL machine would re-adjust on every interrupt. +//The hold is the problem. Autolaunch (Teensy.ino -> RemoteLaunch w/ DoCartDirect) starts a CRT without +// ever loading the C64 menu, so MainMenu.asm never writes the register and an NTSC machine keeps the +// PAL 430 - inside NTSC's own 430-450 partial-byte band. DMA is reachable there: the REU handler, +// and Write/ReadC64MemToken, which ProcessCommand() answers ahead of the busy check. +//Not fixed by dropping this to 410: that needs a PAL board that reproduces the 390 (err) below, and the +// one PAL rig measured so far does not. The real fix is timing Phi2 on the Teensy rather than +// waiting for the C64 to report it, which would retire this whole bootstrap. +uint32_t nS_MaxAdj = Def_nS_MaxAdjPAL; uint32_t nS_RWnReady = Def_nS_RWnReady; uint32_t nS_PLAprop = Def_nS_PLAprop; uint32_t nS_DataSetup = Def_nS_DataSetup; @@ -360,7 +379,9 @@ uint32_t nS_DataHold = Def_nS_DataHold; uint32_t nS_VICStart = Def_nS_VICStart; uint32_t nS_VICDHold = Def_nS_VICDHold; uint32_t nS_DMAAssert = Def_nS_DMAAssert; -uint32_t nS_DMASetup = Def_nS_DMASetupPAL; //default to PAL, updated on main menu load (wRegVid_TOD_Clks write) +uint32_t nS_DMASetup = Def_nS_DMASetupPAL; +uint32_t nS_DMADataSetup = Def_nS_DMADataSetupPAL; +uint32_t nS_DMADataHold = Def_nS_DMADataHoldPAL; uint32_t Cyc_KernProp = Def_Cyc_KernProp; __attribute__((always_inline)) inline void DataPortWriteWait(uint8_t Data) diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_TeensyROM.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_TeensyROM.c index d2ce9de1..07d7c291 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_TeensyROM.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_TeensyROM.c @@ -602,8 +602,20 @@ void IO1Hndlr_TeensyROM(uint8_t Address, bool R_Wn) case wRegVid_TOD_Clks: IO1[wRegVid_TOD_Clks]=Data; //make NTSC/PAL specific timing tweaks upon discovery - if (Data & 1) { nS_DMASetup = Def_nS_DMASetupNTSC; nS_MaxAdj = Def_nS_MaxAdjNTSC; } - else { nS_DMASetup = Def_nS_DMASetupPAL; nS_MaxAdj = Def_nS_MaxAdjPAL; } + if (Data & 1) + { + nS_DMASetup = Def_nS_DMASetupNTSC; + nS_MaxAdj = Def_nS_MaxAdjNTSC; + nS_DMADataSetup = Def_nS_DMADataSetupNTSC; + nS_DMADataHold = Def_nS_DMADataHoldNTSC; + } + else + { + nS_DMASetup = Def_nS_DMASetupPAL; + nS_MaxAdj = Def_nS_MaxAdjPAL; + nS_DMADataSetup = Def_nS_DMADataSetupPAL; + nS_DMADataHold = Def_nS_DMADataHoldPAL; + } break; case rwRegPageNumber: IO1[rwRegPageNumber]=Data; diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/StatusFunctions.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/StatusFunctions.c index d116e9da..144f7a1b 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/StatusFunctions.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/StatusFunctions.c @@ -732,6 +732,100 @@ FLASHMEM bool TestDMAPage(uint16_t Address, uint8_t BytePat) //SendMsgPrintf(" OK"); return true; } + +FLASHMEM bool TestDMAPattern(uint16_t Address, uint8_t PriorVal, uint8_t ValA, uint8_t ValB, uint16_t Passes) +{ + //Alternating ValA/ValB swings the data bus between DMA cycles, which a uniform fill never does. + // Pre-filling with PriorVal is what makes a dropped write visible - TestDMAPage() writing $ff + // over a page already holding $ff verifies clean either way. PriorVal==ValA==ValB is a control. + uint8_t PageBuf[TestPageSize], PriorBuf[TestPageSize]; + uint32_t BadBytes = 0, WorstPass = 0, Unchanged = 0, PrefillBad = 0; + uint32_t BitFell[8] = {0}, BitRose[8] = {0}; + uint32_t PrefillFell = 0, PrefillRose = 0; + uint8_t XorMask = 0, PrefillXor = 0; + + for(uint16_t Pass = 0; Pass < Passes; Pass++) + { + memset(PageBuf, PriorVal, TestPageSize); + PerformDMA(false, Address, PageBuf, TestPageSize, false); + CloseDMA(); + PerformDMA(true, Address, PriorBuf, TestPageSize, false); //what the page really holds now + CloseDMA(); + //the prefill is a full-swing write too - $00 over $ff and back - so it needs the same + // partial-byte vs whole-byte detail as the pattern write, not just a count + for(uint16_t ByteNum = 0; ByteNum < TestPageSize; ByteNum++) + { + uint8_t Diff = PriorBuf[ByteNum] ^ PriorVal; + if(Diff == 0) continue; + PrefillBad++; + PrefillXor |= Diff; + for(uint8_t Bit = 0; Bit < 8; Bit++) + { + if(!(Diff & (1< WorstPass) WorstPass = PassBad; + } + + uint32_t TotalBytes = (uint32_t)Passes * TestPageSize; + uint32_t Fell = 0, Rose = 0; + for(uint8_t Bit = 0; Bit < 8; Bit++) { Fell += BitFell[Bit]; Rose += BitRose[Bit]; } + + if(ValA == ValB) Serial.printf("$%02x", ValA); + else Serial.printf("$%02x/$%02x alt", ValA, ValB); + Serial.printf(" over $%02x @ $%04x: %lu bad of %lu bytes", PriorVal, Address, BadBytes, TotalBytes); + if(PrefillBad) Serial.printf(", pre-fill %lu bad", PrefillBad); + if(BadBytes == 0 && PrefillBad == 0) + { + Serial.printf(" CLEAN\n"); + return true; + } + Serial.printf("\n"); + if(PrefillBad) + Serial.printf(" pre-fill: xor mask $%02x, 1->0: %lu, 0->1: %lu\n", + PrefillXor, PrefillFell, PrefillRose); + if(BadBytes) + { + Serial.printf(" worst page: %lu, unchanged: %lu, xor mask $%02x, 1->0: %lu, 0->1: %lu\n", + WorstPass, Unchanged, XorMask, Fell, Rose); + //Not a drive-strength measure - a uniform pattern forces every failing bit into one column + Serial.printf(" bit: "); + for(int8_t Bit = 7; Bit >= 0; Bit--) Serial.printf("%8d", Bit); + Serial.printf("\n 1->0: "); + for(int8_t Bit = 7; Bit >= 0; Bit--) Serial.printf("%8lu", BitFell[Bit]); + Serial.printf("\n 0->1: "); + for(int8_t Bit = 7; Bit >= 0; Bit--) Serial.printf("%8lu", BitRose[Bit]); + Serial.printf("\n"); + } + return false; +} #endif FLASHMEM void ExpPortDMA() @@ -862,6 +956,21 @@ FLASHMEM void ExpPortDMA() SendMsgPrintf(" OK"); +//DMA bit transitions + SendMsgPrintfln("DMA Bit Transition Tests"); + //Unlike the uniform fills above, these set the prior page contents so the data lines actually move + if (!TestDMAPattern(0xc000, 0xff, 0xff, 0xff, 16) || //control: nothing has to change + !TestDMAPattern(0xc000, 0x00, 0xff, 0xff, 64) || //uniform, over the opposite value + !TestDMAPattern(0xc000, 0xff, 0x00, 0x00, 64) || + !TestDMAPattern(0xc000, 0x00, 0x00, 0xff, 64) || //alternating: bus swings each cycle + !TestDMAPattern(0xc000, 0xff, 0x55, 0xaa, 64)) + { + SendMsgPrintfln(" Failed, details on serial"); + return; + } + SendMsgPrintf(" OK"); + + //IRQ SendMsgPrintfln("IRQ Test"); IO1[wRegIRQNMITest] = 0; diff --git a/Source/Teensy/SerUSBIO.ino b/Source/Teensy/SerUSBIO.ino index 09571bf4..b322365f 100644 --- a/Source/Teensy/SerUSBIO.ino +++ b/Source/Teensy/SerUSBIO.ino @@ -92,7 +92,7 @@ FLASHMEM void ServiceSerial(Stream *ThisCmdChannel) // break; // *** The rest of these cases are used for debug/testing only - // u,v,w,y + // u,v,w,y,z #ifdef Dbg_SerDMA #ifdef Fab04_FullDMACapable case 'u': //Perform DMA Write @@ -179,6 +179,28 @@ FLASHMEM void ServiceSerial(Stream *ThisCmdChannel) CloseDMA(); } break; + case 'z': //DMA bit-transition test, for sweeping the DMA data timing constants + { + const uint16_t Addr = 0xc000; + uint32_t Passes = 0; + GetDigits(3, &Passes); //z### : 256 byte pages written per pattern + if(Passes == 0) Passes = 64; + //restore rather than enable on exit - ExpPortDMA and some handlers leave these off on purpose + bool ENETWasOn = NVIC_IS_ENABLED(IRQ_ENET), PITWasOn = NVIC_IS_ENABLED(IRQ_PIT); + NVIC_DISABLE_IRQ(IRQ_ENET); //keep the bus quiet while testing, as ExpPortDMA does + NVIC_DISABLE_IRQ(IRQ_PIT); + Serial.printf("\nDMA pattern test: DMADataHold=%lu DMADataSetup=%lu DMASetup=%lu Passes=%lu" + "\n (overwrites C64 $%04x-$%04x)\n", + nS_DMADataHold, nS_DMADataSetup, nS_DMASetup, Passes, Addr, Addr+255); + TestDMAPattern(Addr, 0xff, 0xff, 0xff, Passes); //control: nothing has to change + TestDMAPattern(Addr, 0x00, 0xff, 0xff, Passes); //uniform, over the opposite value + TestDMAPattern(Addr, 0xff, 0x00, 0x00, Passes); + TestDMAPattern(Addr, 0x00, 0x00, 0xff, Passes); //alternating: bus swings each cycle + TestDMAPattern(Addr, 0xff, 0x55, 0xaa, Passes); + if(PITWasOn) NVIC_ENABLE_IRQ(IRQ_PIT); + if(ENETWasOn) NVIC_ENABLE_IRQ(IRQ_ENET); + } + break; #ifdef USE_PSRAM case 'y': //Load REU PSRAM from file { // example: y/reu/nuvies/speed.reu @@ -448,11 +470,19 @@ FLASHMEM void ServiceSerial(Stream *ThisCmdChannel) case 'e': //nS_DMASetup change GetDigits(3, &nS_DMASetup); break; + case 'w': //nS_DMADataHold change + GetDigits(3, &nS_DMADataHold); + break; + case 'y': //nS_DMADataSetup change + GetDigits(3, &nS_DMADataSetup); + break; case 'k': //Cyc_KernProp change GetDigits(2, &Cyc_KernProp); break; case 'd': //Set Defaults - nS_MaxAdj = Def_nS_MaxAdjPAL; + { //match what detection would have set, or a sweep resumes from the wrong standard's values + bool IsNTSC = (IO1[wRegVid_TOD_Clks] & 1); + nS_MaxAdj = IsNTSC ? Def_nS_MaxAdjNTSC : Def_nS_MaxAdjPAL; nS_PLAprop = Def_nS_PLAprop; nS_DataSetup = Def_nS_DataSetup; nS_DataHold = Def_nS_DataHold; @@ -460,10 +490,13 @@ FLASHMEM void ServiceSerial(Stream *ThisCmdChannel) nS_VICDHold = Def_nS_VICDHold; nS_RWnReady = Def_nS_RWnReady; nS_DMAAssert = Def_nS_DMAAssert; - nS_DMASetup = Def_nS_DMASetupPAL; + nS_DMASetup = IsNTSC ? Def_nS_DMASetupNTSC : Def_nS_DMASetupPAL; + nS_DMADataHold = IsNTSC ? Def_nS_DMADataHoldNTSC : Def_nS_DMADataHoldPAL; + nS_DMADataSetup = IsNTSC ? Def_nS_DMADataSetupNTSC : Def_nS_DMADataSetupPAL; Cyc_KernProp = Def_Cyc_KernProp; - CmdChannel->printf("Defaults set\n"); + CmdChannel->printf("Defaults set (%s)\n", IsNTSC ? "NTSC" : "PAL"); break; + } default: CmdChannel->printf("No changes\n"); break; @@ -478,6 +511,8 @@ FLASHMEM void ServiceSerial(Stream *ThisCmdChannel) CmdChannel->printf("\t nS_VICDHold %03d (ti###)\n", nS_VICDHold); CmdChannel->printf("\t nS_DMAAssert %03d (ta###)\n", nS_DMAAssert); CmdChannel->printf("\t nS_DMASetup %03d (te###)\n", nS_DMASetup); + CmdChannel->printf("\t nS_DMADataHold %03d (tw###)\n", nS_DMADataHold); + CmdChannel->printf("\t nS_DMADataSetup %03d (ty###)\n", nS_DMADataSetup); CmdChannel->printf("\t Cyc_KernProp %02d (tk##)\n", Cyc_KernProp); CmdChannel->printf("\tSet Defaults (td)\n");