From 0ab36bcbf6b35b79bdca04f39fa511eea5515254 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Wed, 1 Jul 2026 23:47:13 +0200 Subject: [PATCH] added qemu68k compilation target --- .../src/prog8/code/core/ICompilationTarget.kt | 3 + codeCore/src/prog8/code/target/C64Target.kt | 2 + .../src/prog8/code/target/Qemu68kTarget.kt | 79 +++++ codeGenM68k/src/prog8/codegen/m68k/Main.kt | 4 +- .../prog8/codegen/new6502/CodeGenerator.kt | 2 +- .../src/prog8/codegen/new6502/Main.kt | 2 +- compiler/res/prog8lib/qemu68k/syslib.p8 | 294 ++++++++++++++++++ .../test/helpers/Cpu6502SimulatorHelper.kt | 2 +- docs/source/compiling.rst | 2 +- docs/source/targetsystem.rst | 1 + docs/source/todo.rst | 1 - examples/test.p8 | 108 +------ simpleAst/src/prog8/code/SymbolTableMaker.kt | 3 +- 13 files changed, 388 insertions(+), 115 deletions(-) create mode 100644 codeCore/src/prog8/code/target/Qemu68kTarget.kt create mode 100644 compiler/res/prog8lib/qemu68k/syslib.p8 diff --git a/codeCore/src/prog8/code/core/ICompilationTarget.kt b/codeCore/src/prog8/code/core/ICompilationTarget.kt index 001bb40ba..ebe633b0b 100644 --- a/codeCore/src/prog8/code/core/ICompilationTarget.kt +++ b/codeCore/src/prog8/code/core/ICompilationTarget.kt @@ -35,6 +35,9 @@ enum class CpuType(val statusBitsOnMultiByteOps: Boolean) { /** 8-bit CMOS 65C02. Same status-bit issue as plain 6502. */ CPU65C02(false), + + /** 16/32 bit Motorola 68000. has single instructions that set status bits when dealing with multi byte operands */ + M68000(true), /** * Virtual machine target. We use `false` here so the IR generator always diff --git a/codeCore/src/prog8/code/target/C64Target.kt b/codeCore/src/prog8/code/target/C64Target.kt index 92d8b7d5a..463bef630 100644 --- a/codeCore/src/prog8/code/target/C64Target.kt +++ b/codeCore/src/prog8/code/target/C64Target.kt @@ -97,6 +97,7 @@ val CompilationTargets = listOf( C128Target.NAME, Cx16Target.NAME, PETTarget.NAME, + Qemu68kTarget.NAME, VMTarget.NAME ) @@ -105,6 +106,7 @@ fun getCompilationTargetByName(name: String) = when(name.lowercase()) { C128Target.NAME -> C128Target() Cx16Target.NAME -> Cx16Target() PETTarget.NAME -> PETTarget() + Qemu68kTarget.NAME -> Qemu68kTarget() VMTarget.NAME -> VMTarget() else -> throw IllegalArgumentException("invalid compilation target") } diff --git a/codeCore/src/prog8/code/target/Qemu68kTarget.kt b/codeCore/src/prog8/code/target/Qemu68kTarget.kt new file mode 100644 index 000000000..eb2444c88 --- /dev/null +++ b/codeCore/src/prog8/code/target/Qemu68kTarget.kt @@ -0,0 +1,79 @@ +package prog8.code.target + +import prog8.code.core.* +import prog8.code.target.encodings.Encoder +import java.nio.file.Path + +class Qemu68kTarget: ICompilationTarget, + IStringEncoding by Encoder(false), + IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) { + + override val name = NAME + override val supportsBankedCalls = false + override val defaultEncoding = Encoding.ISO + override val libraryPath = null + override val customLauncher = emptyList() + override val additionalAssemblerOptions = emptyList() + override val defaultOutputType = OutputType.RAW + override val defaultLauncherType = CbmPrgLauncherType.NONE + + companion object { + const val NAME = "qemu68k" + const val FLOAT_MEM_SIZE = 4 // 32-bits double + } + + override val cpu = CpuType.M68000 + + override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble() + override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble() + override val FLOAT_MEM_SIZE = Qemu68kTarget.FLOAT_MEM_SIZE.toUInt() + override val PROGRAM_LOAD_ADDRESS = 0x10000u + override val PROGRAM_MEMTOP_ADDRESS = 0xfffffffeu + + override val BSSHIGHRAM_START = 0u // not actually used + override val BSSHIGHRAM_END = 0u // not actually used + override val BSSGOLDENRAM_START = 0u // not actually used + override val BSSGOLDENRAM_END = 0u // not actually used + override lateinit var zeropage: Zeropage // not actually used + + override fun getFloatAsmBytes(num: Number): String { + TODO("float asm bytes") + } + + override fun convertFloatToBytes(num: Double): List { + TODO("convert float to bytes") + } + + override fun convertBytesToFloat(bytes: List): Double { + require(bytes.size==4) { "need 4 bytes" } + TODO("convert bytes to float") + } + + override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) { + if(selectedEmulator!=1) { + System.err.println("The qemu68k target only supports the main emulator (Qemu).") + return + } + TODO("launch qemu on $programNameWithPath") + } + + override fun isIOAddress(address: UInt): Boolean = address>=0xff000000u + + override fun initializeMemoryAreas(compilerOptions: CompilationOptions) { + zeropage = M68kZeropage(compilerOptions) + } +} + + +private class M68kZeropage(options: CompilationOptions): Zeropage(options) { + override val SCRATCH_B1: UInt + get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") + override val SCRATCH_REG: UInt + get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") + override val SCRATCH_W1: UInt + get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") + override val SCRATCH_W2: UInt + get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") + override val SCRATCH_PTR: UInt + get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") +} diff --git a/codeGenM68k/src/prog8/codegen/m68k/Main.kt b/codeGenM68k/src/prog8/codegen/m68k/Main.kt index e3130e7ae..17b8c924f 100644 --- a/codeGenM68k/src/prog8/codegen/m68k/Main.kt +++ b/codeGenM68k/src/prog8/codegen/m68k/Main.kt @@ -26,8 +26,8 @@ fun main(args: Array) { println("Target system and CPU: ${target.name} / ${target.cpu}") val gen = when(target.cpu) { - CpuType.CPU6502, CpuType.CPU65C02 -> CodeGenerator(program, target.cpu) - CpuType.VIRTUAL -> { + CpuType.M68000 -> CodeGenerator(program, target.cpu) + else -> { println("This code generator only works for m68000 CPUs.") exitProcess(1) } diff --git a/codeGenNew6502/src/prog8/codegen/new6502/CodeGenerator.kt b/codeGenNew6502/src/prog8/codegen/new6502/CodeGenerator.kt index 07527f9dc..56c185104 100644 --- a/codeGenNew6502/src/prog8/codegen/new6502/CodeGenerator.kt +++ b/codeGenNew6502/src/prog8/codegen/new6502/CodeGenerator.kt @@ -444,7 +444,7 @@ class CodeGenerator(val program: IRProgram, private val target: ICompilationTarg when (cpu) { CpuType.CPU65C02 -> emitRaw(".cpu 'w65c02'") CpuType.CPU6502 -> emitRaw(".cpu '6502'") - CpuType.VIRTUAL -> emitRaw(".cpu '6502'") + else -> throw IllegalArgumentException("invalid cpu type for this code generator: $cpu") } emitRaw(".enc 'none'") emitRaw("") diff --git a/codeGenNew6502/src/prog8/codegen/new6502/Main.kt b/codeGenNew6502/src/prog8/codegen/new6502/Main.kt index 212a4a49a..5fe2e8538 100644 --- a/codeGenNew6502/src/prog8/codegen/new6502/Main.kt +++ b/codeGenNew6502/src/prog8/codegen/new6502/Main.kt @@ -57,7 +57,7 @@ fun main(args: Array) { val gen = when(target.cpu) { CpuType.CPU6502, CpuType.CPU65C02 -> CodeGenerator(program, target) - CpuType.VIRTUAL -> { + else -> { println("This code generator only works for 6502 and 65C02 CPUs.") exitProcess(1) } diff --git a/compiler/res/prog8lib/qemu68k/syslib.p8 b/compiler/res/prog8lib/qemu68k/syslib.p8 new file mode 100644 index 000000000..31b71720a --- /dev/null +++ b/compiler/res/prog8lib/qemu68k/syslib.p8 @@ -0,0 +1,294 @@ +; Prog8 definitions for the Qemu M68k target + +%option ignore_unused + +sys { + ; ------- lowlevel system routines -------- + + const ubyte target = 68 ; compilation target specifier. + + const ubyte SIZEOF_BOOL = sizeof(bool) + const ubyte SIZEOF_BYTE = sizeof(byte) + const ubyte SIZEOF_UBYTE = sizeof(ubyte) + const ubyte SIZEOF_WORD = sizeof(word) + const ubyte SIZEOF_UWORD = sizeof(uword) + const ubyte SIZEOF_LONG = sizeof(long) + const ubyte SIZEOF_POINTER = sizeof(&sys.wait) + const ubyte SIZEOF_FLOAT = sizeof(float) + const byte MIN_BYTE = -128 + const byte MAX_BYTE = 127 + const ubyte MIN_UBYTE = 0 + const ubyte MAX_UBYTE = 255 + const word MIN_WORD = -32768 + const word MAX_WORD = 32767 + const uword MIN_UWORD = 0 + const uword MAX_UWORD = 65535 + ; MIN_FLOAT and MAX_FLOAT are defined in the floats module if imported + + + sub reset_system() { + ; TODO reset qemu + } + + sub poweroff_system() { + ; TODO poweroff qemu + } + + sub die(ubyte code, str message) { + ; -- kill the program by jumping into the debugger/monitor (if available). Status code is in register A, a pointer to the death message is in X,Y. + str @shared warning = iso:"\n\nPROGRAM DIED: " + ; TODO print die message and exit qemu + poweroff_system() + } + + sub wait(uword jiffies) { + ; --- wait approximately the given number of jiffies (1/60th seconds) + ; TODO implement the wait + } + + sub memcopy(uword source, uword tgt, uword count) { + ; TODO implement memcopy + } + + sub memset(uword mem, uword numbytes, ubyte value) { + ; TODO implement memset + } + + sub memsetw(uword mem, uword numwords, uword value) { + ; TODO implement memsetw + } + + sub memcmp(uword address1, uword address2, uword size) -> byte { + ; Compares two blocks of memory of up to 65535 bytes in size + ; Returns -1 (255), 0 or 1, meaning: block 1 sorts before, equal or after block 2. + ; TODO implement memcmp + return 0 + } + + sub exit(ubyte returnvalue) { + ; -- immediately exit the program with a return code in the D0 register + ; TODO implement exit + poweroff_system() + } + + sub set_carry() { + %asm {{ + sec + }} + } + + sub clear_carry() { + %asm {{ + clc + }} + } + + + sub clear_irqd() { + ; TODO implement m68k CLI + } + + sub progstart() -> long { + return $10000 + } + + sub progend() -> long { + return $00fffffe ; just a dummy value + } +} + +cx16 { + + ; the sixteen virtual 16-bit registers that the Commander X16 has defined in the zeropage + ; they are on this target as well, but their location in memory is different + ; TODO determine memory location for all of these + &uword r0 = $ff02 + &uword r1 = $ff04 + &uword r2 = $ff06 + &uword r3 = $ff08 + &uword r4 = $ff0a + &uword r5 = $ff0c + &uword r6 = $ff0e + &uword r7 = $ff10 + &uword r8 = $ff12 + &uword r9 = $ff14 + &uword r10 = $ff16 + &uword r11 = $ff18 + &uword r12 = $ff1a + &uword r13 = $ff1c + &uword r14 = $ff1e + &uword r15 = $ff20 + + ; signed word versions + &word r0s = $ff02 + &word r1s = $ff04 + &word r2s = $ff06 + &word r3s = $ff08 + &word r4s = $ff0a + &word r5s = $ff0c + &word r6s = $ff0e + &word r7s = $ff10 + &word r8s = $ff12 + &word r9s = $ff14 + &word r10s = $ff16 + &word r11s = $ff18 + &word r12s = $ff1a + &word r13s = $ff1c + &word r14s = $ff1e + &word r15s = $ff20 + + ; signed long versions + &long r0r1sl = $ff02 + &long r2r3sl = $ff06 + &long r4r5sl = $ff0a + &long r6r7sl = $ff0e + &long r8r9sl = $ff12 + &long r10r11sl = $ff16 + &long r12r13sl = $ff1a + &long r14r15sl = $ff1e + + ; ubyte versions (low and high bytes) + &ubyte r0L = $ff02 + &ubyte r1L = $ff04 + &ubyte r2L = $ff06 + &ubyte r3L = $ff08 + &ubyte r4L = $ff0a + &ubyte r5L = $ff0c + &ubyte r6L = $ff0e + &ubyte r7L = $ff10 + &ubyte r8L = $ff12 + &ubyte r9L = $ff14 + &ubyte r10L = $ff16 + &ubyte r11L = $ff18 + &ubyte r12L = $ff1a + &ubyte r13L = $ff1c + &ubyte r14L = $ff1e + &ubyte r15L = $ff20 + + &ubyte r0H = $ff03 + &ubyte r1H = $ff05 + &ubyte r2H = $ff07 + &ubyte r3H = $ff09 + &ubyte r4H = $ff0b + &ubyte r5H = $ff0d + &ubyte r6H = $ff0f + &ubyte r7H = $ff11 + &ubyte r8H = $ff13 + &ubyte r9H = $ff15 + &ubyte r10H = $ff17 + &ubyte r11H = $ff19 + &ubyte r12H = $ff1b + &ubyte r13H = $ff1d + &ubyte r14H = $ff1f + &ubyte r15H = $ff21 + + ; signed byte versions (low and high bytes) + &byte r0sL = $ff02 + &byte r1sL = $ff04 + &byte r2sL = $ff06 + &byte r3sL = $ff08 + &byte r4sL = $ff0a + &byte r5sL = $ff0c + &byte r6sL = $ff0e + &byte r7sL = $ff10 + &byte r8sL = $ff12 + &byte r9sL = $ff14 + &byte r10sL = $ff16 + &byte r11sL = $ff18 + &byte r12sL = $ff1a + &byte r13sL = $ff1c + &byte r14sL = $ff1e + &byte r15sL = $ff20 + + &byte r0sH = $ff03 + &byte r1sH = $ff05 + &byte r2sH = $ff07 + &byte r3sH = $ff09 + &byte r4sH = $ff0b + &byte r5sH = $ff0d + &byte r6sH = $ff0f + &byte r7sH = $ff11 + &byte r8sH = $ff13 + &byte r9sH = $ff15 + &byte r10sH = $ff17 + &byte r11sH = $ff19 + &byte r12sH = $ff1b + &byte r13sH = $ff1d + &byte r14sH = $ff1f + &byte r15sH = $ff21 + + ; boolean versions + &bool r0bL = $ff02 + &bool r1bL = $ff04 + &bool r2bL = $ff06 + &bool r3bL = $ff08 + &bool r4bL = $ff0a + &bool r5bL = $ff0c + &bool r6bL = $ff0e + &bool r7bL = $ff10 + &bool r8bL = $ff12 + &bool r9bL = $ff14 + &bool r10bL = $ff16 + &bool r11bL = $ff18 + &bool r12bL = $ff1a + &bool r13bL = $ff1c + &bool r14bL = $ff1e + &bool r15bL = $ff20 + + &bool r0bH = $ff03 + &bool r1bH = $ff05 + &bool r2bH = $ff07 + &bool r3bH = $ff09 + &bool r4bH = $ff0b + &bool r5bH = $ff0d + &bool r6bH = $ff0f + &bool r7bH = $ff11 + &bool r8bH = $ff13 + &bool r9bH = $ff15 + &bool r10bH = $ff17 + &bool r11bH = $ff19 + &bool r12bH = $ff1b + &bool r13bH = $ff1d + &bool r14bH = $ff1f + &bool r15bH = $ff21 + + + sub save_virtual_registers() { + uword[32] storage + storage[0] = r0 + storage[1] = r1 + storage[2] = r2 + storage[3] = r3 + storage[4] = r4 + storage[5] = r5 + storage[6] = r6 + storage[7] = r7 + storage[8] = r8 + storage[9] = r9 + storage[10] = r10 + storage[11] = r11 + storage[12] = r12 + storage[13] = r13 + storage[14] = r14 + storage[15] = r15 + } + + sub restore_virtual_registers() { + r0 = cx16.save_virtual_registers.storage[0] + r1 = cx16.save_virtual_registers.storage[1] + r2 = cx16.save_virtual_registers.storage[2] + r3 = cx16.save_virtual_registers.storage[3] + r4 = cx16.save_virtual_registers.storage[4] + r5 = cx16.save_virtual_registers.storage[5] + r6 = cx16.save_virtual_registers.storage[6] + r7 = cx16.save_virtual_registers.storage[7] + r8 = cx16.save_virtual_registers.storage[8] + r9 = cx16.save_virtual_registers.storage[9] + r10 = cx16.save_virtual_registers.storage[10] + r11 = cx16.save_virtual_registers.storage[11] + r12 = cx16.save_virtual_registers.storage[12] + r13 = cx16.save_virtual_registers.storage[13] + r14 = cx16.save_virtual_registers.storage[14] + r15 = cx16.save_virtual_registers.storage[15] + } +} diff --git a/compiler/test/helpers/Cpu6502SimulatorHelper.kt b/compiler/test/helpers/Cpu6502SimulatorHelper.kt index aedaed559..753eea3f7 100644 --- a/compiler/test/helpers/Cpu6502SimulatorHelper.kt +++ b/compiler/test/helpers/Cpu6502SimulatorHelper.kt @@ -71,7 +71,7 @@ class Cpu6502SimulatorHelper(val result: CompilationResult) { val actualCpuType: CpuType = when(cpuType) { prog8.code.core.CpuType.CPU6502 -> CpuType.CPU6502 prog8.code.core.CpuType.CPU65C02 -> CpuType.CPU65C02 - prog8.code.core.CpuType.VIRTUAL -> throw IllegalStateException("Virtual CPU cannot be simulated here") + else -> throw IllegalStateException("non-6502 cpus cannot be simulated here") } val ioHandler = serialAndPower ?: CapturingSerialIO() diff --git a/docs/source/compiling.rst b/docs/source/compiling.rst index cf00211c6..a01b23cc6 100644 --- a/docs/source/compiling.rst +++ b/docs/source/compiling.rst @@ -284,7 +284,7 @@ One or more .p8 module files ``-target `` Sets the target output of the compiler. This option is required. ``c64`` = Commodore 64, ``c128`` = Commodore 128, ``cx16`` = Commander X16, ``pet32`` - Commodore PET model 4032, - ``virtual`` = builtin virtual machine. + ``qemu68k`` = Qemu M68k, ``virtual`` = builtin virtual machine. You can also specify a file name as target, prog8 will then try to read the target machine's configuration and properties from that configuration file instead of using one of the built-in targets. See :ref:`customizable_target` for details about this. diff --git a/docs/source/targetsystem.rst b/docs/source/targetsystem.rst index 9923c508b..4c4f0457c 100644 --- a/docs/source/targetsystem.rst +++ b/docs/source/targetsystem.rst @@ -16,6 +16,7 @@ Currently these machines can be selected as a compilation target (via the ``-tar - 'cx16': the `Commander X16 `_ - 'c128': the Commodore 128 - 'pet32': the Commodore PET 4032 +- 'qemu68k': Qemu M68k simulator - 'virtual': a builtin virtual machine - custom targets via a separate configuration file (see :ref:`customizable_target`) diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 23cef79bf..d11edbf6a 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -1,7 +1,6 @@ TODO ==== -make a qemu68k target that triggers the (new) 68k codegen based on IR rename -expericodegen into -newcodegen that should switch all existing 6502 codegens to using the new one based on IR remove Expericodegen module diff --git a/examples/test.p8 b/examples/test.p8 index 93dff42d4..de6637d45 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,111 +1,5 @@ -%import textio -%import floats -%zeropage basicsafe - main { sub start() { - ubyte @shared ubb = 122 - uword @shared uww = 1222 - long @shared uwl = 12222 - float @shared fl = 12222 - - txt.print_uw(sqrt(ubb)) - txt.spc() - txt.print_uw(sqrt(uww)) - txt.spc() - txt.print_uw(sqrt(uwl)) - txt.nl() - txt.print("floats: ") - ; TODO: none of these floats are being printed when compiled with expericodegen! Work fine in cx16 and virtual targets. - txt.print_f(1.23456789) - txt.spc() - txt.print_f(fl) - txt.spc() - txt.print_f(sqrt(fl)) - txt.chrout('!') - txt.nl() + cx16.r0++ } } - -;%import gfx_lores -;%import math -; -; -;main { -; const ubyte MAX_NUM_CIRCLES = 80 -; const ubyte GROWTH_RATE = 2 -; uword[MAX_NUM_CIRCLES] circle_x -; uword[MAX_NUM_CIRCLES] circle_y -; ubyte[MAX_NUM_CIRCLES] circle_radius -; ubyte num_circles = 0 -; ubyte background_color -; -; sub start() { -; gfx_lores.graphics_mode() -; -; repeat { -; background_color = math.rnd() -; gfx_lores.clear_screen(background_color) -; num_circles = 0 -; draw_circles() -; } -; } -; -; sub draw_circles() { -; uword @zp x -; uword @zp y -; ubyte @zp radius -; -; while num_circles bool { -; if num_circles==0 -; return true -; ubyte @zp c -; for c in 0 to num_circles-1 { -; if distance(c) < (radius as uword) + circle_radius[c] -; return false -; } -; return true -; } -; -; sub distance(ubyte cix) -> uword { -; word dx = x as word - circle_x[cix] -; word dy = y as word - circle_y[cix] -; uword sqx = dx*dx as uword -; uword sqy = dy*dy as uword -; return sqrt(sqx + sqy) -; } -; -; sub not_edge() -> bool { -; if x as word - radius < 0 -; return false -; if x + radius >= gfx_lores.WIDTH -; return false -; if y as word - radius < 0 -; return false -; if y + radius >= gfx_lores.HEIGHT -; return false -; return true -; } -; } -;} diff --git a/simpleAst/src/prog8/code/SymbolTableMaker.kt b/simpleAst/src/prog8/code/SymbolTableMaker.kt index 0b35335a7..e9bab6afc 100644 --- a/simpleAst/src/prog8/code/SymbolTableMaker.kt +++ b/simpleAst/src/prog8/code/SymbolTableMaker.kt @@ -2,6 +2,7 @@ package prog8.code import prog8.code.ast.* import prog8.code.core.* +import prog8.code.target.Qemu68kTarget import prog8.code.target.VMTarget @@ -34,7 +35,7 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp } require(scopestack.size==1) - if(options.compTarget.name != VMTarget.NAME) { + if(options.compTarget.name !in setOf(VMTarget.NAME, Qemu68kTarget.NAME)) { listOf( PtMemMapped("P8ZP_SCRATCH_B1", DataType.UBYTE, options.compTarget.zeropage.SCRATCH_B1, null, Position.DUMMY), PtMemMapped("P8ZP_SCRATCH_REG", DataType.UBYTE, options.compTarget.zeropage.SCRATCH_REG, null, Position.DUMMY),