//
// Simple CPU simulator, based loosely on the architecture of the 6502
import java.awt.*;
import java.io.*;
import java.applet.*;
import java.util.*;
public class cpu_sim extends Applet
{ Button assemble, runProg, singleStep; // Buttons for the user to assemble or run the program
Button chooseFont; // Button to convert from small font to big font and vice-versa.
Button numberMode;
TextArea progText; // Text area for the user to enter the program
static final Font textFont=new Font("Helvetica", Font.PLAIN, 14);
static final int DEFAULT_INITIAL_PC = 500; // Execution starts at this address by default
static final Font bigFont = new Font("Helvetica",Font.PLAIN, 20);
static final Font smallFont = new Font("Helvetica",Font.PLAIN, 14);
// These constants are the 6502 instruction set opcodes
static final int ADC_imm = 105; // ADC immediate mode
static final int ADC_abs = 109; // ADC absolute mode
static final int ADC_zp = 101; // ADC zero page
static final int ADC_absx = 125; // ADC absolute,X
static final int ADC_absy = 121; // ADC absolute,Y
static final int BCC = 144;
static final int BCS = 176;
static final int BEQ = 240;
static final int BMI = 48;
static final int BNE = 208;
static final int BPL = 16;
static final int BRK = 0;
static final int BVC = 80;
static final int BVS = 112;
static final int CLC = 24;
static final int CLD = 216;
static final int CLI = 88;
static final int CLV = 184;
static final int CMP_abs = 205;
static final int CMP_imm = 201;
static final int CMP_zp = 197;
static final int CPX_imm = 224;
static final int CPX_abs = 236;
static final int CPX_zp = 228;
static final int CPY_imm = 192;
static final int CPY_abs = 204;
static final int CPY_zp = 196;
static final int DEX = 202;
static final int DEY = 136;
static final int INX = 232;
static final int INY = 200;
static final int JMP_abs = 76;
static final int JMP_ind = 108;
static final int JSR = 32;
static final int LDA_imm = 169; // LDA immediate mode
static final int LDA_zp = 165; // LDA zero page mode
static final int LDA_abs = 173; // LDA absolute mode
static final int LDA_absx = 189; // LDA absolute,X mode
static final int LDA_absy = 185; // LDA absolute,Y mode
static final int LDX_imm = 162; // Ditto LDX
static final int LDX_zp = 166;
static final int LDX_abs = 174;
static final int LDX_absy = 190;
static final int LDY_imm = 160; // Ditto LDY
static final int LDY_zp = 164;
static final int LDY_abs = 172;
static final int LDY_absx = 188;
static final int NOP = 234;
static final int PHA = 72;
static final int PHP = 8;
static final int PLA = 104;
static final int PLP = 40;
static final int RTS = 96;
static final int SBC_imm = 233;
static final int SBC_zp = 229;
static final int SBC_abs = 237;
static final int SBC_absx = 253;
static final int SBC_absy = 249;
static final int SEC = 54;
static final int SED = 251;
static final int SEI = 120;
static final int STA_zp = 133;
static final int STA_abs = 141;
static final int STA_absx = 149;
static final int STA_absy = 153;
static final int STX_zp = 134;
static final int STX_abs = 142;
static final int STX_absy = 150;
static final int STY_zp = 132;
static final int STY_abs = 140;
static final int STY_absx = 148;
static final int TAX = 170;
static final int TAY = 177;
static final int TXA = 138;
static final int TYA = 152;
static final int CARRY = 0; // Status flags
static final int DECIMAL = 1;
static final int OVERFLOW = 2;
static final int INTERRUPT = 3;
static final int NEGATIVE = 4;
static final int ZERO = 5;
static final int UNKNOWN = -1;
static final int IMMEDIATE = 0; // Addressing modes
static final int ZEROPAGE = 1;
static final int ABSOLUTE = 2;
static final int INDEX_X = 3; // Equivalent to (value,X)
static final int INDEX_Y = 4; // Equivalent to (value),Y
static final int ACCUM = 5; // Accumulator mode (specified by 'A')
static final int ABS_X = 6; // Equivalent to value,X
static final int ABS_Y = 7; // Equivalent to value,Y
static final int ZP_X = 8; // Same as ABS_X and ABS_Y except for zero page
static final int ZP_Y = 9;
static final int INDIRECT = 10; // Same as (value)
static final int MAXMEM = 65536;
static final int MAXLABELS = 100;
static final int BADNUMBER = 1; // Error messages
static final int OUTOFRANGE = 2;
static final int TOOMANYLABELS = 3;
static final int DUPLICATELABEL = 4;
static final int WRONGADDRESSINGMODE = 5;
int A = 0, X = 0, Y = 0, progCounter = DEFAULT_INITIAL_PC; // Here are the register values
int stack = 255; // Stack register counts downwards
int stack_base = 254; // High byte of stack pointer (&FE)
int maxAssembledAddress = 0; // Set to high water mark of assembled code
int memory[] = new int[MAXMEM];
int flags[] = new int[8];
boolean hexMode = false; // If set to true, then display all numbers in hex
int memoryDisplayOffset = DEFAULT_INITIAL_PC; // The first memory slot displayed on screen
int zpDisplayOffset = 0; // Ditto for the zero page displayed
boolean singleStepMode = false; // When true, indicates program being single stepped
int assemblyError = 0; // Set to a non-zero value to indicate an error
int assemblyPass = 0; // Two-pass assembler implemented
int addressingMode = 0;
int initialPCvalue = DEFAULT_INITIAL_PC; // Default place where code is assembled to
int runFromHere = initialPCvalue; // First instruction to run from
int startSpecified = -1; // The last word in where to run the program
boolean first_ever_instruction;
boolean font_is_big = false; // Set to true when big font selected
String label[] = new String[MAXLABELS]; // Used to store named locations
String assemblyErrorString = ""; // Used to report error messages during assembly
int labelAddress[] = new int[MAXLABELS]; // Holds the addresses corresponding to the locations
int numLabels = 0; // How many labels are stored in the array
public void init ()
{ int i;
setBackground(Color.cyan);
setFont(textFont);
assemble = new Button("Assemble");
add(assemble);
runProg = new Button("Run");
add(runProg);
singleStep = new Button("Single Step");
add(singleStep);
numberMode = new Button("Hex mode");
add(numberMode);
chooseFont = new Button(" Big font ");
add(chooseFont);
progText = new TextArea(10,50);
add(progText);
// Initialise memory
for (i = 0; i < MAXMEM; i++)
memory[i] = 0;
// Initialise flags to zero
for (i = 0; i < 8; i++)
flags[i] = 0;
// Initialise all labels to 'blank'
for (i = 0; i < MAXLABELS; i++)
{ label[i] = "";
labelAddress[i] = 0;
}
}
public void paint (Graphics g)
{ // Draw the processor with its three registers, and its program counter
g.drawRect(8,220,71,150);
g.drawString("A",15,238);
box(g, 30, 225, A);
g.drawString("X",15,262);
box(g, 30, 248, X);
g.drawString("Y",15,286);
box(g, 30, 271, Y);
g.drawString("SP",10,308);
box(g, 30, 294, stack);
g.drawString("PC",10,331);
box(g, 30, 317, progCounter);
drawMemory(g);
drawZeroPage(g);
drawFlags(g);
g.drawString(assemblyErrorString, 300, 300);
}
// Draw a number with a box round it
public void box (Graphics g, int x, int y, int value)
{ g.drawRect(x, y, 45, 17);
g.drawString(num(value), x+2, y+13);
}
// Draw a section of memory in the lower half of the screen
public void drawMemory (Graphics g)
{ for (int i = 0; i < 10; i++)
{ int loc = i + memoryDisplayOffset; // The memory location being displayed
if (loc == progCounter) // The memory location indicated by the pc is shown in red
g.setColor(Color.red);
else
g.setColor(Color.black);
g.drawString(hex4(loc),100,234 + 17 * i);
box(g, 137, 220 + 17 * i, memory[loc]);
}
g.setColor(Color.black); // Just in case
}
// Draw the first 20 locations of zero page memory
public void drawZeroPage (Graphics g)
{ for (int i = 0; i < 10; i++)
{ int loc = i + zpDisplayOffset; // The memory location being displayed
if (loc == progCounter) // The memory location indicated by the pc is shown in red
g.setColor(Color.red);
else
g.setColor(Color.black);
g.drawString(hex4(loc),200,234 + 17 * i);
box(g, 237, 220 + 17 * i, memory[loc]);
}
g.setColor(Color.black); // Just in case
g.drawRect(284,220, 10,10); // Draw icon indicating "move window up"
g.drawLine(284, 230, 289, 220);
g.drawLine(289, 220, 294, 230);
g.drawRect(284, 380, 10, 10);
g.drawLine(284, 380, 289, 390);
g.drawLine(289, 390, 294, 380);
}
// Draw the flags
public void drawFlags (Graphics g)
{ String s = "CDVINZ";
for (int i = 0; i < 6; i++)
{ if (i == 3)
g.drawString("I",48, 350);
else
g.drawString("" + s.charAt(i),15 + 10 * i, 350);
g.drawString("" + flags[i],15 + 10 * i, 365);
}
}
// Return a number as a string in either decimal or hexadecimal mode
public String num (int x)
{ if (hexMode == false)
{ return Integer.toString(x);
}
else
{ if (x > 255)
return "&" + hex4(x);
else
return ("&" + hexDigit(x / 16) + hexDigit(x % 16));
}
}
// Turn a simple number into a four-digit hex number
public String hex4 (int x)
{ String s = hexDigit(x / (16*16*16));
x %= 16 * 16 * 16;
s = s + hexDigit(x / (16 * 16));
x %= 16 * 16;
return s + hexDigit(x / 16) + hexDigit(x % 16);
}
// Turn a single number in the range 0..15 into a hexadecimal digit
public String hexDigit (int digit)
{ String s = "";
switch (digit) // Yes, I know it's inefficient but I can't find any other way!
{ case 0 : s = "0"; break;
case 1 : s = "1"; break;
case 2 : s = "2"; break;
case 3 : s = "3"; break;
case 4 : s = "4"; break;
case 5 : s = "5"; break;
case 6 : s = "6"; break;
case 7 : s = "7"; break;
case 8 : s = "8"; break;
case 9 : s = "9"; break;
case 10 : s = "A"; break;
case 11 : s = "B"; break;
case 12 : s = "C"; break;
case 13 : s = "D"; break;
case 14 : s = "E"; break;
case 15 : s = "F";
}
return s;
}
// Process the button clicks
public boolean action (Event e, Object arg)
{ if (e.target == numberMode)
{ hexMode = !hexMode;
if (hexMode == true)
numberMode.setLabel("Dec mode");
else
numberMode.setLabel("Hex mode");
repaint();
}
if (e.target == assemble)
{ assemblyError = 0; // 0 indicates no error
assemblyErrorString = "";
numLabels = 0;
startSpecified = -1;
for (assemblyPass = 1; assemblyPass <= 2; assemblyPass++)
if (assemblyError == 0)
assemble(assemblyPass);
repaint();
}
if (e.target == singleStep && singleStepMode == true)
singleStep();
if (e.target == runProg)
runProgram();
if (e.target == chooseFont)
{ if (font_is_big == true)
{ font_is_big = false;
progText.setFont(smallFont);
chooseFont.setLabel("Big font");
}
else
{ font_is_big = true;
progText.setFont(bigFont);
chooseFont.setLabel("Small font");
}
repaint();
}
return true;
}
// Handle general mouse clicks somewhere on the screen
public boolean mouseDown (Event event, int x, int y)
{ if (x > 284 && x < 294 && y > 220 && y < 230 && zpDisplayOffset > 0)
{ zpDisplayOffset--;
repaint();
}
if (x > 284 && x < 294 && y > 380 && y < 390 && zpDisplayOffset < 65525)
{ zpDisplayOffset++;
repaint();
}
return true;
}
// Assemble the code. The assembly pass is necessary to determine if labels are missing
public void assemble (int assemblyPass)
{ String s = progText.getText(); // Copy the text into 's'
StringTokenizer t = new StringTokenizer(s," \n"); // Splits text into tokens separated by spaces
progCounter = initialPCvalue;
first_ever_instruction = true;
memoryDisplayOffset = progCounter;
while (t.hasMoreTokens() && assemblyError == 0)
{ String s2 = t.nextToken();
s2 = s2.toUpperCase();
if (s2.charAt(0) == '.' && assemblyPass == 1) // Store a label
storeLabel(s2.substring(1,s2.length()));
if (s2.compareTo("ADC") == 0) // Assemble ADC
assembleAdd(t.nextToken());
if (s2.compareTo("BCC") == 0) // Assemble BCC
assembleBranch(t.nextToken(), BCC);
if (s2.compareTo("BCS") == 0) // Assemble BCS
assembleBranch(t.nextToken(), BCS);
if (s2.compareTo("BEQ") == 0) // Assemble BEQ
assembleBranch(t.nextToken(), BEQ);
if (s2.compareTo("BNE") == 0) // Assemble BNE
assembleBranch(t.nextToken(), BNE);
if (s2.compareTo("BRK") == 0) // Assemble BRK
store(BRK);
if (s2.compareTo("BVC") == 0) // Assemble BVC
assembleBranch(t.nextToken(), BVC);
if (s2.compareTo("BVS") == 0) // Assemble BVS
assembleBranch(t.nextToken(), BVS);
if (s2.compareTo("CLC") == 0) // Assemble CLC
store(CLC);
if (s2.compareTo("CLD") == 0) // Assemble CLD
store(CLD);
if (s2.compareTo("CLI") == 0) // Assemble CLI
store(CLI);
if (s2.compareTo("CLV") == 0) // Assemble CLV
store(CLV);
if (s2.compareTo("CMP") == 0) // Assemble CMP
assembleCompare(t.nextToken());
if (s2.compareTo("CPX") == 0) // Assemble CPX
assembleCompareXorY('X',t.nextToken());
if (s2.compareTo("CPY") == 0) // Assemble CPY
assembleCompareXorY('Y',t.nextToken());
if (s2.compareTo("DEX") == 0) // Assemble DEX
store(DEX);
if (s2.compareTo("DEY") == 0) // Assemble DEY
store(DEY);
if (s2.compareTo("EQUB") == 0) // Assemble EQUB (byte value)
store(getNumber(t.nextToken()));
if (s2.compareTo("EQUS") == 0) // Assemble EQUS (string)
storeString(t.nextToken());
if (s2.compareTo("EQUW") == 0) // Assemble EQUW (word value)
{ int val = getNumber(t.nextToken());
store(val % 256); // Low byte
store(val / 256); // High byte
}
if (s2.compareTo("INX") == 0) // Assemble INX
store(INX);
if (s2.compareTo("INY") == 0) // Assemble INY
store(INY);
if (s2.compareTo("JMP") == 0) // Assemble JMP
assembleJump(t.nextToken());
if (s2.compareTo("JSR") == 0) // Assemble JSR
assembleJSR(t.nextToken());
if (s2.compareTo("LDA") == 0)
assembleLoad(t.nextToken(),'A');
if (s2.compareTo("LDX") == 0)
assembleLoad(t.nextToken(),'X');
if (s2.compareTo("LDY") == 0)
assembleLoad(t.nextToken(),'Y');
if (s2.compareTo("NOP") == 0) // Assemble NOP
store(NOP);
if (s2.compareTo("PHA") == 0) // Assemble PHA
store(PHA);
if (s2.compareTo("PHP") == 0) // Assemble PHP
store(PHP);
if (s2.compareTo("PLA") == 0) // Assemble PLA
store(PLA);
if (s2.compareTo("PLP") == 0) // Assemble PLP
store(PLP);
if (s2.compareTo("RTS") == 0) // Assemble RTS
store(RTS);
if (s2.compareTo("PC") == 0)
{ int newInitialAddress = getNumber(t.nextToken());
if (assemblyError == 0)
{ if (first_ever_instruction) // Affects starts position only if this is the first instruc
{ initialPCvalue = newInitialAddress;
if (startSpecified == -1)
runFromHere = newInitialAddress;
}
memoryDisplayOffset = newInitialAddress;
progCounter = newInitialAddress;
}
}
if (s2.compareTo("SBC") == 0) // Assemble SBC
assembleSubtract(t.nextToken());
if (s2.compareTo("SEC") == 0) // Assemble SEC
store(SEC);
if (s2.compareTo("SED") == 0) // Assemble SED
store(SED);
if (s2.compareTo("SEI") == 0) // Assemble SEI
store(SEI);
if (s2.compareTo("STA") == 0)
assembleStore(t.nextToken(),'A');
if (s2.compareTo("STARTAT") == 0) // Directive telling processor where to start
{ int newInitialAddress = getNumber(t.nextToken()); // executing code
if (assemblyError == 0)
{ runFromHere = newInitialAddress;
startSpecified = newInitialAddress;
}
}
if (s2.compareTo("STX") == 0)
assembleStore(t.nextToken(),'X');
if (s2.compareTo("STY") == 0)
assembleStore(t.nextToken(),'Y');
if (s2.compareTo("TAX") == 0) // Assemble TAX
store(TAX);
if (s2.compareTo("TAY") == 0) // Assemble TAY
store(TAY);
if (s2.compareTo("TXA") == 0) // Assemble TXA
store(TXA);
if (s2.compareTo("TYA") == 0) // Assemble TYA
store(TYA);
if (progCounter != initialPCvalue) // Detects whether an instruction has been assembled
first_ever_instruction = false;
}
if (assemblyError != 0)
reportError();
maxAssembledAddress = progCounter; // Store high water mark of assembled addresses
if (startSpecified != -1) // Ready for program to run
progCounter = startSpecified;
else
progCounter = runFromHere;
singleStepMode = true; // Indicates program can be stepped through
repaint();
}
// Store a label in the defined list
public void storeLabel (String lab)
{ lab = lab.toUpperCase(); // Just in case
if (findLabel(lab) == -1) // Label not present
{ if (numLabels == MAXLABELS)
assemblyError = TOOMANYLABELS;
else
{ label[numLabels] = lab;
labelAddress[numLabels] = progCounter;
numLabels++;
}
}
else
assemblyError = DUPLICATELABEL;
}
// find whether a label is present in the stored label list and to return the corresponding address
// or -1 if the label is not present
public int findLabel (String lab)
{ int address = -1; // default value
lab = lab.toUpperCase(); // Just in case
if (numLabels > 0)
for (int i = 0; i < numLabels; i++)
if (lab.compareTo(label[i]) == 0)
address = labelAddress[i];
// If found the label, assume that the mode is absolute
if (address != -1)
addressingMode = ABSOLUTE;
return address;
}
// Assemble the instruction ADC
public void assembleAdd (String operand)
{ int operandNumber = decodeOperand(operand);
if (assemblyError == 0)
switch(addressingMode)
{ case IMMEDIATE : store(ADC_imm);
store(operandNumber); break;
case ZEROPAGE : store(ADC_zp);
store(operandNumber); break;
case ABSOLUTE : store(ADC_abs);
store(operandNumber % 256); // Store lower byte of operand
store(operandNumber / 256); // Store upper byte
break;
case ABS_X : store(ADC_absx);
store(operandNumber % 256); // Store lower byte of operand
store(operandNumber / 256); // Store upper byte
break;
case ABS_Y : store(ADC_absy);
store(operandNumber % 256); // Store lower byte of operand
store(operandNumber / 256); // Store upper byte
break;
default : assemblyError = WRONGADDRESSINGMODE;
}
}
// Assemble the JMP instruction. Must be followed either by absolute address or indirect
public void assembleJump (String operand)
{ int operandAddress = decodeOperand(operand);
if (addressingMode == ABSOLUTE || addressingMode == ZEROPAGE)
{ store(JMP_abs);
store(operandAddress % 256); // Low byte
store(operandAddress / 256); // high byte
}
else if (addressingMode == INDIRECT)
{ store(JMP_ind);
store(operandAddress % 256); // Low byte
store(operandAddress / 256); // high byte
}
else
{ if (assemblyPass == 2)
assemblyError = WRONGADDRESSINGMODE;
else
{ // If assembly mode = 1, then store three empty bytes to mark the position
store(0); // These will be replaced by proper values on pass 2
store(0);
store(0);
}
}
}
// Assemble the JSR instruction. Must be followed by absolute address
public void assembleJSR (String operand)
{ int operandAddress = decodeOperand(operand);
if (addressingMode == ABSOLUTE || addressingMode == ZEROPAGE)
{ store(JSR);
store(operandAddress % 256); // Low byte
store(operandAddress / 256); // high byte
}
else
{ if (assemblyPass == 2)
assemblyError = WRONGADDRESSINGMODE;
else
{ // If assembly mode = 1, then store three empty bytes to mark the position
store(0); // These will be replaced by proper values on pass 2
store(0);
store(0);
}
}
}
// Assemble the Bxx (i.e. Branch on condition) instruction. The exact op-code is passed
public void assembleBranch (String operand, int opCode)
{ int operandAddress = decodeOperand(operand);
int offset = 0;
store(opCode); // Store the op code for this particular instruction
// If the operand was a label which happens to be in zero page, calculate the offset as per normal
if (findLabel(operand) != -1 && addressingMode == ZEROPAGE)
addressingMode = ABSOLUTE; // Treat it as if it were not zero page
// On the first pass, the labels may not have been defined, so store offset 0
if (assemblyPass == 1)
store(0);
else
switch (addressingMode)
{ case ABSOLUTE : offset = operandAddress - (progCounter + 1);
if (offset >= 0 && offset <= 127)
store(offset);
else
if (offset < 0 && offset >= -128)
store(256 + offset);
else
assemblyError = OUTOFRANGE;
break;
case ZEROPAGE : store(operandAddress); // A simple number after the Bxx command appears
break; // the same as a zero page number
default : assemblyError = WRONGADDRESSINGMODE;
}
}
public void assembleLoad (String operand, char register)
{ int operandNumber = decodeOperand(operand);
if (assemblyError == 0)
switch(addressingMode)
{ case IMMEDIATE : switch (register)
{ case 'A' : store(LDA_imm); break;
case 'X' : store(LDX_imm); break;
case 'Y' : store(LDY_imm);
}
store(operandNumber); break;
case ZEROPAGE : switch(register)
{ case 'A' : store(LDA_zp); break;
case 'X' : store(LDX_zp); break;
case 'Y' : store(LDY_zp);
}
store(operandNumber); break;
case ABSOLUTE : switch(register)
{ case 'A' : store(LDA_abs); break;
case 'X' : store(LDX_abs); break;
case 'Y' : store(LDY_abs);
}
store(operandNumber % 256); // Store lower byte of operand
store(operandNumber / 256); // Store upper byte
break;
case ABS_X : switch(register)
{ case 'A' : store(LDA_absx); break;
case 'X' : assemblyError = WRONGADDRESSINGMODE; break;
case 'Y' : store(LDY_absx);
}
store(operandNumber % 256); // Store lower byte of operand
store(operandNumber / 256); // Store upper byte
break;
case ABS_Y : switch(register)
{ case 'A' : store(LDA_absy); break;
case 'X' : store(LDX_absy); break;
case 'Y' : assemblyError = WRONGADDRESSINGMODE;
}
store(operandNumber % 256); // Store lower byte of operand
store(operandNumber / 256); // Store upper byte
break;
}
}
// Assemble the instruction SBC
public void assembleSubtract (String operand)
{ int operandNumber = decodeOperand(operand);
if (assemblyError == 0)
switch(addressingMode)
{ case IMMEDIATE : store(SBC_imm);
store(operandNumber); break;
case ZEROPAGE : store(SBC_zp);
store(operandNumber); break;
case ABSOLUTE : store(SBC_abs);
store(operandNumber % 256); // Store lower byte of operand
store(operandNumber / 256); // Store upper byte
break;
case ABS_X : store(SBC_absx);
store(operandNumber % 256); // Store lower byte of operand
store(operandNumber / 256); // Store upper byte
break;
case ABS_Y : store(SBC_absy);
store(operandNumber % 256); // Store lower byte of operand
store(operandNumber / 256); // Store upper byte
break;
default : assemblyError = WRONGADDRESSINGMODE;
}
}
// Assemble the instruction CMP
public void assembleCompare (String operand)
{ int operandNumber = decodeOperand(operand);
if (assemblyError == 0)
switch(addressingMode)
{ case IMMEDIATE : store(CMP_imm);
store(operandNumber); break;
case ZEROPAGE : store(CMP_zp);
store(operandNumber); break;
case ABSOLUTE : store(CMP_abs);
store(operandNumber % 256); // Store lower byte of operand
store(operandNumber / 256); // Store upper byte
break;
}
}
// Assemble the instruction CPX and CPY
public void assembleCompareXorY (char register, String operand)
{ int operandNumber = decodeOperand(operand);
if (assemblyError == 0)
switch(addressingMode)
{ case IMMEDIATE : if (register == 'X')
store(CPX_imm);
else
store(CPY_imm);
store(operandNumber);
break;
case ZEROPAGE : if (register == 'X')
store(CPX_zp);
else
store(CPY_zp);
store(operandNumber);
break;
case ABSOLUTE : if (register == 'X')
store(CPX_abs);
else
store(CPY_abs);
store(operandNumber % 256); // Store lower byte of operand
store(operandNumber / 256); // Store upper byte
break;
default : assemblyError = WRONGADDRESSINGMODE;
}
}
// Assemble the instruction STA, STX or STY (register specified as 'A', 'X' or 'Y')
public void assembleStore (String operand, char register)
{ int operandNumber = decodeOperand(operand);
if (assemblyError == 0)
switch(addressingMode)
{ case ZEROPAGE : switch(register)
{ case 'A' : store(STA_zp); break;
case 'X' : store(STX_zp); break;
case 'Y' : store(STY_zp);
}
store(operandNumber); break;
case ABSOLUTE : switch(register)
{ case 'A' : store(STA_abs); break;
case 'X' : store(STX_abs); break;
case 'Y' : store(STY_abs);
}
store(operandNumber % 256); // Store lower byte of operand
store(operandNumber / 256); // Store upper byte
break;
case ABS_X : switch (register)
{ case 'A' : store(STA_absx); break;
case 'X' : assemblyError = WRONGADDRESSINGMODE; break;
case 'Y' : store(STY_absx);
}
store(operandNumber % 256); // Store lower byte of operand
store(operandNumber / 256); // Store upper byte
break;
case ABS_Y : switch(register)
{ case 'A' : store(STA_absy); break;
case 'X' : store(STX_absy); break;
case 'Y' : assemblyError = WRONGADDRESSINGMODE;
}
store(operandNumber % 256); // Store lower byte of operand
store(operandNumber / 256); // Store upper byte
break;
default : assemblyError = WRONGADDRESSINGMODE;
}
}
// Store a single byte value in memory at the address specified by the program counter
public void store (int x)
{ memory[progCounter] = x;
progCounter++;
}
// Store a string as a sequence of bytes in ascending memory locations
// At the moment the string cannot contain spaces as it is extracted using nextToken()
// but needn't be contained within quotation marks
public void storeString (String s)
{ for (int count = 0; count < s.length(); count++)
{ char c = s.charAt(count);
memory[progCounter] = c;
progCounter++;
}
}
// Get a number parameter (e.g. the #0 part of LDA #0)
// If an unrecognised parameter is found, then assemblyError is set to 'unrecognised parameter'
public int decodeOperand (String token)
{ token = token.toUpperCase();
int value = 0;
addressingMode = UNKNOWN;
if (token.charAt(0) == '#')
{ addressingMode = IMMEDIATE;
value = getNumber(token.substring(1,token.length()));
if (assemblyError == 0 && (value < 0 || value > 255))
assemblyError = OUTOFRANGE;
}
// Detect special addressing modes here!
if (token == "A")
{ addressingMode = ACCUM; // "A" indicates accumulator mode
value = 0;
}
// Detect indexed X addressing mode, i.e. (value,X)
if (token.charAt(0) == '(' && token.endsWith(",X)"))
{ int endPos = token.indexOf(",X)");
value = getNumber(token.substring(1,endPos));
if (assemblyError == 0 && assemblyPass == 2 && (value < 0 || value > 65535))
assemblyError = OUTOFRANGE;
if (assemblyError == 0)
addressingMode = INDEX_X;
}
// Detect indexed Y addressing mode, i.e. (value),Y
if (token.charAt(0) == '(' && token.endsWith("),Y"))
{ int endPos = token.indexOf("),Y");
value = getNumber(token.substring(1,endPos));
if (assemblyError == 0 && assemblyPass == 2 && (value < 0 || value > 65535))
assemblyError = OUTOFRANGE;
if (assemblyError == 0)
addressingMode = INDEX_Y;
}
// Now detect indirect mode, i.e. (value)
if (assemblyError == 0 && addressingMode == UNKNOWN &&
token.charAt(0) == '(' && token.endsWith(")"))
{ value = getNumber(token.substring(1,token.length() - 1));
if (assemblyError == 0 && assemblyPass == 2 && (value < 0 || value > 65535))
assemblyError = OUTOFRANGE;
if (assemblyError == 0)
addressingMode = INDIRECT;
}
// Detect absolute X addressing mode, i.e. value,X
if (addressingMode == UNKNOWN && token.endsWith(",X"))
{ int endPos = token.indexOf(",X");
value = getNumber(token.substring(0,endPos));
if (assemblyError == 0 && assemblyPass == 2 && (value < 0 || value > 65535))
assemblyError = OUTOFRANGE;
if (assemblyError == 0)
addressingMode = ABS_X;
}
// Detect indexed Y addressing mode, i.e. value,Y
// Not confused with index Y mode as tests for an unknown addressing mode
if (addressingMode == UNKNOWN && token.endsWith(",Y"))
{ int endPos = token.indexOf(",Y");
value = getNumber(token.substring(0,endPos));
if (assemblyError == 0 && assemblyPass == 2 && (value < 0 || value > 65535))
assemblyError = OUTOFRANGE;
if (assemblyError == 0)
addressingMode = ABS_Y;
}
// If addressing mode is still unknown, then it must be a simple number, i.e. zero page or absolute
// Only flag an error in an 'improper' number if on the second pass (could be an unencountered label)
if (addressingMode == UNKNOWN && assemblyError == 0)
{ value = getNumber(token);
if (assemblyError == 0 && assemblyPass == 2 && (value < 0 || value > 65535))
assemblyError = OUTOFRANGE;
if (assemblyError == 0 && addressingMode == UNKNOWN) // Addressing mode may have been
{ if (value < 256) // set by findLabel()
addressingMode = ZEROPAGE;
else
addressingMode = ABSOLUTE;
}
// If the token starts with a letter or underscore then it is definitely a label.
// Assume absolute addressing if this it's a label we haven't come across.
if (token_is_label(token) == true && findLabel(token) == -1)
addressingMode = ABSOLUTE;
}
return value;
}
// Determine whether a string starts with a letter (of either case)
public boolean token_is_label (String s)
{ boolean temp = false;
s = s.toUpperCase(); // No permanent damage done
// I can't think of a better way of doing the following!
if (s.startsWith("A") || s.startsWith("B") || s.startsWith("C") || s.startsWith("D") ||
s.startsWith("E") || s.startsWith("F") || s.startsWith("G") || s.startsWith("H") ||
s.startsWith("I") || s.startsWith("J") || s.startsWith("K") || s.startsWith("L") ||
s.startsWith("M") || s.startsWith("N") || s.startsWith("O") || s.startsWith("P") ||
s.startsWith("Q") || s.startsWith("R") || s.startsWith("S") || s.startsWith("T") ||
s.startsWith("U") || s.startsWith("V") || s.startsWith("W") || s.startsWith("X") ||
s.startsWith("Y") || s.startsWith("Z") == true)
temp = true;
return temp;
}
// Turn a token into a number - it should detect for & indicating a hex number
public int getNumber (String s)
{ int value = 0, i;
int temp = findLabel(s); // Firstly, detect if the operand is a stored named location
if (temp > -1)
return temp;
// If we get here, then it is not a stored label
int startingPosition = 0, multiplier = 10;
if (s.charAt(0) == '&')
{ startingPosition = 1; // Ignore & from now on
multiplier = 16;
}
for (i = startingPosition; i < s.length(); i++)
{ int x = getDig(s.charAt(i));
if (x < 0 && assemblyPass == 2) // Only register errors in numbers on second pass
assemblyError = BADNUMBER; // as it might be an unrecognised label.
else
value = multiplier * value + x;
}
return value;
}
// Turn a character into a value (I can't seem to find a 'code' function!)
public int getDig (char c)
{ int value = 0;
switch (c)
{ case '0' : value = 0; break;
case '1' : value = 1; break;
case '2' : value = 2; break;
case '3' : value = 3; break;
case '4' : value = 4; break;
case '5' : value = 5; break;
case '6' : value = 6; break;
case '7' : value = 7; break;
case '8' : value = 8; break;
case '9' : value = 9; break;
case 'A' : value = 10; break;
case 'B' : value = 11; break;
case 'C' : value = 12; break;
case 'D' : value = 13; break;
case 'E' : value = 14; break;
case 'F' : value = 15; break;
default : value = -1; // Indicates error!
}
return value;
}
// Report an assembly error
public void reportError ()
{ String s = "";
switch (assemblyError)
{ case BADNUMBER : s = "A number in an invalid format."; break;
case OUTOFRANGE : s = "A number which is out of range."; break;
case TOOMANYLABELS : s = "You have defined too many labels."; break;
case DUPLICATELABEL : s = "You have defined a label twice."; break;
case WRONGADDRESSINGMODE : s = "Illegal addressing mode."; break;
}
assemblyErrorString = s;
}
// Single step through the program
public void singleStep ()
{ singleStepMode = true;
int opCode = memory[progCounter]; // Extract the opcode from memory
switch (opCode)
{ case ADC_imm : add(IMMEDIATE); break;
case ADC_zp : add(ZEROPAGE); break;
case ADC_abs : add(ABSOLUTE); break;
case ADC_absx : add(ABS_X); break;
case ADC_absy : add(ABS_Y); break;
case BCC : branch(CARRY, 0); break;
case BCS : branch(CARRY, 1); break;
case BEQ : branch(ZERO, 1); break;
case BMI : branch(NEGATIVE, 1); break;
case BNE : branch(ZERO, 0); break;
case BPL : branch(NEGATIVE, 0); break;
case BRK : singleStepMode = false; // Indicates overflowed off end of program, so stop running
case BVC : branch(OVERFLOW, 0); break;
case BVS : branch(OVERFLOW, 1); break;
case CLC : flags[CARRY] = 0; advance(); break;
case CLD : flags[DECIMAL] = 0; advance(); break;
case CLI : flags[INTERRUPT] = 0; advance(); break;
case CLV : flags[OVERFLOW] = 0; advance(); break;
case CMP_abs : compare('A',ABSOLUTE); break;
case CMP_imm : compare('A',IMMEDIATE); break;
case CMP_zp : compare('A',ZEROPAGE); break;
case CPX_imm : compare('X',IMMEDIATE); break;
case CPX_zp : compare('X',ZEROPAGE); break;
case CPX_abs : compare('X',ABSOLUTE); break;
case CPY_imm : compare('Y',IMMEDIATE); break;
case CPY_zp : compare('Y',ZEROPAGE); break;
case CPY_abs : compare('Y',ABSOLUTE); break;
case DEX : inx_or_dex(255); break;
case DEY : iny_or_dey(255); break;
case INX : inx_or_dex(1); break;
case INY : iny_or_dey(1); break;
case JMP_abs : advance();
progCounter = getOperand(ABSOLUTE);
memoryDisplayOffset = progCounter; // Display at correct location
break;
case JMP_ind : advance();
progCounter = getOperand(INDIRECT);
memoryDisplayOffset = progCounter; // Display at correct location
break;
case JSR : jump_to_subroutine(); break;
case LDA_imm : loadRegister('A',IMMEDIATE); break;
case LDA_zp : loadRegister('A',ZEROPAGE); break;
case LDA_abs : loadRegister('A',ABSOLUTE); break;
case LDA_absx : loadRegister('A',ABS_X); break;
case LDA_absy : loadRegister('A',ABS_Y); break;
case LDX_imm : loadRegister('X',IMMEDIATE); break;
case LDX_zp : loadRegister('X',ZEROPAGE); break;
case LDX_abs : loadRegister('X',ABSOLUTE); break;
case LDX_absy : loadRegister('X',ABS_Y); break;
case LDY_imm : loadRegister('Y',IMMEDIATE); break;
case LDY_zp : loadRegister('Y',ZEROPAGE); break;
case LDY_abs : loadRegister('Y',ABSOLUTE); break;
case LDY_absx : loadRegister('Y',ABS_X); break;
case NOP : advance(); break;
case PHA : push(A);
advance();
break;
case PHP : push_flags_register(); break;
case PLA : A = pop();
advance();
break;
case PLP : pop_flags_register(); break;
case RTS : return_from_subroutine(); break;
case SBC_imm : subtract(IMMEDIATE); break;
case SBC_zp : subtract(ZEROPAGE); break;
case SBC_abs : subtract(ABSOLUTE); break;
case SBC_absx : subtract(ABS_X); break;
case SBC_absy : subtract(ABS_Y); break;
case SEC : flags[CARRY] = 1; advance(); break;
case SED : flags[DECIMAL] = 1; advance(); break;
case SEI : flags[INTERRUPT] = 1; advance(); break;
case STA_zp : storeRegister('A',ZEROPAGE); break;
case STA_abs : storeRegister('A',ABSOLUTE); break;
case STA_absx : storeRegister('A',ABS_X); break;
case STA_absy : storeRegister('A',ABS_Y); break;
case STX_zp : storeRegister('X',ZEROPAGE); break;
case STX_abs : storeRegister('X',ABSOLUTE); break;
case STX_absy : storeRegister('X',ABS_Y); break;
case STY_zp : storeRegister('Y',ZEROPAGE); break;
case STY_abs : storeRegister('Y',ABSOLUTE); break;
case STY_absx : storeRegister('Y',ABS_Y); break;
case TAX : X = A; advance(); break;
case TAY : Y = A; advance(); break;
case TXA : A = X; advance(); break;
case TYA : A = Y; advance(); break;
}
repaint();
}
public void runProgram ()
{ progCounter = initialPCvalue;
do
{ singleStep();
}
while (singleStepMode == true);
}
// Perform a relative branch if a given flag is a given value (1 or 0)
public void branch (int flag_to_test, int one_or_zero)
{ advance(); // Get the next byte of memory
int offset = getOperand(IMMEDIATE); // getOperand also moves to byte after operand!
if (flags[flag_to_test] == one_or_zero) // Do the branch if the flag has specified value
{ if (offset < 128) // Represents a leap forward
progCounter += offset;
else
progCounter -= 256 - offset;
if (progCounter < memoryDisplayOffset || progCounter > memoryDisplayOffset + 9)
memoryDisplayOffset = progCounter; // Display at correct new position
}
}
// Execute ADC.
public void add (int mode)
{ advance();
int value = getOperand(mode);
value += flags[CARRY]; // Add value of carry flag
A += value; // Add adjusted value to accumulator
if (A > 255) // Cope with overflow
{ A -= 256;
flags[OVERFLOW] = 1;
flags[CARRY] = 1;
}
else
{ flags[OVERFLOW] = 0;
flags[CARRY] = 0;
}
zero_and_neg(A);
}
// Execute SBC. Can cope with immediate, zero page and absolute modes
public void subtract (int mode)
{ advance();
int value = getOperand(mode);
// If absolute addressing mode, then the value is the address where value is to be found
// Otherwise processor thinks the address is to be placed in the register!
if (mode == ABSOLUTE)
value = memory[value];
value += 1 - flags[CARRY]; // Add 1 if carry flag is clear
A -= value; // Subtract adjusted value to accumulator
if (A < 0) // Cope with underflow
{ A += 256;
flags[NEGATIVE] = 1;
flags[CARRY] = 0; // Carry = 0 indicates borrow occurred (in multi-byte subtraction)
}
else
{ flags[NEGATIVE] = 0;
flags[CARRY] = 1; // Indicates that no borrow has occurred
}
if (A == 0)
flags[ZERO] = 1;
else
flags[ZERO] = 0;
}
// Execute CMP, CPX and CPY.
public void compare (char register, int mode)
{ advance();
int regValue = 0; // This represents the register being compared
switch (register)
{ case 'A' : regValue = A; break;
case 'X' : regValue = X; break;
case 'Y' : regValue = 'Y';
}
int value = getOperand(mode);
// If absolute addressing mode, then the value is the address where value is to be found
// Otherwise processor thinks the address is to be placed in the register!
if (mode == ABSOLUTE)
value = memory[value];
if (regValue >= value)
flags[CARRY] = 1;
else
flags[CARRY] = 0;
if (regValue == value)
flags[ZERO] = 1;
else
flags[ZERO] = 0;
int result = regValue - value; // Calculate whether bit 7 of the "subtraction" is set
if (result < 0)
result += 256;
if (result > 127)
flags[NEGATIVE] = 1;
else
flags[NEGATIVE] = 0;
}
// Execute LDA, LDX, LDY. Can cope with immediate, zero page and absolute modes
public void loadRegister (char register, int mode)
{ advance();
int value = getOperand(mode);
// If absolute addressing mode, then the value is the address where value is to be found
// Otherwise processor thinks the address is to be placed in the register!
if (mode == ABSOLUTE)
value = memory[value];
zero_and_neg(value);
switch (register)
{ case 'A' : A = value; break;
case 'X' : X = value; break;
case 'Y' : Y = value; break;
}
}
// Get an operand for instructions such as ADC, SBC and LDA/X/Y
public int getOperand (int addressingMode)
{ int value = 0;
int address = 0; // Used for building complex addresses
switch (addressingMode)
{ case IMMEDIATE : value = memory[progCounter]; break;
case ZEROPAGE : value = memory[memory[progCounter]]; break;
case ABSOLUTE : value = memory[progCounter];
advance();
value += 256 * memory[progCounter];
break;
case INDIRECT : address = memory[progCounter];
advance();
address += 256 * memory[progCounter];
value = memory[address] + 256 * memory[(address+1) % MAXMEM];
break; // % MAXMEM prevents memory overflow
case ABS_X : address = memory[progCounter];
advance();
address += 256 * memory[progCounter];
value = memory[(address + X) % MAXMEM];
break;
case ABS_Y : address = memory[progCounter];
advance();
address += 256 * memory[progCounter];
value = memory[(address + Y) % MAXMEM];
break;
}
advance(); // Moves program counter to start of next instruction
return value;
}
// Execute STA, STX, STY. Can cope with immediate, zero page and absolute modes
public void storeRegister (char register, int mode)
{ int address = 0;
advance();
int value = 0;
switch (register)
{ case 'A' : value = A; break;
case 'X' : value = X; break;
case 'Y' : value = Y; break;
}
switch (mode)
{ case ZEROPAGE : memory[memory[progCounter]] = value; break;
case ABSOLUTE : address = memory[progCounter];
advance();
address += 256 * memory[progCounter];
memory[address] = value;
break;
case ABS_X : address = memory[progCounter];
advance();
address += 256 * memory[progCounter];
memory[(address + X) % MAXMEM] = value;
break;
case ABS_Y : address = memory[progCounter];
advance();
address += 256 * memory[progCounter];
memory[(address + Y) % MAXMEM] = value;
break;
}
advance();
}
public void jump_to_subroutine ()
{ advance();
int destination = memory[progCounter]; // Low byte first
advance();
destination += 256 * memory[progCounter];
advance(); // Move to the next address so that it can be stacked
push(progCounter % 256); // Stack low byte first
push((int)(progCounter / 256)); // Then stack high byte;
progCounter = destination;
if (progCounter < memoryDisplayOffset || progCounter > memoryDisplayOffset + 9)
memoryDisplayOffset = progCounter; // Display at correct new position
}
public void return_from_subroutine ()
{ int return_address = 256 * pop();
return_address += pop();
progCounter = return_address;
}
// Push a value onto the stack. There is no check for stack pointer underflow
public void push (int value)
{ memory[256 * stack_base + stack] = value;
stack--; // Decrement stack pointer
if (stack < 0)
stack = 255;
}
// Pop a value from the stack. There is no check for stack pointer overflow
public int pop ()
{ stack++; // Firstly increment stack pointer
if (stack > 255)
stack = 0;
return memory[256 * stack_base + stack];
}
// Construct a byte value from the flags and push it. I don't know if this
// is the correct order for the flags.
public void push_flags_register ()
{ push(32 * flags[CARRY] + 16 * flags[DECIMAL] + 8 * flags[OVERFLOW] +
4 * flags[INTERRUPT] + 2 * flags[NEGATIVE] + flags[ZERO]);
advance();
}
// Pop the flags register from the stack
public void pop_flags_register ()
{ int temp = pop();
flags[ZERO] = temp % 2;
temp = (temp - flags[ZERO]) / 2;
flags[NEGATIVE] = temp % 2;
temp = (temp - flags[NEGATIVE]) / 2;
flags[INTERRUPT] = temp % 2;
temp = (temp - flags[INTERRUPT]) / 2;
flags[OVERFLOW] = temp % 2;
temp = (temp - flags[OVERFLOW]) / 2;
flags[DECIMAL] = temp % 2;
temp = (temp - flags[DECIMAL]) / 2;
flags[CARRY] = temp % 2; // Just in case an unsuitable number was popped
advance();
}
// Execute INX (value = 1) or DEX (value = 255)
public void inx_or_dex (int value)
{ X = (X + value) % 256;
zero_and_neg(X);
advance();
}
// Execute INY (value = 1) or DEY (value = 255)
public void iny_or_dey (int value)
{ Y = (Y + value) % 256;
zero_and_neg(Y);
advance();
}
// Determine the status of the Zero and Negative flags
public void zero_and_neg (int value)
{ if (value == 0)
flags[ZERO] = 1;
else
flags[ZERO] = 0;
if (value > 127) // MSB set
flags[NEGATIVE] = 1;
else
flags[NEGATIVE] = 0;
}
// Advance the program counter one space. If it moves "off the screen" then advance the screen
// offset one space unless we are already at the end of memory.
public void advance ()
{ progCounter++;
if (progCounter > MAXMEM) // Exceeded maximum memory capacity!
progCounter = 0;
if (progCounter < memoryDisplayOffset) // Program counter is prior to displayed memory
memoryDisplayOffset = progCounter;
if (progCounter > memoryDisplayOffset + 10) // Program counter is beyond displayed memory
memoryDisplayOffset = progCounter - 5; // so display executed instruction in the middle
} // No need for repaint() as calling routine does that!
// Report an error
public void reportError (Graphics g, int errorNumber, String duffToken)
{ switch(errorNumber)
{ case BADNUMBER : g.drawString(duffToken + " is a bad number!",350,250); break;
}
}
}
//