Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Source/Teensy/DMAControl.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand All @@ -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
Expand Down
25 changes: 23 additions & 2 deletions Source/Teensy/MinimalBoot/Common/Common_Defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -346,21 +346,42 @@ 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;
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)
Expand Down
16 changes: 14 additions & 2 deletions Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_TeensyROM.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
109 changes: 109 additions & 0 deletions Source/Teensy/MinimalBoot/Common/IO_Handlers/StatusFunctions.c
Original file line number Diff line number Diff line change
Expand Up @@ -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<<Bit))) continue;
if(PriorVal & (1<<Bit)) PrefillFell++;
else PrefillRose++;
}
}

for(uint16_t ByteNum = 0; ByteNum < TestPageSize; ByteNum++)
PageBuf[ByteNum] = (ByteNum & 1) ? ValB : ValA;
PerformDMA(false, Address, PageBuf, TestPageSize, false);
CloseDMA();
PerformDMA(true, Address, PageBuf, TestPageSize, false);
CloseDMA();

uint32_t PassBad = 0;
for(uint16_t ByteNum = 0; ByteNum < TestPageSize; ByteNum++)
{
uint8_t Expected = (ByteNum & 1) ? ValB : ValA;
uint8_t Diff = PageBuf[ByteNum] ^ Expected;
if(Diff == 0) continue;
PassBad++;
XorMask |= Diff;
if(PageBuf[ByteNum] == PriorBuf[ByteNum]) Unchanged++; //write never landed, vs a partial byte that landed mid-settle
for(uint8_t Bit = 0; Bit < 8; Bit++)
{
if(!(Diff & (1<<Bit))) continue;
if(Expected & (1<<Bit)) BitFell[Bit]++;
else BitRose[Bit]++;
}
}
BadBytes += PassBad;
if(PassBad > 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()
Expand Down Expand Up @@ -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;
Expand Down
43 changes: 39 additions & 4 deletions Source/Teensy/SerUSBIO.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -448,22 +470,33 @@ 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;
nS_VICStart = Def_nS_VICStart;
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;
Expand All @@ -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");
Expand Down