Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local component = require("component")
- local gpu = component.gpu
- local computer = require("computer")
- local filesystem = require("filesystem")
- local event = require("event")
- local keyboard = require("keyboard")
- local bit = require("bit32")
- local band, bor, bxor, bnot, lshift, rshift = bit.band, bit.bor, bit.bxor, bit.bnot, bit.lshift, bit.rshift
- -- Utility functions
- local function nthBitIsSet(value, n)
- return band(value, lshift(1, n)) ~= 0
- end
- local function nthBitIsSetInt(value, n)
- return band(value, lshift(1, n)) ~= 0 and 1 or 0
- end
- -- CPU Emulation
- local CPU = {}
- CPU.__index = CPU
- CPU.RP2A03_CC = 12
- CPU.FOREVER_CLOCK = 0xffffffff
- CPU.CLK = {}
- do
- local clocks = {1, 2, 3, 4, 5, 6, 7, 8}
- for i = 1, #clocks do
- CPU.CLK[i] = clocks[i] * CPU.RP2A03_CC
- end
- end
- CPU.RAM_SIZE = 0x0800
- CPU.MAINMEM_SIZE = 0x10000
- CPU.NMI_VECTOR = 0xfffa
- CPU.RESET_VECTOR = 0xfffc
- CPU.IRQ_VECTOR = 0xfffe
- CPU.IRQ_EXT = 0x01
- CPU.IRQ_FRAME = 0x40
- CPU.IRQ_DMC = 0x80
- CPU.ClockRates = {
- Ntsc = 1789773,
- Pal = 1662607,
- Dendy = 1773448
- }
- function CPU:new(conf)
- local cpu = setmetatable({}, CPU)
- cpu:initialize(conf)
- return cpu
- end
- function CPU:initialize(conf)
- self.conf = conf or {loglevel = 0, pc = nil}
- self.ram = {}
- for i = 0, CPU.RAM_SIZE - 1 do
- self.ram[i] = 0
- end
- self._store = {}
- self._fetch = {}
- for i = 0, CPU.MAINMEM_SIZE - 1 do
- self._store[i] = CPU.PokeNop
- self._fetch[i] = CPU.peek_nop
- end
- self.peeks = {}
- self.pokes = {}
- self.clk_rate = CPU.ClockRates.Ntsc
- self.clk = 0
- self.clk_frame = 0
- self.clk_target = 0
- self.clk_total = 0
- self.clk_nmi = CPU.FOREVER_CLOCK
- self.clk_irq = CPU.FOREVER_CLOCK
- self.irq_flags = 0
- self.jammed = false
- self:reset()
- self.data = 0
- self.addr = 0
- self.opcode = nil
- self.ppu_sync = nil
- -- DUMMY APU
- self.apu = {
- clock_dma = function(clk) end,
- do_clock = function() return CPU.CLK[1] end
- }
- end
- function CPU:reset()
- self._a = 0
- self._x = 0
- self._y = 0
- self._sp = 0xfd
- self._pc = 0xfffc
- self._p_nz = 1
- self._p_c = 0
- self._p_v = 0
- self._p_i = 0x04
- self._p_d = 0
- for i = 0, CPU.RAM_SIZE - 1 do
- self.ram[i] = 0xff
- end
- self.clk = 0
- self.clk_total = 0
- self:add_mappings(0x0000, 0x07ff, self.peek_ram, self.poke_ram)
- self:add_mappings(0x0800, 0x1fff, self.peek_ram, self.poke_ram)
- self:add_mappings(0x2000, 0xffff, self.peek_nop, CPU.PokeNop)
- end
- function CPU:add_mappings(start_addr, end_addr, peek, poke)
- for addr = start_addr, end_addr do
- self._fetch[addr] = peek
- self._store[addr] = poke
- end
- end
- function CPU:peek_nop(addr)
- return rshift(addr, 8)
- end
- function CPU:peek_ram(addr)
- return self.ram[addr % CPU.RAM_SIZE]
- end
- function CPU:poke_ram(addr, data)
- self.ram[addr % CPU.RAM_SIZE] = data
- end
- CPU.PokeNop = function() end
- function CPU:fetch(addr)
- return self._fetch[addr](self, addr)
- end
- function CPU:store(addr, value)
- return self._store[addr](self, addr, value)
- end
- function CPU:peek16(addr)
- return self:fetch(addr) + lshift(self:fetch(addr + 1), 8)
- end
- function CPU:push8(data)
- self:store(0x0100 + self._sp, data)
- self._sp = band(self._sp - 1, 0xff)
- end
- function CPU:push16(data)
- self:push8(rshift(data, 8))
- self:push8(band(data, 0xff))
- end
- function CPU:pull8()
- self._sp = band(self._sp + 1, 0xff)
- return self:fetch(0x0100 + self._sp)
- end
- function CPU:pull16()
- local lo = self:pull8()
- local hi = self:pull8()
- return lo + hi * 256
- end
- function CPU:flags_pack()
- local nz = self._p_nz
- return bor(
- bor(
- bor(
- bor(
- bor(
- bor(
- band(bor(rshift(nz, 1), nz), 0x80),
- (band(nz, 0xff) ~= 0 and 0 or 2)
- ),
- self._p_c
- ),
- self._p_v ~= 0 and 0x40 or 0
- ),
- self._p_i
- ),
- self._p_d
- ),
- 0x20
- )
- end
- function CPU:flags_unpack(f)
- self._p_nz = bor(band(bnot(f), 2), lshift(band(f, 0x80), 1))
- self._p_c = band(f, 0x01)
- self._p_v = band(f, 0x40)
- self._p_i = band(f, 0x04)
- self._p_d = band(f, 0x08)
- end
- function CPU:branch(cond)
- if cond then
- local tmp = self._pc + 1
- local rel = self:fetch(self._pc)
- self._pc = band(tmp + (rel < 128 and rel or bor(rel, 0xff00)), 0xffff)
- self.clk = self.clk + (nthBitIsSetInt(tmp, 8) == nthBitIsSetInt(self._pc, 8) and CPU.CLK[3] or CPU.CLK[4])
- else
- self._pc = self._pc + 1
- self.clk = self.clk + CPU.CLK[2]
- end
- end
- function CPU:do_irq(line, clk)
- self.irq_flags = bor(self.irq_flags, line)
- if self.clk_irq == CPU.FOREVER_CLOCK and self._p_i == 0 then
- self.clk_irq = self:next_interrupt_clock(clk)
- end
- end
- function CPU:do_nmi(clk)
- if self.clk_nmi == CPU.FOREVER_CLOCK then
- self.clk_nmi = self:next_interrupt_clock(clk)
- end
- end
- function CPU:do_isr(vector)
- if self.jammed then return end
- self:push16(self._pc)
- self:push8(self:flags_pack())
- self._p_i = 0x04
- self.clk = self.clk + CPU.CLK[7]
- local addr = vector == CPU.NMI_VECTOR and CPU.NMI_VECTOR or self:fetch_irq_isr_vector()
- self._pc = self:peek16(addr)
- end
- function CPU:fetch_irq_isr_vector()
- if self.clk >= self.clk_frame then
- self:fetch(0x3000)
- end
- if self.clk_nmi ~= CPU.FOREVER_CLOCK then
- if self.clk_nmi + CPU.CLK[2] <= self.clk then
- self.clk_nmi = CPU.FOREVER_CLOCK
- return CPU.NMI_VECTOR
- end
- self.clk_nmi = self.clk + 1
- end
- return CPU.IRQ_VECTOR
- end
- function CPU:step()
- local opcode = self:fetch(self._pc)
- self._pc = band(self._pc + 1, 0xffff)
- self:executeInstruction(opcode)
- end
- function CPU:executeInstruction(opcode)
- local instructions = {
- [0x00] = function() self:BRK() end,
- [0x01] = function() self:ORA(self:IndirectX()) end,
- [0x05] = function() self:ORA(self:ZeroPage()) end,
- [0x06] = function() self:ASL(self:ZeroPage()) end,
- [0x08] = function() self:PHP() end,
- [0x09] = function() self:ORA(self:Immediate()) end,
- [0x0A] = function() self:ASL_A() end,
- [0x0D] = function() self:ORA(self:Absolute()) end,
- [0x0E] = function() self:ASL(self:Absolute()) end,
- [0x10] = function() self:BPL() end,
- [0x11] = function() self:ORA(self:IndirectY()) end,
- [0x15] = function() self:ORA(self:ZeroPageX()) end,
- [0x16] = function() self:ASL(self:ZeroPageX()) end,
- [0x18] = function() self:CLC() end,
- [0x19] = function() self:ORA(self:AbsoluteY()) end,
- [0x1D] = function() self:ORA(self:AbsoluteX()) end,
- [0x1E] = function() self:ASL(self:AbsoluteX()) end,
- [0x20] = function() self:JSR() end,
- [0x21] = function() self:AND(self:IndirectX()) end,
- [0x24] = function() self:BIT(self:ZeroPage()) end,
- [0x25] = function() self:AND(self:ZeroPage()) end,
- [0x26] = function() self:ROL(self:ZeroPage()) end,
- [0x28] = function() self:PLP() end,
- [0x29] = function() self:AND(self:Immediate()) end,
- [0x2A] = function() self:ROL_A() end,
- [0x2C] = function() self:BIT(self:Absolute()) end,
- [0x2D] = function() self:AND(self:Absolute()) end,
- [0x2E] = function() self:ROL(self:Absolute()) end,
- [0x30] = function() self:BMI() end,
- [0x31] = function() self:AND(self:IndirectY()) end,
- [0x35] = function() self:AND(self:ZeroPageX()) end,
- [0x36] = function() self:ROL(self:ZeroPageX()) end,
- [0x38] = function() self:SEC() end,
- [0x39] = function() self:AND(self:AbsoluteY()) end,
- [0x3D] = function() self:AND(self:AbsoluteX()) end,
- [0x3E] = function() self:ROL(self:AbsoluteX()) end,
- [0x40] = function() self:RTI() end,
- [0x41] = function() self:EOR(self:IndirectX()) end,
- [0x45] = function() self:EOR(self:ZeroPage()) end,
- [0x46] = function() self:LSR(self:ZeroPage()) end,
- [0x48] = function() self:PHA() end,
- [0x49] = function() self:EOR(self:Immediate()) end,
- [0x4A] = function() self:LSR_A() end,
- [0x4C] = function() self:JMP_ABS() end,
- [0x4D] = function() self:EOR(self:Absolute()) end,
- [0x4E] = function() self:LSR(self:Absolute()) end,
- [0x50] = function() self:BVC() end,
- [0x51] = function() self:EOR(self:IndirectY()) end,
- [0x55] = function() self:EOR(self:ZeroPageX()) end,
- [0x56] = function() self:LSR(self:ZeroPageX()) end,
- [0x58] = function() self:CLI() end,
- [0x59] = function() self:EOR(self:AbsoluteY()) end,
- [0x5D] = function() self:EOR(self:AbsoluteX()) end,
- [0x5E] = function() self:LSR(self:AbsoluteX()) end,
- [0x60] = function() self:RTS() end,
- [0x61] = function() self:ADC(self:IndirectX()) end,
- [0x65] = function() self:ADC(self:ZeroPage()) end,
- [0x66] = function() self:ROR(self:ZeroPage()) end,
- [0x68] = function() self:PLA() end,
- [0x69] = function() self:ADC(self:Immediate()) end,
- [0x6A] = function() self:ROR_A() end,
- [0x6C] = function() self:JMP_IND() end,
- [0x6D] = function() self:ADC(self:Absolute()) end,
- [0x6E] = function() self:ROR(self:Absolute()) end,
- [0x70] = function() self:BVS() end,
- [0x71] = function() self:ADC(self:IndirectY()) end,
- [0x75] = function() self:ADC(self:ZeroPageX()) end,
- [0x76] = function() self:ROR(self:ZeroPageX()) end,
- [0x78] = function() self:SEI() end,
- [0x79] = function() self:ADC(self:AbsoluteY()) end,
- [0x7D] = function() self:ADC(self:AbsoluteX()) end,
- [0x7E] = function() self:ROR(self:AbsoluteX()) end,
- [0x81] = function() self:STA(self:IndirectX()) end,
- [0x84] = function() self:STY(self:ZeroPage()) end,
- [0x85] = function() self:STA(self:ZeroPage()) end,
- [0x86] = function() self:STX(self:ZeroPage()) end,
- [0x88] = function() self:DEY() end,
- [0x8A] = function() self:TXA() end,
- [0x8C] = function() self:STY(self:Absolute()) end,
- [0x8D] = function() self:STA(self:Absolute()) end,
- [0x8E] = function() self:STX(self:Absolute()) end,
- [0x90] = function() self:BCC() end,
- [0x91] = function() self:STA(self:IndirectY()) end,
- [0x94] = function() self:STY(self:ZeroPageX()) end,
- [0x95] = function() self:STA(self:ZeroPageX()) end,
- [0x96] = function() self:STX(self:ZeroPageY()) end,
- [0x98] = function() self:TYA() end,
- [0x99] = function() self:STA(self:AbsoluteY()) end,
- [0x9A] = function() self:TXS() end,
- [0x9D] = function() self:STA(self:AbsoluteX()) end,
- [0xA0] = function() self:LDY(self:Immediate()) end,
- [0xA1] = function() self:LDA(self:IndirectX()) end,
- [0xA2] = function() self:LDX(self:Immediate()) end,
- [0xA4] = function() self:LDY(self:ZeroPage()) end,
- [0xA5] = function() self:LDA(self:ZeroPage()) end,
- [0xA6] = function() self:LDX(self:ZeroPage()) end,
- [0xA8] = function() self:TAY() end,
- [0xA9] = function() self:LDA(self:Immediate()) end,
- [0xAA] = function() self:TAX() end,
- [0xAC] = function() self:LDY(self:Absolute()) end,
- [0xAD] = function() self:LDA(self:Absolute()) end,
- [0xAE] = function() self:LDX(self:Absolute()) end,
- [0xB0] = function() self:BCS() end,
- [0xB1] = function() self:LDA(self:IndirectY()) end,
- [0xB4] = function() self:LDY(self:ZeroPageX()) end,
- [0xB5] = function() self:LDA(self:ZeroPageX()) end,
- [0xB6] = function() self:LDX(self:ZeroPageY()) end,
- [0xB8] = function() self:CLV() end,
- [0xB9] = function() self:LDA(self:AbsoluteY()) end,
- [0xBA] = function() self:TSX() end,
- [0xBC] = function() self:LDY(self:AbsoluteX()) end,
- [0xBD] = function() self:LDA(self:AbsoluteX()) end,
- [0xBE] = function() self:LDX(self:AbsoluteY()) end,
- [0xC0] = function() self:CPY(self:Immediate()) end,
- [0xC1] = function() self:CMP(self:IndirectX()) end,
- [0xC4] = function() self:CPY(self:ZeroPage()) end,
- [0xC5] = function() self:CMP(self:ZeroPage()) end,
- [0xC6] = function() self:DEC(self:ZeroPage()) end,
- [0xC8] = function() self:INY() end,
- [0xC9] = function() self:CMP(self:Immediate()) end,
- [0xCA] = function() self:DEX() end,
- [0xCC] = function() self:CPY(self:Absolute()) end,
- [0xCD] = function() self:CMP(self:Absolute()) end,
- [0xCE] = function() self:DEC(self:Absolute()) end,
- [0xD0] = function() self:BNE() end,
- [0xD1] = function() self:CMP(self:IndirectY()) end,
- [0xD5] = function() self:CMP(self:ZeroPageX()) end,
- [0xD6] = function() self:DEC(self:ZeroPageX()) end,
- [0xD8] = function() self:CLD() end,
- [0xD9] = function() self:CMP(self:AbsoluteY()) end,
- [0xDD] = function() self:CMP(self:AbsoluteX()) end,
- [0xDE] = function() self:DEC(self:AbsoluteX()) end,
- [0xE0] = function() self:CPX(self:Immediate()) end,
- [0xE1] = function() self:SBC(self:IndirectX()) end,
- [0xE4] = function() self:CPX(self:ZeroPage()) end,
- [0xE5] = function() self:SBC(self:ZeroPage()) end,
- [0xE6] = function() self:INC(self:ZeroPage()) end,
- [0xE8] = function() self:INX() end,
- [0xE9] = function() self:SBC(self:Immediate()) end,
- [0xEA] = function() self:NOP() end,
- [0xEC] = function() self:CPX(self:Absolute()) end,
- [0xED] = function() self:SBC(self:Absolute()) end,
- [0xEE] = function() self:INC(self:Absolute()) end,
- [0xF0] = function() self:BEQ() end,
- [0xF1] = function() self:SBC(self:IndirectY()) end,
- [0xF5] = function() self:SBC(self:ZeroPageX()) end,
- [0xF6] = function() self:INC(self:ZeroPageX()) end,
- [0xF8] = function() self:SED() end,
- [0xF9] = function() self:SBC(self:AbsoluteY()) end,
- [0xFD] = function() self:SBC(self:AbsoluteX()) end,
- [0xFE] = function() self:INC(self:AbsoluteX()) end,
- }
- local instruction = instructions[opcode]
- if instruction then
- instruction()
- else
- print("Unknown opcode: " .. string.format("%02X", opcode))
- end
- end
- -- Implement all CPU instructions here
- function CPU:ADC(value)
- local result = self._a + value + self._p_c
- self._p_c = result > 0xFF and 1 or 0
- self._p_v = ((self._a ^ result) & (value ^ result) & 0x80) ~= 0 and 1 or 0
- self._a = band(result, 0xFF)
- self:setZN(self._a)
- end
- function CPU:AND(value)
- self._a = band(self._a, value)
- self:setZN(self._a)
- end
- function CPU:ASL(addr)
- local value = self:read(addr)
- self._p_c = band(value, 0x80) ~= 0 and 1 or 0
- value = band(lshift(value, 1), 0xFF)
- self:write(addr, value)
- self:setZN(value)
- end
- function CPU:BCC()
- self:branch(self._p_c == 0)
- end
- function CPU:BCS()
- self:branch(self._p_c == 1)
- end
- function CPU:BEQ()
- self:branch(self._p_nz == 0)
- end
- function CPU:BIT(value)
- self._p_v = band(value, 0x40) ~= 0 and 1 or 0
- self._p_nz = bor(band(value, 0x80), band(self._a, value) == 0 and 0x02 or 0)
- end
- function CPU:BMI()
- self:branch(band(self._p_nz, 0x80) ~= 0)
- end
- function CPU:BNE()
- self:branch(self._p_nz ~= 0)
- end
- function CPU:BPL()
- self:branch(band(self._p_nz, 0x80) == 0)
- end
- function CPU:BRK()
- self:push16(self._pc + 1)
- self:push8(bor(self:flags_pack(), 0x10))
- self._p_i = 1
- self._pc = self:peek16(CPU.IRQ_VECTOR)
- end
- function CPU:BVC()
- self:branch(self._p_v == 0)
- end
- function CPU:BVS()
- self:branch(self._p_v == 1)
- end
- function CPU:CLC()
- self._p_c = 0
- end
- function CPU:CLD()
- self._p_d = 0
- end
- function CPU:CLI()
- self._p_i = 0
- end
- function CPU:CLV()
- self._p_v = 0
- end
- function CPU:CMP(value)
- local result = self._a - value
- self._p_c = result >= 0 and 1 or 0
- self:setZN(band(result, 0xFF))
- end
- function CPU:CPX(value)
- local result = self._x - value
- self._p_c = result >= 0 and 1 or 0
- self:setZN(band(result, 0xFF))
- end
- function CPU:CPY(value)
- local result = self._y - value
- self._p_c = result >= 0 and 1 or 0
- self:setZN(band(result, 0xFF))
- end
- function CPU:DEC(addr)
- local value = band(self:read(addr) - 1, 0xFF)
- self:write(addr, value)
- self:setZN(value)
- end
- function CPU:DEX()
- self._x = band(self._x - 1, 0xFF)
- self:setZN(self._x)
- end
- function CPU:DEY()
- self._y = band(self._y - 1, 0xFF)
- self:setZN(self._y)
- end
- function CPU:EOR(value)
- self._a = bxor(self._a, value)
- self:setZN(self._a)
- end
- function CPU:INC(addr)
- local value = band(self:read(addr) + 1, 0xFF)
- self:write(addr, value)
- self:setZN(value)
- end
- function CPU:INX()
- self._x = band(self._x + 1, 0xFF)
- self:setZN(self._x)
- end
- function CPU:INY()
- self._y = band(self._y + 1, 0xFF)
- self:setZN(self._y)
- end
- function CPU:JMP_ABS()
- self._pc = self:peek16(self._pc)
- end
- function CPU:JMP_IND()
- local addr = self:peek16(self._pc)
- if band(addr, 0xFF) == 0xFF then
- self._pc = bor(self:read(addr), lshift(self:read(band(addr, 0xFF00)), 8))
- else
- self._pc = self:peek16(addr)
- end
- end
- function CPU:JSR()
- self:push16(self._pc + 1)
- self._pc = self:peek16(self._pc)
- end
- function CPU:LDA(value)
- self._a = value
- self:setZN(self._a)
- end
- function CPU:LDX(value)
- self._x = value
- self:setZN(self._x)
- end
- function CPU:LDY(value)
- self._y = value
- self:setZN(self._y)
- end
- function CPU:LSR(addr)
- local value = self:read(addr)
- self._p_c = band(value, 0x01)
- value = rshift(value, 1)
- self:write(addr, value)
- self:setZN(value)
- end
- function CPU:NOP()
- -- Do nothing
- end
- function CPU:ORA(value)
- self._a = bor(self._a, value)
- self:setZN(self._a)
- end
- function CPU:PHA()
- self:push8(self._a)
- end
- function CPU:PHP()
- self:push8(bor(self:flags_pack(), 0x10))
- end
- function CPU:PLA()
- self._a = self:pull8()
- self:setZN(self._a)
- end
- function CPU:PLP()
- self:flags_unpack(band(self:pull8(), 0xEF))
- end
- function CPU:ROL(addr)
- local value = self:read(addr)
- local newCarry = band(value, 0x80) ~= 0 and 1 or 0
- value = bor(lshift(value, 1), self._p_c)
- self._p_c = newCarry
- self:write(addr, band(value, 0xFF))
- self:setZN(band(value, 0xFF))
- end
- function CPU:ROR(addr)
- local value = self:read(addr)
- local newCarry = band(value, 0x01)
- value = bor(rshift(value, 1), lshift(self._p_c, 7))
- self._p_c = newCarry
- self:write(addr, value)
- self:setZN(value)
- end
- function CPU:RTI()
- self:flags_unpack(band(self:pull8(), 0xEF))
- self._pc = self:pull16()
- end
- function CPU:RTS()
- self._pc = band(self:pull16() + 1, 0xFFFF)
- end
- function CPU:SBC(value)
- local result = self._a - value - (1 - self._p_c)
- self._p_c = result >= 0 and 1 or 0
- self._p_v = ((self._a ^ result) & (self._a ^ value) & 0x80) ~= 0 and 1 or 0
- self._a = band(result, 0xFF)
- self:setZN(self._a)
- end
- function CPU:SEC()
- self._p_c = 1
- end
- function CPU:SED()
- self._p_d = 1
- end
- function CPU:SEI()
- self._p_i = 1
- end
- function CPU:STA(addr)
- self:write(addr, self._a)
- end
- function CPU:STX(addr)
- self:write(addr, self._x)
- end
- function CPU:STY(addr)
- self:write(addr, self._y)
- end
- function CPU:TAX()
- self._x = self._a
- self:setZN(self._x)
- end
- function CPU:TAY()
- self._y = self._a
- self:setZN(self._y)
- end
- function CPU:TSX()
- self._x = self._sp
- self:setZN(self._x)
- end
- function CPU:TXA()
- self._a = self._x
- self:setZN(self._a)
- end
- function CPU:TXS()
- self._sp = self._x
- end
- function CPU:TYA()
- self._a = self._y
- self:setZN(self._a)
- end
- -- PPU Emulation
- local PPU = {}
- PPU.__index = PPU
- function PPU:new(nes)
- local ppu = setmetatable({}, PPU)
- ppu:initialize(nes)
- return ppu
- end
- function PPU:initialize(nes)
- self.nes = nes
- self.vram = {}
- for i = 0, 0x3FFF do
- self.vram[i] = 0
- end
- self.oam = {}
- for i = 0, 255 do
- self.oam[i] = 0
- end
- self.palette = {}
- for i = 0, 31 do
- self.palette[i] = 0
- end
- self.scanline = 0
- self.cycle = 0
- self.frame = 0
- self.v = 0 -- Current VRAM address (15 bits)
- self.t = 0 -- Temporary VRAM address (15 bits)
- self.x = 0 -- Fine X scroll (3 bits)
- self.w = 0 -- First or second write toggle (1 bit)
- self.f = 0 -- Even/odd frame flag (1 bit)
- self.register = 0
- self.nmi_output = false
- self.nmi_occurred = false
- self.sprite_zero_hit = false
- self.sprite_overflow = false
- end
- function PPU:read(address)
- address = address % 0x4000
- if address < 0x2000 then
- return self.vram[address]
- elseif address < 0x3F00 then
- -- Handle mirroring
- return self.vram[address % 0x1000]
- else
- -- Palette RAM
- return self.palette[address % 32]
- end
- end
- function PPU:write(address, value)
- address = address % 0x4000
- if address < 0x2000 then
- self.vram[address] = value
- elseif address < 0x3F00 then
- -- Handle mirroring
- self.vram[address % 0x1000] = value
- else
- -- Palette RAM
- self.palette[address % 32] = value
- end
- end
- function PPU:step()
- self.cycle = self.cycle + 1
- if self.cycle > 340 then
- self.cycle = 0
- self.scanline = self.scanline + 1
- if self.scanline > 261 then
- self.scanline = 0
- self.frame = self.frame + 1
- self.f = band(self.f + 1, 1)
- end
- end
- if self.scanline < 240 and self.cycle < 256 then
- self:renderPixel()
- elseif self.scanline == 241 and self.cycle == 1 then
- self.nmi_occurred = true
- if self.nmi_output then
- self.nes.cpu:do_nmi(self.nes.cpu.clk)
- end
- end
- end
- function PPU:renderPixel()
- local x = self.cycle - 1
- local y = self.scanline
- -- Simplified background rendering
- local tileX = band(rshift(x, 3), 31)
- local tileY = band(rshift(y, 3), 29)
- local nameTableAddr = 0x2000 | (band(self.v, 0x0C00))
- local attributeTableAddr = 0x23C0 | (band(self.v, 0x0C00))
- local tileIndex = self:read(nameTableAddr + tileY * 32 + tileX)
- local tileAddr = 0x1000 * band(rshift(self.register, 4), 1) + tileIndex * 16 + band(y, 7)
- local lowTileByte = self:read(tileAddr)
- local highTileByte = self:read(tileAddr + 8)
- local colorBit0 = band(rshift(lowTileByte, 7 - band(x, 7)), 1)
- local colorBit1 = band(rshift(highTileByte, 7 - band(x, 7)), 1)
- local colorNum = bor(lshift(colorBit1, 1), colorBit0)
- -- Simplified palette handling
- local paletteIndex = band(self:read(0x3F00 + colorNum), 63)
- self:setPixel(x, y, paletteIndex)
- end
- function PPU:setPixel(x, y, paletteIndex)
- -- Implement this based on your rendering method
- -- For example, if using OpenComputers' GPU:
- local color = paletteIndex * 4 -- Simple color mapping
- self.nes.gpu.setBackground(color)
- self.nes.gpu.set(x + 1, y + 1, " ")
- end
- -- NES Emulator
- local NES = {}
- NES.__index = NES
- function NES:new()
- local nes = setmetatable({}, NES)
- nes.cpu = CPU:new()
- nes.ppu = PPU:new(nes)
- nes.gpu = component.gpu
- nes.mapper = nil
- nes.frameBuffer = {}
- nes.controller1 = {up = false, down = false, left = false, right = false, a = false, b = false, start = false, select = false}
- return nes
- end
- function NES:loadROM(filename)
- if not filesystem.exists(filename) then
- error("ROM file not found: " .. filename)
- end
- local file = filesystem.open(filename, "rb")
- if not file then
- error("Could not open ROM file: " .. filename)
- end
- -- Read NES header
- local header = file:read(16)
- if not header or #header < 16 or header:sub(1, 4) ~= "NES\x1A" then
- file:close()
- error("Invalid NES ROM file")
- end
- local prgRomSize = string.byte(header, 5) * 16384
- local chrRomSize = string.byte(header, 6) * 8192
- local flags6 = string.byte(header, 7)
- local flags7 = string.byte(header, 8)
- -- Determine mapper number
- local mapper = bor(band(flags7, 0xF0), rshift(band(flags6, 0xF0), 4))
- -- Load PRG ROM
- local prgRom = file:read(prgRomSize)
- -- Load CHR ROM
- local chrRom = file:read(chrRomSize)
- file:close()
- print("Loaded NES ROM. Mapper: " .. mapper .. ", PRG ROM: " .. prgRomSize .. " bytes, CHR ROM: " .. chrRomSize .. " bytes")
- -- Set up mapper
- self:setupMapper(mapper, prgRom, chrRom, prgRomSize, chrRomSize)
- -- Reset CPU and PPU
- self.cpu:reset()
- self.ppu:reset()
- end
- function NES:setupMapper(mapperNumber, prgRom, chrRom, prgRomSize, chrRomSize)
- local mappers = {
- [0] = function() return NROM:new(prgRom, chrRom, prgRomSize, chrRomSize) end,
- [1] = function() return MMC1:new(prgRom, chrRom, prgRomSize, chrRomSize) end,
- [2] = function() return UxROM:new(prgRom, chrRom, prgRomSize, chrRomSize) end,
- [3] = function() return CNROM:new(prgRom, chrRom, prgRomSize, chrRomSize) end,
- [4] = function() return MMC3:new(prgRom, chrRom, prgRomSize, chrRomSize) end,
- }
- local mapperConstructor = mappers[mapperNumber]
- if mapperConstructor then
- self.mapper = mapperConstructor()
- else
- error("Unsupported mapper: " .. mapperNumber)
- end
- -- Set up CPU and PPU memory mappings
- self.cpu:setMapper(self.mapper)
- self.ppu:setMapper(self.mapper)
- end
- function NES:run()
- local running = true
- local frameTime = 1 / 60 -- 60 FPS
- local lastTime = computer.uptime()
- while running do
- local currentTime = computer.uptime()
- local deltaTime = currentTime - lastTime
- if deltaTime >= frameTime then
- lastTime = currentTime
- -- Run one frame of emulation
- for i = 1, 29780 do -- Approximate CPU cycles per frame
- self.cpu:step()
- self.ppu:step()
- self.ppu:step()
- self.ppu:step()
- end
- -- Render frame
- self:renderFrame()
- -- Handle input
- running = self:handleInput()
- end
- end
- end
- function NES:renderFrame()
- -- Clear frame buffer
- self.frameBuffer = {}
- -- Render frame to buffer
- for y = 0, 239 do
- self.frameBuffer[y] = {}
- for x = 0, 255 do
- self.frameBuffer[y][x] = self.ppu:getPixel(x, y)
- end
- end
- -- Render frame buffer to GPU
- for y = 0, 239 do
- local row = self.frameBuffer[y]
- local gpuRow = {}
- for x = 0, 255 do
- gpuRow[x + 1] = string.char(row[x])
- end
- self.gpu.set(1, y + 1, table.concat(gpuRow))
- end
- end
- function NES:handleInput()
- -- Handle input using OpenComputers event API
- while true do
- local _, _, code = event.pull(0, "key_down")
- if code == keyboard.keys.q then
- return false -- Quit emulator
- elseif code == keyboard.keys.up then
- self.controller1.up = true
- elseif code == keyboard.keys.down then
- self.controller1.down = true
- elseif code == keyboard.keys.left then
- self.controller1.left = true
- elseif code == keyboard.keys.right then
- self.controller1.right = true
- elseif code == keyboard.keys.z then
- self.controller1.a = true
- elseif code == keyboard.keys.x then
- self.controller1.b = true
- elseif code == keyboard.keys.leftCtrl then
- self.controller1.start = true
- elseif code == keyboard.keys.leftAlt then
- self.controller1.select = true
- end
- local _, _, code = event.pull(0, "key_up")
- if code == keyboard.keys.up then
- self.controller1.up = false
- elseif code == keyboard.keys.down then
- self.controller1.down = false
- elseif code == keyboard.keys.left then
- self.controller1.left = false
- elseif code == keyboard.keys.right then
- self.controller1.right = false
- elseif code == keyboard.keys.z then
- self.controller1.a = false
- elseif code == keyboard.keys.x then
- self.controller1.b = false
- elseif code == keyboard.keys.leftCtrl then
- self.controller1.start = false
- elseif code == keyboard.keys.leftAlt then
- self.controller1.select = false
- end
- end
- return true
- end
- -- Mapper base class
- local Mapper = {}
- Mapper.__index = Mapper
- function Mapper:new(prgRom, chrRom, prgRomSize, chrRomSize)
- local mapper = setmetatable({}, Mapper)
- mapper.prgRom = prgRom
- mapper.chrRom = chrRom
- mapper.prgRomSize = prgRomSize
- mapper.chrRomSize = chrRomSize
- return mapper
- end
- -- NROM (Mapper 0)
- local NROM = setmetatable({}, {__index = Mapper})
- NROM.__index = NROM
- function NROM:new(prgRom, chrRom, prgRomSize, chrRomSize)
- local nrom = setmetatable(Mapper:new(prgRom, chrRom, prgRomSize, chrRomSize), NROM)
- nrom.prgBankCount = prgRomSize / 16384
- return nrom
- end
- function NROM:cpuRead(address)
- if address >= 0x8000 then
- local index = (address - 0x8000) % self.prgRomSize
- return string.byte(self.prgRom, index + 1)
- end
- return 0
- end
- function NROM:cpuWrite(address, value)
- -- NROM doesn't support writes to ROM
- end
- function NROM:ppuRead(address)
- if address < 0x2000 then
- return string.byte(self.chrRom, address + 1)
- end
- return 0
- end
- function NROM:ppuWrite(address, value)
- -- NROM doesn't support writes to CHR ROM
- end
- -- Main
- local nes = NES:new()
- local romFile = "/home/super_mario_bros.nes"
- -- Load and run the ROM
- nes:loadROM(romFile)
- nes:run()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement