Skip to content

Commit

Permalink
feat(core): add basic bus, memory, and cpu impl
Browse files Browse the repository at this point in the history
  • Loading branch information
exuanbo committed Oct 1, 2024
1 parent fc79f26 commit f373d54
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 0 deletions.
52 changes: 52 additions & 0 deletions src/core/bus/bus.ts
Original file line number Diff line number Diff line change
@@ -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<Signals>) {
const nextSignals = {
...this.source$.getValue(),
...next,
}
this.source$.next(nextSignals)
return this.shared$.pipe(
skipWhile((signals) => (signals !== nextSignals)),
)
}
}
43 changes: 43 additions & 0 deletions src/core/cpu/cpu.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
60 changes: 60 additions & 0 deletions src/core/memory/memory.ts
Original file line number Diff line number Diff line change
@@ -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<Signals>
private readonly write$: Observable<Signals>

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)
}
}

0 comments on commit f373d54

Please sign in to comment.