Making progress on PPU implementation

This commit is contained in:
Felipe Lima 2018-08-10 18:03:19 -07:00
parent 9a9ad49aa3
commit 143bd655c6
8 changed files with 263 additions and 78 deletions

View File

@ -109,7 +109,7 @@ class CPU(val memory: Memory) : Display.Callbacks {
target.method.invoke() target.method.invoke()
} else { } else {
val candidate = Opcodes.MAP.entries val candidate = Opcodes.MAP.entries
.first { it.value.any { it.opcode == instruction } } .first { e -> e.value.any { it.opcode == instruction } }
throw Exception( throw Exception(
"Address $${PC.toHexString()} - unknown opcode 0x${instruction.toHexString()} " + "Address $${PC.toHexString()} - unknown opcode 0x${instruction.toHexString()} " +
"(instruction ${candidate.key.name})") "(instruction ${candidate.key.name})")

View File

@ -0,0 +1,9 @@
package android.emu6502
infix fun Byte.shr(other: Int): Byte {
return this.toInt().shr(other).toByte()
}
infix fun Byte.shl(other: Int): Byte {
return this.toInt().shr(other).toByte()
}

View File

@ -1,5 +1,13 @@
package android.emu6502.nes package android.emu6502.nes
class Controller { class Controller {
var buttons: Array<Boolean> = Array(8) { false }
var index: Byte = 0
var strobe: Byte = 0
fun read() {
}
fun write() {
}
} }

View File

@ -8,7 +8,6 @@ import java.util.*
// Sample header: // Sample header:
// 4e 45 53 1a 10 10 40 00 00 00 00 00 00 00 00 00 // 4e 45 53 1a 10 10 40 00 00 00 00 00 00 00 00 00
data class INESFileHeader( data class INESFileHeader(
// @formatter:off
val magic: ByteArray, // Constant $4E $45 $53 $1A ("NES" followed by MS-DOS end-of-file) val magic: ByteArray, // Constant $4E $45 $53 $1A ("NES" followed by MS-DOS end-of-file)
val numPRG: Byte, // Size of PRG ROM in 16 KB units val numPRG: Byte, // Size of PRG ROM in 16 KB units
val numCHR: Byte, // Size of CHR ROM in 8 KB units (Value 0 means the board uses CHR RAM) val numCHR: Byte, // Size of CHR ROM in 8 KB units (Value 0 means the board uses CHR RAM)
@ -16,7 +15,6 @@ data class INESFileHeader(
val control2: Byte, // Flags 7 val control2: Byte, // Flags 7
val numRAM: Byte, // Size of PRG RAM in 8 KB units val numRAM: Byte, // Size of PRG RAM in 8 KB units
val padding: ByteArray // 7 bytes, unused val padding: ByteArray // 7 bytes, unused
// @formatter:on
) { ) {
fun isValid() = fun isValid() =
Arrays.equals(magic, INES_FILE_MAGIC) && Arrays.equals(padding, PADDING) Arrays.equals(magic, INES_FILE_MAGIC) && Arrays.equals(padding, PADDING)

View File

@ -21,30 +21,31 @@ internal class INESFileParser {
(0..6).map { dataStream.readByte() }.toByteArray()) (0..6).map { dataStream.readByte() }.toByteArray())
} }
internal fun parseCartridge(file: File): Cartridge { internal fun parseCartridge(file: File): Cartridge =
val stream = file.inputStream() file.inputStream().use {
stream.use { val inesFileHeader = parseFileHeader(it)
val inesFileHeader = parseFileHeader(stream) // mapper type
// mapper type val control1 = inesFileHeader.control1.toInt()
val control1 = inesFileHeader.control1.toInt() val mapper1 = control1 shr 4
val mapper1 = control1 shr 4 val mapper2 = inesFileHeader.control2.toInt() shr 4
val mapper2 = inesFileHeader.control2.toInt() shr 4 val mapper = mapper1 or (mapper2 shl 4)
val mapper = mapper1 or (mapper2 shl 4) // mirroring type
// mirroring type val mirror1 = control1 and 1
val mirror1 = control1 and 1 val mirror2 = (control1 shr 3) and 1
val mirror2 = (control1 shr 3) and 1 val mirror = mirror1 or (mirror2 shl 1)
val mirror = mirror1 or (mirror2 shl 1) // battery-backed RAM
// battery-backed RAM val battery = (control1 shr 1).and(1).toByte()
val battery = control1.shr(1).and(1).toByte() // read prg-rom bank(s)
// read prg-rom bank(s) val prg = ByteArray(inesFileHeader.numPRG.toInt() * 16384)
val pgr = ByteArray(inesFileHeader.numPRG.toInt() * 16384) it.read(prg)
stream.read(pgr) // read chr-rom bank(s)
// read chr-rom bank(s) val chr = ByteArray(inesFileHeader.numCHR.toInt() * 8192)
val chr = ByteArray(inesFileHeader.numCHR.toInt() * 8192) it.read(chr)
stream.read(chr) return Cartridge(
return Cartridge(pgr.map(Byte::toInt).toIntArray(), chr.map(Byte::toInt).toIntArray(), prg.map(Byte::toInt).toIntArray(),
mapper.toByte(), mirror, battery) chr.map(Byte::toInt).toIntArray(),
} mapper.toByte(), mirror, battery
} )
}
} }
} }

View File

@ -1,47 +1,211 @@
package android.emu6502.nes package android.emu6502.nes
import android.emu6502.shl
import android.emu6502.shr
import android.media.Image
import android.system.Os.read
import kotlin.experimental.and
import kotlin.experimental.xor
class PPU( class PPU(
val console: Console,
// @formatter:off // @formatter:off
val cycle: Int = 0, // 0-340 var cycle: Int = 0, // 0-340
val scanLine: Int = 0, // 0-261, 0-239=visible, 240=post, 241-260=vblank, 261=pre var scanLine: Int = 0, // 0-261, 0-239=visible, 240=post, 241-260=vblank, 261=pre
val frame: Int = 0, // frame counter var frame: Int = 0, // frame counter
// storage variables
var paletteData: ByteArray = ByteArray(32),
var nameTableData: ByteArray = ByteArray(2048),
var oamData: ByteArray = ByteArray(256),
var front: Image,
var back: Image,
// PPU registers // PPU registers
val v: Int = 0, // current vram address (15 bit) var v: Int = 0, // current vram address (15 bit)
val t: Int = 0, // temporary vram address (15 bit) var t: Int = 0, // temporary vram address (15 bit)
val x: Byte = 0, // fine x scroll (3 bit) var x: Byte = 0, // fine x scroll (3 bit)
val w: Byte = 0, // write toggle (1 bit) var w: Byte = 0, // write toggle (1 bit)
val f: Byte = 0, // even/odd frame flag (1 bit) var f: Byte = 0, // even/odd frame flag (1 bit)
val register: Byte = 0, var register: Byte = 0,
var nmiOccurred: Boolean = false,
var nmiOutput: Boolean = false,
var nmiPrevious: Boolean = false,
var nmiDelay: Byte = 0,
// background temporary variables
var nameTableByte: Byte = 0,
var attributeTableByte: Byte = 0,
var lowTileByte: Byte = 0,
var highTileByte: Byte = 0,
var tileData: Int = 0,
// $2000 PPUCTRL // $2000 PPUCTRL
val flagNameTable: Boolean = false, // 0: $2000; 1: $2400; 2: $2800; 3: $2C00 var flagNameTable: Byte = 0, // 0: $2000; 1: $2400; 2: $2800; 3: $2C00
val flagIncrement: Boolean = false, // 0: add 1; 1: add 32 var flagIncrement: Byte = 0, // 0: add 1; 1: add 32
val flagSpriteTable: Boolean = false, // 0: $0000; 1: $1000; ignored in 8x16 mode var flagSpriteTable: Byte = 0, // 0: $0000; 1: $1000; ignored in 8x16 mode
val flagBackgroundTable: Boolean = false, // 0: $0000; 1: $1000 var flagBackgroundTable: Byte = 0, // 0: $0000; 1: $1000
val flagSpriteSize: Boolean = false, // 0: 8x8; 1: 8x16 var flagSpriteSize: Byte = 0, // 0: 8x8; 1: 8x16
val flagMasterSlave: Boolean = false, // 0: read EXT; 1: write EXT var flagMasterSlave: Byte = 0, // 0: read EXT; 1: write EXT
// $2001 PPUMASK // $2001 PPUMASK
val flagGrayscale: Boolean = false, // 0: color; 1: grayscale var flagGrayscale: Byte = 0, // 0: color; 1: grayscale
val flagShowLeftBackground: Boolean = false, // 0: hide; 1: show var flagShowLeftBackground: Byte = 0, // 0: hide; 1: show
val flagShowLeftSprites: Boolean = false, // 0: hide; 1: show var flagShowLeftSprites: Byte = 0, // 0: hide; 1: show
val flagShowBackground: Boolean = false, // 0: hide; 1: show var flagShowBackground: Byte = 0, // 0: hide; 1: show
val flagShowSprites: Boolean = false, // 0: hide; 1: show var flagShowSprites: Byte = 0, // 0: hide; 1: show
val flagRedTint: Boolean = false, // 0: normal; 1: emphasized var flagRedTint: Byte = 0, // 0: normal; 1: emphasized
val flagGreenTint: Boolean = false, // 0: normal; 1: emphasized var flagGreenTint: Byte = 0, // 0: normal; 1: emphasized
val flagBlueTint: Boolean = false, // 0: normal; 1: emphasized var flagBlueTint: Byte = 0, // 0: normal; 1: emphasized
// $2002 PPUSTATUS // $2002 PPUSTATUS
val flagSpriteZeroHit: Boolean = false, val flagSpriteZeroHit: Boolean = false,
val flagSpriteOverflow: Boolean = false, val flagSpriteOverflow: Boolean = false,
// $2003 OAMADDR // $2003 OAMADDR
val oamAddress: Byte = 0, var oamAddress: Byte = 0,
// $2007 PPUDATA // $2007 PPUDATA
val bufferedData: Byte = 0 // for buffered reads val bufferedData: Byte = 0 // for buffered reads
// @formatter:on // @formatter:on
) ) {
fun writeRegister(address: Int, value: Byte) {
register = value
when (address) {
0x2000 -> writeControl(value)
0x2001 -> writeMask(value)
0x2003 -> writeOAMAddress(value)
0x2004 -> writeOAMData(value)
0x2005 -> writeScroll(value)
0x2006 -> writeAddress(value)
0x2007 -> writeData(value)
0x4014 -> writeDMA(value)
}
}
private fun writeDMA(value: Byte) {
TODO()
}
private fun step() {
tick()
val renderingEnabled = flagShowBackground != 0.toByte() || flagShowSprites != 0.toByte()
val preLine = scanLine == 261
val visibleLine = scanLine < 240
// postLine = scanLine == 240
val renderLine = preLine || visibleLine
val preFetchCycle = cycle in 321..336
val visibleCycle = cycle in 1..256
val fetchCycle = preFetchCycle || visibleCycle
}
private fun tick() {
if (nmiDelay > 0) {
nmiDelay--
if (nmiDelay == 0.toByte() && nmiOutput && nmiOccurred) {
console.cpu.triggerNMI()
}
}
if (flagShowBackground != 0.toByte() || flagShowSprites != 0.toByte()) {
if (f == 1.toByte() && scanLine == 261 && cycle == 339) {
cycle = 0
scanLine = 0
frame++
f = f xor 1
return
}
}
cycle++
if (cycle > 340) {
cycle = 0
scanLine++
if (scanLine > 261) {
scanLine = 0
frame++
f = f xor 1
}
}
}
private fun writeData(value: Byte) {
TODO("not implemented")
}
// $2006: PPUADDR
private fun writeAddress(value: Byte) {
if (w == 0.toByte()) {
// t: ..FEDCBA ........ = d: ..FEDCBA
// t: .X...... ........ = 0
// w: = 1
t = (t and 0x80FF) or ((value and 0x3F) shl 8).toInt()
w = 1
} else {
// t: ........ HGFEDCBA = d: HGFEDCBA
// v = t
// w: = 0
t = (t and 0xFF00) or value.toInt()
v = t
w = 0
}
}
// $2005: PPUSCROLL
private fun writeScroll(value: Byte) {
TODO("not implemented")
}
// $2004: OAMDATA (write)
private fun writeOAMData(value: Byte) {
oamData[oamAddress.toInt()] = value
oamAddress++
}
// $2003: OAMADDR
private fun writeOAMAddress(value: Byte) {
oamAddress = value
}
private fun writeMask(value: Byte) {
flagGrayscale = (value shr 0) and 1
flagShowLeftBackground = (value shr 1) and 1
flagShowLeftSprites = (value shr 2) and 1
flagShowBackground = (value shr 3) and 1
flagShowSprites = (value shr 4) and 1
flagRedTint = (value shr 5) and 1
flagGreenTint = (value shr 6) and 1
flagBlueTint = (value shr 7) and 1
}
// $2000: PPUCTRL
private fun writeControl(value: Byte) {
flagNameTable = (value shr 0) and 3
flagIncrement = (value shr 2) and 1
flagSpriteTable = (value shr 3) and 1
flagBackgroundTable = (value shr 4) and 1
flagSpriteSize = (value shr 5) and 1
flagMasterSlave = (value shr 6) and 1
nmiOutput = (value shr 7) and 1 == 1.toByte()
nmiChange()
// t: ....BA.. ........ = d: ......BA
t = (t and 0xF3FF) or ((value and 0x03) shl 10).toInt()
}
private fun nmiChange() {
val nmi = nmiOutput && nmiOccurred
if (nmi && !nmiPrevious) {
// TODO: this fixes some games but the delay shouldn't have to be so
// long, so the timings are off somewhere
nmiDelay = 15
}
nmiPrevious = nmi
}
private fun fetchNameTableByte() {
val address = 0x2000 or (v and 0x0FFF)
nameTableByte = read(address)
}
}

