Write automated test suite, 8 MHz clock, code cleanup.

This commit is contained in:
Andrew Makousky 2020-09-11 15:34:58 -05:00
parent 1d36abf470
commit 3079bd621f
2 changed files with 683 additions and 117 deletions

View File

@ -22,6 +22,13 @@
* 2020-09-04: <https://www.gryphel.com/d/minivmac/minivmac-36.04/minivmac-36.04.src.tgz>
*/
// 8 MHz clock is recommended for a physical device. For real-time
// simulation, a slower 400 kHz clock is needed.
#ifndef F_CPU
#define F_CPU 8000000
//#define F_CPU 400000 // DEBUG
#endif
#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/interrupt.h>
@ -46,7 +53,8 @@ typedef uint8_t byte;
* RTC chip in early Apple Macintosh *
* computers, using an ATtiny85. *
* Uses an external 32.768kHz crystal *
* on pins 2 and 3 as a clock source. *
* on pins 2 and 3 as a clock source.* *
* SEE ELECTRICAL SPECIFICATIONS. *
* __ __ *
* 1SEC -|1 \/ 8|- VCC *
* XTAL2 -|2 7|- RTC.CLK *
@ -71,6 +79,52 @@ const int RTC_ENABLE_PIN = 0; // Active low chip enable on PB0
const int SERIAL_DATA_PIN = 1; // Bi-directional serial data line on PB1
const int SERIAL_CLOCK_PIN = 2; // Serial clock input on PB2
/* ELECTRICAL SPECIFICATIONS:
* When the Macintosh is powered off, the RTC is powered by the
clock battery. The battery supply voltage is anywhere from 3.6V
to 3V, though depleted batteries can sink below 3V.
* When the Macintosh is powered on, a diode supplies power to the
RTC from the main logic board's power rails. This means the RTC
runs off of 5V power during power-on operation. If necessary, we
can take advantage of this to run the AVR core clock at 16 MHz.
* All dedicated input lines already have a pull-up resistor, so
there is no need to enable the AVR's internal pull-up resistors.
* The bi-directional serial data line is also wired to a pull-up
resistor. This means we can use open-drain signaling to avoid
the risk of the output drivers getting burned out if both sides
inadvertently configure themseslves as outputs at the same time.
* What about the one-second interrupt pin? Since this is wired to
a dedicated input line, it's okay to leave this as a "totem-pole"
buffered output.
* The serial data clock needs to be able to operate at a frequency
of at least 1 kHz, maybe up to 20 kHz.
* Because of the requirement on the serial clock speed, the AVR
core clock speed should be around 8 MHz, given that it can take
about 100 cycles to process one edge of the serial data clock.
* Because the AVR core clock speed needs to operate faster than the
32.768 kHz crystal oscillator clock frequency, the external clock
would ideally be used as the crystal oscillator input to an
asynchronous timer. Unfortunately, the ATTiny85 does not have
the necessary circuitry or ASSR control register.
If you are willing to forgo the cosmetics of a pin-compatible DIP
package, you can instead use the ATTiny87 which has an AS0
asynchronous timer. Though it has extra pins, it comes in a
smaller form factor, so you can just mount it on a custom adapter
circuit board that breaks out the desired pins to through-hole
and ignores/grounds the unnecessary pins.
* TODO: Determine the target standby power consumption.
*/
#if NoXPRAM
// Models earlier than the Plus had 20 bytes of PRAM
#define PRAM_SIZE 20
@ -83,6 +137,31 @@ const int group1Base = 0x10;
const int group2Base = 0x08;
#endif
// Timer constants
#if F_CPU == 8000000
#define PRESCALER_MASK 0b101 /* 1/1024 */
#define LIM_OFLOWS 15
#define LIM_REMAIN 66
#define NUMER_FRAC_REMAIN 1
// `fracRemain` denominator is assumed to be a power-of-two, this
// makes calculations more efficient.
#define DENOM_FRAC_REMAIN 4
#define MASK_FRAC_REMAIN (DENOM_FRAC_REMAIN-1)
#elif F_CPU == 400000
#define PRESCALER_MASK 0b100 /* 1/256 */
#define LIM_OFLOWS 3
#define LIM_REMAIN 13
#define NUMER_FRAC_REMAIN 1
// `fracRemain` denominator is assumed to be a power-of-two, this
// makes calculations more efficient.
#define DENOM_FRAC_REMAIN 4
#define MASK_FRAC_REMAIN (DENOM_FRAC_REMAIN-1)
#else
#error "Invalid clock frequency selection"
#endif
enum SerialStateType { SERIAL_DISABLED, RECEIVING_COMMAND,
SENDING_DATA, RECEIVING_DATA,
RECEIVING_XCMD_ADDR, RECEIVING_XCMD_DATA };
@ -112,6 +191,43 @@ volatile unsigned long seconds = 60UL * 60 * 24 * (365 * 4 + 1) * 20;
volatile byte writeProtect = 0;
volatile byte pram[PRAM_SIZE] = {}; // PRAM initialized as zeroed data
// Extra timer precision book-keeping.
volatile byte numOflows = 0;
volatile byte fracRemain = 0;
/* Explanation of the 1-second timer calculations.
First divide the AVR core clock frequency by two since we count
half-second cycles.
8000000 / 2 = 4000000
Find quotient and remainder of timer frequency divider.
4000000 / 1024 = 3906 + 256/1024 = 3906 + 1/4
Now, divide by 256 to find out how many 8-bit timer overflows we
need to process.
3906 / 256 = 15 + 66/256
The remainder is the fractional overflow to process. We achieve
this by setting the counter register to 256 - remainder after 15
overflows. On the 16th overflow, we then just let the register
wrap to zero.
But we're not finished yet, we still have the other remainder to
adjust for. Here's how we do it.
1/4 / 256 = (1/4)/256
Okay, so what does that mean? That means we have 1/4 of a counter
tick to accumulate every half-second cycle in `fracRemain`. After 4
half-second cycles, the error is one full counter tick to add to
the fractional overflow. So, every 4 half-second cycles, we use 67
as the remainder rather than 66.
*/
#define shiftReadPB(output, bitNum, portBit) \
bitWrite(output, bitNum, ((PINB&_BV(portBit))) ? 1 : 0)
@ -171,8 +287,7 @@ void setup(void)
//set up timer
bitSet(GTCCR, TSM); // Turns off timers while we set it up
bitSet(TIMSK, TOIE0); // Set Timer/Counter0 Overflow Interrupt Enable
// NOTE: 0b111 external clock, 0b011, uses 1/64 prescaler on I/O clock.
TCCR0B = 0b011; // Set prescaler, 32,768Hz/64 = 512Hz, fills up the 8-bit counter (256) once every half second
TCCR0B = PRESCALER_MASK; // Set prescaler
TCNT0 = 0; // Clear the counter
bitClear(GTCCR, TSM); // Turns timers back on
@ -197,16 +312,26 @@ void clearState(void)
* An interrupt to both increment the seconds counter and generate the
* square wave
*/
void halfSecondInterrupt(void)
void oflowInterrupt(void)
{
PINB = 1<<ONE_SEC_PIN; // Flip the one-second pin
if (!(PINB&(1<<ONE_SEC_PIN))) { // If the one-second pin is low
seconds++;
// Make up for lost time, something around 6.4 cycles.
TCNT0 += 6;
numOflows++;
if (numOflows == LIM_OFLOWS) {
// Configure a timer interrupt to handle the final remainder cycle
// wait. We simply subtract the value from 256, which is the same
// as going negative two's complement and using 8-bit wrap-around.
fracRemain += NUMER_FRAC_REMAIN;
TCNT0 = (fracRemain >= DENOM_FRAC_REMAIN) ?
-(LIM_REMAIN + 1) : -LIM_REMAIN;
fracRemain &= MASK_FRAC_REMAIN;
} else if (numOflows == LIM_OFLOWS + 1) {
// Reset the timer-related flags now that we've reached a
// half-second.
numOflows = 0;
PINB = 1<<ONE_SEC_PIN; // Flip the one-second pin
if (!(PINB&(1<<ONE_SEC_PIN))) { // If the one-second pin is low
seconds++;
}
}
else
TCNT0 += 7;
}
/*
@ -271,7 +396,9 @@ uint8_t decodePramCmd(boolean writeRequest)
if (address == 12) // test write
return WRTEST_CMD;
if (address == 13) // write-protect
return WRPROT_CMD;
return WRPROT_CMD;
// Addresses 14 and 15 are used for the encoding of the first
// byte of an extended command.
}
return INVALID_CMD;
} else {
@ -426,7 +553,7 @@ void loop(void)
// Read the data byte before continuing.
serialState = RECEIVING_XCMD_DATA;
serialBitNum = 0;
serialData = 0;
serialData = 0;
break;
}
@ -445,15 +572,15 @@ void loop(void)
break;
// Write the PRAM register.
if (!writeProtect)
pram[address] = serialData;
if (!writeProtect)
pram[address] = serialData;
// Finished with the write command.
clearState();
break;
#endif
default:
// Invalid command.
// Invalid state.
clearState();
break;
}
@ -481,7 +608,7 @@ ISR(PCINT0_vect)
ISR(TIMER0_OVF_vect)
{
halfSecondInterrupt();
oflowInterrupt();
}
// Arduino main function.

View File

@ -76,6 +76,7 @@ typedef enum { false, true } bool;
typedef uint8_t byte;
typedef bool boolean;
typedef uint8_t bool8_t;
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
@ -154,7 +155,7 @@ uint8_t monMode = 0;
*/
boolean simAvrStep(void);
bool8_t simAvrStep(void);
avr_cycle_count_t notify_timeup(avr_t *avr, avr_cycle_count_t when,
void *param);
@ -213,67 +214,40 @@ void viaBitWrite(uint8_t *ptr, uint8_t bit, uint8_t bitvalue)
silicon RTC. */
void waitQuarterCycle(void)
{
#ifdef RPI_DRIVER
struct timespec tv = { 0, 500000 };
struct timespec tvNext;
#ifdef RPI_DRIVER
do {
if (clock_nanosleep(CLOCK_MONOTONIC, 0, &tv, &tvNext) == 0)
break;
tv.tv_nsec = tvNext.tv_nsec;
} while (tv.tv_nsec > 0);
#else
// For simavr, the simulator sleeps for the appropriate real time so
// long as the firmware calls the sleep function between cycles.
// So, with our numbers, calculate the number of cycles we need to
// simulate during this slseep period:
// 0.0005 * 32768 = 5 * 32768 / 10000 = 32768 / 2000 = 16.384 ~= 16
// So, our approach now. Set a target time, and keep stepping the
// simulator until the time is reached.
/* {
unsigned i = 16;
while (i > 0) {
if (!simAvrStep())
break;
i--;
}
} */
if (0) {
g_waitTimeUp = false;
avr_cycle_timer_register(avr, 16, notify_timeup, NULL);
while (!g_waitTimeUp) {
if (!simAvrStep())
break;
}
}
// Unfortunately, if the AVR runs at 32.768 kHz, I've found from
// simulation that serial communications are only reliable at an
// abysmal 50 Hz serial clock speed. Therefore, running at a higher
// core speed and using a phase-locked loop on the crystal clock
// frequency a must.
{ // Use this alternative for slow simulation.
struct timespec tv, tvTarget;
// N.B. Over here we are using cycle timers mainly to prevent
// simulation waits stretching unbearably long.
g_timePoll = 16;
avr_cycle_timer_register(avr, g_timePoll, notify_timeup, NULL);
clock_gettime(CLOCK_MONOTONIC, &tv);
tvTarget.tv_nsec = tv.tv_nsec + 500000 * 10;
tvTarget.tv_sec = tv.tv_sec;
if (tvTarget.tv_nsec >= 1000000000) {
tvTarget.tv_nsec -= 1000000000;
tvTarget.tv_sec++;
}
while (tv.tv_sec < tvTarget.tv_sec ||
(tv.tv_sec == tvTarget.tv_sec &&
tv.tv_nsec < tvTarget.tv_nsec)) {
if (!simAvrStep())
break;
clock_gettime(CLOCK_MONOTONIC, &tv);
}
g_timePoll = 0;
struct timespec tv, tvTarget;
// N.B. Over here we are using cycle timers mainly to prevent
// simulation waits stretching unbearably long.
g_timePoll = 16;
avr_cycle_timer_register(avr, g_timePoll, notify_timeup, NULL);
clock_gettime(CLOCK_MONOTONIC, &tv);
tvTarget.tv_nsec = tv.tv_nsec + 500000;
tvTarget.tv_sec = tv.tv_sec;
if (tvTarget.tv_nsec >= 1000000000) {
tvTarget.tv_nsec -= 1000000000;
tvTarget.tv_sec++;
}
while (tv.tv_sec < tvTarget.tv_sec ||
(tv.tv_sec == tvTarget.tv_sec &&
tv.tv_nsec < tvTarget.tv_nsec)) {
if (!simAvrStep())
break;
clock_gettime(CLOCK_MONOTONIC, &tv);
}
g_timePoll = 0;
#endif
}
@ -289,12 +263,36 @@ void waitCycle(void)
waitHalfCycle();
}
void waitOneSec(void)
{
#ifdef RPI_DRIVER
sleep(1);
#else
struct timespec tv, tvTarget;
// N.B. Over here we are using cycle timers mainly to prevent
// simulation waits stretching unbearably long.
g_timePoll = 16;
avr_cycle_timer_register(avr, g_timePoll, notify_timeup, NULL);
clock_gettime(CLOCK_MONOTONIC, &tv);
tvTarget.tv_nsec = tv.tv_nsec;
tvTarget.tv_sec = tv.tv_sec + 1;
while (tv.tv_sec < tvTarget.tv_sec ||
(tv.tv_sec == tvTarget.tv_sec &&
tv.tv_nsec < tvTarget.tv_nsec)) {
if (!simAvrStep())
break;
clock_gettime(CLOCK_MONOTONIC, &tv);
}
g_timePoll = 0;
#endif
}
/********************************************************************/
/* PRAM C library module */
// Configure whether the PRAM should be traditional 20-byte PRAM
// (false) or XPRAM (true).
void setPramType(boolean isXPram)
void setPramType(bool8_t isXPram)
{
if (isXPram) {
pramSize = 256;
@ -308,7 +306,7 @@ void setPramType(boolean isXPram)
}
// Return true if the PRAM type is set to XPRAM, false otherwise.
boolean getPramType(void)
bool8_t getPramType(void)
{
if (pramSize == 256)
return true;
@ -429,7 +427,7 @@ void clearWriteProtect(void)
compared for equality to verify a consistent read. If the read is
inconsistent, this function will retry up to a maximum of 4 times
before returning failure. */
boolean dumpTime(void)
bool8_t dumpTime(void)
{
uint8_t retry = 0;
uint32_t newTime1, newTime2;
@ -447,9 +445,7 @@ boolean dumpTime(void)
newTime2 |= sendReadCmd(0x98) << 16;
newTime2 |= sendReadCmd(0x9c) << 24;
// TODO FIXME DEBUG USE ONLY: Quirk to work around running the
// serial communication clock too slowly.
if (true || newTime1 == newTime2) {
if (newTime1 == newTime2) {
// TODO: Use mutex lock for time update.
timeSecs = newTime1;
return true;
@ -575,7 +571,7 @@ void setCurTime(void)
// Convenience function to generate a traditional PRAM command from
// logical command address and write-request flag. `addr` must not
// exceed 0x1f.
byte genCmd(byte addr, boolean writeRequest)
byte genCmd(byte addr, bool8_t writeRequest)
{
return ((!writeRequest) << 7) | (addr << 2);
}
@ -623,7 +619,7 @@ void loadAllTradMem(void)
// Generate an extended command from a byte address. The first byte
// to send is the most significant byte in the returned 16-bit
// integer.
uint16_t genXCmd(byte addr, boolean writeRequest)
uint16_t genXCmd(byte addr, bool8_t writeRequest)
{
uint16_t xcmd = 0x3800 | ((addr & 0xe0) << 3) | ((addr & 0x1f) << 2);
if (!writeRequest)
@ -652,7 +648,8 @@ void dumpAllXMem(void)
do {
pram[i] = genSendReadXCmd(i);
i++;
} while (i < 0xff);
} while (i != 0);
// N.B. We rely on overflow here to copy all 256 bytes.
}
// Clear write-protect and copy all XPRAM memory from host to RTC.
@ -663,7 +660,8 @@ void loadAllXMem(void)
do {
genSendWriteXCmd(i, pram[i]);
i++;
} while (i < 0xff);
} while (i != 0);
// N.B. We rely on overflow here to copy all 256 bytes.
}
/* For 20-byte equivalent PRAM commands, read or write the
@ -675,7 +673,7 @@ void loadAllXMem(void)
firmware. */
byte hostTradPramCmd(byte cmd, byte data)
{
boolean writeRequest = !(cmd&(1<<7));
bool8_t writeRequest = !(cmd&(1<<7));
// Discard the first bit and the last two bits, it's not pertinent
// to address interpretation.
byte address = (cmd&~(1<<7))>>2;
@ -709,8 +707,12 @@ byte hostTradPramCmd(byte cmd, byte data)
writeProtect = ((data & 0x80)) ? 1 : 0;
// Fall through to send command to RTC.
}
else
return 0; // invalid command
else {
// Addresses 14 and 15 are used for the encoding of the first
// byte of an extended command. Therefore, interpretation as
// a traditional PRAM command is invalid.
return 0;
}
} else
return 0; // invalid command
} else {
@ -746,7 +748,7 @@ byte hostReadXMem(byte address)
// Load the host copy of the traditional PRAM from a file and update
// the RTC device memory. Also clears write-protect. Returns true on
// success, false on failure.
boolean fileLoadAllTradMem(const char *filename)
bool8_t fileLoadAllTradMem(const char *filename)
{
FILE *fp = fopen(filename, "rb");
int ch;
@ -784,7 +786,7 @@ boolean fileLoadAllTradMem(const char *filename)
// Save the host copy of the traditional PRAM to a file. Returns true
// on success, false on failure.
boolean fileDumpAllTradMem(const char *filename)
bool8_t fileDumpAllTradMem(const char *filename)
{
FILE *fp = fopen(filename, "wb");
uint8_t i;
@ -813,7 +815,7 @@ boolean fileDumpAllTradMem(const char *filename)
// Load the host copy of the XPRAM from a file and update the RTC
// device memory. Also clears write-protect. Returns true on
// success, false on failure.
boolean fileLoadAllXMem(const char *filename)
bool8_t fileLoadAllXMem(const char *filename)
{
FILE *fp = fopen(filename, "rb");
if (fp == NULL)
@ -830,7 +832,7 @@ boolean fileLoadAllXMem(const char *filename)
// Save the host copy of the XPRAM to a file. Returns true on
// success, false on failure.
boolean fileDumpAllXMem(const char *filename)
bool8_t fileDumpAllXMem(const char *filename)
{
FILE *fp = fopen(filename, "wb");
if (fp == NULL)
@ -851,8 +853,9 @@ void simRec(void);
void simNoRec(void);
void setMonMode(uint8_t newMonMode);
uint8_t getMonMode(void);
byte monMemAccess(uint16_t address, boolean writeRequest, byte data);
boolean execMonLine(char *lineBuf);
byte monMemAccess(uint16_t address, bool8_t writeRequest, byte data);
bool8_t execMonLine(char *lineBuf);
bool8_t autoTestSuite(bool8_t verbose, bool8_t simRealTime);
/* Since every subroutine for command-line commands only has zero to
three arguments, all being numeric except for the file commands
@ -900,7 +903,7 @@ uint8_t parse8Bits(byte *output, uint8_t limit, char *parsePtr)
// Bit flag 2|0: Quit command encountered vs. continue
uint8_t execCmdLine(char *lineBuf)
{
boolean splitCmd = false;
bool8_t splitCmd = false;
char *cmdName;
char *parsePtr = lineBuf;
SKIP_WHITESPACE(parsePtr);
@ -959,6 +962,7 @@ uint8_t execCmdLine(char *lineBuf)
" file-dump-all-xmem filename\n"
" sim-rec -- start recording RTC pin signal waveforms\n"
" sim-no-rec -- stop recording RTC pin signal waveforms\n"
" auto-test-suite verbose simRealTime\n"
" q, quit -- exit the program\n"
"\n"
"Most commands are named after the corresponding library subroutines,\n"
@ -1045,8 +1049,10 @@ uint8_t execCmdLine(char *lineBuf)
clearWriteProtect();
return 1;
} else if (strcmp(cmdName, "dump-time") == 0) {
byte result;
PARSE_8BIT_HEAD(0);
dumpTime();
result = dumpTime();
printf("0x%02x\n", result);
return 1;
} else if (strcmp(cmdName, "load-time") == 0) {
PARSE_8BIT_HEAD(0);
@ -1199,6 +1205,12 @@ uint8_t execCmdLine(char *lineBuf)
PARSE_8BIT_HEAD(0);
simNoRec();
return 1;
} else if (strcmp(cmdName, "auto-test-suite") == 0) {
byte result;
PARSE_8BIT_HEAD(2);
result = autoTestSuite(params[0], params[1]);
printf("0x%02x\n", result);
return 1;
} else if (strcmp(cmdName, "q") == 0 ||
strcmp(cmdName, "quit") == 0) {
return 3; // Time to quit.
@ -1233,7 +1245,7 @@ uint8_t execCmdLine(char *lineBuf)
}
// Return false on exit with error, true on graceful exit.
boolean cmdLoop(void)
bool8_t cmdLoop(void)
{
uint8_t retVal = true;
char lineBuf[512];
@ -1298,7 +1310,7 @@ uint8_t getMonMode(void)
and do nothing on write. For reads, `data` is ignored. Returns
data on successful reads, zero on unsuccessful reads, one on
successful writes, zero on unsuccessful writes. */
byte monMemAccess(uint16_t address, boolean writeRequest, byte data)
byte monMemAccess(uint16_t address, bool8_t writeRequest, byte data)
{
if (monMode == 1) {
// Traditional PRAM
@ -1345,7 +1357,7 @@ void dumphex(unsigned short addr, unsigned short end_addr,
unsigned char one_line);
void writehex(char *rch);
boolean execMonLine(char *lineBuf)
bool8_t execMonLine(char *lineBuf)
{
char ch;
g_monLineBuf = lineBuf;
@ -1625,7 +1637,7 @@ sig_int(int sign)
exit(0);
}
int setupSimAvr(char *progName, const char *fname, boolean interactMode)
int setupSimAvr(char *progName, const char *fname, bool8_t interactMode)
{
elf_firmware_t f;
@ -1634,7 +1646,9 @@ int setupSimAvr(char *progName, const char *fname, boolean interactMode)
return 1;
}
strcpy(f.mmcu, "attiny85");
f.frequency = 32768;
//f.frequency = 8000000;
// DEBUG NOTE: I'm only able to do real-time simulation at 400 kHz.
f.frequency = 400000;
printf("firmware %s f=%d mmcu=%s\n", fname, (int)f.frequency, f.mmcu);
@ -1768,7 +1782,7 @@ int setupSimAvr(char *progName, const char *fname, boolean interactMode)
// Run a single step of the AVR simulation, return true if the
// simulation should continue, false if it should stop.
boolean simAvrStep(void)
bool8_t simAvrStep(void)
{
// Simulation main loop.
int state = avr_run(avr);
@ -1784,46 +1798,468 @@ boolean simAvrStep(void)
/********************************************************************/
/* Automated test suite module */
int autoTestSuite(void)
bool8_t autoTestSuite(bool8_t verbose, bool8_t simRealTime)
{
/* TODO: Things to test:
uint8_t failCount = 0;
uint8_t skipCount = 0;
const uint8_t numTests = 18;
* Listen for 1-second ping, compare with host clock to verify
second counting is working correctly.
// Use a non-deterministic seed for randomized tests... but print
// out the value just in case we want to go deterministic.
time_t seed = time(NULL);
printf("INFO:random seed = 0x%08x\n", seed);
srand(seed);
* Do a test write, just because we can. Yes, even though it does
absolutely nothing.
if (!simRealTime) {
fputs("SKIP:", stdout);
fputs("1-second interrupt line\n", stdout);
skipCount++;
} else {
/* Listen for 1-second ping, compare with host clock to verify
second counting is working correctly. */
bool8_t result = false;
uint8_t tries = 0;
// Retry up to one time simply because we might cross a one-second
// time boundary intermittently.
do {
// Check that the 1-second line interrupt is working as expected
// for three seconds.
uint32_t expectTimeSecs, actualTimeSecs;
expectTimeSecs = getTime();
waitOneSec(); expectTimeSecs++;
actualTimeSecs = getTime();
if (verbose)
printf("INFO:0x%08x ?= 0x%08x", expectTimeSecs, actualTimeSecs);
result = (expectTimeSecs == actualTimeSecs);
if (!result)
continue;
waitOneSec(); expectTimeSecs++;
actualTimeSecs = getTime();
if (verbose)
printf("INFO:0x%08x ?= 0x%08x", expectTimeSecs, actualTimeSecs);
result = (expectTimeSecs == actualTimeSecs);
if (!result)
continue;
waitOneSec(); expectTimeSecs++;
actualTimeSecs = getTime();
if (verbose)
printf("INFO:0x%08x ?= 0x%08x", expectTimeSecs, actualTimeSecs);
result = (expectTimeSecs == actualTimeSecs);
if (!result)
continue;
} while (!result && ++tries < 2);
fputs((result) ? "PASS:" : "FAIL:", stdout);
fputs("1-second interrupt line\n", stdout);
if (!result) failCount++;
}
* Test reading and writing all bytes of the clock time in seconds
{ /* Do a test write, just because we can. Yes, even though it does
absolutely nothing. */
bool8_t result = false;
testWrite();
result = true;
fputs((result) ? "PASS:" : "FAIL:", stdout);
fputs("Test write\n", stdout);
if (!result) failCount++;
}
if (!simRealTime) {
fputs("SKIP:", stdout);
fputs("Read clock registers\n", stdout);
skipCount++;
} else {
/* Read the clock registers into host memory to sync our time. */
bool8_t result = dumpTime();
fputs((result) ? "PASS:" : "FAIL:", stdout);
fputs("Read clock registers\n", stdout);
if (!result) failCount++;
}
if (!simRealTime) {
fputs("SKIP:", stdout);
fputs("Write and read clock time registers\n", stdout);
skipCount++;
} else {
/* Test writing and reading all bytes of the clock time in seconds
register. Code that doesn't properly cast to long can result
in inability to write the high-order bytes.
in inability to write the high-order bytes. */
bool8_t result = false;
uint8_t tries = 0;
// Retry up to one time simply because we might cross a one-second
// time boundary intermittently.
do {
uint32_t testTimeSecs = 0x983b80d5;
uint32_t readTimeSecs;
setTime(testTimeSecs);
dumpTime();
readTimeSecs = getTime();
if (verbose)
printf("INFO:0x%08x ?= 0x%08x", readTimeSecs, testTimeSecs);
result = (readTimeSecs == testTimeSecs);
} while (!result && ++tries < 2);
fputs((result) ? "PASS:" : "FAIL:", stdout);
fputs("Write and read clock time registers\n", stdout);
if (!result) failCount++;
}
* Set/clear write-protect, test both traditional and XPRAM writes
and reads with write-protect set and clear.
{ /* Set/clear write-protect, test seconds registers, traditional
PRAM, and XPRAM writes and reads with write-protect set and
clear. */
bool8_t result = false;
byte oldVal, newVal, actualVal;
setWriteProtect();
oldVal = genSendReadCmd(0x07);
newVal = ~oldVal;
genSendWriteCmd(0x07, newVal);
actualVal = genSendReadCmd(0x07);
if (verbose)
printf("INFO:0x%02x ?!= 0x%02x\n", actualVal, newVal);
result = (actualVal != newVal);
fputs((result) ? "PASS:" : "FAIL:", stdout);
fputs("Clock register write nulled with write-protect enabled\n",
stdout);
if (!result) failCount++;
* Test for expected memory overlap behavior for memory regions
clearWriteProtect();
oldVal = genSendReadCmd(0x07);
newVal = ~oldVal;
genSendWriteCmd(0x07, newVal);
actualVal = genSendReadCmd(0x07);
if (verbose)
printf("INFO:0x%02x ?= 0x%02x\n", actualVal, newVal);
result = (actualVal == newVal);
fputs((result) ? "PASS:" : "FAIL:", stdout);
fputs("Clock register write with write-protect disabled\n", stdout);
if (!result) failCount++;
setWriteProtect();
oldVal = genSendReadCmd(0x08);
newVal = ~oldVal;
genSendWriteCmd(0x08, newVal);
actualVal = genSendReadCmd(0x08);
if (verbose)
printf("INFO:0x%02x ?!= 0x%02x\n", actualVal, newVal);
result = (actualVal != newVal);
fputs((result) ? "PASS:" : "FAIL:", stdout);
fputs("Traditional PRAM write nulled with write-protect enabled\n",
stdout);
if (!result) failCount++;
clearWriteProtect();
oldVal = genSendReadCmd(0x08);
newVal = ~oldVal;
genSendWriteCmd(0x08, newVal);
actualVal = genSendReadCmd(0x08);
if (verbose)
printf("INFO:0x%02x ?= 0x%02x\n", actualVal, newVal);
result = (actualVal == newVal);
fputs((result) ? "PASS:" : "FAIL:", stdout);
fputs("Traditional PRAM write with write-protect disabled\n", stdout);
if (!result) failCount++;
setWriteProtect();
oldVal = genSendReadXCmd(0x30);
newVal = ~oldVal;
genSendWriteXCmd(0x30, newVal);
actualVal = genSendReadXCmd(0x30);
if (verbose)
printf("INFO:0x%02x ?!= 0x%02x\n", actualVal, newVal);
result = (actualVal != newVal);
fputs((result) ? "PASS:" : "FAIL:", stdout);
fputs("XPRAM write nulled with write-protect enabled\n", stdout);
if (!result) failCount++;
clearWriteProtect();
oldVal = genSendReadXCmd(0x30);
newVal = ~oldVal;
genSendWriteXCmd(0x30, newVal);
actualVal = genSendReadXCmd(0x30);
if (verbose)
printf("INFO:0x%02x ?= 0x%02x\n", actualVal, newVal);
result = (actualVal == newVal);
fputs((result) ? "PASS:" : "FAIL:", stdout);
fputs("XPRAM write with write-protect disabled\n", stdout);
if (!result) failCount++;
}
{ /* Test for expected memory overlap behavior for memory regions
sharaed in common in both traditional PRAM and XPRAM. Only
applicable to XPRAM.
applicable to XPRAM. */
bool8_t result = true;
byte groupVal, xpramVal;
groupVal = genSendReadCmd(0x10);
xpramVal = genSendReadXCmd(0x10);
if (verbose)
printf("INFO: 0x%02x ?= 0x%02x\n", groupVal, xpramVal);
result &= (groupVal == xpramVal);
genSendWriteCmd(0x10, ~groupVal);
groupVal = genSendReadCmd(0x10);
xpramVal = genSendReadXCmd(0x10);
if (verbose)
printf("INFO: 0x%02x ?= 0x%02x\n", groupVal, xpramVal);
result &= (groupVal == xpramVal);
fputs((result) ? "PASS:" : "FAIL:", stdout);
fputs("Group 1 and XPRAM memory overlap\n", stdout);
if (!result) failCount++;
* Test that we can read the contents of the clock, wait a few
result = true;
groupVal = genSendReadCmd(0x08);
xpramVal = genSendReadXCmd(0x08);
if (verbose)
printf("INFO: 0x%02x ?= 0x%02x\n", groupVal, xpramVal);
result &= (groupVal == xpramVal);
genSendWriteCmd(0x08, ~groupVal);
groupVal = genSendReadCmd(0x08);
xpramVal = genSendReadXCmd(0x08);
if (verbose)
printf("INFO: 0x%02x ?= 0x%02x\n", groupVal, xpramVal);
result &= (groupVal == xpramVal);
fputs((result) ? "PASS:" : "FAIL:", stdout);
fputs("Group 2 and XPRAM memory overlap\n", stdout);
if (!result) failCount++;
}
if (!simRealTime) {
fputs("SKIP:", stdout);
fputs("Consistent 1-second interrupt and clock reguister increment\n",
stdout);
skipCount++;
} else {
/* Test that we can read the contents of the clock, wait a few
seconds, incrementing on the one-second interrupt, then read
the clock register again. The values should match
equivalently.
equivalently. */
bool8_t result = false;
uint8_t tries = 0;
// Retry up to one time simply because we might cross a one-second
// time boundary intermittently.
do {
uint32_t expectTimeSecs, actualTimeSecs;
// Two one-second waits in succession, followed by a
// three-second wait.
dumpTime();
waitOneSec();
expectTimeSecs = getTime();
dumpTime(); actualTimeSecs = getTime();
if (verbose)
printf("INFO:0x%08x ?= 0x%08x\n", expectTimeSecs, actualTimeSecs);
result = (expectTimeSecs == actualTimeSecs);
if (!result)
continue;
waitOneSec();
expectTimeSecs = getTime();
dumpTime(); actualTimeSecs = getTime();
if (verbose)
printf("INFO:0x%08x ?= 0x%08x\n", expectTimeSecs, actualTimeSecs);
result = (expectTimeSecs == actualTimeSecs);
if (!result)
continue;
waitOneSec();
waitOneSec();
waitOneSec();
expectTimeSecs = getTime();
dumpTime(); actualTimeSecs = getTime();
if (verbose)
printf("INFO:0x%08x ?= 0x%08x\n", expectTimeSecs, actualTimeSecs);
result = (expectTimeSecs == actualTimeSecs);
if (!result)
continue;
} while (!result && ++tries < 2);
fputs((result) ? "PASS:" : "FAIL:", stdout);
fputs("Consistent 1-second interrupt and clock reguister increment\n",
stdout);
if (!result) failCount++;
}
* Read/write memory regions randomly and verify expected memory
behavior.
{ /* Write/read memory regions randomly and verify expected memory
behavior. */
bool8_t result = true;
/* Suitable traditional PRAM address range for testing, keep out
of the clock, write-protect, test write, and extended command
registers:
0x08 - 0x0b
0x10 - 0x1f
Total 20 bytes
* Load and dump and memory linearly, compare for expected memory
behavior.
Select 8 bytes at random for testing.
*/
byte src_addrs[256];
uint16_t src_addrs_len = 0;
byte rnd_addrs[64], rnd_data[64];
byte rnd_len = 0;
byte i;
// Draw and remove from a source address pool, this guarantees we
// don't pick the same address twice.
while (src_addrs_len < 20) {
byte pick = 8 + src_addrs_len;
if (pick >= 0x0c)
pick += 4;
src_addrs[src_addrs_len++] = pick;
}
while (rnd_len < 8) {
byte pick = rand() % src_addrs_len;
rnd_addrs[rnd_len] = src_addrs[pick];
src_addrs[pick] = src_addrs[--src_addrs_len];
rnd_data[rnd_len] = rand() & 0xff;
genSendWriteCmd(rnd_addrs[rnd_len], rnd_data[rnd_len]);
rnd_len++;
}
while (rnd_len > 0) {
// Pick an element randomly, read-verify it, then delete it from
// the list by overwriting it with the last element.
byte pick = rand() % rnd_len;
byte actualVal = genSendReadCmd(rnd_addrs[pick]);
if (verbose)
printf("INFO:0x%02x: 0x%02x ?= 0x%02x\n", rnd_addrs[pick],
actualVal, rnd_data[pick]);
result &= (actualVal == rnd_data[pick]);
rnd_len--;
rnd_addrs[pick] = rnd_addrs[rnd_len];
rnd_data[pick] = rnd_data[rnd_len];
}
fputs((result) ? "PASS:" : "FAIL:", stdout);
fputs("Random traditional PRAM register write/read\n", stdout);
if (!result) failCount++;
* Send invalid communication bit sequence, de-select, re-select
result = true;
src_addrs_len = 0;
// Draw and remove from a source address pool, this guarantees we
// don't pick the same address twice.
while (src_addrs_len < 256) {
src_addrs[src_addrs_len] = src_addrs_len;
src_addrs_len++;
}
while (rnd_len < 64) {
byte pick = rand() % src_addrs_len;
rnd_addrs[rnd_len] = src_addrs[pick];
src_addrs[pick] = src_addrs[--src_addrs_len];
rnd_data[rnd_len] = rand() & 0xff;
genSendWriteXCmd(rnd_addrs[rnd_len], rnd_data[rnd_len]);
rnd_len++;
}
while (rnd_len > 0) {
// Pick an element randomly, read-verify it, then delete it from
// the list by overwriting it with the last element.
byte pick = rand() % rnd_len;
byte actualVal = genSendReadXCmd(rnd_addrs[pick]);
if (verbose)
printf("INFO:0x%02x: 0x%02x ?= 0x%02x\n", rnd_addrs[pick],
actualVal, rnd_data[pick]);
result &= (actualVal == rnd_data[pick]);
rnd_len--;
rnd_addrs[pick] = rnd_addrs[rnd_len];
rnd_data[pick] = rnd_data[rnd_len];
}
fputs((result) ? "PASS:" : "FAIL:", stdout);
fputs("Random XPRAM register write/read\n", stdout);
if (!result) failCount++;
}
{ /* Load and dump and memory linearly, compare for expected memory
behavior. */
bool8_t result = false;
uint8_t oldMonMode = getMonMode();
byte expectedXPram[256];
uint16_t i;
result = true;
setMonMode(2);
// Randomly initialize group 1 registers.
for (i = 0; i < 16; i++)
expectedXPram[group1Base+i] = rand() & 0xff;
// Randomly initialize group 2 registers.
for (i = 0; i < 4; i++)
expectedXPram[group2Base+i] = rand() & 0xff;
// Copy both groups to RTC.
memcpy(pram + group1Base, expectedXPram + group1Base, 16);
memcpy(pram + group2Base, expectedXPram + group2Base, 4);
if (verbose) {
fputs("INFO:Expected data:\n", stdout);
execMonLine("0008.001f\n");
}
loadAllTradMem();
// Zero our host copy to be sure we don't compare stale data.
memset(pram + group1Base, 0, 16);
memset(pram + group2Base, 0, 4);
dumpAllTradMem();
if (verbose) {
fputs("INFO:Actual data:\n", stdout);
execMonLine("0008.001f\n");
}
result &= (memcmp(pram + group1Base,
expectedXPram + group1Base, 16) == 0);
result &= (memcmp(pram + group2Base,
expectedXPram + group2Base, 4) == 0);
fputs((result) ? "PASS:" : "FAIL:", stdout);
fputs("Load and dump traditional PRAM\n", stdout);
if (!result) failCount++;
setMonMode(2);
for (i = 0; i < 256; i++)
expectedXPram[i] = rand() & 0xff;
memcpy(pram, expectedXPram, 256);
if (verbose) {
fputs("INFO:Expected data:\n", stdout);
execMonLine("0000.00ff\n");
}
loadAllXMem();
// Zero our host copy to be sure we don't compare stale data.
memset(pram, 0, 256);
dumpAllXMem();
if (verbose) {
fputs("INFO:Actual data:\n", stdout);
execMonLine("0000.00ff\n");
}
result = (memcmp(pram, expectedXPram, 256) == 0);
fputs((result) ? "PASS:" : "FAIL:", stdout);
fputs("Load and dump XPRAM\n", stdout);
if (!result) failCount++;
setMonMode(oldMonMode);
}
{ /* Send invalid communication bit sequence, de-select, re-select
chip, then send a valid communication sequence. Verify that
chip can robustly recover from invalid communication sequences.
chip can robustly recover from invalid communication
sequences.
*/
It turns out that the protocol is actually quite robust, the
only way to potentially cause an invalid communication state
would be to disable the chip-enable line before a communication
sequence is complete. */
bool8_t result = false;
byte testVal;
genSendWriteCmd(0x10, 0xcd);
serialBegin();
{ /* Fragmented sendByte() that would otherwise clobber the byte
we just wrote. Send only 6 out of 8 bits. */
uint8_t data = genCmd(0x10, true);
uint8_t bitNum = 0;
viaBitWrite(vBase + vDirB, rtcData, DIR_OUT);
while (bitNum <= 5) {
uint8_t bit = (data >> (7 - bitNum)) & 1;
bitNum++;
viaBitWrite(vBase + vBufB, rtcData, bit);
waitQuarterCycle();
viaBitWrite(vBase + vBufB, rtcClk, 1);
waitHalfCycle();
viaBitWrite(vBase + vBufB, rtcClk, 0);
waitQuarterCycle();
}
}
serialEnd();
testVal = genSendReadCmd(0x10);
if (verbose)
printf("INFO:0x%02x ?= 0x%02x\n", testVal, 0xcd);
result = (testVal == 0xcd);
fputs((result) ? "PASS:" : "FAIL:", stdout);
fputs("Recovery from invalid communication\n", stdout);
if (!result) failCount++;
}
fputs("FAIL: No automated test suite implemented!\n", stdout);
return 1;
printf("\n%d passed, %d failed, %d skipped\n",
numTests - failCount - skipCount, failCount, skipCount);
return (failCount == 0);
}
/********************************************************************/
@ -1832,7 +2268,7 @@ int autoTestSuite(void)
int main(int argc, char *argv[])
{
char *firmwareName = "";
boolean interactMode = false;
bool8_t interactMode = false;
int retVal;
{ // Parse command-line arguments.
@ -1867,5 +2303,8 @@ int main(int argc, char *argv[])
}
// Run automated test suite.
return autoTestSuite();
fputs("Running automated test suite.\n", stdout);
retVal = !autoTestSuite(false, true);
avr_terminate(avr);
return retVal;
}