mac-rom-simm-programmer/tests/simm_electrical_test.c
Doug Brown 82df6ea459 Fix a few minor issues
I noticed that after I implemented the SPI optimization of cycle
counting instead of polling on SPIF, the first "normal" SPI transaction
I tried would fail. This is because nothing was clearing the SPIF flag
anymore, and the normal SPI driver still looks at it. So it was thinking
that the latest transaction was already completed (it wasn't). Worked
around this by making sure we clear the flag in SPI_Assert. I'm not
concerned about performance impact here because the actual clean SPI
driver is not used in performance-bound situations.

Fixed an issue that identified the wrong pins as shorted to ground in
the electrical test functionality. Whoops!
2020-11-27 00:16:35 -08:00

474 lines
14 KiB
C

/*
* simm_electrical_test.c
*
* Created on: Nov 26, 2011
* Author: Doug
*
* Copyright (C) 2011-2020 Doug Brown
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "simm_electrical_test.h"
#include "../hal/parallel_bus.h"
#include "hardware.h"
/// The index of the highest SIMM address pin
#define SIMM_HIGHEST_ADDRESS_LINE 20
/// Mask that represents every SIMM address pin
#define SIMM_ADDRESS_PINS_MASK ((1UL << (SIMM_HIGHEST_ADDRESS_LINE + 1)) - 1)
/// The index of the highest SIMM data pin
#define SIMM_HIGHEST_DATA_LINE 31
/// Mask that represents every SIMM data pin
#define SIMM_DATA_PINS_MASK 0xFFFFFFFFUL
/// Milliseconds to wait before testing for shorts
#define DELAY_SETTLE_TIME_MS 20
/// The index reported as a short when it's a ground short
#define GROUND_FAIL_INDEX 0xFF
/// The index reported when A0 is shorted
#define FIRST_ADDRESS_LINE_FAIL_INDEX 0
/// The index reported when A20 is shorted
#define LAST_ADDRESS_LINE_FAIL_INDEX (FIRST_ADDRESS_LINE_FAIL_INDEX + SIMM_HIGHEST_ADDRESS_LINE)
/// The index reported when D0 is shorted
#define FIRST_DATA_LINE_FAIL_INDEX (LAST_ADDRESS_LINE_FAIL_INDEX + 1)
/// The index reported when D31 is shorted
#define LAST_DATA_LINE_FAIL_INDEX (FIRST_DATA_LINE_FAIL_INDEX + SIMM_HIGHEST_DATA_LINE)
/// The index reported when CS is shorted
#define CS_FAIL_INDEX (LAST_DATA_LINE_FAIL_INDEX + 1)
/// The index reported when OE is shorted
#define OE_FAIL_INDEX (CS_FAIL_INDEX + 1)
/// The index reported when WE is shorted
#define WE_FAIL_INDEX (OE_FAIL_INDEX + 1)
/// Enum representing the step we're on during the electrical test
typedef enum ElectricalTestStage
{
TestingAddressLines,//!< We are testing an address pin
TestingDataLines, //!< We are testing a data pin
TestingCS, //!< We are testing the CS pin
TestingOE, //!< We are testing the OE pin
TestingWE, //!< We are testing the WE pin
DoneTesting //!< We completed the test
} ElectricalTestStage;
static void SIMMElectricalTest_ResetGroundShorts(void);
static void SIMMElectricalTest_AddGroundShort(uint8_t index);
static bool SIMMElectricalTest_IsGroundShort(uint8_t index);
/// Pin indexes that were detected as shorted to ground. They have to be saved,
/// because they end up being detected as a short against every other pin,
/// so we have to be able to filter them out when testing non-ground shorts.
static uint32_t groundShorts[2];
/** Runs the electrical test
*
* @param errorHandler Pointer to function to call when a short is detected.
* @return The number of errors we found
*
* The two parameters to errorHandler are the two indexes that are shorted.
* (See the _FAIL_INDEX defines at the top of this file)
*/
int SIMMElectricalTest_Run(void (*errorHandler)(uint8_t, uint8_t))
{
// Returns number of errors found
int numErrors = 0;
// Reset the pins we have determined that are shorted to ground
// (We have to ignore them during the second phase of the test)
SIMMElectricalTest_ResetGroundShorts();
// First check for anything shorted to ground. Set all lines as inputs with
// a weak pull-up resistor. Then read the values back and check for any
// zeros. This would indicate a short to ground.
ParallelBus_SetAddressDir(0);
ParallelBus_SetDataDir(0);
ParallelBus_SetCSDir(false);
ParallelBus_SetOEDir(false);
ParallelBus_SetWEDir(false);
ParallelBus_SetAddressPullups(SIMM_ADDRESS_PINS_MASK);
ParallelBus_SetDataPullups(SIMM_DATA_PINS_MASK);
ParallelBus_SetCSPullup(true);
ParallelBus_SetOEPullup(true);
ParallelBus_SetWEPullup(true);
// Wait a brief moment...
DelayMS(DELAY_SETTLE_TIME_MS);
// Now loop through every pin and check it.
uint8_t curPin = 0;
uint8_t i;
// Read the address pins back first
uint32_t readback = ParallelBus_ReadAddress();
// Check each bit for a LOW which would indicate a short to ground
for (i = 0; i <= SIMM_HIGHEST_ADDRESS_LINE; i++)
{
// Did we find a low bit?
if (!(readback & 1))
{
// That means this pin is shorted to ground.
// So notify the caller that we have a ground short on this pin
if (errorHandler)
{
errorHandler(curPin, GROUND_FAIL_INDEX);
}
// Add it to our internal list of ground shorts also.
SIMMElectricalTest_AddGroundShort(curPin);
// And of course increment the error counter.
numErrors++;
}
// No matter what, though, move on to the next bit and pin.
readback >>= 1;
curPin++;
}
// Repeat the exact same process for the data pins
readback = ParallelBus_ReadData();
for (i = 0; i <= SIMM_HIGHEST_DATA_LINE; i++)
{
if (!(readback & 1))
{
if (errorHandler)
{
errorHandler(curPin, GROUND_FAIL_INDEX);
}
SIMMElectricalTest_AddGroundShort(curPin);
numErrors++;
}
readback >>= 1;
curPin++;
}
// Check chip select in the same way...
if (!ParallelBus_ReadCS())
{
if (errorHandler)
{
errorHandler(curPin, GROUND_FAIL_INDEX);
}
SIMMElectricalTest_AddGroundShort(curPin);
numErrors++;
}
curPin++;
// Output enable...
if (!ParallelBus_ReadOE())
{
if (errorHandler)
{
errorHandler(curPin, GROUND_FAIL_INDEX);
}
SIMMElectricalTest_AddGroundShort(curPin);
numErrors++;
}
curPin++;
// Write enable...
if (!ParallelBus_ReadWE())
{
if (errorHandler)
{
errorHandler(curPin, GROUND_FAIL_INDEX);
}
SIMMElectricalTest_AddGroundShort(curPin);
numErrors++;
}
curPin++; // Doesn't need to be here, but for consistency I'm leaving it.
// OK, now we know which lines are shorted to ground. We need to keep that
// in mind, because those lines will now show as shorted to ALL other
// lines...ignore them during tests to find other independent shorts
// Now, check each individual line vs. all other lines on the SIMM for any
// shorts between them
ElectricalTestStage curStage = TestingAddressLines;
// Counter of what address or data pin we're on. Not used for control lines.
uint8_t addrDataPin = 0;
// What pin we are currently testing all other pins against.
uint8_t testPin = 0;
// More explanation: addrDataPin is only a counter inside the address or
// data pins. testPin is a total counter of ALL pins.
while (curStage != DoneTesting)
{
// Set one pin to output a 0.
// Set all other pins as inputs with pull-ups.
// Then read back all the other pins. If any of them read back as 0,
// it means they are shorted to the pin we set as an output.
// If we're testing address lines right now, set the current address
// line as an output (and make it output a LOW). Set all other address
// lines as inputs with pullups.
if (curStage == TestingAddressLines)
{
// Mask of the address pin we're testing
uint32_t addressLineMask = (1UL << addrDataPin);
// Set it as an output and all other address pins as inputs.
ParallelBus_SetAddressDir(addressLineMask);
ParallelBus_SetAddress(0);
ParallelBus_SetAddressPullups(~addressLineMask);
}
else
{
// If not testing an address line, set all address pins as inputs
// with pullups. All the other stages follow the same pattern so I
// won't bother commenting them.
ParallelBus_SetAddressDir(0);
ParallelBus_SetAddressPullups(SIMM_ADDRESS_PINS_MASK);
}
// Do the same thing for data lines...
if (curStage == TestingDataLines)
{
uint32_t dataLineMask = (1UL << addrDataPin);
ParallelBus_SetDataDir(dataLineMask);
ParallelBus_SetData(0);
ParallelBus_SetDataPullups(~dataLineMask);
}
else
{
ParallelBus_SetDataDir(0);
ParallelBus_SetDataPullups(SIMM_DATA_PINS_MASK);
}
// Chip select...
if (curStage == TestingCS)
{
ParallelBus_SetCSDir(true);
ParallelBus_SetCS(false);
}
else
{
ParallelBus_SetCSDir(false);
ParallelBus_SetCSPullup(true);
}
// Output enable...
if (curStage == TestingOE)
{
ParallelBus_SetOEDir(true);
ParallelBus_SetOE(false);
}
else
{
ParallelBus_SetOEDir(false);
ParallelBus_SetOEPullup(true);
}
// And write enable.
if (curStage == TestingWE)
{
ParallelBus_SetWEDir(true);
ParallelBus_SetWE(false);
}
else
{
ParallelBus_SetWEDir(false);
ParallelBus_SetWEPullup(true);
}
// OK, so now we have set up all lines as needed. Exactly one pin is
// outputting a 0, and all other pins are inputs with pull-ups enabled.
// Read back all the lines, and if any pin reads back as 0, it means
// that pin is shorted to the pin we are testing (overpowering its
// pullup). However, because we test each pin against every other pin,
// any short would appear twice. Once as "pin 1 is shorted to pin 2"
// and once as "pin 2 is shorted to pin 1". To avoid that annoyance, I
// only check each combination of two pins once. You'll see below how I
// do this by testing to ensure curPin > testPin.
// Allow everything to settle
DelayMS(DELAY_SETTLE_TIME_MS);
// Now keep a count of how many pins we have actually checked during
// THIS test iteration. This is the "fail index" of the current pin.
curPin = 0;
// Read back the address data to see if any shorts were found
readback = ParallelBus_ReadAddress();
// Count any shorted pins
for (i = 0; i <= SIMM_HIGHEST_ADDRESS_LINE; i++)
{
// Failure here?
if ((curPin > testPin) && // We haven't already checked this combination of pins (don't test pin against itself either)
!(readback & 1) && // It's showing as low (which indicates a short)
!SIMMElectricalTest_IsGroundShort(curPin)) // And it's not recorded as a short to ground
{
// Send it out as an error notification and increase error counter
if (errorHandler)
{
errorHandler(testPin, curPin);
}
numErrors++;
}
// No matter what, move on to the next bit and pin
readback >>= 1;
curPin++;
}
// Same thing for data pins
readback = ParallelBus_ReadData();
// Count any shorted pins
for (i = 0; i <= SIMM_HIGHEST_DATA_LINE; i++)
{
// Failure here?
if ((curPin > testPin) && // We haven't already checked this combination of pins (don't test pin against itself either)
!(readback & 1) && // It's showing as low (which indicates a short)
!SIMMElectricalTest_IsGroundShort(curPin)) // And it's not recorded as a short to ground
{
if (errorHandler)
{
errorHandler(testPin, curPin);
}
numErrors++;
}
readback >>= 1;
curPin++;
}
// And chip select...
if ((curPin > testPin) &&
!ParallelBus_ReadCS() &&
!SIMMElectricalTest_IsGroundShort(curPin))
{
if (errorHandler)
{
errorHandler(testPin, curPin);
}
numErrors++;
}
curPin++;
// Output enable...
if ((curPin > testPin) &&
!ParallelBus_ReadOE() &&
!SIMMElectricalTest_IsGroundShort(curPin))
{
if (errorHandler)
{
errorHandler(testPin, curPin);
}
numErrors++;
}
curPin++;
// And write enable
if ((curPin > testPin) &&
!ParallelBus_ReadWE() &&
!SIMMElectricalTest_IsGroundShort(curPin))
{
if (errorHandler)
{
errorHandler(testPin, curPin);
}
numErrors++;
}
curPin++; // Not needed, kept for consistency
// Finally, move on to the next stage if needed.
if (curStage == TestingAddressLines)
{
// If we've exhausted all address lines, move on to the next stage
// (and reset the pin counter to 0)
if (++addrDataPin > SIMM_HIGHEST_ADDRESS_LINE)
{
curStage++;
addrDataPin = 0;
}
}
else if (curStage == TestingDataLines)
{
// If we've exhausted all data lines, move on to the next stage
// (don't bother resetting the pin counter -- the other stages don't use it)
if (++addrDataPin > SIMM_HIGHEST_DATA_LINE)
{
curStage++;
}
}
else
{
curStage++;
}
// Move on to test the next pin
testPin++;
}
// Restore to a normal state by calling ParallelBus_Init again.
ParallelBus_Init();
// Now that the final state is restored, return the number of errors found
return numErrors;
}
/** Resets our list of pins that are shorted to ground
*
*/
static void SIMMElectricalTest_ResetGroundShorts(void)
{
groundShorts[0] = 0;
groundShorts[1] = 0;
}
/** Adds a pin to the list of ground shorts
*
* @param index The index of the pin in the electrical test
*/
static void SIMMElectricalTest_AddGroundShort(uint8_t index)
{
if (index < 32)
{
groundShorts[0] |= (1UL << index);
}
else if (index < 64)
{
groundShorts[1] |= (1UL << (index - 32));
}
// None are >= 64, no further handling needed
}
/** Determines if a pin has already been saved as a ground short
*
* @param index The index of the pin in the electrical test
* @return True if this pin has been determined as a short to GND, false if not
*/
static bool SIMMElectricalTest_IsGroundShort(uint8_t index)
{
if (index < 32)
{
return ((groundShorts[0] & (1UL << index)) != 0);
}
else if (index < 64)
{
return ((groundShorts[1] & (1UL << (index - 32))) != 0);
}
else
{
return false;
}
}