mirror of
https://github.com/felipecsl/6502Android.git
synced 2024-06-08 06:29:32 +00:00
Making progress on PPU implementation
This commit is contained in:
parent
9a9ad49aa3
commit
143bd655c6
|
@ -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})")
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -21,10 +21,9 @@ 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
|
||||||
|
@ -35,16 +34,18 @@ internal class INESFileParser {
|
||||||
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 pgr = ByteArray(inesFileHeader.numPRG.toInt() * 16384)
|
val prg = ByteArray(inesFileHeader.numPRG.toInt() * 16384)
|
||||||
stream.read(pgr)
|
it.read(prg)
|
||||||
// read chr-rom bank(s)
|
// read chr-rom bank(s)
|
||||||
val chr = ByteArray(inesFileHeader.numCHR.toInt() * 8192)
|
val chr = ByteArray(inesFileHeader.numCHR.toInt() * 8192)
|
||||||
stream.read(chr)
|
it.read(chr)
|
||||||
return Cartridge(pgr.map(Byte::toInt).toIntArray(), chr.map(Byte::toInt).toIntArray(),
|
return Cartridge(
|
||||||
mapper.toByte(), mirror, battery)
|
prg.map(Byte::toInt).toIntArray(),
|
||||||
}
|
chr.map(Byte::toInt).toIntArray(),
|
||||||
|
mapper.toByte(), mirror, battery
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
address < 0x2000 -> {
|
||||||
val bank = address / 0x0400
|
val bank = address / 0x0400
|
||||||
val offset = address % 0x0400
|
val offset = address % 0x0400
|
||||||
return cartridge.chr[chrOffsets[bank] + offset]
|
cartridge.chr[chrOffsets[bank] + offset]
|
||||||
}
|
}
|
||||||
if (address >= 0x8000) {
|
address >= 0x8000 -> {
|
||||||
val addr = address - 0x8000
|
val addr = address - 0x8000
|
||||||
val bank = addr / 0x2000
|
val bank = addr / 0x2000
|
||||||
val offset = addr % 0x2000
|
val offset = addr % 0x2000
|
||||||
return cartridge.pgr[prgOffsets[bank] + offset]
|
cartridge.pgr[prgOffsets[bank] + offset]
|
||||||
}
|
}
|
||||||
if (address >= 0x6000) {
|
address >= 0x6000 -> {
|
||||||
return cartridge.sram[address - 0x6000]
|
return cartridge.sram[address - 0x6000]
|
||||||
}
|
}
|
||||||
throw RuntimeException("unhandled mapper4 read at address: ${address.toHexString()}")
|
else -> 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 {
|
||||||
|
address < 0x2000 -> {
|
||||||
val bank = address / 0x0400
|
val bank = address / 0x0400
|
||||||
val offset = address % 0x0400
|
val offset = address % 0x0400
|
||||||
cartridge.chr[chrOffsets[bank] + offset] = value
|
cartridge.chr[chrOffsets[bank] + offset] = value
|
||||||
} else if (address >= 0x8000) {
|
}
|
||||||
|
address >= 0x8000 -> {
|
||||||
writeRegister(address, value)
|
writeRegister(address, value)
|
||||||
} else if (address >= 0x6000) {
|
}
|
||||||
|
address >= 0x6000 -> {
|
||||||
cartridge.sram[address - 0x6000] = value
|
cartridge.sram[address - 0x6000] = value
|
||||||
} else {
|
}
|
||||||
throw RuntimeException("unhandled mapper4 write at address ${address.toHexString()}")
|
else -> throw RuntimeException("unhandled mapper4 write at address ${address.toHexString()}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user