From 7ea2fe6a4e80e550eb6bafe49afc150a4e80e8af Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Fri, 27 Jul 2018 19:07:12 +0200 Subject: [PATCH] Almost full LR35902 opcode space coverage --- docs/README.md | 2 +- docs/abi/undefined-behaviour.md | 2 +- docs/abi/undocumented.md | 8 ++++ docs/index.md | 2 +- docs/lang/assemblyz80.md | 9 +++- docs/lang/interfacing.md | 2 +- .../scala/millfork/assembly/z80/ZLine.scala | 6 +++ .../scala/millfork/assembly/z80/ZOpcode.scala | 2 +- src/main/scala/millfork/node/Node.scala | 6 +-- .../scala/millfork/output/Z80Assembler.scala | 42 ++++++++++++++++++- .../scala/millfork/parser/Z80Parser.scala | 38 +++++++++++++---- 11 files changed, 100 insertions(+), 19 deletions(-) diff --git a/docs/README.md b/docs/README.md index 0ff81f09..ba8923b9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -27,7 +27,7 @@ * [Inline 6502 assembly syntax](lang/assembly.md) -* [Inline Z80 assembly syntax](lang/assemblyz80.md) +* [Inline 8080/LR35902/Z80 assembly syntax](lang/assemblyz80.md) * [Important guidelines regarding reentrancy](lang/reentrancy.md) diff --git a/docs/abi/undefined-behaviour.md b/docs/abi/undefined-behaviour.md index b748b9d8..9180b2d7 100644 --- a/docs/abi/undefined-behaviour.md +++ b/docs/abi/undefined-behaviour.md @@ -28,7 +28,7 @@ even up to hardware damage. * on ROM-based platforms: using global variables with an initial value (they will not be initialized!) -* violating the [safe assembly rules](../lang/assembly.md) +* violating the safe assembly rules ([6502](../lang/assembly.md), [8080/LR35902/Z80](../lang/assemblyz80.md)) * violating the [safe reentrancy rules](../lang/reentrancy.md) diff --git a/docs/abi/undocumented.md b/docs/abi/undocumented.md index 4578cf64..0daa16ab 100644 --- a/docs/abi/undocumented.md +++ b/docs/abi/undocumented.md @@ -2,6 +2,8 @@ # Undocumented opcodes +## 6502 + Original 6502 processors accidentally supported a bunch of extra undocumented instructions. Millfork can emit them if so desired. @@ -61,3 +63,9 @@ and either it must appear in an assembly block or it may be a result of optimiza Optimization will never emit any of the following opcodes due to their instability and/or uselessness: AHX, LAS, LXA, SHX, SHY, TAS, XAA. + +## Z80 + +Original Z80 processors accidentally supported a bunch of extra undocumented instructions. +Millfork will not emit them. +The only exception is SLL, which will be emitted if it occurs in a handwritten assembly block. diff --git a/docs/index.md b/docs/index.md index 0ff81f09..ba8923b9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -27,7 +27,7 @@ * [Inline 6502 assembly syntax](lang/assembly.md) -* [Inline Z80 assembly syntax](lang/assemblyz80.md) +* [Inline 8080/LR35902/Z80 assembly syntax](lang/assemblyz80.md) * [Important guidelines regarding reentrancy](lang/reentrancy.md) diff --git a/docs/lang/assemblyz80.md b/docs/lang/assemblyz80.md index 68e57f29..04e857bf 100644 --- a/docs/lang/assemblyz80.md +++ b/docs/lang/assemblyz80.md @@ -1,6 +1,6 @@ [< back to index](../index.md) -# Using Intel8080/Z80 assembly within Millfork programs +# Using 8080/LR35902/Z80 assembly within Millfork programs There are two ways to include raw assembly code in your Millfork programs: @@ -10,13 +10,18 @@ There are two ways to include raw assembly code in your Millfork programs: ## Assembly syntax -Millfork uses Zilog syntax for Intel 8080 and Z80 assembly. Intel syntax is not supported. +Millfork uses Zilog syntax for Intel 8080, Z80 and LR35902 assembly. Intel syntax is not supported. Indexing via the IX/IY register uses the following syntax: `IX(1)` + +LR35902 instructions that load/store the accumulator indirectly via HL and then increment/decrement HL are written +`LD A,(HLI)`, `LD, A,(HLD)`, `LD (HLI),A` and `LD (HLD),A` Only instructions available on the current CPU architecture are available. Undocumented instructions are not supported, except for `SLL`. +LR35902 instructions for faster access to the $FFxx addresses are not available yet. + Labels have to be followed by a colon and they can optionally be on a separate line. Indentation is not important: diff --git a/docs/lang/interfacing.md b/docs/lang/interfacing.md index 98848a47..a26fe24b 100644 --- a/docs/lang/interfacing.md +++ b/docs/lang/interfacing.md @@ -28,7 +28,7 @@ For more details about how to pass parameters to `asm` functions, see: * for 6502: [Using 6502 assembly within Millfork programs#Assembly functions](./assembly.md#assembly-functions). -* for Z80: [Using Z80 assembly within Millfork programs#Assembly functions](./assembly.md#assembly-functions). +* for Z80: [Using Z80 assembly within Millfork programs#Assembly functions](./assemblyz80.md#assembly-functions). ## Calling external functions at a dynamic address diff --git a/src/main/scala/millfork/assembly/z80/ZLine.scala b/src/main/scala/millfork/assembly/z80/ZLine.scala index 5761b09f..89bda500 100644 --- a/src/main/scala/millfork/assembly/z80/ZLine.scala +++ b/src/main/scala/millfork/assembly/z80/ZLine.scala @@ -241,6 +241,12 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta case RST => s" RST $parameter" case IM => s" IM $parameter" case EX_AF_AF => " EX AF,AF'" + case LD_AHLI => " LD A,(HLI)" + case LD_AHLD => " LD A,(HLD)" + case LD_HLIA => " LD (HLI),A" + case LD_HLDA => " LD (HLD),A" + case LD_HLSP => " LD HL,SP+" + parameter + case ADD_SP => " ADD SP," + parameter case EX_SP => registers match { case OneRegister(r) => s" EX (SP),${asAssemblyString(r)}" case _ => ??? diff --git a/src/main/scala/millfork/assembly/z80/ZOpcode.scala b/src/main/scala/millfork/assembly/z80/ZOpcode.scala index fe86172c..87f95963 100644 --- a/src/main/scala/millfork/assembly/z80/ZOpcode.scala +++ b/src/main/scala/millfork/assembly/z80/ZOpcode.scala @@ -23,7 +23,7 @@ object ZOpcode extends Enumeration { RST, IM, EI, DI, DJNZ, JP, JR, CALL, RET, RETN, RETI, HALT, //sharp: - LD_AHLI, LD_AHLD, LD_HLIA, LD_HLDA, SWAP, LD_H, + LD_AHLI, LD_AHLD, LD_HLIA, LD_HLDA, SWAP, LD_H, LD_HLSP, ADD_SP, STOP, DISCARD_A, DISCARD_F, DISCARD_HL, DISCARD_BCDEIX, LABEL, BYTE = Value } diff --git a/src/main/scala/millfork/node/Node.scala b/src/main/scala/millfork/node/Node.scala index a032952a..ab0bb404 100644 --- a/src/main/scala/millfork/node/Node.scala +++ b/src/main/scala/millfork/node/Node.scala @@ -138,17 +138,17 @@ object ZRegister extends Enumeration { val A, B, C, D, E, H, L, AF, BC, HL, DE, SP, IXH, IXL, IYH, IYL, IX, IY, R, I, MEM_HL, MEM_BC, MEM_DE, MEM_IX_D, MEM_IY_D, MEM_ABS_8, MEM_ABS_16, IMM_8, IMM_16 = Value - def size(reg: Value): Int = reg match { + def registerSize(reg: Value): Int = reg match { case AF | BC | DE | HL | IX | IY | IMM_16 => 2 case A | B | C | D | E | H | L | IXH | IXL | IYH | IYL | R | I | IMM_8 => 1 } - def matchingImm(target: ZRegister.Value): ZRegister.Value = size(target) match { + def matchingImm(target: ZRegister.Value): ZRegister.Value = registerSize(target) match { case 1 => IMM_8 case 2 => IMM_16 } - def matchingMemAbs(target: ZRegister.Value): ZRegister.Value = size(target) match { + def matchingMemAbs(target: ZRegister.Value): ZRegister.Value = registerSize(target) match { case 1 => MEM_ABS_8 case 2 => MEM_ABS_16 } diff --git a/src/main/scala/millfork/output/Z80Assembler.scala b/src/main/scala/millfork/output/Z80Assembler.scala index a294516b..85512cfd 100644 --- a/src/main/scala/millfork/output/Z80Assembler.scala +++ b/src/main/scala/millfork/output/Z80Assembler.scala @@ -63,7 +63,7 @@ class Z80Assembler(program: Program, options.flag(EmitSharpOpcodes) } - instr match { + try { instr match { case ZLine(LABEL, NoRegisters, MemoryAddressConstant(Label(labelName)), _) => labelMap(labelName) = index index @@ -142,10 +142,12 @@ class Z80Assembler(program: Program, writeByte(bank, index + 1, 9 + 16 * internalRegisterIndex(source)) index + 2 case ZLine(ADC_16, TwoRegisters(ZRegister.HL, reg), _, _) => + requireZ80() writeByte(bank, index, 0xed) writeByte(bank, index + 1, 0x4a + 0x10 * internalRegisterIndex(reg)) index + 2 case ZLine(SBC_16, TwoRegisters(ZRegister.HL, reg), _, _) => + requireZ80() writeByte(bank, index, 0xed) writeByte(bank, index + 1, 0x42 + 0x10 * internalRegisterIndex(reg)) index + 2 @@ -342,11 +344,13 @@ class Z80Assembler(program: Program, writeByte(bank, index, 0x1a) index + 1 case TwoRegistersOffset(ix@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), source, offset) => + requireZ80() writeByte(bank, index, prefixByte(ix)) writeByte(bank, index + 1, 0x40 + internalRegisterIndex(source) + internalRegisterIndex(ZRegister.MEM_HL) * 8) writeByte(bank, index + 2, offset) index + 3 case TwoRegistersOffset(target, ix@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), offset) => + requireZ80() writeByte(bank, index, prefixByte(ix)) writeByte(bank, index + 1, 0x40 + internalRegisterIndex(ZRegister.MEM_HL) + internalRegisterIndex(target) * 8) writeByte(bank, index + 2, offset) @@ -568,9 +572,44 @@ class Z80Assembler(program: Program, requireIntel8080() writeByte(bank, index, 0xeb) index + 1 + + case ZLine(ADD_SP, _, param, _) => + requireSharp() + writeByte(bank, index, 0xe8) + writeByte(bank, index + 1, param) + index + 2 + case ZLine(LD_HLSP, _, param, _) => + requireSharp() + writeByte(bank, index, 0xf8) + writeByte(bank, index + 1, param) + index + 2 + case ZLine(LD_AHLI, _, _, _) => + requireSharp() + writeByte(bank, index, 0x2a) + index + 1 + case ZLine(LD_AHLD, _, _, _) => + requireSharp() + writeByte(bank, index, 0x3a) + index + 1 + case ZLine(LD_HLIA, _, _, _) => + requireSharp() + writeByte(bank, index, 0x22) + index + 1 + case ZLine(LD_HLDA, _, _, _) => + requireSharp() + writeByte(bank, index, 0x32) + index + 1 + case ZLine(STOP, _, _, _) => + requireSharp() + writeByte(bank, index, 0x10) + index + 1 case _ => ErrorReporting.fatal("Cannot assemble " + instr) index + } } catch { + case _ : MatchError => + ErrorReporting.fatal("Cannot assemble " + instr) + index } } @@ -613,7 +652,6 @@ object Z80Assembler { immediates(CP) = 0xfe edImplieds(NEG) = 0x44 - edImplieds(RETI) = 0x4d edImplieds(RETN) = 0x45 edImplieds(RRD) = 0x67 edImplieds(RLD) = 0x6f diff --git a/src/main/scala/millfork/parser/Z80Parser.scala b/src/main/scala/millfork/parser/Z80Parser.scala index 8fe6e0ea..025b8a80 100644 --- a/src/main/scala/millfork/parser/Z80Parser.scala +++ b/src/main/scala/millfork/parser/Z80Parser.scala @@ -4,7 +4,7 @@ import java.util.Locale import fastparse.all.{parserApi, _} import fastparse.core -import millfork.CompilationOptions +import millfork.{CompilationFlag, CompilationOptions} import millfork.assembly.z80.{ZOpcode, _} import millfork.env.{ByZRegister, Constant, ParamPassingConvention} import millfork.error.ErrorReporting @@ -92,16 +92,16 @@ case class Z80Parser(filename: String, input: String, currentDirectory: String, } private val jumpCondition: P[ZRegisters] = (HWS ~ ( - "NZ" | "nz" | "nc" | "NC" | + "NZ" | "nz" | "nc" | "NC" | "NV" | "nv" | "PO" | "po" | "PE" | "pe" | "m" | "M" | "p" | "P" | - "c" | "C" | "Z" | "z").! ~ HWS).map { + "c" | "C" | "Z" | "z" | "V" | "v").! ~ HWS).map { case "Z" | "z" => IfFlagSet(ZFlag.Z) - case "PE" | "pe" => IfFlagSet(ZFlag.P) + case "PE" | "pe" | "v" | "V" => IfFlagSet(ZFlag.P) case "C" | "c" => IfFlagSet(ZFlag.C) case "M" | "m" => IfFlagSet(ZFlag.S) case "NZ" | "nz" => IfFlagClear(ZFlag.Z) - case "PO" | "po" => IfFlagClear(ZFlag.P) + case "PO" | "po" | "NV" | "nv" => IfFlagClear(ZFlag.P) case "NC" | "nc" => IfFlagClear(ZFlag.C) case "P" | "p" => IfFlagClear(ZFlag.S) case _ => NoRegisters // shouldn't happen @@ -162,12 +162,13 @@ case class Z80Parser(filename: String, input: String, currentDirectory: String, el <- elidable pos <- position() opcode: String <- identifier ~/ HWS - tuple4/*: (ZOpcode.Value, ZRegisters, Option[Expression], Expression)*/ <- opcode.toUpperCase(Locale.ROOT) match { + tuple4: (ZOpcode.Value, ZRegisters, Option[Expression], Expression) <- opcode.toUpperCase(Locale.ROOT) match { case "RST" => asmExpression.map((RST, NoRegisters, None, _)) case "IM" => asmExpression.map((IM, NoRegisters, None, _)) case "EI" => imm(EI) case "DI" => imm(DI) case "HALT" => imm(HALT) + case "STOP" => imm(STOP) case "RETN" => imm(RETN) case "RETI" => imm(RETI) @@ -209,6 +210,7 @@ case class Z80Parser(filename: String, input: String, currentDirectory: String, case "SLL" => one8Register(SLL) case "SRA" => one8Register(SRA) case "SRL" => one8Register(SRL) + case "SWAP" => one8Register(SWAP) case "BIT" => (param(false) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ param(false)).map { case (ZRegister.IMM_8, Some(LiteralExpression(n, _)), (r2, e2)) @@ -295,7 +297,7 @@ case class Z80Parser(filename: String, input: String, currentDirectory: String, } case "EX" => (asmExpressionWithParens ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ asmExpressionWithParensOrApostrophe).map { p: (Expression, Boolean, (Expression, Boolean)) => p match { - case (VariableExpression("AF" | "af"), false, (VariableExpression("AF" | "af"), true)) => + case (VariableExpression("AF" | "af"), false, (VariableExpression("AF" | "af"), _)) => (EX_AF_AF, NoRegisters, None, zero) case (VariableExpression("DE" | "de"), false, (VariableExpression("HL" | "hl"), false)) => (EX_DE_HL, NoRegisters, None, zero) @@ -314,9 +316,31 @@ case class Z80Parser(filename: String, input: String, currentDirectory: String, } case "LD" => (param(allowAbsolute = true, allowRI = true) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ param(allowAbsolute = true, allowRI = true)).map { + case (ZRegister.HL, None, (ZRegister.IMM_8 | ZRegister.IMM_16, Some(SumExpression((false, VariableExpression("sp" | "SP")) :: offset, false)))) + if options.flags(CompilationFlag.EmitSharpOpcodes) => + (LD_HLSP, OneRegister(ZRegister.IMM_8), None, offset match { + case List((false, expr)) => expr + case (true, _) :: _ => SumExpression((false -> LiteralExpression(0, 1)) :: offset, decimal = false) + case _ => SumExpression(offset, decimal = false) + }) + case (ZRegister.A, None, (ZRegister.MEM_ABS_8, Some(VariableExpression("HLI" | "hli")))) + if options.flags(CompilationFlag.EmitSharpOpcodes) => + (LD_AHLI, NoRegisters, None, zero) + case (ZRegister.A, None, (ZRegister.MEM_ABS_8, Some(VariableExpression("HLD" | "hld")))) + if options.flags(CompilationFlag.EmitSharpOpcodes) => + (LD_AHLD, NoRegisters, None, zero) + case (ZRegister.MEM_ABS_8, Some(VariableExpression("HLI" | "hli")), (ZRegister.A, None)) + if options.flags(CompilationFlag.EmitSharpOpcodes) => + (LD_HLIA, NoRegisters, None, zero) + case (ZRegister.MEM_ABS_8, Some(VariableExpression("HLD" | "hld")), (ZRegister.A, None)) + if options.flags(CompilationFlag.EmitSharpOpcodes) => + (LD_HLDA, NoRegisters, None, zero) + 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.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)) } case "ADC" => (param(allowAbsolute = false) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ param(allowAbsolute = false)).map {