var registers=[]; var blocks=[]; var wires=[]; var tooltips=[]; var embedded = {}; var images={}; const BORDER_THICKNESS=5.5; var canvas, ctx; var outputBuf = [] var clockTimer; var opcodes = ['add','sub','adc','subb','nand','xor','sll','srl','mov','ldb','stb', 'nop','bflag','bnoflag', 'jmp', 'hlt']; var rs = ['io','stackptr','datapg','instpg','gp0','gp1','gp2','gp3']; var flags = ['zero','carry','negative','overflow','input']; var memory=new Array(65536).fill(0); var memPage = 0; var dataPage = 0; function getNumber(pins,reverse=true){ var val = 0; for(var i=0;i>12; var addressMode = (machineCode & 0b0000100000000000) >>11; var r1 = (machineCode & 0b0000011100000000)>>8; var r2 = (machineCode & 0b0000000011100000)>>5; var imm8 = machineCode & 0b0000000011111111; if(opcode==15){ return(opcodes[opcode]); } else if(opcode<11) { return(`${opcodes[opcode]} ${specific?rs[r1]:'reg'}, ${specific?((addressMode?imm8:rs[r2])):addressMode?'imm8':'reg'}`); } else if (opcode==11){ return(`${opcodes[opcode]}`); } else if(opcode==12) { return(`${opcodes[opcode]} ${specific?flags[r1 & 0b00000111]:'flag'}, ${specific?((addressMode?imm8:rs[r2])):addressMode?'imm8':'reg'}`); } else { return(`${opcodes[opcode]} ${specific?((addressMode?imm8:rs[r2])):(addressMode?'imm8':'reg')}`); } } document.addEventListener("DOMContentLoaded", function(event){ event; canvas = document.getElementById("sim"); ctx = canvas.getContext('2d'); setup(); setTimeout(function(){ embedded['alu'] = document.getElementById('ALUframe').contentWindow.chip; embedded['mux1'] = document.getElementById('MUX1frame').contentWindow.chip; embedded['mux2'] = document.getElementById('MUX2frame').contentWindow.chip; },2000); }); function w(width){ return ((width*canvas.width)/1440); } function h(height){ return ((height*canvas.height)/976); } function drawBorder(thing,thickness,color="black"){ ctx.fillStyle=color; ctx.fillRect(thing.x-thickness,thing.y-thickness,thing.width+thickness*2,thing.height+thickness*2); } class Block { constructor(name, x, y, width, height,points=[],editable=true){ this.name=name; this.url=""; this.x=w(x); this.y=h(y); this.width=w(width); this.height=h(height); this.editable=editable; this.points=points; if(this.editable) this.done=false; else this.done=true; } update(){ } reset(){ } makePointArray(){ this.points=[] for(const[key,value] of Object.entries(this.pins)){ key; //this.points=this.points.concat(value); value.forEach((pin,i)=>{ pin.number=i; this.points.push(pin) }); } } drawOverlay(){ if(!(this.done||this.attempted)){ ctx.fillStyle="rgba(255,255,255,0.6)"; ctx.fillRect(this.x,this.y+BORDER_THICKNESS+35,this.width,this.height-BORDER_THICKNESS-35); ctx.fillStyle="#FFA500" if(this.wrong) ctx.fillStyle="#dd3333"; ctx.fillRect(this.x+this.width/2-35,this.y+this.height/2-35+17,70,70); ctx.drawImage(images[this.editable?'pencil':'question'],this.x+this.width/2-30,this.y+this.height/2-30+17,60,60); } else if(this.editable) ctx.drawImage(images[this.editable?'pencil':'question'],this.x+this.width-w(37),this.y+this.height-h(30)-h(3),w(30),h(30)); } draw(color='white',textcolor='white'){ ctx.fillStyle=color; ctx.fillRect(this.x, this.y, this.width, this.height); ctx.fillStyle="#444"; ctx.fillRect(this.x,this.y,this.width,BORDER_THICKNESS); ctx.fillRect(this.x-BORDER_THICKNESS,this.y,BORDER_THICKNESS,this.height+BORDER_THICKNESS); ctx.fillRect(this.x+this.width,this.y,BORDER_THICKNESS,this.height+BORDER_THICKNESS); ctx.fillRect(this.x,this.y+this.height,this.width,BORDER_THICKNESS); if(this.done) ctx.fillStyle="#43c143"; else if(!this.attempted) ctx.fillStyle="#ffa500"; else ctx.fillStyle="#dd3333"; if(this.wrong){ ctx.fillStyle="#dd3333"; } ctx.fillRect(this.x,this.y+BORDER_THICKNESS,this.width,35); ctx.fillStyle=textcolor; ctx.font=170*0.15+"px vcr_osd_monoregular"; ctx.textBaseline = "top"; ctx.textAlign='left'; ctx.fillText(this.name,this.x+w(7), this.y+h(13)); this.points.forEach(p=>p.draw()); } checkMouse(pos){ // check if mouse is over icon if(!this.attempted&&this.editable){ return pos.x>=this.x+this.width/2-35 && pos.y>=this.y+this.height/2-35 && pos.x<=this.x+this.width/2-35+70 && pos.y<=this.y+this.height/2-35+70; } else return pos.x>=this.x+this.width-w(37) && pos.x<=this.x+this.width-w(7) && pos.y>=this.y+this.height-h(33) && pos.y<=this.y+this.height-h(3) && JSON.stringify(Array.from(ctx.getImageData(pos.x,pos.y,1,1).data.slice(0,3)))!="[255,255,255]"; } } class Decoder extends Block { update(){ this.setInstruction(humanInstruction(registers[0].value,false),(registers[0].value>>12)); for(var i=0;i<16;i++){ if(i<4){ this.pins.toALU[i].write(this.pins.instruction[i].read()); } } // read from first register only if it's an ALU operation // write to the first register unless we're in a store or branch instruction this.pins.registerReadEnable[0].write(this.opcode<8); this.pins.registerWriteEnable[0].write(this.opcode<10); // imm8 this.pins.toMux[0].write(this.pins.instruction[4].read() && this.opcode!=11 &&this.opcode!=15);//(this.mode==1 && this.opcode!=11 && this.opcode!=15)); // mem load this.pins.toMux[1].write(this.opcode==9); this.pins.memDataReadEnable[0].write(this.opcode==9); this.pins.memDataWriteEnable[0].write(this.opcode==10); // this.pins.memAddrWriteEnable[0].write(this.opcode==9||this.opcode==10); // jumps if(this.opcode==14) this.pins.pcWriteEnable[0].write(1); else if(this.opcode!=12&&this.opcode!=13) this.pins.pcWriteEnable[0].write(0); else this.pins.pcWriteEnable[0].write(branchyes?1:0); } setInstruction(instruction,opcode,mode){ this.instruction = instruction; this.opcode=opcode; this.mode=mode; } constructor(x,y,width,height,points=[],editable=true){ super("decoder",x,y,width,height,points,editable); this.mode=0; this.pins = { instruction: [ ], toALU: [ ], toMux: [ new Pin(this.x+this.width,this.y+this.height-50,6,false,"right"), new Pin(this.x+this.width,this.y+this.height-50+17,6,false,"right") ], registerWriteEnable: [ new Pin(this.x+this.width,this.y+this.height-140,6,false,"right") ], registerReadEnable: [ new Pin(this.x+this.width,this.y+this.height-157,6,false,"right") ], memAddrWriteEnable: [ new Pin(this.x+190,this.y+this.height), ], memAddrReadEnable: [ new Pin(this.x+207,this.y+this.height) ], pcWriteEnable: [ new Pin(this.x+227+17,this.y+this.height) ], pcReadEnable: [ new Pin(this.x+227+34,this.y+this.height) ], memDataWriteEnable:[ new Pin(this.x+282+17,this.y+this.height) ], memDataReadEnable:[ new Pin(this.x+282+34,this.y+this.height) ], } for(var i=0;i<5;i++){ this.pins.instruction.push(new Pin(this.x+17+17*i, this.y)); if(i<4) this.pins.toALU.push(new Pin((this.x+17+17*i+(i<4?0:34)), this.y+this.height)); } this.makePointArray(); this.instruction="add reg,reg"; this.opcode=0; } draw(){ super.draw(); ctx.fillStyle="black"; ctx.font=250*0.07+"px vcr_osd_monoregular"; ctx.textBaseline = "top"; ctx.textAlign='center'; ctx.fillText("opcode: "+Register.getBits(this.opcode,4)+" / "+Register.getHex(this.opcode,1)+"",this.x+this.width/2-w(4), this.y+h(55)); var am=this.pins["instruction"][4].read(); this.mode=am; ctx.fillText("mode: "+(am?1:0)+" / "+(this.opcode==15?'special':(((am==0||this.opcode==11)?"register":"immediate"))),this.x+this.width/2-w(4), this.y+h(75)); ctx.font=240*0.15+"px vcr_osd_monoregular"; ctx.fillText(this.instruction,this.x+this.width/2-w(4), this.y+h(100)); } } class Mux extends Block { setChannel(c){ this.channel=c; } update(){ if(this.second){ if(!embedded.mux2 || !embedded.mux2.pins) return; embedded.mux2.pins[16] = this.pins.selector[0].read(); for(var i=0;i<8;i++){ embedded.mux2.pins[i] = this.pins.channel0[i].read(); embedded.mux2.pins[8+i] = this.pins.channel1[7-i].read(); this.pins.output[7-i].write(embedded.mux2.pins[24-i]); }}else{ if(!embedded.mux1 || !embedded.mux1.pins) return; embedded.mux1.pins[16] = this.pins.selector[0].read(false); for(var i=0;i<8;i++){ embedded.mux1.pins[i] = this.pins.channel0[7-i].read(); embedded.mux1.pins[8+i] = this.pins.channel1[i].read(); this.pins.output[7-i].write(embedded.mux1.pins[24-i]); }} this.setChannel(this.pins.selector[0].read()==true?1:0); } constructor(x,y,width,height,second=false,de=false,points=[],editable=true){ super((de?"de":"")+"multiplexer",x,y,width,height,points,editable); this.de=de; this.second=second; this.channel=0; if(second) var theselector=new Pin(this.x+this.width-17, this.y); else var theselector=new Pin(this.x-BORDER_THICKNESS,blocks[0].pins.toMux[0].y,6,"","right") if(!this.de){ this.pins={ selector: [ theselector ], channel0:[ ], channel1: [ ], output:[ ] } for(var i=0;i<8;i++){ if(second){ this.pins.channel1.push(new Pin(this.x+62+17*i,this.y+BORDER_THICKNESS+this.height,6,"","up")); this.pins.output.push(new Pin(this.x+this.width,this.y+17+17*i,6,"","right")); this.pins.channel0.push(new Pin(this.x-BORDER_THICKNESS,this.y+17+17*i,6,"","right")); } else { this.pins.channel1.push(new Pin(this.x+17+17*i,this.y)); this.pins.channel0.push(new Pin(this.x+BORDER_THICKNESS+this.width,this.y+17+17*i,6,"","left")); this.pins.output.push(new Pin(this.x+17+17*i,this.y+this.height)); } } } else { this.pins={ selector: [ new Pin(this.x-BORDER_THICKNESS,this.y+15,6,"","right"), new Pin(this.x-BORDER_THICKNESS,this.y+30,6,"","right") ], input:[ ], channel0:[ ], channel1:[ ], channel2:[ ] } for(var i=0;i<8;i++){ this.pins.input.push(new Pin(this.x-BORDER_THICKNESS,this.y+67+17*i,6,"","right")); this.pins.channel0.push(new Pin(this.x+this.width,this.y+17+17*i,6,"","right")); this.pins.channel1.push(new Pin(this.x+17+17*i,this.y+this.height)); this.pins.channel2.push(new Pin(this.x+220+17*i,this.y+this.height)); } } this.makePointArray(); } draw(){ super.draw(); ctx.fillStyle="black"; ctx.font="25px vcr_osd_monoregular"; ctx.baseline="middle"; ctx.textAlign="center"; ctx.fillText(" channel: "+this.channel,this.x+this.width/2,this.y+this.height/2); } } class ALU extends Block { update(){ if(!embedded.alu) return; for(var i=0;i<8;i++){ if(i<4){ embedded.alu.pins[19-i] = this.pins.opcode[i].read(); this.pins.flags[i].write(embedded.alu.pins[29+i]); } embedded.alu.pins[i] = this.pins.operand1[i].read(); embedded.alu.pins[i+8] = this.pins.operand2[i].read(); this.pins.result[i].write(embedded.alu.pins[21+i]); } this.values[3].set(getNumber(this.pins.flags)); this.values[1].set(getNumber(this.pins.operand1,false)); this.values[0].set(getNumber(this.pins.operand2,false)); this.values[2].set(getNumber(this.pins.result,false)); var opcode=getNumber(this.pins.opcode); if(opcode==6){ this.correctAnswer = this.values[1].value << this.values[0].value; } else { this.correctAnswer = -1; } if(opcode>=8){ this.operation = "move"; } else { this.operation = opcodes[opcode]; } } constructor(x,y,width,height,points=[],editable=true){ points; super("ALU",x,y,width,height,wires,editable); this.wrong=false; this.operation="add"; this.values=[new Register("operand 1",8,0,this.x+w(20),this.y+h(80),50,"above",true,"black"), new Register("operand 0",8,0,this.x+w(20),this.y+h(175),50,"above",true,"black"), new Register("result",8,0,this.x+w(20),this.y+h(340),50,"above",true,"black"), new Register("O N C Z flags",4,1,this.x+w(20),this.y+h(410),32,"above",false,"black")]; //new Register("O . N . C . Z",4,0,this.x+w(125),this.y+h(360),30,"above",false,"black")]; this.pins={ opcode: []/*,setFlags:[]*/, operand1:[], operand2:[],flags:[],result:[] } for(var i=0;i<4;i++){ this.pins.opcode.push(new Pin(this.x+17+17*i,this.y)); this.pins.flags.push(new Pin(this.x+17+17*i,this.y+this.height)); } this.pins.flags[3].write(1); for(var i=0;i<8;i++){ this.pins.operand2.push(new Pin(this.x+this.width+BORDER_THICKNESS, this.y+17+17*i,6,"","left")); this.pins.operand1.push(new Pin(this.x+this.width+BORDER_THICKNESS, this.y+167+17*i,6,"","left")); this.pins.result.push(new Pin(this.x+this.width, this.y+317+17*i,6,"","right")); } //this.pins.setFlags.push(new Pin(this.x+17*5+34,this.y)); //this.pins.setFlags[0].write(1); this.makePointArray(); } draw(){ super.draw(); ctx.fillStyle="black"; ctx.textAlign="center"; ctx.textBaseline="middle"; ctx.font="25px vcr_osd_monoregular"; ctx.fillText("operation: "+this.operation,this.x+this.width/2, this.y+260); ctx.font="15px vcr_osd_monoregular"; //ctx.fillText("set flags: "+(this.pins.setFlags[0].read()==1?"yes":"no"),this.x+this.width/2, this.y+280); ctx.fillRect(this.x+20,this.y+300,this.width-40,2); this.values.forEach(v=>v.draw(0)); } } class RegisterFile extends Block { fileRegisters = []; selected(){ return{r1:this.fileRegisters[7-getNumber(this.pins.address1,false)], r2:this.fileRegisters[7-getNumber(this.pins.address2,false)]}; } update(){ for(var i=0;i<8;i++){ if(this.pins.read1Enable[0].read()) this.pins.data1[7-i].write(this.selected().r1.binary[i]); this.pins.data2[7-i].write(this.selected().r2.binary[i]); } } realUpdate(){ if(!!this.pins.write1Enable[0].read()) { this.selected().r1.set(getNumber(this.pins.writeback,false),true); } } constructor(x,y,fileRegisters=[],pins=[],editable=true){ pins,editable; super("registers",x,y,0,fileRegisters.length*h(85)+h(45),wires,false); fileRegisters.forEach((r,i)=>{ if(!r.bits){ r.bits=8 } this.fileRegisters.push(new Register(r.name,r.bits,0,x+w(20),y+i*h(85)+h(65),40,"above",true,"black")); }); this.width=this.fileRegisters[0].width+w(80); this.pins={ clock:[ new Pin(this.x-BORDER_THICKNESS,this.y+17,6,"","right") ], read1Enable: [ new Pin(this.x-BORDER_THICKNESS,this.y+17+35,6,"","right") ], write1Enable: [new Pin(this.x-BORDER_THICKNESS,this.y+34+35,6,"","right") ], address1: [ ], address2: [ ], data2:[ ], data1:[ ], writeback:[] }; for(var i=0;i<3;i++){ this.pins.address2.push(new Pin(this.x-BORDER_THICKNESS,this.y+55+35+17+17*i,6,"","right")); this.pins.address1.push(new Pin(this.x-BORDER_THICKNESS,this.y+125+35+17+17*i,6,"","right")); } for(var i=0;i<8;i++){ this.pins.data2.push(new Pin(this.x,this.y+205+35+17+17*i,6,"","left")); this.pins.data1.push(new Pin(this.x,this.y+365+35+17+17*i,6,"","left")); this.pins.writeback.push(new Pin(this.x-BORDER_THICKNESS,this.y+530+35+17+17*i,6,"","right")); } super.makePointArray(); } draw(){ super.draw(); this.fileRegisters.forEach(r=>r.draw(1)); } } class Clock extends Block { reset(){ this.state=0; this.cycles=0; } update(){ this.pins.clk[0].write(this.state); } constructor(x,y,width,height){ super("clock",x,y,width,height,[],false); this.pins={clk:[new Pin(this.x+this.width,this.y+17,6,"","right")]}; super.makePointArray(); this.cycles=0; this.state=0; } pulse(t,w=false){ this.state = 1; if(!w) this.cycles+=1; setTimeout(function(){this.state=0}.bind(this),t/10); } draw(){ super.draw(); ctx.fillStyle="black"; ctx.textAlign="center"; ctx.textBaseline="middle"; ctx.fillText(this.cycles,this.x+this.width/2,this.y+this.height/2+20); } } class Register { pins=[]; update(fuck=false){ /*if(this.name=="memory address register" && fuck) return; if(this.name=="m. data reg." && fuck) return;*/ if(this.name=="flags"){ this.set(getNumber(this.pins)); if(document.getElementById("input").value.length>0) this.value=this.value|0b10000; else { this.value=this.value & 0b01111; } this.set(this.value); } else if('writeEnable' in this) { if(!!this.writeEnable.read()){ this.set(getNumber(this.pins)); } } if(this.name=="m. data reg."){ var mAddr = registers[2].value; if(this.writeEnable.read()){ memory[mAddr] = this.value; } if(this.readEnable.read()||initialRun){ initialRun=false; this.set(memory[mAddr]); } } } static getBits(n,digits=8){ if(n<0||n>=Math.pow(2,digits)||n%1!==0) { //console.error(n+" is fucked, yo"); } return ("0000000000000000"+n.toString(2)).substr(-digits); } static getHex(n, digits=2){ return "0x"+("0000"+n.toString(16).toUpperCase()).substr(-digits); } constructor(name, bits, value,x,y,scale,position="above",showValue=true,textColor="white",pins=false,numPins=8){ this.name=name; this.bits=bits; this.x=w(x); this.y=h(y); this.width=w(scale * this.bits)*0.75; this.height=h(scale); this.above=position=="above"; this.showValue=showValue; this.textColor=textColor; if(pins) for(var i=bits-Math.min(bits,numPins);i{ pin.write(this.binary[i]) }); } draw(border=0){ if(this.name=="flags"){ this.value=actualflags; if(document.getElementById("input").value.length>0) this.value=this.value|0b10000; else { this.value=this.value & 0b01111; } this.set(this.value); } drawBorder(this,border); ctx.fillStyle='white'; ctx.fillRect(this.x,this.y,this.width,this.height); var section_width = this.width/this.bits; // draw text and separators // labels ctx.fillStyle = this.textColor; ctx.font=this.height*0.4+"px vcr_osd_monoregular"; var textY; if(this.above){ ctx.textBaseline = "bottom"; textY = this.y-this.height*0.05; } else { ctx.textBaseline = "top"; textY = this.y + this.height + this.height * 0.05; ctx.fillStyle="rgba(24,27,32,0.5)"; ctx.fillRect(this.x,this.y+this.height,this.width,21); ctx.fillStyle="white"; } ctx.textAlign = "left"; ctx.fillText(this.name,this.x,textY) ctx.textAlign = "right"; if(this.name=="instruction register"){ ctx.fillText(humanInstruction(this.value),this.x+this.width,textY); } else this.showValue ? ctx.fillText(this.value+" | "+Register.getHex(this.value,(this.bits/4)),this.x+this.width,textY) : null; for(var i=0;i0) { ctx.fillRect(this.x+section_width*i,this.y,2,this.height); } } this.pins.forEach(pin=>pin.draw()); if(this.readEnable){ ctx.fillStyle="black"; ctx.fillRect(this.x-6,this.y,6,this.height); this.readEnable.draw(); this.writeEnable.draw(); } } } var actualflags=0; function pointInCircle(x, y, cx, cy, radius) { var distancesquared = (x - cx) * (x - cx) + (y - cy) * (y - cy); return distancesquared <= radius * radius; } class Pin { constructor(x,y,radius=6,label=false,direction="down"){ this.x=x; this.y=y; this.radius=radius; this.state=0; this.label=label; this.direction=direction; } read(){ return this.state; } write(state){ this.state=state==true?1:0; } checkMouse(pos){ return pointInCircle(pos.x,pos.y,this.x,this.y,this.radius); } draw(){ ctx.fillStyle=this.read()==1?"red":"lightgray"; ctx.beginPath(); if(this.direction=="down"){ ctx.arc(this.x,this.y,this.radius, 0*Math.PI,1*Math.PI,false); } else if(this.direction=="right"){ ctx.arc(this.x,this.y,this.radius,1.5*Math.PI,0.5*Math.PI,false); } else if(this.direction=="left") { ctx.arc(this.x,this.y,this.radius,0.5*Math.PI,1.5*Math.PI,false); } else if (this.direction=="up") { ctx.arc(this.x,this.y,this.radius, -1*Math.PI,0*Math.PI,false); } ctx.fill(); } } class Wire { update(){ var state=!!this.input.read(); if(this.killme>-1){ if(registers[4].readEnable.read()){ state = parseInt(registers[4].binary[7-this.killme]); this.input.write(state); } } this.state=state; var hovered = this.input.hovered =1 || this.output.hovered == 1; if(!this.behind) this.color=state?"#F00":"#AAA"; else if(this.behind==1) this.color=state?"#A00":"#777"; else this.color=state?"#800":"#555"; this.output.write(state); } draw(){ ctx.beginPath(); ctx.moveTo(this.input.x,this.input.y); var n = this.output.number; n = n - this.reindex; var fuckyou = (this.output.number==0 && this.input.direction=="down")?2:0; if (this.fuckoffanddie){ ctx.lineTo(435,this.input.y); ctx.lineTo(435,650); ctx.lineTo(760,650); ctx.lineTo(760,570); ctx.lineTo(this.output.x,570); } else { if(!this.horizontal){ if(!this.reverse){ ctx.lineTo(this.input.x,this.input.y+fuckyou+Math.max((this.output.y-this.input.y)*0.45+n*7,0)); ctx.lineTo(this.output.x,this.input.y+fuckyou+Math.max((this.output.y-this.input.y)*0.45+n*7,0.0)); } else { /* ctx.lineTo(this.input.x,this.input.y-15+((this.output.y-this.input.y)*(1-(0.15*n)))); ctx.lineTo(this.output.x,this.input.y-15+((this.output.y-this.input.y)*(1-(0.15*n)))); */ ctx.lineTo(this.input.x,this.input.y+fuckyou+Math.max((this.output.y-this.input.y)*0.56-n*6,0)); ctx.lineTo(this.output.x,this.input.y+fuckyou+Math.max((this.output.y-this.input.y)*0.56-n*6,0.0)); } } else { if(!this.reverse){ //ctx.lineTo(this.input.x+fuckyou+Math.max((this.output.x-this.input.x)*0.45+n*7,0),this.input.y); //ctx.lineTo(this.input.x+fuckyou+Math.max((this.output.x-this.input.x)*0.45+n*7,0.0),this.output.y); if(!this.zigzag){ ctx.lineTo(this.input.x-this.flip*(Math.abs(this.output.x-this.input.x)*0.45+n*7),this.input.y); ctx.lineTo(this.input.x-this.flip*(Math.abs(this.output.x-this.input.x)*0.45+n*7),this.output.y); } else{ if(this.kms) ctx.lineTo(this.input.x,this.output.y) else ctx.lineTo(this.output.x,this.input.y) } } else { if(!this.zigzag){ ctx.lineTo(this.input.x-this.flip*(Math.abs(this.output.x-this.input.x)*0.45-n*7),this.input.y); ctx.lineTo(this.input.x-this.flip*(Math.abs(this.output.x-this.input.x)*0.45-n*7),this.output.y); } else{ ctx.lineTo(this.input.x,this.output.y) } /* ctx.lineTo(this.input.x,this.input.y-15+((this.output.y-this.input.y)*(1-(0.15*n)))); ctx.lineTo(this.output.x,this.input.y-15+((this.output.y-this.input.y)*(1-(0.15*n)))); */ } }} ctx.lineTo(this.output.x, this.output.y); var redcolor,graycolor; if(this.behind==0){ redcolor="#F0F" graycolor="#99F" } else if(this.behind==1) { redcolor="#A0A" graycolor="#55F" } else { redcolor = "#808" graycolor="#22F" } ctx.strokeStyle=this.hovered?(this.state?redcolor:graycolor):this.color // cursed ctx.strokeStyle=this.color.substr(0,3)+(this.hovered?"F"/*(Math.min(parseInt(this.color[1],16)+3,15)).toString(16)*/:this.color[3]); ctx.lineWidth=this.hovered?6:4; ctx.stroke(); } constructor(input, output, label,behind=false, reverse=false,reindex=0,horizontal=false,flip=false,zigzag=false,kms=false,fuckoffanddie=false,killme=-1 ){ this.input=input; this.fuckoffanddie=fuckoffanddie; this.output=output; this.label=label; this.reverse=reverse; this.reindex=reindex; this.behind=behind; this.horizontal=horizontal; this.flip=flip?-1:1; this.zigzag=zigzag; this.kms=kms; this.killme=killme; } } class Tooltip { constructor(x,y,message,persist=false){ this.x=x; this.y=y; this.message=message; this.persist=persist; } draw(offset){ ctx.fillStyle="rgba(0,0,0,0.65)"; ctx.font="20px vcr_osd_monoregular"; ctx.textBaseline="bottom"; ctx.textAlign="left"; var width=ctx.measureText(this.message).width+6; ctx.fillRect(this.x<720?this.x:this.x-width,this.y-25-(26*offset),width,25); ctx.fillStyle="white"; ctx.fillText(this.message,this.x<720?(this.x+3):(this.x-width+3),this.y-(26*offset)); } } function wireUp(){ // clock wires.push(new Wire(blocks[4].pins.clk[0],blocks[3].pins.clock[0],"clock")); // instruction to decoder //decoder to ALU for(var i=0;i<5;i++){ wires.push(new Wire(registers[0].pins[i],blocks[0].pins["instruction"][i],i<4?("opcode bit "+(3-i)):"addressing mode",false,false,2)); if(i<4) wires.push(new Wire(blocks[0].pins.toALU[i],blocks[2].points[i],i<4?"ALU opcode bit ":"set flags?")); } for(var i=0;i<8;i++){ ///m data reg to mux wires.push(new Wire(blocks[5].pins.channel1[7-i],registers[4].pins[7-i],"MDR (mux ch. 1), bit "+i,false,true,3,false,false,false,false,false,i)); //wires.push(new Wire(blocks[5].pins.channel1[7-i],registers[4].pins[7-i],"memory data register, bit "+(7-i),false,true,5,false,false,false)); } // ALU to flags var flagtext=["overflow flag","negative flag","carry flag","zero flag"] for(var i=0;i<4;i++){ wires.push(new Wire(blocks[2].pins.flags[i],registers[1].pins[i+1],flagtext[i],false,true,2)); } //// INPUT OUTPUT STOPS BEHIND REVERSE REINDEX HORIZONTAL FLIP // decoder to mux control wires.push(new Wire(blocks[0].pins.toMux[0],blocks[1].pins.selector[0],"mux 0 selector")); // instruction to multiplexer for(var i=0;i<8;i++){ // reg to mux and alu wires.push(new Wire(blocks[3].pins.data1[i],blocks[2].pins.operand1[i],"first register data (operand 0), bit "+i,false,true,30,true)); wires.push(new Wire(blocks[3].pins.data2[i],blocks[1].pins.channel0[i],"second register data (mux ch. 0), bit "+i,false,false,0,true)); //mux to alu wires.push(new Wire(blocks[1].pins.output[i],blocks[2].pins.operand2[7-i],/*"mux output, bit "+i*/"mux output (operand 1), bit "+[7-i],false,true,0,true,true,true)); //alu to writeback //wires.push(new Wire(blocks[2].pins.result[i],blocks[3].pins.writeback[i],"ALU result, bit "+i,false,false,-29,true,true)); wires.push(new Wire(blocks[5].pins.output[i],blocks[3].pins.writeback[i],"mux output (reg writeback), bit "+i,false,false,-29,true,true)); wires.push(new Wire(blocks[2].pins.result[i],blocks[5].pins.channel0[i],"mux ch. 0 (ALU result), bit "+i,false,false,-29,true,true)); //alu to mar wires.unshift(new Wire(blocks[2].pins.result[i],registers[2].pins[7-i],"MAR (ALU result), bit "+i,true,false,0,true,true,true)); wires.unshift(new Wire(blocks[2].pins.result[i],registers[4].pins[7-i],"MDR (ALU result), bit "+i,true,false,0,true,true,true)); wires.unshift(new Wire(blocks[2].pins.result[i],registers[3].pins[7-i],"PC (ALU result), bit "+i,3,false,0,true,true,true)); //inst to mux wires.push(new Wire(registers[0].pins[15-i],blocks[1].pins.channel1[7-i],"immediate data (mux ch. 1), bit "+i,false,(7-i)<3,(7-i)<3?-2:8)); } // decoder to mux2 wires.unshift(new Wire(blocks[0].pins.toMux[1],blocks[5].pins.selector[0],"mux 1 selector",true,false,0,false,false,true,true,true)); // decoder to reg rwenable wires.unshift(new Wire(blocks[0].pins.registerReadEnable[0],blocks[3].pins.read1Enable[0],"first register read enable",true,false,-34,true,true)); wires.unshift(new Wire(blocks[0].pins.registerWriteEnable[0],blocks[3].pins.write1Enable[0],"first register write enable",true,false,-35,true,true)); // decoder to mar rwenable wires.unshift(new Wire(blocks[0].pins.memAddrReadEnable[0],registers[2].readEnable,"MAR read enable",3, false,0,true,true,true,true)); wires.unshift(new Wire(blocks[0].pins.memAddrWriteEnable[0],registers[2].writeEnable,"MAR write enable",3, false,0,true,true,true,true)); //pc rwenable wires.unshift(new Wire(blocks[0].pins.pcReadEnable[0],registers[3].readEnable,"PC read enable",3, false,0,true,true,true,true)); wires.unshift(new Wire(blocks[0].pins.pcWriteEnable[0],registers[3].writeEnable,"PC write enable",3, false,0,true,true,true,true)); // m d r rwenable wires.unshift(new Wire(blocks[0].pins.memDataReadEnable[0],registers[4].readEnable,"MDR read enable",3, false,0,true,true,true,true)); wires.unshift(new Wire(blocks[0].pins.memDataWriteEnable[0],registers[4].writeEnable,"MDR write enable",3, false,0,true,true,true,true)); // instruction to register address for(var i=0;i<3;i++){ wires.unshift(new Wire(registers[0].pins[7-i],blocks[3].pins.address1[i],"first register address, bit "+i,3,false,3,true,true,true,true)); wires.unshift(new Wire(registers[0].pins[10-i],blocks[3].pins.address2[i],"second register address, bit "+i,3,false,3,true,true,true,true)); } } url=""; function setup(){ images["pencil"]=new Image(); images["pencil"].src='img/pencil.png'; images["question"]=new Image(); images["question"].src='img/question.png'; ctx.canvas.width = 1440//window.innerWidth*0.75; ctx.canvas.height = 976 //window.innerHeight; registers.push(new Register("instruction register",16, 0, w(13), h(36), 60,"above",true,"white","below",16)); registers.push(new Register("flags",5,0,w(13),h(885),45,"below",false,"white","above")); registers.push(new Register("memory address register", 16, 0, w(250), h(885), 45, "below",true,"white","above")) registers.push(new Register("program counter", 16, 0, w(880), h(885), 45, "below",true,"white","above")) registers.push(new Register("m. data reg.",8,0,880+(registers[registers.length-1].width/2),803,45,"below",true,"white","above")); //goddammit for(var i=2;i{ var rect = canvas.getBoundingClientRect(); tooltips = tooltips.filter(t=>t.persist==true); scaleX=canvas.width/rect.width; scaleY=canvas.height/rect.height; isOverOne=false; this.wires.forEach(wire=>{ wire.hovered=false; var pos = {x:(e.clientX-rect.left)*scaleX, y:(e.clientY-rect.top)*scaleY}; if(wire.input.checkMouse(pos)||wire.output.checkMouse(pos)){ wire.hovered=true; tooltips.push(new Tooltip(pos.x,pos.y,wire.label)); isOverOne=2; } }); blocks.forEach(block=>{ if(block.checkMouse({x:(e.clientX-rect.left)*scaleX, y:(e.clientY-rect.top)*scaleY})){isOverOne=true;url=block.url;} }); if(isOverOne==1){ canvas.style.cursor="pointer"; } else if(isOverOne==2){ canvas.style.cursor="help"; } else { canvas.style.cursor="default"; url=""; } }); document.addEventListener('click',e=>{ if(url){ window.location.href=url; console.log("no"); } else { console.log("no"); } }); requestAnimationFrame(drawCanvas); } var wait=false; function clockTick(t=1000,oneTime=false){ blocks[4].pulse(1000,wait); if(wait){ registers[2].update(); registers[4].update(); wait=false; } else { if(blocks[2].correctAnswer>-1){ if(blocks[2].correctAnswer!=blocks[2].values[2].value){ tick=false; blocks[2].done = false; blocks[2].editable = true; blocks[2].wrong = true; return; } } if(!tick && !oneTime) return; registers.forEach(r=>r.update(true)); actualflags=getNumber(blocks[2].pins.flags); if(blocks[4].cycles>1) blocks[3].realUpdate(); drawMemory(); nextInstruction(); } if(tick){ setTimeout(clockTick,t,t); } } var tick=false; const zeroPad = (num, places) => String(num).padStart(places, '0') var memPage=0; function nextMemPage(){ memPage++; if(memPage>255) memPage=0; document.getElementById('label_mempage').innerHTML=zeroPad(memPage,3); drawMemory(); } function prevMemPage(){ memPage--; if(memPage<0) memPage=255; document.getElementById('label_mempage').innerHTML=zeroPad(memPage,3); drawMemory(); } function drawMemory(){ var memTable = document.getElementById('memory').children[0]; for(var i=1;i<=16;i++){ var row = memTable.children[i]; for(var j=1;j<=16;j++){ var cell = row.children[j]; var address = ((memPage)<<8)+(i-1)*16+(j-1); var val = memory[address]; cell.innerHTML = zeroPad(val,3); if(val>0) cell.classList.add('nonzero'); else cell.classList.remove('nonzero'); } } } initialRun=false; function compile(){ resetAll(); try { ass = new assembler(); var bytecode = ass.assemble(document.getElementById('program').value); console.log(ass.labelAddresses); for(var i=0;ir.update()); } var branchyes = false; var prevInstruction=-1; function nextInstruction(){ var pc = registers[3]; if(!nosetpc){ if(prevInstruction!=-1) { pc.set(pc.value+2,false); } } else nosetpc=false; var flag_reg = registers[1]; var ir = registers[0]; var mar = registers[2]; var mdr = registers[4]; ir.set((memory[pc.value]<<8)+memory[pc.value+1]); var instruction = ir.value; var opcode = instruction >> 12; if(opcode==15){ tick=false; runLabel.innerHTML='Run (finished)'; } if(opcode==0b1011) return; prevInstruction=instruction; // glue code wait=opcode==0b1001; var mode = (instruction>>11)&0b00001; var f = (registers[0].value >> 8)&0b00000111; //input var r2 = (instruction & 0x00FF)>>5; if((opcode!=11 && opcode<=12)&&r2==0&&mode==0){ var inp = document.getElementById('input'); if(inp.value.length>0){ var setTo; if(document.getElementById('ascii').checked){ setTo = inp.value[0]; setTo = setTo.charCodeAt(0); inp.value = inp.value.substr(1); } else { setTo = parseInt(inp.value.split(" ")[0]); if(inp.value.indexOf(" ")==-1) inp.value=""; else inp.value = inp.value.substring(inp.value.indexOf(" ")+1); } blocks[3].fileRegisters[7].set(setTo,false); flag_reg.set(flag_reg.value+(1<<4)); } else { blocks[3].fileRegisters[7].set(0,false); } } // memory stores if(opcode==10){ registers[4].set(blocks[3].fileRegisters[7-f].value); } // conditional branching if(opcode==12||opcode==13){ if((actualflags+(registers[1].binary[0]=='1'?16:0)) & (1 << f)){ branchyes=(opcode==12); nosetpc=(opcode==12); } else { branchyes=(opcode==13); nosetpc=(opcode==13); } } var mode = (ir.value & 0b0000100000000000)>>11; if(opcode==0b1110) { //jmp: don't increment pc nosetpc=true; } /* if(opcode==1111 && mode==0){ // setipg ir.set((171<<8)+((pc.value+4)>>8)); } */ } var nosetpc=false; function resetAll(){ initialRun=true; prevInstruction=-1; branchyes=false; nosetpc=false; document.getElementById("output").value=""; outputBuf = []; memory = new Array(65536).fill(0); blocks.forEach(b=>b.reset()); registers.forEach(r=>r.set(0,false)); blocks[3].fileRegisters.forEach(r=>r.set(0,false)); } function drawCanvas(){ ctx.clearRect(0, 0, canvas.width, canvas.height); if(window.debugall) {;} else { wires.forEach(w=>w.update()); blocks.forEach(b=>b.update()); for(var i=0;i<8;i++){ registers[4].pins[i].write(registers[4].binary[i]); } } wires.forEach(w=>w.draw()); blocks.forEach(b=>b.draw()); registers.forEach(r=>r.draw(1)); blocks.forEach(b=>b.drawOverlay()); const flags = new Set(); tooltips.filter(tt=>{if(flags.has(tt.message)){return false};flags.add(tt.message);return true;}).forEach((t,i)=>t.draw(i)); requestAnimationFrame(drawCanvas); }; function displayOutput(){ outputBox = document.getElementById("output"); outputBox.value = "" outputBuf.forEach(value=>{ if(document.getElementById('ascii').checked) outputBox.value += String.fromCharCode(value); else document.getElementById("output").value += value + " "; }) }