diff options
| author | Uri Shaked | 2020-11-25 13:46:54 +0200 |
|---|---|---|
| committer | Uri Shaked | 2020-11-25 13:49:16 +0200 |
| commit | 43fe0a5b858060f569087acaee002be064b6c70b (patch) | |
| tree | eed366e3e544f92b99cfbc39ececad5e443bb7ce /src/peripherals | |
| parent | 0.12.0 (diff) | |
| download | avr8js-43fe0a5b858060f569087acaee002be064b6c70b.tar.gz avr8js-43fe0a5b858060f569087acaee002be064b6c70b.tar.bz2 avr8js-43fe0a5b858060f569087acaee002be064b6c70b.zip | |
feat(clock): Clock Prescale (CLKPR) support #68
close #68
Diffstat (limited to '')
| -rw-r--r-- | src/peripherals/clock.spec.ts | 92 | ||||
| -rw-r--r-- | src/peripherals/clock.ts | 92 |
2 files changed, 184 insertions, 0 deletions
diff --git a/src/peripherals/clock.spec.ts b/src/peripherals/clock.spec.ts new file mode 100644 index 0000000..69fc832 --- /dev/null +++ b/src/peripherals/clock.spec.ts @@ -0,0 +1,92 @@ +import { CPU } from '../cpu/cpu'; +import { AVRClock, clockConfig } from './clock'; + +// Clock Registers +const CLKPC = 0x61; + +// Register bit names +const CLKPCE = 128; + +describe('Clock', () => { + it('should set the prescaler when double-writing CLKPC', () => { + const cpu = new CPU(new Uint16Array(0x1000)); + const clock = new AVRClock(cpu, 16e6, clockConfig); + cpu.writeData(CLKPC, CLKPCE); + cpu.writeData(CLKPC, 3); // Divide by 8 (2^3) + expect(clock.frequency).toEqual(2e6); // 2MHz + expect(cpu.readData(CLKPC)).toEqual(3); + }); + + it('should not update the prescaler if CLKPCE was not set CLKPC', () => { + const cpu = new CPU(new Uint16Array(0x1000)); + const clock = new AVRClock(cpu, 16e6, clockConfig); + cpu.writeData(CLKPC, 3); // Divide by 8 (2^3) + expect(clock.frequency).toEqual(16e6); // still 16MHz + expect(cpu.readData(CLKPC)).toEqual(0); + }); + + it('should not update the prescaler if more than 4 cycles passed since setting CLKPCE', () => { + const cpu = new CPU(new Uint16Array(0x1000)); + const clock = new AVRClock(cpu, 16e6, clockConfig); + cpu.writeData(CLKPC, CLKPCE); + cpu.cycles += 6; + cpu.writeData(CLKPC, 3); // Divide by 8 (2^3) + expect(clock.frequency).toEqual(16e6); // still 16MHz + expect(cpu.readData(CLKPC)).toEqual(0); + }); + + describe('prescaler property', () => { + it('should return the current prescaler value', () => { + const cpu = new CPU(new Uint16Array(0x1000)); + const clock = new AVRClock(cpu, 16e6, clockConfig); + cpu.writeData(CLKPC, CLKPCE); + cpu.writeData(CLKPC, 5); // Divide by 32 (2^5) + cpu.cycles = 16e6; + expect(clock.prescaler).toEqual(32); + }); + }); + + describe('time properties', () => { + it('should return current number of microseconds, derived from base freq + prescaler', () => { + const cpu = new CPU(new Uint16Array(0x1000)); + const clock = new AVRClock(cpu, 16e6, clockConfig); + cpu.writeData(CLKPC, CLKPCE); + cpu.writeData(CLKPC, 2); // Divide by 4 (2^2) + cpu.cycles = 16e6; + expect(clock.timeMillis).toEqual(4000); // 4 seconds + }); + + it('should return current number of milliseconds, derived from base freq + prescaler', () => { + const cpu = new CPU(new Uint16Array(0x1000)); + const clock = new AVRClock(cpu, 16e6, clockConfig); + cpu.writeData(CLKPC, CLKPCE); + cpu.writeData(CLKPC, 2); // Divide by 4 (2^2) + cpu.cycles = 16e6; + expect(clock.timeMicros).toEqual(4e6); // 4 seconds + }); + + it('should return current number of nanoseconds, derived from base freq + prescaler', () => { + const cpu = new CPU(new Uint16Array(0x1000)); + const clock = new AVRClock(cpu, 16e6, clockConfig); + cpu.writeData(CLKPC, CLKPCE); + cpu.writeData(CLKPC, 2); // Divide by 4 (2^2) + cpu.cycles = 16e6; + expect(clock.timeNanos).toEqual(4e9); // 4 seconds + }); + + it('should correctly calculate time when changing the prescale value at runtime', () => { + const cpu = new CPU(new Uint16Array(0x1000)); + const clock = new AVRClock(cpu, 16e6, clockConfig); + cpu.cycles = 16e6; // run 1 second at 16MHz + cpu.writeData(CLKPC, CLKPCE); + cpu.writeData(CLKPC, 2); // Divide by 4 (2^2) + cpu.cycles += 2 * 4e6; // run 2 more seconds at 4MhZ + expect(clock.timeMillis).toEqual(3000); // 3 seconds in total + + cpu.writeData(CLKPC, CLKPCE); + cpu.writeData(CLKPC, 1); // Divide by 2 (2^1) + cpu.cycles += 0.5 * 8e6; // run 0.5 more seconds at 8MhZ + expect(clock.timeMillis).toEqual(3500); // 3.5 seconds in total + }); + }); +}); diff --git a/src/peripherals/clock.ts b/src/peripherals/clock.ts new file mode 100644 index 0000000..1dbf4ba --- /dev/null +++ b/src/peripherals/clock.ts @@ -0,0 +1,92 @@ +/** + * AVR8 Clock + * Part of AVR8js + * Reference: http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf + * + * Copyright (C) 2020, Uri Shaked + */ + +import { CPU } from '../cpu/cpu'; +import { u32, u8 } from '../types'; + +const CLKPCE = 128; + +export interface AVRClockConfig { + CLKPR: u8; +} + +export const clockConfig: AVRClockConfig = { + CLKPR: 0x61, +}; + +const prescalers = [ + 1, + 2, + 4, + 8, + 16, + 32, + 64, + 128, + 256, + + // The following values are "reserved" according to the datasheet, so we measured + // with a scope to figure them out (on ATmega328p) + 2, + 4, + 8, + 16, + 32, + 64, + 128, +]; + +export class AVRClock { + private clockEnabledCycles = 0; + private prescalerValue = 1; + cyclesDelta = 0; + + constructor( + private cpu: CPU, + private baseFreqHz: u32, + private config: AVRClockConfig = clockConfig + ) { + this.cpu.writeHooks[this.config.CLKPR] = (clkpr) => { + if ((!this.clockEnabledCycles || this.clockEnabledCycles < cpu.cycles) && clkpr === CLKPCE) { + this.clockEnabledCycles = this.cpu.cycles + 4; + } else if (this.clockEnabledCycles && this.clockEnabledCycles >= cpu.cycles) { + this.clockEnabledCycles = 0; + const index = clkpr & 0xf; + const oldPrescaler = this.prescalerValue; + this.prescalerValue = prescalers[index]; + this.cpu.data[this.config.CLKPR] = index; + if (oldPrescaler !== this.prescalerValue) { + this.cyclesDelta = + (cpu.cycles + this.cyclesDelta) * (oldPrescaler / this.prescalerValue) - cpu.cycles; + } + } + + return true; + }; + } + + get frequency() { + return this.baseFreqHz / this.prescalerValue; + } + + get prescaler() { + return this.prescalerValue; + } + + get timeNanos() { + return ((this.cpu.cycles + this.cyclesDelta) / this.frequency) * 1e9; + } + + get timeMicros() { + return ((this.cpu.cycles + this.cyclesDelta) / this.frequency) * 1e6; + } + + get timeMillis() { + return ((this.cpu.cycles + this.cyclesDelta) / this.frequency) * 1e3; + } +} |
