| 1 | package com.rox.mem; | |
| 2 | ||
| 3 | import com.rox.input.ControllerConfiguration; | |
| 4 | import com.rox.input.ControllerPort; | |
| 5 | ||
| 6 | /** | |
| 7 | * Routes the NES: | |
| 8 | * <ul> | |
| 9 | * <li><b>$0000-$1FFF</b> to the wrapped RAM-backed bus - 2KB of CPU RAM, mirrored four times: real | |
| 10 | * hardware never decodes address lines A11/A12 for RAM, so {@code $0042}, {@code $0842}, | |
| 11 | * {@code $1042} and {@code $1842} are all the same byte, and this bus only ever passes | |
| 12 | * {@code $0000-$07FF} on</li> | |
| 13 | * <li><b>$2000-$3FFF</b> PPU register range to a PPU bus</li> | |
| 14 | * <li><b>$4000-$4017</b> I/O range to a device bus (e.g. the APU)</li> | |
| 15 | * <li><b>$6000-$FFFF</b> to a cartridge bus (PRG-RAM/PRG-ROM via a mapper)</li> | |
| 16 | * <li><b>$4018-$5FFF</b> nowhere: reads 0, writes ignored. On real hardware this is the disabled | |
| 17 | * APU test registers plus cartridge expansion space, which none of the supported mappers use; open | |
| 18 | * bus (a read returning the last value on the data bus) isn't modeled, same as the I/O range's | |
| 19 | * write-only registers</li> | |
| 20 | * </ul> | |
| 21 | * $4016/$4017 reads are routed to a {@link ControllerPort} each rather than the generic I/O device | |
| 22 | * bus. $4016 writes (the joypad strobe) drive <b>both</b> ports at once - real hardware wires the | |
| 23 | * strobe line to every controller port simultaneously - and never reach the device bus, since that | |
| 24 | * address has no meaning to the APU. $4017 writes are still routed to the device bus unchanged, since | |
| 25 | * that address is genuinely the APU's frame counter register on write. | |
| 26 | * | |
| 27 | * {@code $4014} (OAM DMA) is special-cased out of the I/O range: it reads 256 bytes back out of | |
| 28 | * {@code this} bus (whatever the source page actually maps to - normally internal RAM) starting at | |
| 29 | * {@code value << 8}, and hands them to the PPU bus in one shot via {@link OamDmaBus#writeOamDma} | |
| 30 | * rather than going through the byte-at-a-time OAMDATA register. | |
| 31 | */ | |
| 32 | public class NESMemoryBus implements MemoryBus { | |
| 33 | public static final int PPU_START_ADDRESS = 0x2000; | |
| 34 | public static final int PPU_END_ADDRESS = 0x3FFF; | |
| 35 | public static final int IO_START_ADDRESS = 0x4000; | |
| 36 | public static final int IO_END_ADDRESS = 0x4017; | |
| 37 | public static final int STATUS_REGISTER_ADDRESS = 0x4015; | |
| 38 | public static final int OAM_DMA_ADDRESS = 0x4014; | |
| 39 | public static final int CONTROLLER_1_ADDRESS = 0x4016; | |
| 40 | public static final int CONTROLLER_2_ADDRESS = 0x4017; | |
| 41 | private static final int STROBE_BIT = 0x01; | |
| 42 | public static final int CARTRIDGE_START_ADDRESS = 0x6000; | |
| 43 | public static final int CPU_RAM_END_ADDRESS = 0x1FFF; | |
| 44 | /** 2KB of real RAM: every address in $0000-$1FFF is reduced to its low 11 bits. */ | |
| 45 | public static final int CPU_RAM_MIRROR_MASK = 0x07FF; | |
| 46 | private static final int UNMAPPED_READ_VALUE = 0; | |
| 47 | private static final int OAM_DMA_PAGE_SIZE = 0x100; | |
| 48 | ||
| 49 | private final MemoryBus ramBus; | |
| 50 | private final MemoryBus apuBus; | |
| 51 | private final MemoryBus cartridgeBus; | |
| 52 | private final OamDmaBus ppuBus; | |
| 53 | private final ControllerPort controllerPort1; | |
| 54 | private final ControllerPort controllerPort2; | |
| 55 | ||
| 56 | public NESMemoryBus(final MemoryBus ramBus, | |
| 57 | final MemoryBus apuBus, | |
| 58 | final MemoryBus cartridgeBus, | |
| 59 | final OamDmaBus ppuBus, | |
| 60 | final ControllerConfiguration controllers){ | |
| 61 | this.ramBus = ramBus; | |
| 62 | this.apuBus = apuBus; | |
| 63 | this.cartridgeBus = cartridgeBus; | |
| 64 | this.ppuBus = ppuBus; | |
| 65 | this.controllerPort1 = new ControllerPort(controllers.player1(), controllers.player3(), controllers.fourScoreEnabled(), | |
| 66 | ControllerPort.FOUR_SCORE_PORT_1_SIGNATURE_BIT); | |
| 67 | this.controllerPort2 = new ControllerPort(controllers.player2(), controllers.player4(), controllers.fourScoreEnabled(), | |
| 68 | ControllerPort.FOUR_SCORE_PORT_2_SIGNATURE_BIT); | |
| 69 | } | |
| 70 | ||
| 71 | @Override | |
| 72 | public int read(final int address) { | |
| 73 |
1
1. read : negated conditional → KILLED |
if (address == STATUS_REGISTER_ADDRESS) { |
| 74 |
1
1. read : replaced int return with 0 for com/rox/mem/NESMemoryBus::read → KILLED |
return apuBus.read(address); |
| 75 | } | |
| 76 |
1
1. read : negated conditional → KILLED |
if (address == CONTROLLER_1_ADDRESS) { |
| 77 |
1
1. read : replaced int return with 0 for com/rox/mem/NESMemoryBus::read → KILLED |
return controllerPort1.read(); |
| 78 | } | |
| 79 |
1
1. read : negated conditional → KILLED |
if (address == CONTROLLER_2_ADDRESS) { |
| 80 |
1
1. read : replaced int return with 0 for com/rox/mem/NESMemoryBus::read → KILLED |
return controllerPort2.read(); |
| 81 | } | |
| 82 |
1
1. read : negated conditional → KILLED |
if (isInPpuRange(address)) { |
| 83 |
1
1. read : replaced int return with 0 for com/rox/mem/NESMemoryBus::read → KILLED |
return ppuBus.read(address); |
| 84 | } | |
| 85 |
1
1. read : negated conditional → KILLED |
if (isInIORange(address)) { |
| 86 | return 0; | |
| 87 | } | |
| 88 |
1
1. read : negated conditional → KILLED |
if (isInCartridgeRange(address)) { |
| 89 |
1
1. read : replaced int return with 0 for com/rox/mem/NESMemoryBus::read → KILLED |
return cartridgeBus.read(address); |
| 90 | } | |
| 91 |
1
1. read : negated conditional → KILLED |
if (isInCpuRamRange(address)) { |
| 92 |
2
1. read : Replaced bitwise AND with OR → KILLED 2. read : replaced int return with 0 for com/rox/mem/NESMemoryBus::read → KILLED |
return ramBus.read(address & CPU_RAM_MIRROR_MASK); |
| 93 | } | |
| 94 | return UNMAPPED_READ_VALUE; | |
| 95 | } | |
| 96 | ||
| 97 | @Override | |
| 98 | public void write(final int address, final int value) { | |
| 99 |
1
1. write : negated conditional → KILLED |
if (address == CONTROLLER_1_ADDRESS) { |
| 100 | //the joypad strobe - wired to both ports simultaneously on real hardware, and unlike | |
| 101 | //$4017 this address has no meaning to the APU, so (unlike everything else in the I/O | |
| 102 | //range) this must not reach the device bus | |
| 103 |
2
1. write : Replaced bitwise AND with OR → KILLED 2. write : negated conditional → KILLED |
final boolean strobeHigh = (value & STROBE_BIT) != 0; |
| 104 |
1
1. write : removed call to com/rox/input/ControllerPort::strobe → KILLED |
controllerPort1.strobe(strobeHigh); |
| 105 |
1
1. write : removed call to com/rox/input/ControllerPort::strobe → KILLED |
controllerPort2.strobe(strobeHigh); |
| 106 | return; | |
| 107 | } | |
| 108 |
1
1. write : negated conditional → KILLED |
if (isInPpuRange(address)) { |
| 109 |
1
1. write : removed call to com/rox/mem/OamDmaBus::write → KILLED |
ppuBus.write(address, value); |
| 110 | return; | |
| 111 | } | |
| 112 |
1
1. write : negated conditional → KILLED |
if (address == OAM_DMA_ADDRESS) { |
| 113 | final int[] pageBytes = readDmaSourcePage(value); | |
| 114 |
1
1. write : removed call to com/rox/mem/OamDmaBus::writeOamDma → KILLED |
ppuBus.writeOamDma(pageBytes); |
| 115 | return; | |
| 116 | } | |
| 117 |
1
1. write : negated conditional → KILLED |
if (isInIORange(address)) { |
| 118 |
1
1. write : removed call to com/rox/mem/MemoryBus::write → KILLED |
apuBus.write(address, value); |
| 119 | return; | |
| 120 | } | |
| 121 |
1
1. write : negated conditional → KILLED |
if (isInCartridgeRange(address)) { |
| 122 |
1
1. write : removed call to com/rox/mem/MemoryBus::write → KILLED |
cartridgeBus.write(address, value); |
| 123 | return; | |
| 124 | } | |
| 125 |
1
1. write : negated conditional → KILLED |
if (isInCpuRamRange(address)) { |
| 126 |
2
1. write : Replaced bitwise AND with OR → KILLED 2. write : removed call to com/rox/mem/MemoryBus::write → KILLED |
ramBus.write(address & CPU_RAM_MIRROR_MASK, value); |
| 127 | } | |
| 128 | } | |
| 129 | ||
| 130 | /** | |
| 131 | * @param page from which to getch DMA block | |
| 132 | * @return the block of {@link #OAM_DMA_PAGE_SIZE} bytes from the given `page` | |
| 133 | */ | |
| 134 | private int[] readDmaSourcePage(final int page) { | |
| 135 |
2
1. readDmaSourcePage : Replaced Shift Left with Shift Right → KILLED 2. readDmaSourcePage : Replaced bitwise AND with OR → KILLED |
final int pageStart = (page & 0xFF) << 8; |
| 136 | final int[] pageBytes = new int[OAM_DMA_PAGE_SIZE]; | |
| 137 |
2
1. readDmaSourcePage : negated conditional → KILLED 2. readDmaSourcePage : changed conditional boundary → KILLED |
for (int i = 0; i < OAM_DMA_PAGE_SIZE; i++){ |
| 138 |
1
1. readDmaSourcePage : Replaced integer addition with subtraction → KILLED |
pageBytes[i] = read(pageStart + i); |
| 139 | } | |
| 140 |
1
1. readDmaSourcePage : replaced return value with null for com/rox/mem/NESMemoryBus::readDmaSourcePage → KILLED |
return pageBytes; |
| 141 | } | |
| 142 | ||
| 143 | private static boolean isInCpuRamRange(final int address){ | |
| 144 |
3
1. isInCpuRamRange : replaced boolean return with true for com/rox/mem/NESMemoryBus::isInCpuRamRange → KILLED 2. isInCpuRamRange : changed conditional boundary → KILLED 3. isInCpuRamRange : negated conditional → KILLED |
return address <= CPU_RAM_END_ADDRESS; |
| 145 | } | |
| 146 | ||
| 147 | private static boolean isInPpuRange(final int address){ | |
| 148 |
5
1. isInPpuRange : replaced boolean return with true for com/rox/mem/NESMemoryBus::isInPpuRange → KILLED 2. isInPpuRange : changed conditional boundary → KILLED 3. isInPpuRange : negated conditional → KILLED 4. isInPpuRange : changed conditional boundary → KILLED 5. isInPpuRange : negated conditional → KILLED |
return address >= PPU_START_ADDRESS && address <= PPU_END_ADDRESS; |
| 149 | } | |
| 150 | ||
| 151 | private static boolean isInIORange(final int address){ | |
| 152 |
5
1. isInIORange : negated conditional → KILLED 2. isInIORange : replaced boolean return with true for com/rox/mem/NESMemoryBus::isInIORange → KILLED 3. isInIORange : negated conditional → KILLED 4. isInIORange : changed conditional boundary → KILLED 5. isInIORange : changed conditional boundary → KILLED |
return address >= IO_START_ADDRESS && address <= IO_END_ADDRESS; |
| 153 | } | |
| 154 | ||
| 155 | private static boolean isInCartridgeRange(final int address){ | |
| 156 |
3
1. isInCartridgeRange : changed conditional boundary → KILLED 2. isInCartridgeRange : negated conditional → KILLED 3. isInCartridgeRange : replaced boolean return with true for com/rox/mem/NESMemoryBus::isInCartridgeRange → KILLED |
return address >= CARTRIDGE_START_ADDRESS; |
| 157 | } | |
| 158 | } | |
Mutations | ||
| 73 |
1.1 |
|
| 74 |
1.1 |
|
| 76 |
1.1 |
|
| 77 |
1.1 |
|
| 79 |
1.1 |
|
| 80 |
1.1 |
|
| 82 |
1.1 |
|
| 83 |
1.1 |
|
| 85 |
1.1 |
|
| 88 |
1.1 |
|
| 89 |
1.1 |
|
| 91 |
1.1 |
|
| 92 |
1.1 2.2 |
|
| 99 |
1.1 |
|
| 103 |
1.1 2.2 |
|
| 104 |
1.1 |
|
| 105 |
1.1 |
|
| 108 |
1.1 |
|
| 109 |
1.1 |
|
| 112 |
1.1 |
|
| 114 |
1.1 |
|
| 117 |
1.1 |
|
| 118 |
1.1 |
|
| 121 |
1.1 |
|
| 122 |
1.1 |
|
| 125 |
1.1 |
|
| 126 |
1.1 2.2 |
|
| 135 |
1.1 2.2 |
|
| 137 |
1.1 2.2 |
|
| 138 |
1.1 |
|
| 140 |
1.1 |
|
| 144 |
1.1 2.2 3.3 |
|
| 148 |
1.1 2.2 3.3 4.4 5.5 |
|
| 152 |
1.1 2.2 3.3 4.4 5.5 |
|
| 156 |
1.1 2.2 3.3 |