retro-static Projects

4004 Emulator


A Work in progress

I am writing this emulator for the intel 4004 microprocessor in javascript.

I have written in all the registers and structure of the processor, and I am now working on building the different instructions.
So far I have written the following instructions,

NOP - No Operation,
ADD - Add index register to accumulator with carry,
SUB - Subtract index register from accumulator with borrow,
LD 0x? - Load index register to Accumulator,
XCH 0x? - Exchange index register and accumulator,
LDM 0x? - Load Data to Accumulator,
JIN - Jump indirect,
FIN - Fetch indirect from ROM,

I have also written a simple display so the I can test each new instruction as I write it into the program.
The simulator as it is so far can be run by clicking on this link.

Here is the code I have written so far.


<!DOCTYPE HTML<
<html<
<head<
<title<intel 4004</title<
<meta charset="UTF-8"<

<style<
#BGPage{
	background-position: left top;
    background-color: BurlyWood;
    background-repeat: no-repeat; 
	background-size: cover;
	}
p {text-align: center;}
</style<

<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"<</head<

<body id="BGPage"<
<p<<strong<<font size="7" face="Geneva, Arial, Helvetica, sans-serif"<intel 4004 Processor</font<</strong<</p<
<p<This is an emulation of the intel 4004 microprocessor.</p<
<p<Press Space to RUN the program in the simulator, Press ENTER to step through the program in the simulator.</p<
<p<
    <canvas id="intel4004" width="1280" height="720"<</canvas<

  <script< 
		// declare variables	
        var canvas, context; 
		
		simRun = false;
		simStep = false;
		var calcBuffer = new Array(0,0);
		var stackPointer = new Array(0, 0, 100, 100);  // stackPointer(0) is the Program Counter, All 4 stack registers are 12 bit.
		var accumulator = 0;  // 4 bit
		var accumulatorBuffer = 0;  // 4 bit
		var indexRegister = new Array(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);  // All 16 index registers are 4 bit
		var instructionRegister = 0;  // 8 bit
		var carry =0;
		var testSignal = 0;
		
		var ROMAddressLow = 0;  // 4 bit
		var RAMAddressLow = 0;  // 4 bit
		var ROMChip = 0;	// 4 bit chip select, is first 4 bits of ROM memory address
		var ROM = new Array();
		var i = 0;
		while (i < 4096) {
  			ROM[i] = 0;	//ROM memory x 16 4001 chip (16 chips of 256 x 8 bits, ROM address is 12 bits, first 4 bits are ROM Chip select(var ROMChip)
			// & next 8 bits are 256 bytes on that chip)
  			i++;
		}
		
		// Program - 
		// NOP 0x0 - No Operation, 
		// LDM 0xa - Load Data to Accumulator, 
		// LD 0x0 - Load Index Register to Accumulator, 
		// XCH 0x1 - Exchange Index Register and Accumulator, 
		// NOP 0x0 - No Operation, 
		// LDM 0x7 - Load Data to Accumulator, 
		// XCH 0x2 - Exchange Index Register and Accumulator, 
		// LDM 0x7 - Load Data to Accumulator, 
		// ADD 0x2 - Add index register to accumulator with carry, 
		// LDM 0xa - Load Data to Accumulator, 
		// ADD 0x2 - Add index register to accumulator with carry, 
		// LDM 0x2 - Load Data to Accumulator, 
		// ADD 0x2 - Add index register to accumulator with carry,		
		// LDM 0xb - Load Data to Accumulator,
		// SUB 0x8- Subtract index register from accumulator & set carry to 0 if < 0, else set carry to 1
		// SUB 0x8- Subtract index register from accumulator & set carry to 0 if < 0, else set carry to 1
		
		// NOP 0x0 - No Operation,
		// LDM 0x6 - Load Data to Accumulator,
		// XCH 0x0 - Exchange Index Register and Accumulator, 
		// FIN 0x8 - Fetch Indirect From ROM, (loads data(0x0 & 0xb) from ROM location 0x6 & 0x7 (as read from index register pair 0x0) into index register pair 0x4),
		//			(least significant bit of OPA 0 to select FIN command or 1 to select JIN command)
		
		// NOP 0x0 - No Operation,
		// LDM 0x3 - Load Data to Accumulator,
		// XCH 0x4 - Exchange Index Register and Accumulator,
		// LDM 0xe - Load Data to Accumulator,
		// XCH 0x5 - Exchange Index Register and Accumulator,
		// JIN 0x9 - Jump Indirect, (Sets Program Counter to Address stored in Index Register 0x4 within the same ROM Page(chip)), 
		// 			(will repeat program from 4th instruction (XCH 0x1)),
		//			(least significant bit of OPA 0 to select FIN command or 1 to select JIN command)
		// NOP 0x0 - No Operation,
		// NOP 0x0 - No Operation,
		// NOP 0x0 - No Operation,
		// NOP 0x0 - No Operation,
		// NOP 0x0 - No Operation,
		// NOP 0x0 - No Operation,
		// LDM 0x0 - Load Data to Accumulator,
		// XCH 0x4 - Exchange Index Register and Accumulator,
		// LDM 0x0 - Load Data to Accumulator,
		// XCH 0x5 - Exchange Index Register and Accumulator,
		// JIN 0x9 - Jump Indirect, (jump to address 0 and restart program),
		
		 
		var programCode = new Array(0, 0, 14, 10, 10, 0, 11, 1, 0, 0, 14, 7, 11, 2, 14, 7, 8, 2, 14, 10, 8, 2, 14, 2, 8, 2, 14, 11, 9, 8, 9, 8, 0, 0, 14, 6, 11, 0, 3, 8, 
	  	0, 0, 14, 3, 11, 4, 14, 14, 11, 5, 3, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 11, 4, 14, 0, 11, 5, 3, 9);
		i = 0;
		while (i < 74) {
  			ROM[i] = programCode[i];
			i++;
		}
		
		ROM[126] = 256;	// instruction to stop program running on endlessly, resets Program Counter to 0.

		var RAM = new Array(); 
		var RAMSta = new Array();
		var RAMBank = 0;	// 3 bit bank select, is least significant of first 4 bits of ROM memory address
		i = 0;
		while (i < 2047) {
  			RAM[i] = 0;	//RAM memory is 8 banks of 4 x 4002 chips, memory = 2047 bytes, first 3 bits to select bank, and next 8 bits to select mamory location within the bank)
  			i++;		// RAM memory and  RAM memory status are located in the same chips and so the first 3 bit bank select chooses both at the same time
		}
		i = 0;
		while (i < 512) {
  			RAMSta[i] = 0;	//RAM memory status is 8 banks of 4 x 4002 chips, memory status = 512 bytes, first 3 bits to select bank, and next 6 bits to select mamory status location within the bank)
  			i++;		// RAM memory and  RAM memory status are located in the same chips and so the first 3 bit bank select chooses both at the same time
		}
		
		RAM[0] = 0;
		RAM[1] = 1;
		RAM[2] = 2;
		
		var numberString;
		var	numberLength;
		var memData = new Array();

		// Load Canvas
		canvas = document.getElementById("intel4004"); // set variable = HTML <canvas< tag above
        context = canvas.getContext("2d"); // sets the type of graphics drawn on the canvas
		
		// set up Emulator loop
		const emulatorLoop = setInterval(processorMain, 2000); // setInterval inbuilt Window Object Function calls "processorMain" function every 1000 mili seconds divided by FramesPerSecond variable
		
		// Check for key press
		document.onkeydown = stepStart;	// stepStart function
		
		function processorMain() { // define the processorMain function
			
			if (simRun == true){
			instructionRegister = stackPointer[0];
			processInstruction(ROM[instructionRegister]);
			reDraw();
			}
			
			else if (simStep == true){
			alert("Press Enter to step to next instruction");
			instructionRegister = stackPointer[0];
			processInstruction(ROM[instructionRegister]);
			reDraw();
			simStep = false;
			}
	     }
		 
		function processInstruction(instructionReg){
		
			stackPointer[0]++;
			var OPAValue = ROM[stackPointer[0]];
			
			switch (instructionReg){
				case 0:			// NOP (No Operation)
					stackPointer[0]++;
					break;
				case 3:			// FIN (Fetch indirect from ROM) & JIN (Jump indirect)
					if ((ROM[stackPointer[0]] + 2) % 2 == 0)	// Test if OPA + 2 is even, ie. OPA least significant bit = 0,
					{	// FIN (Fetch indirect from ROM)
					indexRegister[OPAValue << 1] = ROM[indexRegister[0]];
					indexRegister[(OPAValue << 1) + 1] = ROM[indexRegister[0] + 1];
					stackPointer[0]++;
					} 
					else {	// JIN (Jump indirect)
					stackPointer[0] = stackPointer[0] << 8;
					stackPointer[0] = stackPointer[0] << 4;
					stackPointer[0] = stackPointer[0] + indexRegister[OPAValue << 1];
					stackPointer[0] = stackPointer[0] << 4;
					stackPointer[0] = stackPointer[0] + indexRegister[(OPAValue << 1) + 1];
					}
					break;
				case 8:			// ADD (Add index register to accumulator with carry)
					accumulator = accumulator + indexRegister[ROM[stackPointer[0]]];
					carry = 0;
					if (accumulator < 15){
						carry = 1;
					}
					stackPointer[0]++;
					break;
				case 9:			// SUB (Subtract index register from accumulator with borrow)
					accumulator = accumulator - indexRegister[ROM[stackPointer[0]]];
					carry = 1;
					if (accumulator < 0){
						accumulator = accumulator * -1;
						carry = 0;
					}
					stackPointer[0]++;
					break;
				case 10:		// LD 0x?(Load index register to Accumulator)
					accumulator = indexRegister[ROM[stackPointer[0]]];
					stackPointer[0]++;
					break;
				case 11:		// XCH 0x?(Exchange index register and accumulator)
					accumulatorBuffer = accumulator;
					accumulator = indexRegister[ROM[stackPointer[0]]];
					indexRegister[ROM[stackPointer[0]]] = accumulatorBuffer;
					stackPointer[0]++;
					break;					
				case 14:		// LDM 0x?(Load data to Accumulator)
					accumulator = ROM[stackPointer[0]];
					stackPointer[0]++;
					break;
				case 256:		// Reset program counter to 0
					stackPointer[0] = 0;
					break;
			}
		}
		
		function printByte(binaryNumber){
	
			numberString = binaryNumber.toString(2);
			numberLength = numberString.length;
			binaryString = numberString;

			while(numberLength < 4){ 
				binaryString = "0" + binaryString;
				numberLength++;
			}
			
			return binaryString;
		 }
		 
		 function printWord(binaryNumber){
	
			numberString = binaryNumber.toString(2);
			numberLength = numberString.length;
			binaryString = numberString;
			
			while(numberLength < 8){ 
				binaryString = "0" + binaryString;
				numberLength++;
			}
			
			return binaryString;
		 }
		 
		 function printMemory(memType, memBank, memAddress, positionX, positionY){     // ROMNumber 0 to 15, ROMAddress 0 to 255
		 	i = 0;
			if(memType == "ROM"){
			while(i < 16){
				memData[i] = ROM[memAddress + i];
				i++;
			}
			context.fillText("0x" + memData[0].toString(16) + ", 0x" + memData[1].toString(16) + ", 0x" + memData[2].toString(16) + ", 0x" + memData[3].toString(16) + ", 0x" + memData[4].toString(16) + ", 0x" + memData[5].toString(16) + ", 0x" + memData[6].toString(16) + ", 0x" + memData[7].toString(16) + ", 0x" + memData[8].toString(16) + ", 0x" + memData[9].toString(16) + ", 0x" + memData[10].toString(16) + ", 0x" + memData[11].toString(16) + ", 0x" + memData[12].toString(16) + ", 0x" + memData[13].toString(16) + ", 0x" + memData[14].toString(16) + ", 0x" + memData[15].toString(16), positionX, positionY);
			}
			else if(memType == "RAM"){
			while(i < 16){
		 		memData[i] = RAM[memAddress + i];
				i++;
			}
			context.fillText("0x" + memData[0].toString(16) + ", 0x" + memData[1].toString(16) + ", 0x" + memData[2].toString(16) + ", 0x" + memData[3].toString(16) + ", 0x" + memData[4].toString(16) + ", 0x" + memData[5].toString(16) + ", 0x" + memData[6].toString(16) + ", 0x" + memData[7].toString(16) + ", 0x" + memData[8].toString(16) + ", 0x" + memData[9].toString(16) + ", 0x" + memData[10].toString(16) + ", 0x" + memData[11].toString(16) + ", 0x" + memData[12].toString(16) + ", 0x" + memData[13].toString(16) + ", 0x" + memData[14].toString(16) + ", 0x" + memData[15].toString(16), positionX, positionY);	
			context.fillText(" -   RAM Chip Number " + memNumber, 700, positionY - 25);
			}
		 }
		 
		 
		function reDraw(){	// define the reDraw function
		
		    // colour background
            context.fillStyle = "white"; // draw canvas background in white
            context.fillRect(0, 0, canvas.width, canvas.height);
			
            context.fillStyle = "black"; // draw canvas text in black
			context.font = "30px Arial";
			context.fillText("Program Counter", 10, 50);
			context.fillText(printWord(stackPointer[0]) + "   " + "0x" + stackPointer[0].toString(16), 320, 50);
			context.fillText("Stack Pointer Level 1", 20, 100);
			context.fillText(printWord(stackPointer[1]) + "   " + "0x" + stackPointer[1].toString(16), 320, 100);
			context.fillText("Stack Pointer Level 2", 20, 150);
			context.fillText(printWord(stackPointer[2]) + "   " + "0x" + stackPointer[2].toString(16), 320, 150);
			context.fillText("Stack Pointer Level 3", 20, 200);
			context.fillText(printWord(stackPointer[3]) + "   " + "0x" + stackPointer[1].toString(16), 320, 200);
			context.fillText("Accummulator", 10, 300);
			context.fillText(printByte(accumulator) + "   " + "0x" + accumulator.toString(16), 320, 300);
			context.fillText("Carry/Link", 10, 350);
			context.fillText(printByte(carry) + "   " + "0x" + carry.toString(16), 320, 350);
			
			context.fillText("Index Register 0", 650, 50);
			context.fillText(printByte(indexRegister[0]) + "   " + "0x" + indexRegister[0].toString(16), 900, 50);
			context.fillText("Index Register 1", 650, 75);
			context.fillText(printByte(indexRegister[1]) + "   " + "0x" + indexRegister[1].toString(16), 900, 75);
			context.fillText("Index Register 2", 650, 100);
			context.fillText(printByte(indexRegister[2]) + "   " + "0x" + indexRegister[2].toString(16), 900, 100);
			context.fillText("Index Register 3", 650,125);
			context.fillText(printByte(indexRegister[3]) + "   " + "0x" + indexRegister[3].toString(16), 900, 125);
			context.fillText("Index Register 4", 650, 150);
			context.fillText(printByte(indexRegister[4]) + "   " + "0x" + indexRegister[4].toString(16), 900, 150);
			context.fillText("Index Register 5", 650, 175);
			context.fillText(printByte(indexRegister[5]) + "   " + "0x" + indexRegister[5].toString(16), 900, 175);
			context.fillText("Index Register 6", 650, 200);
			context.fillText(printByte(indexRegister[6]) + "   " + "0x" + indexRegister[6].toString(16), 900, 200);
			context.fillText("Index Register 7", 650, 225);
			context.fillText(printByte(indexRegister[7]) + "   " + "0x" + indexRegister[7].toString(16), 900, 225);
			context.fillText("Index Register 8", 650, 250);
			context.fillText(printByte(indexRegister[8]) + "   " + "0x" + indexRegister[8].toString(16), 900, 250);
			context.fillText("Index Register 9", 650, 275);
			context.fillText(printByte(indexRegister[9]) + "   " + "0x" + indexRegister[9].toString(16), 900, 275);
			context.fillText("Index Register 10", 650, 300);
			context.fillText(printByte(indexRegister[10]) + "   " + "0x" + indexRegister[10].toString(16), 900, 300);
			context.fillText("Index Register 11", 650, 325);
			context.fillText(printByte(indexRegister[11]) + "   " + "0x" + indexRegister[11].toString(16), 900, 325);
			context.fillText("Index Register 12", 650, 350);
			context.fillText(printByte(indexRegister[12]) + "   " + "0x" + indexRegister[12].toString(16), 900, 350);
			context.fillText("Index Register 13", 650, 375);
			context.fillText(printByte(indexRegister[13]) + "   " + "0x" + indexRegister[13].toString(16), 900, 375);
			context.fillText("Index Register 14", 650, 400);
			context.fillText(printByte(indexRegister[14]) + "   " + "0x" + indexRegister[14].toString(16), 900, 400);
			context.fillText("Index Register 15", 650, 425);
			context.fillText(printByte(indexRegister[15]) + "   " + "0x" + indexRegister[15].toString(16), 900, 425);
			
			context.fillText("ROM memory between addresses 0x" + ROMAddressLow.toString(16) + " and 0x" + (ROMAddressLow + 15).toString(16), 10, 500);
			printMemory("ROM", 15, 0, 10, 525);
			context.fillText("RAM memory between addresses 0x" + RAMAddressLow.toString(16) + " and 0x" + (RAMAddressLow + 15).toString(16), 10, 575);
			printMemory("RAM", 0, 0, 10, 600);
		}
		
		function stepStart(KeyPressed){ 
			Keypressed = event.keyCode;
       		// Space, run program
    		if (KeyPressed.keyCode == '32') {
				simRun = true;	
  			}
			
			// Enter, step through program
  			else if (KeyPressed.keyCode == '13') {
				simStep = true;
	   		}
		}
	
	</script<
</p<
</body<
</html<