mirror of
https://github.com/quorten/macsehw.git
synced 2024-06-04 01:29:31 +00:00
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>
|
* 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/io.h>
|
||||||
#include <avr/wdt.h>
|
#include <avr/wdt.h>
|
||||||
#include <avr/interrupt.h>
|
#include <avr/interrupt.h>
|
||||||
|
@ -46,7 +53,8 @@ typedef uint8_t byte;
|
||||||
* RTC chip in early Apple Macintosh *
|
* RTC chip in early Apple Macintosh *
|
||||||
* computers, using an ATtiny85. *
|
* computers, using an ATtiny85. *
|
||||||
* Uses an external 32.768kHz crystal *
|
* 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 *
|
* 1SEC -|1 \/ 8|- VCC *
|
||||||
* XTAL2 -|2 7|- RTC.CLK *
|
* 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_DATA_PIN = 1; // Bi-directional serial data line on PB1
|
||||||
const int SERIAL_CLOCK_PIN = 2; // Serial clock input on PB2
|
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
|
#if NoXPRAM
|
||||||
// Models earlier than the Plus had 20 bytes of PRAM
|
// Models earlier than the Plus had 20 bytes of PRAM
|
||||||
#define PRAM_SIZE 20
|
#define PRAM_SIZE 20
|
||||||
|
@ -83,6 +137,31 @@ const int group1Base = 0x10;
|
||||||
const int group2Base = 0x08;
|
const int group2Base = 0x08;
|
||||||
#endif
|
#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,
|
enum SerialStateType { SERIAL_DISABLED, RECEIVING_COMMAND,
|
||||||
SENDING_DATA, RECEIVING_DATA,
|
SENDING_DATA, RECEIVING_DATA,
|
||||||
RECEIVING_XCMD_ADDR, RECEIVING_XCMD_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 writeProtect = 0;
|
||||||
volatile byte pram[PRAM_SIZE] = {}; // PRAM initialized as zeroed data
|
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) \
|
#define shiftReadPB(output, bitNum, portBit) \
|
||||||
bitWrite(output, bitNum, ((PINB&_BV(portBit))) ? 1 : 0)
|
bitWrite(output, bitNum, ((PINB&_BV(portBit))) ? 1 : 0)
|
||||||
|
|
||||||
|
@ -171,8 +287,7 @@ void setup(void)
|
||||||
//set up timer
|
//set up timer
|
||||||
bitSet(GTCCR, TSM); // Turns off timers while we set it up
|
bitSet(GTCCR, TSM); // Turns off timers while we set it up
|
||||||
bitSet(TIMSK, TOIE0); // Set Timer/Counter0 Overflow Interrupt Enable
|
bitSet(TIMSK, TOIE0); // Set Timer/Counter0 Overflow Interrupt Enable
|
||||||
// NOTE: 0b111 external clock, 0b011, uses 1/64 prescaler on I/O clock.
|
TCCR0B = PRESCALER_MASK; // Set prescaler
|
||||||
TCCR0B = 0b011; // Set prescaler, 32,768Hz/64 = 512Hz, fills up the 8-bit counter (256) once every half second
|
|
||||||
TCNT0 = 0; // Clear the counter
|
TCNT0 = 0; // Clear the counter
|
||||||
bitClear(GTCCR, TSM); // Turns timers back on
|
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
|
* An interrupt to both increment the seconds counter and generate the
|
||||||
* square wave
|
* square wave
|
||||||
*/
|
*/
|
||||||
void halfSecondInterrupt(void)
|
void oflowInterrupt(void)
|
||||||
{
|
{
|
||||||
PINB = 1<<ONE_SEC_PIN; // Flip the one-second pin
|
numOflows++;
|
||||||
if (!(PINB&(1<<ONE_SEC_PIN))) { // If the one-second pin is low
|
if (numOflows == LIM_OFLOWS) {
|
||||||
seconds++;
|
// Configure a timer interrupt to handle the final remainder cycle
|
||||||
// Make up for lost time, something around 6.4 cycles.
|
// wait. We simply subtract the value from 256, which is the same
|
||||||
TCNT0 += 6;
|
// 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
|
if (address == 12) // test write
|
||||||
return WRTEST_CMD;
|
return WRTEST_CMD;
|
||||||
if (address == 13) // write-protect
|
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;
|
return INVALID_CMD;
|
||||||
} else {
|
} else {
|
||||||
|
@ -426,7 +553,7 @@ void loop(void)
|
||||||
// Read the data byte before continuing.
|
// Read the data byte before continuing.
|
||||||
serialState = RECEIVING_XCMD_DATA;
|
serialState = RECEIVING_XCMD_DATA;
|
||||||
serialBitNum = 0;
|
serialBitNum = 0;
|
||||||
serialData = 0;
|
serialData = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,15 +572,15 @@ void loop(void)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Write the PRAM register.
|
// Write the PRAM register.
|
||||||
if (!writeProtect)
|
if (!writeProtect)
|
||||||
pram[address] = serialData;
|
pram[address] = serialData;
|
||||||
// Finished with the write command.
|
// Finished with the write command.
|
||||||
clearState();
|
clearState();
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Invalid command.
|
// Invalid state.
|
||||||
clearState();
|
clearState();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -481,7 +608,7 @@ ISR(PCINT0_vect)
|
||||||
|
|
||||||
ISR(TIMER0_OVF_vect)
|
ISR(TIMER0_OVF_vect)
|
||||||
{
|
{
|
||||||
halfSecondInterrupt();
|
oflowInterrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arduino main function.
|
// Arduino main function.
|
||||||
|
|
|
@ -76,6 +76,7 @@ typedef enum { false, true } bool;
|
||||||
|
|
||||||
typedef uint8_t byte;
|
typedef uint8_t byte;
|
||||||
typedef bool boolean;
|
typedef bool boolean;
|
||||||
|
typedef uint8_t bool8_t;
|
||||||
|
|
||||||
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
|
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
|
||||||
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
|
#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,
|
avr_cycle_count_t notify_timeup(avr_t *avr, avr_cycle_count_t when,
|
||||||
void *param);
|
void *param);
|
||||||
|
|
||||||
|
@ -213,67 +214,40 @@ void viaBitWrite(uint8_t *ptr, uint8_t bit, uint8_t bitvalue)
|
||||||
silicon RTC. */
|
silicon RTC. */
|
||||||
void waitQuarterCycle(void)
|
void waitQuarterCycle(void)
|
||||||
{
|
{
|
||||||
|
#ifdef RPI_DRIVER
|
||||||
struct timespec tv = { 0, 500000 };
|
struct timespec tv = { 0, 500000 };
|
||||||
struct timespec tvNext;
|
struct timespec tvNext;
|
||||||
#ifdef RPI_DRIVER
|
|
||||||
do {
|
do {
|
||||||
if (clock_nanosleep(CLOCK_MONOTONIC, 0, &tv, &tvNext) == 0)
|
if (clock_nanosleep(CLOCK_MONOTONIC, 0, &tv, &tvNext) == 0)
|
||||||
break;
|
break;
|
||||||
tv.tv_nsec = tvNext.tv_nsec;
|
tv.tv_nsec = tvNext.tv_nsec;
|
||||||
} while (tv.tv_nsec > 0);
|
} while (tv.tv_nsec > 0);
|
||||||
#else
|
#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
|
// Unfortunately, if the AVR runs at 32.768 kHz, I've found from
|
||||||
// simulation that serial communications are only reliable at an
|
// simulation that serial communications are only reliable at an
|
||||||
// abysmal 50 Hz serial clock speed. Therefore, running at a higher
|
// abysmal 50 Hz serial clock speed. Therefore, running at a higher
|
||||||
// core speed and using a phase-locked loop on the crystal clock
|
// core speed and using a phase-locked loop on the crystal clock
|
||||||
// frequency a must.
|
// frequency a must.
|
||||||
{ // Use this alternative for slow simulation.
|
struct timespec tv, tvTarget;
|
||||||
struct timespec tv, tvTarget;
|
// N.B. Over here we are using cycle timers mainly to prevent
|
||||||
// N.B. Over here we are using cycle timers mainly to prevent
|
// simulation waits stretching unbearably long.
|
||||||
// simulation waits stretching unbearably long.
|
g_timePoll = 16;
|
||||||
g_timePoll = 16;
|
avr_cycle_timer_register(avr, g_timePoll, notify_timeup, NULL);
|
||||||
avr_cycle_timer_register(avr, g_timePoll, notify_timeup, NULL);
|
clock_gettime(CLOCK_MONOTONIC, &tv);
|
||||||
clock_gettime(CLOCK_MONOTONIC, &tv);
|
tvTarget.tv_nsec = tv.tv_nsec + 500000;
|
||||||
tvTarget.tv_nsec = tv.tv_nsec + 500000 * 10;
|
tvTarget.tv_sec = tv.tv_sec;
|
||||||
tvTarget.tv_sec = tv.tv_sec;
|
if (tvTarget.tv_nsec >= 1000000000) {
|
||||||
if (tvTarget.tv_nsec >= 1000000000) {
|
tvTarget.tv_nsec -= 1000000000;
|
||||||
tvTarget.tv_nsec -= 1000000000;
|
tvTarget.tv_sec++;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,12 +263,36 @@ void waitCycle(void)
|
||||||
waitHalfCycle();
|
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 */
|
/* PRAM C library module */
|
||||||
|
|
||||||
// Configure whether the PRAM should be traditional 20-byte PRAM
|
// Configure whether the PRAM should be traditional 20-byte PRAM
|
||||||
// (false) or XPRAM (true).
|
// (false) or XPRAM (true).
|
||||||
void setPramType(boolean isXPram)
|
void setPramType(bool8_t isXPram)
|
||||||
{
|
{
|
||||||
if (isXPram) {
|
if (isXPram) {
|
||||||
pramSize = 256;
|
pramSize = 256;
|
||||||
|
@ -308,7 +306,7 @@ void setPramType(boolean isXPram)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return true if the PRAM type is set to XPRAM, false otherwise.
|
// Return true if the PRAM type is set to XPRAM, false otherwise.
|
||||||
boolean getPramType(void)
|
bool8_t getPramType(void)
|
||||||
{
|
{
|
||||||
if (pramSize == 256)
|
if (pramSize == 256)
|
||||||
return true;
|
return true;
|
||||||
|
@ -429,7 +427,7 @@ void clearWriteProtect(void)
|
||||||
compared for equality to verify a consistent read. If the read is
|
compared for equality to verify a consistent read. If the read is
|
||||||
inconsistent, this function will retry up to a maximum of 4 times
|
inconsistent, this function will retry up to a maximum of 4 times
|
||||||
before returning failure. */
|
before returning failure. */
|
||||||
boolean dumpTime(void)
|
bool8_t dumpTime(void)
|
||||||
{
|
{
|
||||||
uint8_t retry = 0;
|
uint8_t retry = 0;
|
||||||
uint32_t newTime1, newTime2;
|
uint32_t newTime1, newTime2;
|
||||||
|
@ -447,9 +445,7 @@ boolean dumpTime(void)
|
||||||
newTime2 |= sendReadCmd(0x98) << 16;
|
newTime2 |= sendReadCmd(0x98) << 16;
|
||||||
newTime2 |= sendReadCmd(0x9c) << 24;
|
newTime2 |= sendReadCmd(0x9c) << 24;
|
||||||
|
|
||||||
// TODO FIXME DEBUG USE ONLY: Quirk to work around running the
|
if (newTime1 == newTime2) {
|
||||||
// serial communication clock too slowly.
|
|
||||||
if (true || newTime1 == newTime2) {
|
|
||||||
// TODO: Use mutex lock for time update.
|
// TODO: Use mutex lock for time update.
|
||||||
timeSecs = newTime1;
|
timeSecs = newTime1;
|
||||||
return true;
|
return true;
|
||||||
|
@ -575,7 +571,7 @@ void setCurTime(void)
|
||||||
// Convenience function to generate a traditional PRAM command from
|
// Convenience function to generate a traditional PRAM command from
|
||||||
// logical command address and write-request flag. `addr` must not
|
// logical command address and write-request flag. `addr` must not
|
||||||
// exceed 0x1f.
|
// exceed 0x1f.
|
||||||
byte genCmd(byte addr, boolean writeRequest)
|
byte genCmd(byte addr, bool8_t writeRequest)
|
||||||
{
|
{
|
||||||
return ((!writeRequest) << 7) | (addr << 2);
|
return ((!writeRequest) << 7) | (addr << 2);
|
||||||
}
|
}
|
||||||
|
@ -623,7 +619,7 @@ void loadAllTradMem(void)
|
||||||
// Generate an extended command from a byte address. The first byte
|
// Generate an extended command from a byte address. The first byte
|
||||||
// to send is the most significant byte in the returned 16-bit
|
// to send is the most significant byte in the returned 16-bit
|
||||||
// integer.
|
// 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);
|
uint16_t xcmd = 0x3800 | ((addr & 0xe0) << 3) | ((addr & 0x1f) << 2);
|
||||||
if (!writeRequest)
|
if (!writeRequest)
|
||||||
|
@ -652,7 +648,8 @@ void dumpAllXMem(void)
|
||||||
do {
|
do {
|
||||||
pram[i] = genSendReadXCmd(i);
|
pram[i] = genSendReadXCmd(i);
|
||||||
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.
|
// Clear write-protect and copy all XPRAM memory from host to RTC.
|
||||||
|
@ -663,7 +660,8 @@ void loadAllXMem(void)
|
||||||
do {
|
do {
|
||||||
genSendWriteXCmd(i, pram[i]);
|
genSendWriteXCmd(i, pram[i]);
|
||||||
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
|
/* For 20-byte equivalent PRAM commands, read or write the
|
||||||
|
@ -675,7 +673,7 @@ void loadAllXMem(void)
|
||||||
firmware. */
|
firmware. */
|
||||||
byte hostTradPramCmd(byte cmd, byte data)
|
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
|
// Discard the first bit and the last two bits, it's not pertinent
|
||||||
// to address interpretation.
|
// to address interpretation.
|
||||||
byte address = (cmd&~(1<<7))>>2;
|
byte address = (cmd&~(1<<7))>>2;
|
||||||
|
@ -709,8 +707,12 @@ byte hostTradPramCmd(byte cmd, byte data)
|
||||||
writeProtect = ((data & 0x80)) ? 1 : 0;
|
writeProtect = ((data & 0x80)) ? 1 : 0;
|
||||||
// Fall through to send command to RTC.
|
// Fall through to send command to RTC.
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
return 0; // invalid command
|
// 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
|
} else
|
||||||
return 0; // invalid command
|
return 0; // invalid command
|
||||||
} else {
|
} else {
|
||||||
|
@ -746,7 +748,7 @@ byte hostReadXMem(byte address)
|
||||||
// Load the host copy of the traditional PRAM from a file and update
|
// Load the host copy of the traditional PRAM from a file and update
|
||||||
// the RTC device memory. Also clears write-protect. Returns true on
|
// the RTC device memory. Also clears write-protect. Returns true on
|
||||||
// success, false on failure.
|
// success, false on failure.
|
||||||
boolean fileLoadAllTradMem(const char *filename)
|
bool8_t fileLoadAllTradMem(const char *filename)
|
||||||
{
|
{
|
||||||
FILE *fp = fopen(filename, "rb");
|
FILE *fp = fopen(filename, "rb");
|
||||||
int ch;
|
int ch;
|
||||||
|
@ -784,7 +786,7 @@ boolean fileLoadAllTradMem(const char *filename)
|
||||||
|
|
||||||
// Save the host copy of the traditional PRAM to a file. Returns true
|
// Save the host copy of the traditional PRAM to a file. Returns true
|
||||||
// on success, false on failure.
|
// on success, false on failure.
|
||||||
boolean fileDumpAllTradMem(const char *filename)
|
bool8_t fileDumpAllTradMem(const char *filename)
|
||||||
{
|
{
|
||||||
FILE *fp = fopen(filename, "wb");
|
FILE *fp = fopen(filename, "wb");
|
||||||
uint8_t i;
|
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
|
// Load the host copy of the XPRAM from a file and update the RTC
|
||||||
// device memory. Also clears write-protect. Returns true on
|
// device memory. Also clears write-protect. Returns true on
|
||||||
// success, false on failure.
|
// success, false on failure.
|
||||||
boolean fileLoadAllXMem(const char *filename)
|
bool8_t fileLoadAllXMem(const char *filename)
|
||||||
{
|
{
|
||||||
FILE *fp = fopen(filename, "rb");
|
FILE *fp = fopen(filename, "rb");
|
||||||
if (fp == NULL)
|
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
|
// Save the host copy of the XPRAM to a file. Returns true on
|
||||||
// success, false on failure.
|
// success, false on failure.
|
||||||
boolean fileDumpAllXMem(const char *filename)
|
bool8_t fileDumpAllXMem(const char *filename)
|
||||||
{
|
{
|
||||||
FILE *fp = fopen(filename, "wb");
|
FILE *fp = fopen(filename, "wb");
|
||||||
if (fp == NULL)
|
if (fp == NULL)
|
||||||
|
@ -851,8 +853,9 @@ void simRec(void);
|
||||||
void simNoRec(void);
|
void simNoRec(void);
|
||||||
void setMonMode(uint8_t newMonMode);
|
void setMonMode(uint8_t newMonMode);
|
||||||
uint8_t getMonMode(void);
|
uint8_t getMonMode(void);
|
||||||
byte monMemAccess(uint16_t address, boolean writeRequest, byte data);
|
byte monMemAccess(uint16_t address, bool8_t writeRequest, byte data);
|
||||||
boolean execMonLine(char *lineBuf);
|
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
|
/* Since every subroutine for command-line commands only has zero to
|
||||||
three arguments, all being numeric except for the file commands
|
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
|
// Bit flag 2|0: Quit command encountered vs. continue
|
||||||
uint8_t execCmdLine(char *lineBuf)
|
uint8_t execCmdLine(char *lineBuf)
|
||||||
{
|
{
|
||||||
boolean splitCmd = false;
|
bool8_t splitCmd = false;
|
||||||
char *cmdName;
|
char *cmdName;
|
||||||
char *parsePtr = lineBuf;
|
char *parsePtr = lineBuf;
|
||||||
SKIP_WHITESPACE(parsePtr);
|
SKIP_WHITESPACE(parsePtr);
|
||||||
|
@ -959,6 +962,7 @@ uint8_t execCmdLine(char *lineBuf)
|
||||||
" file-dump-all-xmem filename\n"
|
" file-dump-all-xmem filename\n"
|
||||||
" sim-rec -- start recording RTC pin signal waveforms\n"
|
" sim-rec -- start recording RTC pin signal waveforms\n"
|
||||||
" sim-no-rec -- stop 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"
|
" q, quit -- exit the program\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Most commands are named after the corresponding library subroutines,\n"
|
"Most commands are named after the corresponding library subroutines,\n"
|
||||||
|
@ -1045,8 +1049,10 @@ uint8_t execCmdLine(char *lineBuf)
|
||||||
clearWriteProtect();
|
clearWriteProtect();
|
||||||
return 1;
|
return 1;
|
||||||
} else if (strcmp(cmdName, "dump-time") == 0) {
|
} else if (strcmp(cmdName, "dump-time") == 0) {
|
||||||
|
byte result;
|
||||||
PARSE_8BIT_HEAD(0);
|
PARSE_8BIT_HEAD(0);
|
||||||
dumpTime();
|
result = dumpTime();
|
||||||
|
printf("0x%02x\n", result);
|
||||||
return 1;
|
return 1;
|
||||||
} else if (strcmp(cmdName, "load-time") == 0) {
|
} else if (strcmp(cmdName, "load-time") == 0) {
|
||||||
PARSE_8BIT_HEAD(0);
|
PARSE_8BIT_HEAD(0);
|
||||||
|
@ -1199,6 +1205,12 @@ uint8_t execCmdLine(char *lineBuf)
|
||||||
PARSE_8BIT_HEAD(0);
|
PARSE_8BIT_HEAD(0);
|
||||||
simNoRec();
|
simNoRec();
|
||||||
return 1;
|
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 ||
|
} else if (strcmp(cmdName, "q") == 0 ||
|
||||||
strcmp(cmdName, "quit") == 0) {
|
strcmp(cmdName, "quit") == 0) {
|
||||||
return 3; // Time to quit.
|
return 3; // Time to quit.
|
||||||
|
@ -1233,7 +1245,7 @@ uint8_t execCmdLine(char *lineBuf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return false on exit with error, true on graceful exit.
|
// Return false on exit with error, true on graceful exit.
|
||||||
boolean cmdLoop(void)
|
bool8_t cmdLoop(void)
|
||||||
{
|
{
|
||||||
uint8_t retVal = true;
|
uint8_t retVal = true;
|
||||||
char lineBuf[512];
|
char lineBuf[512];
|
||||||
|
@ -1298,7 +1310,7 @@ uint8_t getMonMode(void)
|
||||||
and do nothing on write. For reads, `data` is ignored. Returns
|
and do nothing on write. For reads, `data` is ignored. Returns
|
||||||
data on successful reads, zero on unsuccessful reads, one on
|
data on successful reads, zero on unsuccessful reads, one on
|
||||||
successful writes, zero on unsuccessful writes. */
|
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) {
|
if (monMode == 1) {
|
||||||
// Traditional PRAM
|
// Traditional PRAM
|
||||||
|
@ -1345,7 +1357,7 @@ void dumphex(unsigned short addr, unsigned short end_addr,
|
||||||
unsigned char one_line);
|
unsigned char one_line);
|
||||||
void writehex(char *rch);
|
void writehex(char *rch);
|
||||||
|
|
||||||
boolean execMonLine(char *lineBuf)
|
bool8_t execMonLine(char *lineBuf)
|
||||||
{
|
{
|
||||||
char ch;
|
char ch;
|
||||||
g_monLineBuf = lineBuf;
|
g_monLineBuf = lineBuf;
|
||||||
|
@ -1625,7 +1637,7 @@ sig_int(int sign)
|
||||||
exit(0);
|
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;
|
elf_firmware_t f;
|
||||||
|
|
||||||
|
@ -1634,7 +1646,9 @@ int setupSimAvr(char *progName, const char *fname, boolean interactMode)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
strcpy(f.mmcu, "attiny85");
|
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);
|
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
|
// Run a single step of the AVR simulation, return true if the
|
||||||
// simulation should continue, false if it should stop.
|
// simulation should continue, false if it should stop.
|
||||||
boolean simAvrStep(void)
|
bool8_t simAvrStep(void)
|
||||||
{
|
{
|
||||||
// Simulation main loop.
|
// Simulation main loop.
|
||||||
int state = avr_run(avr);
|
int state = avr_run(avr);
|
||||||
|
@ -1784,46 +1798,468 @@ boolean simAvrStep(void)
|
||||||
/********************************************************************/
|
/********************************************************************/
|
||||||
/* Automated test suite module */
|
/* 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
|
// Use a non-deterministic seed for randomized tests... but print
|
||||||
second counting is working correctly.
|
// 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
|
if (!simRealTime) {
|
||||||
absolutely nothing.
|
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
|
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
|
{ /* Set/clear write-protect, test seconds registers, traditional
|
||||||
and reads with write-protect set and clear.
|
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
|
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
|
seconds, incrementing on the one-second interrupt, then read
|
||||||
the clock register again. The values should match
|
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
|
{ /* Write/read memory regions randomly and verify expected memory
|
||||||
behavior.
|
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
|
Select 8 bytes at random for testing.
|
||||||
behavior.
|
*/
|
||||||
|
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, 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);
|
printf("\n%d passed, %d failed, %d skipped\n",
|
||||||
return 1;
|
numTests - failCount - skipCount, failCount, skipCount);
|
||||||
|
return (failCount == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/********************************************************************/
|
/********************************************************************/
|
||||||
|
@ -1832,7 +2268,7 @@ int autoTestSuite(void)
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
char *firmwareName = "";
|
char *firmwareName = "";
|
||||||
boolean interactMode = false;
|
bool8_t interactMode = false;
|
||||||
int retVal;
|
int retVal;
|
||||||
|
|
||||||
{ // Parse command-line arguments.
|
{ // Parse command-line arguments.
|
||||||
|
@ -1867,5 +2303,8 @@ int main(int argc, char *argv[])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run automated test suite.
|
// 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
Block a user