Created
December 14, 2022 14:19
-
-
Save roxwize/80a06089d6acf242b52ee8a1c8757304 to your computer and use it in GitHub Desktop.
abandoned LMC implementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // this was originally intended to be a full implementation of a little man computer including assembling, but i got to the assembly part and gave up. | |
| // i might remake this at some point, but itll probably require manual program assembly. | |
| const INT_MAX = 999; | |
| const INT_MIN = -999; | |
| const zeroPad = (num, places) => { | |
| const res = String(Math.abs(num)).padStart(places, "0"); | |
| return num < 0 ? "-" + res : res; | |
| }; | |
| const mem = new Array(100).fill(0); | |
| mem[7] = 0; | |
| mem[99] = 25; | |
| let drawMem, drawRegs, drawOut; | |
| let stepButton, runButton, assembleButton; | |
| let stepping = true; | |
| let programDone = true; | |
| let looping = false; | |
| const CPU = { | |
| programCounter: 0, | |
| instructionRegister: 0, | |
| addressRegister: 0, | |
| accumulator: 0, | |
| error: "Good to go", | |
| output: [], | |
| ticks: 0, // This is to prevent crashes | |
| labels: {}, | |
| }; | |
| CPU.opcodes = { | |
| ADD: 1, | |
| SUB: 2, | |
| STA: 3, | |
| LDA: 5, | |
| BRA: 6, | |
| BRZ: 7, | |
| BRP: 8, | |
| INP: 901, | |
| OUT: 902, | |
| OTC: 922, | |
| HLT: 0, | |
| }; | |
| CPU.reset = function () { | |
| CPU.programCounter = 0; | |
| CPU.instructionRegister = 0; | |
| CPU.addressRegister = 0; | |
| CPU.accumulator = 0; | |
| CPU.error = "Good to go"; | |
| CPU.output = []; | |
| CPU.ticks = 0; | |
| CPU.labels = {}; | |
| }; | |
| CPU.throwError = function (msg) { | |
| programDone = true; | |
| CPU.reset(); | |
| CPU.error = zeroPad(CPU.programCounter, 2) + ": " + msg; | |
| }; | |
| // If the branch goes to a place in memory before or at the current one it counts as looping | |
| CPU.loopCheck = function () { | |
| if (CPU.addressRegister <= CPU.programCounter) looping = true; | |
| else looping = false; | |
| }; | |
| CPU.accOverflow = function () { | |
| const n = CPU.accumulator - 999; | |
| }; | |
| CPU.fetchCycle = function () { | |
| if (CPU.ticks >= 9999) | |
| CPU.throwError("9,999 tick limit reached (check your loops)"); | |
| const newInp = zeroPad(mem[CPU.programCounter].toString(), 3); | |
| if (newInp.length > 3) CPU.throwError("Improper address"); | |
| CPU.instructionRegister = parseInt(newInp.slice(0, 1)); | |
| CPU.addressRegister = parseInt(newInp.slice(1)); | |
| CPU.programCounter++; | |
| }; | |
| CPU.handleInstruction = function (instruction) { | |
| switch (instruction) { | |
| case 0: | |
| programDone = true; | |
| return; | |
| case 1: | |
| // ADD | |
| CPU.accumulator += mem[CPU.addressRegister]; | |
| // Overflow | |
| while (CPU.accumulator > INT_MAX) { | |
| CPU.accumulator = | |
| ((CPU.accumulator + INT_MAX) % (INT_MAX * 2)) - INT_MAX - 1; | |
| } | |
| break; | |
| case 2: | |
| // SUB | |
| CPU.accumulator -= mem[CPU.addressRegister]; | |
| // Underflow | |
| while (CPU.accumulator < INT_MIN) { | |
| // TODO make this work right | |
| } | |
| break; | |
| case 3: | |
| // STA | |
| mem[CPU.addressRegister] = CPU.accumulator; | |
| break; | |
| // There is no 4xx opcode | |
| case 5: | |
| // LDA | |
| CPU.accumulator = mem[CPU.addressRegister]; | |
| break; | |
| case 6: | |
| // BRA | |
| CPU.loopCheck(); | |
| CPU.programCounter = CPU.addressRegister; | |
| break; | |
| case 7: | |
| // BRZ | |
| CPU.loopCheck(); | |
| if (CPU.accumulator === 0) CPU.programCounter = CPU.addressRegister; | |
| break; | |
| case 8: | |
| // BRP | |
| CPU.loopCheck(); | |
| if (CPU.accumulator >= 0) CPU.programCounter = CPU.addressRegister; | |
| break; | |
| case 9: | |
| // INP / OUT / OTC | |
| if (CPU.addressRegister === 1) { | |
| let val = parseInt(prompt("Input required")); | |
| while (isNaN(val)) { | |
| val = parseInt(prompt("Input required (must be a number)")); | |
| } | |
| if (val > INT_MAX) CPU.throwError("Input out of range"); | |
| else CPU.accumulator = val; | |
| } else if (CPU.addressRegister === 2) { | |
| CPU.output.push(CPU.accumulator); | |
| } else if (CPU.addressRegister === 22) { | |
| CPU.output.push(String.fromCharCode(CPU.accumulator)); | |
| } else { | |
| CPU.throwError("Improper input/output operation"); | |
| } | |
| break; | |
| default: | |
| CPU.throwError("Unknown operation"); | |
| break; | |
| } | |
| }; | |
| CPU.findVariables = function (l, opcode, counter) { | |
| if (l.length === 3) { | |
| if (l[1] === "DAT") { | |
| CPU.labels[l[0]] = counter; | |
| mem[counter] = l[2]; | |
| console.log(mem[counter]); // AUGHUHUGJG HG find how to make this work | |
| // Look at the two implementations inspect element when you get home and just | |
| // PRay to god you kee pthe motivation to work on this while you dont do just | |
| // that for like 10 hours | |
| } else if (!(l[1] in CPU.opcodes)) { | |
| CPU.throwError("Assembly: Unknown data storage"); | |
| } | |
| } | |
| if (!(l[0] in CPU.opcodes) && l[1] in CPU.opcodes) { | |
| // e.g. test LDA 50 | |
| mem[counter] = CPU.opcodes[l[1]] + l[2]; | |
| CPU.labels[l[0]] = counter; | |
| } else if (!(l[0] in CPU.opcodes) && l[1] === "DAT") { | |
| // e.g. test DAT | |
| mem[counter] = 0; | |
| CPU.labels[l[0]] = counter; | |
| } | |
| }; | |
| CPU.parseInstructions = function(txt) { | |
| // let counter = 0; | |
| const lines = txt.toUpperCase().split("\n"); | |
| for (let line of lines) { | |
| if (line.trim() === "") continue; | |
| const l = line.trim().split(/\s+/i); | |
| let opcode; | |
| if (l.length === 3) opcode = l[1]; | |
| else if (l.length <= 2) { | |
| opcode = l[0] in CPU.opcodes ? l[0] : l[1] | |
| } | |
| else CPU.throwError("Assembly: Improper instruction length") | |
| const numCode = CPU.opcodes[opcode]; | |
| if (!numCode) continue; | |
| console.log(numCode) | |
| if (opcode == 901 || opcode == 902 || opcode == 922 || opcode == 0) { | |
| // Special instructions that don't need parameters | |
| console.log("Okay") | |
| mem[counter] = opcode; | |
| } | |
| } | |
| } | |
| CPU.scanLines = function(txt) { | |
| // Initial passthrough: go through instructions and add data variables at approrpiate locations | |
| let counter = 0; | |
| const lines = txt.toUpperCase().split("\n"); | |
| for (let line of lines) { | |
| if (line.trim() === "") continue; | |
| const l = line.trim().split(/\s+/i); | |
| if (l.length === 2 && (l[1] === "DAT" || !(l[0] in CPU.opcodes))) { | |
| // e.g. val DAT or val INP | |
| CPU.labels[l[0]] = counter; | |
| if (l[1] === "DAT") mem[counter] = 0; // val DAT | |
| } | |
| if (l.length === 3 && (l[1] === "DAT" || !(l[0] in CPU.opcodes))) { | |
| // e.g. val LDA 55 or val DAT 55 | |
| CPU.labels[l[0]] = counter; | |
| if (l[1] === "DAT") mem[counter] = l[2]; // val DAT 55 | |
| } | |
| counter++; | |
| } | |
| } | |
| CPU.assemble = function (txt) { | |
| CPU.scanLines(txt); | |
| CPU.parseInstructions(txt); | |
| }; | |
| function setup() { | |
| createCanvas(600, 300); | |
| runButton = createButton("RUN"); | |
| runButton.mousePressed(() => { | |
| CPU.reset(); | |
| programDone = false; | |
| }); | |
| stepButton = createButton("STEP"); | |
| stepButton.mousePressed(() => { | |
| if (!stepping) stepping = true; | |
| console.log(stepping); // IMplement this once cpu logic is Done | |
| }); | |
| runButton = createButton("ASSEMBLE"); | |
| runButton.mousePressed(() => { | |
| CPU.assemble(`INP | |
| thing LDA 20 | |
| pab INP | |
| HLT | |
| test DAT 99`); | |
| }); | |
| const drawValue = (txt, value, x, y, size = 20) => { | |
| push(); | |
| fill(0); | |
| textStyle(BOLD); | |
| textAlign(CENTER); | |
| textSize(size); | |
| text(txt, x, y); | |
| textStyle(NORMAL); | |
| text(value, x, y + 20); | |
| pop(); | |
| }; | |
| drawMem = function (sx, sy) { | |
| let x = 0; | |
| let y = 0; | |
| const w = 30; | |
| const h = 20; | |
| textAlign(CENTER); | |
| textSize(8); | |
| for (let addr in mem) { | |
| const n = parseInt(addr); | |
| rect(sx + x, sy + y, w, h); | |
| textStyle(BOLD); | |
| text(zeroPad(addr, 2), x + w * 10 + w / 2, y + h / 2); | |
| textStyle(NORMAL); | |
| text(zeroPad(mem[addr], 3), x + w * 10 + w / 2, y + h - 2); | |
| x += w; | |
| if ((n + 1) % 10 === 0) { | |
| y += h; | |
| x = 0; | |
| } | |
| } | |
| }; | |
| drawRegs = function (sx, sy) { | |
| let w = width / 2; | |
| let h = height; | |
| push(); | |
| fill(200); | |
| rect(sx, sy, w, h); | |
| pop(); | |
| // Write program counter | |
| drawValue( | |
| "Program counter", | |
| zeroPad(CPU.programCounter, 2), | |
| w / 2, | |
| sy + 20 | |
| ); | |
| drawValue( | |
| "Instruction register", | |
| CPU.instructionRegister, | |
| w / 4, | |
| sy + 60, | |
| 14 | |
| ); | |
| drawValue( | |
| "Address register", | |
| zeroPad(CPU.addressRegister, 2), | |
| w / 2 + w / 4, | |
| sy + 60, | |
| 14 | |
| ); | |
| drawValue("Accumulator", zeroPad(CPU.accumulator, 3), w / 2, sy + 120); | |
| drawValue("Error (if any)", CPU.error, w / 2, sy + 160, 10); | |
| }; | |
| drawOut = function () { | |
| let w = width; | |
| let h = 100; | |
| push(); | |
| fill(50); | |
| rect(0, 200, w, h); | |
| textStyle(BOLDITALIC); | |
| textAlign(LEFT); | |
| textSize(20); | |
| fill(255); | |
| text("Output", 10, 225); | |
| textFont("Consolas"); | |
| textSize(10); | |
| textStyle(NORMAL); | |
| for (let line in CPU.output) { | |
| text(CPU.output[line], 10, 240 + 10 * line); // Make it wrap when it reaches the bottom (maybe) | |
| } | |
| pop(); | |
| }; | |
| } | |
| function draw() { | |
| background(0); | |
| drawMem(width - 300, 0); | |
| drawRegs(0, 0); | |
| drawOut(); | |
| while (!programDone) { | |
| CPU.ticks++; | |
| CPU.fetchCycle(); | |
| CPU.handleInstruction(CPU.instructionRegister); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment