Skip to content

Commit

Permalink
Add SuperCHIP-8 executor.
Browse files Browse the repository at this point in the history
scroll not working
superchip8 unit tests to be added.
  • Loading branch information
krk committed Jun 30, 2020
1 parent c08e34a commit 98031f1
Show file tree
Hide file tree
Showing 62 changed files with 1,661 additions and 569 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ A [CHIP-8](https://en.wikipedia.org/wiki/CHIP-8) interpreter and emulator in jav

This repository contains two different projects, the [chipsekiz interpreter](chipsekiz) and the [emulator](emulator-awt) implemented in Java.

To build them both and run the emulator:
To build them both and run the **CHIP-8** emulator:

```bash
mvn clean install
java --enable-preview -jar emulator-awt/target/emulator-awt-1.0-SNAPSHOT-jar-with-dependencies.jar
```

To emulate **SuperCHIP-8**:

```bash
java --enable-preview -jar emulator-awt/target/emulator-awt-1.0-SNAPSHOT-jar-with-dependencies.jar --superchip8
```

Emulator will launch the chipsekiz demo ROM with the memory debug view, you can load other ROMs from the menu.

Java 14 is required.
Expand Down Expand Up @@ -96,6 +102,10 @@ Optional `IDebugger` instance receives notifications of memory and register chan

`InterpreterTest` executes included ROMs via `Interpreter` until they halt or until they ran for 600 (arbitrary) instruction cycles, whichever is first.

#### SuperCHIP-8

SuperCHIP-8 components live in the [dev.krk.chipsekiz.superchip](chipsekiz/src/main/java/dev/krk/chipsekiz/superchip) package. It has a decoder, a hal, an interpreter and a vm, all of which are extended from CHIP-8 components.

#### Using the library

To create a default instance of the `Interpreter`:
Expand Down
5 changes: 5 additions & 0 deletions chipsekiz/src/main/java/dev/krk/chipsekiz/hal/Direction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package dev.krk.chipsekiz.hal;

public enum Direction {
Up, Down, Left, Right,
}
26 changes: 26 additions & 0 deletions chipsekiz/src/main/java/dev/krk/chipsekiz/hal/Framebuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,32 @@ public boolean getPixel(byte x, byte y) {
return buffer[y][x];
}

public void scroll(Direction direction, int pixels) {
checkArgument(pixels >= 0, "pixels out of bounds.");

if (direction == Direction.Left || direction == Direction.Up) {
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
switch (direction) {
case Left -> buffer[i][j] =
j >= width - pixels ? false : buffer[i][j + pixels];
case Up -> buffer[i][j] =
i >= height - pixels ? false : buffer[i + pixels][j];
}
}
}
} else {
for (int i = height - 1; i >= 0; i--) {
for (int j = width - 1; j >= 0; j--) {
switch (direction) {
case Right -> buffer[i][j] = j >= pixels ? buffer[i][j - pixels] : false;
case Down -> buffer[i][j] = i >= pixels ? buffer[i - pixels][j] : false;
}
}
}
}
}

@Override public String toString() {
StringBuilder builder = new StringBuilder((height + 1) * width);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

public class FramebufferHal implements IHal {
private final Random random;
private final Framebuffer fb;
protected Framebuffer fb;
private boolean sound;
private Byte key;
private boolean dirty;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package dev.krk.chipsekiz.interpreter;

public class ChipVariation implements IChipVariation {
private final IInterpreterFactory interpreterFactory;
public class ChipVariation<THal extends IHal> implements IChipVariation<THal> {
private final IInterpreterFactory<THal> interpreterFactory;
private final int displayWidth;
private final int displayHeight;
private final int demoOrigin;
private final byte[] demoProgram;
private final String name;

ChipVariation(String name, IInterpreterFactory interpreterFactory, int displayWidth,
ChipVariation(String name, IInterpreterFactory<THal> interpreterFactory, int displayWidth,
int displayHeight) {
this(name, interpreterFactory, displayWidth, displayHeight, 0, null);
}

ChipVariation(String name, IInterpreterFactory interpreterFactory, int displayWidth,
ChipVariation(String name, IInterpreterFactory<THal> interpreterFactory, int displayWidth,
int displayHeight, int demoOrigin, byte[] demoProgram) {
this.name = name;
this.interpreterFactory = interpreterFactory;
Expand All @@ -27,7 +27,7 @@ public boolean hasDemoProgram() {
return demoProgram != null;
}

public IInterpreter createInterpreter(IHal hal) {
public IInterpreter createInterpreter(THal hal) {
return interpreterFactory.create(hal);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package dev.krk.chipsekiz.interpreter;

import dev.krk.chipsekiz.sprites.CharacterSprites;
import dev.krk.chipsekiz.superchip.interpreter.ISuperChipHal;

import java.io.IOException;


public class ChipVariationFactory {
public static IChipVariation createChip8() {
public static IChipVariation<IHal> createChip8() {
byte[] program = null;
try {
// Load demo ROM at start.
Expand All @@ -16,12 +16,11 @@ public static IChipVariation createChip8() {
e.printStackTrace();
}

return new ChipVariation("CHIP-8", hal -> InterpreterFactory
.create(hal, CharacterSprites.getAddressLocator(), null, null,
new Executor(true, true)), 64, 32, 0x200, program);
return new ChipVariation<IHal>("CHIP-8", hal -> InterpreterFactory.create(hal, null, null),
64, 32, 0x200, program);
}

public static IChipVariation createChip48() {
public static IChipVariation<ISuperChipHal> createSuperChip8() {
byte[] program = null;
try {
// Load demo ROM at start.
Expand All @@ -31,8 +30,7 @@ public static IChipVariation createChip48() {
e.printStackTrace();
}

return new ChipVariation("S-CHIP",
hal -> InterpreterFactory.create(hal, CharacterSprites.getAddressLocator(), null, null),
64, 32, 0x200, program);
return new ChipVariation<ISuperChipHal>("S-CHIP",
hal -> InterpreterFactory.createSuperChip(hal, null, null), 64, 32, 0x200, program);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,24 @@
import dev.krk.chipsekiz.opcodes.Opcode;
import dev.krk.chipsekiz.vm.IVirtualMachine;

import javax.annotation.Nullable;

import java.util.Optional;

public class Executor implements IExecutor {
private IVirtualMachine vm;
private final IHal hal;
private final ICharacterAddressLocator characterAddressLocator;
private final boolean bitShiftsIgnoreVY;
private final boolean saveDumpIncreasesI;

public Executor() {
this(false, false);
public Executor(IHal hal, ICharacterAddressLocator characterAddressLocator) {
this(null, hal, characterAddressLocator, false, false);
}

public Executor(@Nullable IVirtualMachine vm, IHal hal,
ICharacterAddressLocator characterAddressLocator) {
this(vm, hal, characterAddressLocator, false, false);
}

/**
Expand All @@ -52,13 +62,21 @@ public Executor() {
* @param loadDumpIncreasesI FX55 and FX65 register load-dump operations increase I.
* https://en.wikipedia.org/wiki/CHIP-8#cite_note-increment-16
*/
public Executor(boolean bitShiftsIgnoreVY, boolean loadDumpIncreasesI) {
public Executor(@Nullable IVirtualMachine vm, IHal hal,
ICharacterAddressLocator characterAddressLocator, boolean bitShiftsIgnoreVY,
boolean loadDumpIncreasesI) {
this.vm = vm;
this.hal = hal;
this.characterAddressLocator = characterAddressLocator;
this.bitShiftsIgnoreVY = bitShiftsIgnoreVY;
this.saveDumpIncreasesI = loadDumpIncreasesI;
}

@Override public void execute(IVirtualMachine vm, IHal hal,
ICharacterAddressLocator characterAddressLocator, Opcode opcode) {
@Override public void setVM(IVirtualMachine vm) {
this.vm = vm;
}

@Override public void execute(Opcode opcode) {
switch (opcode.getKind()) {
case Op00E0 -> hal.clearScreen();
case Op00EE -> vm.setPC(vm.pop());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package dev.krk.chipsekiz.interpreter;

public interface IChipVariation {
public interface IChipVariation<THal extends IHal> {
String getName();

IInterpreter createInterpreter(IHal hal);
IInterpreter createInterpreter(THal hal);

int getDisplayWidth();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package dev.krk.chipsekiz.interpreter;

import dev.krk.chipsekiz.hal.ICharacterAddressLocator;
import dev.krk.chipsekiz.opcodes.Opcode;
import dev.krk.chipsekiz.vm.IVirtualMachine;

public interface IExecutor {
void execute(IVirtualMachine vm, IHal hal, ICharacterAddressLocator characterAddressLocator,
Opcode opcode);
void execute(Opcode opcode);

void setVM(IVirtualMachine vm);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package dev.krk.chipsekiz.interpreter;

public interface IInterpreterFactory {
IInterpreter create(IHal hal);
public interface IInterpreterFactory<THal> {
IInterpreter create(THal hal);
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
package dev.krk.chipsekiz.interpreter;

import dev.krk.chipsekiz.IDecoder;
import dev.krk.chipsekiz.hal.ICharacterAddressLocator;
import dev.krk.chipsekiz.loader.ILoader;
import dev.krk.chipsekiz.loader.Layout;
import dev.krk.chipsekiz.opcodes.OpFX0A;
import dev.krk.chipsekiz.opcodes.Opcode;
import dev.krk.chipsekiz.opcodes.OpcodeOrData;
import dev.krk.chipsekiz.tracer.ITracer;
import dev.krk.chipsekiz.vm.VM;
import dev.krk.chipsekiz.vm.IVirtualMachine;
import dev.krk.chipsekiz.vm.IVirtualMachineFactory;

import javax.annotation.Nullable;

public class Interpreter implements IInterpreter {
private VM vm;
private IVirtualMachine vm;
private final IDecoder decoder;
private final IExecutor executor;
private final ILoader loader;
private final IHal hal;
private final ICharacterAddressLocator characterAddressLocator;
@Nullable private ITracer tracer;
@Nullable private IDebugger debugger;
private final int memorySize;
Expand All @@ -29,41 +28,40 @@ public class Interpreter implements IInterpreter {
private Opcode lastExecutedOpcode;
private InterpreterStatus status;
private long prevTimersTick;
private IVirtualMachineFactory vmFactory;

public Interpreter(ILoader loader, IDecoder decoder, IExecutor executor, IHal hal,
ICharacterAddressLocator characterAddressLocator, @Nullable ITracer tracer, int origin,
byte[] program, int memorySize, Layout layout) {
this(loader, decoder, executor, hal, characterAddressLocator, tracer, null, memorySize,
layout, false);
public Interpreter(IVirtualMachineFactory vmFactory, ILoader loader, IDecoder decoder,
IExecutor executor, IHal hal, @Nullable ITracer tracer, int origin, byte[] program,
int memorySize, Layout layout) {
this(vmFactory, loader, decoder, executor, hal, tracer, null, memorySize, layout, false);

load(origin, program);
}

public Interpreter(ILoader loader, IDecoder decoder, IExecutor executor, IHal hal,
ICharacterAddressLocator characterAddressLocator, @Nullable ITracer tracer,
@Nullable IDebugger debugger, int origin, byte[] program, int memorySize, Layout layout,
boolean timersSixtyHertz) {
this(loader, decoder, executor, hal, characterAddressLocator, tracer, debugger, memorySize,
layout, timersSixtyHertz);
public Interpreter(IVirtualMachineFactory vmFactory, ILoader loader, IDecoder decoder,
IExecutor executor, IHal hal, @Nullable ITracer tracer, @Nullable IDebugger debugger,
int origin, byte[] program, int memorySize, Layout layout, boolean timersSixtyHertz) {
this(vmFactory, loader, decoder, executor, hal, tracer, debugger, memorySize, layout,
timersSixtyHertz);

load(origin, program);
}

public Interpreter(ILoader loader, IDecoder decoder, IExecutor executor, IHal hal,
ICharacterAddressLocator characterAddressLocator, @Nullable ITracer tracer,
@Nullable IDebugger debugger, int memorySize, Layout layout, boolean timersSixtyHertz) {
public Interpreter(IVirtualMachineFactory vmFactory, ILoader loader, IDecoder decoder,
IExecutor executor, IHal hal, @Nullable ITracer tracer, @Nullable IDebugger debugger,
int memorySize, Layout layout, boolean timersSixtyHertz) {
this.loader = loader;
this.decoder = decoder;
this.executor = executor;
this.hal = hal;
this.characterAddressLocator = characterAddressLocator;
this.tracer = tracer;
this.debugger = debugger;
this.memorySize = memorySize;
this.layout = layout;
this.timersSixtyHertz = timersSixtyHertz;
this.status = InterpreterStatus.READY;
this.prevTimersTick = System.nanoTime();
this.vmFactory = vmFactory;
}

private short fetch() {
Expand All @@ -78,7 +76,7 @@ private OpcodeOrData decode(short instruction) {
}

private void execute(Opcode opcode) {
executor.execute(vm, hal, characterAddressLocator, opcode);
executor.execute(opcode);
}

public void load(int origin, byte[] program) {
Expand All @@ -89,7 +87,8 @@ public void load(int origin, byte[] program) {
lastExecutedOpcode = null;

byte[] memory = loader.load(origin, program, memorySize, layout);
this.vm = new VM(origin, memory, debugger);
this.vm = this.vmFactory.create(origin, memory, debugger);
executor.setVM(vm);
if (debugger != null) {
debugger.setVM(vm);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,44 @@
package dev.krk.chipsekiz.interpreter;

import dev.krk.chipsekiz.Decoder;
import dev.krk.chipsekiz.hal.ICharacterAddressLocator;
import dev.krk.chipsekiz.IDecoder;
import dev.krk.chipsekiz.loader.Loader;
import dev.krk.chipsekiz.sprites.CharacterSprites;
import dev.krk.chipsekiz.superchip.decoder.SuperChipDecoder;
import dev.krk.chipsekiz.superchip.interpreter.ISuperChipHal;
import dev.krk.chipsekiz.superchip.interpreter.SuperChipExecutor;
import dev.krk.chipsekiz.superchip.sprites.SuperChipCharacterSprites;
import dev.krk.chipsekiz.superchip.vm.SuperChipVM;
import dev.krk.chipsekiz.tracer.ITracer;
import dev.krk.chipsekiz.vm.IVirtualMachineFactory;
import dev.krk.chipsekiz.vm.VM;

import javax.annotation.Nullable;

public class InterpreterFactory {
public static Interpreter create(IHal hal,
@Nullable ICharacterAddressLocator characterAddressLocator, @Nullable ITracer tracer,
public static IInterpreter create(IHal hal, @Nullable ITracer tracer,
@Nullable IDebugger debugger) {
IExecutor executor = new Executor(false, true);
IDecoder decoder = new Decoder();
IExecutor executor =
new Executor(null, hal, CharacterSprites.getAddressLocator(), false, true);

return create(hal, characterAddressLocator, tracer, debugger, executor);
return create(VM::new, decoder, hal, tracer, debugger, executor);
}

public static Interpreter create(IHal hal,
@Nullable ICharacterAddressLocator characterAddressLocator, @Nullable ITracer tracer,
@Nullable IDebugger debugger, IExecutor executor) {
public static IInterpreter createSuperChip(ISuperChipHal hal, @Nullable ITracer tracer,
@Nullable IDebugger debugger) {
IDecoder decoder = new SuperChipDecoder();
IExecutor executor =
new SuperChipExecutor(null, hal, SuperChipCharacterSprites.getLargeAddressLocator());

return create(SuperChipVM::new, decoder, hal, tracer, debugger, executor);
}

private static IInterpreter create(IVirtualMachineFactory vmFactory, IDecoder decoder, IHal hal,
@Nullable ITracer tracer, @Nullable IDebugger debugger, IExecutor executor) {
Loader loader = new Loader();
Decoder decoder = new Decoder();

return new Interpreter(loader, decoder, executor, hal, characterAddressLocator, tracer,
debugger, 0x1000, CharacterSprites.DefaultLayout(), true);
return new Interpreter(vmFactory, loader, decoder, executor, hal, tracer, debugger, 0x1000,
CharacterSprites.DefaultLayout(), true);
}
}
Loading

0 comments on commit 98031f1

Please sign in to comment.