207 lines
7.4 KiB
TypeScript
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);
|
|
}
|
|
}
|
|
}
|