From f373d547bbc2431e9e66fa352b494cf70d732d0e Mon Sep 17 00:00:00 2001 From: exuanbo Date: Tue, 1 Oct 2024 20:44:24 +0100 Subject: [PATCH] feat(core): add basic bus, memory, and cpu impl --- src/core/bus/bus.ts | 52 +++++++++++++++++++++++++++++++++ src/core/cpu/cpu.ts | 43 ++++++++++++++++++++++++++++ src/core/memory/memory.ts | 60 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 src/core/bus/bus.ts create mode 100644 src/core/cpu/cpu.ts create mode 100644 src/core/memory/memory.ts diff --git a/src/core/bus/bus.ts b/src/core/bus/bus.ts new file mode 100644 index 00000000..66876e19 --- /dev/null +++ b/src/core/bus/bus.ts @@ -0,0 +1,52 @@ +import { asapScheduler, BehaviorSubject, filter, observeOn, shareReplay, skipWhile } from 'rxjs' + +export const enum Control { + CLOCK_IDLE = 0b0000, + MEMORY_READ = 0b1000, + IO_READ = 0b1001, + MEMORY_WRITE = 0b1010, + IO_WRITE = 0b1011, + INTERRUPT = 0b1100, +} + +export interface Signals { + data: number + address: number + control: Control +} + +const initialSignals: Signals = { + data: 0x00, + address: 0x00, + control: Control.CLOCK_IDLE, +} + +export class Bus { + private readonly source$ = new BehaviorSubject(initialSignals) + + private readonly shared$ = this.source$.pipe( + observeOn(asapScheduler), + shareReplay(1), + ) + + get signals$() { + return this.shared$ + } + + get idle$() { + return this.shared$.pipe( + filter((signals) => (signals.control === Control.CLOCK_IDLE)), + ) + } + + put(next: Partial) { + const nextSignals = { + ...this.source$.getValue(), + ...next, + } + this.source$.next(nextSignals) + return this.shared$.pipe( + skipWhile((signals) => (signals !== nextSignals)), + ) + } +} diff --git a/src/core/cpu/cpu.ts b/src/core/cpu/cpu.ts new file mode 100644 index 00000000..cdc64b46 --- /dev/null +++ b/src/core/cpu/cpu.ts @@ -0,0 +1,43 @@ +import { filter, firstValueFrom, map, skipUntil } from 'rxjs' + +import { type Bus, Control, type Signals } from '../bus/bus' + +export class Cpu { + constructor( + private readonly bus: Bus, + ) { + this.bus.signals$.pipe( + filter((signals) => (signals.control === Control.INTERRUPT)), + ).subscribe(this.handleInterrupt) + } + + async step() { + // TODO: implement step + // const opcode = await this.readMemory(...) + } + + private readMemory(address: number) { + const data$ = this.bus.put({ + address, + control: Control.MEMORY_READ, + }).pipe( + skipUntil(this.bus.idle$), + map((signals) => signals.data), + ) + return firstValueFrom(data$) + } + + private writeMemory(address: number, data: number) { + const complete$ = this.bus.put({ + data, + address, + control: Control.MEMORY_WRITE, + }) + return firstValueFrom(complete$) + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private handleInterrupt = (_signals: Signals) => { + // TODO: implement interrupt handling + } +} diff --git a/src/core/memory/memory.ts b/src/core/memory/memory.ts new file mode 100644 index 00000000..a5dc6f88 --- /dev/null +++ b/src/core/memory/memory.ts @@ -0,0 +1,60 @@ +import { filter, map, type Observable, share, tap } from 'rxjs' + +import { type Bus, Control, type Signals } from '../bus/bus' + +export class Memory { + // TODO: use shared constants + private readonly data = new Uint8Array(0x100) + + private readonly read$: Observable + private readonly write$: Observable + + constructor( + private readonly bus: Bus, + ) { + this.read$ = this.bus.signals$.pipe( + filter((signals) => signals.control === Control.MEMORY_READ), + tap(this.read), + share(), + ) + + this.write$ = this.bus.signals$.pipe( + filter((signals) => signals.control === Control.MEMORY_WRITE), + tap(this.write), + share(), + ) + + this.read$.subscribe() + this.write$.subscribe() + } + + private read = (signals: Signals) => { + this.bus.put({ + data: this.data[signals.address], + control: Control.CLOCK_IDLE, + }) + } + + private write = (signals: Signals) => { + this.data[signals.address] = signals.data + this.bus.put({ + control: Control.CLOCK_IDLE, + }) + } + + get data$() { + return this.write$.pipe(map(() => this.getData())) + } + + getData() { + return Array.from(this.data) + } + + load(data: Uint8Array, offset: number) { + this.data.set(data, offset) + } + + reset() { + this.data.fill(0) + } +}