cardiograph-computer/2023-09-03.md

9.9 KiB
Raw Blame History

Dev notes — 2023-09-03

Todos

  • add yesterdays reading to dev notes
    • chip8 stuff
    • js stuff
  • reconsider revised ISA
      • a more-binary simulator
  • set up semi-standard js eslint
  • remember: meowbit

Misc. references

TODO: organize these

From earlier:

Startup: correction

It doesnt execute “JMP $FF.” It just sets the IP to $FF on reset, and you put a JMP there in ROM.

  • TODO: update docs elsewhere

Detailed CPU design/low-level simulator

Revising ISA

See notes from 2023-08-23

Instruction cycle

For now, treat each instruction as taking one cycle.

I started making notes on this, which I am leaving below… but this doesnt seem useful at this point so Im forgetting about it for now.

  • 1 cycle - fetch
    • copy IP to MAR
    • send Read command on memory control line
    • copy data from data bus to instruction register
    • calculate next IP (if no branch); IP += 2
  • 1 cycle - decode
    • (i think:) split instruction data into addressing mode, op, data
    • calculate branch target
  • 2 cycles - execute
    • 1 cycle - memory read
    • 1 cycle - compute
  • 1 cycle - write back

Sketch in JS

TODO:

References:

  • 6502 SYNC pin
    • "The SYNC output is provided to identify those cycles during which the microprocessor is fetching an OpCode. The SYNC line goes high during the clock cycle of an opcode fetch and stays high for the entire cycle. If the RDY line is pulled low during the clock cycle in which SYNC went high, the processor will stop in its current state and will remain in the state until the RDY line goes high. In this manner, the SYNC signal can be used to control RDY to cause single instruction execution."
    • -- http://archive.6502.org/datasheets/wdc_w65c02s_mar_2000.pdf
class CPU {
  constructor (memory, addressBus, dataBus, memoryReadSignal, memoryWriteSignal, cpuWait) {
    this.memory = memory;
    
    // TODO: move buses etc to this.pins.foo
    this.pins = {
      dataBus : dataBus,
      addressBus : addressBus,
      memoryReadSignal : 0,
      wait : cpuWait, // If 1, execution is paused (cf. 6502 RDY pin)
      // readNotWrite : 0 // 1 if want to read mem // TODO lower level than i need right now?
    }
    this.reset();
  }

  IP = 0;
  A = 0;
  flags = 0b00000000;
  instruction = 0x00;

  //What do I want for interrupts…?
  // interruptRequest = 0;

  // TODO: can it be a simple setup with fixed instruction cycle length pls?
  _instructionCycleStep = 0;

  tickClock () {
    if (this.wait) return false;

    switch (this._instructionCycleStep) {
      // Fetch
      case (0) {
        // this.instruction = this.memory.read(this.IP);
        this.readWriteControl = 1; // TODO
        break;
      } case (1) {
        // initiate read
        this.pins.addressBus = this.IP;
        this.pins.memoryReadSignal = 1;
      } case (2) {
        // complete read
        this.pins.memoryReadSignal = 0;
        this.instruction = this.dataBus;
      }
      // TODO: continue switch statement
      // ...
      /*
      // decode
      const op = this._decode(this.instruction);
      // execute
      op();
      */
    }
  }
  
  reset () {
    // this.IP = this.memory.read(0xFF);
    this.IP = 0xFF;
    this.A = 0;
    this.flags = 0b00000000;
    this.pins.dataBus = 0b00000000;
    this._instructionCycleStep = 0;
    this.debug.running = true;
  }

  _decode (instruction) {
    const instrs = {
      0x00: end,
      // TODO etc
    };
    return instrs[instruction];
  }

  debug = {
    // TODO include the rest of the stuff that I have here in the current version
    running: false,
  }
}
class Memory {
  // ROM format: { addr: nn, data: nn }
  constructor (sizeInBytes, ROM, addressBus, dataBus, readSignal, writeSignal) {
    // memory format: [ { data: nn, type: str } ]
    this.memory = new Array(sizeInBytes);
    objectForEach(ROM, (byte) => {
      this.memory[byte.addr] = { data: byte[data], type: ROM };
    })

    this.pins = {
      addressBus: addressBus,
      dataBus: dataBus,
      readSignal: readSignal,
      writeSignal: writeSignal,
    }
  }

  tickClock () {
    if (this.pins.readSignal) this._read();
    if (this.pins.writeSignal) this._write();
  }

  _read () {
    this.pins.dataBus = this.memory[this.pins.addressBus];
  }

  _write () {
    if (this.memory[this.pins.addressBus].type === ROM) throw new Error(Attempted write to ROM);
    this.memory[this.pins.addressBus].data = data;
  }
}

function objectForEach (object, fn) {
  return Object.keys(object).forEach(key => fn(key, object[key]));
}
/** DMA controller for screen, keypad... **/
class IO {
  // TODO…
  constructor (addressBus, dataBus, cpuWaitPin) {
    // ...
  }
}

cardiograph.js:

// TODO: these might all need to be functions?
let dataBus = 0;
let addressBus = 0;
let memoryReadSignal = 0;
let memoryWriteSignal = 0;
let cpuWait = 0;

let rom = // TODO read in…

let memory = new Memory(256, rom, addressBus, dataBus, memoryReadSignal, memoryWriteSignal);
let cpu = new CPU(memory, addressBus, dataBus, memoryReadSignal, memoryWriteSignal, cpuWait);

Sketch is missing:

  • DMA stuff (IO)
  • actual instruction decoding/execution