diff --git a/src/main/java/dk/camelot64/cpufamily6502/CpuAddressingMode.java b/src/main/java/dk/camelot64/cpufamily6502/CpuAddressingMode.java index b221e8aca..f602f8dc0 100644 --- a/src/main/java/dk/camelot64/cpufamily6502/CpuAddressingMode.java +++ b/src/main/java/dk/camelot64/cpufamily6502/CpuAddressingMode.java @@ -10,14 +10,14 @@ public enum CpuAddressingMode { * ACCUMULATOR ADDRESSING — This form of addressing is represented with a one byte instruction, implying an operation * on the accumulator" */ - NON("", "%i", 1), + NON("", "%i", 0), /** * #imm Immediate
* IMMEDIATE ADDRESSING — In immediate addressing, the operand is contained in the second byte of the instruction, * with no further memory addressing required. */ - IMM("#imm", "%i #%p", 2), + IMM("#imm", "%i #%p", 1), /** * zp Zeropage
@@ -25,7 +25,7 @@ public enum CpuAddressingMode { * second byte of the instruction and assuming a zero high address byte. Careful use of the zero page can result in * significant increase in code efficiency. */ - ZP("zp", "%i.z %p", 2), + ZP("zp", "%i.z %p", 1), /** * zp,x X Indexed Zeropage
@@ -35,7 +35,7 @@ public enum CpuAddressingMode { * the second byte references a location in page zero. Additionally, due to the “Zero Page" addressing nature of this * mode, no carry is added to the high order 8 bits of memory and crossing of page boundaries does not occur. */ - ZPX("zp,x", "%i.z %p,x", 2), + ZPX("zp,x", "%i.z %p,x", 1), /** * zp,y Y Indexed Zeropage
@@ -45,7 +45,7 @@ public enum CpuAddressingMode { * of the second byte references a location in page zero. Additionally, due to the “Zero Page" addressing nature of * this mode, no carry is added to the high order 8 bits of memory and crossing of page boundaries does not occur. */ - ZPY("zp,y", "%i.z %p,y", 2), + ZPY("zp,y", "%i.z %p,y", 1), /** * abs Absolute
@@ -53,7 +53,7 @@ public enum CpuAddressingMode { * bits of the effective address while the third byte specifies the eight high order bits. Thus, the absolute * addressing mode allows access to the entire 65 K bytes of addressable memory. */ - ABS("abs", "%i %p", 3), + ABS("abs", "%i %p", 2), /** * abs,x Absolute X
@@ -64,7 +64,7 @@ public enum CpuAddressingMode { * of indexing allows any location referencing and the index to modify multiple fields resulting in reduced coding * and execution time. */ - ABX("abs,x", "%i %p,x", 3), + ABX("abs,x", "%i %p,x", 2), /** * abs,y Absolute Y
@@ -75,7 +75,7 @@ public enum CpuAddressingMode { * indexing allows any location referencing and the index to modify multiple fields resulting in reduced coding and * execution time. */ - ABY("abs,y", "%i %p,y", 4), + ABY("abs,y", "%i %p,y", 2), /** * (zp,x) Indirect Zeropage X
@@ -86,7 +86,7 @@ public enum CpuAddressingMode { * location in page zero contains the high order eight bits of the effective address. Both memory locations * specifying the high and low order bytes of the effective address must be in page zero." */ - IZX("(zp,x)", "%i (%p,x)", 2), + IZX("(zp,x)", "%i (%p,x)", 1), /** * (abs,x) Indirect Absolute X
@@ -95,7 +95,7 @@ public enum CpuAddressingMode { * the instruction to form an address to a pointer. This address mode is only used with the JMP/JSR instruction and the * program Counter is loaded with the first and second bytes at this pointer." */ - IAX("(abs,x)", "%i (%p,x)", 3), + IAX("(abs,x)", "%i (%p,x)", 2), /** * (zp),y Indirect Zeropage Y @@ -105,7 +105,7 @@ public enum CpuAddressingMode { * address. The carry from this addition is added to the contents of the next page zero memory location, the result * being the high order eight bits of the effective address." */ - IZY("(zp),y", "%i (%p),y", 2), + IZY("(zp),y", "%i (%p),y", 1), /** * (zp),z Indirect Zeropage Z
@@ -115,7 +115,7 @@ public enum CpuAddressingMode { * address. The carry from this addition is added to the contents of the next page zero memory location, the result * being the high order eight bits of the effective address." */ - IZZ("(zp),z", "%i.z (%p),z", 2), + IZZ("(zp),z", "%i.z (%p),z", 1), /** * (abs) Indirect Absolute
@@ -125,14 +125,14 @@ public enum CpuAddressingMode { * The next memory location contains the high order byte of the effective address which is loaded into the sixteen * bits of the program counter. */ - IND("(abs)", "%i (%p)", 3), + IND("(abs)", "%i (%p)", 2), /** * (zp) Indirect Zeropage
* ZEROPAGE INDIRECT * The second byte of the instruction contains address of a zeropage memory location. */ - INZ("(zp)", "%i.z (%p)", 2), + INZ("(zp)", "%i.z (%p)", 1), /** * ((zp)) 32-bit Indirect Zeropage
@@ -140,7 +140,7 @@ public enum CpuAddressingMode { * In indirect addressing the second byte of the instruction points to a memory location in page zero. This mode is * formed by preceding a Base Page Indirect Mode instruction with NEG NEG NOP instructions. */ - LIN("((zp))", "%i.z ((%p))", 2), + LIN("((zp))", "%i.z ((%p))", 1), /** * ((zp)),z 32-bit Indirect Zeropage Z
@@ -148,7 +148,7 @@ public enum CpuAddressingMode { * In indirect indexed addressing the second byte of the instruction points to a memory location in page zero. This * mode is formed by preceding a Base Page Indirect Z-Indexed Mode instruction with the NOP instruction (opcode $EA). */ - LIZ("((zp)),z", "%i.z ((%p)),z", 2), + LIZ("((zp)),z", "%i.z ((%p)),z", 1), /** * (zp,sp),y Stack Pointer Indirect Indexed
@@ -159,7 +159,7 @@ public enum CpuAddressingMode { * addition is added to the contents of the next (D -1) stack location the result being the high order eight bits of * the effective address." STA ($12,SP),Y */ - ISY("(zp,sp),y", "%i.z (%p,sp),y", 2), + ISY("(zp,sp),y", "%i.z (%p,sp),y", 1), /** * Relative
@@ -168,7 +168,7 @@ public enum CpuAddressingMode { * contents of the lower eight bits of the program counter when the counter is set at the next instruction. The range * of the offset is — 128 to + 127 bytes from the next instruction." */ - REL("rel", "%i %p", 2), + REL("rel", "%i %p", 1), /** * zp,rel Zeropage Test Relative @@ -176,7 +176,7 @@ public enum CpuAddressingMode { * test, and one indicating the signed relative PC offset if the branch is taken. This makes BBRi and BBSi the single * instructions with two explicit operands. */ - REZ("zp,rel", "%i %p,%q", 3); + REZ("zp,rel", "%i %p,%q", 2); /** The short name of the addressing mode. */ private String name; @@ -184,7 +184,9 @@ public enum CpuAddressingMode { /** The template for an instruction using the addressing mode. */ private String template; - /** The number of bytes that an instruction takes up when using the addressing mode. This includes both opcode and operands. */ + /** + * The number of bytes that the operands of the instruction uses. This does not include the bytes used by the opcode. + * TODO: This does not take into account word-relative branches and immediate word*/ private int bytes; CpuAddressingMode(String name, String template, int bytes) { @@ -193,6 +195,12 @@ public enum CpuAddressingMode { this.name = name; } + /** + * Get the number of bytes that the operands of the instruction uses. This does not include the bytes used by the opcode. + * NOTE: This misreports number of bytes for instructions that use immediate word or long relative. + * TODO: This does not take into account word-relative branches and immediate word + * @return The number of bytes. + */ public int getBytes() { return bytes; } diff --git a/src/main/java/dk/camelot64/cpufamily6502/CpuOpcode.java b/src/main/java/dk/camelot64/cpufamily6502/CpuOpcode.java index 7fcfe0c0f..4454b0e58 100644 --- a/src/main/java/dk/camelot64/cpufamily6502/CpuOpcode.java +++ b/src/main/java/dk/camelot64/cpufamily6502/CpuOpcode.java @@ -1,6 +1,7 @@ package dk.camelot64.cpufamily6502; import java.util.Arrays; +import java.util.List; /** A specific opcode in the instruction set of a 6502 family CPU. */ public class CpuOpcode { @@ -65,13 +66,18 @@ public class CpuOpcode { return cycles; } + /** Opcodes that use an extra byte for their operand that the addressing mode reports. This is immediate word and long branches. + * The format of the string is mnemonic + " " + addressingMode */ + public static List LONG_MNEMONICS = Arrays.asList("phw #imm", "lbra rel", "lbne rel", "lbeq rel", "lbcc rel", "lbcs rel", "lbmi rel", "lbpl rel", "lbvs rel", "lbvc rel", "lbsr rel" ); + /** * Get the number of bytes the instruction with operands takes up in memory * * @return The number of bytes. */ public int getBytes() { - return addressingMode.getBytes(); + final int numBytes = opcode.length + addressingMode.getBytes() + (LONG_MNEMONICS.contains(mnemonic+" "+addressingMode.getName())?1:0); + return numBytes; } /** @@ -85,16 +91,6 @@ public class CpuOpcode { return opcode; } - /** - * Determines if this instruction has a specific single byte opcode - * - * @param opcode The byte opcode to check - * @return true if this instruction has a 1-byte opcode that matches the passed value. - */ - public boolean hasOpcode(int opcode) { - return this.opcode.length == 1 && this.opcode[0] == (byte) opcode; - } - /** * Get the printed ASM code for the instruction with an operand value. * This prints to the syntax that KickAssembler expects. diff --git a/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentInstance.java b/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentInstance.java index 364366709..53a1e2c65 100644 --- a/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentInstance.java +++ b/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentInstance.java @@ -181,17 +181,17 @@ public class AsmFragmentInstance { private static class AsmSequenceGenerator extends KickCParserBaseVisitor { private final String name; - private final AsmProgram program; + private final AsmProgram asmProgram; private final AsmFragmentInstance fragmentInstance; - public AsmSequenceGenerator(String name, AsmFragmentInstance fragmentInstance, AsmProgram program) { + public AsmSequenceGenerator(String name, AsmFragmentInstance fragmentInstance, AsmProgram asmProgram) { this.name = name; this.fragmentInstance = fragmentInstance; - this.program = program; + this.asmProgram = asmProgram; } - public AsmProgram getProgram() { - return program; + public AsmProgram getAsmProgram() { + return asmProgram; } public void generate(KickCParser.AsmLinesContext context) { @@ -200,14 +200,14 @@ public class AsmFragmentInstance { @Override public Object visitAsmLabelName(KickCParser.AsmLabelNameContext ctx) { - program.addLine(new AsmLabel(ctx.ASM_NAME().getText())); + asmProgram.addLine(new AsmLabel(ctx.ASM_NAME().getText())); return null; } @Override public Object visitAsmLabelMulti(KickCParser.AsmLabelMultiContext ctx) { String label = ctx.ASM_MULTI_NAME().getText(); - program.addLine(new AsmLabel(label)); + asmProgram.addLine(new AsmLabel(label)); return null; } @@ -217,7 +217,7 @@ public class AsmFragmentInstance { for(int i = 1; i < ctx.getChildCount(); i = i + 2) { values.add(ctx.getChild(i).getText()); } - program.addLine(new AsmDataNumeric(null, AsmDataNumeric.Type.BYTE, values)); + asmProgram.addLine(new AsmDataNumeric(null, AsmDataNumeric.Type.BYTE, values)); return null; } @@ -231,7 +231,7 @@ public class AsmFragmentInstance { instruction = (AsmInstruction) this.visit(paramModeCtx); } if(instruction != null) { - program.addLine(instruction); + asmProgram.addLine(instruction); } else { throw new RuntimeException("Error parsing ASM fragment line " + name + ".asm\n - Line: " + ctx.getText()); } @@ -334,7 +334,7 @@ public class AsmFragmentInstance { AsmParameter param2 = operand2Ctx == null ? null : (AsmParameter) this.visit(operand2Ctx); // Convert to ZP-addressing mode if possible boolean isZp = param1 != null && param1.isZp(); - CpuOpcode cpuOpcode = this.fragmentInstance.fragmentTemplate.getTargetCpu().getCpu65xx().getOpcode(mnemonic, addressingMode, isZp); + CpuOpcode cpuOpcode = this.getAsmProgram().getTargetCpu().getCpu65xx().getOpcode(mnemonic, addressingMode, isZp); String operand1 = param1 == null ? null : param1.getParam(); String operand2 = param2 == null ? null : param2.getParam(); if(cpuOpcode == null) { diff --git a/src/test/java/dk/camelot64/cpufamily6502/TestCpuFamilyKickAssCompatibility.java b/src/test/java/dk/camelot64/cpufamily6502/TestCpuFamilyKickAssCompatibility.java index ab0a401cd..6394c5c81 100644 --- a/src/test/java/dk/camelot64/cpufamily6502/TestCpuFamilyKickAssCompatibility.java +++ b/src/test/java/dk/camelot64/cpufamily6502/TestCpuFamilyKickAssCompatibility.java @@ -8,8 +8,7 @@ import org.junit.Test; import java.util.*; -import static junit.framework.TestCase.assertNotNull; -import static junit.framework.TestCase.assertTrue; +import static junit.framework.TestCase.*; public class TestCpuFamilyKickAssCompatibility { @@ -49,41 +48,57 @@ public class TestCpuFamilyKickAssCompatibility { assertNotNull("KickAss CPU " + kaCpu.name + " does not know the KickC CPU " + kcCpu.getName() + " mnemonic", kcOpcode.getMnemonic()); final List<_65xxArgType> kaArgTypes = kaAddressingModeMap.get(kcOpcode.getAddressingMode()); assertNotNull("KickAss addressing mode not found " + kcOpcode.getAddressingMode().getName(), kaArgTypes); - // Try each argtype + // Try each argtype to find the one that works boolean found = false; for(_65xxArgType kaArgType : kaArgTypes) { final int kaArgTypeIdx = kaArgType.getIdNo(); - if(kaOpcodes!=null && kaOpcodes.length>kaArgTypeIdx) { + if(kaOpcodes != null && kaOpcodes.length > kaArgTypeIdx) { final int kaOpcodeRaw = kaOpcodes[kaArgTypeIdx]; if(kaOpcodeRaw >= 0) { found = true; - int[] kaOpcode; - if(kcOpcode.getOpcode().length==1) { - kaOpcode = new int[]{kaOpcodeRaw}; - } else { - List kaOpcodeList = new ArrayList<>(); - if(CPU_45GS02.R32_MNEMONICS.contains(kcOpcode.getMnemonic())) { - kaOpcodeList.add((int)CPU_45GS02.R32_OPCODE_PREFIX); - kaOpcodeList.add((int)CPU_45GS02.R32_OPCODE_PREFIX); - } - if (kaArgType == _65xxArgType.indirect32ZeropageZ || kaArgType == _65xxArgType.indirect32Zeropage) { - // Make sure the prefix is unsigned - kaOpcodeList.add(CPU_45GS02.A32_OPCODE_PREFIX&0xff); - } - kaOpcodeList.add(kaOpcodeRaw); - kaOpcode = kaOpcodeList.stream().mapToInt(i->i).toArray(); - } + int[] kaOpcode = getKAOpcode(kaOpcodeRaw, kaArgType, kcOpcode.getMnemonic()); Assert.assertArrayEquals("KickAss opcode not matching for mnemonic " + kcOpcode.toString(), kcOpcode.getOpcode(), kaOpcode); + + int kaByteSize = kaOpcode.length + kaArgType.getByteSize(); + assertEquals("KickAss opcode byte size not matching KickC byte size "+kcOpcode.toString(), kcOpcode.getBytes(), kaByteSize); } } } assertTrue("KickAss opcode not found for mnemonic " + kcOpcode.toString(), found); } + // Test that each KickAss opcode has a matching KickC opcode + + + } + + /** + * Convert KickAssembler opcode to int array, that also contains the prefix opcodes used by 45GS02. + * + * @param kaOpcodeRaw The "raw" one-byte opcode + * @param kaArgType The addressing mode + * @param mnemonic The instruction mnemonic + * @return The opcode list. + */ + private int[] getKAOpcode(int kaOpcodeRaw, _65xxArgType kaArgType, String mnemonic) { + List kaOpcodeList = new ArrayList<>(); + if(CPU_45GS02.R32_MNEMONICS.contains(mnemonic)) { + // Make sure the prefix is unsigned + kaOpcodeList.add((int) CPU_45GS02.R32_OPCODE_PREFIX & 0xff); + kaOpcodeList.add((int) CPU_45GS02.R32_OPCODE_PREFIX & 0xff); + } + if(kaArgType == _65xxArgType.indirect32ZeropageZ || kaArgType == _65xxArgType.indirect32Zeropage) { + // Make sure the prefix is unsigned + kaOpcodeList.add(CPU_45GS02.A32_OPCODE_PREFIX & 0xff); + } + kaOpcodeList.add(kaOpcodeRaw); + int[] kaOpcode = kaOpcodeList.stream().mapToInt(i -> i).toArray(); + return kaOpcode; } /** * Get the KickAss ArgType that matches a KickC addressing mode. + * * @return The argtype. */ Map> getKAAddressingModeMap() { @@ -107,7 +122,6 @@ public class TestCpuFamilyKickAssCompatibility { map.put(CpuAddressingMode.ISY, Collections.singletonList(_65xxArgType.indirectStackZeropageY)); map.put(CpuAddressingMode.REL, Arrays.asList(_65xxArgType.relative, _65xxArgType.relativeWord)); map.put(CpuAddressingMode.REZ, Collections.singletonList(_65xxArgType.zeropageRelative)); - // TODO: Handle Immediate Word, relative Word return map; }