diff options
| author | crecher <crecher@crecher.net> | 2026-10-13 01:13:13 +0200 |
|---|---|---|
| committer | crecher <crecher@crecher.net> | 2026-10-13 01:13:13 +0200 |
| commit | 99cae5a5a37f41f878a84857361eda47910303ba (patch) | |
| tree | 31c8665ca991f95e09e93bb2e6ec91d81d2427ff /nes | |
| parent | 96ff6d8eda2ba434682909f4ff4ce350d78b2273 (diff) | |
| download | gones-99cae5a5a37f41f878a84857361eda47910303ba.tar.gz gones-99cae5a5a37f41f878a84857361eda47910303ba.zip | |
sprites!!!!!!
Diffstat (limited to 'nes')
| -rw-r--r-- | nes/bus.go | 9 | ||||
| -rw-r--r-- | nes/controller.go | 53 | ||||
| -rw-r--r-- | nes/dma.go | 19 | ||||
| -rw-r--r-- | nes/nes.go | 11 |
4 files changed, 91 insertions, 1 deletions
@@ -24,6 +24,10 @@ func (n *Console) Read(addr uint16) uint8 { return n.RAM[addr&0x7FF] case addr < 0x4000: // PPU registers, mirrored every 8 bytes return n.PPU.ReadRegister(addr) + case addr == 0x4016: // controller 1 + return n.controllers[0].read() + case addr == 0x4017: // controller 2 + return n.controllers[1].read() case addr >= 0x4020: // cartridge return n.Cart.Mapper.CPURead(addr) default: @@ -37,6 +41,11 @@ func (n *Console) Write(addr uint16, v uint8) { n.RAM[addr&0x7FF] = v case addr < 0x4000: n.PPU.WriteRegister(addr, v) + case addr == 0x4014: // oam dma + n.oamDMA(v) + case addr == 0x4016: // strobe is shared by both controllers + n.controllers[0].setStrobe(v&1 != 0) + n.controllers[1].setStrobe(v&1 != 0) case addr >= 0x4020: n.Cart.Mapper.CPUWrite(addr, v) } diff --git a/nes/controller.go b/nes/controller.go new file mode 100644 index 0000000..51afe20 --- /dev/null +++ b/nes/controller.go @@ -0,0 +1,53 @@ +package nes + +// bit order matches the order the console reads them out in +const ( + ButtonA uint8 = 1 << iota + ButtonB + ButtonSelect + ButtonStart + ButtonUp + ButtonDown + ButtonLeft + ButtonRight +) + +// standard controller, it is just an 8 bit shift register +// https://www.nesdev.org/wiki/Standard_controller +type controller struct { + buttons uint8 // what is held right now + shift uint8 // what the cpu is reading out + strobe bool +} + +// while strobe is high the shift register keeps copying the buttons +func (c *controller) setStrobe(on bool) { + c.strobe = on + if on { + c.shift = c.buttons + } +} + +func (c *controller) setButtons(b uint8) { + c.buttons = b + if c.strobe { + c.shift = b + } +} + +// returns one button per read, A first. with strobe high it stays on A +func (c *controller) read() uint8 { + bit := c.shift & 1 + if !c.strobe { + // ones get shifted in, so reads after the 8th return 1 + c.shift = c.shift>>1 | 0x80 + } + // upper bits are open bus, which is $40 here since that is the high byte + // of the address that was read + return 0x40 | bit +} + +// port is 0 for controller 1 and 1 for controller 2 +func (n *Console) SetButtons(port int, buttons uint8) { + n.controllers[port].setButtons(buttons) +} diff --git a/nes/dma.go b/nes/dma.go new file mode 100644 index 0000000..ebdbcea --- /dev/null +++ b/nes/dma.go @@ -0,0 +1,19 @@ +package nes + +// im too lazy to write an explanation so heres the nesdev page +// https://www.nesdev.org/wiki/DMA#OAM_DMA +func (n *Console) oamDMA(page uint8) { + base := uint16(page) << 8 + for i := uint16(0); i < 256; i++ { + n.PPU.WriteRegister(0x2004, n.Read(base+i)) + } + n.dmaPending = true +} + +func (n *Console) dmaStallCycles() int { + stall := 513 + if n.CPU.Cycles&1 == 1 { + stall++ + } + return stall +} @@ -11,7 +11,10 @@ type Console struct { PPU *ppu.PPU Cart *cart.Cartridge RAM [0x800]uint8 - // APU, controllers once they exist + + controllers [2]controller + dmaPending bool + // APU once it exists cycles uint64 } @@ -25,6 +28,12 @@ func New(c *cart.Cartridge) *Console { func (n *Console) Step() int { cycles := n.CPU.Step() + if n.dmaPending { + n.dmaPending = false + stall := n.dmaStallCycles() + n.CPU.Stall(stall) + cycles += stall + } n.cycles += uint64(cycles) for i := 0; i < cycles*3; i++ { n.PPU.Tick() |