View File

@ -22,34 +22,39 @@ class MMC3(
private var irqEnable: Boolean = false private var irqEnable: Boolean = false
override fun read(address: Int): Int { override fun read(address: Int): Int {
if (address < 0x2000) { return when {
val bank = address / 0x0400 address < 0x2000 -> {
val offset = address % 0x0400 val bank = address / 0x0400
return cartridge.chr[chrOffsets[bank] + offset] val offset = address % 0x0400
cartridge.chr[chrOffsets[bank] + offset]
}
address >= 0x8000 -> {
val addr = address - 0x8000
val bank = addr / 0x2000
val offset = addr % 0x2000
cartridge.pgr[prgOffsets[bank] + offset]
}
address >= 0x6000 -> {
return cartridge.sram[address - 0x6000]
}
else -> throw RuntimeException("unhandled mapper4 read at address: ${address.toHexString()}")
} }
if (address >= 0x8000) {
val addr = address - 0x8000
val bank = addr / 0x2000
val offset = addr % 0x2000
return cartridge.pgr[prgOffsets[bank] + offset]
}
if (address >= 0x6000) {
return cartridge.sram[address - 0x6000]
}
throw RuntimeException("unhandled mapper4 read at address: ${address.toHexString()}")
} }
override fun write(address: Int, value: Int) { override fun write(address: Int, value: Int) {
if (address < 0x2000) { when {
val bank = address / 0x0400 address < 0x2000 -> {
val offset = address % 0x0400 val bank = address / 0x0400
cartridge.chr[chrOffsets[bank] + offset] = value val offset = address % 0x0400
} else if (address >= 0x8000) { cartridge.chr[chrOffsets[bank] + offset] = value
writeRegister(address, value) }
} else if (address >= 0x6000) { address >= 0x8000 -> {
cartridge.sram[address - 0x6000] = value writeRegister(address, value)
} else { }
throw RuntimeException("unhandled mapper4 write at address ${address.toHexString()}") address >= 0x6000 -> {
cartridge.sram[address - 0x6000] = value
}
else -> throw RuntimeException("unhandled mapper4 write at address ${address.toHexString()}")
} }
} }

View File

@ -13,7 +13,7 @@ interface Mapper {
fun newMapper(cartridge: Cartridge, ppu: PPU, cpu: CPU): Mapper = fun newMapper(cartridge: Cartridge, ppu: PPU, cpu: CPU): Mapper =
when (cartridge.mapper.toInt()) { when (cartridge.mapper.toInt()) {
4 -> MMC3(cartridge, ppu, cpu) 4 -> MMC3(cartridge, ppu, cpu)
else -> throw NotImplementedError() else -> throw NotImplementedError("Mapper ${cartridge.mapper.toInt()} not implemented")
} }
} }
} }