aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/peripherals/spi.spec.ts2
-rw-r--r--src/peripherals/timer.spec.ts216
-rw-r--r--src/peripherals/timer.ts166
-rw-r--r--src/utils/assembler.spec.ts3
-rw-r--r--src/utils/assembler.ts5
-rw-r--r--src/utils/test-utils.ts28
6 files changed, 353 insertions, 67 deletions
diff --git a/src/peripherals/spi.spec.ts b/src/peripherals/spi.spec.ts
index 635e657..f323b88 100644
--- a/src/peripherals/spi.spec.ts
+++ b/src/peripherals/spi.spec.ts
@@ -160,7 +160,7 @@ describe('SPI', () => {
return 0x5b; // we copy this byte to
};
- const runner = new TestProgramRunner(cpu);
+ const runner = new TestProgramRunner(cpu, () => 0);
runner.runToBreak();
// 16 cycles per clock * 8 bits = 128
diff --git a/src/peripherals/timer.spec.ts b/src/peripherals/timer.spec.ts
index 3b5026c..1fa0dc6 100644
--- a/src/peripherals/timer.spec.ts
+++ b/src/peripherals/timer.spec.ts
@@ -246,6 +246,7 @@ describe('timer', () => {
cpu.tick();
cpu.cycles = 2;
cpu.tick();
+ expect(cpu.readData(TCNT0)).toEqual(0);
expect(cpu.data[TIFR0] & (OCF0A | OCF0B)).toEqual(OCF0A | OCF0B);
expect(cpu.pc).toEqual(0);
expect(cpu.cycles).toEqual(2);
@@ -417,7 +418,7 @@ describe('timer', () => {
const { program, instructionCount } = asmProgram(`
LDI r16, 0x1 ; TCCR0B = 1 << CS00;
OUT 0x25, r16
- LDI r16, 0x30 ; TCNT <- 0x30
+ LDI r16, 0x30 ; TCNT0 <- 0x30
OUT 0x26, r16
NOP
IN r17, 0x26 ; r17 <- TCNT
@@ -495,6 +496,152 @@ describe('timer', () => {
expect(cpu.readData(R17)).toEqual(2);
});
+ describe('Fast PWM mode', () => {
+ it('should set OC0A on Compare Match, clear on Bottom (issue #78)', () => {
+ const { program, labels } = asmProgram(`
+ LDI r16, 0xfc ; TCNT0 = 0xfc;
+ OUT 0x26, r16
+ LDI r16, 0xfe ; OCR0A = 0xfe;
+ OUT 0x27, r16
+ ; WGM: Fast PWM, enable OC0A mode 3 (set on Compare Match, clear on Bottom)
+ LDI r16, 0xc3 ; TCCR0A = (1 << COM0A1) | (1 << COM0A0) | (1 << WGM01) | (1 << WGM00);
+ OUT 0x24, r16
+ LDI r16, 0x1 ; TCCR0B = 1 << CS00;
+ OUT 0x25, r16
+
+ NOP ; TCNT is now 0xfd
+ beforeMatch:
+ NOP ; TCNT is now 0xfe (Compare Match)
+ afterMatch:
+ NOP ; TCNT is now 0xff
+ beforeBottom:
+ NOP ; TCNT is now 0x00 (BOTTOM)
+ afterBottom:
+ NOP
+ `);
+
+ const cpu = new CPU(program);
+ new AVRTimer(cpu, timer0Config);
+
+ // Listen to Port D's internal callback
+ const gpioCallback = jest.fn();
+ cpu.gpioTimerHooks[PORTD] = gpioCallback;
+
+ const runner = new TestProgramRunner(cpu);
+
+ runner.runToAddress(labels.beforeMatch);
+ expect(cpu.readData(TCNT0)).toEqual(0xfd);
+ expect(gpioCallback).toHaveBeenCalledTimes(1);
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable, 0x2b); // OC0A: Enable
+ gpioCallback.mockClear();
+
+ runner.runToAddress(labels.afterMatch);
+ expect(cpu.readData(TCNT0)).toEqual(0xfe);
+ expect(gpioCallback).toHaveBeenCalledTimes(1);
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Set, 0x2b); // OC0A: Set
+ gpioCallback.mockClear();
+
+ runner.runToAddress(labels.beforeBottom);
+ expect(cpu.readData(TCNT0)).toEqual(0xff);
+ expect(gpioCallback).toHaveBeenCalledTimes(0);
+ gpioCallback.mockClear();
+
+ runner.runToAddress(labels.afterBottom);
+ expect(cpu.readData(TCNT0)).toEqual(0x0);
+ expect(gpioCallback).toHaveBeenCalledTimes(1);
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Clear, 0x2b); // OC0A: Clear
+ });
+
+ it('should toggle OC0A on Compare Match when COM0An = 1 (issue #78)', () => {
+ const { program, labels } = asmProgram(`
+ LDI r16, 0xfc ; TCNT0 = 0xfc;
+ OUT 0x26, r16
+ LDI r16, 0xfe ; OCR0A = 0xfe;
+ OUT 0x27, r16
+ ; WGM: Fast PWM, enable OC0A mode 1 (Toggle)
+ LDI r16, 0x43 ; TCCR0A = (1 << COM0A0) | (1 << WGM01) | (1 << WGM00);
+ OUT 0x24, r16
+ LDI r16, 0x09 ; TCCR0B = (1 << WGM02) | (1 << CS00);
+ OUT 0x25, r16
+
+ NOP ; TCNT is now 0xfd
+ beforeMatch:
+ NOP ; TCNT is now 0xfe (Compare Match, TOP)
+ afterMatch:
+ NOP ; TCNT is now 0
+ afterOverflow:
+ NOP
+ `);
+
+ const cpu = new CPU(program);
+ new AVRTimer(cpu, timer0Config);
+
+ // Listen to Port D's internal callback
+ const gpioCallback = jest.fn();
+ cpu.gpioTimerHooks[PORTD] = gpioCallback;
+
+ const runner = new TestProgramRunner(cpu);
+
+ runner.runToAddress(labels.beforeMatch);
+ expect(cpu.readData(TCNT0)).toEqual(0xfd);
+ expect(gpioCallback).toHaveBeenCalledTimes(1);
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable, 0x2b); // OC0A: Enable
+ gpioCallback.mockClear();
+
+ runner.runToAddress(labels.afterMatch);
+ expect(cpu.readData(TCNT0)).toEqual(0xfe);
+ expect(gpioCallback).toHaveBeenCalledTimes(1);
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Toggle, 0x2b); // OC0A: Toggle
+ gpioCallback.mockClear();
+
+ runner.runToAddress(labels.afterOverflow);
+ expect(cpu.readData(TCNT0)).toEqual(0);
+ expect(gpioCallback).toHaveBeenCalledTimes(0);
+ });
+
+ it('should leave OC0A disconnected when COM0An = 1 and WGM02 = 0 (issue #78)', () => {
+ const { program, labels } = asmProgram(`
+ LDI r16, 0xfc ; TCNT0 = 0xfc;
+ OUT 0x26, r16
+ LDI r16, 0xfe ; OCR0A = 0xfe;
+ OUT 0x27, r16
+ ; WGM: Fast PWM mode 7, enable OC0A mode 1 (Toggle)
+ LDI r16, 0x43 ; TCCR0A = (1 << COM0A0) | (1 << WGM01) | (1 << WGM00);
+ OUT 0x24, r16
+ LDI r16, 0x09 ; TCCR0B = (1 << WGM02) | (1 << CS00);
+ OUT 0x25, r16
+
+ beforeClearWGM02:
+ LDI r16, 0x01 ; TCCR0B = (1 << CS00);
+ OUT 0x25, r16
+
+ afterClearWGM02:
+ NOP
+ `);
+
+ const cpu = new CPU(program);
+ new AVRTimer(cpu, timer0Config);
+
+ // Listen to Port D's internal callback
+ const gpioCallback = jest.fn();
+ cpu.gpioTimerHooks[PORTD] = gpioCallback;
+
+ const runner = new TestProgramRunner(cpu);
+
+ // First, run with the bit set and assert that the Pin Override was enabled (OC0A connected)
+ runner.runToAddress(labels.beforeClearWGM02);
+ expect(gpioCallback).toHaveBeenCalledTimes(1);
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable, 0x2b);
+ gpioCallback.mockClear();
+
+ // Now clear WGM02 and observe that Pin Override was disabled (OC0A disconnected)
+ runner.runToAddress(labels.afterClearWGM02);
+ expect(gpioCallback).toHaveBeenCalledTimes(1);
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.None, 0x2b);
+ gpioCallback.mockClear();
+ });
+ });
+
describe('Phase-correct PWM mode', () => {
it('should count up to TOP, down to 0, and then set TOV flag', () => {
const { program, instructionCount } = asmProgram(`
@@ -575,6 +722,71 @@ describe('timer', () => {
expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Set, 0x2b);
});
+ it('should toggle OC0A when TCNT0=OCR0A and COM0An=1 (issue #78)', () => {
+ const { program, labels } = asmProgram(`
+ LDI r16, 0xfe ; OCR0A = 0xfe; // <- TOP value
+ OUT 0x27, r16
+ ; Set waveform generation mode (WGM) to PWM, Phase Correct (mode 5)
+ LDI r16, 0x41 ; TCCR0A = (1 << COM0A0) | (1 << WGM00);
+ OUT 0x24, r16
+ LDI r16, 0x09 ; TCCR0B = (1 << WGM02) | (1 << CS00);
+ OUT 0x25, r16
+ LDI r16, 0xfd ; TCNT0 = 0xfd;
+ OUT 0x26, r16
+
+ beforeMatch:
+ NOP ; TCNT0 will be 0xfe
+ afterMatch:
+ NOP
+ `);
+
+ const cpu = new CPU(program);
+ new AVRTimer(cpu, timer0Config);
+
+ // Listen to Port D's internal callback
+ const gpioCallback = jest.fn();
+ cpu.gpioTimerHooks[PORTD] = gpioCallback;
+
+ const runner = new TestProgramRunner(cpu);
+
+ runner.runToAddress(labels.beforeMatch);
+ expect(cpu.readData(TCNT0)).toEqual(0xfd);
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable, 0x2b);
+ gpioCallback.mockClear();
+
+ runner.runToAddress(labels.afterMatch);
+ expect(cpu.readData(TCNT0)).toEqual(0xfe);
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Toggle, 0x2b);
+ gpioCallback.mockClear();
+ });
+
+ it('should leave OC0A disconnected TCNT0=OCR0A and COM0An=1 in WGM mode 1 (issue #78)', () => {
+ const { program, instructionCount } = asmProgram(`
+ LDI r16, 0xfe ; OCR0A = 0xfe; // <- TOP value
+ OUT 0x27, r16
+ ; Set waveform generation mode (WGM) to PWM, Phase Correct (mode 1)
+ LDI r16, 0x41 ; TCCR0A = (1 << COM0A0) | (1 << WGM00);
+ OUT 0x24, r16
+ LDI r16, 0x01 ; TCCR0B = (1 << CS00);
+ OUT 0x25, r16
+ LDI r16, 0xfd ; TCNT0 = 0xfd;
+ OUT 0x26, r16
+ `);
+
+ const cpu = new CPU(program);
+ new AVRTimer(cpu, timer0Config);
+
+ // Listen to Port D's internal callback
+ const gpioCallback = jest.fn();
+ cpu.gpioTimerHooks[PORTD] = gpioCallback;
+
+ const runner = new TestProgramRunner(cpu);
+ runner.runInstructions(instructionCount);
+
+ // Assert that the pin callback wasn't called (thus it's disconnected)
+ expect(gpioCallback).not.toHaveBeenCalled();
+ });
+
it('should not miss Compare Match when executing multi-cycle instruction (issue #79)', () => {
const { program, instructionCount } = asmProgram(`
LDI r16, 0x10 ; OCR0A = 0x10; // <- TOP value
@@ -793,7 +1005,7 @@ describe('timer', () => {
expect(gpioCallback).toHaveBeenCalledWith(2, PinOverrideMode.Toggle, 0x25);
});
- it('should only update OCR0A when TCNT0=BOTTOM in PWM Phase/Frequency Correct mode (issue #76)', () => {
+ it('should only update OCR1A when TCNT1=BOTTOM in PWM Phase/Frequency Correct mode (issue #76)', () => {
const { program, instructionCount } = asmProgram(`
LDI r16, 0x0 ; OCR1AH = 0x0;
STS 0x89, r16
diff --git a/src/peripherals/timer.ts b/src/peripherals/timer.ts
index 6ea29e3..b1a64d9 100644
--- a/src/peripherals/timer.ts
+++ b/src/peripherals/timer.ts
@@ -189,37 +189,42 @@ const TopOCRA = 1;
const TopICR = 2;
type TimerTopValue = 0xff | 0x1ff | 0x3ff | 0xffff | typeof TopOCRA | typeof TopICR;
-type WGMConfig = [TimerMode, TimerTopValue, OCRUpdateMode, TOVUpdateMode];
+type WGMConfig = [TimerMode, TimerTopValue, OCRUpdateMode, TOVUpdateMode, number];
+
+// Enable Toggle mode for OCxA in PWM Wave Generation mode
+const OCToggle = 1;
+
+const { Normal, PWMPhaseCorrect, CTC, FastPWM, Reserved, PWMPhaseFrequencyCorrect } = TimerMode;
const wgmModes8Bit: WGMConfig[] = [
- /*0*/ [TimerMode.Normal, 0xff, OCRUpdateMode.Immediate, TOVUpdateMode.Max],
- /*1*/ [TimerMode.PWMPhaseCorrect, 0xff, OCRUpdateMode.Top, TOVUpdateMode.Bottom],
- /*2*/ [TimerMode.CTC, TopOCRA, OCRUpdateMode.Immediate, TOVUpdateMode.Max],
- /*3*/ [TimerMode.FastPWM, 0xff, OCRUpdateMode.Bottom, TOVUpdateMode.Max],
- /*4*/ [TimerMode.Reserved, 0xff, OCRUpdateMode.Immediate, TOVUpdateMode.Max],
- /*5*/ [TimerMode.PWMPhaseCorrect, TopOCRA, OCRUpdateMode.Top, TOVUpdateMode.Bottom],
- /*6*/ [TimerMode.Reserved, 0xff, OCRUpdateMode.Immediate, TOVUpdateMode.Max],
- /*7*/ [TimerMode.FastPWM, TopOCRA, OCRUpdateMode.Bottom, TOVUpdateMode.Top],
+ /*0*/ [Normal, 0xff, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
+ /*1*/ [PWMPhaseCorrect, 0xff, OCRUpdateMode.Top, TOVUpdateMode.Bottom, 0],
+ /*2*/ [CTC, TopOCRA, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
+ /*3*/ [FastPWM, 0xff, OCRUpdateMode.Bottom, TOVUpdateMode.Max, 0],
+ /*4*/ [Reserved, 0xff, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
+ /*5*/ [PWMPhaseCorrect, TopOCRA, OCRUpdateMode.Top, TOVUpdateMode.Bottom, OCToggle],
+ /*6*/ [Reserved, 0xff, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
+ /*7*/ [FastPWM, TopOCRA, OCRUpdateMode.Bottom, TOVUpdateMode.Top, OCToggle],
];
// Table 16-4 in the datasheet
const wgmModes16Bit: WGMConfig[] = [
- /*0 */ [TimerMode.Normal, 0xffff, OCRUpdateMode.Immediate, TOVUpdateMode.Max],
- /*1 */ [TimerMode.PWMPhaseCorrect, 0x00ff, OCRUpdateMode.Top, TOVUpdateMode.Bottom],
- /*2 */ [TimerMode.PWMPhaseCorrect, 0x01ff, OCRUpdateMode.Top, TOVUpdateMode.Bottom],
- /*3 */ [TimerMode.PWMPhaseCorrect, 0x03ff, OCRUpdateMode.Top, TOVUpdateMode.Bottom],
- /*4 */ [TimerMode.CTC, TopOCRA, OCRUpdateMode.Immediate, TOVUpdateMode.Max],
- /*5 */ [TimerMode.FastPWM, 0x00ff, OCRUpdateMode.Bottom, TOVUpdateMode.Top],
- /*6 */ [TimerMode.FastPWM, 0x01ff, OCRUpdateMode.Bottom, TOVUpdateMode.Top],
- /*7 */ [TimerMode.FastPWM, 0x03ff, OCRUpdateMode.Bottom, TOVUpdateMode.Top],
- /*8 */ [TimerMode.PWMPhaseFrequencyCorrect, TopICR, OCRUpdateMode.Bottom, TOVUpdateMode.Bottom],
- /*9 */ [TimerMode.PWMPhaseFrequencyCorrect, TopOCRA, OCRUpdateMode.Bottom, TOVUpdateMode.Bottom],
- /*10*/ [TimerMode.PWMPhaseCorrect, TopICR, OCRUpdateMode.Top, TOVUpdateMode.Bottom],
- /*11*/ [TimerMode.PWMPhaseCorrect, TopOCRA, OCRUpdateMode.Top, TOVUpdateMode.Bottom],
- /*12*/ [TimerMode.CTC, TopICR, OCRUpdateMode.Immediate, TOVUpdateMode.Max],
- /*13*/ [TimerMode.Reserved, 0xffff, OCRUpdateMode.Immediate, TOVUpdateMode.Max],
- /*14*/ [TimerMode.FastPWM, TopICR, OCRUpdateMode.Bottom, TOVUpdateMode.Top],
- /*15*/ [TimerMode.FastPWM, TopOCRA, OCRUpdateMode.Bottom, TOVUpdateMode.Top],
+ /*0 */ [Normal, 0xffff, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
+ /*1 */ [PWMPhaseCorrect, 0x00ff, OCRUpdateMode.Top, TOVUpdateMode.Bottom, 0],
+ /*2 */ [PWMPhaseCorrect, 0x01ff, OCRUpdateMode.Top, TOVUpdateMode.Bottom, 0],
+ /*3 */ [PWMPhaseCorrect, 0x03ff, OCRUpdateMode.Top, TOVUpdateMode.Bottom, 0],
+ /*4 */ [CTC, TopOCRA, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
+ /*5 */ [FastPWM, 0x00ff, OCRUpdateMode.Bottom, TOVUpdateMode.Top, 0],
+ /*6 */ [FastPWM, 0x01ff, OCRUpdateMode.Bottom, TOVUpdateMode.Top, 0],
+ /*7 */ [FastPWM, 0x03ff, OCRUpdateMode.Bottom, TOVUpdateMode.Top, 0],
+ /*8 */ [PWMPhaseFrequencyCorrect, TopICR, OCRUpdateMode.Bottom, TOVUpdateMode.Bottom, 0],
+ /*9 */ [PWMPhaseFrequencyCorrect, TopOCRA, OCRUpdateMode.Bottom, TOVUpdateMode.Bottom, OCToggle],
+ /*10*/ [PWMPhaseCorrect, TopICR, OCRUpdateMode.Top, TOVUpdateMode.Bottom, 0],
+ /*11*/ [PWMPhaseCorrect, TopOCRA, OCRUpdateMode.Top, TOVUpdateMode.Bottom, OCToggle],
+ /*12*/ [CTC, TopICR, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
+ /*13*/ [Reserved, 0xffff, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
+ /*14*/ [FastPWM, TopICR, OCRUpdateMode.Bottom, TOVUpdateMode.Top, OCToggle],
+ /*15*/ [FastPWM, TopOCRA, OCRUpdateMode.Bottom, TOVUpdateMode.Top, OCToggle],
];
type CompBitsValue = 0 | 1 | 2 | 3;
@@ -329,10 +334,6 @@ export class AVRTimer {
}
cpu.writeHooks[config.TCCRA] = (value) => {
this.cpu.data[config.TCCRA] = value;
- this.compA = ((value >> 6) & 0x3) as CompBitsValue;
- this.updateCompA(this.compA ? PinOverrideMode.Enable : PinOverrideMode.None);
- this.compB = ((value >> 4) & 0x3) as CompBitsValue;
- this.updateCompB(this.compB ? PinOverrideMode.Enable : PinOverrideMode.None);
this.updateWGMConfig();
return true;
};
@@ -406,12 +407,37 @@ export class AVRTimer {
}
private updateWGMConfig() {
- const wgmModes = this.config.bits === 16 ? wgmModes16Bit : wgmModes8Bit;
- const [timerMode, topValue, ocrUpdateMode, tovUpdateMode] = wgmModes[this.WGM];
+ const { config, WGM } = this;
+ const wgmModes = config.bits === 16 ? wgmModes16Bit : wgmModes8Bit;
+ const TCCRA = this.cpu.data[config.TCCRA];
+ const [timerMode, topValue, ocrUpdateMode, tovUpdateMode, flags] = wgmModes[WGM];
this.timerMode = timerMode;
this.topValue = topValue;
this.ocrUpdateMode = ocrUpdateMode;
this.tovUpdateMode = tovUpdateMode;
+
+ const pwmMode =
+ timerMode === FastPWM ||
+ timerMode === PWMPhaseCorrect ||
+ timerMode === PWMPhaseFrequencyCorrect;
+
+ const prevCompA = this.compA;
+ this.compA = ((TCCRA >> 6) & 0x3) as CompBitsValue;
+ if (this.compA === 1 && pwmMode && !(flags & OCToggle)) {
+ this.compA = 0;
+ }
+ if (!!prevCompA !== !!this.compA) {
+ this.updateCompA(this.compA ? PinOverrideMode.Enable : PinOverrideMode.None);
+ }
+
+ const prevCompB = this.compB;
+ this.compB = ((TCCRA >> 4) & 0x3) as CompBitsValue;
+ if (this.compB === 1 && pwmMode) {
+ this.compB = 0; // Reserved, according to the datasheet
+ }
+ if (!!prevCompB !== !!this.compB) {
+ this.updateCompB(this.compB ? PinOverrideMode.Enable : PinOverrideMode.None);
+ }
}
count = (reschedule = true) => {
@@ -423,8 +449,7 @@ export class AVRTimer {
this.lastCycle += counterDelta * divider;
const val = this.tcnt;
const { timerMode, TOP } = this;
- const phasePwm =
- timerMode === TimerMode.PWMPhaseCorrect || timerMode === TimerMode.PWMPhaseFrequencyCorrect;
+ const phasePwm = timerMode === PWMPhaseCorrect || timerMode === PWMPhaseFrequencyCorrect;
const newVal = phasePwm
? this.phasePwmCount(val, counterDelta)
: (val + counterDelta) % (TOP + 1);
@@ -432,10 +457,22 @@ export class AVRTimer {
// A CPU write overrides (has priority over) all counter clear or count operations.
if (!this.tcntUpdated) {
this.tcnt = newVal;
- this.timerUpdated(newVal, val);
+ if (!phasePwm) {
+ this.timerUpdated(newVal, val);
+ }
}
if (!phasePwm) {
+ if (timerMode === FastPWM && overflow) {
+ const { compA, compB } = this;
+ if (compA) {
+ this.updateCompPin(compA, 'A', true);
+ }
+ if (compB) {
+ this.updateCompPin(compB, 'B', true);
+ }
+ }
+
if (this.ocrUpdateMode == OCRUpdateMode.Bottom && overflow) {
// OCRUpdateMode.Top only occurs in Phase Correct modes, handled by phasePwmCount()
this.ocrA = this.nextOcrA;
@@ -476,10 +513,11 @@ export class AVRTimer {
};
private phasePwmCount(value: u16, delta: u8) {
+ const { ocrA, ocrB, TOP, tcntUpdated } = this;
while (delta > 0) {
if (this.countingUp) {
value++;
- if (value === this.TOP && !this.tcntUpdated) {
+ if (value === TOP && !tcntUpdated) {
this.countingUp = false;
if (this.ocrUpdateMode === OCRUpdateMode.Top) {
this.ocrA = this.nextOcrA;
@@ -488,7 +526,7 @@ export class AVRTimer {
}
} else {
value--;
- if (!value && !this.tcntUpdated) {
+ if (!value && !tcntUpdated) {
this.countingUp = true;
this.cpu.setInterruptFlag(this.OVF);
if (this.ocrUpdateMode === OCRUpdateMode.Bottom) {
@@ -497,28 +535,33 @@ export class AVRTimer {
}
}
}
+ if (!tcntUpdated && value === ocrA) {
+ this.cpu.setInterruptFlag(this.OCFA);
+ if (this.compA) {
+ this.updateCompPin(this.compA, 'A');
+ }
+ }
+ if (!tcntUpdated && value === ocrB) {
+ this.cpu.setInterruptFlag(this.OCFB);
+ if (this.compB) {
+ this.updateCompPin(this.compB, 'B');
+ }
+ }
delta--;
}
return value;
}
private timerUpdated(value: number, prevValue: number) {
- const { ocrA, ocrB, countingUp } = this;
- if (
- (countingUp && prevValue < ocrA && value >= ocrA) ||
- (countingUp && prevValue > value && ocrA >= value) ||
- (!countingUp && prevValue > ocrA && value <= ocrA)
- ) {
+ const { ocrA, ocrB } = this;
+ const overflow = prevValue > value;
+ if ((prevValue < ocrA || overflow) && value >= ocrA) {
this.cpu.setInterruptFlag(this.OCFA);
if (this.compA) {
this.updateCompPin(this.compA, 'A');
}
}
- if (
- (countingUp && prevValue < ocrB && value >= ocrB) ||
- (countingUp && prevValue > value && ocrB >= value) ||
- (!countingUp && prevValue > ocrB && value <= ocrB)
- ) {
+ if ((prevValue < ocrB || overflow) && value >= ocrB) {
this.cpu.setInterruptFlag(this.OCFB);
if (this.compB) {
this.updateCompPin(this.compB, 'B');
@@ -526,20 +569,31 @@ export class AVRTimer {
}
}
- private updateCompPin(compValue: CompBitsValue, pinName: 'A' | 'B') {
+ private updateCompPin(compValue: CompBitsValue, pinName: 'A' | 'B', bottom = false) {
let newValue: PinOverrideMode = PinOverrideMode.None;
- const inverted = compValue === 3;
- const isSet = this.countingUp === inverted;
+ const invertingMode = compValue === 3;
+ const isSet = this.countingUp === invertingMode;
switch (this.timerMode) {
- case TimerMode.Normal:
- case TimerMode.CTC:
- case TimerMode.FastPWM:
+ case Normal:
+ case CTC:
newValue = compToOverride(compValue);
break;
- case TimerMode.PWMPhaseCorrect:
- case TimerMode.PWMPhaseFrequencyCorrect:
- newValue = isSet ? PinOverrideMode.Set : PinOverrideMode.Clear;
+ case FastPWM:
+ if (compValue === 1) {
+ newValue = bottom ? PinOverrideMode.None : PinOverrideMode.Toggle;
+ } else {
+ newValue = invertingMode !== bottom ? PinOverrideMode.Set : PinOverrideMode.Clear;
+ }
+ break;
+
+ case PWMPhaseCorrect:
+ case PWMPhaseFrequencyCorrect:
+ if (compValue === 1) {
+ newValue = PinOverrideMode.Toggle;
+ } else {
+ newValue = isSet ? PinOverrideMode.Set : PinOverrideMode.Clear;
+ }
break;
}
diff --git a/src/utils/assembler.spec.ts b/src/utils/assembler.spec.ts
index f0daf64..836f388 100644
--- a/src/utils/assembler.spec.ts
+++ b/src/utils/assembler.spec.ts
@@ -14,6 +14,7 @@ describe('AVR assembler', () => {
bytes: bytes('0b0d'),
errors: [],
lines: [{ byteOffset: 0, bytes: '0d0b', line: 1, text: 'ADD r16, r11' }],
+ labels: {},
});
});
@@ -36,6 +37,7 @@ describe('AVR assembler', () => {
bytes: new Uint8Array(0),
errors: [],
lines: [],
+ labels: {},
});
});
@@ -44,6 +46,7 @@ describe('AVR assembler', () => {
bytes: new Uint8Array(0),
errors: ['Line 0: Rd out of range: 16<>31'],
lines: [],
+ labels: {},
});
});
diff --git a/src/utils/assembler.ts b/src/utils/assembler.ts
index 4823937..278e944 100644
--- a/src/utils/assembler.ts
+++ b/src/utils/assembler.ts
@@ -29,7 +29,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
-interface LabelTable {
+export interface LabelTable {
[key: string]: number;
}
@@ -938,7 +938,7 @@ function passTwo(lineTable: LineTablePass1[], labels: LabelTable) {
}
}
- return { errors: errorTable, bytes: resultTable, lines: lineTable as LineTable[] };
+ return { errors: errorTable, bytes: resultTable, lines: lineTable as LineTable[], labels };
}
/**
@@ -951,6 +951,7 @@ export function assemble(input: string) {
bytes: new Uint8Array(0),
errors: mid.errors,
lines: [],
+ labels: {},
};
}
return passTwo(mid.lines, mid.labels);
diff --git a/src/utils/test-utils.ts b/src/utils/test-utils.ts
index 4b2efff..e620748 100644
--- a/src/utils/test-utils.ts
+++ b/src/utils/test-utils.ts
@@ -5,38 +5,54 @@ import { avrInstruction } from '../cpu/instruction';
const BREAK_OPCODE = 0x9598;
export function asmProgram(source: string) {
- const { bytes, errors, lines } = assemble(source);
+ const { bytes, errors, lines, labels } = assemble(source);
if (errors.length) {
throw new Error('Assembly failed: ' + errors);
}
- return { program: new Uint16Array(bytes.buffer), lines, instructionCount: lines.length };
+ return { program: new Uint16Array(bytes.buffer), lines, instructionCount: lines.length, labels };
}
+const defaultOnBreak = () => {
+ throw new Error('BREAK instruction encountered');
+};
+
export class TestProgramRunner {
- constructor(private readonly cpu: CPU, private readonly onBreak?: (cpu: CPU) => void) {}
+ constructor(
+ private readonly cpu: CPU,
+ private readonly onBreak: (cpu: CPU) => void = defaultOnBreak
+ ) {}
runInstructions(count: number) {
const { cpu, onBreak } = this;
for (let i = 0; i < count; i++) {
if (cpu.progMem[cpu.pc] === BREAK_OPCODE) {
onBreak?.(cpu);
- throw new Error('BREAK instruction encountered');
}
avrInstruction(cpu);
cpu.tick();
}
}
- runToBreak(maxIterations = 5000) {
+ runUntil(predicate: (cpu: CPU) => boolean, maxIterations = 5000) {
const { cpu, onBreak } = this;
for (let i = 0; i < maxIterations; i++) {
if (cpu.progMem[cpu.pc] === BREAK_OPCODE) {
onBreak?.(cpu);
+ }
+ if (predicate(cpu)) {
return;
}
avrInstruction(cpu);
cpu.tick();
}
- throw new Error('Program ran for too long without a BREAK instruction');
+ throw new Error('Test program ran for too long, check your predicate');
+ }
+
+ runToBreak() {
+ this.runUntil((cpu) => cpu.progMem[cpu.pc] === BREAK_OPCODE);
+ }
+
+ runToAddress(byteAddr: number) {
+ this.runUntil((cpu) => cpu.pc * 2 === byteAddr);
}
}
If you find this content useful please consider a small donation via bitcoin: bitcoin:1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2?amount=0.001&message=Donation Scraping git contents via cgit is very resource intensive. The continued hosting of these git repositories is entirely financed by your bitcoin contributions. For donations of a least 1 mBTC you include your IP address in the message of your bitcoin donation. This will remove this message in the future for requests from your IP, which will significantly reduce token usage. In any case, any contributions to bitcoin address 1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2 are very welcome. Thanks in advance for you contribution to a self-sustaining ecosystem.