From b24ac32932599cc0fee38b67f2bd28fc85461a57 Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Fri, 24 Jul 2020 17:27:37 +0200 Subject: [PATCH] Z80: Support IXH/IXL/IYH/IYL registers. Add Intel syntax for Z80 instructions. --- docs/abi/undocumented.md | 7 +- docs/lang/assemblyz80.md | 116 ++++++- .../scala/millfork/assembly/z80/ZLine.scala | 168 +++++++++- .../scala/millfork/output/Z80Assembler.scala | 39 +++ .../scala/millfork/parser/Z80Parser.scala | 225 ++++++++++++- .../millfork/test/Z80AssemblySuite.scala | 307 ++++++++++++++++++ .../scala/millfork/test/emu/EmuZ80Run.scala | 5 +- 7 files changed, 858 insertions(+), 9 deletions(-) diff --git a/docs/abi/undocumented.md b/docs/abi/undocumented.md index cd9515a1..6606f70d 100644 --- a/docs/abi/undocumented.md +++ b/docs/abi/undocumented.md @@ -69,8 +69,11 @@ 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. +Millfork will emit some of them if used in an assembly block: + +* `SLL` – supported +* instructions using the IXH, IXL, IYH, IYL registers – supported (can only be used in Zilog syntax) +* instructions of the form `RLC IX(1),B` – not supported ## 8085 diff --git a/docs/lang/assemblyz80.md b/docs/lang/assemblyz80.md index 889a48f7..4d97aa19 100644 --- a/docs/lang/assemblyz80.md +++ b/docs/lang/assemblyz80.md @@ -22,10 +22,15 @@ LR35902 instructions that load/store the accumulator indirectly via HL and then LR35902 instructions for faster access to the $FFxx addresses use the `LDH` mnemonic: `LDH A,(4)`, `LDH (C),A` etc. 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`. +Undocumented Z80 instructions are partially supported: +* `SLL` – supported +* instructions using the IXH, IXL, IYH, IYL registers – supported (can only be used in Zilog syntax) +* instructions of the form `RLC IX(1),B` – not supported -Not all ZX Spectrum Next are supported. `JP (C)`, `BSLA` and similar instructions are not supported. +Intel syntax supports the 8080 instructions, the documented Z80 instructions and `SLL`. +It does not support instructions that are unavailable on the Z80 or other undocumented Z80 instructions. + +Not all ZX Spectrum Next instructions 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: @@ -200,3 +205,108 @@ it should abide to the following rules: * end non-inline assembly functions with `RET`, `JP`, `RETI` or `RETN` (Zilog) / `RET` or `JMP` (Intel) as appropriate The above list is not exhaustive. + +## Z80 instructions in the Intel syntax + +Millfork uses the same extensions for Intel syntax as Z80.LIB from Digital Research. +Some mnemonics from the TDL Z80 Relocating/Linking Assembler are also supported. + +In the list below, `c` is a flag, `r` is a register, and `n` and `d` are parameters. +For instructions using the index registers, only the IY variant is given; +the IX variant has the same mnemonic, but with `Y` replaced with `X`. + +Intel syntax | Zilog syntax +----|---- +**EXAF** | **EX AF,AF'** +**JR n**, JMPR n | **JR n** +**JRc n** | **JR c,n** +**INP r** | **IN r,(C)** +**OUTP r** | **OUT r,(C)** +**CCI** | **CPI** +**CCIR** | **CPIR** +**CCD** | **CPD** +**CCDR** | **CPDR** +**OUTIR** | **OTIR**, OUTIR +**OUTDR** | **OTDR**, OUTDR +**IM0** | **IM 0** +**IM1** | **IM 1** +**IM2** | **IM 2** +**DSBC r** | **SBC HL,rr** +**DADC r** | **ADC HL,rr** +**DADY r** | **ADD IY,rr** +**INXIY**, INX IY | **INC IY** +**DCXIY**, DCX IY | **DEC IY** +**SBCD nn** | **LD (nn),BC** +**SDED nn** | **LD (nn),DE** +**SSPD nn** | **LD (nn),SP** +**SIYD nn** | **LD (nn),IY** +**LBCD nn** | **LD BC,(nn)** +**LDED nn** | **LD DE,(nn)** +**LSPD nn** | **LD SP,(nn)** +**LIYD nn** | **LD IY,(nn)** +**SETB n,r**, SET n,r | **SET n,r** +**BITY n,d** | **BIT n,IY(d)** +**SETY n,d** | **SET n,IY(d)** +**RESY n,d** | **RES n,IY(d)** +**PCIY** | **JP IY** +**RLCR r** | **RLC r** +**RALR r** | **RL r** +**RRCR r** | **RRC r** +**RARR r** | **RR r** +**SLAR r** | **SLA r** +**SRAR r** | **SRA r** +**SRLR r** | **SRL r** +**RLCX r** | **RLC r** +**RALY d** | **RL IY(d)** +**RRCY d** | **RRC IY(d)** +**RARY d** | **RR IY(d)** +**SLAY d** | **SLA IY(d)** +**SRAY d** | **SRA IY(d)** +**SRLY d** | **SRL IY(d)** +**SLLR r** | **SLL r** +**SLLY d** | **SLL IY(d)** +**SPIY** | **LD SP,IY** +**PUSHIY**, PUSH IY | **PUSH IY** +**POPIY**, POP IY | **POP IY** +**XTIY** | **EX (SP),IY** +**LDAI** | **LD A,I** +**LDAR** | **LD A,R** +**STAI** | **LD I,A** +**STAR** | **LD R,A** +**LXIY nn**, LXI IY,nn | **LD IY,nn** +**ADDY d** | **ADD A,IY(d)** +**ADCY d** | **ADC A,IY(d)** +**SUBY d** | **SUB IY(d)** +**SBCY d** | **SBC A,IY(d)** +**ANDY d** | **AND IY(d)** +**XORY d** | **XOR IY(d)** +**ORY d** | **OR IY(d)** +**CMPY d** | **CMP IY(d)** +**INRY d** | **INC IY(d)** +**DCRY d** | **DEC IY(d)** +**MVIY n,d** | **LD IY(d),n** +**LDY r,d** | **LD r,IY(d)** +**STY r,d** | **LD IY(d),r** + + +Instructions that are the same in both syntaxes: + +**BIT n,r**, +**RES n,r**, +**DJNZ n**, +**EXX**, +**NEG**, +**RETI**, +**RETN**, +**RLD**, +**RRD**, +**LDI**, +**LDIR**, +**LDD**, +**LDDR**, +**INI**, +**INIR**, +**IND**, +**INDR**, +**OUTI**, +**OUTD** diff --git a/src/main/scala/millfork/assembly/z80/ZLine.scala b/src/main/scala/millfork/assembly/z80/ZLine.scala index e103fc98..ee4fe96b 100644 --- a/src/main/scala/millfork/assembly/z80/ZLine.scala +++ b/src/main/scala/millfork/assembly/z80/ZLine.scala @@ -275,7 +275,7 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta override def isPrintable: Boolean = true - private def asAssemblyString(r: ZRegister.Value, offset: Int = 0): String = r match { + private def asAssemblyString(r: ZRegister.Value, offset: Int = 666): String = r match { case ZRegister.A => "A" case ZRegister.B => "B" case ZRegister.C => "C" @@ -325,6 +325,8 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta case ZRegister.IMM_8 => s"$parameter" case ZRegister.IMM_16 => s"$parameter" case ZRegister.MEM_HL => "M" + case ZRegister.IX => "IX" + case ZRegister.IY => "IY" case _ => "???" } @@ -467,6 +469,12 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta case DISCARD_IY => " ; DISCARD_IY" case BYTE => " DB " + parameter.toString case LD => registers match { + case TwoRegistersOffset(MEM_IX_D, IMM_8, offset) => s" MVIX ${parameter.toIntelString}, ${offset}" + case TwoRegistersOffset(MEM_IY_D, IMM_8, offset) => s" MVIY ${parameter.toIntelString}, ${offset}" + case TwoRegistersOffset(MEM_IX_D, source, offset) => s" STX ${asIntelAssemblyString(source)}, ${offset}" + case TwoRegistersOffset(MEM_IY_D, source, offset) => s" STY ${asIntelAssemblyString(source)}, ${offset}" + case TwoRegistersOffset(target, MEM_IX_D, offset) => s" LDX ${asIntelAssemblyString(target)}, ${offset}" + case TwoRegistersOffset(target, MEM_IY_D, offset) => s" LDY ${asIntelAssemblyString(target)}, ${offset}" case TwoRegisters(target, IMM_8) => s" MVI ${asIntelAssemblyString(target)}, ${parameter.toIntelString}" case TwoRegisters(A, MEM_ABS_8) => s" LDA ${parameter.toIntelString}" case TwoRegisters(MEM_ABS_8, A) => s" STA ${parameter.toIntelString}" @@ -474,50 +482,169 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta case TwoRegisters(MEM_BC, A) => " STAX B" case TwoRegisters(A, MEM_DE) => " LDAX D" case TwoRegisters(MEM_DE, A) => " STAX D" + case TwoRegisters(I, A) => " STAI" + case TwoRegisters(A, I) => " LDAI" + case TwoRegisters(R, A) => " STAR" + case TwoRegisters(A, R) => " LDAR" case TwoRegisters(target, source) => s" MOV ${asIntelAssemblyString(target)}, ${asIntelAssemblyString(source)}" case _ => "???" } case LD_16 => registers match { case TwoRegisters(SP, HL) => " SPHL" + case TwoRegisters(SP, IX) => " SPIX" + case TwoRegisters(SP, IY) => " SPIY" + case TwoRegisters(IX, IMM_16) => s" LXIX ${parameter.toIntelString}" + case TwoRegisters(IY, IMM_16) => s" LXIY ${parameter.toIntelString}" case TwoRegisters(target, IMM_16) => s" LXI ${asIntelAssemblyString(target)}, ${parameter.toIntelString}" case TwoRegisters(HL, MEM_ABS_16) => s" LHLD ${parameter.toIntelString}" + case TwoRegisters(BC, MEM_ABS_16) => s" LBCD ${parameter.toIntelString}" + case TwoRegisters(DE, MEM_ABS_16) => s" LDED ${parameter.toIntelString}" + case TwoRegisters(IX, MEM_ABS_16) => s" LIXD ${parameter.toIntelString}" + case TwoRegisters(IY, MEM_ABS_16) => s" LIYD ${parameter.toIntelString}" + case TwoRegisters(SP, MEM_ABS_16) => s" LSPD ${parameter.toIntelString}" case TwoRegisters(MEM_ABS_16, HL) => s" SHLD ${parameter.toIntelString}" + case TwoRegisters(MEM_ABS_16, BC) => s" SBCD ${parameter.toIntelString}" + case TwoRegisters(MEM_ABS_16, DE) => s" SDED ${parameter.toIntelString}" + case TwoRegisters(MEM_ABS_16, IX) => s" SIXD ${parameter.toIntelString}" + case TwoRegisters(MEM_ABS_16, IY) => s" SIYD ${parameter.toIntelString}" + case TwoRegisters(MEM_ABS_16, SP) => s" SSPD ${parameter.toIntelString}" case _ => "???" } case ADD_16 => registers match { + case TwoRegisters(IX, source) => s" DADX ${asIntelAssemblyString(source)}" + case TwoRegisters(IY, source) => s" DADY ${asIntelAssemblyString(source)}" case TwoRegisters(HL, source) => s" DAD ${asIntelAssemblyString(source)}" case _ => "???" } + case ADC_16 => registers match { + case TwoRegisters(HL, source) => s" DADC ${asIntelAssemblyString(source)}" + case _ => "???" + } + case SBC_16 => registers match { + case TwoRegisters(HL, source) => s" DSBC ${asIntelAssemblyString(source)}" + case _ => "???" + } case DEC_16 => registers match { + case OneRegister(IX) => s" DCXIX" + case OneRegister(IY) => s" DCXIY" case OneRegister(register) => s" DCX ${asIntelAssemblyString(register)}" case _ => "???" } case INC_16 => registers match { + case OneRegister(IX) => s" INXIX" + case OneRegister(IY) => s" INXIY" case OneRegister(register) => s" INX ${asIntelAssemblyString(register)}" case _ => "???" } case DEC => registers match { + case OneRegisterOffset(MEM_IX_D, offset) => s" DCRX ${offset}" + case OneRegisterOffset(MEM_IY_D, offset) => s" DCRY ${offset}" case OneRegister(register) => s" DCR ${asIntelAssemblyString(register)}" case _ => "???" } case INC => registers match { + case OneRegisterOffset(MEM_IX_D, offset) => s" INRX ${offset}" + case OneRegisterOffset(MEM_IY_D, offset) => s" INRY ${offset}" case OneRegister(register) => s" INR ${asIntelAssemblyString(register)}" case _ => "???" } + case op if ZOpcodeClasses.RES(op) => registers match { + case OneRegisterOffset(MEM_IX_D, offset) => s" RESX ${ZOpcodeClasses.RES_seq.indexOf(op)},${offset}" + case OneRegisterOffset(MEM_IY_D, offset) => s" RESY ${ZOpcodeClasses.RES_seq.indexOf(op)},${offset}" + case OneRegister(register) => s" RES ${ZOpcodeClasses.RES_seq.indexOf(op)},${asIntelAssemblyString(register)}" + case _ => "???" + } + case op if ZOpcodeClasses.SET(op) => registers match { + case OneRegisterOffset(MEM_IX_D, offset) => s" SETX ${ZOpcodeClasses.SET_seq.indexOf(op)},${offset}" + case OneRegisterOffset(MEM_IY_D, offset) => s" SETY ${ZOpcodeClasses.SET_seq.indexOf(op)},${offset}" + case OneRegister(register) => s" SETB ${ZOpcodeClasses.SET_seq.indexOf(op)},${asIntelAssemblyString(register)}" + case _ => "???" + } + case op if ZOpcodeClasses.BIT(op) => registers match { + case OneRegisterOffset(MEM_IX_D, offset) => s" BITX ${ZOpcodeClasses.BIT_seq.indexOf(op)},${offset}" + case OneRegisterOffset(MEM_IY_D, offset) => s" BITY ${ZOpcodeClasses.BIT_seq.indexOf(op)},${offset}" + case OneRegister(register) => s" BIT ${ZOpcodeClasses.BIT_seq.indexOf(op)},${asIntelAssemblyString(register)}" + case _ => "???" + } case PUSH => registers match { + case OneRegister(IX) => s" PUSHIX" + case OneRegister(IY) => s" PUSHIY" case OneRegister(register) => s" PUSH ${asIntelAssemblyString(register)}" case _ => "???" } case POP => registers match { + case OneRegister(IX) => s" POPIX" + case OneRegister(IY) => s" POPIY" case OneRegister(register) => s" POP ${asIntelAssemblyString(register)}" case _ => "???" } + case IM => parameter match { + case NumericConstant(0, _) => s" IM0" + case NumericConstant(1, _) => s" IM1" + case NumericConstant(2, _) => s" IM2" + case _ => "???" + } + case RL => registers match { + case OneRegister(register) => s" RALR ${asIntelAssemblyString(register)}" + case OneRegisterOffset(MEM_IX_D, offset) => s" RALX $offset" + case OneRegisterOffset(MEM_IY_D, offset) => s" RALY $offset" + case _ => "???" + } + case RLC => registers match { + case OneRegister(register) => s" RLCR ${asIntelAssemblyString(register)}" + case OneRegisterOffset(MEM_IX_D, offset) => s" RLCX $offset" + case OneRegisterOffset(MEM_IY_D, offset) => s" RLCY $offset" + case _ => "???" + } + case RR => registers match { + case OneRegister(register) => s" RARR ${asIntelAssemblyString(register)}" + case OneRegisterOffset(MEM_IX_D, offset) => s" RARX $offset" + case OneRegisterOffset(MEM_IY_D, offset) => s" RARY $offset" + case _ => "???" + } + case RRC => registers match { + case OneRegister(register) => s" RRCR ${asIntelAssemblyString(register)}" + case OneRegisterOffset(MEM_IX_D, offset) => s" RRCX $offset" + case OneRegisterOffset(MEM_IY_D, offset) => s" RRCY $offset" + case _ => "???" + } + case SLA => registers match { + case OneRegister(register) => s" SLAR ${asIntelAssemblyString(register)}" + case OneRegisterOffset(MEM_IX_D, offset) => s" SLAX $offset" + case OneRegisterOffset(MEM_IY_D, offset) => s" SLAY $offset" + case _ => "???" + } + case SLL => registers match { + case OneRegister(register) => s" SLLR ${asIntelAssemblyString(register)}" + case OneRegisterOffset(MEM_IX_D, offset) => s" SLLX $offset" + case OneRegisterOffset(MEM_IY_D, offset) => s" SLLY $offset" + case _ => "???" + } + case SRA => registers match { + case OneRegister(register) => s" SRAR ${asIntelAssemblyString(register)}" + case OneRegisterOffset(MEM_IX_D, offset) => s" SRAX $offset" + case OneRegisterOffset(MEM_IY_D, offset) => s" SRAY $offset" + case _ => "???" + } + case SRL => registers match { + case OneRegister(register) => s" SRLR ${asIntelAssemblyString(register)}" + case OneRegisterOffset(MEM_IX_D, offset) => s" SRLX $offset" + case OneRegisterOffset(MEM_IY_D, offset) => s" SRLY $offset" + case _ => "???" + } case DAA => " DAA" case RLA => " RAL" case RLCA => " RLC" case RRA => " RAR" case RRCA => " RRC" case HALT => " HLT" + case LDI|LDIR|LDD|LDDR|INI|INIR|IND|INDR|OUTI|OUTD|EXX|NEG|RLD|RRD|RETN|RETI => " " + opcode + case OUTIR => " OUTIR" + case OUTDR => " OUTDR" + case CPI => " CCI" + case CPIR => " CCIR" + case CPD => " CCD" + case CPDR => " CCDR" case RET => registers match { case NoRegisters => " RET" case IfFlagClear(ZFlag.C) => " RNC" @@ -532,6 +659,8 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta } case JP => registers match { case OneRegister(HL) => " PCHL" + case OneRegister(IX) => " PCIX" + case OneRegister(IY) => " PCIY" case NoRegisters => s" JMP ${parameter.toIntelString}" case IfFlagClear(ZFlag.C) => s" JNC ${parameter.toIntelString}" case IfFlagClear(ZFlag.Z) => s" JNZ ${parameter.toIntelString}" @@ -545,6 +674,15 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta case IfFlagSet(ZFlag.K) => s" JK ${parameter.toIntelString}" case _ => "???" } + case JR => registers match { + case NoRegisters => s" JR ${parameter.toIntelString}" + case IfFlagClear(ZFlag.C) => s" JRNC ${parameter.toIntelString}" + case IfFlagClear(ZFlag.Z) => s" JRNZ ${parameter.toIntelString}" + case IfFlagSet(ZFlag.C) => s" JRC ${parameter.toIntelString}" + case IfFlagSet(ZFlag.Z) => s" JRZ ${parameter.toIntelString}" + case _ => "???" + } + case DJNZ => s" DJNZ ${parameter.toIntelString}" case CALL => registers match { case NoRegisters => s" CALL ${parameter.toIntelString}" case IfFlagClear(ZFlag.C) => s" CNC ${parameter.toIntelString}" @@ -559,52 +697,78 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta } case ADD => registers match { case OneRegister(IMM_8) => s" ADI ${parameter.toIntelString}" + case OneRegisterOffset(MEM_IX_D, offset) => s" ADDX ${offset}" + case OneRegisterOffset(MEM_IY_D, offset) => s" ADDY ${offset}" case OneRegister(register) => s" ADD ${asIntelAssemblyString(register)}" case _ => "???" } case ADC => registers match { case OneRegister(IMM_8) => s" ACI ${parameter.toIntelString}" + case OneRegisterOffset(MEM_IX_D, offset) => s" ADCX ${offset}" + case OneRegisterOffset(MEM_IY_D, offset) => s" ADCY ${offset}" case OneRegister(register) => s" ADC ${asIntelAssemblyString(register)}" case _ => "???" } case SUB => registers match { case OneRegister(IMM_8) => s" SUI ${parameter.toIntelString}" + case OneRegisterOffset(MEM_IX_D, offset) => s" SUBX ${offset}" + case OneRegisterOffset(MEM_IY_D, offset) => s" SUBY ${offset}" case OneRegister(register) => s" SUB ${asIntelAssemblyString(register)}" case _ => "???" } case SBC => registers match { case OneRegister(IMM_8) => s" SBI ${parameter.toIntelString}" + case OneRegisterOffset(MEM_IX_D, offset) => s" SBCX ${offset}" + case OneRegisterOffset(MEM_IY_D, offset) => s" SBCY ${offset}" case OneRegister(register) => s" SBB ${asIntelAssemblyString(register)}" case _ => "???" } case AND => registers match { case OneRegister(IMM_8) => s" ANI ${parameter.toIntelString}" + case OneRegisterOffset(MEM_IX_D, offset) => s" ANDX ${offset}" + case OneRegisterOffset(MEM_IY_D, offset) => s" ANDY ${offset}" case OneRegister(register) => s" ANA ${asIntelAssemblyString(register)}" case _ => "???" } case OR => registers match { case OneRegister(IMM_8) => s" ORI ${parameter.toIntelString}" + case OneRegisterOffset(MEM_IX_D, offset) => s" ORX ${offset}" + case OneRegisterOffset(MEM_IY_D, offset) => s" ORY ${offset}" case OneRegister(register) => s" ORA ${asIntelAssemblyString(register)}" case _ => "???" } case XOR => registers match { case OneRegister(IMM_8) => s" XRI ${parameter.toIntelString}" + case OneRegisterOffset(MEM_IX_D, offset) => s" XORX ${offset}" + case OneRegisterOffset(MEM_IY_D, offset) => s" XORY ${offset}" case OneRegister(register) => s" XRA ${asIntelAssemblyString(register)}" case _ => "???" } case CP => registers match { case OneRegister(IMM_8) => s" CPI ${parameter.toIntelString}" + case OneRegisterOffset(MEM_IX_D, offset) => s" CMPX ${offset}" + case OneRegisterOffset(MEM_IY_D, offset) => s" CMPY ${offset}" case OneRegister(register) => s" CMP ${asIntelAssemblyString(register)}" case _ => "???" } case EX_SP => registers match { case OneRegister(HL) => s" XTHL" + case OneRegister(IX) => s" XTIX" + case OneRegister(IY) => s" XTIY" case _ => "???" } case RST => parameter match { case NumericConstant(n, _) if n % 8 == 0 => s" RST ${n / 8}" case _ => "???" } + case IN_C => registers match { + case OneRegister(register) => s" INP ${asIntelAssemblyString(register)}" + case _ => "???" + } + case OUT_C => registers match { + case OneRegister(register) => s" OUTP ${asIntelAssemblyString(register)}" + case _ => "???" + } case IN_IMM => s" IN ${parameter.toIntelString}" case OUT_IMM => s" OUT ${parameter.toIntelString}" case EI => " EI" @@ -616,6 +780,8 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta case CCF => " CMC" case EI => " EI" + case EX_AF_AF => " EXAF" + case DSUB => " DSUB" case RRHL => " ARHL" case RLDE => " RLDE" diff --git a/src/main/scala/millfork/output/Z80Assembler.scala b/src/main/scala/millfork/output/Z80Assembler.scala index a179b72b..23ed5623 100644 --- a/src/main/scala/millfork/output/Z80Assembler.scala +++ b/src/main/scala/millfork/output/Z80Assembler.scala @@ -32,6 +32,10 @@ class Z80Assembler(program: Program, case ZRegister.E => 3 case ZRegister.H => 4 case ZRegister.L => 5 + case ZRegister.IXH => 4 + case ZRegister.IXL => 5 + case ZRegister.IYH => 4 + case ZRegister.IYL => 5 case ZRegister.MEM_HL => 6 case ZRegister.MEM_IX_D => 6 case ZRegister.MEM_IY_D => 6 @@ -46,6 +50,8 @@ class Z80Assembler(program: Program, private def prefixByte(reg: ZRegister.Value): Int = reg match { case ZRegister.IX | ZRegister.MEM_IX_D => 0xdd case ZRegister.IY | ZRegister.MEM_IY_D => 0xfd + case ZRegister.IXH | ZRegister.IXL => 0xdd + case ZRegister.IYH | ZRegister.IYL => 0xfd } override def emitInstruction(bank: String, options: CompilationOptions, index: Int, instr: ZLine): Int = { @@ -317,6 +323,12 @@ class Z80Assembler(program: Program, writeByte(bank, index + 1, o.opcode + internalRegisterIndex(ZRegister.HL) * o.multiplier) writeByte(bank, index + 2, instr.parameter) index + 3 + case ZLine0(op, OneRegister(ix@(IXH | IYH | IXL | IYL)), _) if oneRegister.contains(op) => + requireZ80Illegals() + val o = oneRegister(op) + writeByte(bank, index, prefixByte(ix)) + writeByte(bank, index + 1, o.opcode + internalRegisterIndex(ix) * o.multiplier) + index + 2 case ZLine0(op, OneRegister(reg), _) if reg != ZRegister.IMM_8 && oneRegister.contains(op) => val o = oneRegister(op) writeByte(bank, index, o.opcode + internalRegisterIndex(reg) * o.multiplier) @@ -428,6 +440,33 @@ class Z80Assembler(program: Program, writeByte(bank, index + 1, 0x40 + internalRegisterIndex(ZRegister.MEM_HL) + internalRegisterIndex(target) * 8) writeByte(bank, index + 2, offset) index + 3 + case TwoRegisters(target@(IXH | IYH | IXL | IYL), source@(A | B | C | D | E)) => + requireZ80Illegals() + writeByte(bank, index, prefixByte(target)) + writeByte(bank, index, 0x40 + internalRegisterIndex(source) + internalRegisterIndex(target) * 8) + index + 2 + case TwoRegisters(target@(A | B | C | D | E), source@(IXH | IYH | IXL | IYL)) => + requireZ80Illegals() + writeByte(bank, index, prefixByte(source)) + writeByte(bank, index, 0x40 + internalRegisterIndex(source) + internalRegisterIndex(target) * 8) + index + 2 + case TwoRegisters(target@(IXH | IXL), source@(IXH | IXL)) => + requireZ80Illegals() + writeByte(bank, index, prefixByte(source)) + writeByte(bank, index, 0x40 + internalRegisterIndex(source) + internalRegisterIndex(target) * 8) + index + 2 + case TwoRegisters(target@(IYH | IYL), source@(IYH | IYL)) => + requireZ80Illegals() + writeByte(bank, index, prefixByte(source)) + writeByte(bank, index, 0x40 + internalRegisterIndex(source) + internalRegisterIndex(target) * 8) + index + 2 + case TwoRegisters(target@(H | L), source@(H | L)) => + writeByte(bank, index, 0x40 + internalRegisterIndex(source) + internalRegisterIndex(target) * 8) + index + 1 + case TwoRegisters(target@(IXH | IYH | IXL | IYL | H | L), source@(IXH | IYH | IXL | IYL | H | L)) => + log.error("Cannot assemble " + instr) + writeByte(bank, index, 0x40 + internalRegisterIndex(source) + internalRegisterIndex(target) * 8) + index + 1 case TwoRegisters(target, source) => writeByte(bank, index, 0x40 + internalRegisterIndex(source) + internalRegisterIndex(target) * 8) index + 1 diff --git a/src/main/scala/millfork/parser/Z80Parser.scala b/src/main/scala/millfork/parser/Z80Parser.scala index f10b4b15..8989568b 100644 --- a/src/main/scala/millfork/parser/Z80Parser.scala +++ b/src/main/scala/millfork/parser/Z80Parser.scala @@ -9,7 +9,7 @@ import millfork.{CompilationFlag, CompilationOptions, node} import millfork.assembly.z80.{ZOpcode, _} import millfork.env.{ByZRegister, Constant, ParamPassingConvention} import millfork.error.ConsoleLogger -import millfork.node._ +import millfork.node.{ZRegister, _} import millfork.output.{MemoryAlignment, NoAlignment, WithinPageAlignment} /** @@ -73,6 +73,13 @@ case class Z80Parser(filename: String, "SP" -> ZRegister.SP, "sp" -> ZRegister.SP, ) + private val toIndexHalf: Map[String, ZRegister.Value] = Map( + "IXH" -> ZRegister.IXH, "ixh" -> ZRegister.IXH, + "IXL" -> ZRegister.IXL, "ixl" -> ZRegister.IXL, + "IYH" -> ZRegister.IYH, "iyh" -> ZRegister.IYH, + "IYL" -> ZRegister.IYL, "iyl" -> ZRegister.IYL, + ) + private val toIntelRegister8: Map[String, ZRegister.Value] = Map( "A" -> ZRegister.A, "a" -> ZRegister.A, "B" -> ZRegister.B, "b" -> ZRegister.B, @@ -86,16 +93,25 @@ case class Z80Parser(filename: String, private val toIntelRegister16: Map[String, ZRegister.Value] = Map( "H" -> ZRegister.HL, "h" -> ZRegister.HL, + "HL" -> ZRegister.HL, "hl" -> ZRegister.HL, "PSW" -> ZRegister.AF, "psw" -> ZRegister.AF, "B" -> ZRegister.BC, "b" -> ZRegister.BC, + "BC" -> ZRegister.BC, "bc" -> ZRegister.BC, "D" -> ZRegister.DE, "d" -> ZRegister.DE, + "DE" -> ZRegister.DE, "de" -> ZRegister.DE, "SP" -> ZRegister.SP, "sp" -> ZRegister.SP, + "IX" -> ZRegister.IX, "ix" -> ZRegister.IX, + "IY" -> ZRegister.IY, "iy" -> ZRegister.IY, ) private def param(allowAbsolute: Boolean, allowRI: Boolean = false, allowFfc: Boolean = false): P[(ZRegister.Value, Option[Expression])] = asmExpressionWithParens.map { case (VariableExpression("R" | "r"), false) if allowRI => (ZRegister.R, None) case (VariableExpression("I" | "i"), false) if allowRI => (ZRegister.I, None) case (VariableExpression(r), false) if toRegister.contains(r)=> (toRegister(r), None) + case (VariableExpression(r), false) + if options.flag(CompilationFlag.EmitZ80Opcodes) && + options.flag(CompilationFlag.EmitIllegals) && + toIndexHalf.contains(r)=> (toIndexHalf(r), None) case (SumExpression(List( (false, LiteralExpression(0xff00, _)), (false, VariableExpression("C" | "c")) @@ -138,6 +154,8 @@ case class Z80Parser(filename: String, case (reg, addr) => (op, OneRegister(reg), None, addr.getOrElse(zero)) } + def one8RegisterIntel(op: ZOpcode.Value): P[(ZOpcode.Value, OneRegister, Option[Expression], Expression)] = intel8.map(reg => (op, OneRegister(reg), None, zero)) + def one8RegisterOr8085Illegal(op: ZOpcode.Value, illegalTarget: ZRegister.Value, illegalOp: ZOpcode.Value): P[(ZOpcode.Value, ZRegisters, Option[Expression], Expression)] = param(allowAbsolute = false).map{ case (reg@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), Some(e)) => (op, OneRegister(reg), Some(e), zero) case (reg, _) if reg == illegalTarget && options.flag(CompilationFlag.EmitIntel8085Opcodes) && options.flag(CompilationFlag.EmitIllegals) => @@ -237,6 +255,27 @@ case class Z80Parser(filename: String, private val jumpConditionWithComma: P[ZRegisters] = (jumpCondition ~ "," ~/ HWS).?.map (_.getOrElse(NoRegisters)) private val jumpConditionWithoutComma: P[ZRegisters] = (jumpCondition ~/ HWS).?.map (_.getOrElse(NoRegisters)) + private val indexHalves: Set[ZRegister.Value] = { + import ZRegister._ + Set(IXH, IXL, IYH, IYL) + } + private val MEM_R: Set[ZRegister.Value] = { + import ZRegister._ + Set(MEM_HL,MEM_IX_D,MEM_IY_D) + } + + def invalidIllegalRegisters(r1: ZRegister.Value, r2: ZRegister.Value): Boolean = { + import ZRegister._ + if (MEM_R(r1) && MEM_R(r2)) return true + if (!indexHalves(r1) && !indexHalves(r2)) return false + if (!options.flag(CompilationFlag.EmitIllegals) && !options.flag(CompilationFlag.EmitZ80Opcodes) && (indexHalves(r1) || indexHalves(r2))) return true + if ((r1 == H || r1 == L || r1 == MEM_IX_D || r1 == MEM_IY_D) && indexHalves(r2)) return true + if ((r2 == H || r2 == L || r2 == MEM_IX_D || r2 == MEM_IY_D) && indexHalves(r1)) return true + if ((r1 == IYH || r1 == IYL) && (r2 == IXH || r2 == IXL)) return true + if ((r1 == IXH || r1 == IXL) && (r2 == IYH || r2 == IYL)) return true + false // TODO + } + val zilogAsmInstruction: P[ExecutableStatement] = { import ZOpcode._ for { @@ -449,7 +488,11 @@ case class Z80Parser(filename: String, 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 (r1, e1, (r2, e2)) => + if (invalidIllegalRegisters(r1,r2)) { + log.error("Invalid parameters for LD", Some(pos)) + } + merge(LD, LD_16, skipTargetA = false)((r1, e1, r2, e2)) } case "ADD" => (param(allowAbsolute = false) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ param(allowAbsolute = false)).map { @@ -534,13 +577,73 @@ case class Z80Parser(filename: String, case "RIM" => imm(RIM) case "SIM" => imm(SIM) + case "EXAF" => imm(EX_AF_AF) + case "EXX" => imm(EXX) + case "NEG" => imm(NEG) + case "IM0" => P("").map(_ => (IM, NoRegisters, None, LiteralExpression(0, 1))) + case "IM1" => P("").map(_ => (IM, NoRegisters, None, LiteralExpression(1, 1))) + case "IM2" => P("").map(_ => (IM, NoRegisters, None, LiteralExpression(2, 1))) + case "RETI" => imm(RETI) + case "RETN" => imm(RETN) + case "RLD" => imm(RLD) + case "RRD" => imm(RRD) + case "INI" => imm(INI) + case "INIR" => imm(INIR) + case "IND" => imm(IND) + case "INDR" => imm(INDR) + case "OUTI" => imm(OUTI) + case "OUTIR" => imm(OUTIR) + case "OUTD" => imm(OUTD) + case "OUTDR" => imm(OUTDR) + case "LDI" => imm(LDI) + case "LDIR" => imm(LDIR) + case "LDD" => imm(LDD) + case "LDDR" => imm(LDDR) + case "CCI" => imm(CPI) + case "CCIR" => imm(CPIR) + case "CCD" => imm(CPD) + case "CCDR" => imm(CPDR) + + case "RALR" => one8RegisterIntel(RL) + case "RARR" => one8RegisterIntel(RR) + case "RLCR" => one8RegisterIntel(RLC) + case "RRCR" => one8RegisterIntel(RRC) + case "SLAR" => one8RegisterIntel(SLA) + case "SLLR" => one8RegisterIntel(SLL) + case "SRAR" => one8RegisterIntel(SRA) + case "SRLR" => one8RegisterIntel(SRL) + + case "RALX" => asmExpression.map(d => (RL, OneRegister(MEM_IX_D), Some(d), zero)) + case "RARX" => asmExpression.map(d => (RR, OneRegister(MEM_IX_D), Some(d), zero)) + case "RLCX" => asmExpression.map(d => (RLC, OneRegister(MEM_IX_D), Some(d), zero)) + case "RRCX" => asmExpression.map(d => (RRC, OneRegister(MEM_IX_D), Some(d), zero)) + case "SLAX" => asmExpression.map(d => (SLA, OneRegister(MEM_IX_D), Some(d), zero)) + case "SLLX" => asmExpression.map(d => (SLL, OneRegister(MEM_IX_D), Some(d), zero)) + case "SRAX" => asmExpression.map(d => (SRA, OneRegister(MEM_IX_D), Some(d), zero)) + case "SRLX" => asmExpression.map(d => (SRL, OneRegister(MEM_IX_D), Some(d), zero)) + + case "RALY" => asmExpression.map(d => (RL, OneRegister(MEM_IY_D), Some(d), zero)) + case "RARY" => asmExpression.map(d => (RR, OneRegister(MEM_IY_D), Some(d), zero)) + case "RLCY" => asmExpression.map(d => (RLC, OneRegister(MEM_IY_D), Some(d), zero)) + case "RRCY" => asmExpression.map(d => (RRC, OneRegister(MEM_IY_D), Some(d), zero)) + case "SLAY" => asmExpression.map(d => (SLA, OneRegister(MEM_IY_D), Some(d), zero)) + case "SLLY" => asmExpression.map(d => (SLL, OneRegister(MEM_IY_D), Some(d), zero)) + case "SRAY" => asmExpression.map(d => (SRA, OneRegister(MEM_IY_D), Some(d), zero)) + case "SRLY" => asmExpression.map(d => (SRL, OneRegister(MEM_IY_D), Some(d), zero)) + case "HLT" => imm(HALT) case "EI" => imm(EI) case "DI" => imm(DI) case "XCHG" => imm(EX_DE_HL) case "XTHL" => P("").map(_ => (EX_SP, OneRegister(HL), None, zero)) + case "XTIX" => P("").map(_ => (EX_SP, OneRegister(IX), None, zero)) + case "XTIY" => P("").map(_ => (EX_SP, OneRegister(IY), None, zero)) case "SPHL" => P("").map(_ => (LD_16, TwoRegisters(SP, HL), None, zero)) + case "SPIX" => P("").map(_ => (LD_16, TwoRegisters(SP, IX), None, zero)) + case "SPIY" => P("").map(_ => (LD_16, TwoRegisters(SP, IY), None, zero)) case "PCHL" => P("").map(_ => (JP, z80.OneRegister(HL), None, zero)) + case "PCIX" => P("").map(_ => (JP, z80.OneRegister(IX), None, zero)) + case "PCIY" => P("").map(_ => (JP, z80.OneRegister(IY), None, zero)) case "MOV" => (intel8 ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ intel8).map { case (r1, r2) => (LD, TwoRegisters(r1, r2), None, zero) @@ -548,10 +651,38 @@ case class Z80Parser(filename: String, case "LXI" => (intel16(false) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ asmExpression).map { case (r, e) => (LD_16, TwoRegisters(r, IMM_16), None, e) } + case "LXIX" => asmExpression.map { + case (e) => (LD_16, TwoRegisters(IX, IMM_16), None, e) + } + case "LXIY" => asmExpression.map { + case (e) => (LD_16, TwoRegisters(IY, IMM_16), None, e) + } case "MVI" => (intel8 ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ asmExpression).map { case (r, e) => (LD, TwoRegisters(r, IMM_8), None, e) } + case "MVIX" => (asmExpression ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ asmExpression).map { + case (e, d) => (LD, TwoRegisters(MEM_IX_D, IMM_8), Some(d), e) + } + case "MVIY" => (asmExpression ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ asmExpression).map { + case (e, d) => (LD, TwoRegisters(MEM_IY_D, IMM_8), Some(d), e) + } + case "LDX" => (intel8 ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ asmExpression).map { + case (r, d) => (LD, TwoRegisters(r, MEM_IX_D), Some(d), zero) + } + case "LDY" => (intel8 ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ asmExpression).map { + case (r, d) => (LD, TwoRegisters(r, MEM_IY_D), Some(d), zero) + } + case "STX" => (intel8 ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ asmExpression).map { + case (r, d) => (LD, TwoRegisters(MEM_IX_D, r), Some(d), zero) + } + case "STY" => (intel8 ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ asmExpression).map { + case (r, d) => (LD, TwoRegisters(MEM_IY_D, r), Some(d), zero) + } case "DAD" => intel16(false).map { r => (ADD_16, TwoRegisters(HL, r), None, zero)} + case "DADC" => intel16(false).map { r => (ADC_16, TwoRegisters(HL, r), None, zero)} + case "DSBC" => intel16(false).map { r => (SBC_16, TwoRegisters(HL, r), None, zero)} + case "DADX" => intel16(false).map { r => (ADD_16, TwoRegisters(IX, r), None, zero)} + case "DADY" => intel16(false).map { r => (ADD_16, TwoRegisters(IY, r), None, zero)} case "STAX" => intel16(false).map { case r@(ZRegister.BC | ZRegister.DE) => (LD, TwoRegisters(r, A), None, zero) case _ => @@ -565,19 +696,47 @@ case class Z80Parser(filename: String, (NOP, NoRegisters, None, zero) } case "INX" => intel16(false).map { r => (INC_16, OneRegister(r), None, zero)} + case "INXIX" => P("").map(_ => (INC_16, OneRegister(IX), None, zero)) + case "INXIY" => P("").map(_ => (INC_16, OneRegister(IY), None, zero)) case "DCX" => intel16(false).map { r => (DEC_16, OneRegister(r), None, zero)} + case "DCXIX" => P("").map(_ => (DEC_16, OneRegister(IX), None, zero)) + case "DCXIY" => P("").map(_ => (DEC_16, OneRegister(IY), None, zero)) case "POP" => intel16(true).map { r => (POP, OneRegister(r), None, zero)} + case "POPIX" => P("").map(_ => (POP, OneRegister(IX), None, zero)) + case "POPIY" => P("").map(_ => (POP, OneRegister(IY), None, zero)) case "PUSH" => intel16(true).map { r => (PUSH, OneRegister(r), None, zero)} + case "PUSHIX" => P("").map(_ => (PUSH, OneRegister(IX), None, zero)) + case "PUSHIY" => P("").map(_ => (PUSH, OneRegister(IY), None, zero)) case "INR" => intel8.map { r => (INC, OneRegister(r), None, zero)} + case "INRX" => asmExpression.map { d => (INC, OneRegister(MEM_IX_D), Some(d), zero)} + case "INRY" => asmExpression.map { d => (INC, OneRegister(MEM_IY_D), Some(d), zero)} case "DCR" => intel8.map { r => (DEC, OneRegister(r), None, zero)} + case "DCRX" => asmExpression.map { d => (DEC, OneRegister(MEM_IX_D), Some(d), zero)} + case "DCRY" => asmExpression.map { d => (DEC, OneRegister(MEM_IY_D), Some(d), zero)} case "ADD" => intel8.map { r => (ADD, OneRegister(r), None, zero)} + case "ADDX" => asmExpression.map { d => (ADD, OneRegister(MEM_IX_D), Some(d), zero)} + case "ADDY" => asmExpression.map { d => (ADD, OneRegister(MEM_IY_D), Some(d), zero)} case "SUB" => intel8.map { r => (SUB, OneRegister(r), None, zero)} + case "SUBX" => asmExpression.map { d => (SUB, OneRegister(MEM_IX_D), Some(d), zero)} + case "SUBY" => asmExpression.map { d => (SUB, OneRegister(MEM_IY_D), Some(d), zero)} case "ADC" => intel8.map { r => (ADC, OneRegister(r), None, zero)} + case "ADCX" => asmExpression.map { d => (ADC, OneRegister(MEM_IX_D), Some(d), zero)} + case "ADCY" => asmExpression.map { d => (ADC, OneRegister(MEM_IY_D), Some(d), zero)} case "SBB" => intel8.map { r => (SBC, OneRegister(r), None, zero)} + case "SBCX" => asmExpression.map { d => (SBC, OneRegister(MEM_IX_D), Some(d), zero)} + case "SBCY" => asmExpression.map { d => (SBC, OneRegister(MEM_IY_D), Some(d), zero)} case "ORA" => intel8.map { r => (OR, OneRegister(r), None, zero)} + case "ORX" => asmExpression.map { d => (OR, OneRegister(MEM_IX_D), Some(d), zero)} + case "ORY" => asmExpression.map { d => (OR, OneRegister(MEM_IY_D), Some(d), zero)} case "ANA" => intel8.map { r => (AND, OneRegister(r), None, zero)} + case "ANDX" => asmExpression.map { d => (AND, OneRegister(MEM_IX_D), Some(d), zero)} + case "ANDY" => asmExpression.map { d => (AND, OneRegister(MEM_IY_D), Some(d), zero)} case "XRA" => intel8.map { r => (XOR, OneRegister(r), None, zero)} + case "XORX" => asmExpression.map { d => (XOR, OneRegister(MEM_IX_D), Some(d), zero)} + case "XORY" => asmExpression.map { d => (XOR, OneRegister(MEM_IY_D), Some(d), zero)} case "CMP" => intel8.map { r => (CP, OneRegister(r), None, zero)} + case "CMPX" => asmExpression.map { d => (CP, OneRegister(MEM_IX_D), Some(d), zero)} + case "CMPY" => asmExpression.map { d => (CP, OneRegister(MEM_IY_D), Some(d), zero)} case "ADI" => asmExpression.map { e => (ADD, OneRegister(IMM_8), None, e)} case "ACI" => asmExpression.map { e => (ADC, OneRegister(IMM_8), None, e)} case "SUI" => asmExpression.map { e => (SUB, OneRegister(IMM_8), None, e)} @@ -589,8 +748,22 @@ case class Z80Parser(filename: String, case "SHLD" => asmExpression.map { e => (LD_16, TwoRegisters(MEM_ABS_16, HL), None, e)} case "LHLD" => asmExpression.map { e => (LD_16, TwoRegisters(HL, MEM_ABS_16), None, e)} + case "LBCD" => asmExpression.map { e => (LD_16, TwoRegisters(BC, MEM_ABS_16), None, e)} + case "SBCD" => asmExpression.map { e => (LD_16, TwoRegisters(MEM_ABS_16, BC), None, e)} + case "LDED" => asmExpression.map { e => (LD_16, TwoRegisters(DE, MEM_ABS_16), None, e)} + case "SDED" => asmExpression.map { e => (LD_16, TwoRegisters(MEM_ABS_16, DE), None, e)} + case "LSPD" => asmExpression.map { e => (LD_16, TwoRegisters(SP, MEM_ABS_16), None, e)} + case "SSPD" => asmExpression.map { e => (LD_16, TwoRegisters(MEM_ABS_16, SP), None, e)} + case "LIXD" => asmExpression.map { e => (LD_16, TwoRegisters(IX, MEM_ABS_16), None, e)} + case "SIXD" => asmExpression.map { e => (LD_16, TwoRegisters(MEM_ABS_16, IX), None, e)} + case "LIYD" => asmExpression.map { e => (LD_16, TwoRegisters(IY, MEM_ABS_16), None, e)} + case "SIYD" => asmExpression.map { e => (LD_16, TwoRegisters(MEM_ABS_16, IY), None, e)} case "STA" => asmExpression.map { e => (LD, TwoRegisters(MEM_ABS_8, A), None, e)} case "LDA" => asmExpression.map { e => (LD, TwoRegisters(A, MEM_ABS_8), None, e)} + case "LDAI" => P("").map(_ => (LD, TwoRegisters(A, I), None, zero)) + case "LDAR" => P("").map(_ => (LD, TwoRegisters(A, R), None, zero)) + case "STAI" => P("").map(_ => (LD, TwoRegisters(I, A), None, zero)) + case "STAR" => P("").map(_ => (LD, TwoRegisters(R, A), None, zero)) case "RST" => asmExpression.map { case LiteralExpression(value, _) if value >=0 && value <= 7=> (RST, NoRegisters, None, LiteralExpression(value * 8, 1)) case _ => @@ -598,10 +771,14 @@ case class Z80Parser(filename: String, (NOP, NoRegisters, None, zero) } + case "INP" => intel8.map { r => (IN_C, OneRegister(r), None, zero) } + case "OUTP" => intel8.map { r => (OUT_C, OneRegister(r), None, zero) } case "IN" => asmExpression.map { e => (IN_IMM, OneRegister(A), None, e) } case "OUT" => asmExpression.map { e => (OUT_IMM, OneRegister(A), None, e) } + case "DJNZ" => asmExpression.map { e => (DJNZ, NoRegisters, None, e)} case "JMP" => asmExpression.map { e => (JP, NoRegisters, None, e)} + case "JMPR" | "JR" => asmExpression.map { e => (JR, NoRegisters, None, e)} case "JC" => asmExpression.map { e => (JP, IfFlagSet(ZFlag.C), None, e)} case "JZ" => asmExpression.map { e => (JP, IfFlagSet(ZFlag.Z), None, e)} case "JM" => asmExpression.map { e => (JP, IfFlagSet(ZFlag.S), None, e)} @@ -610,6 +787,14 @@ case class Z80Parser(filename: String, case "JNZ" => asmExpression.map { e => (JP, IfFlagClear(ZFlag.Z), None, e)} case "JP" => asmExpression.map { e => (JP, IfFlagClear(ZFlag.S), None, e)} case "JPO" => asmExpression.map { e => (JP, IfFlagClear(ZFlag.P), None, e)} + case "JRC" => asmExpression.map { e => (JR, IfFlagSet(ZFlag.C), None, e)} + case "JRZ" => asmExpression.map { e => (JR, IfFlagSet(ZFlag.Z), None, e)} + case "JRM" => asmExpression.map { e => (JR, IfFlagSet(ZFlag.S), None, e)} + case "JRPE" => asmExpression.map { e => (JR, IfFlagSet(ZFlag.P), None, e)} + case "JRNC" => asmExpression.map { e => (JR, IfFlagClear(ZFlag.C), None, e)} + case "JRNZ" => asmExpression.map { e => (JR, IfFlagClear(ZFlag.Z), None, e)} + case "JRP" => asmExpression.map { e => (JR, IfFlagClear(ZFlag.S), None, e)} + case "JRPO" => asmExpression.map { e => (JR, IfFlagClear(ZFlag.P), None, e)} case "RET" => imm(RET) case "RC" => P("").map { _ => (RET, IfFlagSet(ZFlag.C), None, zero)} @@ -642,6 +827,18 @@ case class Z80Parser(filename: String, case "SHLDE" | "SHLX" => imm(SHLX) case "LHLDE" | "LHLX" =>imm(LHLX) + + case "RES" => parseSingleBitOpWithIntelSyntax(ZOpcodeClasses.RES_seq, "RES", pos) + case "SETB" => parseSingleBitOpWithIntelSyntax(ZOpcodeClasses.SET_seq, "SETB", pos) + case "SET" => parseSingleBitOpWithIntelSyntax(ZOpcodeClasses.SET_seq, "SET", pos) + case "BIT" => parseSingleBitOpWithIntelSyntax(ZOpcodeClasses.BIT_seq, "BIT", pos) + case "RESX" => parseSingleBitIndexedOpWithIntelSyntax(ZOpcodeClasses.RES_seq, MEM_IX_D, "RESX", pos) + case "SETX" => parseSingleBitIndexedOpWithIntelSyntax(ZOpcodeClasses.SET_seq, MEM_IX_D, "SETX", pos) + case "BITX" => parseSingleBitIndexedOpWithIntelSyntax(ZOpcodeClasses.BIT_seq, MEM_IX_D, "BITX", pos) + case "RESY" => parseSingleBitIndexedOpWithIntelSyntax(ZOpcodeClasses.RES_seq, MEM_IY_D, "RESY", pos) + case "SETY" => parseSingleBitIndexedOpWithIntelSyntax(ZOpcodeClasses.SET_seq, MEM_IY_D, "SETY", pos) + case "BITY" => parseSingleBitIndexedOpWithIntelSyntax(ZOpcodeClasses.BIT_seq, MEM_IY_D, "BITY", pos) + case _ => log.error("Unsupported opcode " + opcode, Some(pos)) imm(NOP) @@ -652,6 +849,30 @@ case class Z80Parser(filename: String, } } + private def parseSingleBitOpWithIntelSyntax(OP_seq: IndexedSeq[ZOpcode.Value], opcodeString: String, pos: Position): P[(ZOpcode.Value, ZRegisters, Option[Expression], Expression)] = { + import ZOpcode.NOP + (param(allowAbsolute = false) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ intel8).map { + case (ZRegister.IMM_8, Some(LiteralExpression(n, _)), r2) + if n >= 0 && n <= 7 && r2 != ZRegister.MEM_BC && r2 != ZRegister.MEM_DE => + (OP_seq(n.toInt), OneRegister(r2), None, zero) + case _ => + log.error("Invalid parameters for " + opcodeString, Some(pos)) + (NOP, NoRegisters, None, zero) + } + } + + private def parseSingleBitIndexedOpWithIntelSyntax(OP_seq: IndexedSeq[ZOpcode.Value], memIndex: ZRegister.Value, opcodeString: String, pos: Position): P[(ZOpcode.Value, ZRegisters, Option[Expression], Expression)] = { + import ZOpcode.NOP + (param(allowAbsolute = false) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ asmExpression).map { + case (ZRegister.IMM_8, Some(LiteralExpression(n, _)), e2) + if n >= 0 && n <= 7 => + (OP_seq(n.toInt), OneRegister(memIndex), Some(e2), zero) + case _ => + log.error("Invalid parameters for " + opcodeString, Some(pos)) + (NOP, NoRegisters, None, zero) + } + } + val asmInstruction: P[ExecutableStatement] = if (useIntelSyntax) intelAsmInstruction else zilogAsmInstruction private def imm(opcode: ZOpcode.Value): P[(ZOpcode.Value, ZRegisters, Option[Expression], Expression)] = diff --git a/src/test/scala/millfork/test/Z80AssemblySuite.scala b/src/test/scala/millfork/test/Z80AssemblySuite.scala index ad21b760..4887a3e6 100644 --- a/src/test/scala/millfork/test/Z80AssemblySuite.scala +++ b/src/test/scala/millfork/test/Z80AssemblySuite.scala @@ -873,6 +873,281 @@ class Z80AssemblySuite extends FunSuite with Matchers { """.stripMargin) } + test("Extended I80 instructions (Intel syntax)") { + EmuUnoptimizedZ80Run( + """ + | #pragma intel_syntax + | asm void main () { + | ret + | + | reti + | + | jmpr main + | jrnz main + | jrz main + | jrnc main + | jrc main + | lspd 34 + | + | rlcr b + | rlcr c + | rlcr d + | rlcr e + | rlcr h + | rlcr l + | rlcr m + | rlcr a + | + | rrcr b + | rrcr c + | rrcr d + | rrcr e + | rrcr h + | rrcr l + | rrcr m + | rrcr a + | + | ralr b + | ralr c + | ralr d + | ralr e + | ralr h + | ralr l + | ralr m + | ralr a + | + | rarr b + | rarr c + | rarr d + | rarr e + | rarr h + | rarr l + | rarr m + | rarr a + | + | slar b + | slar c + | slar d + | slar e + | slar h + | slar l + | slar m + | slar a + | + | srar b + | srar c + | srar d + | srar e + | srar h + | srar l + | srar m + | srar a + | + | srlr b + | srlr c + | srlr d + | srlr e + | srlr h + | srlr l + | srlr m + | srlr a + | + | bit 1,a + | res 1,a + | setb 1,a + | set 1,a + | bit 1,m + | res 1,m + | setb 1,m + | set 1,m + | + | ret + | } + """.stripMargin) + } + + test("Z80 instructions with IX (Intel syntax)") { + EmuUnoptimizedZ80Run( + """ + | #pragma intel_syntax + | asm void main () { + | ret + | addx 0 + | adcx 0 + | subx 0 + | sbcx 0 + | andx 0 + | xorx 0 + | orx 0 + | cmpx 0 + | + | rrcx 0 + | rarx 0 + | rlcx 0 + | ralx 0 + | slax 0 + | srax 0 + | srlx 0 + | sllx 0 + | + | popix + | pushix + | pop ix + | push ix + | dadx sp + | dadx ix + | dadx d + | dadx b + | inxix + | dcxix + | inx ix + | dcx ix + | lxix 3 + | lixd 3 + | sixd 3 + | xtix + | pcix + | spix + | ldx a,0 + | stx a,0 + | + | ret + | } + """.stripMargin) + } + + test("Z80 instructions with IY (Intel syntax)") { + EmuUnoptimizedZ80Run( + """ + | #pragma intel_syntax + | asm void main () { + | ret + | addy 0 + | adcy 0 + | suby 0 + | sbcy 0 + | andy 0 + | xory 0 + | ory 0 + | cmpy 0 + | + | rrcy 0 + | rary 0 + | rlcy 0 + | raly 0 + | slay 0 + | sray 0 + | srly 0 + | slly 0 + | + | popiy + | pushiy + | pop iy + | push iy + | dady sp + | dady iy + | dady d + | dady b + | inxiy + | dcxiy + | inx iy + | dcx iy + | lxiy 3 + | liyd 3 + | siyd 3 + | xtiy + | pciy + | spiy + | ldy a,0 + | sty a,0 + | + | ret + | } + """.stripMargin) + } + + test("Other Z80 instructions (Intel syntax)") { + EmuUnoptimizedZ80Run( + """ + | #pragma intel_syntax + | asm void main () { + | ret + | + | djnz main + | exaf + | exx + | + | sllr b + | sllr c + | sllr d + | sllr e + | sllr h + | sllr l + | sllr m + | sllr a + | + | inp b + | outp b + | dsbc b + | lbcd 34 + | neg + | retn + | im0 + | stai + | inp c + | outp c + | dadc b + | lbcd 7 + | star + | inp d + | outp d + | dsbc d + | lded 55 + | im1 + | ldai + | inp e + | outp e + | dadc d + | lded 33 + | im2 + | ldar + | + | inp h + | outp h + | dsbc h + | rrd + | inp l + | outp l + | dadc h + | rld + | dsbc sp + | inp a + | outp a + | dadc sp + | lspd 345 + | + | ldi + | cci + | ini + | outi + | ldd + | ccd + | ind + | outd + | ldir + | ccir + | inir + | outir + | lddr + | ccdr + | indr + | outdr + | + | ret + | } + """.stripMargin) + } + test("Gameboy instructions") { EmuUnoptimizedSharpRun( """ @@ -1012,4 +1287,36 @@ class Z80AssemblySuite extends FunSuite with Matchers { """.stripMargin) } + test("Z80 halves of index registers") { + EmuUnoptimizedZ80NextRun( + """ + | #pragma zilog_syntax + | asm void main () { + | ret + | inc ixh + | inc ixl + | inc iyh + | inc iyl + | dec ixh + | dec ixl + | dec iyh + | dec iyl + | ld a,ixh + | ld a,ixl + | ld iyh,a + | ld iyl,a + | add a,iyl + | adc a,iyl + | sub iyl + | sbc a,iyl + | or iyl + | xor iyl + | and iyl + | cp iyl + | ld ixh,0 + | ret + | } + """.stripMargin) + } + } diff --git a/src/test/scala/millfork/test/emu/EmuZ80Run.scala b/src/test/scala/millfork/test/emu/EmuZ80Run.scala index 86da93e8..cbef6614 100644 --- a/src/test/scala/millfork/test/emu/EmuZ80Run.scala +++ b/src/test/scala/millfork/test/emu/EmuZ80Run.scala @@ -77,7 +77,7 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio val log = TestErrorReporting.log println(source) val platform = EmuPlatform.get(cpu) - val extraFlags = Map( + var extraFlags = Map( CompilationFlag.DangerousOptimizations -> true, CompilationFlag.EnableInternalTestSyntax -> true, CompilationFlag.InlineFunctions -> this.inline, @@ -87,6 +87,9 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio CompilationFlag.EmitIllegals -> (cpu == millfork.Cpu.Z80 || cpu == millfork.Cpu.Intel8085 || cpu == millfork.Cpu.Z80Next), CompilationFlag.EmitZ80NextOpcodes -> (cpu == millfork.Cpu.Z80Next), CompilationFlag.LenientTextEncoding -> true) + if (source.contains("intel_syntax")) { + extraFlags += CompilationFlag.UseIntelSyntaxForOutput -> true + } val options = CompilationOptions(platform, millfork.Cpu.defaultFlags(cpu).map(_ -> true).toMap ++ extraFlags, None, 0, Map(), EmuPlatform.textCodecRepository, JobContext(log, new LabelGenerator)) println(cpu) println(options.flags.filter(_._2).keys.toSeq.sorted)