aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-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;
}
}