cpu-simulator/js/index_old.js

1113 lines
39 KiB
JavaScript

var registers=[];
var blocks=[];
var wires=[];
var tooltips=[];
var embedded = {};
var images={};
const BORDER_THICKNESS=5.5;
var canvas, ctx;
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<pins.length;i++){
if(!reverse)
val += Math.pow(2,i) * pins[i].read();
else
val += Math.pow(2,pins.length-1 - i) * pins[i].read();
}
return val;
}
function nToHex(d){
return (d).toString(16).padStart(2, '0')
}
function humanInstruction(machineCode,specific=true){
var opcode = machineCode>>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<bits;i++){
this.pins.push(new Pin(this.x+((this.width/this.bits)/2)+(this.width/this.bits)*i,this.y+(pins=="below"?this.height:0),6));
this.pins[this.pins.length-1].number=i;
}
this.set(value,false)
}
set(value, magic=true,writebackOnly=false){
this.value=value;
if(this.name=="I/O"&&magic){
if(document.getElementById('ascii').checked)
document.getElementById("output").value += String.fromCharCode(value);
else
document.getElementById("output").value += value + " ";
}
if(this.name=="program counter" && magic||writebackOnly){
if(blocks[3])
this.value+=blocks[3].fileRegisters[4].value<<8;
} else if(this.name=="memory address register"){
if(blocks[3])
this.value+=blocks[3].fileRegisters[4].value<<8;
} else if(this.name=="m. data reg."){
}
this.binary=Register.getBits(this.value,this.bits).split("");
this.pins.forEach((pin,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;i<this.bits;i++){
// populate bits
ctx.fillStyle="black";
ctx.textAlign="center";
ctx.textBaseline="middle";
ctx.font=this.height*0.7+"px vcr_osd_monoregular";
ctx.fillText(this.binary[i],this.x+section_width*i+section_width/2,this.y+this.height*0.53);
// separators
ctx.fillStyle= i%8==0&&"black"||"lightgray"
if(i>0) {
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<registers.length;i++){
registers[i].readEnable=new Pin(registers[i].x-6,registers[i].y+13,6,"","right");
registers[i].writeEnable=new Pin(registers[i].x-6,registers[i].y+30,6,"","right");
}
blocks.push(new Decoder(13,150,350,170,[],false))
blocks.push(new Mux(450,200,200,150));
blocks[blocks.length-1].url="build/multiplexer";
//blocks.push(new Mux(530,600,360,200,true));
blocks.push(new ALU(13,345,350,450));
blocks[blocks.length-1].url="build/alu";
blocks.push(new RegisterFile(1110,10,[{name:"GP 3"},{name:"GP 2"},{name:"GP 1"},{name:"GP 0"},{name:"instr. page"},{name:"data page"},{name:"stack pointer"},{name:"I/O"}]));
blocks.push(new Clock(800,5,100,100));
blocks.push(new Mux(850,575,200,150,true));
blocks[blocks.length-1].url="build/multiplexer";
var chips=localStorage.getItem('chips');
if(chips){
c = JSON.parse(chips);
if(c["ALU"]) { blocks[2].editable=false; blocks[2].done=true; };
if(c["16:8 mux"]) { blocks[1].editable=false; blocks[1].done=true; };
if(c["16:8 mux"]) { blocks[5].editable=false; blocks[5].done=true; };
}
wireUp();
document.addEventListener('mousemove',e=>{
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;i<bytecode.length;i++){
memory[i]=bytecode[i];
}
drawMemory();
document.getElementById('label_error').classList.add('hidden');
} catch(e) {
console.error(e);
document.getElementById('label_error').classList.remove('hidden');
}
registers[3].set(0);
registers[0].set(0)//)(memory[0]<<8) + memory[1]);
registers.forEach(r=>r.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 <i>(finished)</i>';
}
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="";
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);
};