aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUri Shaked2020-04-28 23:42:20 +0300
committerUri Shaked2020-04-28 23:42:20 +0300
commitf16d9f9f48fb5e74ab10fe79a8206223342d5010 (patch)
tree0ae771f977ce51e1d34ab7f79cd6a31d918bfd12
parentchore: release 0.8.2 (diff)
downloadavr8js-f16d9f9f48fb5e74ab10fe79a8206223342d5010.tar.gz
avr8js-f16d9f9f48fb5e74ab10fe79a8206223342d5010.tar.bz2
avr8js-f16d9f9f48fb5e74ab10fe79a8206223342d5010.zip
fix(timer): incorrect high counter byte behavior
According to the datasheet, the value of the high byte of the counter for 16-bit timers (such as timer 1) is only updated when the low byte is being read/written. close #37
-rw-r--r--src/cpu/cpu.ts8
-rw-r--r--src/peripherals/timer.spec.ts93
-rw-r--r--src/peripherals/timer.ts42
3 files changed, 97 insertions, 46 deletions
diff --git a/src/cpu/cpu.ts b/src/cpu/cpu.ts
index 0c60c9e..5663a1c 100644
--- a/src/cpu/cpu.ts
+++ b/src/cpu/cpu.ts
@@ -40,11 +40,16 @@ export interface CPUMemoryHooks {
[key: number]: CPUMemoryHook;
}
+export type CPUMemoryReadHook = (addr: u16) => u8;
+export interface CPUMemoryReadHooks {
+ [key: number]: CPUMemoryReadHook;
+}
export class CPU implements ICPU {
readonly data: Uint8Array = new Uint8Array(this.sramBytes + registerSpace);
readonly data16 = new Uint16Array(this.data.buffer);
readonly dataView = new DataView(this.data.buffer);
readonly progBytes = new Uint8Array(this.progMem.buffer);
+ readonly readHooks: CPUMemoryReadHooks = [];
readonly writeHooks: CPUMemoryHooks = [];
readonly pc22Bits = this.progBytes.length > 0x20000;
@@ -61,6 +66,9 @@ export class CPU implements ICPU {
}
readData(addr: number) {
+ if (addr >= 32 && this.readHooks[addr]) {
+ return this.readHooks[addr](addr);
+ }
return this.data[addr];
}
diff --git a/src/peripherals/timer.spec.ts b/src/peripherals/timer.spec.ts
index 5a62246..7d280ed 100644
--- a/src/peripherals/timer.spec.ts
+++ b/src/peripherals/timer.spec.ts
@@ -23,7 +23,8 @@ describe('timer', () => {
cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1
cpu.cycles = 1;
timer.tick();
- expect(cpu.data[0x46]).toEqual(1); // TCNT should be 1
+ const tcnt = cpu.readData(0x46);
+ expect(tcnt).toEqual(1); // TCNT should be 1
});
it('should update timer every 64 ticks when prescaler is 3', () => {
@@ -31,7 +32,8 @@ describe('timer', () => {
cpu.data[0x45] = 0x3; // TCCR0B.CS <- 3
cpu.cycles = 64;
timer.tick();
- expect(cpu.data[0x46]).toEqual(1); // TCNT should be 1
+ const tcnt = cpu.readData(0x46);
+ expect(tcnt).toEqual(1); // TCNT should be 1
});
it('should not update timer if it has been disabled', () => {
@@ -39,52 +41,61 @@ describe('timer', () => {
cpu.data[0x45] = 0; // TCCR0B.CS <- 0
cpu.cycles = 100000;
timer.tick();
- expect(cpu.data[0x46]).toEqual(0); // TCNT should stay 0
+ const tcnt = cpu.readData(0x46);
+ expect(tcnt).toEqual(0); // TCNT should stay 0
});
it('should set TOV if timer overflows', () => {
const timer = new AVRTimer(cpu, timer0Config);
- cpu.data[0x46] = 0xff; // TCNT0 <- 0xff
+ cpu.writeData(0x46, 0xff); // TCNT0 <- 0xff
+ timer.tick();
cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1
cpu.cycles = 1;
timer.tick();
- expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0
+ const tcnt = cpu.readData(0x46);
+ expect(tcnt).toEqual(0); // TCNT should be 0
expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR
});
it('should set TOV if timer overflows in PWM Phase Correct mode', () => {
const timer = new AVRTimer(cpu, timer0Config);
- cpu.data[0x46] = 0xff; // TCNT0 <- 0xff
+ cpu.writeData(0x46, 0xff); // TCNT0 <- 0xff
+ timer.tick();
cpu.writeData(0x47, 0x7f); // OCRA <- 0x7f
cpu.writeData(0x44, 0x1); // WGM0 <- 1 (PWM, Phase Correct)
cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1
cpu.cycles = 1;
timer.tick();
- expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0
+ const tcnt = cpu.readData(0x46);
+ expect(tcnt).toEqual(0); // TCNT should be 0
expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR
});
it('should set TOV if timer overflows in FAST PWM mode', () => {
const timer = new AVRTimer(cpu, timer0Config);
- cpu.data[0x46] = 0xff; // TCNT0 <- 0xff
+ cpu.writeData(0x46, 0xff); // TCNT0 <- 0xff
+ timer.tick();
cpu.writeData(0x47, 0x7f); // OCRA <- 0x7f
cpu.writeData(0x44, 0x3); // WGM0 <- 3 (FAST PWM)
cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1
cpu.cycles = 1;
timer.tick();
- expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0
+ const tcnt = cpu.readData(0x46);
+ expect(tcnt).toEqual(0); // TCNT should be 0
expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR
});
it('should generate an overflow interrupt if timer overflows and interrupts enabled', () => {
const timer = new AVRTimer(cpu, timer0Config);
- cpu.data[0x46] = 0xff; // TCNT0 <- 0xff
+ cpu.writeData(0x46, 0xff); // TCNT0 <- 0xff
+ timer.tick();
cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1
cpu.data[0x6e] = 0x1; // TIMSK0: TOIE0
cpu.data[95] = 0x80; // SREG: I-------
cpu.cycles = 1;
timer.tick();
- expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0
+ const tcnt = cpu.readData(0x46);
+ expect(tcnt).toEqual(0); // TCNT should be 0
expect(cpu.data[0x35]).toEqual(0); // TOV bit in TIFR should be clear
expect(cpu.pc).toEqual(0x20);
expect(cpu.cycles).toEqual(3);
@@ -92,7 +103,8 @@ describe('timer', () => {
it('should not generate an overflow interrupt when global interrupts disabled', () => {
const timer = new AVRTimer(cpu, timer0Config);
- cpu.data[0x46] = 0xff; // TCNT0 <- 0xff
+ cpu.writeData(0x46, 0xff); // TCNT0 <- 0xff
+ timer.tick();
cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1
cpu.data[0x6e] = 0x1; // TIMSK0: TOIE0
cpu.data[95] = 0x0; // SREG: --------
@@ -105,7 +117,8 @@ describe('timer', () => {
it('should not generate an overflow interrupt when TOIE0 is clear', () => {
const timer = new AVRTimer(cpu, timer0Config);
- cpu.data[0x46] = 0xff; // TCNT0 <- 0xff
+ cpu.writeData(0x46, 0xff); // TCNT0 <- 0xff
+ timer.tick();
cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1
cpu.data[0x6e] = 0; // TIMSK0: clear
cpu.data[95] = 0x80; // SREG: I-------
@@ -139,7 +152,8 @@ describe('timer', () => {
cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1
cpu.cycles = 1;
timer.tick();
- expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0
+ const tcnt = cpu.readData(0x46);
+ expect(tcnt).toEqual(0); // TCNT should be 0
expect(cpu.pc).toEqual(0);
expect(cpu.cycles).toEqual(1);
});
@@ -168,7 +182,8 @@ describe('timer', () => {
cpu.writeData(95, 0x80); // SREG: I-------
cpu.cycles = 1;
timer.tick();
- expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21
+ const tcnt = cpu.readData(0x46);
+ expect(tcnt).toEqual(0x21); // TCNT should be 0x21
expect(cpu.data[0x35]).toEqual(0); // OCFA bit in TIFR should be clear
expect(cpu.pc).toEqual(0x1c);
expect(cpu.cycles).toEqual(3);
@@ -184,7 +199,8 @@ describe('timer', () => {
cpu.writeData(95, 0x80); // SREG: I-------
cpu.cycles = 1;
timer.tick();
- expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21
+ const tcnt = cpu.readData(0x46);
+ expect(tcnt).toEqual(0x21); // TCNT should be 0x21
expect(cpu.pc).toEqual(0);
expect(cpu.cycles).toEqual(1);
});
@@ -199,7 +215,8 @@ describe('timer', () => {
cpu.writeData(95, 0x80); // SREG: I-------
cpu.cycles = 1;
timer.tick();
- expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21
+ const tcnt = cpu.readData(0x46);
+ expect(tcnt).toEqual(0x21); // TCNT should be 0x21
expect(cpu.data[0x35]).toEqual(0); // OCFB bit in TIFR should be clear
expect(cpu.pc).toEqual(0x1e);
expect(cpu.cycles).toEqual(3);
@@ -231,11 +248,11 @@ describe('timer', () => {
cpu.cycles = 511;
timer.tick();
- expect(cpu.data[0xb2]).toEqual(1); // TCNT2 should be 2
+ expect(cpu.readData(0xb2)).toEqual(1); // TCNT2 should be 2
cpu.cycles = 512;
timer.tick();
- expect(cpu.data[0xb2]).toEqual(2); // TCNT2 should be 2
+ expect(cpu.readData(0xb2)).toEqual(2); // TCNT2 should be 2
});
describe('16 bit timers', () => {
@@ -244,18 +261,21 @@ describe('timer', () => {
cpu.writeData(0x85, 0x22); // TCNT1 <- 0x2233
cpu.writeData(0x84, 0x33); // ...
timer.tick();
- expect(timer.TCNT).toEqual(0x2233);
+ const timerLow = cpu.readData(0x84);
+ const timerHigh = cpu.readData(0x85);
+ expect((timerHigh << 8) | timerLow).toEqual(0x2233);
cpu.writeData(0x80, 0x0); // WGM1 <- 0 (Normal)
cpu.writeData(0x81, 0x1); // TCCR1B.CS <- 1
cpu.cycles = 1;
timer.tick();
+ cpu.readData(0x84);
expect(cpu.dataView.getUint16(0x84, true)).toEqual(0x2234); // TCNT1 should increment
});
it('should set OCF0A flag when timer equals OCRA (16 bit mode)', () => {
const timer = new AVRTimer(cpu, timer1Config);
- cpu.writeData(0x84, 0xee); // TCNT1 <- 0x10ee
- cpu.writeData(0x85, 0x10); // ...
+ cpu.writeData(0x85, 0x10); // TCNT1 <- 0x10ee
+ cpu.writeData(0x84, 0xee); // ...
timer.tick();
cpu.writeData(0x88, 0xef); // OCR1A <- 0x10ef
cpu.writeData(0x89, 0x10); // ...
@@ -280,7 +300,8 @@ describe('timer', () => {
cpu.data[95] = 0x80; // SREG: I-------
cpu.cycles = 1;
timer.tick();
- expect(cpu.dataView.getUint16(0x84, true)).toEqual(0); // TCNT should be 0
+ cpu.readData(0x84); // Refresh TCNT1
+ expect(cpu.dataView.getUint16(0x84, true)).toEqual(0); // TCNT1 should be 0
expect(cpu.data[0x36]).toEqual(0); // TOV bit in TIFR should be clear
expect(cpu.pc).toEqual(0x1a);
expect(cpu.cycles).toEqual(3);
@@ -296,9 +317,35 @@ describe('timer', () => {
cpu.writeData(0x81, 0x19); // TCCR1B <- WGM13 | WGM12 | CS10
cpu.cycles = 2; // 2 cycles should increment timer twice, beyond ICR1
timer.tick();
+ cpu.readData(0x84); // Refresh TCNT1
expect(cpu.dataView.getUint16(0x84, true)).toEqual(0); // TCNT should be 0
expect(cpu.data[0x36]).toEqual(0); // TOV bit in TIFR should be clear
expect(cpu.cycles).toEqual(2);
});
+
+ it('should not update the high byte of TCNT if written after the low byte (issue #37)', () => {
+ const timer = new AVRTimer(cpu, timer1Config);
+ cpu.writeData(0x84, 0x22); // TCNT1L <- 0x22
+ cpu.writeData(0x85, 0x55); // TCNT1H <- 0x55
+ timer.tick();
+ const timerLow = cpu.readData(0x84);
+ const timerHigh = cpu.readData(0x85);
+ expect((timerHigh << 8) | timerLow).toEqual(0x22);
+ });
+
+ it('reading from TCNT1H before TCNT1L should return old value (issue #37)', () => {
+ const timer = new AVRTimer(cpu, timer1Config);
+ cpu.writeData(0x85, 0xff); // TCNT1H <- 0xff
+ cpu.writeData(0x84, 0xff); // TCNT1L <- 0xff
+ cpu.writeData(0x81, 0x9); // TCCR1B <- CS10
+ timer.tick();
+ cpu.cycles = 1;
+ timer.tick();
+ // We read the high byte before the low byte, so the high byte should still have
+ // the previous value:
+ const timerHigh = cpu.readData(0x85);
+ const timerLow = cpu.readData(0x84);
+ expect((timerHigh << 8) | timerLow).toEqual(0xff00);
+ });
});
});
diff --git a/src/peripherals/timer.ts b/src/peripherals/timer.ts
index ebd7b58..04c09aa 100644
--- a/src/peripherals/timer.ts
+++ b/src/peripherals/timer.ts
@@ -191,16 +191,24 @@ export class AVRTimer {
private ocrB: u16 = 0;
private timerMode: TimerMode;
private topValue: TimerTopValue;
+ private tcnt: u16 = 0;
private tcntUpdated = false;
constructor(private cpu: CPU, private config: AVRTimerConfig) {
this.updateWGMConfig();
- this.registerHook(config.TCNT, (value: u16) => {
- this.TCNT = value;
+ this.cpu.readHooks[config.TCNT] = (addr: u8) => {
+ if (this.config.bits === 16) {
+ this.cpu.data[addr + 1] = this.tcnt >> 8;
+ }
+ return (this.cpu.data[addr] = this.tcnt & 0xff);
+ };
+
+ this.cpu.writeHooks[config.TCNT] = (value: u8) => {
+ const highByte = this.config.bits === 16 ? this.cpu.data[config.TCNT + 1] : 0;
+ this.tcnt = (highByte << 8) | value;
this.tcntUpdated = true;
- this.timerUpdated(value);
- return true;
- });
+ this.timerUpdated();
+ };
this.registerHook(config.OCRA, (value: u16) => {
// TODO implement buffering when timer running in PWM mode
this.ocrA = value;
@@ -234,19 +242,6 @@ export class AVRTimer {
this.cpu.data[this.config.TIFR] = value;
}
- get TCNT() {
- return this.config.bits === 16
- ? this.cpu.dataView.getUint16(this.config.TCNT, true)
- : this.cpu.data[this.config.TCNT];
- }
-
- set TCNT(value: u16) {
- this.cpu.data[this.config.TCNT] = value & 0xff;
- if (this.config.bits === 16) {
- this.cpu.data[this.config.TCNT + 1] = (value >> 8) & 0xff;
- }
- }
-
get TCCRA() {
return this.cpu.data[this.config.TCCRA];
}
@@ -306,12 +301,12 @@ export class AVRTimer {
if (divider && delta >= divider) {
const counterDelta = Math.floor(delta / divider);
this.lastCycle += counterDelta * divider;
- const val = this.TCNT;
+ const val = this.tcnt;
const newVal = (val + counterDelta) % (this.TOP + 1);
// A CPU write overrides (has priority over) all counter clear or count operations.
if (!this.tcntUpdated) {
- this.TCNT = newVal;
- this.timerUpdated(newVal);
+ this.tcnt = newVal;
+ this.timerUpdated();
}
const { timerMode } = this;
if (
@@ -341,12 +336,13 @@ export class AVRTimer {
}
}
- private timerUpdated(value: u8) {
+ private timerUpdated() {
+ const value = this.tcnt;
if (this.ocrA && value === this.ocrA) {
this.TIFR |= OCFA;
if (this.timerMode === TimerMode.CTC) {
// Clear Timer on Compare Match (CTC) Mode
- this.TCNT = 0;
+ this.tcnt = 0;
this.TIFR |= TOV;
}
}
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.