From 8e4e3b956fbe76558f56ca49fc4a18e75e6703aa Mon Sep 17 00:00:00 2001 From: Forest Belton <65484+forestbelton@users.noreply.github.com> Date: Tue, 3 Aug 2021 17:25:48 -0400 Subject: [PATCH] Add equivalence classes for instructions --- gbso/cpu/insn.py | 379 ++++++++++++++++++++++++++- gbso/cpu/regs.py | 3 +- gbso/program/mutate.py | 68 ++++- tests/{insn => cpu}/__init__.py | 0 tests/cpu/insn/__init__.py | 0 tests/{ => cpu}/insn/helpers.py | 0 tests/{ => cpu}/insn/test_alu16.py | 8 +- tests/{ => cpu}/insn/test_alu8.py | 6 +- tests/{ => cpu}/insn/test_bit.py | 2 +- tests/{ => cpu}/insn/test_cpu.py | 2 +- tests/{ => cpu}/insn/test_loads16.py | 8 +- tests/{ => cpu}/insn/test_loads8.py | 2 +- tests/{ => cpu}/insn/test_shift.py | 2 +- tests/program/__init__.py | 0 tests/program/test_mutate.py | 95 +++++++ 15 files changed, 555 insertions(+), 20 deletions(-) rename tests/{insn => cpu}/__init__.py (100%) create mode 100644 tests/cpu/insn/__init__.py rename tests/{ => cpu}/insn/helpers.py (100%) rename tests/{ => cpu}/insn/test_alu16.py (88%) rename tests/{ => cpu}/insn/test_alu8.py (98%) rename tests/{ => cpu}/insn/test_bit.py (95%) rename tests/{ => cpu}/insn/test_cpu.py (86%) rename tests/{ => cpu}/insn/test_loads16.py (80%) rename tests/{ => cpu}/insn/test_loads8.py (98%) rename tests/{ => cpu}/insn/test_shift.py (99%) create mode 100644 tests/program/__init__.py create mode 100644 tests/program/test_mutate.py diff --git a/gbso/cpu/insn.py b/gbso/cpu/insn.py index b3c1026..6c4f2e2 100644 --- a/gbso/cpu/insn.py +++ b/gbso/cpu/insn.py @@ -1,11 +1,29 @@ from abc import ABC, abstractmethod +from collections import defaultdict from dataclasses import dataclass +from enum import Enum +from typing import Dict, List, Type from gbso.cpu.cpu import CPU from gbso.cpu.regs import R16, R8 +class Operand(Enum): + R8 = "R8" + R16 = "R16" # BC, DE, HL, SP + R16_NO_SP = "R16_NO_SP" # BC, DE, HL + IMM3 = "IMM3" + IMM8 = "IMM8" + IMM16 = "IMM16" + SIMM8 = "SIMM8" + + class Insn(ABC): + @staticmethod + @abstractmethod + def signature() -> List[Operand]: + pass + @staticmethod @abstractmethod def cycles() -> int: @@ -25,6 +43,10 @@ class LD_R_R(Insn): dst: R8 src: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8, Operand.R8] + @staticmethod def cycles() -> int: return 4 @@ -41,6 +63,10 @@ class LD_R_N8(Insn): dst: R8 imm: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8, Operand.IMM8] + @staticmethod def cycles() -> int: return 8 @@ -56,6 +82,10 @@ class LD_R_N8(Insn): class LD_R_HL(Insn): dst: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 8 @@ -71,6 +101,10 @@ class LD_R_HL(Insn): class LD_HL_R(Insn): src: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 8 @@ -86,6 +120,10 @@ class LD_HL_R(Insn): class LD_HL_N(Insn): imm: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.IMM8] + @staticmethod def cycles() -> int: return 12 @@ -99,6 +137,10 @@ class LD_HL_N(Insn): @dataclass class LD_A_BC(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -112,6 +154,10 @@ class LD_A_BC(Insn): @dataclass class LD_A_DE(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -127,6 +173,10 @@ class LD_A_DE(Insn): class LD_A_NN(Insn): nn: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.IMM16] + @staticmethod def cycles() -> int: return 16 @@ -140,6 +190,10 @@ class LD_A_NN(Insn): @dataclass class LD_BC_A(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -153,6 +207,10 @@ class LD_BC_A(Insn): @dataclass class LD_DE_A(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -168,6 +226,10 @@ class LD_DE_A(Insn): class LD_NN_A(Insn): nn: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.IMM16] + @staticmethod def cycles() -> int: return 16 @@ -183,6 +245,10 @@ class LD_NN_A(Insn): class LD_A_FF_N(Insn): n: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.IMM8] + @staticmethod def cycles() -> int: return 12 @@ -198,6 +264,10 @@ class LD_A_FF_N(Insn): class LD_FF_N_A(Insn): n: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.IMM8] + @staticmethod def cycles() -> int: return 12 @@ -211,6 +281,10 @@ class LD_FF_N_A(Insn): @dataclass class LD_A_FF_C(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -224,6 +298,10 @@ class LD_A_FF_C(Insn): @dataclass class LD_FF_C_A(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -237,6 +315,10 @@ class LD_FF_C_A(Insn): @dataclass class LDI_HL_A(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -252,6 +334,10 @@ class LDI_HL_A(Insn): @dataclass class LDI_A_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -267,6 +353,10 @@ class LDI_A_HL(Insn): @dataclass class LDD_HL_A(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -282,6 +372,10 @@ class LDD_HL_A(Insn): @dataclass class LDD_A_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -300,6 +394,10 @@ class LD_RR_NN(Insn): rr: R16 nn: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.R16, Operand.IMM16] + @staticmethod def cycles() -> int: return 12 @@ -315,6 +413,10 @@ class LD_RR_NN(Insn): class LD_NN_SP(Insn): nn: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.IMM16] + @staticmethod def cycles() -> int: return 20 @@ -328,6 +430,10 @@ class LD_NN_SP(Insn): @dataclass class LD_SP_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -345,6 +451,10 @@ class LD_SP_HL(Insn): class PUSH_RR(Insn): rr: R16 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R16_NO_SP] + @staticmethod def cycles() -> int: return 16 @@ -362,6 +472,10 @@ class PUSH_RR(Insn): class POP_RR(Insn): rr: R16 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R16_NO_SP] + @staticmethod def cycles() -> int: return 12 @@ -379,6 +493,10 @@ class POP_RR(Insn): class ADD_A_R(Insn): r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 4 @@ -396,6 +514,10 @@ class ADD_A_R(Insn): class ADD_A_N(Insn): n: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.IMM8] + @staticmethod def cycles() -> int: return 8 @@ -411,6 +533,10 @@ class ADD_A_N(Insn): @dataclass class ADD_A_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -428,6 +554,10 @@ class ADD_A_HL(Insn): class ADC_A_R(Insn): r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 4 @@ -445,6 +575,10 @@ class ADC_A_R(Insn): class ADC_A_N(Insn): n: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.IMM8] + @staticmethod def cycles() -> int: return 8 @@ -460,6 +594,10 @@ class ADC_A_N(Insn): @dataclass class ADC_A_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -477,6 +615,10 @@ class ADC_A_HL(Insn): class SUB_A_R(Insn): r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 4 @@ -494,6 +636,10 @@ class SUB_A_R(Insn): class SUB_A_N(Insn): n: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.IMM8] + @staticmethod def cycles() -> int: return 8 @@ -509,6 +655,10 @@ class SUB_A_N(Insn): @dataclass class SUB_A_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -526,6 +676,10 @@ class SUB_A_HL(Insn): class SBC_A_R(Insn): r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 4 @@ -543,6 +697,10 @@ class SBC_A_R(Insn): class SBC_A_N(Insn): n: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.IMM8] + @staticmethod def cycles() -> int: return 8 @@ -558,6 +716,10 @@ class SBC_A_N(Insn): @dataclass class SBC_A_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -575,6 +737,10 @@ class SBC_A_HL(Insn): class AND_A_R(Insn): r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 4 @@ -592,6 +758,10 @@ class AND_A_R(Insn): class AND_A_N(Insn): n: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.IMM8] + @staticmethod def cycles() -> int: return 8 @@ -607,6 +777,10 @@ class AND_A_N(Insn): @dataclass class AND_A_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -624,6 +798,10 @@ class AND_A_HL(Insn): class XOR_A_R(Insn): r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 4 @@ -641,6 +819,10 @@ class XOR_A_R(Insn): class XOR_A_N(Insn): n: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.IMM8] + @staticmethod def cycles() -> int: return 8 @@ -656,6 +838,10 @@ class XOR_A_N(Insn): @dataclass class XOR_A_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -673,6 +859,10 @@ class XOR_A_HL(Insn): class OR_A_R(Insn): r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 4 @@ -690,6 +880,10 @@ class OR_A_R(Insn): class OR_A_N(Insn): n: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.IMM8] + @staticmethod def cycles() -> int: return 8 @@ -705,6 +899,10 @@ class OR_A_N(Insn): @dataclass class OR_A_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -722,6 +920,10 @@ class OR_A_HL(Insn): class CP_A_R(Insn): r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 4 @@ -737,6 +939,10 @@ class CP_A_R(Insn): class CP_A_N(Insn): n: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.IMM8] + @staticmethod def cycles() -> int: return 8 @@ -750,6 +956,10 @@ class CP_A_N(Insn): @dataclass class CP_A_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 8 @@ -767,6 +977,10 @@ class CP_A_HL(Insn): class INC_R(Insn): r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 4 @@ -780,7 +994,9 @@ class INC_R(Insn): @dataclass class INC_HL(Insn): - r: R8 + @staticmethod + def signature() -> List[Operand]: + return [] @staticmethod def cycles() -> int: @@ -798,6 +1014,10 @@ class INC_HL(Insn): class DEC_R(Insn): r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 4 @@ -811,7 +1031,9 @@ class DEC_R(Insn): @dataclass class DEC_HL(Insn): - r: R8 + @staticmethod + def signature() -> List[Operand]: + return [] @staticmethod def cycles() -> int: @@ -827,6 +1049,10 @@ class DEC_HL(Insn): @dataclass class CPL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 4 @@ -842,6 +1068,10 @@ class CPL(Insn): class ADD_HL_RR(Insn): rr: R16 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R16] + @staticmethod def cycles() -> int: return 8 @@ -859,6 +1089,10 @@ class ADD_HL_RR(Insn): class INC_RR(Insn): rr: R16 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R16] + @staticmethod def cycles() -> int: return 8 @@ -874,6 +1108,10 @@ class INC_RR(Insn): class DEC_RR(Insn): rr: R16 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R16] + @staticmethod def cycles() -> int: return 8 @@ -889,6 +1127,10 @@ class DEC_RR(Insn): class ADD_SP_DD(Insn): dd: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.SIMM8] + @staticmethod def cycles() -> int: return 16 @@ -906,6 +1148,10 @@ class ADD_SP_DD(Insn): class LD_HL_SP_DD(Insn): dd: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.SIMM8] + @staticmethod def cycles() -> int: return 12 @@ -921,6 +1167,10 @@ class LD_HL_SP_DD(Insn): @dataclass class RLCA(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 4 @@ -936,6 +1186,10 @@ class RLCA(Insn): @dataclass class RLA(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 4 @@ -952,6 +1206,10 @@ class RLA(Insn): @dataclass class RRCA(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 4 @@ -967,6 +1225,10 @@ class RRCA(Insn): @dataclass class RRA(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 4 @@ -985,6 +1247,10 @@ class RRA(Insn): class RLC_R(Insn): r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 8 @@ -1000,6 +1266,10 @@ class RLC_R(Insn): @dataclass class RLC_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 16 @@ -1016,6 +1286,10 @@ class RLC_HL(Insn): class RL_R(Insn): r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 8 @@ -1032,6 +1306,10 @@ class RL_R(Insn): @dataclass class RL_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 16 @@ -1049,6 +1327,10 @@ class RL_HL(Insn): class RRC_R(Insn): r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 8 @@ -1064,6 +1346,10 @@ class RRC_R(Insn): @dataclass class RRC_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 16 @@ -1080,6 +1366,10 @@ class RRC_HL(Insn): class RR_R(Insn): r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 8 @@ -1096,6 +1386,10 @@ class RR_R(Insn): @dataclass class RR_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 16 @@ -1113,6 +1407,10 @@ class RR_HL(Insn): class SLA_R(Insn): r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 8 @@ -1128,6 +1426,10 @@ class SLA_R(Insn): @dataclass class SLA_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 16 @@ -1144,6 +1446,10 @@ class SLA_HL(Insn): class SWAP_R(Insn): r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 8 @@ -1159,6 +1465,10 @@ class SWAP_R(Insn): @dataclass class SWAP_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 16 @@ -1175,6 +1485,10 @@ class SWAP_HL(Insn): class SRA_R(Insn): r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 8 @@ -1190,6 +1504,10 @@ class SRA_R(Insn): @dataclass class SRA_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 16 @@ -1206,6 +1524,10 @@ class SRA_HL(Insn): class SRL_R(Insn): r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.R8] + @staticmethod def cycles() -> int: return 8 @@ -1221,6 +1543,10 @@ class SRL_R(Insn): @dataclass class SRL_HL(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 16 @@ -1238,6 +1564,10 @@ class SET_N_R(Insn): n: int r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.IMM3, Operand.R8] + @staticmethod def cycles() -> int: return 8 @@ -1253,6 +1583,10 @@ class SET_N_R(Insn): class SET_N_HL(Insn): n: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.IMM3] + @staticmethod def cycles() -> int: return 16 @@ -1270,6 +1604,10 @@ class RES_N_R(Insn): n: int r: R8 + @staticmethod + def signature() -> List[Operand]: + return [Operand.IMM3, Operand.R8] + @staticmethod def cycles() -> int: return 8 @@ -1285,6 +1623,10 @@ class RES_N_R(Insn): class RES_N_HL(Insn): n: int + @staticmethod + def signature() -> List[Operand]: + return [Operand.IMM3] + @staticmethod def cycles() -> int: return 16 @@ -1299,6 +1641,10 @@ class RES_N_HL(Insn): @dataclass class CCF(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 4 @@ -1312,6 +1658,10 @@ class CCF(Insn): @dataclass class SCF(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 4 @@ -1325,6 +1675,10 @@ class SCF(Insn): @dataclass class UNUSED(Insn): + @staticmethod + def signature() -> List[Operand]: + return [] + @staticmethod def cycles() -> int: return 0 @@ -1334,3 +1688,24 @@ class UNUSED(Insn): def pretty(self) -> str: return "UNUSED" + + +ALL_INSN_CLASSES: List[Type[Insn]] = [ + cls for cls in Insn.__subclasses__() if cls != Insn # type: ignore +] + +INSNS_BY_SIGNATURE: Dict[str, List[Type[Insn]]] = defaultdict(list) + + +def get_signature_class(insn: Insn) -> List[Type[Insn]]: + signature_key = get_signature_key(insn.signature()) + return INSNS_BY_SIGNATURE[signature_key] + + +def get_signature_key(signature: List[Operand]) -> str: + return "-".join([ty.value for ty in signature]) + + +for insn_cls in ALL_INSN_CLASSES: + signature_key = get_signature_key(insn_cls.signature()) + INSNS_BY_SIGNATURE[signature_key].append(insn_cls) diff --git a/gbso/cpu/regs.py b/gbso/cpu/regs.py index c518c0f..b7aaf17 100644 --- a/gbso/cpu/regs.py +++ b/gbso/cpu/regs.py @@ -12,7 +12,6 @@ class R8(Enum): class R16(Enum): - AF = "AF" BC = "BC" DE = "DE" HL = "HL" @@ -30,3 +29,5 @@ R16_LO = { R16.DE: R8.E, R16.HL: R8.L, } + +R16_WITHOUT_SP = [R16.BC, R16.DE, R16.HL] diff --git a/gbso/program/mutate.py b/gbso/program/mutate.py index fa52210..d59b63c 100644 --- a/gbso/program/mutate.py +++ b/gbso/program/mutate.py @@ -1,7 +1,9 @@ from enum import Enum -from random import random -from typing import List, Tuple, TypeVar +from gbso.cpu.regs import R16, R16_WITHOUT_SP, R8 +from random import choice, randint, random, randrange +from typing import List, Tuple, Type, TypeVar, Union +from gbso.cpu.insn import ALL_INSN_CLASSES, Insn, Operand, UNUSED from gbso.program.program import Program @@ -40,6 +42,42 @@ def mutate_program( return new_prgm +# TODO: Implement +def mutate_opcode(prgm: Program) -> Program: + return prgm + + +# TODO: Implement +def mutate_operand(prgm: Program) -> Program: + return prgm + + +def mutate_swap(prgm: Program) -> Program: + i = randrange(len(prgm.insns)) + j = randrange(len(prgm.insns)) + + insns = prgm.insns.copy() + insn = insns[i] + insns[i] = insns[j] + insns[j] = insn + + return Program(insns=insns) + + +def mutate_insn(prgm: Program, prob_unused: float) -> Program: + unused = random() + next_insn: Insn = UNUSED() + if unused > prob_unused: + cls = choice(ALL_INSN_CLASSES) + next_insn = create_random_insn(cls) + + insns = prgm.insns.copy() + insn_index = randrange(len(prgm.insns)) + insns[insn_index] = next_insn + + return Program(insns=insns) + + A = TypeVar("A") @@ -55,3 +93,29 @@ def sample_probs(probs: List[Tuple[A, float]]) -> A: val, _ = probs[-1] return val + + +def create_random_insn(insn_cls: Type[Insn]) -> Insn: + args = [create_random_operand(op) for op in insn_cls.signature()] + return insn_cls(*args) # type: ignore + + +def create_random_operand(op: Operand) -> Union[int, R8, R16]: + value: Union[int, R8, R16] + + if op == Operand.R8: + value = choice(list(R8)) + elif op == Operand.R16: + value = choice(list(R16)) + elif op == Operand.R16_NO_SP: + value = choice(R16_WITHOUT_SP) + elif op == Operand.IMM3: + value = randrange(8) + elif op == Operand.IMM8: + value = randrange(256) + elif op == Operand.IMM16: + value = randrange(65536) + elif op == Operand.SIMM8: + value = randint(-128, 127) + + return value diff --git a/tests/insn/__init__.py b/tests/cpu/__init__.py similarity index 100% rename from tests/insn/__init__.py rename to tests/cpu/__init__.py diff --git a/tests/cpu/insn/__init__.py b/tests/cpu/insn/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/insn/helpers.py b/tests/cpu/insn/helpers.py similarity index 100% rename from tests/insn/helpers.py rename to tests/cpu/insn/helpers.py diff --git a/tests/insn/test_alu16.py b/tests/cpu/insn/test_alu16.py similarity index 88% rename from tests/insn/test_alu16.py rename to tests/cpu/insn/test_alu16.py index 303b15d..fe7147f 100644 --- a/tests/insn/test_alu16.py +++ b/tests/cpu/insn/test_alu16.py @@ -1,9 +1,9 @@ import pytest -from tests.insn.helpers import * +from tests.cpu.insn.helpers import * -@pytest.mark.parametrize("rr", set(R16) - {R16.AF, R16.HL}) +@pytest.mark.parametrize("rr", set(R16) - {R16.HL}) def test_add_hl_rr(rr): cpu = CPU() cpu.set_reg16(R16.HL, 0xABCD) @@ -21,7 +21,7 @@ def test_add_hl_rr(rr): assert cpu.state.carry == 1 -@pytest.mark.parametrize("rr", set(R16) - {R16.AF}) +@pytest.mark.parametrize("rr", set(R16)) def test_inc_rr(rr): cpu = CPU() cpu.set_reg16(rr, 0xABCD) @@ -31,7 +31,7 @@ def test_inc_rr(rr): assert cpu.get_reg16(rr) == 0xABCE -@pytest.mark.parametrize("rr", set(R16) - {R16.AF}) +@pytest.mark.parametrize("rr", set(R16)) def test_dec_rr(rr): cpu = CPU() cpu.set_reg16(rr, 0xABCD) diff --git a/tests/insn/test_alu8.py b/tests/cpu/insn/test_alu8.py similarity index 98% rename from tests/insn/test_alu8.py rename to tests/cpu/insn/test_alu8.py index af6bde8..7ff23c2 100644 --- a/tests/insn/test_alu8.py +++ b/tests/cpu/insn/test_alu8.py @@ -1,6 +1,6 @@ import pytest -from tests.insn.helpers import * +from tests.cpu.insn.helpers import * @pytest.mark.parametrize("r", set(R8) - {R8.A}) @@ -281,7 +281,7 @@ def test_inc_hl(n): cpu.set_reg16(R16.HL, 0x1234) cpu.set_mem8(0x1234, n) - INC_HL(n).exec(cpu) + INC_HL().exec(cpu) assert cpu.hl == (n + 1) & 0xFF @@ -302,7 +302,7 @@ def test_dec_hl(n): cpu.set_reg16(R16.HL, 0x1234) cpu.set_mem8(0x1234, n) - DEC_HL(n).exec(cpu) + DEC_HL().exec(cpu) assert cpu.hl == (n - 1) & 0xFF diff --git a/tests/insn/test_bit.py b/tests/cpu/insn/test_bit.py similarity index 95% rename from tests/insn/test_bit.py rename to tests/cpu/insn/test_bit.py index 050b0b3..0614a66 100644 --- a/tests/insn/test_bit.py +++ b/tests/cpu/insn/test_bit.py @@ -1,6 +1,6 @@ import pytest -from tests.insn.helpers import * +from tests.cpu.insn.helpers import * @pytest.mark.parametrize("n,r", [(n, r) for n in range(8) for r in R8]) diff --git a/tests/insn/test_cpu.py b/tests/cpu/insn/test_cpu.py similarity index 86% rename from tests/insn/test_cpu.py rename to tests/cpu/insn/test_cpu.py index 75530be..fde5da7 100644 --- a/tests/insn/test_cpu.py +++ b/tests/cpu/insn/test_cpu.py @@ -1,4 +1,4 @@ -from tests.insn.helpers import * +from tests.cpu.insn.helpers import * def test_ccf(): diff --git a/tests/insn/test_loads16.py b/tests/cpu/insn/test_loads16.py similarity index 80% rename from tests/insn/test_loads16.py rename to tests/cpu/insn/test_loads16.py index 4f650a5..38d9558 100644 --- a/tests/insn/test_loads16.py +++ b/tests/cpu/insn/test_loads16.py @@ -1,9 +1,9 @@ import pytest -from tests.insn.helpers import * +from tests.cpu.insn.helpers import * -@pytest.mark.parametrize("rr", set(R16) - {R16.AF}) +@pytest.mark.parametrize("rr", set(R16)) def test_ld_rr_nn(rr): cpu = CPU() @@ -30,7 +30,7 @@ def test_ld_sp_hl(): assert cpu.get_reg16(R16.SP) == 0x1234 -@pytest.mark.parametrize("rr", set(R16) - {R16.AF, R16.SP}) +@pytest.mark.parametrize("rr", R16_WITHOUT_SP) def test_push_rr(rr): cpu = CPU() cpu.set_reg16(R16.SP, 0xFFFF) @@ -42,7 +42,7 @@ def test_push_rr(rr): assert cpu.get_mem16(0xFFFD) == 0x1234 -@pytest.mark.parametrize("rr", set(R16) - {R16.AF, R16.SP}) +@pytest.mark.parametrize("rr", R16_WITHOUT_SP) def test_pop_rr(rr): cpu = CPU() cpu.set_reg16(R16.SP, 0xFFFD) diff --git a/tests/insn/test_loads8.py b/tests/cpu/insn/test_loads8.py similarity index 98% rename from tests/insn/test_loads8.py rename to tests/cpu/insn/test_loads8.py index 9425a6f..2c36b3b 100644 --- a/tests/insn/test_loads8.py +++ b/tests/cpu/insn/test_loads8.py @@ -1,6 +1,6 @@ import pytest -from tests.insn.helpers import * +from tests.cpu.insn.helpers import * @pytest.mark.parametrize("dst,src", [(x, y) for x in R8 for y in R8]) diff --git a/tests/insn/test_shift.py b/tests/cpu/insn/test_shift.py similarity index 99% rename from tests/insn/test_shift.py rename to tests/cpu/insn/test_shift.py index a70feda..06d8aeb 100644 --- a/tests/insn/test_shift.py +++ b/tests/cpu/insn/test_shift.py @@ -1,6 +1,6 @@ import pytest -from tests.insn.helpers import * +from tests.cpu.insn.helpers import * def test_rlca(): diff --git a/tests/program/__init__.py b/tests/program/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/program/test_mutate.py b/tests/program/test_mutate.py new file mode 100644 index 0000000..473cb99 --- /dev/null +++ b/tests/program/test_mutate.py @@ -0,0 +1,95 @@ +from gbso.cpu.insn import ADD_A_N, ADD_A_R, DEC_HL, LD_RR_NN +from gbso.program.mutate import * + + +def test_mutate_swap(): + trials = 1000 + swapped_count = 0 + + insn_a = ADD_A_R(R8.C) + insn_b = DEC_HL() + prgm = Program(insns=[insn_a, insn_b]) + + for _ in range(trials): + new_prgm = mutate_swap(prgm) + + assert len(new_prgm.insns) == 2 + assert isinstance(new_prgm.insns[0], Insn) + assert isinstance(new_prgm.insns[1], Insn) + assert new_prgm.insns == prgm.insns or new_prgm.insns == [insn_b, insn_a] + + if new_prgm.insns != prgm.insns: + swapped_count += 1 + + assert abs(0.5 - (swapped_count / trials)) < 0.05 + + +def test_mutate_insn(): + trials = 1000 + seen_add_insns = 0 + seen_unused = 0 + + add_insn = ADD_A_N(128) + prgm = Program(insns=[add_insn]) + + for _ in range(trials): + new_prgm = mutate_insn(prgm, 0.5) + + assert len(new_prgm.insns) == 1 + assert isinstance(new_prgm.insns[0], Insn) + + if new_prgm.insns[0] == add_insn: + seen_add_insns += 1 + elif new_prgm.insns[0] == UNUSED(): + seen_unused += 1 + + assert seen_add_insns < 10 + assert abs(0.5 - (seen_unused / trials)) < 0.05 + + +def test_create_random_insn(): + insn = create_random_insn(ADD_A_R) + assert type(insn) == ADD_A_R + assert type(insn.r) == R8 + + insn = create_random_insn(ADD_A_N) + assert type(insn) == ADD_A_N + assert type(insn.n) == int + + insn = create_random_insn(LD_RR_NN) + assert type(insn) == LD_RR_NN + assert type(insn.rr) == R16 + assert type(insn.nn) == int + + +def test_create_random_operand(): + op = create_random_operand(Operand.R8) + assert type(op) == R8 + + op = create_random_operand(Operand.R16) + assert type(op) == R16 + + for _ in range(100): + op = create_random_operand(Operand.R16_NO_SP) + assert type(op) == R16 + assert op != R16.SP + + for _ in range(100): + op = create_random_operand(Operand.IMM3) + assert type(op) == int + assert op >= 0 and op < 8 + + for _ in range(300): + op = create_random_operand(Operand.IMM8) + assert type(op) == int + assert op >= 0 and op < 256 + + for _ in range(500): + op = create_random_operand(Operand.IMM16) + assert type(op) == int + assert op >= 0 and op < 65536 + + for _ in range(300): + op = create_random_operand(Operand.SIMM8) + assert type(op) == int + assert op >= -128 and op < 128 \ No newline at end of file