8-bit-computer/memory.ts

207 lines
7.4 KiB
TypeScript

// Memory RAM and ROM component for the LogicSimulator used on the 8-bit-computer
// Copyright (C) 2022 Sascha Nitsch
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
interface MemoryParam extends BaseComponentParam {
type: string;
size: number;
source: string;
content?: string;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class Memory extends BaseComponent {
private writeable: boolean;
private size: number;
private source: string;
private r: number[] = [];
private W = false;
private R = false;
private D = 0;
private endaddr: number;
private CLK = false;
private oldCLK = false;
private invEnable = true;
private memory: HTMLTextAreaElement | null = null;
private addr = 0;
constructor(simulator: Simulator, id: string, param: MemoryParam) {
super(simulator, id, param);
this.writeable = param.type === 'RAM';
this.size = param.size;
this.source = param.source;
if (param.content === null) {
for (let r = 0; r < this.size; ++r) {
this.r[r] = 0;
}
}
if (param.content === 'rising') {
for (let r = 0; r < this.size; ++r) {
this.r[r] = r & 255;
}
}
if (param.content === 'random') {
for (let r = 0; r < this.size; ++r) {
this.r[r] = Math.round(Math.random() * 255);
}
}
this.endaddr = 15;
let threshold = 65536;
// disable addr lines if needed
while (this.size < threshold) {
--this.endaddr;
threshold = threshold / 2;
}
for (let a = 0; a < 8; ++a) {
this.pins.set('D' + (7 - a), new TriState(this, 40, a * 10 - 75));
}
this.pins.set('R', new TriState(this, 40, 25));
if (this.writeable) {
this.pins.set('W', new TriState(this, 40, 35));
}
this.pins.set('CLK', new TriState(this, 40, 5));
this.pins.set('invEnable', new TriState(this, 40, 45));
for (let i = 0; i <= this.endaddr; ++i) {
this.pins.set('A' + (this.endaddr - i), new TriState(this, -40, i * 10 - 75));
}
}
setup(canvas: SVGElement) {
super.doSetup('memory', canvas);
if (this.element === null) {
return;
}
const offset = -(15 - this.endaddr) * 10;
for (let i = 0; i < 16; ++i) {
const e = this.element.getElementsByClassName('a' + i);
if (i <= this.endaddr) {
// move up
if (offset !== 0) {
e[0].setAttribute('transform', 'translate(0,' + offset + ')');
}
} else {
e[0].classList.add('hidden');
}
}
// resize box
const height = Math.max(170 + offset, 140);
this.element.getElementsByClassName('outer')[0].setAttribute('height', height.toString());
if (!this.writeable) {
this.element.getElementsByClassName('w')[0].classList.add('hidden');
}
this.memory = <HTMLTextAreaElement>document.getElementById(this.source);
const memorysave = document.getElementById(this.source + 'save');
if (memorysave) {
memorysave.addEventListener('mousedown', this.updateMem.bind(this));
}
this.updateText();
}
updateMem() {
if (this.memory === null) return;
const txt = this.memory.value.split('\n');
let addr = 0;
for (let i = 0; i < txt.length - 1; ++i) {
const cells = txt[i].split(' ');
for (let j = 0; j < 36; ++j) {
if (j % 9 === 0) continue;
this.r[addr] = parseInt(cells[j], 16);
++addr;
}
}
this.simulator.manualtick();
}
update(): boolean {
// is R set ? then send out to databus
if (this.R && this.invEnable === false) {
// which address is selected
const value = this.r[this.addr];
for (let i = 0; i < 8; ++i) {
this.getPin('D' + i).setBool((value & (1 << i)) > 0 ? true : false);
}
}
const rising = this.CLK && this.oldCLK === false;
this.oldCLK = this.CLK;
// is W set ? read from bus on raising clock edge
if (this.writeable && this.W && this.invEnable === false && rising) {
this.r[this.addr] = this.D;
// update text input
this.updateText();
}
return false;
}
hex(d: number, padding: number) {
let hex = d.toString(16).toUpperCase();
while (hex.length < padding) {
hex = '0' + hex;
}
return hex;
}
updateText() {
let txt = '';
for (let i = 0; i < this.size; ++i) {
if (i % 32 !== 0) {
txt += ' ';
if (i % 8 === 0) {
txt += '0x' + this.hex(i, 4) + ': ';
}
} else {
txt += '0x' + this.hex(i, 4) + ': ';
}
txt += (this.r[i] >> 4).toString(16) + (this.r[i] & 15).toString(16);
if (i % 32 === 31) {
txt += '\n';
}
}
if (this.memory) {
this.memory.value = txt;
}
}
io() {
this.addr = 0;
if (this.endaddr >= 15) this.addr += this.asint(this.binary(this.getPin('A15').getAndReset(), true), 32768);
if (this.endaddr >= 14) this.addr += this.asint(this.binary(this.getPin('A14').getAndReset(), true), 16384);
if (this.endaddr >= 13) this.addr += this.asint(this.binary(this.getPin('A13').getAndReset(), true), 8192);
if (this.endaddr >= 12) this.addr += this.asint(this.binary(this.getPin('A12').getAndReset(), true), 4096);
if (this.endaddr >= 11) this.addr += this.asint(this.binary(this.getPin('A11').getAndReset(), true), 2048);
if (this.endaddr >= 10) this.addr += this.asint(this.binary(this.getPin('A10').getAndReset(), true), 1024);
if (this.endaddr >= 9) this.addr += this.asint(this.binary(this.getPin('A9').getAndReset(), true), 512);
if (this.endaddr >= 8) this.addr += this.asint(this.binary(this.getPin('A8').getAndReset(), true), 256);
if (this.endaddr >= 7) this.addr += this.asint(this.binary(this.getPin('A7').getAndReset(), true), 128);
if (this.endaddr >= 6) this.addr += this.asint(this.binary(this.getPin('A6').getAndReset(), true), 64);
if (this.endaddr >= 5) this.addr += this.asint(this.binary(this.getPin('A5').getAndReset(), true), 32);
if (this.endaddr >= 4) this.addr += this.asint(this.binary(this.getPin('A4').getAndReset(), true), 16);
if (this.endaddr >= 3) this.addr += this.asint(this.binary(this.getPin('A3').getAndReset(), true), 8);
if (this.endaddr >= 2) this.addr += this.asint(this.binary(this.getPin('A2').getAndReset(), true), 4);
if (this.endaddr >= 1) this.addr += this.asint(this.binary(this.getPin('A1').getAndReset(), true), 2);
if (this.endaddr >= 0) this.addr += this.asint(this.binary(this.getPin('A0').getAndReset(), true), 1);
this.CLK = this.binary(this.getPin('CLK').getAndReset(), true);
this.R = this.binary(this.getPin('R').getAndReset(), true);
this.invEnable = this.binary(this.getPin('invEnable').getAndReset(), true);
if (this.writeable) {
this.W = this.binary(this.getPin('W').getAndReset(), true);
}
this.D = 0;
for (let i = 0; i < 8; ++i) {
this.D += this.asint(this.binary(this.getPin('D' + i).getAndReset(), true), 1 << i);
}
}
}