Write automated test suite, 8 MHz clock, code cleanup.
This commit is contained in:
parent
1d36abf470
commit
3079bd621f
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue