aboutsummaryrefslogtreecommitdiff
path: root/src/peripherals/eeprom.ts
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/peripherals/eeprom.ts147
1 files changed, 147 insertions, 0 deletions
diff --git a/src/peripherals/eeprom.ts b/src/peripherals/eeprom.ts
new file mode 100644
index 0000000..97ca178
--- /dev/null
+++ b/src/peripherals/eeprom.ts
@@ -0,0 +1,147 @@
+import { CPU } from '../cpu/cpu';
+import { avrInterrupt } from '../cpu/interrupt';
+import { u8, u16, u32 } from '../types';
+
+export interface EEPROMBackend {
+ readMemory(addr: u16): u8;
+ writeMemory(addr: u16, value: u8): void;
+ eraseMemory(addr: u16): void;
+}
+
+export class EEPROMMemoryBackend implements EEPROMBackend {
+ readonly memory: Uint8Array;
+
+ constructor(size: u16) {
+ this.memory = new Uint8Array(size);
+ this.memory.fill(0xff);
+ }
+
+ readMemory(addr: u16) {
+ return this.memory[addr];
+ }
+
+ writeMemory(addr: u16, value: u8) {
+ this.memory[addr] &= value;
+ }
+
+ eraseMemory(addr: u16) {
+ this.memory[addr] = 0xff;
+ }
+}
+
+export interface AVREEPROMConfig {
+ eepromReadyInterrupt: u8;
+
+ EECR: u8;
+ EEDR: u8;
+ EEARL: u8;
+ EEARH: u8;
+
+ /** The amount of clock cycles erase takes */
+ eraseCycles: u32;
+ /** The amount of clock cycles a write takes */
+ writeCycles: u32;
+}
+
+export const eepromConfig: AVREEPROMConfig = {
+ eepromReadyInterrupt: 0x2c,
+ EECR: 0x3f,
+ EEDR: 0x40,
+ EEARL: 0x41,
+ EEARH: 0x42,
+ eraseCycles: 28800, // 1.8ms at 16MHz
+ writeCycles: 28800, // 1.8ms at 16MHz
+};
+
+const EERE = 1 << 0;
+const EEPE = 1 << 1;
+const EEMPE = 1 << 2;
+const EERIE = 1 << 3;
+const EEPM0 = 1 << 4;
+const EEPM1 = 1 << 5;
+
+export class AVREEPROM {
+ /**
+ * Used to keep track on the last write to EEMPE. From the datasheet:
+ * The EEMPE bit determines whether setting EEPE to one causes the EEPROM to be written.
+ * When EEMPE is set, setting EEPE within four clock cycles will write data to the EEPROM
+ * at the selected address If EEMPE is zero, setting EEPE will have no effect.
+ */
+ private writeEnabledCycles = 0;
+
+ private writeCompleteCycles = 0;
+
+ constructor(
+ private cpu: CPU,
+ private backend: EEPROMBackend,
+ private config: AVREEPROMConfig = eepromConfig
+ ) {
+ this.cpu.writeHooks[this.config.EECR] = (eecr) => {
+ const { EEARH, EEARL, EECR, EEDR } = this.config;
+
+ const addr = (this.cpu.data[EEARH] << 8) | this.cpu.data[EEARL];
+
+ if (eecr & EEMPE) {
+ this.writeEnabledCycles = this.cpu.cycles + 4;
+ }
+
+ // Read
+ if (eecr & EERE) {
+ this.cpu.data[EEDR] = this.backend.readMemory(addr);
+ // When the EEPROM is read, the CPU is halted for four cycles before the
+ // next instruction is executed.
+ this.cpu.cycles += 4;
+ return true;
+ }
+
+ // Write
+ if (eecr & EEPE) {
+ // If EEMPE is zero, setting EEPE will have no effect.
+ if (this.cpu.cycles >= this.writeEnabledCycles) {
+ return true;
+ }
+ // Check for write-in-progress
+ if (this.writeCompleteCycles) {
+ return true;
+ }
+
+ const eedr = this.cpu.data[EEDR];
+
+ this.writeCompleteCycles = this.cpu.cycles;
+
+ // Erase
+ if (!(eecr & EEPM1)) {
+ this.backend.eraseMemory(addr);
+ this.writeCompleteCycles += this.config.eraseCycles;
+ }
+ // Write
+ if (!(eecr & EEPM0)) {
+ this.backend.writeMemory(addr, eedr);
+ this.writeCompleteCycles += this.config.writeCycles;
+ }
+
+ this.cpu.data[EECR] |= EEPE;
+ // When EEPE has been set, the CPU is halted for two cycles before the
+ // next instruction is executed.
+ this.cpu.cycles += 2;
+ return true;
+ }
+
+ return false;
+ };
+ }
+
+ tick() {
+ const { EECR, eepromReadyInterrupt } = this.config;
+
+ if (this.writeEnabledCycles && this.cpu.cycles > this.writeEnabledCycles) {
+ this.cpu.data[EECR] &= ~EEMPE;
+ }
+ if (this.writeCompleteCycles && this.cpu.cycles > this.writeCompleteCycles) {
+ this.cpu.data[EECR] &= ~EEPE;
+ if (this.cpu.interruptsEnabled && this.cpu.data[EECR] & EERIE) {
+ avrInterrupt(this.cpu, eepromReadyInterrupt);
+ }
+ }
+ }
+}