1
0
mirror of https://github.com/KarolS/millfork.git synced 2026-04-20 03:16:45 +00:00

ZX Spectrum Next CPU support

This commit is contained in:
Karol Stasiak
2019-10-01 00:46:15 +02:00
parent f7dd78e4c0
commit d1058b6223
11 changed files with 178 additions and 13 deletions
+2
View File
@@ -31,6 +31,8 @@ if a line ends with a backslash character, the value continues to the next line.
* `strictz80` (Z80 without illegal instructions)
* `z80next` (Z80 core from ZX Spectrum Next)
* `i8080` (Intel 8080)
* `i8085` (Intel 8085)
+2
View File
@@ -25,6 +25,8 @@ Only instructions available on the current CPU architecture are available.
Intel syntax does not support instructions that are unavailable on the 8080.
Undocumented Z80 instructions are not supported, except for `SLL`.
Not all ZX Spectrum Next are supported. `JP (C)`, `BSLA` and similar instructions are not supported.
Labels have to be followed by a colon and they can optionally be on a separate line.
Indentation is not important:
+1 -1
View File
@@ -52,7 +52,7 @@ The following features are defined based on the chosen CPU and compilation optio
* `CPUFEATURE_65C02`, `CPUFEATURE_65CE02`, `CPUFEATURE_HUC6280`, `CPUFEATURE_65816_EMULATION`, `CPUFEATURE_65816_NATIVE`,
`CPUFEATURE_8080`, `CPUFEATURE_8085`, `CPUFEATURE_GAMEBOY`, `CPUFEATURE_Z80`,
`CPUFEATURE_6502_ILLEGALS`, `CPUFEATURE_8085_ILLEGALS`, `CPUFEATURE_Z80_ILLEGALS` 1 if given instruction subset is enabled, 0 otherwise
`CPUFEATURE_6502_ILLEGALS`, `CPUFEATURE_8085_ILLEGALS`, `CPUFEATURE_Z80_ILLEGALS`, `CPUFEATURE_Z80_NEXT` 1 if given instruction subset is enabled, 0 otherwise
* `ENCODING_SAME` - 1 if the encodings `default` and `src` are the same, 0 otherwise.
+9 -3
View File
@@ -4,9 +4,15 @@
#endif
#pragma zilog_syntax
#if CPUFEATURE_Z80 || CPUFEATURE_GAMEBOY
#if CPUFEATURE_Z80_NEXT
inline asm byte __mul_u8u8u8() {
? LD E,A
? MUL
? LD A, E
? RET
}
#elseif CPUFEATURE_Z80 || CPUFEATURE_GAMEBOY
//A = A * D
noinline asm byte __mul_u8u8u8() {
? LD E,A
? LD A, 0
@@ -43,7 +43,7 @@ case class CompilationOptions(platform: Platform,
EmitIntel8085Opcodes, EmitIntel8080Opcodes, UseIxForStack, UseIntelSyntaxForInput, UseIntelSyntaxForOutput)
if (CpuFamily.forType(platform.cpu) != CpuFamily.I80) invalids ++= Set(
EmitExtended80Opcodes, EmitZ80Opcodes, EmitSharpOpcodes, EmitEZ80Opcodes,
EmitExtended80Opcodes, EmitZ80Opcodes, EmitSharpOpcodes, EmitEZ80Opcodes, EmitZ80NextOpcodes,
UseIyForStack, UseIxForScratch, UseIyForScratch, UseShadowRegistersForInterrupts)
if (CpuFamily.forType(platform.cpu) != CpuFamily.M6809) invalids ++= Set(
@@ -111,7 +111,7 @@ case class CompilationOptions(platform: Platform,
}
case CpuFamily.I80 =>
if (flags(EmitIllegals)) {
if (platform.cpu != Z80 && platform.cpu != Intel8085) {
if (platform.cpu != Z80 && platform.cpu != Intel8085 && platform.cpu != Z80Next) {
log.error("Illegal opcodes enabled for architecture that doesn't support them")
}
}
@@ -160,6 +160,11 @@ case class CompilationOptions(platform: Platform,
log.error("Sharp LR35902 opcodes enabled for architecture that doesn't support them")
}
}
if (flags(EmitZ80NextOpcodes)) {
if (platform.cpu != Z80Next) {
log.error("ZX Spectrum Next opcodes enabled for architecture that doesn't support them")
}
}
if (flags(EmitExtended80Opcodes)) {
if (platform.cpu != Sharp && !Z80Compatible(platform.cpu)) {
log.error("Extended 8080-like opcodes enabled for architecture that doesn't support them")
@@ -221,6 +226,7 @@ case class CompilationOptions(platform: Platform,
"CPUFEATURE_65816_NATIVE" -> toLong(flag(CompilationFlag.EmitNative65816Opcodes)),
"CPUFEATURE_6502_ILLEGALS" -> toLong(platform.cpuFamily == CpuFamily.M6502 && flag(CompilationFlag.EmitIllegals)),
"CPUFEATURE_Z80_ILLEGALS" -> toLong(flag(CompilationFlag.EmitZ80Opcodes) && flag(CompilationFlag.EmitIllegals)),
"CPUFEATURE_Z80_NEXT" -> toLong(flag(CompilationFlag.EmitZ80NextOpcodes)),
"CPUFEATURE_8085_ILLEGALS" -> toLong(flag(CompilationFlag.EmitIntel8080Opcodes) && flag(CompilationFlag.EmitIllegals)),
"BIG_ENDIAN" -> toLong(Cpu.isBigEndian(platform.cpu)),
"LITTLE_ENDIAN" -> toLong(!Cpu.isBigEndian(platform.cpu)),
@@ -276,7 +282,7 @@ object CpuFamily extends Enumeration {
import Cpu._
cpu match {
case Mos | StrictMos | Ricoh | StrictRicoh | Cmos | HuC6280 | CE02 | Sixteen => M6502
case Intel8080 | Intel8085 | StrictIntel8085 | Sharp | Z80 | StrictZ80 | EZ80 => I80
case Intel8080 | Intel8085 | StrictIntel8085 | Sharp | Z80 | StrictZ80 | EZ80 | Z80Next => I80
case Intel8086 | Intel80186 => I86
case Cpu.Motorola6809 => M6809
}
@@ -346,6 +352,10 @@ object Cpu extends Enumeration {
* The Zilog eZ80 processor
*/
val EZ80: Cpu.Value = Value
/**
* The Z80 core from the ZX Spectrum Next
*/
val Z80Next: Cpu.Value = Value
/**
* The Sharp LR35902 processor
*/
@@ -370,11 +380,11 @@ object Cpu extends Enumeration {
/**
* Processors that can run code for Zilog Z80
*/
val Z80Compatible: Set[Cpu.Value] = Set(Z80, StrictZ80, EZ80)
val Z80Compatible: Set[Cpu.Value] = Set(Z80, StrictZ80, EZ80, Z80Next)
/**
* Processors that can run code for Intel 8080
*/
val Intel8080Compatible: Set[Cpu.Value] = Set(Intel8080, Intel8085, StrictIntel8085, Z80, StrictZ80, EZ80)
val Intel8080Compatible: Set[Cpu.Value] = Set(Intel8080, Intel8085, StrictIntel8085, Z80, StrictZ80, EZ80, Z80Next)
/**
* Processors that can run code for Intel 8085
*/
@@ -415,6 +425,8 @@ object Cpu extends Enumeration {
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, EmitIntel8085Opcodes, UseIntelSyntaxForInput, UseIntelSyntaxForOutput)
case StrictZ80 | Z80 =>
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, UseIxForStack, UseShadowRegistersForInterrupts)
case Z80Next =>
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, UseIxForStack, UseShadowRegistersForInterrupts, EmitIllegals, EmitZ80NextOpcodes)
case EZ80 =>
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, UseIxForStack, UseShadowRegistersForInterrupts, EmitEZ80Opcodes)
case Sharp =>
@@ -456,6 +468,7 @@ object Cpu extends Enumeration {
case "strict2a07" => StrictRicoh
case "z80" => Z80
case "strictz80" => Z80
case "zx80next" => Z80Next
// disabled for now:
// case "ez80" => EZ80
case "gameboy" => Sharp
@@ -504,7 +517,7 @@ object CompilationFlag extends Enumeration {
EmitCmosOpcodes, EmitCmosNopOpcodes, EmitHudsonOpcodes, Emit65CE02Opcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes,
PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, SoftwareStack,
// compilation options for I80
EmitIntel8080Opcodes, EmitIntel8085Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, EmitEZ80Opcodes, EmitSharpOpcodes,
EmitIntel8080Opcodes, EmitIntel8085Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, EmitEZ80Opcodes, EmitSharpOpcodes, EmitZ80NextOpcodes,
UseShadowRegistersForInterrupts,
UseIxForStack, UseIyForStack,
UseIxForScratch, UseIyForScratch,
@@ -29,6 +29,8 @@ object ZOpcode extends Enumeration {
LD_DESP, LD_DEHL, RRHL, RLDE, DSUB, RSTV, LHLX, SHLX,
//sharp:
LD_AHLI, LD_AHLD, LD_HLIA, LD_HLDA, SWAP, LDH_DA, LDH_AD, LDH_CA, LDH_AC, LD_HLSP, ADD_SP, STOP,
// next:
LDIX, LDWS, LDIRX, LDDX, LDDRX, LDPIRX, OUTINB, MUL, SWAPNIB, MIRROR, NEXTREG, PIXELDN, PIXELAD, SETAE, TEST,
DISCARD_A, DISCARD_F, DISCARD_HL, DISCARD_BC, DISCARD_DE, DISCARD_IX, DISCARD_IY, CHANGED_MEM,
LABEL, BYTE = Value
}
@@ -69,19 +71,25 @@ object ZOpcodeClasses {
val ChangesBCAlways: Set[ZOpcode.Value] = Set(
INI, INIR, OUTI, OUTIR, IND, INDR, OUTD, OUTDR,
LDI, LDIR, LDD, LDDR, CPI, CPIR, CPD, CPDR,
LDIX, LDIRX, LDDX, LDDRX, LDPIRX,
EXX, CALL, JR, JP, LABEL, DJNZ)
val ChangesHLAlways: Set[ZOpcode.Value] = Set(
INI, INIR, OUTI, OUTIR, IND, INDR, OUTD, OUTDR,
LDI, LDIR, LDD, LDDR, CPI, CPIR, CPD, CPDR,
LD_AHLI, LD_AHLD, LD_HLIA, LD_HLDA, LD_HLSP, DSUB,
RRHL, LHLX,
LDWS, LDIX, LDIRX, LDDX, LDDRX, LDPIRX, PIXELAD, PIXELDN, OUTINB,
EXX, EX_DE_HL, CALL, JR, JP, LABEL)
val ChangesDEAlways: Set[ZOpcode.Value] = Set(
LDI, LDIR, LDD, LDDR,
LD_DESP, LD_DEHL, RLDE,
LDWS, LDIX, LDIRX, LDDX, LDDRX, LDPIRX, MUL,
EXX, EX_DE_HL, CALL, JR, JP, LABEL)
val ChangesOnlyRegister: Set[ZOpcode.Value] = Set(INC, DEC, INC_16, DEC_16, POP, EX_SP, IN_C, IN_IMM, RL, RR, RLC, RRC, SLA, SRA, SRL, SLL) ++ SET ++ RES
val ChangesFirstRegister: Set[ZOpcode.Value] = Set(LD, LD_16, ADD_16, SBC_16)
val ChangesAAlways: Set[ZOpcode.Value] = Set(DAA, ADD, ADC, SUB, SBC, XOR, OR, AND, LD_AHLI, LD_AHLD, RIM)
val ChangesAAlways: Set[ZOpcode.Value] = Set(
DAA, ADD, ADC, SUB, SBC, XOR, OR, AND, LD_AHLI, LD_AHLD, RIM,
MIRROR, SETAE,
)
val NonLinear: Set[ZOpcode.Value] = Set(JP, JR, CALL, LABEL, BYTE, EXX, EX_DE_HL, EX_SP, EXX, RET, RETI, RETN, HALT, RST, RSTV)
}
@@ -69,6 +69,8 @@ class Z80Assembler(program: Program,
def requireIntel8085Illegals(): Unit = if (!options.flag(EmitIntel8085Opcodes) || !options.flag(EmitIllegals)) log.error("Unsupported instruction: " + instr)
def requireNext(): Unit = if (!options.flag(EmitZ80NextOpcodes)) log.error("Unsupported instruction: " + instr)
def useSharpOpcodes():Boolean = {
if (!options.flag(EmitSharpOpcodes) && !options.flag(EmitIntel8080Opcodes))
log.error("Cannot determine which variant to emit : " + instr)
@@ -144,6 +146,47 @@ class Z80Assembler(program: Program,
writeByte(bank, index, 0xed)
writeByte(bank, index + 1, edImplieds(op))
index + 2
case ZLine0(op, NoRegisters, _) if nextEdImplieds.contains(op) =>
requireNext()
writeByte(bank, index, 0xed)
writeByte(bank, index + 1, nextEdImplieds(op))
index + 2
case ZLine0(ADD_16, TwoRegisters(r@(ZRegister.HL | ZRegister.BC | ZRegister.DE), ZRegister.A), _) =>
requireNext()
writeByte(bank, index, 0xed)
writeByte(bank, index + 1, 0x33 - internalRegisterIndex(r))
index + 2
case ZLine0(ADD_16, TwoRegisters(r@(ZRegister.HL | ZRegister.BC | ZRegister.DE), ZRegister.IMM_16), nn) =>
requireNext()
writeByte(bank, index, 0xed)
writeByte(bank, index + 1, 0x36 - internalRegisterIndex(r))
writeWord(bank, index + 2, nn)
index + 4
case ZLine0(PUSH, OneRegister(ZRegister.IMM_16), nn) =>
requireNext()
writeByte(bank, index, 0xed)
writeByte(bank, index + 1, 0x8A)
writeByte(bank, index + 2, nn.hiByte)
writeByte(bank, index + 3, nn.loByte)
index + 4
case ZLine0(TEST, OneRegister(ZRegister.IMM_8), nn) =>
requireNext()
writeByte(bank, index, 0xed)
writeByte(bank, index + 1, 0x27)
writeByte(bank, index + 2, nn)
index + 3
case ZLine0(NEXTREG, TwoRegisters(ZRegister.IMM_8, ZRegister.IMM_8), nn) =>
requireNext()
writeByte(bank, index, 0xed)
writeByte(bank, index + 1, 0x91)
writeWord(bank, index + 2, nn)
index + 4
case ZLine0(NEXTREG, TwoRegisters(ZRegister.IMM_8, ZRegister.A), nn) =>
requireNext()
writeByte(bank, index, 0xed)
writeByte(bank, index + 1, 0x92)
writeByte(bank, index + 2, nn)
index + 3
case ZLine0(ADD_16, TwoRegisters(ZRegister.HL, source), _) =>
writeByte(bank, index, 9 + 16 * internalRegisterIndex(source))
index + 1
@@ -776,6 +819,7 @@ object Z80Assembler {
val edImplieds: mutable.Map[ZOpcode.Value, Int] = mutable.Map[ZOpcode.Value, Int]()
val oneRegister: mutable.Map[ZOpcode.Value, One] = mutable.Map[ZOpcode.Value, One]()
val cbOneRegister: mutable.Map[ZOpcode.Value, One] = mutable.Map[ZOpcode.Value, One]()
val nextEdImplieds: mutable.Map[ZOpcode.Value, Int] = mutable.Map[ZOpcode.Value, Int]()
do {
import ZOpcode._
@@ -846,6 +890,20 @@ object Z80Assembler {
cbOneRegister(SLA) = One(0x20, 1)
cbOneRegister(SRA) = One(0x28, 1)
cbOneRegister(SRL) = One(0x38, 1)
nextEdImplieds(LDIX) = 0xa4
nextEdImplieds(LDWS) = 0xa5
nextEdImplieds(LDIRX) = 0xb4
nextEdImplieds(LDDX) = 0xb5
nextEdImplieds(LDDRX) = 0xac
nextEdImplieds(LDPIRX) = 0xbc
nextEdImplieds(OUTINB) = 0x90
nextEdImplieds(MUL) = 0xa4
nextEdImplieds(SWAPNIB) = 0x23
nextEdImplieds(MIRROR) = 0x24
nextEdImplieds(PIXELDN) = 0x93
nextEdImplieds(PIXELAD) = 0x94
nextEdImplieds(SETAE) = 0x95
} while (false)
}
@@ -452,6 +452,20 @@ case class Z80Parser(filename: String,
case (r1, e1, (r2, e2)) => merge(LD, LD_16, skipTargetA = false)((r1, e1, r2, e2))
}
case "ADD" => (param(allowAbsolute = false) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ param(allowAbsolute = false)).map {
case (ZRegister.HL, None, (ZRegister.A, None)) if options.flags(CompilationFlag.EmitZ80NextOpcodes) =>
(ADD_16, TwoRegisters(ZRegister.HL, ZRegister.A), None, zero)
case (ZRegister.DE, None, (ZRegister.A, None)) if options.flags(CompilationFlag.EmitZ80NextOpcodes) =>
(ADD_16, TwoRegisters(ZRegister.DE, ZRegister.A), None, zero)
case (ZRegister.BC, None, (ZRegister.A, None)) if options.flags(CompilationFlag.EmitZ80NextOpcodes) =>
(ADD_16, TwoRegisters(ZRegister.BC, ZRegister.A), None, zero)
case (ZRegister.HL, None, (ZRegister.IMM_8, Some(expr))) =>
(ADD_16, TwoRegisters(ZRegister.HL, ZRegister.IMM_16), None, expr)
case (ZRegister.DE, None, (ZRegister.IMM_8, Some(expr))) =>
(ADD_16, TwoRegisters(ZRegister.DE, ZRegister.IMM_16), None, expr)
case (ZRegister.BC, None, (ZRegister.IMM_8, Some(expr))) =>
(ADD_16, TwoRegisters(ZRegister.BC, ZRegister.IMM_16), None, expr)
case (ZRegister.SP, None, (ZRegister.IMM_8, Some(expr))) if options.flags(CompilationFlag.EmitSharpOpcodes) =>
(ADD_SP, OneRegister(ZRegister.IMM_8), None, expr)
case (r1, e1, (r2, e2)) => merge(ADD, ADD_16, skipTargetA = true)((r1, e1, r2, e2))
@@ -466,6 +480,28 @@ case class Z80Parser(filename: String,
case "DSUB" => imm(DSUB)
case "RSTV" => imm(RSTV)
case "LDIX" => imm(LDIX)
case "LDWS" => imm(LDWS)
case "LDIRX" => imm(LDIRX)
case "LDDX" => imm(LDDX)
case "LDDRX" => imm(LDDRX)
case "LDPIRX" => imm(LDPIRX)
case "OUTINB" => imm(OUTINB)
case "SWAPNIB" => imm(SWAPNIB)
case "PIXELDN" => imm(PIXELDN)
case "PIXELAD" => imm(PIXELAD)
case "SETAE" => imm(SETAE)
case "MUL" => (("D"|"d") ~ HWS ~ "," ~/ HWS ~ ("E" | "e")).?.map { _ => (MUL, NoRegisters, None, zero)}
case "MIRROR" => ("A"|"a").?.map { _ => (MUL, NoRegisters, None, zero)}
case "NEXTREG" =>(param(allowAbsolute = false) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ param(allowAbsolute = false)).map {
case (ZRegister.IMM_8, Some(n), (ZRegister.A, None)) => (NEXTREG, TwoRegisters(ZRegister.IMM_8, ZRegister.A), None, n)
case (ZRegister.IMM_8, Some(n), (ZRegister.IMM_8, Some(v))) => (NEXTREG, TwoRegisters(ZRegister.IMM_8, ZRegister.IMM_8), None, SeparateBytesExpression(v, n))
case _ =>
log.error("Invalid parameters for NEXTREG", Some(pos))
(NOP, NoRegisters, None, zero)
}
case "TEST" => one8Register(TEST)
case _ =>
log.error("Unsupported opcode " + opcode, Some(pos))
imm(NOP)
@@ -1,7 +1,7 @@
package millfork.test
import millfork.Cpu
import millfork.test.emu.{EmuUnoptimizedCrossPlatformRun, EmuUnoptimizedIntel8080Run, EmuUnoptimizedIntel8085Run, EmuUnoptimizedSharpRun, EmuUnoptimizedZ80Run}
import millfork.test.emu.{EmuUnoptimizedCrossPlatformRun, EmuUnoptimizedIntel8080Run, EmuUnoptimizedIntel8085Run, EmuUnoptimizedSharpRun, EmuUnoptimizedZ80NextRun, EmuUnoptimizedZ80Run}
import org.scalatest.{FunSuite, Matchers}
/**
@@ -975,4 +975,41 @@ class Z80AssemblySuite extends FunSuite with Matchers {
| }
""".stripMargin)
}
test("Z80 Next instructions (Zilog syntax)") {
EmuUnoptimizedZ80NextRun(
"""
| #pragma zilog_syntax
| asm void main () {
| ret
| ldix
| ldws
| ldirx
| lddx
| lddrx
| ldpirx
| outinb
| mul
| mul d,e
| add hl,a
| add de,a
| add bc,a
| add hl,1
| add de,2
| add bc,3
| swapnib
| mirror
| mirror a
| push $5555
| nextreg 1,2
| nextreg 1,a
| pixeldn
| pixelad
| setae
| test 8
| ret
| }
""".stripMargin)
}
}
@@ -22,6 +22,8 @@ object EmuUnoptimizedIntel8080Run extends EmuZ80Run(Cpu.Intel8080, Nil, Nil)
object EmuUnoptimizedIntel8085Run extends EmuZ80Run(Cpu.Intel8085, Nil, Nil)
object EmuUnoptimizedZ80NextRun extends EmuZ80Run(Cpu.Z80Next, Nil, Nil)
object EmuUnoptimizedIntel8086Run extends EmuI86Run(Nil, Nil)
object EmuUnoptimizedSharpRun extends EmuZ80Run(Cpu.Sharp, Nil, Nil)
@@ -83,7 +83,8 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio
CompilationFlag.OptimizeStdlib -> this.inline,
CompilationFlag.OptimizeForSize -> this.optimizeForSize,
CompilationFlag.SubroutineExtraction -> optimizeForSize,
CompilationFlag.EmitIllegals -> (cpu == millfork.Cpu.Z80 || cpu == millfork.Cpu.Intel8085),
CompilationFlag.EmitIllegals -> (cpu == millfork.Cpu.Z80 || cpu == millfork.Cpu.Intel8085 || cpu == millfork.Cpu.Z80Next),
CompilationFlag.EmitZ80NextOpcodes -> (cpu == millfork.Cpu.Z80Next),
CompilationFlag.LenientTextEncoding -> true)
val options = CompilationOptions(platform, millfork.Cpu.defaultFlags(cpu).map(_ -> true).toMap ++ extraFlags, None, 0, Map(), JobContext(log, new LabelGenerator))
println(cpu)