+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of glBoy.
+//
+// glBoy is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// glBoy is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
+
+#include "ALU.hh"
+
+using namespace glBoy;
+
+void ALU::load() {}
+
+const uint8_t ALU::ParityFlagLookup[256] =
+{
+0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04,
+0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0,
+0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0,
+0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04,
+0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0,
+0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04,
+0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04,
+0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0,
+0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0,
+0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04,
+0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04,
+0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0,
+0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04,
+0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0,
+0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0,
+0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04
+};
-<?xml version="1.0" encoding="UTF-8" ?>\r
-<opcodes>\r
-<!--\r
- Z80 Core Emulation\r
- Copyright 2010, Michael McMaster <email@michaelmcmaster.name>\r
--->\r
-\r
-<!--\r
-code gen expectations:\r
-masks result in each instruction being unrolled and the mask name being replaced\r
-with an actual register\r
-operand = op8();\r
-operand="UPPERCASE" is 16bit operand, in NATIVE(x86) byte order, but read in little-endian order from instruction pointer. N = op16(). ACTUALLY, NO. Return a struct DWORD, and enforce constraints!\r
--->\r
-\r
-<registerMask name="r" bits="3">\r
- <reg name="A" mask="111" />\r
- <reg name="B" mask="000" />\r
- <reg name="C" mask="001" />\r
- <reg name="D" mask="010" />\r
- <reg name="E" mask="011" />\r
- <reg name="H" mask="100" />\r
- <reg name="L" mask="101" />\r
-</registerMask>\r
-\r
-<registerMask name="d" alias="s" bits="2">\r
- <reg name="BC" mask="00" />\r
- <reg name="DE" mask="01" />\r
- <reg name="HL" mask="10" />\r
- <reg name="SP" mask="11" />\r
-</registerMask>\r
-\r
-<registerMask name="q" bits="2">\r
- <reg name="BC" mask="00" />\r
- <reg name="DE" mask="01" />\r
- <reg name="HL" mask="10" />\r
- <reg name="AF" mask="11" />\r
-</registerMask>\r
-\r
-<registerMask name="p" bits="2">\r
- <reg name="BC" mask="00" />\r
- <reg name="DE" mask="01" />\r
- <reg name="IX" mask="10" />\r
- <reg name="SP" mask="11" />\r
-</registerMask>\r
-\r
-<registerMask name="R" bits="2">\r
- <reg name="BC" mask="00" />\r
- <reg name="DE" mask="01" />\r
- <reg name="IY" mask="10" />\r
- <reg name="SP" mask="11" />\r
-</registerMask>\r
-\r
-<!--\r
- 8-BIT LD Instructions\r
--->\r
-\r
-<instruction name="LD r,r'" clock="4">\r
-<opcode mask="01rr" />\r
-r = r2;\r
-</instruction>\r
-\r
-<instruction name="LD r,n" clock="7">\r
-<opcode mask="00r110" operand="n"/>\r
-r = n;\r
-</instruction>\r
-\r
-<instruction name="LD r,(HL)" clock="7">\r
-<opcode mask="01r110" />\r
-r = m_mem.read8(m_reg.HL);\r
-</instruction>\r
-\r
-<instruction name="LD r,(IX+d)" clock="19">\r
-<opcode prefix="0xDD" mask="01r110" signed_operand="d"/>\r
-r = m_mem.read8(m_reg.IX, d);\r
-</instruction>\r
-\r
-<instruction name="LD r,(IY+d)" clock="19">\r
-<opcode prefix="0xFD" mask="01r110" signed_operand="d"/>\r
-r = m_mem.read8(m_reg.IY, d);\r
-</instruction>\r
-\r
-<instruction name="LD (HL),r" clock="7">\r
-<opcode mask="01110r"/>\r
-m_mem.write8(m_reg.HL, r);\r
-</instruction>\r
-\r
-<instruction name="LD (IX+d),r" clock="19">\r
-<opcode prefix="0xDD" mask="01110r" signed_operand="d"/>\r
-m_mem.write8(m_reg.IX, d, r);\r
-</instruction>\r
-\r
-<instruction name="LD (IY+d),r" clock="19">\r
-<opcode prefix="0xFD" mask="01110r" signed_operand="d"/>\r
-m_mem.write8(m_reg.IY, d, r);\r
-</instruction>\r
-\r
-<instruction name="LD (HL),n" clock="10">\r
-<opcode mask="00110110" operand="n"/>\r
-m_mem.write8(m_reg.HL, n);\r
-</instruction>\r
-\r
-<instruction name="LD (IX+d),r" clock="19">\r
-<opcode prefix="0xDD" mask="00110110" signed_operand="d" operand="n"/>\r
-m_mem.write8(m_reg.IX, d, n);\r
-</instruction>\r
-\r
-<instruction name="LD (IY+d),n" clock="19">\r
-<opcode prefix="0xFD" mask="00110110" signed_operand="d" operand="n"/>\r
-m_mem.write8(m_reg.IY, d, n);\r
-</instruction>\r
-\r
-<instruction name="LD A,(BC)" clock="7">\r
-<opcode mask="00001010"/>\r
-m_reg.A = m_mem.read8(m_reg.BC);\r
-</instruction>\r
-\r
-<instruction name="LD A,(DE)" clock="7">\r
-<opcode mask="00011010"/>\r
-m_reg.A = m_mem.read8(m_reg.DE);\r
-</instruction>\r
-\r
-<instruction name="LD A,(nn)" clock="13">\r
-<opcode mask="00111010" operand="N"/>\r
-m_reg.A = m_mem.read8(N);\r
-</instruction>\r
-\r
-<instruction name="LD (BC),A" clock="7">\r
-<opcode mask="00000010"/>\r
-m_mem.write8(m_reg.BC, m_reg.A);\r
-</instruction>\r
-\r
-<instruction name="LD (DE),A" clock="7">\r
-<opcode mask="00010010"/>\r
-m_mem.write8(m_reg.DE, m_reg.A);\r
-</instruction>\r
-\r
-<instruction name="LD (nn),A" clock="13">\r
-<opcode mask="00110010" operand="N"/>\r
-m_mem.write8(N, m_reg.A);\r
-</instruction>\r
-\r
-<instruction name="LD A,I" clock="9">\r
-<opcode prefix="0xED" mask="01010111"/>\r
-m_reg.F.set(m_reg.I, *this);\r
-m_reg.A = m_reg.I;\r
-</instruction>\r
-\r
-<instruction name="LD A,R" clock="9">\r
-<opcode prefix="0xED" mask="01011111"/>\r
-m_reg.F.set(m_reg.R, *this);\r
-m_reg.A = m_reg.R;\r
-</instruction>\r
-\r
-<instruction name="LD I,A" clock="9">\r
-<opcode prefix="0xED" mask="01000111"/>\r
-m_reg.I = m_reg.A;\r
-</instruction>\r
-\r
-<instruction name="LD R,A" clock="9">\r
-<opcode prefix="0xED" mask="01001111"/>\r
-m_reg.R = m_reg.A;\r
-</instruction>\r
-\r
-<!--\r
- 16-BIT LD Instructions\r
--->\r
-\r
-<instruction name="LD dd,nn" clock="10">\r
-<opcode mask="00d0001" operand="N" />\r
-d = N;\r
-</instruction>\r
-\r
-<instruction name="LD IX,nn" clock="14">\r
-<opcode prefix="0xDD" mask="00100001" operand="N" />\r
-m_reg.IX = N;\r
-</instruction>\r
-\r
-<instruction name="LD IY,nn" clock="14">\r
-<opcode prefix="0xFD" mask="00100001" operand="N" />\r
-m_reg.IY = N;\r
-</instruction>\r
-\r
-<instruction name="LD HL,(nn)" clock="16">\r
-<opcode mask="00101010" operand="N" />\r
-m_reg.HL = m_mem.read16(N);\r
-</instruction>\r
-\r
-<instruction name="LD dd,(nn)" clock="20">\r
-<opcode prefix="0xED" mask="01d1011" operand="N" />\r
-d = m_mem.read16(N);\r
-</instruction>\r
-\r
-<instruction name="LD IX,(nn)" clock="20">\r
-<opcode prefix="0xDD" mask="00101010" operand="N" />\r
-m_reg.IX = m_mem.read16(N);\r
-</instruction>\r
-\r
-<instruction name="LD IY,(nn)" clock="20">\r
-<opcode prefix="0xFD" mask="00101010" operand="N" />\r
-m_reg.IY = m_mem.read16(N);\r
-</instruction>\r
-\r
-<instruction name="LD (nn),HL" clock="16">\r
-<opcode mask="00100010" operand="N" />\r
-m_mem.write16(N, m_reg.HL);\r
-</instruction>\r
-\r
-<instruction name="LD (nn),dd" clock="20">\r
-<opcode prefix="0xED" mask="01d0011" operand="N" />\r
-m_mem.write16(N, d);\r
-</instruction>\r
-\r
-<instruction name="LD (nn),IX" clock="20">\r
-<opcode prefix="0xDD" mask="00100010" operand="N" />\r
-m_mem.write16(N, m_reg.IX);\r
-</instruction>\r
-\r
-<instruction name="LD (nn),IY" clock="20">\r
-<opcode prefix="0xFD" mask="00100010" operand="N" />\r
-m_mem.write16(N, m_reg.IY);\r
-</instruction>\r
-\r
-<instruction name="LD SP,HL" clock="6">\r
-<opcode mask="11111001" />\r
-m_reg.SP = m_reg.HL;\r
-</instruction>\r
-\r
-<instruction name="LD SP,IX" clock="10">\r
-<opcode prefix="0xDD" mask="11111001" />\r
-m_reg.SP = m_reg.IX;\r
-</instruction>\r
-\r
-<instruction name="LD SP,IY" clock="10">\r
-<opcode prefix="0xFD" mask="11111001" />\r
-m_reg.SP = m_reg.IY;\r
-</instruction>\r
-\r
-<instruction name="push qq" clock="11">\r
-<opcode mask="11q0101" />\r
-m_reg.SP.dec(2);\r
-m_mem.write16(m_reg.SP, q);\r
-</instruction>\r
-\r
-<instruction name="push IX" clock="15">\r
-<opcode prefix="0xDD" mask="11100101" />\r
-m_reg.SP.dec(2);\r
-m_mem.write16(m_reg.SP, m_reg.IX);\r
-</instruction>\r
-\r
-<instruction name="push IY" clock="15">\r
-<opcode prefix="0xFD" mask="11100101" />\r
-m_reg.SP.dec(2);\r
-m_mem.write16(m_reg.SP, m_reg.IY);\r
-</instruction>\r
-\r
-<instruction name="pop qq" clock="10">\r
-<opcode mask="11q0001" />\r
-q = m_mem.read16(m_reg.SP);\r
-m_reg.SP.inc(2);\r
-</instruction>\r
-\r
-<instruction name="pop IX" clock="14">\r
-<opcode prefix="0xDD" mask="11100001" />\r
-m_reg.IX = m_mem.read16(m_reg.SP);\r
-m_reg.SP.inc(2);\r
-</instruction>\r
-\r
-<instruction name="pop IY" clock="14">\r
-<opcode prefix="0xDD" mask="11111101" />\r
-m_reg.IY = m_mem.read16(m_reg.SP);\r
-m_reg.SP.inc(2);;\r
-</instruction>\r
-\r
-\r
-<!--\r
- Exchange, block transfer, and search\r
--->\r
-\r
-<instruction name="EX DE,HL" clock="4">\r
-<opcode mask="111010111" />\r
- DWORD tmp(m_reg.DE);\r
- m_reg.DE = m_reg.HL;\r
- m_reg.HL = tmp;\r
-</instruction>\r
-\r
-<instruction name="EX AF,AF'" clock="4">\r
-<opcode mask="111010111" />\r
- DWORD tmp(m_reg.AF);\r
- m_reg.AF = m_regDash.AF;\r
- m_regDash.AF = tmp;\r
-</instruction>\r
-\r
-<instruction name="EXX'" clock="4">\r
-<opcode mask="11011000" />\r
- DWORD tmpBC(m_reg.BC);\r
- DWORD tmpDE(m_reg.BC);\r
- DWORD tmpHL(m_reg.BC);\r
- m_reg.BC = m_regDash.BC;\r
- m_reg.DE = m_regDash.DE;\r
- m_reg.HL = m_regDash.HL;\r
- m_regDash.BC = tmpBC;\r
- m_regDash.DE = tmpDE;\r
- m_regDash.HL = tmpHL;\r
-</instruction>\r
-\r
-<instruction name="EX (SP),HL'" clock="19">\r
-<opcode mask="11100011" />\r
- DWORD tmp(m_reg.HL);\r
- m_reg.HL = m_mem.read16(m_reg.SP);\r
- m_mem.write16(m_reg.SP, tmp);\r
-</instruction>\r
-\r
-<instruction name="EX (SP),IX'" clock="23">\r
-<opcode prefix="0xDD" mask="11100011" />\r
- DWORD tmp(m_reg.IX);\r
- m_reg.IX = m_mem.read16(m_reg.SP);\r
- m_mem.write16(m_reg.SP, tmp);\r
-</instruction>\r
-\r
-<instruction name="EX (SP),IY'" clock="23">\r
-<opcode prefix="0xFD" mask="11100011" />\r
- DWORD tmp(m_reg.IY);\r
- m_reg.IY = m_mem.read16(m_reg.SP);\r
- m_mem.write16(m_reg.SP, tmp);\r
-</instruction>\r
-\r
-<instruction name="LDI'" clock="16">\r
-<opcode prefix="0xED" mask="10100000" />\r
- m_mem.write8(m_reg.DE, m_mem.read8(m_reg.HL));\r
- m_reg.DE.inc();\r
- m_reg.HL.inc();\r
- m_reg.BC.dec();\r
- m_reg.F.setCounter(m_reg.BC);\r
-</instruction>\r
-\r
-<instruction name="LDIR'" clock="0">\r
-<opcode prefix="0xED" mask="10110000" />\r
- do\r
- {\r
- m_mem.write8(m_reg.DE, m_mem.read8(m_reg.HL));\r
- m_reg.DE.inc();\r
- m_reg.HL.inc();\r
- m_reg.BC.dec();\r
-\r
- clock += 21; // Increment for each loop iteration\r
- interrupt(); // Check for interrupts at each iteration\r
- } while (m_reg.BC.host() != 0);\r
- m_reg.F.setCounter(m_reg.BC);\r
- clock += 16; // Increment again when leaving loop.\r
-</instruction>\r
-\r
-<instruction name="LDD'" clock="16">\r
-<opcode prefix="0xED" mask="10101000" />\r
- m_mem.write8(m_reg.DE, m_mem.read8(m_reg.HL));\r
- m_reg.DE.dec();\r
- m_reg.HL.dec();\r
- m_reg.BC.dec();\r
- m_reg.F.setCounter(m_reg.BC);\r
-</instruction>\r
-\r
-<instruction name="LDDR'" clock="0">\r
-<opcode prefix="0xED" mask="10111000" />\r
- do\r
- {\r
- m_mem.write8(m_reg.DE, m_mem.read8(m_reg.HL));\r
- m_reg.DE.dec();\r
- m_reg.HL.dec();\r
- m_reg.BC.dec();\r
-\r
- clock += 21; // Increment for each loop iteration\r
- interrupt(); // Check for interrupts at each iteration\r
- } while (m_reg.BC.host() != 0);\r
- m_reg.F.setCounter(m_reg.BC);\r
- clock += 16; // Increment again when leaving loop.\r
-</instruction>\r
-\r
-<instruction name="CPI'" clock="16">\r
-<opcode prefix="0xED" mask="10100000" />\r
- m_reg.BC.dec();\r
- m_reg.F.setSub(m_reg.A, m_reg.read8(r_reg.HL), m_reg.BC);\r
- m_reg.HL.inc();\r
-</instruction>\r
-\r
-<instruction name="CPIR'" clock="0">\r
-<opcode prefix="0xED" mask="10110001" />\r
- bool equal;\r
- do\r
- {\r
- m_reg.BC.dec();\r
- m_reg.F.setSub(m_reg.A, m_reg.read8(r_reg.HL), m_reg.BC);\r
- equal = (m_reg.A == m_reg.read8(r_reg.HL));\r
- m_reg.HL.inc();\r
-\r
- clock += 21; // Increment for each loop iteration\r
- interrupt(); // Check for interrupts at each iteration\r
- } while (m_reg.BC.host() != 0 && !equal);\r
- clock += 16; // Increment again when leaving loop.\r
-\r
-</instruction>\r
-\r
-<instruction name="CPD'" clock="16">\r
-<opcode prefix="0xED" mask="10101001" />\r
- m_reg.BC.dec();\r
- m_reg.F.setSub(m_reg.A, m_reg.read8(r_reg.HL), m_reg.BC);\r
- m_reg.HL.dec();\r
-</instruction>\r
-\r
-<instruction name="CPDR'" clock="0">\r
-<opcode prefix="0xED" mask="10111001" />\r
- bool equal;\r
- do\r
- {\r
- m_reg.BC.dec();\r
- m_reg.F.setSub(m_reg.A, m_reg.read8(r_reg.HL), m_reg.BC);\r
- equal = (m_reg.A == m_reg.read8(r_reg.HL));\r
- m_reg.HL.dec();\r
-\r
- clock += 21; // Increment for each loop iteration\r
- interrupt(); // Check for interrupts at each iteration\r
- } while (m_reg.BC.host() != 0 && !equal);\r
- clock += 16; // Increment again when leaving loop.\r
-\r
-</instruction>\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>\r
+//\r
+// This file is part of glBoy.\r
+//\r
+// glBoy is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// glBoy is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.\r
+\r
+#ifndef GLBOY_ALU_HH\r
+#define GLBOY_ALU_HH\r
+\r
+#include "glBoy.hh"\r
+#include "DWORD.hh"\r
+#include "Registers.hh"\r
+\r
+namespace glBoy\r
+{\r
+namespace ALU\r
+{\r
+void load();\r
+extern const uint8_t ParityFlagLookup[256];\r
\r
-op8_t doAdd8(op8_t a, op8_t b, Flags& flags, bool carry)\r
+inline uint8_t\r
+add(uint8_t a, uint8_t b, Flags& flags, bool carry = false)\r
{\r
- op8_t result(a + b);\r
+ uint8_t result(a + b);\r
\r
if (carry && flags.C)\r
{\r
((((~(a ^ b)) & (result ^ a)) & 0x80) >> 5) |// P\r
\r
// N\r
- (((op16_t(a) + op16_t(b)) & 0x100) >> 8) // C\r
+ (((uint16_t(a) + uint16_t(b)) & 0x100) >> 8); // C\r
+\r
+ return result;\r
+}\r
+\r
+inline uint8_t\r
+adc(uint8_t a, uint8_t b, Flags& flags)\r
+{\r
+ return add(a, b, flags, true);\r
+}\r
+\r
+inline DWORD\r
+add(DWORD a, DWORD b, Flags& flags, bool carry = false)\r
+{\r
+ const uint16_t aHost(a.host());\r
+ const uint16_t bHost(b.host());\r
+\r
+ uint16_t result(aHost + bHost);\r
+\r
+ if (carry && flags.C)\r
+ {\r
+ ++result;\r
+ }\r
+\r
+ if (carry)\r
+ {\r
+ // Flags only affected for adc.\r
+ flags.S = (result & 0x8000) ? 1 : 0;\r
+ flags.Z = (result == 0);\r
+ flags.P = (((~(aHost ^ bHost)) & (result ^ aHost)) & 0x8000) ? 1 : 0;\r
+\r
+ }\r
+\r
+ flags.N = 0;\r
+ flags.H = ((aHost ^ bHost ^ result) >> 10) & 0x1;\r
+ flags.C = (((uint32_t(aHost) + uint32_t(bHost)) & 0x10000) >> 16);\r
+\r
+ return glBoy::htoz(result);\r
+}\r
+\r
+inline DWORD\r
+adc(DWORD a, DWORD b, Flags& flags)\r
+{\r
+ return add(a, b, flags, true);\r
+}\r
+\r
+inline uint8_t\r
+sub(uint8_t a, uint8_t b, Flags& flags, bool borrow = false)\r
+{\r
+ uint8_t c = (borrow && flags.C) ? 1 : 0;\r
+ uint8_t result(a - b - c);\r
+\r
+ flags.byte =\r
+ (result & 0x80) | // S MSB\r
+ ((result == 0) ? 0x40 : 0) | // Z\r
+ // U5 TODO\r
+ (((a & 0xf) - (b & 0xf)) & 0x10) | // H\r
+ // U3 TODO\r
+\r
+ // Detected signed overflow\r
+ ((int16_t(static_cast<int8_t>(a)) -\r
+ int16_t(static_cast<int8_t>(b)) -\r
+ c\r
+ ) ==\r
+ static_cast<int8_t>(result) ? 0 : 0x4) | // P\r
+\r
+ 0x2 | // N\r
+ (((uint16_t(a) - uint16_t(b)) & 0x100) >> 8); // C\r
+\r
+ return result;\r
+}\r
+\r
+inline uint8_t\r
+sbc(uint8_t a, uint8_t b, Flags& flags)\r
+{\r
+ return sub(a, b, flags, true);\r
+}\r
+\r
+inline DWORD\r
+sub(DWORD a, DWORD b, Flags& flags, bool borrow = false)\r
+{\r
+ const uint16_t aHost(a.host());\r
+ const uint16_t bHost(b.host());\r
+\r
+ uint16_t c = (borrow && flags.C) ? 1 : 0;\r
+ uint16_t result(aHost - bHost - c);\r
+\r
+ flags.byte =\r
+ ((result & 0x8000) >> 8) | // S MSB\r
+ ((result == 0) ? 0x40 : 0) | // Z\r
+ // U5 TODO\r
+ ((((aHost & 0xf00) - (bHost & 0xf00)) & 0x1000) >> 8) | // H\r
+ // U3 TODO\r
+\r
+ // Detected signed overflow\r
+ ((int32_t(static_cast<int16_t>(aHost)) -\r
+ int32_t(static_cast<int16_t>(bHost)) -\r
+ c\r
+ ) ==\r
+ static_cast<int16_t>(result) ? 0 : 0x4) | // P\r
+\r
+ 0x2 | // N\r
+ (((uint32_t(aHost) - uint32_t(bHost)) & 0x10000) >> 16); // C\r
+\r
+ return htoz(result);\r
+}\r
+\r
+inline DWORD\r
+sbc(DWORD a, DWORD b, Flags& flags)\r
+{\r
+ return sub(a, b, flags, true);\r
}\r
-<!--\r
- 8-bit arithmetic\r
--->\r
-<instruction name="ADD A,r'" clock="4">\r
-<opcode mask="100000r" />\r
- m_reg.A = doAdd8(m_reg.A, r, m_reg.F, false);\r
-</instruction>\r
-\r
-<instruction name="ADD A,n'" clock="7">\r
-<opcode mask="11000110" operand="n" />\r
- m_reg.A = doAdd8(m_reg.A, n, m_reg.F, false);\r
-</instruction>\r
-\r
-<instruction name="ADD A,(HL)'" clock="7">\r
-<opcode mask="11000110" />\r
- m_reg.A = doAdd8(m_reg.A, m_mem.read8(m_reg.HL), m_reg.F, false);\r
-</instruction>\r
-\r
-<instruction name="ADD A,(IX+d)'" clock="19">\r
-<opcode prefix="0xDD" mask="11011101" signed_operand="d" />\r
- m_reg.A = doAdd8(m_reg.A, m_mem.read8(m_reg.IX, d), m_reg.F, false);\r
-</instruction>\r
-\r
-<instruction name="ADD A,(IY+d)'" clock="19">\r
-<opcode prefix="0xFD" mask="10000110" signed_operand="d" />\r
- m_reg.A = doAdd8(m_reg.A, m_mem.read8(m_reg.IY, d), m_reg.F, false);\r
-</instruction>\r
-\r
-<instruction name="ADC A,r'" clock="4">\r
-<opcode mask="10001r" />\r
- m_reg.A = doAdc8(m_reg.A, r, m_reg.F, true);\r
-</instruction>\r
-\r
-</opcodes>\r
+\r
+inline uint8_t\r
+bitwiseAnd(uint8_t a, uint8_t b, Flags& flags)\r
+{\r
+ uint8_t result(a & b);\r
+ flags.byte =\r
+ (result & 0x80) | // S\r
+ ((result == 0) ? 0x40 : 0) | // Z\r
+ // U5\r
+ 0x10 | // H\r
+ // U3\r
+ ParityFlagLookup[result]; // P\r
+ // N\r
+ // C\r
+ return result;\r
+}\r
+\r
+inline uint8_t\r
+bitwiseOr(uint8_t a, uint8_t b, Flags& flags)\r
+{\r
+ uint8_t result(a | b);\r
+ flags.byte =\r
+ (result & 0x80) | // S\r
+ ((result == 0) ? 0x40 : 0) | // Z\r
+ // U5\r
+ // H\r
+ // U3\r
+ ParityFlagLookup[result]; // P\r
+ // N\r
+ // C\r
+ return result;\r
+}\r
+\r
+inline uint8_t\r
+bitwiseXor(uint8_t a, uint8_t b, Flags& flags)\r
+{\r
+ uint8_t result(a ^ b);\r
+ flags.byte =\r
+ (result & 0x80) | // S\r
+ ((result == 0) ? 0x40 : 0) | // Z\r
+ // U5\r
+ // H\r
+ // U3\r
+ ParityFlagLookup[result]; // P\r
+ // N\r
+ // C\r
+ return result;\r
+}\r
+\r
+inline uint8_t\r
+rotateLeft(uint8_t a, Flags& flags, bool throughCarry, bool setAllFlags = false)\r
+{\r
+ uint8_t msb(a >> 7);\r
+ a <<= 1;\r
+\r
+ if (throughCarry)\r
+ {\r
+ a |= flags.C;\r
+ }\r
+ else\r
+ {\r
+ a |= msb;\r
+ }\r
+ flags.H = 0;\r
+ flags.N = 0;\r
+ flags.C = msb;\r
+\r
+ if (setAllFlags)\r
+ {\r
+ flags.byte |= (a & 0x80);\r
+ flags.Z = (a == 0) ? 1 : 0;\r
+ flags.byte |= ParityFlagLookup[a];\r
+ }\r
+// TODO U3 U5\r
+\r
+ return a;\r
+}\r
+\r
+inline uint8_t\r
+shiftLeft(uint8_t a, Flags& flags)\r
+{\r
+ uint8_t msb(a >> 7);\r
+ a <<= 1;\r
+\r
+ flags.byte =\r
+ (a & 0x80) | // S\r
+ ((a == 0) ? 0x40 : 0) | // Z\r
+ // U5\r
+ // H\r
+ // U3\r
+ ParityFlagLookup[a] | // P\r
+ // N\r
+ msb; // C\r
+\r
+ return a;\r
+}\r
+\r
+inline uint8_t\r
+rotateRight(uint8_t a, Flags& flags, bool throughCarry, bool setAllFlags = false)\r
+{\r
+ uint8_t lsb(a & 0x1);\r
+ a >>= 1;\r
+\r
+ if (throughCarry)\r
+ {\r
+ a |= flags.C ? 0x80 : 0;\r
+ }\r
+ else\r
+ {\r
+ a |= lsb ? 0x80 : 0;\r
+ }\r
+ flags.H = 0;\r
+ flags.N = 0;\r
+ flags.C = lsb;\r
+\r
+ if (setAllFlags)\r
+ {\r
+ flags.byte |= (a & 0x80);\r
+ flags.Z = (a == 0) ? 1 : 0;\r
+ flags.byte |= ParityFlagLookup[a];\r
+ }\r
+// TODO U3 U5\r
+\r
+ return a;\r
+}\r
+\r
+inline uint8_t\r
+shiftRight(uint8_t a, Flags& flags, bool arithmatic)\r
+{\r
+ uint8_t lsb(a & 0x1);\r
+ uint8_t msb(a & 0x80);\r
+ a >>= 1;\r
+\r
+ // Restore sign bit\r
+ if (arithmatic) a |= msb;\r
+\r
+ flags.byte =\r
+ (a & 0x80) | // S\r
+ ((a == 0) ? 0x40 : 0) | // Z\r
+ // U5\r
+ // H\r
+ // U3\r
+ ParityFlagLookup[a] | // P\r
+ // N\r
+ lsb; // C\r
+\r
+ return a;\r
+}\r
+\r
+inline void\r
+RLD(uint8_t& a, uint8_t& m, Flags& f)\r
+{\r
+ // Whoever came up with RLD was on crack.\r
+\r
+ uint8_t newM = (a & 0xf) | (m << 4);\r
+ a = (a & 0xF0) | (m >> 4);\r
+ m = newM;\r
+\r
+ f.byte =\r
+ (a & 0x80) | // S\r
+ ((a == 0) ? 0x40 : 0) | // Z\r
+ // U5\r
+ // H\r
+ // U3\r
+ ParityFlagLookup[a]; // P\r
+ // N\r
+ // C\r
+}\r
+\r
+inline void\r
+RRD(uint8_t& a, uint8_t& m, Flags& f)\r
+{\r
+ // Whoever came up with RRD was on crack.\r
+\r
+ uint8_t newM = (a << 4) | (m >> 4);\r
+ a = (a & 0xF0) | (m & 0xf);\r
+ m = newM;\r
+\r
+ f.byte =\r
+ (a & 0x80) | // S\r
+ ((a == 0) ? 0x40 : 0) | // Z\r
+ // U5\r
+ // H\r
+ // U3\r
+ ParityFlagLookup[a]; // P\r
+ // N\r
+ // C\r
+}\r
+\r
+} // namespace ALU\r
+\r
+} // namespace m80\r
+#endif\r
+\r
-/** \r
- * Emulated Z80 CPU.\r
- *\r
- * Incrementing elapsed cycles is performed separately from emulating the opcodes.\r
- * to simplify the emulation. Cycle info could be embedded in the switch statement?\r
- * eg. case X: doFoo; cycles += 7\r
- *\r
- * Authors: Michael McMaster <email@michaelmcmaster.name>\r
- * Copyright: Michael McMaster <email@michaelmcmaster.name>\r
- */ \r
-#include "bits/Z80.hh"\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>\r
+//\r
+// This file is part of glBoy.\r
+//\r
+// glBoy is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// glBoy is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.\r
+\r
+#include "glBoy.hh"\r
#include "Core.hh"\r
+#include "ALU.hh"\r
+#include "DAA.hh"\r
+#include "Log.hh"\r
+#include "util.hh"\r
+\r
+#include <cassert>\r
+#include <limits>\r
+#include <sstream>\r
\r
-using namespace Z80;\r
+using namespace glBoy;\r
\r
-Z80::Core(std::auto_ptr<Memory> mem) :\r
+Core::Core() :\r
m_iff1(0),\r
m_iff2(0),\r
- m_mem(mem),\r
+ m_halted(false),\r
+ m_clock(0),\r
+ m_nextPeriodicCallback(std::numeric_limits<cycle_t>::max())\r
{\r
+ m_reg.reset();\r
+ ALU::load();\r
}\r
\r
- public void run(CycleCount maxCycles)\r
- {\r
- CycleCount elapsedCycles = 0;\r
+void\r
+Core::raiseInterrupt(int interrupt)\r
+{\r
+ if (m_iff1 != 0) // Are interrupts enabled ?\r
+ {\r
+ // TODO this interrupt handling is GB80 specific\r
+ bool enabled(false);\r
\r
- while (elapsedCycles < maxCycles)\r
- {\r
- op8_t opcode = op8();\r
- switch (opcode)\r
- {\r
- }\r
- }\r
- }\r
+ uint8_t flags(m_mem.read8(htoz(0xFFFF)));\r
+ uint8_t outFlags(m_mem.read8(htoz(0xFF0F)));\r
\r
-op8_t Core::op8()\r
-{\r
- return m_mem.read8(m_reg.PC++);\r
+ switch (interrupt)\r
+ {\r
+ case 0x40: enabled = flags & 0x1; outFlags |= 0x1; break;\r
+ case 0x48: enabled = flags & 0x2; outFlags |= 0x2; break;\r
+ case 0x50: enabled = flags & 0x4; outFlags |= 0x4; break;\r
+ case 0x58: enabled = flags & 0x8; outFlags |= 0x8; break;\r
+ case 0x60: enabled = flags & 0x10; outFlags += 0x10; break;\r
+ }\r
+ if (enabled)\r
+ {\r
+ m_interruptQueue.push_back(interrupt);\r
+ }\r
+ }\r
}\r
\r
-op16_t Core::op16()\r
+void\r
+Core::run()\r
{\r
- op16_t operand = m_mem.read16(m_reg.PC);\r
- m_reg.PC += 2;\r
- return operand;\r
-}\r
+#include "InstructionSet_Tables.cc"\r
\r
-void Core::reg8(op8_t opcode, u8_t value)\r
-{\r
- // 00RRR000\r
- reg8(opcode >> 3) = value;\r
+ // Instruction decoding occurs in an odd order for the\r
+ // double-prefix instructions. PREFIX PREFIX OPERAND OPCODE\r
+ // This operand value is set in the dispatch method of the double-opcode\r
+ // handler\r
+ // TODO z80 only. Unused for gb80 uint8_t doublePrefixOperand(0);\r
+\r
+ Log& log(Log::Instance());\r
+\r
+ while (true)\r
+ {\r
+ cycle_t clock = 0;\r
+\r
+ uint16_t prefix(0); // Used for debug logging only.\r
+\r
+ // Check for interrupts.\r
+ if (!m_interruptQueue.empty())\r
+ {\r
+ int interrupt = m_interruptQueue.front();\r
+ m_interruptQueue.pop_front();\r
+\r
+ m_iff1 = 0;\r
+ m_iff2 = 0;\r
+\r
+ // TODO interrupt modes!\r
+ m_reg.SP.dec(2);\r
+ m_mem.write16(m_reg.SP, m_reg.PC);\r
+\r
+ m_reg.PC = htoz(interrupt);\r
+\r
+ m_halted = false;\r
+ }\r
+ else if (!m_halted)\r
+ {\r
+ uint16_t opcodePC = m_reg.PC.host(); // For debugging\r
+ uint8_t opcode = op8();\r
+\r
+ goto *(OpcodeTable[opcode]);\r
+\r
+#include "InstructionSet_Opcodes.cc"\r
+\r
+BAD_OPCODE:\r
+ log(Log::DBG_ERROR) <<\r
+ "Unknown Opcode: " << glBoy::hex(opcode) << " (PC=" << opcodePC << ")" <<\r
+ std::endl;\r
+ assert(false);\r
+\r
+END_INSTRUCTIONS: ;\r
+ }\r
+ else\r
+ {\r
+ clock = 4;\r
+ }\r
+\r
+ m_clock += clock;\r
+\r
+ if (m_halted && m_interruptQueue.empty())\r
+ {\r
+ m_clock = std::max(m_clock, m_nextPeriodicCallback);\r
+ }\r
+\r
+ if (m_clock >= m_nextPeriodicCallback)\r
+ {\r
+ m_nextPeriodicCallback = std::numeric_limits<cycle_t>::max();\r
+ for (size_t i = 0; i < m_periodicCallbacks.size(); ++i)\r
+ {\r
+ if (m_clock >= m_periodicCallbacks[i].nextPeriod)\r
+ {\r
+ m_periodicCallbacks[i].callback(i);\r
+\r
+ while (m_periodicCallbacks[i].nextPeriod <= m_clock)\r
+ {\r
+ m_periodicCallbacks[i].nextPeriod +=\r
+ m_periodicCallbacks[i].clocks;\r
+ }\r
+ }\r
+\r
+ if (m_periodicCallbacks[i].nextPeriod < m_nextPeriodicCallback)\r
+ {\r
+ m_nextPeriodicCallback = m_periodicCallbacks[i].nextPeriod;\r
+ }\r
+ }\r
+ }\r
+ }\r
}\r
\r
-reg8_t& Core::reg8(op8_t opcode) const\r
+Core::CallbackId\r
+Core::registerPeriodic(\r
+ int clockPeriod, int offset, const PeriodicCallbackFn& callback\r
+ )\r
{\r
- // 00000RRR\r
- switch (opcode & 0b00000111)\r
+ assert(m_clock == 0);\r
+ PeriodicCallback periodic = { clockPeriod, offset, callback };\r
+\r
+ if (periodic.nextPeriod < m_nextPeriodicCallback)\r
{\r
- case 0: return m_reg.B;\r
- case 1: return m_reg.C;\r
- case 2: return m_reg.D;\r
- case 3: return m_reg.E;\r
- case 4: return m_reg.H;\r
- case 5: return m_reg.L;\r
- case 6: assert(0); FIXME\r
- case 7: return m_reg.A;\r
+ m_nextPeriodicCallback = periodic.nextPeriod;\r
}\r
+\r
+ m_periodicCallbacks.push_back(periodic);\r
+ return m_periodicCallbacks.size() - 1;\r
}\r
- \r
-reg16_t& Core::dd(op8_t opcode) const\r
+\r
+uint8_t Core::op8()\r
{\r
- // 00dd0000\r
- u8_t offset = (opcode & 0b00110000) >> 4;\r
- return (offset == 3) ? m_reg.SP : *(m_reg.begin16 + offset);\r
+ uint8_t result(m_mem.read8(m_reg.PC));\r
+ m_reg.PC.inc();\r
+ return result;\r
}\r
\r
-reg16_t& Core::qq(op8_t opcode) const\r
+DWORD Core::op16()\r
{\r
- // 00qq0000\r
- u8_t offset = (opcode & 0b00110000) >> 4;\r
- return *(m_reg.begin16 + offset);\r
+ DWORD operand(m_mem.read16(m_reg.PC));\r
+ m_reg.PC.inc(2);\r
+ return operand;\r
}\r
\r
-/** \r
- * Emulated Z80 CPU.\r
- *\r
- * Incrementing elapsed cycles is performed separately from emulating the opcodes.\r
- * to simplify the emulation. Cycle info could be embedded in the switch statement?\r
- * eg. case X: doFoo; cycles += 7\r
- *\r
- * Authors: Michael McMaster <email@michaelmcmaster.name>\r
- * Copyright: Michael McMaster <email@michaelmcmaster.name>\r
- */ \r
-#pragma once\r
-\r
-#include "bits/Z80.hh"\r
-#include "Clock.hh"\r
-\r
-#include <memory>\r
-\r
-namespace Z80\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>\r
+//\r
+// This file is part of glBoy.\r
+//\r
+// glBoy is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// glBoy is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.\r
+\r
+#ifndef GLBOY_CORE_HH\r
+#define GLBOY_CORE_HH\r
+\r
+#include "glBoy.hh"\r
+#include "DWORD.hh"\r
+#include "MemoryMap.hh"\r
+#include "Registers.hh"\r
+\r
+#include <deque>\r
+#include <tr1/functional>\r
+#include <functional>\r
+#include <vector>\r
+\r
+namespace glBoy\r
{\r
\r
class Core\r
{\r
public:\r
- Core(std::auto_ptr<Memory> mem);\r
+ Core();\r
\r
- cycle_t run(cycle_t maxCycles);\r
+ MemoryMap& getMemoryMap() { return m_mem; }\r
+\r
+ Registers& getRegisters() { return m_reg; }\r
+\r
+ void run();\r
+\r
+ cycle_t getClockCount() const { return m_clock; }\r
\r
bit getIFF2() const { return m_iff2; }\r
\r
-private:\r
- op8_t op8();\r
- op16_t op16();\r
+ void raiseInterrupt(int interrupt);\r
+ void halt() { m_halted = true; }\r
\r
- void reg8(op8_t opcode, u8_t value);\r
- reg8_t& reg8(op8_t opcode) const;\r
+ typedef int CallbackId;\r
+ typedef std::function<void (CallbackId)> PeriodicCallbackFn;\r
\r
- reg16_t& dd(op8_t opcode) const;\r
- reg16_t& qq(op8_t opcode) const;\r
+ CallbackId registerPeriodic(\r
+ int clockPeriod, int offset, const PeriodicCallbackFn& callback\r
+ );\r
+\r
+private:\r
+ uint8_t op8();\r
+ DWORD op16();\r
\r
// The Z80 can switch between register sets.\r
Registers m_reg;\r
bit m_iff1;\r
bit m_iff2;\r
\r
- std::auto_ptr<Memory> m_mem;\r
+ enum InterruptMode { IM_0, IM_1, IM_2 };\r
+ InterruptMode m_interruptMode;\r
+\r
+ bool m_halted;\r
+ std::deque<uint8_t> m_interruptQueue;\r
+\r
+ MemoryMap m_mem;\r
+\r
+ cycle_t m_clock;\r
+\r
+ struct PeriodicCallback\r
+ {\r
+ int clocks;\r
+ cycle_t nextPeriod;\r
+ PeriodicCallbackFn callback;\r
+ };\r
+ std::vector<PeriodicCallback> m_periodicCallbacks;\r
+ cycle_t m_nextPeriodicCallback;\r
};\r
+\r
+} // namespace glBoy\r
+\r
+#endif\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of glBoy.
+//
+// glBoy is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// glBoy is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef GLBOY_DAA_HH
+#define GLBOY_DAA_HH
+
+#include "glBoy.hh"
+#include "ALU.hh"
+#include "Registers.hh"
+
+namespace glBoy
+{
+
+// Algorithm taken from
+// http://wikiti.brandonw.net/?title=Z80_Instruction_Set
+inline uint8_t DAA(uint8_t a, Flags& flags)
+{
+ uint8_t result(a);
+
+ if (flags.N)
+ {
+ if (flags.H || ((a & 0xf) > 9)) result -= 6;
+ if (flags.C || (a > 0x99)) result -= 0x60;
+ }
+ else
+ {
+ if (flags.H || ((a & 0xf) > 9)) result += 6;
+ if (flags.C || (a > 0x99)) result += 0x60;
+ }
+
+ flags.byte = // S Z U5 H U3 P N C
+ (result & 0x80) |
+ ((result == 0) ? 0x40 : 0) |
+ // U5
+ ((a ^ result) & 0x10) |
+ // U3
+ flags.P |
+ (flags.N ? 0x2 : 0) |
+ ((flags.C || (a > 0x99)) ? 1 : 0);
+
+ // TODO U3 U5
+
+ return result;
+}
+
+} // namespace
+
+#endif
-/** \r
- * Emulated representation of the Z80 CPU registers.\r
- *\r
- * The Z80 cpu supports switching between multiple register sets. Each Register\r
- * object only stores the details of a single register set.\r
-*\r
-* 16bit Register Pairs are represented in a Struct to enable simple byte-order\r
-* conversions where required.\r
- *\r
- * Authors: Michael McMaster <email@michaelmcmaster.name>\r
- * Copyright: Michael McMaster <email@michaelmcmaster.name>\r
- */ \r
-#pragma once\r
-\r
-#include "mm80.hh"\r
-\r
-namespace mm80\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>\r
+//\r
+// This file is part of glBoy.\r
+//\r
+// glBoy is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// glBoy is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.\r
+\r
+#ifndef GLBOY_DWORD_HH\r
+#define GLBOY_DWORD_HH\r
+\r
+#include "glBoy.hh"\r
+#include "util.hh"\r
+\r
+#include <iostream>\r
+\r
+namespace glBoy\r
{\r
\r
-class Core;\r
-\r
-/** 8-bit packed representation of the Z80 flags register.\r
- *\r
- * C Carry. LSB of Flag register\r
- * N Add/Subtract\r
- * P (V) Parity/Overflow\r
- * U3 3rd bit of last 8bit op that altered flags\r
- * H Half-Carry (BCD)\r
- * U5 5th bit of last 8bit op that altered flags\r
- * Z Zero Flag\r
- * S Sign Flag. MSB of Flag register\r
- */\r
-struct Flags\r
-{\r
- union\r
- {\r
- struct\r
- {\r
- unsigned C:1; /// Carry. LSB of Flag register\r
- unsigned N:1; /// Add/Subtract\r
- unsigned P:1; /// (V) Parity/Overflow\r
- unsigned U3:1; /// 3rd bit of last 8bit op that altered flags\r
- unsigned H:1; /// Half-Carry (BCD)\r
- unsigned U5:1; /// 5th bit of last 8bit op that altered flags\r
- unsigned Z:1; /// Zero Flag\r
- unsigned S:1; /// Sign Flag. MSB of Flag register\r
- };\r
- reg8_t byte;\r
- };\r
-\r
- Flags();\r
-\r
- // Update the flags based on an operands value (eg. for simple assignment)\r
- void set(op8_t operand, const Core& context);\r
-\r
- // Update the flags when changing BC\r
- void setCounter(op16_t BC);\r
-\r
- // Set from A - B\r
- void setSub(op8_t A, op8_t B, reg16_t BC);\r
-};\r
-\r
-struct DWORD\r
+struct __attribute__ ((__packed__)) DWORD\r
{\r
// Always constructed, and stored, in Z80 byte order.\r
union\r
{\r
- reg16_t dword;\r
+ uint16_t dword;\r
struct\r
{\r
reg8_t l;\r
};\r
};\r
\r
- reg16_t z80() const { return dword; }\r
- reg16_t host() const\r
+ uint16_t z80() const { return dword; }\r
+ uint16_t host() const\r
{\r
#ifdef HOST_LITTLE_ENDIAN\r
return dword;\r
#else\r
- return (reg16_t(h) << 8) | l;\r
+ return (uint16_t(h) << 8) | l;\r
#endif\r
}\r
\r
- void inc(u8_t val = 1) const\r
+ void inc(uint8_t val = 1)\r
{\r
#ifdef HOST_LITTLE_ENDIAN\r
- ++dword;\r
+ dword += val;\r
#else\r
- reg16_t tmp(host() + val);\r
+ uint16_t tmp(host() + val);\r
l = tmp & 0xf;\r
h = tmp >> 8;\r
#endif\r
}\r
\r
- void dec(u8_t val = 1) const\r
+ void dec(uint8_t val = 1)\r
{\r
#ifdef HOST_LITTLE_ENDIAN\r
- --dword;\r
+ dword -= val;\r
#else\r
- reg16_t tmp(host() - val);\r
+ uint16_t tmp(host() - val);\r
l = tmp & 0xf;\r
h = tmp >> 8;\r
#endif\r
}\r
};\r
\r
+inline std::ostream& operator<<(std::ostream& out, const glBoy::DWORD& val)\r
+{\r
+ out << glBoy::hex(val.host());\r
+ return out;\r
+}\r
+\r
// Create a dword from z80-ordered mem.\r
-static DWORD CreateDWORD(u8_t* in)\r
+inline DWORD CreateDWORD(uint8_t* in)\r
{\r
DWORD result;\r
result.l = in[0];\r
return result;\r
}\r
\r
-/** Z80 register access.\r
- *\r
- * Registers may be accessed in either 8-bit (eg. A) \r
- * or by their 16-bit pair (eg. AF)\r
- *\r
- */\r
-struct Registers\r
+#if defined(HOST_LITTLE_ENDIAN)\r
+inline DWORD htoz(uint16_t host)\r
{\r
- Registers();\r
-\r
-#define Z80_REG_STRUCT(h,l) \\r
- struct \\r
- { \\r
- l; \\r
- h; \\r
- };\r
+ DWORD result;\r
+ result.dword = host;\r
+ return result;\r
+}\r
+#else\r
+inline DWORD htoz(uint16_t host)\r
+{\r
+ DWORD result;\r
+ result.dword = (host << 8) | (host >> 8);\r
+ return result;\r
+}\r
#endif\r
+} // namespace glBoy\r
\r
- // Note: The order of these unions/structs is important, as\r
- // offsets are taken into the Register struct\r
- reg8_t[0] begin8;\r
- reg16_t[0] begin16;\r
-\r
- union\r
- {\r
- Z80_REG_STRUCT(reg8_t B, reg8_t C);\r
- DWORD BC;\r
- };\r
-\r
- union\r
- {\r
- Z80_REG_STRUCT(reg8_t D, reg8_t E);\r
- DWORD DE;\r
- };\r
-\r
- union\r
- {\r
- Z80_REG_STRUCT(reg8_t H, reg8_t L);\r
- DWORD HL;\r
- };\r
-\r
- union\r
- {\r
- Z80_REG_STRUCT(reg8_t A, Flags F,);\r
- DWORD AF;\r
- };\r
-\r
-\r
-#undef Z80_REG_STRUCT\r
-\r
- // Special Purpose\r
- reg8_t I; // Interrupt Vector\r
- reg8_t R; // Memory Refresh\r
- DWORD IX; // Index Register\r
- DWORD IY; // Index Register\r
- DWORD SP; // Stack Pointer\r
- DWORD PC; // Program Counter\r
-};\r
+#endif\r
\r
-}\r
\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of glBoy.
+//
+// glBoy is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// glBoy is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
+
+#include "glBoy.hh"
+#include "GameboyCart.hh"
+#include "RAM.hh"
+#include "ROM.hh"
+
+#include <cassert>
+
+using namespace glBoy;
+using namespace std;
+
+namespace
+{
+ class MBC1 : public MemoryMap::Memory
+ {
+ public:
+ MBC1(GameboyCart* parent, uint8_t* begin, uint8_t* end) :
+ m_parent(parent),
+ m_rom(begin, end),
+ m_mode(ROM_SELECT),
+ m_romLow(1),
+ m_romHigh(0),
+ m_romOffset(0)
+ {
+ }
+
+ virtual uint8_t read8(uint16_t address)
+ {
+ if (address < 0x4000)
+ {
+ return m_rom[address];
+ }
+ else if ((m_romOffset + address) < m_rom.size())
+ {
+ return m_rom[m_romOffset + address];
+ }
+ else
+ {
+std::cerr << address << " " << m_rom.size() << " " << m_romOffset << " " << (int) m_romLow << " " << (int) m_romHigh << std::endl;
+assert(false);
+ return 0;
+ }
+ }
+
+ virtual void write8(uint16_t address, uint8_t value)
+ {
+ if (address < 0x1FFF)
+ {
+ // TODO enable/disable ram
+ }
+ else if (address < 0x4000)
+ {
+ m_romLow = (value == 0) ? 1 : value;
+
+ m_romOffset =
+ ((((m_romHigh & 0x3) << 4) | (m_romLow & 0x1F)) - 1) * 0x4000;
+assert(m_romOffset < m_rom.size());
+ }
+ else if (address < 0x6000)
+ {
+ if (m_mode == RAM_SELECT)
+ {
+ m_parent->setRamBank(value & 0x3);
+ }
+ else
+ {
+ m_romHigh = value;
+
+ m_romOffset =
+ ((((m_romHigh & 0x3) << 4) | (m_romLow & 0x1F)) - 1) * 0x4000;
+assert(m_romOffset < m_rom.size());
+ }
+ }
+ else
+ {
+ if (value & 1)
+ {
+ m_mode = RAM_SELECT;
+ }
+ else
+ {
+ m_mode = ROM_SELECT;
+ m_parent->setRamBank(0);
+ }
+ }
+ }
+
+ private:
+ GameboyCart* m_parent;
+
+ std::vector<uint16_t> m_rom;
+
+ enum Mode { ROM_SELECT, RAM_SELECT };
+ Mode m_mode;
+
+ uint8_t m_romLow;
+ uint8_t m_romHigh;
+
+ uint32_t m_romOffset;
+ };
+}
+
+GameboyCart::GameboyCart(
+ Core& core, uint8_t* rom, size_t size
+ ) :
+ m_core(core)
+{
+ if (size < 16384)
+ {
+ std::cerr << "Invalid ROM (too small)" << std::endl;
+ abort();
+ }
+
+ bool error(false);
+
+ string mbc;
+ switch (rom[0x147])
+ {
+ case 0:
+ {
+ mbc = "None";
+
+ shared_ptr<MemoryMap::Memory> mem(
+ new MBC1(this, rom, rom + size)
+ );
+/* shared_ptr<MemoryMap::Memory> mem(
+ new ROM(rom, size)
+ );*/
+ m_core.getMemoryMap().map(0, 0x8000, mem);
+ m_romBanks.push_back(mem);
+ }; break;
+
+ case 1:
+ case 2:
+ case 3:
+ {
+ mbc = "MBC1";
+ shared_ptr<MemoryMap::Memory> mem(
+ new MBC1(this, rom, rom + size)
+ );
+ m_core.getMemoryMap().map(0, 0x8000, mem);
+ m_romBanks.push_back(mem);
+ };
+ break;
+ default:
+ mbc = "Unsupported";
+ error = true;
+ };
+
+ int cartRam;
+ switch (rom[0x149])
+ {
+ case 0:
+ {
+ // Mis-reported on some carts.
+ cartRam = 0;
+ shared_ptr<MemoryMap::Memory> mem(new RAM(8192));
+ m_core.getMemoryMap().map(0xA000, 8192, mem);
+ m_ramBanks.push_back(mem);
+ }; break;
+ case 1:
+ {
+ cartRam = 2048;;
+
+ // Fill out the entire 8k, just in case.
+ shared_ptr<MemoryMap::Memory> mem(new RAM(8192));
+ m_core.getMemoryMap().map(0xA000, cartRam, mem);
+ m_ramBanks.push_back(mem);
+ }; break;
+
+ case 2:
+ {
+ cartRam = 8192;
+ shared_ptr<MemoryMap::Memory> mem(new RAM(cartRam));
+ m_core.getMemoryMap().map(0xA000, cartRam, mem);
+ m_ramBanks.push_back(mem);
+ }; break;
+
+ case 3:
+ {
+ cartRam = 32768;
+ for (int i = 0; i < 4; ++i)
+ {
+ shared_ptr<MemoryMap::Memory> mem(new RAM(8192));
+ m_ramBanks.push_back(mem);
+ }
+ m_core.getMemoryMap().map(0xA000, 8192, m_ramBanks.front());
+ }; break;
+
+ default:
+ error = true;
+ cartRam = 0;
+ cerr << "Invalid RAM size" << endl;
+ }
+
+ string title(rom + 0x134, rom + 0x134 + 11);
+ cout << "Loading Cart" << endl;
+ cout << "\tTitle:\t\t" << title << endl;
+ cout << "\tRegion:\t\t" << (rom[0x14A] ? "Worldwide" : "Japan") << endl;
+ cout << "\tVersion:\t" << int(rom[0x14D]) << endl;
+ cout << "\tMBC:\t\t" << mbc << endl;
+ cout << "\tROM size:\t" << (32 << rom[0x148]) << "KB" << endl;
+ cout << "\tExt RAM size:\t" << (cartRam / 1024) << "KB" << endl;
+
+ if (error)
+ {
+ abort();
+ }
+}
+
+void
+GameboyCart::setRamBank(size_t bank)
+{
+ if (bank < m_ramBanks.size())
+ {
+ m_core.getMemoryMap().remove(0xA000);
+ m_core.getMemoryMap().map(0xA000, 8192, m_ramBanks[bank]);
+ }
+}
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of glBoy.
+//
+// glBoy is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// glBoy is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef GLBOY_GAMEBOYCART_HH
+#define GLBOY_GAMEBOYCART_HH
+
+#include "glBoy.hh"
+#include "Core.hh"
+
+namespace glBoy
+{
+
+class GameboyCart
+{
+public:
+ GameboyCart(Core& core, uint8_t* rom, size_t size);
+
+ void setRamBank(size_t bank);
+private:
+ Core& m_core;
+
+ std::vector<std::shared_ptr<MemoryMap::Memory> > m_romBanks;
+ std::vector<std::shared_ptr<MemoryMap::Memory> > m_ramBanks;
+};
+
+} // namespace glBoy
+
+#endif
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of glBoy.
+//
+// glBoy is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// glBoy is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
+
+#define GL_GLEXT_PROTOTYPES
+
+#include "glBoy.hh"
+#include "GameboyGraphics.hh"
+#include "Log.hh"
+#include "MemoryMap.hh"
+
+#include <GL/gl.h>
+#include <GL/glext.h>
+
+#include <SDL/SDL_opengl.h>
+
+#include <cassert>
+#include <limits>
+
+#include <sys/time.h>
+
+
+using namespace glBoy;
+
+namespace
+{
+ class VRAM : public MemoryMap::Memory
+ {
+ public:
+ VRAM(uint8_t* mem,
+ const std::function<void ()>& changed
+ ) :
+ m_mem(mem),
+ m_changed(changed)
+ {}
+
+ virtual uint8_t read8(uint16_t address) { return m_mem[address]; }
+ virtual void write8(uint16_t address, uint8_t value)
+ {
+ m_mem[address] = value;
+ m_changed();
+ }
+
+ virtual void copy(uint8_t* dest, uint16_t address, int bytes)
+ {
+ memcpy(dest, m_mem + address, bytes);
+ }
+
+ private:
+ uint8_t* m_mem;
+ std::function<void ()> m_changed;
+ };
+
+}
+
+class GameboyGraphics::GraphicsRegisters : public MemoryMap::Memory
+{
+public:
+ uint8_t LCDC;
+ uint8_t STAT;
+ uint8_t SCY;
+ uint8_t SCX;
+ uint8_t LY;
+ uint8_t LYC;
+ // uint8_t DMA; No need to store this - write-only.
+ uint8_t BGP;
+ uint8_t OBP0;
+ uint8_t OBP1;
+ uint8_t WY;
+ uint8_t WX;
+
+ GraphicsRegisters(
+ Core& core,
+ uint8_t* oam,
+ const std::function<void ()>& backgroundChanged
+ ) :
+ LCDC(0),
+ STAT(0),
+ SCY(0),
+ SCX(0),
+ LY(0),
+ LYC(0),
+ BGP(0),
+ OBP0(0),
+ OBP1(0),
+ WY(0),
+ WX(0),
+ m_core(core),
+ m_mapping(core.getMemoryMap()),
+ m_oam(oam),
+ m_backgroundChanged(backgroundChanged)
+ {
+ }
+
+ virtual uint8_t read8(uint16_t address)
+ {
+ assert(address < 12);
+
+ switch (address)
+ {
+ case 0: // LCDC
+ {
+ return LCDC;
+ } break;
+ case 1: // STAT
+ {
+ return STAT;
+ } break;
+ case 2: // SCY
+ {
+ return SCY;
+ } break;
+ case 3: // SCX
+ {
+ return SCX;
+ } break;
+ case 4: // LY
+ {
+ return LY;
+ } break;
+ case 5: // LYC
+ {
+ return LYC;
+ } break;
+ case 6: // DMA
+ {
+ return 0;
+ } break;
+ case 7: // BGP
+ {
+ return BGP;
+ } break;
+ case 8: // OBP0
+ {
+ return OBP0;
+ } break;
+ case 9: // OBP1
+ {
+ return OBP1;
+ } break;
+ case 10: // WY
+ {
+ return WY;
+ } break;
+ case 11: // WX
+ {
+ return WX;
+ } break;
+ }
+ return 0; // make g++ happy.
+ }
+
+ virtual void write8(uint16_t address, uint8_t value)
+ {
+ assert(address < 12);
+
+ switch (address)
+ {
+ case 0: // LCDC
+ {
+ LCDC = value;
+ m_backgroundChanged();
+ } break;
+ case 1: // STAT
+ {
+ // bits 0-2 are read only.
+ uint8_t tmp(STAT & 0x7);
+ STAT = value & 0xF8;
+ STAT |= tmp;
+ } break;
+ case 2: // SCY
+ {
+ SCY = value;
+ } break;
+ case 3: // SCX
+ {
+ SCX = value;
+ } break;
+ case 4: // LY
+ {
+ // Readonly.
+ } break;
+ case 5: // LYC
+ {
+ LYC = value;
+ } break;
+ case 6: // DMA
+ {
+ // Start a DMA transfer to the OAM memory area. This should
+ // take 160 microseconds, but there's no real need to simulate
+ // this. CPU may only access zero-page mem during the
+ // transfer, and will spin the required number of cycles.
+ uint16_t dmaStart(static_cast<uint16_t>(value) << 8);
+ m_mapping.copy(m_oam, dmaStart, 0x9F);
+ } break;
+ case 7: // BGP
+ {
+ BGP = value;
+ m_backgroundChanged();
+ } break;
+ case 8: // OBP0
+ {
+ OBP0 = value;
+ } break;
+ case 9: // OBP1
+ {
+ OBP1 = value;
+ } break;
+ case 10: // WY
+ {
+ WY = value;
+ } break;
+ case 11: // WX
+ {
+ WX = value;
+ } break;
+ }
+ }
+
+ private:
+ Core& m_core;
+ MemoryMap& m_mapping; // For DMA transfers.
+ uint8_t* m_oam;
+ std::function<void ()> m_backgroundChanged;
+};
+
+
+GameboyGraphics::GameboyGraphics(
+ Core& core,
+ SDL_Surface* surface,
+ PixelScaler scaler,
+ ResampleMethod resampler
+ ) :
+ m_core(core),
+ m_scaler(scaler),
+ m_resampler(resampler),
+ m_hq4x(160, 144),
+ m_vram(new uint8_t[0x2000]),
+ m_oam(new uint8_t[0x100]), // Should be 0x9F, but many access to 100
+ m_reg(
+ new GraphicsRegisters(
+ core,
+ m_oam,
+ std::bind(&GameboyGraphics::backgroundChanged, this)
+ )
+ ),
+ m_backgroundRedraw(true),
+ m_surface(surface),
+ m_frameSkip(false),
+ m_lastSkipped(false),
+ m_stat_frames(0),
+ m_stat_skipped(0),
+ m_stat_redraw(0)
+{
+ MemoryMap& mapping(core.getMemoryMap());
+ mapping.map(
+ 0x8000,
+ 0x2000,
+ std::shared_ptr<VRAM>(
+ new VRAM(
+ m_vram,
+ std::bind(&GameboyGraphics::backgroundChanged, this)
+ )
+ )
+ );
+ mapping.map(
+ 0xFE00,
+ 0x100,
+ std::shared_ptr<VRAM>(
+ new VRAM(
+ m_oam,
+ std::bind(&GameboyGraphics::oamChanged, this)
+ )
+ )
+ );
+ mapping.map(0xFF40, 12, m_reg);
+
+ using namespace std::placeholders;
+ m_mode0 = core.registerPeriodic(
+ 456, 252, std::bind(&glBoy::GameboyGraphics::periodicCallback, this, _1)
+ );
+ m_mode2 = core.registerPeriodic(
+ 456, 0, std::bind(&glBoy::GameboyGraphics::periodicCallback, this, _1)
+ );
+ m_mode3 = core.registerPeriodic(
+ 456, 80, std::bind(&glBoy::GameboyGraphics::periodicCallback, this, _1)
+ );
+
+ gettimeofday(&m_stat_fpsTimer, NULL);
+}
+
+GameboyGraphics::~GameboyGraphics()
+{
+ delete[] m_vram;
+ delete[] m_oam;
+}
+
+void
+GameboyGraphics::periodicCallback(Core::CallbackId id)
+{
+ // Mode 0: H-Blank
+ // Mode 1: V-Blank
+ // Mode 2: Reading from OAM
+ // Mode 3: Reading from OAM and VRAM
+ uint8_t mode;
+
+ if (id == m_mode0)
+ {
+ renderScanLine();
+
+ mode = (m_reg->LY > 143) ? 1 : 0;
+ }
+ else if (id == m_mode2)
+ {
+ startScanLine();
+ mode = (m_reg->LY > 143) ? 1 : 2;
+ }
+ else // mode3
+ {
+ mode = (m_reg->LY > 143) ? 1 : 3;
+ }
+
+ m_reg->STAT = (m_reg->STAT & 0xF8) | mode;
+}
+
+void
+GameboyGraphics::startScanLine()
+{
+ ++m_reg->LY;
+ if (m_reg->LY > 153) m_reg->LY = 0;
+
+ m_lineScrollX[m_reg->LY] = m_reg->SCX;
+ m_lineScrollY[m_reg->LY] = m_reg->SCY;
+
+ bool equal(m_reg->LY == m_reg->LYC);
+ if (equal)
+ {
+ m_reg->STAT |= 0x4;
+ }
+ else
+ {
+ m_reg->STAT &= 0xFB;
+ }
+
+ if ((m_reg->LY == 144))
+ {
+ // Draw the entire screen when entering vblank. This will be done just
+ // under 60 times per second.
+ if (!m_frameSkip || m_lastSkipped)
+ {
+ renderScreen();
+ m_lastSkipped = false;
+ }
+ else
+ {
+ ++m_stat_skipped;
+ m_lastSkipped = true;
+ }
+ m_frameSkip = false;
+ displayStats();
+
+ //if (m_reg->STAT & 0x10) Seems to need to trigger regardless ?
+ // Maybe we need to trigger 0x40 as well, depending on STAT ?
+ {
+ m_core.raiseInterrupt(0x40); // Start of vblank
+ }
+ }
+ else if (
+ (equal && (m_reg->STAT & 0x40)) || // Coincidence Interrupt
+ (m_reg->STAT & 0x20) // OAM interrupt
+ )
+ {
+ m_core.raiseInterrupt(0x48);
+ }
+}
+
+void
+GameboyGraphics::renderScanLine()
+{
+ // Entering H-Blank
+ if (m_reg->STAT & 0x08 && (m_reg->LY <= 143))
+ {
+ m_core.raiseInterrupt(0x48);
+ }
+
+ // Don't bother drawing individual scanlines - just draw the entire screen
+ // when entering VBLANK (which is actually done in the startScanLine method)
+}
+
+// Render the given tile to the screen at the given coordinates.
+// The x and y may be outside the bounds of the screen, and may be negative.
+// Only the visible part of the tile will be rendered.
+void GameboyGraphics::renderTile(
+ uint8_t tile,
+ int x,
+ int y,
+ const FrameBuffer& frameBuffer
+ )
+{
+ uint16_t tileAddr;
+ if (m_reg->LCDC & 0x10)
+ {
+ tileAddr = 0x8000 + static_cast<uint16_t>(tile) * 16;
+ }
+ else
+ {
+ tileAddr = 0x9000 +
+ (static_cast<int8_t>(tile) * static_cast<uint16_t>(16));
+ }
+
+
+ renderTile(tileAddr, x, y, frameBuffer, RENDER_BLIT, m_reg->BGP);
+}
+
+void GameboyGraphics::renderTile(
+ uint16_t tileAddr,
+ int x,
+ int y,
+ const FrameBuffer& frameBuffer,
+ RenderMode mode,
+ uint8_t palette,
+ uint8_t sprite, // 0x80 -> 0xA8, 0 for background/window
+ bool horizFlip,
+ bool vertFlip,
+ int tileHeight // 8 or 16
+ )
+{
+ // Render an 8x8 or 8x16 tile.
+
+ tileAddr -= 0x8000; // Use vram directly.
+
+ if (vertFlip) tileAddr += (tileHeight - 1) * 2;
+ int yStep = vertFlip ? -2 : 2; // Each row takes 2 bytes.
+ if (y < 0) tileAddr += yStep * (-y);
+
+ for (int posY = (y < 0 ? -y : 0);
+ (posY < tileHeight) && (y + posY < frameBuffer.height);
+ ++posY, tileAddr += yStep
+ )
+ {
+ uint8_t rowLSB(m_vram[tileAddr]);
+ uint8_t rowMSB(m_vram[tileAddr + 1]);
+
+ size_t pixelRowAddr((y + posY) * frameBuffer.width + x);
+
+ int xStep = horizFlip ? 1 : -1;
+ int xBit = horizFlip ? 0 : 7;
+ if (x < 0) xBit += xStep * (-x);
+
+ for (int posX = (x < 0 ? -x : 0);
+ (posX < 8) && (x + posX < frameBuffer.width);
+ ++posX, xBit += xStep
+ )
+ {
+ uint8_t col =
+ (rowMSB >> xBit) & 0x01;
+ col <<= 1;
+ col |=
+ (rowLSB >> xBit) & 0x01;
+
+ Pixel& pixel = frameBuffer.frame[pixelRowAddr + posX];
+
+ bool display;
+ if (!sprite)
+ {
+ // Always display background and window data.
+ // window data overwrites background data
+ display = true;
+ }
+ else if (pixel.tileX != std::numeric_limits<int16_t>::max())
+ {
+ if (col)
+ {
+ if (x < pixel.tileX)
+ {
+ display = true;
+ }
+ else
+ {
+ // Existing pixel has priority. Note that transparent
+ // areas of sprites are never output (ie. it remains
+ // background), so there is no need to check of the
+ // oldSprite was actually transparent.
+ // Also note that we draw sprites in order, so this
+ // sprite number can never be less than the already-
+ // drawn sprite (and thus will not have priority)
+ display = false;
+ }
+ }
+ else
+ {
+ // Ignore transparent areas of the sprite.
+ display = false;
+ }
+ }
+ else
+ {
+ // Should this sprite pixel overwrite the background ?
+ // Sprite colour 0 is transparent (shows background)
+ // Background colour 0 is always "behind" the sprite.
+ display =
+ col &&
+ ((mode == RENDER_BLIT) || (pixel.colourIndex == 0));
+ }
+
+ if (display)
+ {
+ pixel.tileX = sprite ? x : std::numeric_limits<int16_t>::max();
+ pixel.palette = palette;
+ pixel.colourIndex = col;
+ }
+ }
+ }
+}
+
+void
+GameboyGraphics::renderScreen()
+{
+ FrameBuffer screenBuffer = { 160, 144, &m_screen[0][0]};
+
+ if (m_reg->LCDC & 0x1)
+ {
+ // Draw background. Background tile map is a 256x256 virtual screen,
+ // that is scrolled via SCX/SCY.
+
+ if (m_backgroundRedraw)
+ {
+ m_backgroundRedraw = false;
+ ++m_stat_redraw;
+
+ // Select one of two maps at 0x9800/0x9C00
+ uint16_t tileMapAddr(m_reg->LCDC & 0x08 ? 0x1C00 : 0x1800);
+
+ // Render entire tile map, then copy to the screen.
+ memset(m_background, 0, sizeof(m_background));
+ FrameBuffer buffer = { 256, 256, &m_background[0][0] };
+
+ for (int y = 0; y < 32; ++y)
+ {
+ uint16_t yAddr(tileMapAddr + (y * 32));
+ for (int x = 0; x < 32; ++x)
+ {
+ renderTile(
+ m_vram[yAddr + x],
+ x * 8,
+ y * 8,
+ buffer
+ );
+ }
+ }
+ }
+
+ for (int y = 0; y < 144; ++y)
+ {
+ int scrollY = y + m_lineScrollY[y];
+ if (scrollY > 255) scrollY -= 256;
+
+ for (int x = 0; x < 160; ++x)
+ {
+ int scrollX = x + m_lineScrollX[y];
+ if (scrollX > 255) scrollX -= 256;
+
+ m_screen[y][x] = m_background[scrollY][scrollX];
+ }
+ }
+ }
+ else
+ {
+ // clear screen.
+ memset(m_screen, 0, sizeof(Pixel) * 144 * 160);
+ }
+
+
+ if (m_reg->LCDC & 0x20)
+ {
+ // Draw window over top of existing background.
+
+ // Draw background. Window tile map is a screen,
+ // that is positioned via WX/WY.
+
+ // Select one of two maps at 0x9800/0x9C00
+ uint16_t tileMapAddr(m_reg->LCDC & 0x40 ? 0x1C00 : 0x1800);
+
+ for (int y = 0; y < 18; ++y)
+ {
+ uint16_t yAddr(tileMapAddr + (y * 32));
+
+ for (int x = 0; x < 20; ++x)
+ {
+ renderTile(
+ m_vram[yAddr + x],
+ x * 8 + m_reg->WX - 7,
+ y * 8 + m_reg->WY,
+ screenBuffer // Render directly to the screen.
+ );
+ }
+ }
+ }
+
+ std::vector<uint8_t> sprite;
+ sprite.resize(160);
+ if (m_reg->LCDC & 0x2)
+ {
+ // Draw sprites
+
+ for (int spriteNum = 0; spriteNum < 40; ++spriteNum)
+ {
+ Sprite sprite(getSprite(spriteNum));
+
+ renderTile(
+ 0x8000 + static_cast<uint16_t>(sprite.tile) * 16,
+ sprite.x,
+ sprite.y,
+ screenBuffer,
+ (sprite.flags & 0x80) ? RENDER_SPRITE : RENDER_BLIT,
+ (sprite.flags & 0x10) ? m_reg->OBP1 : m_reg->OBP0,
+ static_cast<uint8_t>(spriteNum) | 0x80,
+ (sprite.flags & 0x20) ? true : false, // horizontal flip
+ (sprite.flags & 0x40) ? true : false, // vertical flip
+ (m_reg->LCDC & 0x04) ? 16 : 8 // 8x8 or 8x16
+ );
+ }
+ }
+ updateOutputDevice();
+
+
+}
+
+GameboyGraphics::Sprite
+GameboyGraphics::getSprite(int spriteNum) const
+{
+ Sprite result;
+
+ result.y = static_cast<int>(m_oam[spriteNum * 4]) - 16;
+ result.x = static_cast<int>(m_oam[spriteNum * 4 + 1]) - 8;
+ result.tile = m_oam[spriteNum * 4 + 2];
+ result.flags = m_oam[spriteNum * 4 + 3];
+
+ return result;
+}
+
+void
+GameboyGraphics::displayStats()
+{
+ m_stat_frames++;
+ if (m_stat_frames == 300)
+ {
+ timeval startTime(m_stat_fpsTimer);
+ gettimeofday(&m_stat_fpsTimer, 0);
+
+ const double usec(1000000.0);
+ double fps = static_cast<double>(m_stat_frames - m_stat_skipped) /
+ (
+ (m_stat_fpsTimer.tv_sec + m_stat_fpsTimer.tv_usec / usec) -
+ (startTime.tv_sec + startTime.tv_usec / usec)
+ );
+
+ Log::Instance()(Log::DBG_STAT) <<
+ "FPS = " << fps << " (error=" << (100 - (fps * 100 / 59.73)) << "%) "
+ "backround redraw=" << m_stat_redraw << std::endl;
+ m_stat_frames = 0;
+ m_stat_skipped = 0;
+ m_stat_redraw = 0;
+ }
+}
+
+void
+GameboyGraphics::blitGreyscale(uint8_t* dest)
+{
+ for (int y = 0; y < 144; ++y)
+ {
+ uint8_t* row(dest + y*160);
+ for (int x = 0; x < 160; ++x)
+ {
+ int index(m_screen[y][x].colourIndex);
+ int colour((m_screen[y][x].palette >> (index * 2)) & 0x3);
+ row[x] = 255 - ( colour * 85);
+ }
+ }
+}
+
+void
+GameboyGraphics:: updateOutputDevice()
+{
+ if (SDL_MUSTLOCK(m_surface))
+ {
+ if (SDL_LockSurface(m_surface) < 0)
+ {
+ return;
+ }
+ }
+
+ // Use static buffers to reduce stack usage
+ uint8_t textureData[144*160];
+ blitGreyscale(&textureData[0]);
+
+ // Allocate texture
+ GLuint texture;
+ glGenTextures(1, &texture);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, texture);
+
+ // Set the texture's stretching properties
+ // When using opengl shader scalers, we need to use nearest.
+ uint32_t resample(
+ (m_scaler == Scaler_None && m_resampler == Resample_Bilinear) ?
+ GL_LINEAR : GL_NEAREST
+ );
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, resample);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, resample);
+
+ // Create the texture image
+ glTexImage2D(
+ GL_TEXTURE_2D,
+ 0,
+ GL_RGBA,
+ 160,
+ 144,
+ 0,
+ GL_LUMINANCE,
+ GL_UNSIGNED_BYTE,
+ &textureData
+ );
+
+ m_hq4x.prepareShader(texture);
+
+ // Output texture.
+
+ glBegin(GL_QUADS);
+ glTexCoord2i( 0, 0 ); glVertex2i( 0, 0 );
+ glTexCoord2i( 1, 0 ); glVertex2i( 160, 0 );
+ glTexCoord2i( 1, 1 ); glVertex2i( 160, 144 );
+ glTexCoord2i( 0, 1 ); glVertex2i( 0, 144);
+ glEnd();
+
+ glLoadIdentity();
+ SDL_GL_SwapBuffers();
+ glDeleteTextures( 1, &texture );
+
+ if (SDL_MUSTLOCK(m_surface))
+ {
+ SDL_UnlockSurface(m_surface);
+ }
+}
+
-#ifndef MM80_GAMEBOYGRAPHICS_HH
-#define MM80_GAMEBOYGRAPHICS_HH
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of glBoy.
+//
+// glBoy is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// glBoy is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
-#include "mm80.hh"
+#ifndef GLBOY_GAMEBOYGRAPHICS_HH
+#define GLBOY_GAMEBOYGRAPHICS_HH
+
+#include "glBoy.hh"
+#include "Core.hh"
#include "MemoryMap.hh"
+#include "hq4x.hh"
+
+#include <SDL/SDL.h>
+#include <SDL/SDL_opengl.h>
+
+#include <vector>
+
+namespace glBoy
+{
class GameboyGraphics
{
public:
+ enum PixelScaler { Scaler_None, Scaler_Scale2x, Scaler_hq4x };
+ enum ResampleMethod { Resample_Nearest, Resample_Bilinear };
+
+ GameboyGraphics(
+ Core& core,
+ SDL_Surface* surface,
+ PixelScaler scaler,
+ ResampleMethod resampler
+ );
+ ~GameboyGraphics();
+
+ void startScanLine();
void renderScanLine();
+ void setFrameSkip() { m_frameSkip = true; }
+ void clearFrameSkip() { m_frameSkip = false; }
+
private:
+ void periodicCallback(Core::CallbackId);
+
+ void backgroundChanged() { m_backgroundRedraw = true; }
+ void oamChanged() {}
+
+ struct Pixel // Must be POD - used with memset.
+ {
+ int16_t tileX; // Max for background/window
+ uint8_t palette;
+ uint8_t colourIndex; // Index into pallete.
+ };
+
+ struct FrameBuffer
+ {
+ int width;
+ int height;
+ Pixel* frame;
+ };
+
+ // RENDER_BLIT = overwrite background.
+ // RENDER_SPRITE = only overwrite background colour 0 (ie. send
+ // behind background)
+ enum RenderMode{ RENDER_BLIT, RENDER_SPRITE };
+
+ void renderScreen();
+ void displayStats();
+
+ void renderTile(
+ uint8_t tile,
+ int x,
+ int y,
+ const FrameBuffer& frameBuffer
+ );
+
+ void renderTile(
+ uint16_t tileAddr,
+ int x,
+ int y,
+ const FrameBuffer& frameBuffer,
+ RenderMode mode,
+ uint8_t pallete,
+ uint8_t sprite = 0, // 0x80 -> 0xA8, 0 for background/window
+ bool horizFlip = false,
+ bool vertFlip = false,
+ int tileHeight = 8 // 8 or 16
+ );
+
+ struct Sprite
+ {
+ int x;
+ int y;
+ uint8_t tile;
+ uint8_t flags;
+ };
+ Sprite getSprite(int spriteNum) const;
+
+ void blitGreyscale(uint8_t* dest);
+ void updateOutputDevice();
+
+ Core& m_core;
+
+ PixelScaler m_scaler;
+ ResampleMethod m_resampler;
+
+ Hq4x m_hq4x;
+
// Graphics system operates with 8x8 pixel tiles. There is space for 384
// tiles. 256 tiles map be used in a map. Map 1 uses tile numbers 0 to 255,
// other uses -128 to 127.
// 0x9000 - 0x97FF: Tile Set #0: tiles 0-127
// 0x9800 - 0x9BFF: Tile map #0
// 0x9C00 - 0x9FFF: Tile map #1
- std::tr1::shared_ptr<MemoryMap::Memory> m_vram;
+ uint8_t* m_vram;
- enum SpriteOptions
- {
- SPRITE_PALETTE = 0x10, // 0 = Pallete #0, 1 = Pallete #2
- SPRITE_XFLIP = 0x20, // 0 = Normal, 1 = flip.
- SPRITE_YFLIP = 0x40, // 0 = Normal, 1 = flip
- SPRITE_PRIORITY = 0x80 // 0 = Above Background, 1 = Below background
- // (background colour 0 == transparent)
- };
+ // Object Attribute Memory. Up to 40 sprites, which reference first Tile Set
+ uint8_t* m_oam; // 0xFE00 - 0xFE9F
- struct __attribute__ ((packed)) Sprite
- {
- uint8_t y; // Y-coordinate - 16
- uint8_t x; // X-coordinate - 8
- uint8_t tile; // Tile number
- uint8_t options;
- }
+ // Memory-mapped IO Registers
+ class GraphicsRegisters;
+ std::shared_ptr<GraphicsRegisters> m_reg;
- // Object Attribute Memory (Up to 40 sprites), as per Sprite struct.
- std::tr1::shared_ptr<MemoryMap::Memory> m_oam; // 0xFE00 - 0xFE9F
+ // Raster display
+ bool m_backgroundRedraw;
+ Pixel m_background[256][256];
+ Pixel m_screen[144][160];
- struct __attribute__((packed)) Registers
- {
- uint8_t LCDC;
- uint8_t STAT;
- uint8_t SCY;
- uint8_t SCX;
- uint8_t LY;
- uint8_t LYC;
- uint8_t DMA;
- uint8_t OBP0;
- uint8_t OBP1;
- uint8_t WY;
- uint8_t WX;
- };
+ // Output device
+ SDL_Surface* m_surface;
- // Memory-mapped IO Registers
- std::tr1::shared_ptr<MemoryMap::Memory> m_reg;
+ // Shader program
+ GLint m_shaderProgram;
+
+ Core::CallbackId m_mode0;
+ Core::CallbackId m_mode2;
+ Core::CallbackId m_mode3;
+
+ int m_lineScrollX[154];
+ int m_lineScrollY[154];
+
+
+ bool m_frameSkip;
+ bool m_lastSkipped;
+
+ timeval m_stat_fpsTimer;
+ int m_stat_frames;
+ int m_stat_skipped;
+ int m_stat_redraw;
};
+} // namespace glBoy
+
#endif
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of glBoy.
+//
+// glBoy is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// glBoy is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
+
+#include "glBoy.hh"
+#include "GameboyJoypad.hh"
+#include "Core.hh"
+
+#include <algorithm>
+#include <cassert>
+#include <cstdlib>
+
+using namespace glBoy;
+
+// TODO implement interrupt!
+
+
+GameboyJoypad::GameboyJoypad(Core& core) :
+ m_state(Joypad_Off)
+{
+ m_inputs[DOWN].sdlKey = SDLK_DOWN;
+ m_inputs[UP].sdlKey = SDLK_UP;
+ m_inputs[LEFT].sdlKey = SDLK_LEFT;
+ m_inputs[RIGHT].sdlKey = SDLK_RIGHT;
+ m_inputs[START].sdlKey = SDLK_RETURN;
+ m_inputs[SELECT].sdlKey = SDLK_BACKSPACE;
+ m_inputs[A].sdlKey = SDLK_a;
+ m_inputs[B].sdlKey = SDLK_s;
+
+ pollInput(0);
+
+ using namespace std::placeholders;
+ core.registerPeriodic(
+ 32768, // 128Hz
+ 0,
+ std::bind(&glBoy::GameboyJoypad::pollInput, this, _1)
+ );
+}
+
+uint8_t
+GameboyJoypad::read8(uint16_t)
+{
+ uint8_t result(0xff);
+
+ switch (m_state)
+ {
+ case Joypad_Off:
+ result = 0xff;
+ break;
+
+ case Joypad_Directional:
+ result = m_directionalState;
+ break;
+
+ case Joypad_Button:
+ result = m_buttonState;
+ break;
+ };
+ return result;
+}
+
+
+void
+GameboyJoypad::write8(uint16_t, uint8_t value)
+{
+ switch (value)
+ {
+ case 0x20:
+ m_state = Joypad_Directional;
+ break;
+ case 0x10:
+ m_state = Joypad_Button;
+ break;
+ case 0x30:
+ m_state = Joypad_Off;
+ break;
+ default:
+ assert(false);
+ }
+}
+
+void
+GameboyJoypad::pollInput(Core::CallbackId)
+{
+ SDL_Event event;
+
+ while (SDL_PollEvent(&event))
+ {
+ switch(event.type)
+ {
+ case SDL_KEYUP:
+ case SDL_KEYDOWN:
+ {
+ for (int i = 0; i < 8; ++i)
+ {
+ if (event.key.keysym.sym == m_inputs[i].sdlKey)
+ {
+ m_inputs[i].depressed = (event.key.type == SDL_KEYDOWN);
+ break;
+ }
+ }
+ }; break;
+
+ case SDL_QUIT:
+ exit(0);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ m_directionalState = 0xff;
+ if (m_inputs[DOWN].depressed) m_directionalState ^= 0x8;
+ if (m_inputs[UP].depressed) m_directionalState ^= 0x4;
+ if (m_inputs[LEFT].depressed) m_directionalState ^= 0x2;
+ if (m_inputs[RIGHT].depressed) m_directionalState ^= 0x1;
+
+ m_buttonState = 0xff;
+ if (m_inputs[START].depressed) m_buttonState ^= 0x8;
+ if (m_inputs[SELECT].depressed) m_buttonState ^= 0x4;
+ if (m_inputs[B].depressed) m_buttonState ^= 0x2;
+ if (m_inputs[A].depressed) m_buttonState ^= 0x1;
+}
+
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of glBoy.
+//
+// glBoy is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// glBoy is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef GLBOY_GAMEBOYJOYPAD_HH
+#define GLBOY_GAMEBOYJOYPAD_HH
+
+#include "glBoy.hh"
+#include "Core.hh"
+
+#include "MemoryMap.hh"
+
+#include <SDL/SDL.h>
+
+namespace glBoy
+{
+
+class GameboyJoypad : public MemoryMap::Memory
+{
+public:
+ GameboyJoypad(Core& core);
+
+ void pollInput(Core::CallbackId id);
+
+ virtual uint8_t read8(uint16_t address);
+ virtual void write8(uint16_t address, uint8_t value);
+
+private:
+ enum State { Joypad_Off, Joypad_Directional, Joypad_Button };
+ State m_state;
+
+ struct Input
+ {
+ Input() : sdlKey(0), depressed(false) {}
+ int sdlKey;
+ bool depressed;
+ };
+
+ enum Inputs { UP = 0, DOWN, LEFT, RIGHT, A, B, START, SELECT };
+ Input m_inputs[8];
+
+ uint8_t m_buttonState;
+ uint8_t m_directionalState;
+};
+
+} // namespace glBoy
+
+#endif
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of glBoy.
+//
+// glBoy is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// glBoy is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
+
+#include "glBoy.hh"
+#include "GameboySound.hh"
+
+#include <cassert>
+#include <cstdlib>
+#include <cmath>
+
+using namespace glBoy;
+
+enum { SAMPLE_RATE = 48000 };
+
+extern "C"
+{
+ void mixaudio(void *userData, Uint8 *stream, int len)
+ {
+ GameboySound* ourObject(reinterpret_cast<GameboySound*>(userData));
+ ourObject->mixSounds(stream, len);
+ }
+}
+
+GameboySound::GameboySound(Core& core)
+{
+ extern void mixaudio(void *unused, Uint8 *stream, int len);
+ SDL_AudioSpec fmt;
+
+ fmt.freq = SAMPLE_RATE;
+ fmt.format = AUDIO_S16SYS;
+ fmt.channels = 1;
+ fmt.samples = 512;
+ fmt.callback = mixaudio;
+ fmt.userdata = this;
+
+ if ( SDL_OpenAudio(&fmt, NULL) < 0 )
+ {
+ assert(false);
+ }
+ SDL_PauseAudio(0);
+
+ using namespace std::placeholders;
+ core.registerPeriodic(
+ 32768, // 128 Hz
+ 0,
+ std::bind(&glBoy::GameboySound::periodicUpdate, this, _1)
+ );
+}
+
+uint8_t
+GameboySound::read8(uint16_t address)
+{
+ uint8_t result(0);
+
+ switch (address)
+ {
+ case (0x0):
+ result = m_channel1.sweep;
+ break;
+ case (0x1):
+ result = m_channel1.duty;
+ break;
+ case (0x2):
+ result = m_channel1.volume;
+ break;
+ case (0x3):
+ result = m_channel1.freqLow;
+ break;
+ case (0x4):
+ result = m_channel1.freqHigh;
+ break;
+
+ case (0x6):
+ result = m_channel2.duty;
+ break;
+ case (0x7):
+ result = m_channel2.volume;
+ break;
+ case (0x8):
+ result = m_channel2.freqLow;
+ break;
+ case (0x9):
+ result = m_channel2.freqHigh;
+ break;
+
+ case (0xA):
+ result = m_channel3.enabled;
+ break;
+ case (0xB):
+ result = m_channel3.length;
+ break;
+ case (0xC):
+ result = m_channel3.volume;
+ break;
+ case (0xD):
+ result = m_channel3.freqLow;
+ break;
+ case (0xE):
+ result = m_channel3.freqHigh;
+ break;
+
+ case (0x10):
+ result = m_channel4.length;
+ break;
+ case (0x11):
+ result = m_channel4.volume;
+ break;
+ case (0x12):
+ result = m_channel4.poly;
+ break;
+ case (0x13):
+ result = m_channel4.counter;
+ break;
+ // TODO REMOVE THIS AND IMPLEMENT EVERYTHING.
+ };
+
+ if (address >= 0x20 && address < 0x30)
+ {
+ result = m_channel3.waveTable[address - 0x20];
+ }
+ return result;
+}
+
+void
+GameboySound::write8(uint16_t address, uint8_t value)
+{
+ switch (address)
+ {
+ case (0x0):
+ m_channel1.sweep = value;
+ m_channel1.sweepDelay = 0;
+ m_channel1.sweepCount = 0;
+ break;
+ case (0x1):
+ m_channel1.duty = value;
+ break;
+ case (0x2):
+ m_channel1.volume = value;
+ break;
+ case (0x3):
+ m_channel1.freqLow = value;
+ break;
+ case (0x4):
+ m_channel1.freqHigh = value;
+
+ if (value & 0x80)
+ {
+ m_channel1.reset();
+ }
+ break;
+
+ case (0x6):
+ m_channel2.duty = value;
+ break;
+ case (0x7):
+ m_channel2.volume = value;
+ break;
+ case (0x8):
+ m_channel2.freqLow = value;
+ break;
+ case (0x9):
+ m_channel2.freqHigh = value;
+ if (value & 0x80)
+ {
+ m_channel2.reset();
+ }
+ break;
+
+ case (0xA):
+ m_channel3.enabled = value;
+ break;
+ case (0xB):
+ m_channel3.length= value;
+ break;
+ case (0xC):
+ m_channel3.volume = value;
+ break;
+ case (0xD):
+ m_channel3.freqLow = value;
+ break;
+ case (0xE):
+ m_channel3.freqHigh = value;
+ if (value & 0x80)
+ {
+ m_channel3.reset();
+ }
+ break;
+
+
+ case (0x10):
+ m_channel4.length= value;
+ break;
+ case (0x11):
+ m_channel4.volume = value;
+ break;
+ case (0x12):
+ m_channel4.poly= value;
+ break;
+ case (0x13):
+ m_channel4.counter = value;
+ if (value & 0x80)
+ {
+ m_channel4.reset();
+ }
+ break;
+
+ // TODO REMOVE THIS AND IMPLEMENT EVERYTHING.
+ };
+
+ if (address >= 0x20 && address < 0x30)
+ {
+ m_channel3.waveTable[address - 0x20] = value;
+ }
+}
+
+void GameboySound::periodicUpdate(Core::CallbackId)
+{
+ prepChannel(m_channel1);
+ prepChannel(m_channel2);
+ prepChannel3();
+ prepChannel4();
+}
+
+void
+GameboySound::mixSounds(Uint8 *stream, int len)
+{
+ for (int i = 0; i < len / 2; ++i)
+ {
+ int32_t val(0);
+ val += sample(m_channel1);
+ val += sample(m_channel2);
+ val += sampleChannel3();
+ val += sampleChannel4();
+ val >>= 2;
+ if (val > 32767) val = 32767;
+ if (val < -32768) val = -32768;
+
+//TODO ENDIAN FIAL HERE
+ stream[i * 2] = val & 0xff;
+ stream[i * 2 + 1] = val >> 8;
+ }
+
+}
+
+int16_t
+GameboySound::sample(SquareWave& channel)
+{
+ int16_t sample(0);
+
+ if (channel.currentVolume && channel.remainingLength > 0)
+ {
+ int pos = channel.squareStep / (channel.samplesPerDutyBit);
+
+ sample = channel.currentVolume * channel.currentVolume;
+ sample = sample * sample;
+ if (!((channel.dutyMask >> (7 - pos)) & 1))
+ {
+ sample = -sample;
+ }
+
+ ++channel.squareStep;
+ if (channel.squareStep > (channel.samplesPerDutyBit * 8))
+ {
+ channel.squareStep = 0;
+ }
+ }
+ return sample;
+}
+
+void
+GameboySound::prepChannel(SquareWave& channel)
+{
+ uint16_t freqCode(channel.freqHigh & 0x7);
+ freqCode <<= 8;
+ freqCode |= channel.freqLow;
+
+ double freq = 131072 / (2048.0 - freqCode);
+
+ if (channel.sweep & 0x3)
+ {
+ if (channel.sweepDelay >= (channel.sweep & 0x3))
+ {
+ channel.sweepDelay = 0;
+ ++channel.sweepCount;
+ }
+ else
+ {
+ ++channel.sweepDelay;
+ }
+
+ double freqMod = freq / (1 << channel.sweepCount);
+
+ if (channel.sweep & 0x8)
+ {
+ freq -= freqMod;
+ }
+ else
+ {
+ freq += freqMod;
+ }
+ }
+
+ if (channel.volume & 0x3)
+ {
+ // volumeDelay in 1/64 of a sec. x 2, as this callback occurs every
+ // 1/128sec.
+ if (channel.volumeDelay >= ((channel.volume & 0x3) * 2))
+ {
+ channel.volumeDelay = 0;
+ if (channel.volume & 0x8)
+ {
+ if (channel.currentVolume < 0xf)
+ {
+ ++channel.currentVolume;
+ }
+ }
+ else
+ {
+ if (channel.currentVolume != 0)
+ {
+ --channel.currentVolume;
+ }
+
+ }
+ }
+ else
+ {
+ ++channel.volumeDelay;
+ }
+ }
+
+ if (channel.freqHigh & 0x40)
+ {
+ if (channel.remainingLength > 0) --channel.remainingLength;
+ }
+
+ channel.samplesPerDutyBit = (SAMPLE_RATE/freq) / 8;
+}
+
+int16_t
+GameboySound::sampleChannel4()
+{
+ int16_t sample(0);
+
+ if (m_channel4.currentVolume && m_channel4.remainingLength > 0)
+ {
+ sample = m_channel4.currentVolume * m_channel4.currentVolume;
+ sample = sample * sample;
+ if (!m_channel4.high)
+ {
+ sample = -sample;
+ }
+
+ ++m_channel4.currentCounter;
+ if (m_channel4.currentCounter > m_channel4.samplesPerStep)
+ {
+ m_channel4.high = rand() & 0x1;
+ }
+ }
+ return sample;
+}
+
+
+void
+GameboySound::prepChannel4()
+{
+ Noise& channel(m_channel4);
+
+ if (channel.volume & 0x3)
+ {
+ // volumeDelay in 1/64 of a sec. x 2, as this callback occurs every
+ // 1/128sec.
+ if (channel.volumeDelay >= ((channel.volume & 0x3) * 2))
+ {
+ channel.volumeDelay = 0;
+ if (channel.volume & 0x8)
+ {
+ if (channel.currentVolume < 0xf)
+ {
+ ++channel.currentVolume;
+ }
+ }
+ else
+ {
+ if (channel.currentVolume != 0)
+ {
+ --channel.currentVolume;
+ }
+
+ }
+ }
+ else
+ {
+ ++channel.volumeDelay;
+ }
+ }
+
+ if (channel.counter & 0x40)
+ {
+ if (channel.remainingLength > 0) --channel.remainingLength;
+ }
+
+ double ratio = channel.poly & 0x3;
+ if (ratio == 0) ratio = 0.5;
+ double freq = 524288 / ratio / std::pow(2.0, (channel.poly >> 4) + 1);
+ channel.samplesPerStep = (SAMPLE_RATE/freq);
+}
+
+
+void
+GameboySound::prepChannel3()
+{
+ uint16_t freqCode(m_channel3.freqHigh & 0x7);
+ freqCode <<= 8;
+ freqCode |= m_channel3.freqLow;
+
+ double freq = 65536 / (2048.0 - freqCode);
+
+ if (m_channel3.freqHigh & 0x40)
+ {
+ if (m_channel3.remainingLength > 0) --m_channel3.remainingLength;
+ }
+
+ m_channel3.samplesPerNibble = (SAMPLE_RATE/freq) / 32;
+}
+
+int16_t
+GameboySound::sampleChannel3()
+{
+ int16_t sample(0);
+
+ if (m_channel3.remainingLength > 0 &&
+ (m_channel3.enabled & 0x80)
+ )
+ {
+ int volume((m_channel3.volume >> 5) & 0x3);
+
+ uint8_t nibble = m_channel3.waveTable[m_channel3.wavePos / 2];
+
+ if (m_channel3.wavePos & 0x1)
+ {
+ nibble &= 0xf;
+ }
+ else
+ {
+ nibble >>= 4;
+ }
+
+ if (volume == 0)
+ {
+ nibble = 0;
+ }
+ else if (volume == 2)
+ {
+ nibble >>= 1;
+ }
+ else if (volume == 3)
+ {
+ nibble >>= 3;
+ }
+
+ // Ok, nibble is now between 0 and 0xf. Map this to full range.
+ sample = (static_cast<int16_t>(nibble) - 8) * 4096;
+
+ ++m_channel3.waveSamples;
+ if (m_channel3.waveSamples > m_channel3.samplesPerNibble * (m_channel3.wavePos + 1))
+ {
+ m_channel3.wavePos++;
+ if (m_channel3.wavePos >= 32)
+ {
+ m_channel3.waveSamples = 0;
+ m_channel3.wavePos = 0;
+ }
+ }
+
+ }
+ return sample;
+}
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of glBoy.
+//
+// glBoy is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// glBoy is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef GLBOY_GAMEBOYSOUND_HH
+#define GLBOY_GAMEBOYSOUND_HH
+
+#include "glBoy.hh"
+#include "Core.hh"
+
+#include "MemoryMap.hh"
+
+#include <SDL/SDL.h>
+
+namespace glBoy
+{
+
+class GameboySound : public MemoryMap::Memory
+{
+public:
+ GameboySound(Core& core);
+
+ void periodicUpdate(Core::CallbackId id);
+
+ virtual uint8_t read8(uint16_t address);
+ virtual void write8(uint16_t address, uint8_t value);
+
+ void mixSounds(Uint8* stream, int len);
+private:
+ int16_t getSample(uint8_t volume);
+
+ struct SquareWave
+ {
+ SquareWave() : sweep(0), duty(0), volume(0), freqLow(0), freqHigh(0)
+ {
+ reset();
+ }
+
+ void reset()
+ {
+ sweepDelay = 0;
+ sweepCount = 0;
+ volumeDelay = 0;
+ currentVolume = volume >> 4;
+ squareStep = 0;
+
+ switch (duty >> 6)
+ {
+ case 0: dutyMask = 0x8; break;
+ case 1: dutyMask = 0xC0; break;
+ case 2: dutyMask = 0xF0; break;
+ case 3: dutyMask = 0xFC; break;
+ };
+
+ // x 2, since callback @ 128Hz, not 256Hz.
+ if (freqHigh & 0x40)
+ {
+ // Add 1, as we immediately decrement in prep method
+ // without actually playing anything.
+ remainingLength = ((64 - (duty & 0x3f)) / 2) + 1;
+ }
+ else
+ {
+ remainingLength = 255;
+ }
+ }
+ // Regs.
+ uint8_t sweep;
+ uint8_t duty;
+ uint8_t volume;
+ uint8_t freqLow;
+ uint8_t freqHigh;
+
+ // Runtime
+ int sweepDelay;
+ uint32_t sweepCount;
+
+ int volumeDelay;
+ int currentVolume;
+
+ uint8_t dutyMask;
+ double samplesPerDutyBit;
+ int squareStep;
+
+ int remainingLength;
+ };
+ int16_t sample(SquareWave& channel);
+ void prepChannel(SquareWave& channel);
+
+ SquareWave m_channel1;
+ SquareWave m_channel2;
+
+ struct Noise
+ {
+ Noise() : length(0), volume(0), poly(0), counter(0) {reset(); }
+ uint8_t length;
+ uint8_t volume;
+ uint8_t poly;
+ uint8_t counter;
+
+ int currentCounter;
+ int samplesPerStep;
+ int volumeDelay;
+ int currentVolume;
+
+ int remainingLength;
+
+ bool high;
+
+ void reset()
+ {
+ volumeDelay = 0;
+ currentVolume = volume >> 4;
+
+ currentCounter = 0;
+
+ remainingLength = (64 - (length & 0x3f)) / 2;
+
+ high = false;
+ }
+ };
+
+ Noise m_channel4;
+ int16_t sampleChannel4();
+ void prepChannel4();
+
+ struct Wave
+ {
+ Wave() : enabled(0), length(0), volume(0), freqLow(0), freqHigh(0)
+ {
+ reset();
+ }
+
+ void reset()
+ {
+ wavePos = 0;
+ waveSamples = 0;
+
+ // x 2, since callback @ 128Hz, not 256Hz.
+ if (freqHigh & 0x40)
+ {
+ // Add 1, as we immediately decrement in prep method
+ // without actually playing anything.
+ remainingLength = ((256 - length) / 2) + 1;
+ }
+ else
+ {
+ remainingLength = 255;
+ }
+ }
+ // Regs.
+ uint8_t enabled;
+ uint8_t length;
+ uint8_t volume;
+ uint8_t freqLow;
+ uint8_t freqHigh;
+
+ uint8_t waveTable[16];
+
+ // Runtime
+ int wavePos;
+ int waveSamples;
+ int remainingLength;
+ float samplesPerNibble;
+ };
+
+ Wave m_channel3;
+ int16_t sampleChannel3();
+ void prepChannel3();
+
+ uint8_t m_channelControl;
+ uint8_t m_outputTerminal;
+};
+
+} // namespace glBoy
+
+#endif
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of glBoy.
+//
+// glBoy is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// glBoy is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
+
+#include "glBoy.hh"
+#include "GameboyTimer.hh"
+#include "Log.hh"
+
+#include <algorithm>
+#include <cassert>
+
+#include <time.h>
+
+using namespace glBoy;
+
+GameboyTimer::GameboyTimer(
+ Core& core,
+ const std::function<void(void)>& setFrameSkip,
+ const std::function<void(void)>& clearFrameSkip
+ ) :
+ m_core(core),
+ m_setSkipFrame(setFrameSkip),
+ m_clearSkipFrame(clearFrameSkip)
+{
+ memset(m_reg, 0, 4);
+
+ using namespace std::placeholders;
+ core.registerPeriodic(
+ 256,
+ 0,
+ std::bind(&glBoy::GameboyTimer::tick, this, _1)
+ );
+
+ timespec res;
+ clock_getres(CLOCK_REALTIME, &res);
+
+ // Make sure we can perform high-resolution sleeps
+// TODO resolve this
+// meh. high resolution not all that necessary.
+ m_clockSleepDelay = std::min(res.tv_nsec / 60000, 10l);
+ //m_clockSleepDelay = res.tv_nsec / 60000;
+ m_clockSleepCounter = m_clockSleepDelay;
+
+/*
+ if (m_clockSleepDelay)
+ {
+ Log::Instance()(Log::DBG_WARNING) <<
+ "Timer delay set to " << m_clockSleepDelay <<
+ " nanoseconds. Enable HR timers for improved emulation" << std::endl;
+ }
+*/
+
+ clock_gettime(CLOCK_REALTIME, &m_lastSlept);
+}
+
+// Call this every 256 clock cycles. (16384Hz)
+
+void
+GameboyTimer::tick(Core::CallbackId)
+{
+ ++m_reg[DIV];
+
+ if (m_reg[TAC] & 0x4) // Timer enabled
+ {
+ int counter(m_reg[TIMA]);
+ switch (m_reg[TAC] & 0x3)
+ {
+ case 0: // 4096Hz
+ if (m_reg[DIV] % 4 == 0) ++counter;
+ break;
+ case 1: // 268400Hz
+ counter += 16;
+ break;
+ case 2: // 65536Hz
+ counter += 4;
+ break;
+ case 3: // 16384Hz
+ ++counter;
+ break;
+ }
+
+ if (counter > 255)
+ {
+ m_reg[TIMA] = m_reg[TMA];
+ m_core.raiseInterrupt(0x50);
+ }
+ else
+ {
+ m_reg[TIMA] = counter;
+ }
+ }
+
+ m_lastSlept.tv_nsec += 61035;
+ if (m_lastSlept.tv_nsec > 1e9)
+ {
+ m_lastSlept.tv_nsec -= 1e9;
+ m_lastSlept.tv_sec++;
+ }
+
+ if (m_clockSleepCounter == 0)
+ {
+ timespec now;
+ clock_gettime(CLOCK_REALTIME, &now);
+ if (
+ (m_lastSlept.tv_sec > now.tv_sec) ||
+ (
+ (m_lastSlept.tv_sec == now.tv_sec) &&
+ (m_lastSlept.tv_nsec > now.tv_nsec)
+ )
+ )
+ {
+ m_clearSkipFrame();
+ clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &m_lastSlept, NULL);
+ }
+ else
+ {
+ m_setSkipFrame();
+ }
+ m_clockSleepCounter = m_clockSleepDelay;
+ }
+ else
+ {
+ m_clockSleepCounter--;
+ }
+
+}
+
+uint8_t
+GameboyTimer::read8(uint16_t address)
+{
+ return m_reg[address];
+}
+
+void
+GameboyTimer::write8(uint16_t address, uint8_t value)
+{
+ if (!address)
+ {
+ // Writing any value to DIV resets it to zero.
+ m_reg[DIV] = 0;
+ }
+ else
+ {
+ m_reg[address] = value;
+ }
+}
+
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of glBoy.
+//
+// glBoy is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// glBoy is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef GLBOY_GAMEBOYTIMER_HH
+#define GLBOY_GAMEBOYTIMER_HH
+
+#include "glBoy.hh"
+#include "Core.hh"
+
+#include "MemoryMap.hh"
+
+#include <time.h>
+
+#include <SDL/SDL.h>
+
+namespace glBoy
+{
+
+class GameboyTimer : public MemoryMap::Memory
+{
+public:
+ GameboyTimer(
+ Core& core,
+ const std::function<void(void)>& setFrameSkip,
+ const std::function<void(void)>& clearFrameSkip
+ );
+
+ void tick(Core::CallbackId id);
+
+ virtual uint8_t read8(uint16_t address);
+ virtual void write8(uint16_t address, uint8_t value);
+
+private:
+ enum Reg { DIV = 0, TIMA, TMA, TAC };
+
+ Core& m_core;
+ std::function<void(void)> m_setSkipFrame;
+ std::function<void(void)> m_clearSkipFrame;
+
+ uint8_t m_reg[4];
+
+ timespec m_lastSlept;
+
+ int m_clockSleepDelay;
+ int m_clockSleepCounter;
+};
+
+} // namespace glBoy
+
+#endif
<?xml version="1.0" encoding="UTF-8" ?>\r
<opcodes>\r
<!--\r
- Z80 Core Emulation\r
- 8 bit LD instructions\r
- Copyright 2010, Michael McMaster <email@michaelmcmaster.name>\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>\r
+//\r
+// This file is part of glBoy.\r
+//\r
+// glBoy is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// glBoy is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.\r
-->\r
\r
-code gen expectations:\r
-masks result in each instruction being unrolled and the mask name being replaced\r
-with an actual register\r
-operand = op8();\r
-operand="UPPERCASE" is 16bit operand. (first 8 bits = low bytes) N = op16()\r
-\r
-<registerMask name="r" bits="3">\r
- <reg name="A" mask="111">\r
- <reg name="B" mask="000">\r
- <reg name="C" mask="001">\r
- <reg name="D" mask="010">\r
- <reg name="E" mask="011">\r
- <reg name="H" mask="100">\r
- <reg name="L" mask="101">\r
+<registerMask name="r" maskBits="3" regBytes="1" >\r
+ <reg name="A" mask="111" />\r
+ <reg name="B" mask="000" />\r
+ <reg name="C" mask="001" />\r
+ <reg name="D" mask="010" />\r
+ <reg name="E" mask="011" />\r
+ <reg name="H" mask="100" />\r
+ <reg name="L" mask="101" />\r
</registerMask>\r
\r
-<registerMask name="d" alias="s" bits="2">\r
- <reg name="BC" mask="00">\r
- <reg name="DE" mask="01">\r
- <reg name="HL" mask="10">\r
- <reg name="SP" mask="11">\r
+<registerMask name="d" maskBits="2" regBytes="2" >\r
+ <reg name="BC" mask="00" />\r
+ <reg name="DE" mask="01" />\r
+ <reg name="HL" mask="10" />\r
+ <reg name="SP" mask="11" />\r
</registerMask>\r
\r
-<registerMask name="q" bits="2">\r
- <reg name="BC" mask="00">\r
- <reg name="DE" mask="01">\r
- <reg name="HL" mask="10">\r
- <reg name="AF" mask="11">\r
+<registerMask name="s" maskBits="2" regBytes="2" >\r
+ <reg name="BC" mask="00" />\r
+ <reg name="DE" mask="01" />\r
+ <reg name="HL" mask="10" />\r
+ <reg name="SP" mask="11" />\r
</registerMask>\r
\r
-<registerMask name="p" bits="2">\r
- <reg name="BC" mask="00">\r
- <reg name="DE" mask="01">\r
- <reg name="IX" mask="10">\r
- <reg name="SP" mask="11">\r
+<registerMask name="q" maskBits="2" regBytes="2" >\r
+ <reg name="BC" mask="00" />\r
+ <reg name="DE" mask="01" />\r
+ <reg name="HL" mask="10" />\r
+ <reg name="AF" mask="11" />\r
</registerMask>\r
\r
-<registerMask name="R" bits="2">\r
- <reg name="BC" mask="00">\r
- <reg name="DE" mask="01">\r
- <reg name="IY" mask="10">\r
- <reg name="SP" mask="11">\r
+<registerMask name="p" maskBits="2" regBytes="2" variant="z80">\r
+ <reg name="BC" mask="00" />\r
+ <reg name="DE" mask="01" />\r
+ <reg name="IX" mask="10" />\r
+ <reg name="SP" mask="11" />\r
</registerMask>\r
\r
+<registerMask name="R" maskBits="2" regBytes="2" variant="z80">\r
+ <reg name="BC" mask="00" />\r
+ <reg name="DE" mask="01" />\r
+ <reg name="IY" mask="10" />\r
+ <reg name="SP" mask="11" />\r
+</registerMask>\r
+\r
+<bitMask name="b" maskBits="3"/>\r
+\r
+<!--\r
+ 8-BIT LD Instructions\r
+-->\r
+\r
<instruction name="LD r,r'" clock="4">\r
<opcode mask="01rr" />\r
-r = r2;\r
+ r = r2;\r
</instruction>\r
\r
<instruction name="LD r,n" clock="7">\r
<opcode mask="00r110" operand="n"/>\r
-r = n;\r
+ r = n;\r
</instruction>\r
\r
<instruction name="LD r,(HL)" clock="7">\r
<opcode mask="01r110" />\r
-r = m_mem.read8(m_reg.HL);\r
+ r = m_mem.read8(m_reg.HL);\r
</instruction>\r
\r
<instruction name="LD r,(IX+d)" clock="19">\r
-<opcode prefix="0xDD" mask="01r110" operand="d"/>\r
-r = m_mem.read8(m_reg.IX, d);\r
+<opcode prefix="0xDD" mask="01r110" signed_operand="d"/>\r
+ r = m_mem.read8(m_reg.IX, d);\r
</instruction>\r
\r
<instruction name="LD r,(IY+d)" clock="19">\r
-<opcode prefix="0xFD" mask="01r110" operand="d"/>\r
-r = m_mem.read8(m_reg.IY, d);\r
+<opcode prefix="0xFD" mask="01r110" signed_operand="d"/>\r
+ r = m_mem.read8(m_reg.IY, d);\r
</instruction>\r
\r
<instruction name="LD (HL),r" clock="7">\r
<opcode mask="01110r"/>\r
-m_mem.write8(m_reg.HL, r);\r
+ m_mem.write8(m_reg.HL, r);\r
</instruction>\r
\r
<instruction name="LD (IX+d),r" clock="19">\r
-<opcode prefix="0xDD" mask="01110r" operand="d"/>\r
-m_mem.write8(m_reg.IX, d, r);\r
+<opcode prefix="0xDD" mask="01110r" signed_operand="d"/>\r
+ m_mem.write8(m_reg.IX, d, r);\r
</instruction>\r
\r
<instruction name="LD (IY+d),r" clock="19">\r
-<opcode prefix="0xFD" mask="01110r" operand="d"/>\r
-m_mem.write8(m_reg.IY, d, r);\r
+<opcode prefix="0xFD" mask="01110r" signed_operand="d"/>\r
+ m_mem.write8(m_reg.IY, d, r);\r
</instruction>\r
\r
<instruction name="LD (HL),n" clock="10">\r
<opcode mask="00110110" operand="n"/>\r
-m_mem.write8(m_reg.HL, n);\r
+ m_mem.write8(m_reg.HL, n);\r
</instruction>\r
\r
-<instruction name="LD (IX+d),r" clock="19">\r
-<opcode prefix="0xDD" mask="00110110" operand="dn"/>\r
-m_mem.write8(m_reg.IX, d, n);\r
+<instruction name="LD (IX+d),n" clock="19">\r
+<opcode prefix="0xDD" mask="00110110" signed_operand="d" operand="n"/>\r
+ m_mem.write8(m_reg.IX, d, n);\r
</instruction>\r
\r
<instruction name="LD (IY+d),n" clock="19">\r
-<opcode prefix="0xFD" mask="00110110" operand="dn"/>\r
-m_mem.write8(m_reg.IY, d, n);\r
+<opcode prefix="0xFD" mask="00110110" signed_operand="d" operand="n"/>\r
+ m_mem.write8(m_reg.IY, d, n);\r
</instruction>\r
\r
<instruction name="LD A,(BC)" clock="7">\r
<opcode mask="00001010"/>\r
-m_reg.A = m_mem.read8(m_reg.BC);\r
+ m_reg.A = m_mem.read8(m_reg.BC);\r
</instruction>\r
\r
<instruction name="LD A,(DE)" clock="7">\r
<opcode mask="00011010"/>\r
-m_reg.A = m_mem.read8(m_reg.DE);\r
+ m_reg.A = m_mem.read8(m_reg.DE);\r
</instruction>\r
\r
-<instruction name="LD A,(nn)" clock="13">\r
+<instruction name="LD A,(nn)" clock="13" variant="z80">\r
<opcode mask="00111010" operand="N"/>\r
-m_reg.A = m_mem.read8(N);\r
+ m_reg.A = m_mem.read8(N);\r
</instruction>\r
\r
<instruction name="LD (BC),A" clock="7">\r
<opcode mask="00000010"/>\r
-m_mem.write8(m_reg.BC, m_reg.A);\r
+ m_mem.write8(m_reg.BC, m_reg.A);\r
</instruction>\r
\r
<instruction name="LD (DE),A" clock="7">\r
<opcode mask="00010010"/>\r
-m_mem.write8(m_reg.DE, m_reg.A);\r
+ m_mem.write8(m_reg.DE, m_reg.A);\r
</instruction>\r
\r
-<instruction name="LD (nn),A" clock="13">\r
+<instruction name="LD (nn),A" clock="13" variant="z80">\r
<opcode mask="00110010" operand="N"/>\r
-m_mem.write8(N, m_reg.A);\r
+ m_mem.write8(N, m_reg.A);\r
</instruction>\r
\r
<instruction name="LD A,I" clock="9">\r
<opcode prefix="0xED" mask="01010111"/>\r
-m_reg.F.set(m_reg.I, *this);\r
-m_reg.A = m_reg.I;\r
+ m_reg.F.set(m_reg.I, *this);\r
+ m_reg.A = m_reg.I;\r
</instruction>\r
\r
<instruction name="LD A,R" clock="9">\r
<opcode prefix="0xED" mask="01011111"/>\r
-m_reg.F.set(m_reg.R, *this);\r
-m_reg.A = m_reg.R;\r
+ m_reg.F.set(m_reg.R, *this);\r
+ m_reg.A = m_reg.R;\r
</instruction>\r
\r
<instruction name="LD I,A" clock="9">\r
<opcode prefix="0xED" mask="01000111"/>\r
-m_reg.I = m_reg.A;\r
+ m_reg.I = m_reg.A;\r
</instruction>\r
\r
<instruction name="LD R,A" clock="9">\r
<opcode prefix="0xED" mask="01001111"/>\r
-m_reg.R = m_reg.A;\r
+ m_reg.R = m_reg.A;\r
+</instruction>\r
+\r
+<!--\r
+ 16-BIT LD Instructions\r
+-->\r
+\r
+<instruction name="LD dd,nn" clock="10">\r
+<opcode mask="00d0001" operand="N" />\r
+ d = N;\r
+</instruction>\r
+\r
+<instruction name="LD IX,nn" clock="14">\r
+<opcode prefix="0xDD" mask="00100001" operand="N" />\r
+ m_reg.IX = N;\r
+</instruction>\r
+\r
+<instruction name="LD IY,nn" clock="14">\r
+<opcode prefix="0xFD" mask="00100001" operand="N" />\r
+ m_reg.IY = N;\r
+</instruction>\r
+\r
+<instruction name="LD HL,(nn)" clock="16" variant="z80" >\r
+<opcode mask="00101010" operand="N" />\r
+ m_reg.HL = m_mem.read16(N);\r
+</instruction>\r
+\r
+<instruction name="LD dd,(nn)" clock="20">\r
+<opcode prefix="0xED" mask="01d1011" operand="N" />\r
+ d = m_mem.read16(N);\r
+</instruction>\r
+\r
+<instruction name="LD IX,(nn)" clock="20">\r
+<opcode prefix="0xDD" mask="00101010" operand="N" />\r
+ m_reg.IX = m_mem.read16(N);\r
+</instruction>\r
+\r
+<instruction name="LD IY,(nn)" clock="20">\r
+<opcode prefix="0xFD" mask="00101010" operand="N" />\r
+ m_reg.IY = m_mem.read16(N);\r
+</instruction>\r
+\r
+<instruction name="LD (nn),HL" clock="16" variant="z80">\r
+<opcode mask="00100010" operand="N" />\r
+ m_mem.write16(N, m_reg.HL);\r
+</instruction>\r
+\r
+<instruction name="LD (nn),dd" clock="20">\r
+<opcode prefix="0xED" mask="01d0011" operand="N" />\r
+ m_mem.write16(N, d);\r
+</instruction>\r
+\r
+<instruction name="LD (nn),IX" clock="20">\r
+<opcode prefix="0xDD" mask="00100010" operand="N" />\r
+ m_mem.write16(N, m_reg.IX);\r
+</instruction>\r
+\r
+<instruction name="LD (nn),IY" clock="20">\r
+<opcode prefix="0xFD" mask="00100010" operand="N" />\r
+ m_mem.write16(N, m_reg.IY);\r
+</instruction>\r
+\r
+\r
+<instruction name="LD SP,HL" clock="6">\r
+<opcode mask="11111001" />\r
+ m_reg.SP = m_reg.HL;\r
+</instruction>\r
+\r
+<instruction name="LD SP,IX" clock="10">\r
+<opcode prefix="0xDD" mask="11111001" />\r
+ m_reg.SP = m_reg.IX;\r
+</instruction>\r
+\r
+<instruction name="LD SP,IY" clock="10">\r
+<opcode prefix="0xFD" mask="11111001" />\r
+ m_reg.SP = m_reg.IY;\r
+</instruction>\r
+\r
+<instruction name="push qq" clock="11">\r
+<opcode mask="11q0101" />\r
+ m_reg.SP.dec(2);\r
+ m_mem.write16(m_reg.SP, q);\r
+</instruction>\r
+\r
+<instruction name="push IX" clock="15">\r
+<opcode prefix="0xDD" mask="11100101" />\r
+ m_reg.SP.dec(2);\r
+ m_mem.write16(m_reg.SP, m_reg.IX);\r
+</instruction>\r
+\r
+<instruction name="push IY" clock="15">\r
+<opcode prefix="0xFD" mask="11100101" />\r
+ m_reg.SP.dec(2);\r
+ m_mem.write16(m_reg.SP, m_reg.IY);\r
+</instruction>\r
+\r
+<instruction name="pop qq" clock="10">\r
+<opcode mask="11q0001" />\r
+ q = m_mem.read16(m_reg.SP);\r
+ m_reg.SP.inc(2);\r
+</instruction>\r
+\r
+<instruction name="pop IX" clock="14">\r
+<opcode prefix="0xDD" mask="11100001" />\r
+ m_reg.IX = m_mem.read16(m_reg.SP);\r
+ m_reg.SP.inc(2);\r
+</instruction>\r
+\r
+<instruction name="pop IY" clock="14">\r
+<opcode prefix="0xDD" mask="11111101" />\r
+ m_reg.IY = m_mem.read16(m_reg.SP);\r
+ m_reg.SP.inc(2);;\r
+</instruction>\r
+\r
+\r
+<!--\r
+ Exchange, block transfer, and search\r
+-->\r
+\r
+<instruction name="EX DE,HL" clock="4" variant="z80">\r
+<opcode mask="11101011" />\r
+ DWORD tmp(m_reg.DE);\r
+ m_reg.DE = m_reg.HL;\r
+ m_reg.HL = tmp;\r
+</instruction>\r
+\r
+<instruction name="EX AF,AF'" clock="4" variant="z80">\r
+<opcode mask="00001000" />\r
+ DWORD tmp(m_reg.AF);\r
+ m_reg.AF = m_regDash.AF;\r
+ m_regDash.AF = tmp;\r
+</instruction>\r
+\r
+<instruction name="EXX" clock="4" variant="z80">\r
+<opcode mask="11011001" />\r
+ DWORD tmpBC(m_reg.BC);\r
+ DWORD tmpDE(m_reg.BC);\r
+ DWORD tmpHL(m_reg.BC);\r
+ m_reg.BC = m_regDash.BC;\r
+ m_reg.DE = m_regDash.DE;\r
+ m_reg.HL = m_regDash.HL;\r
+ m_regDash.BC = tmpBC;\r
+ m_regDash.DE = tmpDE;\r
+ m_regDash.HL = tmpHL;\r
+</instruction>\r
+\r
+<instruction name="EX (SP),HL" clock="19" variant="z80">\r
+<opcode mask="11100011" />\r
+ DWORD tmp(m_reg.HL);\r
+ m_reg.HL = m_mem.read16(m_reg.SP);\r
+ m_mem.write16(m_reg.SP, tmp);\r
+</instruction>\r
+\r
+<instruction name="EX (SP),IX" clock="23">\r
+<opcode prefix="0xDD" mask="11100011" />\r
+ DWORD tmp(m_reg.IX);\r
+ m_reg.IX = m_mem.read16(m_reg.SP);\r
+ m_mem.write16(m_reg.SP, tmp);\r
+</instruction>\r
+\r
+<instruction name="EX (SP),IY" clock="23">\r
+<opcode prefix="0xFD" mask="11100011" />\r
+ DWORD tmp(m_reg.IY);\r
+ m_reg.IY = m_mem.read16(m_reg.SP);\r
+ m_mem.write16(m_reg.SP, tmp);\r
+</instruction>\r
+\r
+<instruction name="LDI" clock="16">\r
+<opcode prefix="0xED" mask="10100000" />\r
+ m_mem.write8(m_reg.DE, m_mem.read8(m_reg.HL));\r
+ m_reg.DE.inc();\r
+ m_reg.HL.inc();\r
+ m_reg.BC.dec();\r
+ m_reg.F.setCounter(m_reg.BC);\r
+</instruction>\r
+\r
+<instruction name="LDIR" clock="0">\r
+<opcode prefix="0xED" mask="10110000" />\r
+ do\r
+ {\r
+ m_mem.write8(m_reg.DE, m_mem.read8(m_reg.HL));\r
+ m_reg.DE.inc();\r
+ m_reg.HL.inc();\r
+ m_reg.BC.dec();\r
+\r
+ clock += 21; // Increment for each loop iteration\r
+ // TODO interrupt(); // Check for interrupts at each iteration\r
+ } while (m_reg.BC.host() != 0);\r
+ m_reg.F.setCounter(m_reg.BC);\r
+ clock += 16; // Increment again when leaving loop.\r
+</instruction>\r
+\r
+<instruction name="LDD" clock="16">\r
+<opcode prefix="0xED" mask="10101000" />\r
+ m_mem.write8(m_reg.DE, m_mem.read8(m_reg.HL));\r
+ m_reg.DE.dec();\r
+ m_reg.HL.dec();\r
+ m_reg.BC.dec();\r
+ m_reg.F.setCounter(m_reg.BC);\r
+</instruction>\r
+\r
+<instruction name="LDDR" clock="0">\r
+<opcode prefix="0xED" mask="10111000" />\r
+ do\r
+ {\r
+ m_mem.write8(m_reg.DE, m_mem.read8(m_reg.HL));\r
+ m_reg.DE.dec();\r
+ m_reg.HL.dec();\r
+ m_reg.BC.dec();\r
+\r
+ clock += 21; // Increment for each loop iteration\r
+ // TODO interrupt(); // Check for interrupts at each iteration\r
+ } while (m_reg.BC.host() != 0);\r
+ m_reg.F.setCounter(m_reg.BC);\r
+ clock += 16; // Increment again when leaving loop.\r
+</instruction>\r
+\r
+<instruction name="CPI" clock="16">\r
+<opcode prefix="0xED" mask="10100001" />\r
+ m_reg.BC.dec();\r
+ m_reg.F.setSub(m_reg.A, m_mem.read8(m_reg.HL), m_reg.BC);\r
+ m_reg.HL.inc();\r
+</instruction>\r
+\r
+<instruction name="CPIR" clock="0">\r
+<opcode prefix="0xED" mask="10110001" />\r
+ bool equal;\r
+ do\r
+ {\r
+ m_reg.BC.dec();\r
+ m_reg.F.setSub(m_reg.A, m_mem.read8(m_reg.HL), m_reg.BC);\r
+ equal = (m_reg.A == m_mem.read8(m_reg.HL));\r
+ m_reg.HL.inc();\r
+\r
+ clock += 21; // Increment for each loop iteration\r
+ // TODO interrupt(); // Check for interrupts at each iteration\r
+ } while (m_reg.BC.host() != 0 && !equal);\r
+ clock += 16; // Increment again when leaving loop.\r
+\r
+</instruction>\r
+\r
+<instruction name="CPD" clock="16">\r
+<opcode prefix="0xED" mask="10101001" />\r
+ m_reg.BC.dec();\r
+ m_reg.F.setSub(m_reg.A, m_mem.read8(m_reg.HL), m_reg.BC);\r
+ m_reg.HL.dec();\r
+</instruction>\r
+\r
+<instruction name="CPDR" clock="0">\r
+<opcode prefix="0xED" mask="10111001" />\r
+ bool equal;\r
+ do\r
+ {\r
+ m_reg.BC.dec();\r
+ m_reg.F.setSub(m_reg.A, m_mem.read8(m_reg.HL), m_reg.BC);\r
+ equal = (m_reg.A == m_mem.read8(m_reg.HL));\r
+ m_reg.HL.dec();\r
+\r
+ clock += 21; // Increment for each loop iteration\r
+ // TODO interrupt(); // Check for interrupts at each iteration\r
+ } while (m_reg.BC.host() != 0 && !equal);\r
+ clock += 16; // Increment again when leaving loop.\r
+\r
+</instruction>\r
+\r
+<!--\r
+ 8-bit arithmetic\r
+-->\r
+<instruction name="ADD A,r" clock="4">\r
+<opcode mask="10000r" />\r
+ m_reg.A = ALU::add(m_reg.A, r, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="ADD A,n" clock="7">\r
+<opcode mask="11000110" operand="n" />\r
+ m_reg.A = ALU::add(m_reg.A, n, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="ADD A,(HL)" clock="7">\r
+<opcode mask="10000110" />\r
+ m_reg.A = ALU::add(m_reg.A, m_mem.read8(m_reg.HL), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="ADD A,(IX+d)" clock="19">\r
+<opcode prefix="0xDD" mask="11011101" signed_operand="d" />\r
+ m_reg.A = ALU::add(m_reg.A, m_mem.read8(m_reg.IX, d), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="ADD A,(IY+d)" clock="19">\r
+<opcode prefix="0xFD" mask="10000110" signed_operand="d" />\r
+ m_reg.A = ALU::add(m_reg.A, m_mem.read8(m_reg.IY, d), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="ADC A,r" clock="4">\r
+<opcode mask="10001r" />\r
+ m_reg.A = ALU::adc(m_reg.A, r, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="ADC A,n" clock="7">\r
+<opcode mask="11001110" operand="n" />\r
+ m_reg.A = ALU::adc(m_reg.A, n, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="ADC A,(HL)" clock="7">\r
+<opcode mask="10001110" />\r
+ m_reg.A = ALU::adc(m_reg.A, m_mem.read8(m_reg.HL), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="ADC A,(IX+d)" clock="19">\r
+<opcode prefix="0xDD" mask="10001110" signed_operand="d" />\r
+ m_reg.A = ALU::adc(m_reg.A, m_mem.read8(m_reg.IX, d), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="ADC A,(IY+d)" clock="19">\r
+<opcode prefix="0xFD" mask="10001110" signed_operand="d" />\r
+ m_reg.A = ALU::adc(m_reg.A, m_mem.read8(m_reg.IY, d), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="SUB A,r" clock="4">\r
+<opcode mask="10010r" />\r
+ m_reg.A = ALU::sub(m_reg.A, r, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="SUB A,n" clock="7">\r
+<opcode mask="11010110" operand="n" />\r
+ m_reg.A = ALU::sub(m_reg.A, n, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="SUB A,(HL)" clock="7">\r
+<opcode mask="10010110" />\r
+ m_reg.A = ALU::sub(m_reg.A, m_mem.read8(m_reg.HL), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="SUB A,(IX+d)" clock="19">\r
+<opcode prefix="0xDD" mask="10010110" signed_operand="d" />\r
+ m_reg.A = ALU::sub(m_reg.A, m_mem.read8(m_reg.IX, d), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="SUB A,(IY+d)" clock="19">\r
+<opcode prefix="0xFD" mask="10010110" signed_operand="d" />\r
+ m_reg.A = ALU::sub(m_reg.A, m_mem.read8(m_reg.IY, d), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="SBC A,r" clock="4">\r
+<opcode mask="10011r" />\r
+ m_reg.A = ALU::sbc(m_reg.A, r, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="SBC A,n" clock="7">\r
+<opcode mask="11011110" operand="n" />\r
+ m_reg.A = ALU::sbc(m_reg.A, n, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="SBC A,(HL)" clock="7">\r
+<opcode mask="10011110" />\r
+ m_reg.A = ALU::sbc(m_reg.A, m_mem.read8(m_reg.HL), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="SBC A,(IX+d)" clock="19">\r
+<opcode prefix="0xDD" mask="10011110" signed_operand="d" />\r
+ m_reg.A = ALU::sbc(m_reg.A, m_mem.read8(m_reg.IX, d), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="SBC A,(IY+d)" clock="19">\r
+<opcode prefix="0xFD" mask="10011110" signed_operand="d" />\r
+ m_reg.A = ALU::sbc(m_reg.A, m_mem.read8(m_reg.IY, d), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="AND A,r" clock="4">\r
+<opcode mask="10100r" />\r
+ m_reg.A = ALU::bitwiseAnd(m_reg.A, r, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="AND A,n" clock="7">\r
+<opcode mask="11100110" operand="n" />\r
+ m_reg.A = ALU::bitwiseAnd(m_reg.A, n, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="AND A,(HL)" clock="7">\r
+<opcode mask="10100110" />\r
+ m_reg.A = ALU::bitwiseAnd(m_reg.A, m_mem.read8(m_reg.HL), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="AND A,(IX+d)" clock="19">\r
+<opcode prefix="0xDD" mask="10100110" signed_operand="d" />\r
+ m_reg.A = ALU::bitwiseAnd(m_reg.A, m_mem.read8(m_reg.IX, d), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="AND A,(IY+d)" clock="19">\r
+<opcode prefix="0xFD" mask="10100110" signed_operand="d" />\r
+ m_reg.A = ALU::bitwiseAnd(m_reg.A, m_mem.read8(m_reg.IY, d), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="OR A,r" clock="4">\r
+<opcode mask="10110r" />\r
+ m_reg.A = ALU::bitwiseOr(m_reg.A, r, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="OR A,n" clock="7">\r
+<opcode mask="11110110" operand="n" />\r
+ m_reg.A = ALU::bitwiseOr(m_reg.A, n, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="OR A,(HL)" clock="7">\r
+<opcode mask="10110110" />\r
+ m_reg.A = ALU::bitwiseOr(m_reg.A, m_mem.read8(m_reg.HL), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="OR A,(IX+d)" clock="19">\r
+<opcode prefix="0xDD" mask="10110110" signed_operand="d" />\r
+ m_reg.A = ALU::bitwiseOr(m_reg.A, m_mem.read8(m_reg.IX, d), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="OR A,(IY+d)" clock="19">\r
+<opcode prefix="0xFD" mask="10110110" signed_operand="d" />\r
+ m_reg.A = ALU::bitwiseOr(m_reg.A, m_mem.read8(m_reg.IY, d), m_reg.F);\r
+</instruction>\r
+\r
+<!-- xor opcodes are wrong - they match the or opcodes in the Z80 manual! -->\r
+<instruction name="XOR A,r" clock="4">\r
+<opcode mask="10101r" />\r
+ m_reg.A = ALU::bitwiseXor(m_reg.A, r, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="XOR A,n" clock="7">\r
+<opcode mask="11101110" operand="n" />\r
+ m_reg.A = ALU::bitwiseXor(m_reg.A, n, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="XOR A,(HL)" clock="7">\r
+<opcode mask="10101110" />\r
+ m_reg.A = ALU::bitwiseXor(m_reg.A, m_mem.read8(m_reg.HL), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="XOR A,(IX+d)" clock="19">\r
+<opcode prefix="0xDD" mask="10101110" signed_operand="d" />\r
+ m_reg.A = ALU::bitwiseXor(m_reg.A, m_mem.read8(m_reg.IX, d), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="XOR A,(IY+d)" clock="19">\r
+<opcode prefix="0xFD" mask="10101110" signed_operand="d" />\r
+ m_reg.A = ALU::bitwiseXor(m_reg.A, m_mem.read8(m_reg.IY, d), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="CP r" clock="4">\r
+<opcode mask="10111r" />\r
+ ALU::sub(m_reg.A, r, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="CP n" clock="7">\r
+<opcode mask="11111110" operand="n" />\r
+ ALU::sub(m_reg.A, n, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="CP (HL)" clock="7">\r
+<opcode mask="10111110" />\r
+ ALU::sub(m_reg.A, m_mem.read8(m_reg.HL), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="CP (IX+d)" clock="19">\r
+<opcode prefix="0xDD" mask="10111110" signed_operand="d" />\r
+ ALU::sub(m_reg.A, m_mem.read8(m_reg.IX, d), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="CP (IY+d)" clock="19">\r
+<opcode prefix="0xFD" mask="10111110" signed_operand="d" />\r
+ ALU::sub(m_reg.A, m_mem.read8(m_reg.IY, d), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="INC r" clock="4">\r
+<opcode mask="00r100" />\r
+ r = ALU::add(r, 1, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="INC (HL)" clock="11">\r
+<opcode mask="00110100" />\r
+ m_mem.write8(m_reg.HL, ALU::add(m_mem.read8(m_reg.HL), 1, m_reg.F));\r
+</instruction>\r
+\r
+<instruction name="INC (IX+d)" clock="23">\r
+<opcode prefix="0xDD" mask="00110100" signed_operand="d" />\r
+ m_mem.write8(m_reg.IX, d, ALU::add(m_mem.read8(m_reg.IX, d), 1, m_reg.F));\r
+</instruction>\r
+\r
+<instruction name="INC (IY+d)" clock="23">\r
+<opcode prefix="0xFD" mask="00110100" signed_operand="d" />\r
+ m_mem.write8(m_reg.IY, d, ALU::add(m_mem.read8(m_reg.IY, d), 1, m_reg.F));\r
+</instruction>\r
+\r
+<instruction name="DEC r" clock="4">\r
+<opcode mask="00r101" />\r
+ r = ALU::sub(r, 1, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="DEC (HL)" clock="11">\r
+<opcode mask="00110101" />\r
+ m_mem.write8(m_reg.HL, ALU::sub(m_mem.read8(m_reg.HL), 1, m_reg.F));\r
+</instruction>\r
+\r
+<instruction name="DEC (IX+d)" clock="23">\r
+<opcode prefix="0xDD" mask="00110101" signed_operand="d" />\r
+ m_mem.write8(m_reg.IX, d, ALU::sub(m_mem.read8(m_reg.IX, d), 1, m_reg.F));\r
+</instruction>\r
+\r
+<instruction name="DEC (IY+d)" clock="23">\r
+<opcode prefix="0xFD" mask="00110101" signed_operand="d" />\r
+ m_mem.write8(m_reg.IY, d, ALU::sub(m_mem.read8(m_reg.IY, d), 1, m_reg.F));\r
+</instruction>\r
+\r
+\r
+<!--\r
+ General Purpose Arithmetic and CPU Control Instructions\r
+-->\r
+\r
+<instruction name="DAA" clock="4">\r
+<opcode mask="00100111"/>\r
+ m_reg.A = DAA(m_reg.A, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="CPL" clock="4">\r
+<opcode mask="00101111"/>\r
+ m_reg.A = ~m_reg.A;\r
+ m_reg.F.H = 1;\r
+ m_reg.F.N = 1;\r
+</instruction>\r
+\r
+<instruction name="NEG" clock="8">\r
+<opcode prefix="0xED" mask="01000100"/>\r
+ m_reg.A = ALU::sub(0, m_reg.A, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="CCF" clock="4">\r
+<opcode mask="00111111"/>\r
+ m_reg.F.C = (m_reg.F.C) ? 0 : 1;\r
+ m_reg.F.N = 0;\r
+</instruction>\r
+\r
+<instruction name="SCF" clock="4">\r
+<opcode mask="00110111"/>\r
+ m_reg.F.H = 0;\r
+ m_reg.F.N = 0;\r
+ m_reg.F.C = 1;\r
+</instruction>\r
+\r
+<instruction name="NOP" clock="4">\r
+<opcode mask="00000000"/>\r
+ // Do nothing\r
+</instruction>\r
+\r
+<instruction name="HALT" clock="4">\r
+<opcode mask="01110110"/>\r
+ halt();\r
+</instruction>\r
+\r
+<instruction name="DI" clock="4">\r
+<opcode mask="11110011"/>\r
+ m_iff1 = 0;\r
+ m_iff2 = 0;\r
+</instruction>\r
+\r
+<instruction name="EI" clock="4">\r
+<opcode mask="11111011"/>\r
+ m_iff1 = 1;\r
+ m_iff2 = 1;\r
+</instruction>\r
+\r
+<instruction name="IM 0" clock="8">\r
+<opcode prefix="0xED" mask="01000110"/>\r
+ m_interruptMode = IM_0;\r
+</instruction>\r
+\r
+<instruction name="IM 1" clock="8">\r
+<opcode prefix="0xED" mask="01010110"/>\r
+ m_interruptMode = IM_1;\r
+</instruction>\r
+\r
+<instruction name="IM 2" clock="8">\r
+<opcode prefix="0xED" mask="01011110"/>\r
+ m_interruptMode = IM_2;\r
+</instruction>\r
+\r
+<!--\r
+ 16-bit Arithmetic Instructions\r
+-->\r
+\r
+<instruction name="ADD HL, ss" clock="11">\r
+<opcode mask="00s1001"/>\r
+ m_reg.HL = ALU::add(m_reg.HL, s, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="ADC HL, ss" clock="15">\r
+<opcode prefix="0xED" mask="01s1010"/>\r
+ m_reg.HL = ALU::adc(m_reg.HL, s, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="SBC HL, ss" clock="15">\r
+<opcode prefix="0xED" mask="01s0010"/>\r
+ m_reg.HL = ALU::sbc(m_reg.HL, s, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="ADD IX, pp" clock="15">\r
+<opcode prefix="0xDD" mask="00p1001"/>\r
+ m_reg.IX = ALU::add(m_reg.IX, p, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="ADD IY, pp" clock="15">\r
+<opcode prefix="0xFD" mask="00R1001"/>\r
+ m_reg.IY = ALU::add(m_reg.IY, R, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="INC ss" clock="6">\r
+<opcode mask="00s0011"/>\r
+ s.inc();\r
+</instruction>\r
+\r
+<instruction name="INC IX" clock="10">\r
+<opcode prefix="0xDD" mask="00100011"/>\r
+ m_reg.IX.inc();\r
+</instruction>\r
+\r
+<instruction name="INC IY" clock="10">\r
+<opcode prefix="0xFD" mask="00100011"/>\r
+ m_reg.IY.inc();\r
+</instruction>\r
+\r
+<instruction name="DEC ss" clock="6">\r
+<opcode mask="00s1011"/>\r
+ s.dec();\r
+</instruction>\r
+\r
+<instruction name="DEC IX" clock="10">\r
+<opcode prefix="0xDD" mask="00101011"/>\r
+ m_reg.IX.dec();\r
+</instruction>\r
+\r
+<instruction name="DEC IY" clock="10">\r
+<opcode prefix="0xFD" mask="00101011"/>\r
+ m_reg.IY.dec();\r
+</instruction>\r
+\r
+<!--\r
+ Rotate and Shift Instructions\r
+-->\r
+\r
+<instruction name="RLCA" clock="4">\r
+<opcode mask="00000111"/>\r
+ m_reg.A = ALU::rotateLeft(m_reg.A, m_reg.F, false);\r
+</instruction>\r
+\r
+<instruction name="RLA" clock="4">\r
+<opcode mask="00010111"/>\r
+ m_reg.A = ALU::rotateLeft(m_reg.A, m_reg.F, true);\r
+</instruction>\r
+\r
+<instruction name="RRCA" clock="4">\r
+<opcode mask="00001111"/>\r
+ m_reg.A = ALU::rotateRight(m_reg.A, m_reg.F, false);\r
+</instruction>\r
+\r
+<instruction name="RRA" clock="4">\r
+<opcode mask="00011111"/>\r
+ m_reg.A = ALU::rotateRight(m_reg.A, m_reg.F, true);\r
+</instruction>\r
+\r
+<instruction name="RLC r" clock="8">\r
+<opcode prefix="0xCB" mask="00000r"/>\r
+ r = ALU::rotateLeft(r, m_reg.F, false, true);\r
+</instruction>\r
+\r
+<instruction name="RLC (HL)" clock="15">\r
+<opcode prefix="0xCB" mask="00000110"/>\r
+ m_mem.write8(\r
+ m_reg.HL,\r
+ ALU::rotateLeft(m_mem.read8(m_reg.HL), m_reg.F, false, true)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="RLC (IX + d)" clock="23">\r
+<opcode prefix="0xDDCB" mask="00000110" signed_operand="d"/>\r
+ m_mem.write8(\r
+ m_reg.IX,\r
+ d,\r
+ ALU::rotateLeft(m_mem.read8(m_reg.IX, d), m_reg.F, false, true)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="RLC (IY + d)" clock="23">\r
+<opcode prefix="0xFDCB" mask="00000110" signed_operand="d"/>\r
+ m_mem.write8(\r
+ m_reg.IY,\r
+ d,\r
+ ALU::rotateLeft(m_mem.read8(m_reg.IY, d), m_reg.F, false, true)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="RL r" clock="8">\r
+<opcode prefix="0xCB" mask="00010r"/>\r
+ r = ALU::rotateLeft(r, m_reg.F, true, true);\r
+</instruction>\r
+\r
+<instruction name="RL (HL)" clock="15">\r
+<opcode prefix="0xCB" mask="00010110"/>\r
+ m_mem.write8(\r
+ m_reg.HL,\r
+ ALU::rotateLeft(m_mem.read8(m_reg.HL), m_reg.F, true, true)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="RL (IX + d)" clock="23">\r
+<opcode prefix="0xDDCB" mask="00010110" signed_operand="d"/>\r
+ m_mem.write8(\r
+ m_reg.IX,\r
+ d,\r
+ ALU::rotateLeft(m_mem.read8(m_reg.IX, d), m_reg.F, true, true)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="RL (IY + d)" clock="23">\r
+<opcode prefix="0xFDCB" mask="00010110" signed_operand="d"/>\r
+ m_mem.write8(\r
+ m_reg.IY,\r
+ d,\r
+ ALU::rotateLeft(m_mem.read8(m_reg.IY, d), m_reg.F, true, true)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="RRC r" clock="8">\r
+<opcode prefix="0xCB" mask="00001r"/>\r
+ r = ALU::rotateRight(r, m_reg.F, false, true);\r
+</instruction>\r
+\r
+<instruction name="RRC (HL)" clock="15">\r
+<opcode prefix="0xCB" mask="00001110"/>\r
+ m_mem.write8(\r
+ m_reg.HL,\r
+ ALU::rotateRight(m_mem.read8(m_reg.HL), m_reg.F, false, true)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="RRC (IX + d)" clock="23">\r
+<opcode prefix="0xDDCB" mask="00001110" signed_operand="d"/>\r
+ m_mem.write8(\r
+ m_reg.IX,\r
+ d,\r
+ ALU::rotateRight(m_mem.read8(m_reg.IX, d), m_reg.F, false, true)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="RRC (IY + d)" clock="23">\r
+<opcode prefix="0xFDCB" mask="00001110" signed_operand="d"/>\r
+ m_mem.write8(\r
+ m_reg.IY,\r
+ d,\r
+ ALU::rotateRight(m_mem.read8(m_reg.IY, d), m_reg.F, false, true)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="RR r" clock="8">\r
+<opcode prefix="0xCB" mask="00011r"/>\r
+ r = ALU::rotateRight(r, m_reg.F, true, true);\r
+</instruction>\r
+\r
+<instruction name="RR (HL)" clock="15">\r
+<opcode prefix="0xCB" mask="00011110"/>\r
+ m_mem.write8(\r
+ m_reg.HL,\r
+ ALU::rotateRight(m_mem.read8(m_reg.HL), m_reg.F, true, true)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="RR (IX + d)" clock="23">\r
+<opcode prefix="0xDDCB" mask="00011110" signed_operand="d"/>\r
+ m_mem.write8(\r
+ m_reg.IX,\r
+ d,\r
+ ALU::rotateRight(m_mem.read8(m_reg.IX, d), m_reg.F, true, true)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="RR (IY + d)" clock="23">\r
+<opcode prefix="0xFDCB" mask="00011110" signed_operand="d"/>\r
+ m_mem.write8(\r
+ m_reg.IY,\r
+ d,\r
+ ALU::rotateRight(m_mem.read8(m_reg.IY, d), m_reg.F, true, true)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="SLA r" clock="8">\r
+<opcode prefix="0xCB" mask="00100r"/>\r
+ r = ALU::shiftLeft(r, m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="SLA (HL)" clock="15">\r
+<opcode prefix="0xCB" mask="00100110"/>\r
+ m_mem.write8(\r
+ m_reg.HL,\r
+ ALU::shiftLeft(m_mem.read8(m_reg.HL), m_reg.F)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="SLA (IX + d)" clock="23">\r
+<opcode prefix="0xDDCB" mask="00100110" signed_operand="d"/>\r
+ m_mem.write8(\r
+ m_reg.IX,\r
+ d,\r
+ ALU::shiftLeft(m_mem.read8(m_reg.IX, d), m_reg.F)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="SLA (IY + d)" clock="23">\r
+<opcode prefix="0xFDCB" mask="00100110" signed_operand="d"/>\r
+ m_mem.write8(\r
+ m_reg.IY,\r
+ d,\r
+ ALU::shiftLeft(m_mem.read8(m_reg.IY, d), m_reg.F)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="SRA r" clock="8">\r
+<opcode prefix="0xCB" mask="00101r"/>\r
+ r = ALU::shiftRight(r, m_reg.F, true);\r
+</instruction>\r
+\r
+<instruction name="SRA (HL)" clock="15">\r
+<opcode prefix="0xCB" mask="00101110"/>\r
+ m_mem.write8(\r
+ m_reg.HL,\r
+ ALU::shiftRight(m_mem.read8(m_reg.HL), m_reg.F, true)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="SRA (IX + d)" clock="23">\r
+<opcode prefix="0xDDCB" mask="00101110" signed_operand="d"/>\r
+ m_mem.write8(\r
+ m_reg.IX,\r
+ d,\r
+ ALU::shiftRight(m_mem.read8(m_reg.IX, d), m_reg.F, true)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="SRA (IY + d)" clock="23">\r
+<opcode prefix="0xFDCB" mask="00101110" signed_operand="d"/>\r
+ m_mem.write8(\r
+ m_reg.IY,\r
+ d,\r
+ ALU::shiftRight(m_mem.read8(m_reg.IY, d), m_reg.F, true)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="SRL r" clock="8">\r
+<opcode prefix="0xCB" mask="00111r"/>\r
+ r = ALU::shiftRight(r, m_reg.F, false);\r
+</instruction>\r
+\r
+<instruction name="SRL (HL)" clock="15">\r
+<opcode prefix="0xCB" mask="00111110"/>\r
+ m_mem.write8(\r
+ m_reg.HL,\r
+ ALU::shiftRight(m_mem.read8(m_reg.HL), m_reg.F, false)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="SRL (IX + d)" clock="23">\r
+<opcode prefix="0xDDCB" mask="00111110" signed_operand="d"/>\r
+ m_mem.write8(\r
+ m_reg.IX,\r
+ d,\r
+ ALU::shiftRight(m_mem.read8(m_reg.IX, d), m_reg.F, false)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="SRL (IY + d)" clock="23">\r
+<opcode prefix="0xFDCB" mask="00111110" signed_operand="d"/>\r
+ m_mem.write8(\r
+ m_reg.IY,\r
+ d,\r
+ ALU::shiftRight(m_mem.read8(m_reg.IY, d), m_reg.F, false)\r
+ );\r
+</instruction>\r
+\r
+<instruction name="RLD" clock="18">\r
+<opcode prefix="0xED" mask="01101111"/>\r
+ uint8_t m(m_mem.read8(m_reg.HL));\r
+ ALU::RLD(m_reg.A, m, m_reg.F);\r
+ m_mem.write8(m_reg.HL, m);\r
+</instruction>\r
+\r
+<instruction name="RRD" clock="18">\r
+<opcode prefix="0xED" mask="01100111"/>\r
+ uint8_t m(m_mem.read8(m_reg.HL));\r
+ ALU::RRD(m_reg.A, m, m_reg.F);\r
+ m_mem.write8(m_reg.HL, m);\r
+</instruction>\r
+\r
+<!--\r
+ Bit set, reset, and test instructions\r
+-->\r
+\r
+<instruction name="BIT b,r" clock="8">\r
+<opcode prefix="0xCB" mask="01br"/>\r
+ m_reg.F.Z = ((~r) >> b) & 0x1;\r
+ m_reg.F.H = 1;\r
+ m_reg.F.N = 0;\r
+</instruction>\r
+\r
+<instruction name="BIT b,(HL)" clock="12">\r
+<opcode prefix="0xCB" mask="01b110"/>\r
+ m_reg.F.Z = ((~m_mem.read8(m_reg.HL)) >> b) & 0x1;\r
+ m_reg.F.H = 1;\r
+ m_reg.F.N = 0;\r
+</instruction>\r
+\r
+<instruction name="BIT b,(IX+d)" clock="20">\r
+<opcode prefix="0xDDCB" mask="01b110" signed_operand="d"/>\r
+ m_reg.F.Z = ((~m_mem.read8(m_reg.IX, d)) >> b) & 0x1;\r
+ m_reg.F.H = 1;\r
+ m_reg.F.N = 0;\r
+</instruction>\r
+\r
+<instruction name="BIT b,(IY+d)" clock="20">\r
+<opcode prefix="0xFDCB" mask="01b110" signed_operand="d"/>\r
+ m_reg.F.Z = ((~m_mem.read8(m_reg.IY, d)) >> b) & 0x1;\r
+ m_reg.F.H = 1;\r
+ m_reg.F.N = 0;\r
+</instruction>\r
+\r
+<instruction name="SET b,r" clock="8">\r
+<opcode prefix="0xCB" mask="11br"/>\r
+ r |= (0x1 << b);\r
+</instruction>\r
+\r
+<instruction name="SET b,(HL)" clock="15">\r
+<opcode prefix="0xCB" mask="11b110"/>\r
+ uint8_t tmp(m_mem.read8(m_reg.HL));\r
+ tmp |= (0x1 << b);\r
+ m_mem.write8(m_reg.HL, tmp);\r
+</instruction>\r
+\r
+<instruction name="SET b,(IX+d)" clock="23">\r
+<opcode prefix="0xDDCB" mask="11b110" signed_operand="d"/>\r
+ uint8_t tmp(m_mem.read8(m_reg.IX, d));\r
+ tmp |= (0x1 << b);\r
+ m_mem.write8(m_reg.IX, d, tmp);\r
+</instruction>\r
+\r
+<instruction name="SET b,(IY+d)" clock="23">\r
+<opcode prefix="0xFDCB" mask="11b110" signed_operand="d"/>\r
+ uint8_t tmp(m_mem.read8(m_reg.IY, d));\r
+ tmp |= (0x1 << b);\r
+ m_mem.write8(m_reg.IY, d, tmp);\r
+</instruction>\r
+\r
+<instruction name="RES b,r" clock="8">\r
+<opcode prefix="0xCB" mask="10br"/>\r
+ r &= 0xFF ^ (0x1 << b);\r
+</instruction>\r
+\r
+<instruction name="RES b,(HL)" clock="15">\r
+<opcode prefix="0xCB" mask="10b110"/>\r
+ uint8_t tmp(m_mem.read8(m_reg.HL));\r
+ tmp &= 0xFF ^ (0x1 << b);\r
+ m_mem.write8(m_reg.HL, tmp);\r
+</instruction>\r
+\r
+<instruction name="RES b,(IX+d)" clock="23">\r
+<opcode prefix="0xDDCB" mask="10b110" signed_operand="d"/>\r
+ uint8_t tmp(m_mem.read8(m_reg.IX, d));\r
+ tmp &= 0xFF ^ (0x1 << b);\r
+ m_mem.write8(m_reg.IX, d, tmp);\r
+</instruction>\r
+\r
+<instruction name="RES b,(IY+d)" clock="23">\r
+<opcode prefix="0xFDCB" mask="10b110" signed_operand="d"/>\r
+ uint8_t tmp(m_mem.read8(m_reg.IY, d));\r
+ tmp &= 0xFF ^ (0x1 << b);\r
+ m_mem.write8(m_reg.IY, d, tmp);\r
+</instruction>\r
+\r
+<!--\r
+ Jump Instructions\r
+-->\r
+<instruction name="JP nn" clock="10">\r
+<opcode mask="11000011" operand="N"/>\r
+ m_reg.PC = N;\r
+</instruction>\r
+\r
+<instruction name="JP cc,nn" clock="10" variant="z80">\r
+<opcode mask="11b010" operand="N"/>\r
+ switch (b)\r
+ {\r
+ case 0: if (!m_reg.F.Z) m_reg.PC = N; break;\r
+ case 1: if (m_reg.F.Z) m_reg.PC = N; break;\r
+ case 2: if (!m_reg.F.C) m_reg.PC = N; break;\r
+ case 3: if (m_reg.F.C) m_reg.PC = N; break;\r
+ case 4: if (!m_reg.F.P) m_reg.PC = N; break;\r
+ case 5: if (m_reg.F.P) m_reg.PC = N; break;\r
+ case 6: if (!m_reg.F.S) m_reg.PC = N; break;\r
+ case 7: if (m_reg.F.S) m_reg.PC = N; break;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="JR e" clock="12">\r
+<opcode mask="00011000" signed_operand="e"/>\r
+ uint16_t pc = m_reg.PC.host();\r
+ pc = pc + e;\r
+ m_reg.PC = htoz(pc);\r
+</instruction>\r
+\r
+<instruction name="JR C, e" clock="7">\r
+<opcode mask="00111000" signed_operand="e"/>\r
+ if (m_reg.F.C)\r
+ {\r
+ uint16_t pc = m_reg.PC.host();\r
+ pc = pc + e;\r
+ m_reg.PC = htoz(pc);\r
+\r
+ clock += 5;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="JR NC, e" clock="7">\r
+<opcode mask="00110000" signed_operand="e"/>\r
+ if (!m_reg.F.C)\r
+ {\r
+ uint16_t pc = m_reg.PC.host();\r
+ pc = pc + e;\r
+ m_reg.PC = htoz(pc);\r
+\r
+ clock += 5;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="JR Z, e" clock="7">\r
+<opcode mask="00101000" signed_operand="e"/>\r
+ if (m_reg.F.Z)\r
+ {\r
+ uint16_t pc = m_reg.PC.host();\r
+ pc = pc + e;\r
+ m_reg.PC = htoz(pc);\r
+\r
+ clock += 5;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="JR NZ, e" clock="7">\r
+<opcode mask="00100000" signed_operand="e"/>\r
+ if (!m_reg.F.Z)\r
+ {\r
+ uint16_t pc = m_reg.PC.host();\r
+ pc = pc + e;\r
+ m_reg.PC = htoz(pc);\r
+\r
+ clock += 5;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="JP (HL)" clock="4">\r
+<opcode mask="11101001"/>\r
+ m_reg.PC = m_reg.HL;\r
+</instruction>\r
+\r
+<instruction name="JP (IX)" clock="8">\r
+<opcode prefix="0xDD" mask="11101001"/>\r
+ m_reg.PC = m_reg.IX;\r
+</instruction>\r
+\r
+<instruction name="JP (IY)" clock="8">\r
+<opcode prefix="0xFD" mask="11101001"/>\r
+ m_reg.PC = m_reg.IY;\r
+</instruction>\r
+\r
+<instruction name="DJNZ" clock="8" variant="z80" >\r
+<opcode mask="00010000" signed_operand="e"/>\r
+ --m_reg.B;\r
+ if (m_reg.B)\r
+ {\r
+ uint16_t pc = m_reg.PC.host();\r
+ pc = pc + e;\r
+ m_reg.PC = htoz(pc);\r
+\r
+ clock += 5;\r
+ }\r
+</instruction>\r
+\r
+<!--\r
+ Call and Return Instructions\r
+-->\r
+<instruction name="CALL nn" clock="17">\r
+<opcode mask="11001101" operand="N"/>\r
+ m_reg.SP.dec(2);\r
+ m_mem.write16(m_reg.SP, m_reg.PC);\r
+ m_reg.PC = N;\r
+</instruction>\r
+\r
+<instruction name="CALL cc,nn" clock="10" variant="z80">\r
+<opcode mask="11b100" operand="N"/>\r
+ bool jump(false);\r
+ switch (b)\r
+ {\r
+ case 0: if (!m_reg.F.Z) jump = true; break;\r
+ case 1: if (m_reg.F.Z) jump = true; break;\r
+ case 2: if (!m_reg.F.C) jump = true; break;\r
+ case 3: if (m_reg.F.C) jump = true; break;\r
+ case 4: if (!m_reg.F.P) jump = true; break;\r
+ case 5: if (m_reg.F.P) jump = true; break;\r
+ case 6: if (!m_reg.F.S) jump = true; break;\r
+ case 7: if (m_reg.F.S) jump = true; break;\r
+ }\r
+\r
+ if (jump)\r
+ {\r
+ m_reg.SP.dec(2);\r
+ m_mem.write16(m_reg.SP, m_reg.PC);\r
+ m_reg.PC = N;\r
+ clock += 7;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="RET" clock="10">\r
+<opcode mask="11001001" operand="N"/>\r
+ m_reg.PC = m_mem.read16(m_reg.SP);\r
+ m_reg.SP.inc(2);\r
+</instruction>\r
+\r
+<instruction name="RET cc" clock="5" variant="z80">\r
+<opcode mask="11b000"/>\r
+ bool jump(false);\r
+ switch (b)\r
+ {\r
+ case 0: if (!m_reg.F.Z) jump = true; break;\r
+ case 1: if (m_reg.F.Z) jump = true; break;\r
+ case 2: if (!m_reg.F.C) jump = true; break;\r
+ case 3: if (m_reg.F.C) jump = true; break;\r
+ case 4: if (!m_reg.F.P) jump = true; break;\r
+ case 5: if (m_reg.F.P) jump = true; break;\r
+ case 6: if (!m_reg.F.S) jump = true; break;\r
+ case 7: if (m_reg.F.S) jump = true; break;\r
+ }\r
+\r
+ if (jump)\r
+ {\r
+ m_reg.PC = m_mem.read16(m_reg.SP);\r
+ m_reg.SP.inc(2);\r
+ clock += 6;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="RETI" clock="14">\r
+<opcode prefix="0xED" mask="01001101"/>\r
+ m_reg.PC = m_mem.read16(m_reg.SP);\r
+ m_reg.SP.inc(2);\r
+ m_iff1 = 1;\r
+ m_iff2 = 1;\r
+</instruction>\r
+\r
+<instruction name="RETN" clock="14">\r
+<opcode prefix="0xED" mask="01000101"/>\r
+ m_reg.PC = m_mem.read16(m_reg.SP);\r
+ m_reg.SP.inc(2);\r
+\r
+ m_iff1 = m_iff2;\r
+\r
+ // TODO interruptComplete();\r
+</instruction>\r
+\r
+<instruction name="RST p" clock="11">\r
+<opcode mask="11b111"/>\r
+ uint8_t addr;\r
+ switch (b)\r
+ {\r
+ case 0: addr = 0; break;\r
+ case 1: addr = 0x08; break;\r
+ case 2: addr = 0x10; break;\r
+ case 3: addr = 0x18; break;\r
+ case 4: addr = 0x20; break;\r
+ case 5: addr = 0x28; break;\r
+ case 6: addr = 0x30; break;\r
+ case 7: addr = 0x38; break;\r
+ }\r
+\r
+ m_reg.SP.dec(2);\r
+ m_mem.write16(m_reg.SP, m_reg.PC);\r
+ m_reg.PC.h = 0;\r
+ m_reg.PC.l = addr;\r
+</instruction>\r
+\r
+<!--\r
+ IN/OUT Instructions\r
+\r
+TODO NOT IMPLEMENTED\r
+-->\r
+\r
+<!--\r
+ Prefix Dispatch\r
+-->\r
+<instruction name="CB" clock="0">\r
+<opcode mask="11001011"/>\r
+ prefix = (prefix << 8) | opcode;\r
+ opcode = op8();\r
+ goto *(OpcodeTableCB[opcode]);\r
+</instruction>\r
+\r
+<instruction name="DD" clock="0" variant="z80">\r
+<opcode mask="11011101"/>\r
+ prefix = (prefix << 8) | opcode;\r
+ opcode = op8();\r
+ goto *(OpcodeTableDD[opcode]);\r
+</instruction>\r
+\r
+<instruction name="ED" clock="0" variant="z80">\r
+<opcode mask="11101101"/>\r
+ prefix = (prefix << 8) | opcode;\r
+ opcode = op8();\r
+ goto *(OpcodeTableED[opcode]);\r
+</instruction>\r
+\r
+<instruction name="FD" clock="0" variant="z80">\r
+<opcode mask="11111101"/>\r
+ prefix = (prefix << 8) | opcode;\r
+ opcode = op8();\r
+ goto *(OpcodeTableFD[opcode]);\r
+</instruction>\r
+\r
+<instruction name="DDCB" clock="0" variant="z80">\r
+<opcode prefix="0xDD" mask="11001011"/>\r
+ prefix = (prefix << 8) | opcode;\r
+ doublePrefixOperand = op8();\r
+ opcode = op8();\r
+ goto *(OpcodeTableDDCB[opcode]);\r
+</instruction>\r
+\r
+<instruction name="FDCB" clock="0" variant="z80">\r
+<opcode prefix="0xFD" mask="11001011"/>\r
+ prefix = (prefix << 8) | opcode;\r
+ doublePrefixOperand = op8();\r
+ opcode = op8();\r
+ goto *(OpcodeTableFDCB[opcode]);\r
+</instruction>\r
+\r
+<!--\r
+ GB80 Instructions\r
+-->\r
+<instruction name="LD (nn),SP" clock="16" variant="gb80" >\r
+<opcode mask="00001000" operand="N" />\r
+ m_mem.write16(N, m_reg.SP);\r
+</instruction>\r
+\r
+<instruction name="LD (HLI),A" clock="16" variant="gb80">\r
+<opcode mask="00100010" />\r
+ m_mem.write8(m_reg.HL, m_reg.A);\r
+ m_reg.HL.inc();\r
+</instruction>\r
+\r
+<instruction name="LD A, (HLI)" clock="16" variant="gb80">\r
+<opcode mask="00101010" />\r
+ m_reg.A = m_mem.read8(m_reg.HL);\r
+ m_reg.HL.inc();\r
+</instruction>\r
+\r
+<instruction name="LD (HLD),A" clock="13" variant="gb80">\r
+<opcode mask="00110010"/>\r
+ m_mem.write8(m_reg.HL, m_reg.A);\r
+ m_reg.HL.dec();\r
+</instruction>\r
+\r
+<instruction name="LD A,(HLD)" clock="13" variant="gb80">\r
+<opcode mask="00111010"/>\r
+ m_reg.A = m_mem.read8(m_reg.HL);\r
+ m_reg.HL.dec();\r
+</instruction>\r
+\r
+<instruction name="RETI" clock="14" variant="gb80">\r
+<opcode mask="11011001" />\r
+ m_reg.PC = m_mem.read16(m_reg.SP);\r
+ m_reg.SP.inc(2);\r
+ m_iff1 = 1;\r
+ m_iff2 = 1;\r
+</instruction>\r
+\r
+<instruction name="STOP" clock="4" variant="gb80">\r
+<opcode mask="00010000" />\r
+ halt();\r
+</instruction>\r
+\r
+\r
+<instruction name="ADD SP,d" clock="4" variant="gb80">\r
+<opcode mask="11101000" signed_operand="d"/>\r
+ m_reg.SP = ALU::add(m_reg.SP, htoz(int16_t(d)), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="LD (N),A" clock="4" variant="gb80">\r
+<opcode mask="11101010" operand="N"/>\r
+ m_mem.write8(N, m_reg.A);\r
</instruction>\r
\r
+\r
+<instruction name="LDHL SP,d" clock="4" variant="gb80">\r
+<opcode mask="11111000" signed_operand="d"/>\r
+ m_reg.HL = ALU::add(m_reg.SP, htoz(int16_t(d)), m_reg.F);\r
+</instruction>\r
+\r
+<instruction name="LD A,(N)" clock="4" variant="gb80">\r
+<opcode mask="11111010" operand="N"/>\r
+ m_reg.A = m_mem.read8(N);;\r
+</instruction>\r
+\r
+<instruction name="RET C" clock="5" variant="gb80">\r
+<opcode mask="11011000"/>\r
+ if (m_reg.F.C)\r
+ {\r
+ m_reg.PC = m_mem.read16(m_reg.SP);\r
+ m_reg.SP.inc(2);\r
+ clock += 6;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="RET NC" clock="5" variant="gb80">\r
+<opcode mask="11010000"/>\r
+ if (!m_reg.F.C)\r
+ {\r
+ m_reg.PC = m_mem.read16(m_reg.SP);\r
+ m_reg.SP.inc(2);\r
+ clock += 6;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="JP Z" clock="5" variant="gb80">\r
+<opcode mask="11001010" operand="N"/>\r
+ if (m_reg.F.Z)\r
+ {\r
+ m_reg.PC = N;\r
+ clock += 6;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="RET Z" clock="5" variant="gb80">\r
+<opcode mask="11001000"/>\r
+ if (m_reg.F.Z)\r
+ {\r
+ m_reg.PC = m_mem.read16(m_reg.SP);\r
+ m_reg.SP.inc(2);\r
+ clock += 6;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="RET NZ" clock="8" variant="gb80">\r
+<opcode mask="11000000"/>\r
+ if (!m_reg.F.Z)\r
+ {\r
+ m_reg.PC = m_mem.read16(m_reg.SP);\r
+ m_reg.SP.inc(2);\r
+ clock += 6;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="CALL Z" clock="8" variant="gb80">\r
+<opcode mask="11001100" operand="N"/>\r
+ if (m_reg.F.Z)\r
+ {\r
+ m_reg.SP.dec(2);\r
+ m_mem.write16(m_reg.SP, m_reg.PC);\r
+ m_reg.PC = N;\r
+ clock += 7;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="CALL NZ" clock="8" variant="gb80">\r
+<opcode mask="11000100" operand="N"/>\r
+ if (!m_reg.F.Z)\r
+ {\r
+ m_reg.SP.dec(2);\r
+ m_mem.write16(m_reg.SP, m_reg.PC);\r
+ m_reg.PC = N;\r
+ clock += 7;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="CALL C" clock="8" variant="gb80">\r
+<opcode mask="11011100" operand="N"/>\r
+ if (m_reg.F.C)\r
+ {\r
+ m_reg.SP.dec(2);\r
+ m_mem.write16(m_reg.SP, m_reg.PC);\r
+ m_reg.PC = N;\r
+ clock += 7;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="CALL NC" clock="8" variant="gb80">\r
+<opcode mask="11010100" operand="N"/>\r
+ if (!m_reg.F.C)\r
+ {\r
+ m_reg.SP.dec(2);\r
+ m_mem.write16(m_reg.SP, m_reg.PC);\r
+ m_reg.PC = N;\r
+ clock += 7;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="JP NZ" clock="5" variant="gb80">\r
+<opcode mask="11000010" operand="N"/>\r
+ if (!m_reg.F.Z)\r
+ {\r
+ m_reg.PC = N;\r
+ clock += 6;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="JP NC" clock="5" variant="gb80">\r
+<opcode mask="11010010" operand="N"/>\r
+ if (!m_reg.F.C)\r
+ {\r
+ m_reg.PC = N;\r
+ clock += 6;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="JP C" clock="5" variant="gb80">\r
+<opcode mask="11011010" operand="N"/>\r
+ if (m_reg.F.C)\r
+ {\r
+ m_reg.PC = N;\r
+ clock += 6;\r
+ }\r
+</instruction>\r
+\r
+<instruction name="SWAP r" clock="8" variant="gb80">\r
+<opcode prefix="0xCB" mask="00110r"/>\r
+ uint8_t tmp(r);\r
+ r <<= 4;\r
+ r |= (tmp >> 4);\r
+\r
+ m_reg.F.Z = r == 0;\r
+ m_reg.F.N = 0;\r
+ m_reg.F.H = 0;\r
+ m_reg.F.C = 0;\r
+</instruction>\r
+\r
+<!-- Zero-page gameboy instructions -->\r
+<instruction name="LD (FF00+n),A" clock="4" variant="gb80">\r
+<opcode mask="11100000" operand="n"/>\r
+ m_mem.write8(htoz(0xFF00 + n), m_reg.A);\r
+</instruction>\r
+\r
+<instruction name="LD (FF00+C),A" clock="4" variant="gb80">\r
+<opcode mask="11100010"/>\r
+ m_mem.write8(htoz(0xFF00 + m_reg.C), m_reg.A);\r
+</instruction>\r
+\r
+\r
+<instruction name="LD A,(FF00+d)" clock="4" variant="gb80">\r
+<opcode mask="11110000" operand="n"/>\r
+ m_reg.A = m_mem.read8(htoz(0xFF00 + n));\r
+</instruction>\r
+\r
+<!-- Gameboy NOP's -->\r
+\r
+<instruction name="NOP_D3" clock="4" variant="gb80">\r
+<opcode mask="11010011"/>\r
+</instruction>\r
+\r
+<instruction name="NOP_DB" clock="4" variant="gb80">\r
+<opcode mask="11011011"/>\r
+</instruction>\r
+\r
+<instruction name="NOP_DD" clock="4" variant="gb80">\r
+<opcode mask="11011101"/>\r
+</instruction>\r
+\r
+<instruction name="NOP_E3" clock="4" variant="gb80">\r
+<opcode mask="11100011"/>\r
+</instruction>\r
+\r
+<instruction name="NOP_E4" clock="4" variant="gb80">\r
+<opcode mask="11100100"/>\r
+</instruction>\r
+\r
+<instruction name="NOP_EB" clock="4" variant="gb80">\r
+<opcode mask="11101011"/>\r
+</instruction>\r
+\r
+<instruction name="NOP_EC" clock="4" variant="gb80">\r
+<opcode mask="11101100"/>\r
+</instruction>\r
+\r
+<instruction name="NOP_F2" clock="4" variant="gb80">\r
+<opcode mask="11110010"/>\r
+</instruction>\r
+\r
+<instruction name="NOP_F4" clock="4" variant="gb80">\r
+<opcode mask="11110100"/>\r
+</instruction>\r
+\r
+<instruction name="NOP_FC" clock="4" variant="gb80">\r
+<opcode mask="11111100"/>\r
+</instruction>\r
+\r
+<instruction name="NOP_FD" clock="4" variant="gb80">\r
+<opcode mask="11111101"/>\r
+</instruction>\r
</opcodes>\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of glBoy.
+//
+// glBoy is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// glBoy is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
+
+#include "Log.hh"
+
+#include <cstdlib>
+#include <iostream>
+#include <sstream>
+
+using namespace glBoy;
+
+Log&
+Log::Instance()
+{
+ static Log s_instance;
+ return s_instance;
+}
+
+Log::Log() :
+ m_levelMask(7),
+ m_nullStream(&m_nullBuf)
+{
+ char* env(getenv("GLBOY_DEBUG_LEVEL"));
+ if (env)
+ {
+ std::stringstream stream;
+ stream << env;
+
+ // If this conversion fails, level mask remains 0, which is ok.
+ stream >> m_levelMask;
+ }
+}
+
+void
+Log::setDebugLevel(uint32_t levelMask)
+{
+ m_levelMask = levelMask;
+}
+
+bool
+Log::isDebug(DebugLevel level) const
+{
+ return (m_levelMask & level) != 0;
+}
+
+std::ostream&
+Log::operator()(DebugLevel level)
+{
+ if (isDebug(level))
+ {
+ return std::cout;
+ }
+ else
+ {
+ return m_nullStream;
+ }
+}
+
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of glBoy.
+//
+// glBoy is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// glBoy is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef GLBOY_LOG_HH
+#define GLBOY_LOG_HH
+
+#include "glBoy.hh"
+
+#include <cstdint>
+#include <ostream>
+#include <streambuf>
+
+namespace glBoy
+{
+ class Log
+ {
+ public:
+
+ enum DebugLevel
+ {
+ DBG_STAT = 1, DBG_ERROR = 2, DBG_WARNING = 4, DBG_MEMORY = 8, DBG_TRACE = 16
+ };
+ static Log& Instance();
+
+ void setDebugLevel(uint32_t levelMask);
+
+ bool isDebug(DebugLevel level) const;
+ std::ostream& operator()(DebugLevel level);
+
+ private:
+ Log();
+
+ Log(const Log&);
+ Log& operator=(const Log&);
+
+ uint32_t m_levelMask;
+
+ class NullBuf : public std::streambuf
+ {
+ public:
+ };
+
+ NullBuf m_nullBuf;
+ std::ostream m_nullStream;
+ };
+
+} // namespace glBoy
+#endif
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of glBoy.
+//
+// glBoy is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// glBoy is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
+
+
+#include "glBoy.hh"
+#include "Core.hh"
+#include "GameboyCart.hh"
+#include "GameboyGraphics.hh"
+#include "GameboyJoypad.hh"
+#include "GameboySound.hh"
+#include "GameboyTimer.hh"
+#include "RAM.hh"
+#include "ROM.hh"
+#include "Log.hh"
+
+#include <SDL/SDL.h>
+#include <SDL/SDL_opengl.h>
+#include <SDL/SDL_video.h>
+
+#include <cassert>
+#include <set>
+#include <sstream>
+
+#include <getopt.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+namespace
+{
+ SDL_Surface*
+ initOpenGL(double xScale, double yScale, bool fullscreen)
+ {
+ glBoy::Log::Instance()(glBoy::Log::DBG_STAT) << "Enabling OpenGL" << std::endl;
+
+ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+
+ unsigned int options(SDL_OPENGL);
+ if (fullscreen)
+ {
+ options |= SDL_FULLSCREEN;
+ }
+
+ SDL_Surface* surface(
+ SDL_SetVideoMode(160*xScale, 144*yScale, 32, options)
+ );
+
+ glEnable(GL_TEXTURE_2D);
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glDisable(GL_DEPTH_TEST);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(0.0f, 160, 144, 0.0f, -1.0f, 1.0f);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ return surface;
+ }
+
+ void usage(const std::string& exe)
+ {
+ std::cout <<
+ "Usage: " << exe << " "
+ "[--scale ratio] [--width w] [--height h] \n"
+ //"\t\t [--resample-method={bilinear|nearest} \n"
+ //"\t\t[--scaling-method={hq4x|scale2x|none}] "
+ "rom.gb" << std::endl <<
+ std::endl <<
+ "\t--scale\t(Default=4.0)" << std::endl <<
+ "\t\tMultiplier applied to the display output dimensions." << std::endl <<
+ "\t--width" << std::endl <<
+ "\t\tSet the display output dimensions directly" << std::endl <<
+ "\t--height" << std::endl <<
+ "\t\tSet the display output dimensions directly" << std::endl;
+ /*"\t--scaling-method\n\t\t\t(Default=\"hq4x\")" << std::endl <<
+ "\t\tSets the upscaling interpolation method." << std::endl <<
+ "\t--resample-method\n\t\t\t(Default=\"bilinear\")" << std::endl <<
+ "\t\tSets the resampling method." << std::endl;*/
+ }
+}
+
+int main(int argc, char** argv)
+{
+ static struct option longOptions[] =
+ {
+ {"video", 1, NULL, 'v'},
+ {"scale", 1, NULL, 's'},
+ {"width", 1, NULL, 'w'},
+ {"height", 1, NULL, 'h'},
+ //{"resample-method", 1, NULL, 'r'},
+ //{"scaling-method", 1, NULL, 'm'},
+ {NULL, 0, NULL, 0}
+ };
+
+ std::string filename("tetris.gb");
+ glBoy::GameboyGraphics::PixelScaler scaler(glBoy::GameboyGraphics::Scaler_hq4x);
+ glBoy::GameboyGraphics::ResampleMethod resampler(glBoy::GameboyGraphics::Resample_Bilinear);
+ bool fullscreen(false);
+ double scaleX(4.0);
+ double scaleY(4.0);
+
+ int optionIndex(0);
+ int c;
+ while (
+ (c = getopt_long(argc, argv, "s:w:h:", &longOptions[0], &optionIndex))
+ != -1)
+ {
+ switch (c)
+ {
+ case 'h':
+ {
+ int height(160);
+ std::stringstream convert(optarg);
+ convert >> height;
+ if (convert && height > 14 && height < 1440)
+ {
+ scaleY = height / 144.0;
+ }
+ }; break;
+ case 'r':
+ {
+ if (std::string(optarg) == "bilinear")
+ {
+ resampler = glBoy::GameboyGraphics::Resample_Bilinear;
+ }
+ else
+ {
+ resampler = glBoy::GameboyGraphics::Resample_Nearest;
+ }
+ }; break;
+ case 's':
+ {
+ double scale(1);
+ std::stringstream convert(optarg);
+ convert >> scale;
+ if (convert && scale > 0.1 && scale < 10)
+ {
+ scaleX = scale;
+ scaleY = scale;
+ }
+ }; break;
+ case 'm':
+ {
+ if (std::string(optarg) == "hq4x")
+ {
+ scaler = glBoy::GameboyGraphics::Scaler_hq4x;
+ }
+ else if (std::string(optarg) == "scale2x")
+ {
+ scaler = glBoy::GameboyGraphics::Scaler_Scale2x;
+ }
+ else
+ {
+ scaler = glBoy::GameboyGraphics::Scaler_None;
+ }
+ }; break;
+ case 'w':
+ {
+ int width(160);
+ std::stringstream convert(optarg);
+ convert >> width;
+ if (convert && width > 16 && width < 1600)
+ {
+ scaleX = width / 160.0;
+ }
+ }; break;
+
+ case '?':
+ usage(argv[0]);
+ exit(1);
+ break;
+
+ default:
+ abort();
+ };
+ }
+
+ if (optind < argc)
+ {
+ filename = argv[optind++];
+ }
+
+ glBoy::Core core;
+ glBoy::MemoryMap& map(core.getMemoryMap());
+
+ SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO);
+
+ SDL_Surface* surface(initOpenGL(scaleX, scaleY, fullscreen));
+
+ glBoy::GameboyGraphics graphics(core, surface, scaler, resampler);
+
+ // Map the cart memory.
+ int fd(open(filename.c_str(), O_RDONLY));
+ assert(fd >= 0);
+
+ struct stat buf;
+ if (fstat(fd, &buf))
+ {
+ assert(!"Failed fstat");
+ }
+ uint8_t* cart = new uint8_t[buf.st_size];
+ read(fd, cart, buf.st_size);
+ close(fd);
+
+ glBoy::GameboyCart tmp(core, cart, buf.st_size);
+
+
+/*
+ int biosFd(open("DMG_ROM.bin", O_RDONLY));
+ uint8_t bios[256];
+ read(biosFd, bios, 256);
+ std::shared_ptr<glBoy::MemoryMap::Memory> biosMem(new glBoy::ROM(bios, 256));
+ map.map(0, 256, biosMem);
+*/
+
+ // Map the cart mem.
+ // Map the standard internal RAM, plus shadow.
+ {
+ std::shared_ptr<glBoy::MemoryMap::Memory> mem(new glBoy::RAM(8192));
+ map.map(0xC000, 8192, mem);
+ map.map(0xE000, 7680, mem);
+ }
+ // Map the "zero-page" high-speed internal ram.
+ // Most interaction between GB hardware and the program occurs here.
+ {
+ std::shared_ptr<glBoy::MemoryMap::Memory> mem(new glBoy::RAM(128));
+ map.map(0xFF80, 128, mem);
+ }
+
+ // Map the Interrupt flags. Currently handled in Core.cc
+ {
+ std::shared_ptr<glBoy::MemoryMap::Memory> mem(new glBoy::RAM(1));
+ mem->write8(0, 0);
+ map.map(0xFF0F, 1, mem);
+ }
+
+ // Map other devices
+ std::shared_ptr<glBoy::GameboyJoypad> joypad(new glBoy::GameboyJoypad(core));
+ map.map(0xFF00, 1, joypad);
+ std::shared_ptr<glBoy::GameboyTimer> timer(
+ new glBoy::GameboyTimer(
+ core,
+ std::bind(&glBoy::GameboyGraphics::setFrameSkip, &graphics),
+ std::bind(&glBoy::GameboyGraphics::clearFrameSkip, &graphics)
+ )
+ );
+ map.map(0xFF04, 4, timer);
+ std::shared_ptr<glBoy::GameboySound> sound(new glBoy::GameboySound(core));
+ map.map(0xFF10, 48, sound);
+
+ // Cart starts from offset 0x100 with a NOP; JP
+ core.getRegisters().PC = glBoy::htoz(0x100);
+ core.getRegisters().SP = glBoy::htoz(0xFFFE);
+ core.getRegisters().AF = glBoy::htoz(0x01B0);
+ core.getRegisters().BC = glBoy::htoz(0x0013);
+ core.getRegisters().DE = glBoy::htoz(0x00D8);
+ core.getRegisters().HL = glBoy::htoz(0x014D);
+
+ core.run();
+
+ std::cerr << "Done" << std::endl;
+
+ SDL_FreeSurface(surface);
+ SDL_Quit();
+}
+
+# Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+#
+# This file is part of glBoy.
+#
+# glBoy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# glBoy is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with glBoy. If not, see <http://www.gnu.org/licenses/>.
-mm80: InstructionSet_Opcodes.cc ALU.cc Core.cc Memory.cc Registers.cc
- g++ -std=c++0x -I. -o $@ $^
+VERSION=$(shell cat VERSION)
+
+all: glBoy
+
+OBJS = \
+ ALU.o \
+ Core.o \
+ GameboyCart.o \
+ GameboyGraphics.o \
+ GameboyJoypad.o \
+ GameboySound.o \
+ GameboyTimer.o \
+ hq4x.o \
+ Log.o \
+ Main.o \
+ MemoryMap.o \
+ RAM.o \
+ Registers.o \
+ ROM.o \
+
+DEFINES=
+# -DGLBOY_DEBUG
+
+CPPFLAGS=-DHOST_LITTLE_ENDIAN -I. $(DEFINES)
+CXXFLAGS=-g -O3 -march=native -W -Wall -Werror -std=c++0x
+
+LDFLAGS=-lSDL -lGL -lGLU -lglut
+
+glBoy: InstructionSet_Opcodes.cc $(OBJS)
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJS)
InstructionSet_Opcodes.cc: InstructionSet.opcode makeOpcodes.pl
perl ./makeOpcodes.pl
-test: InstructionSet_Opcodes.cc Test.cc
- g++ -std=c++0x -I. -o $@ Test.cc
+Core.o: InstructionSet_Opcodes.cc
+
+test: InstructionSet_Opcodes.cc Test.cc ALU.cc
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ Test.cc ALU.cc
+
+package:
+ git commit -a
+ git tag -a -f $(VERSION)
+ mkdir /tmp/glBoy-$(VERSION)
+ -rm /tmp/glBoy-$(VERSION)/*
+ cp *.cc *.hh *.opcode *.pl Makefile COPYING README VERSION /tmp/glBoy-$(VERSION)
+ tar cvf glBoy-$(VERSION).tar -C /tmp glBoy-$(VERSION)
+
+clean:
+ -rm -f *.o
+ -rm -f glBoy
+ -rm -f InstructionSet_Opcodes.cc
+ -rm -f InstructionSet_Tables.cc
+ -rm -f gmon.out
-/** \r
- * Flat memory-model emulation.\r
- *\r
- * Authors: Michael McMaster <email@michaelmcmaster.name>\r
- * Copyright: Michael McMaster <email@michaelmcmaster.name>\r
- */\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>\r
+//\r
+// This file is part of glBoy.\r
+//\r
+// glBoy is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// glBoy is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.\r
\r
-#include "bits/Z80.hh"\r
-#include "ByteOrder.hh"\r
-#include "Memory.hh"\r
+#include "glBoy.hh"\r
+#include "MemoryMap.hh"\r
+#include "Log.hh"\r
+#include "util.hh"\r
\r
-#include <cstring>\r
+#include <algorithm>\r
+#include <cassert>\r
\r
-using namespace Z80;\r
+using namespace glBoy;\r
\r
-Memory::Memory(address_t initialSize)\r
+namespace\r
{\r
- m_mem.resize(initialSize);\r
+ class NullMemory : public MemoryMap::Memory\r
+ {\r
+ public:\r
+ virtual uint8_t read8(uint16_t) { return 0; }\r
+ virtual void write8(uint16_t, uint8_t) { }\r
+ };\r
+\r
+ struct NullDeleter\r
+ {\r
+ void operator()(MemoryMap::Memory*) {}\r
+ };\r
}\r
\r
-u8_t Memory::read8(address_t address)\r
+MemoryMap::MemoryMap()\r
{\r
- return m_mem[address];\r
}\r
\r
-u8_t Memory::read8(address_t address, offset_t offset)\r
+void\r
+MemoryMap::map(\r
+ uint16_t base,\r
+ uint16_t size,\r
+ const std::shared_ptr<Memory>& mem\r
+ )\r
{\r
- return m_mem[address + offset];\r
+ MappedArea mapping(base, size, mem);\r
+\r
+ MapType::iterator it(\r
+ std::lower_bound(m_map.begin(), m_map.end(), mapping)\r
+ );\r
+ m_map.insert(it, mapping);\r
+\r
+ Log::Instance()(Log::DBG_MEMORY) <<\r
+ "Registering " << glBoy::hex(base) <<\r
+ " for " << glBoy::hex(size) << " bytes."<< std::endl;\r
}\r
\r
-void Memory::write8(address_t address, u8_t value)\r
+void\r
+MemoryMap::remove(\r
+ uint16_t base\r
+ )\r
{\r
- m_mem[address] = value;\r
+ MapType::iterator it(m_map.begin());\r
+ for (; it != m_map.end() && it->base != base ; ++it) ;\r
+\r
+ if (it != m_map.end())\r
+ {\r
+ Log::Instance()(Log::DBG_MEMORY) <<\r
+ "Removing " << glBoy::hex(it->base) <<\r
+ " for " << glBoy::hex(it->size) << " bytes."<< std::endl;\r
+\r
+ m_map.erase(it);\r
+ }\r
}\r
\r
-void Memory::write8(address_t address, offset_t offset, u8_t value)\r
+\r
+const MemoryMap::MappedArea&\r
+MemoryMap::get(uint16_t address) const\r
{\r
- m_mem[address + offset] = value;\r
+\r
+/*\r
+ MappedArea test(address, 0, std::shared_ptr<Memory>());\r
+ MapType::const_iterator it(\r
+ std::lower_bound(m_map.begin(), m_map.end(), test)\r
+ );\r
+\r
+\r
+ if (it != m_map.end() && it->base == address)\r
+ {\r
+ return *it;\r
+ }\r
+ else if (it != m_map.begin())\r
+ {\r
+ --it;\r
+ if (address < it->base + static_cast<uint32_t>(it->size))\r
+ {\r
+ return *it;\r
+ }\r
+ }\r
+*/\r
+\r
+\r
+ for (\r
+ MapType::const_iterator it(m_map.begin());\r
+ it != m_map.end();\r
+ ++it\r
+ )\r
+ {\r
+ if ((address >= it->base) &&\r
+ (address < it->base + static_cast<uint32_t>(it->size))\r
+ )\r
+ {\r
+ return *it;\r
+ }\r
+ }\r
+\r
+ Log::Instance()(Log::DBG_MEMORY) <<\r
+ "Returning NULL memory for address " << glBoy::hex(address) << std::endl;\r
+\r
+ static NullMemory s_null;\r
+ static MappedArea s_nullArea(\r
+ 0, 0, std::shared_ptr<Memory>(&s_null, NullDeleter())\r
+ );\r
+ return s_nullArea;\r
}\r
\r
-u16_t Memory::read16(address_t address)\r
+uint8_t\r
+MemoryMap::read8(DWORD address, int8_t offset)\r
{\r
- return Z80ToHost(&m_mem[address]);\r
+ uint16_t offsetAddress(address.host() + offset);\r
+ const MappedArea& mapping(get(offsetAddress));\r
+ return mapping.memory->read8(offsetAddress - mapping.base);\r
}\r
\r
-u16_t Memory::read16(address_t address, offset_t offset)\r
+DWORD\r
+MemoryMap::read16(DWORD address, int8_t offset)\r
{\r
- return Z80ToHost(&m_mem[address + offset]);\r
+ uint16_t offsetAddress(address.host() + offset);\r
+\r
+ const MappedArea& loMap(get(offsetAddress));\r
+ uint8_t lo(loMap.memory->read8(offsetAddress - loMap.base));\r
+\r
+ ++offsetAddress;\r
+ const MappedArea& hiMap(get(offsetAddress));\r
+ uint16_t hi(hiMap.memory->read8(offsetAddress - hiMap.base));\r
+\r
+ return htoz((hi << 8) | lo);\r
}\r
\r
-void Memory::write16(address_t address, u16_t value)\r
+void\r
+MemoryMap::write8(DWORD address, uint8_t value)\r
{\r
- hostToZ80(value, &m_mem[address]);\r
+ write8(address, 0, value);\r
}\r
\r
-void Memory::write16(address_t address, offset_t offset, u16_t value)\r
+void\r
+MemoryMap::write8(DWORD address, int8_t offset, uint8_t value)\r
{\r
- hostToZ80(value, &m_mem[address + offset]);\r
+ uint16_t offsetAddress(address.host() + offset);\r
+ const MappedArea& mapping(get(offsetAddress));\r
+ mapping.memory->write8(offsetAddress - mapping.base, value);\r
}\r
\r
+void\r
+MemoryMap::write16(DWORD address, DWORD value)\r
+{\r
+ write16(address, 0, value);\r
+}\r
+\r
+void\r
+MemoryMap::write16(DWORD address, int8_t offset, DWORD value)\r
+{\r
+ uint16_t offsetAddress(address.host() + offset);\r
+ const MappedArea& loMap(get(offsetAddress));\r
+ loMap.memory->write8(offsetAddress - loMap.base, value.l);\r
+\r
+ ++offsetAddress;\r
+ const MappedArea& hiMap(get(offsetAddress));\r
+ hiMap.memory->write8(offsetAddress - hiMap.base, value.h);\r
+}\r
+\r
+void\r
+MemoryMap::copy(uint8_t* dest, uint16_t srcAddress, int bytes)\r
+{\r
+ const MappedArea& srcMap(get(srcAddress));\r
+\r
+ assert((srcAddress + bytes) <= (srcMap.base + srcMap.size));\r
+\r
+ srcMap.memory->copy(dest, srcAddress - srcMap.base, bytes);\r
+}\r
\r
-void Memory::copy(address_t src, address_t dst, address_t count)\r
+void\r
+MemoryMap::Memory::copy(uint8_t* dest, uint16_t address, int bytes)\r
{\r
- memcpy(&m_mem[dst], &m_mem[src], count);\r
+ for (int i = 0; i < bytes; ++i)\r
+ {\r
+ dest[i] = read8(address + i);\r
+ }\r
}\r
\r
-/** \r
- * Flat memory-model emulation.\r
- *\r
- * Authors: Michael McMaster <email@michaelmcmaster.name>\r
- * Copyright: Michael McMaster <email@michaelmcmaster.name>\r
- */\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>\r
+//\r
+// This file is part of glBoy.\r
+//\r
+// glBoy is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// glBoy is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.\r
\r
-#include "bits/Z80.hh"\r
-#include "ByteOrder.hh"\r
-#include "Memory.hh"\r
+#ifndef GLBOY_MEMORYMAP_HH\r
+#define GLBOY_MEMORYMAP_HH\r
\r
-#include <cstring>\r
+#include "glBoy.hh"\r
+#include "DWORD.hh"\r
\r
-using namespace Z80;\r
-\r
-Memory::Memory(address_t initialSize)\r
+#include <memory>\r
+#include <set>\r
+#include <vector>\r
+namespace glBoy\r
{\r
- m_mem.resize(initialSize);\r
-}\r
\r
-u8_t Memory::read8(address_t address)\r
+class MemoryMap\r
{\r
- return m_mem[address];\r
-}\r
+public:\r
+ class Memory\r
+ {\r
+ public:\r
+ virtual ~Memory() {}\r
\r
-u8_t Memory::read8(address_t address, offset_t offset)\r
-{\r
- return m_mem[address + offset];\r
-}\r
+ virtual uint8_t read8(uint16_t address) = 0;\r
+ virtual void write8(uint16_t address, uint8_t value) = 0;\r
+ virtual void copy(uint8_t* dest, uint16_t address, int bytes);\r
+ };\r
\r
-void Memory::write8(address_t address, u8_t value)\r
-{\r
- m_mem[address] = value;\r
-}\r
+ MemoryMap();\r
\r
-void Memory::write8(address_t address, offset_t offset, u8_t value)\r
-{\r
- m_mem[address + offset] = value;\r
-}\r
+ void map(\r
+ uint16_t base,\r
+ uint16_t size,\r
+ const std::shared_ptr<Memory>& mem\r
+ );\r
\r
-u16_t Memory::read16(address_t address)\r
-{\r
- return Z80ToHost(&m_mem[address]);\r
-}\r
+ void remove(uint16_t base);\r
\r
-u16_t Memory::read16(address_t address, offset_t offset)\r
-{\r
- return Z80ToHost(&m_mem[address + offset]);\r
-}\r
+ uint8_t read8(DWORD address, int8_t offset = 0);\r
+ void write8(DWORD address, uint8_t value);\r
+ void write8(DWORD address, int8_t offset, uint8_t value);\r
\r
-void Memory::write16(address_t address, u16_t value)\r
-{\r
- hostToZ80(value, &m_mem[address]);\r
-}\r
+ DWORD read16(DWORD address, int8_t offset = 0);\r
+ void write16(DWORD address, DWORD value);\r
+ void write16(DWORD address, int8_t offset, DWORD value);\r
\r
-void Memory::write16(address_t address, offset_t offset, u16_t value)\r
-{\r
- hostToZ80(value, &m_mem[address + offset]);\r
-}\r
+ void copy(uint8_t* dest, uint16_t srcAddress, int bytes);\r
\r
+private:\r
+ struct MappedArea\r
+ {\r
+ MappedArea(\r
+ uint16_t _base, uint16_t _size, std::shared_ptr<Memory> _mem\r
+ ) :\r
+ base(_base), size(_size), memory(_mem) {}\r
\r
-void Memory::copy(address_t src, address_t dst, address_t count)\r
-{\r
- memcpy(&m_mem[dst], &m_mem[src], count);\r
-}\r
+ uint16_t base;\r
+ uint16_t size;\r
+ std::shared_ptr<Memory> memory;\r
+\r
+ bool operator<(const MappedArea& b) const { return base < b.base; }\r
+ };\r
+ const MappedArea& get(uint16_t address) const;\r
+\r
+ typedef std::vector<MappedArea> MapType;\r
+ MapType m_map;\r
+ //std::set<MappedArea> m_map;\r
+};\r
+\r
+} // namespace glBoy\r
+\r
+#endif\r
\r
-/** \r
- * Flat memory-model emulation.\r
- *\r
- * Authors: Michael McMaster <email@michaelmcmaster.name>\r
- * Copyright: Michael McMaster <email@michaelmcmaster.name>\r
- */\r
-\r
-#include "mm80.hh"\r
-#include "Memory.hh"\r
-\r
-using namespace mm80;\r
-\r
-Memory::Memory(address_t initialSize)\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>\r
+//\r
+// This file is part of glBoy.\r
+//\r
+// glBoy is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// glBoy is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.\r
+\r
+#include "glBoy.hh"\r
+#include "RAM.hh"\r
+\r
+#include <string.h>\r
+\r
+using namespace glBoy;\r
+\r
+RAM::RAM(uint16_t size)\r
{\r
- m_mem.resize(initialSize);\r
+ m_mem.resize(size);\r
}\r
\r
uint8_t\r
-Memory::read8(DWORD address, int8_t offset)\r
+RAM::read8(uint16_t address)\r
{\r
- return m_mem[address.host() + offset];\r
-}\r
-\r
-DWORD\r
-Memory::read16(DWORD address, int8_t offset)\r
-{\r
- return CreateDWORD(&m_mem[address.host() + offset]);\r
+ return m_mem[address];\r
}\r
\r
void\r
-Memory::write8(DWORD address, uint8_t value)\r
+RAM::write8(uint16_t address, uint8_t value)\r
{\r
- m_mem[address.host()] = value;\r
+ m_mem[address] = value;\r
}\r
\r
-void\r
-Memory::write8(DWORD address, int8_t offset, uint8_t value)\r
-{\r
- m_mem[address.host() + offset] = value;\r
-}\r
-\r
-void\r
-Memory::write16(DWORD address, DWORD value)\r
-{\r
- m_mem[address.host()] = value.l;\r
- m_mem[address.host() + 1] = value.h;\r
-}\r
\r
void\r
-Memory::write16(DWORD address, int8_t offset, DWORD value)\r
+RAM::copy(uint8_t* dest, uint16_t address, int bytes)\r
{\r
- m_mem[address.host() + offset] = value.l;\r
- m_mem[address.host() + offset + 1] = value.h;\r
+ memcpy(dest, &m_mem[0] + address, bytes);\r
}\r
\r
-/** \r
- * Flat memory-model emulation.\r
- *\r
- * Authors: Michael McMaster <email@michaelmcmaster.name>\r
- * Copyright: Michael McMaster <email@michaelmcmaster.name>\r
- */\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>\r
+//\r
+// This file is part of glBoy.\r
+//\r
+// glBoy is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// glBoy is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.\r
+\r
+#ifndef GLBOY_RAM_HH\r
+#define GLBOY_RAM_HH\r
+\r
+#include "glBoy.hh"\r
+#include "MemoryMap.hh"\r
\r
-#ifndef MM80_MEMORYMAP_HH\r
-#define MM80_MEMORYMAP_HH\r
-\r
-#include "mm80.hh"\r
-#include "DWORD.hh"\r
-\r
-#include <memory>\r
#include <vector>\r
-namespace mm80\r
+namespace glBoy\r
{\r
\r
-class MemoryMap\r
+class RAM : public MemoryMap::Memory\r
{\r
public:\r
- class Memory\r
- {\r
- public:\r
- virtual ~Memory;\r
-\r
- uint8_t read8(DWORD address) = 0;\r
- void write8(DWORD address, uint8_t value) = 0;\r
- };\r
-\r
- Memory();\r
+ RAM(uint16_t size);\r
+ virtual uint8_t read8(uint16_t address);\r
+ virtual void write8(uint16_t address, uint8_t value);\r
+ virtual void copy(uint8_t* dest, uint16_t address, int bytes);\r
\r
- void map(\r
- address_t base,\r
- address_t size,\r
- const std::tr1::shared_ptr<Memory>& mem\r
- );\r
-\r
- uint8_t read8(DWORD address, int8_t offset = 0);\r
- void write8(DWORD address, uint8_t value);\r
- void write8(DWORD address, int8_t offset, uint8_t value);\r
-\r
- DWORD read16(DWORD address, int8_t offset = 0);\r
- void write16(DWORD address, DWORD value);\r
- void write16(DWORD address, int8_t offset, DWORD value);\r
\r
private:\r
-\r
- std::vector<uint8_t> m_map;\r
std::vector<uint8_t> m_mem;\r
};\r
\r
-} // namespace mm80\r
+} // namespace glBoy\r
\r
#endif\r
\r
-Third attempt at a Z80 CPU emulator.
-email@michaelmcmaster.name 2008
+glBoy
+Original gameboy emulator.
-Language choice: C++
+Usage:
+Usage: ./glBoy [--scale ratio] [--width w] [--height h] rom.gb
+
+ --scale (Default=4.0)
+ Multiplier applied to the display output dimensions.
+ --width
+ Set the display output dimensions directly
+ --height
+ Set the display output dimensions directly
+
+
+Features
+ - MBC1 emulation
+ - Sound
+ - OpenGL hq4x scaling
+
+Known Issues
+ - The emulator crashes on some games due to incorrect z80
+ emulation.
+
+Missing
+ - MBC2/3/4 emulation
+ - Saved games
+ - Serial I/O
-#include "mm80.hh"\r
-#include "RAM.hh"\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>\r
+//\r
+// This file is part of glBoy.\r
+//\r
+// glBoy is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// glBoy is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.\r
\r
-using namespace mm80;\r
+#include "glBoy.hh"\r
+#include "ROM.hh"\r
+#include "Log.hh"\r
\r
-RAM::RAM(uint16_t size)\r
+#include <string.h>\r
+\r
+#include <cassert>\r
+\r
+using namespace glBoy;\r
+\r
+ROM::ROM(const uint8_t* data, uint16_t size) :\r
+ m_mem(data, data + size)\r
{\r
- m_mem.resize(size);\r
}\r
\r
uint8_t\r
-RAM::read8(uint16_t address)\r
+ROM::read8(uint16_t address)\r
{\r
return m_mem[address];\r
}\r
\r
void\r
-RAM::write8(uint16_t address, uint8_t value)\r
+ROM::write8(uint16_t address, uint8_t)\r
+{\r
+ //assert(false);\r
+ Log::Instance()(Log::DBG_MEMORY) <<\r
+ "Attempt to write to read-only address " << address <<\r
+ std::endl;\r
+}\r
+\r
+void\r
+ROM::copy(uint8_t* dest, uint16_t address, int bytes)\r
{\r
- m_mem[address] = value;\r
+ memcpy(dest, &m_mem[0] + address, bytes);\r
}\r
\r
-#ifndef MM80_RAM_HH\r
-#define MM80_RAM_HH\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>\r
+//\r
+// This file is part of glBoy.\r
+//\r
+// glBoy is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// glBoy is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.\r
\r
-#include "mm80.hh"\r
+#ifndef GLBOY_ROM_HH\r
+#define GLBOY_ROM_HH\r
+\r
+#include "glBoy.hh"\r
#include "MemoryMap.hh"\r
\r
#include <vector>\r
-namespace mm80\r
+namespace glBoy\r
{\r
\r
class ROM : public MemoryMap::Memory\r
{\r
public:\r
- RAM(uint16_t size);\r
- virtual uint8_t read8(uint16_t address) = 0;\r
- virtual void write8(uint16_t address, uint8_t value) = 0;\r
+ ROM(const uint8_t* data, uint16_t size);\r
+ virtual uint8_t read8(uint16_t address);\r
+ virtual void write8(uint16_t address, uint8_t value);\r
+ virtual void copy(uint8_t* dest, uint16_t address, int bytes);\r
\r
private:\r
std::vector<uint8_t> m_mem;\r
};\r
\r
-} // namespace mm80\r
+} // namespace glBoy\r
\r
#endif\r
\r
-/** \r
- * Authors: Michael McMaster <email@michaelmcmaster.name>\r
- * Copyright: Michael McMaster <email@michaelmcmaster.name>\r
- */ \r
-#include "bits/Z80.hh"\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>\r
+//\r
+// This file is part of glBoy.\r
+//\r
+// glBoy is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// glBoy is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.\r
+\r
+#include "glBoy.hh"\r
#include "Registers.hh"\r
\r
#include "Core.hh"\r
\r
-using namespace Z80;\r
-\r
-Flags::Flags() :\r
- C(0),\r
- N(0),\r
- P(0),\r
- U3(0),\r
- H(0),\r
- U5(0),\r
- Z(0),\r
- S(0)\r
-{\r
- // do nothing\r
-}\r
+#include <string.h>\r
+\r
+using namespace glBoy;\r
\r
-op8_t Flags::operator()(op8_t operand, const Core& context)\r
+void\r
+Flags::set(op8_t operand, const Core& context)\r
{\r
- S = operand < 0 ? 1 : 0;\r
+ S = static_cast<int8_t>(operand) < 0 ? 1 : 0;\r
Z = operand == 0 ? 1 : 0;\r
- U5 = operand & 0b00010000 >> 4;\r
+ U5 = (operand & 0b00010000) >> 4;\r
H = 0;\r
- U3 = operand & 0b00000100 >> 2;\r
+ U3 = (operand & 0b00000100) >> 2;\r
P = context.getIFF2();\r
N = 0;\r
// C is not affected\r
- return operand;\r
}\r
\r
-Registers::Registers() :\r
- AF(0),\r
- BC(0),\r
- DE(0),\r
- HL(0),\r
- I(0),\r
- R(0),\r
- IX(0),\r
- IY(0),\r
- SP(0),\r
- PC(0)\r
+void\r
+Flags::setCounter(DWORD BC)\r
{\r
+ // S is not affected\r
+ // Z is not affected\r
+ U5 = (BC.host() & 0b00010000) >> 4;\r
+ H = 0;\r
+ U3 = (BC.host() & 0b00000100) >> 2;\r
+ P = BC.host() ? 1 : 0;\r
+ N = 0;\r
+ // C is not affected\r
}\r
+\r
+void\r
+Flags::setSub(op8_t A, op8_t B, DWORD BC)\r
+{\r
+ S = (A > B) ? 1 : 0;\r
+ Z = (A == B) ? 1 : 0;\r
+ U5 = (A & 0b00010000) >> 4;\r
+ H = (B & 0xF) > (A & 0xF);\r
+ U3 = (A & 0b00000100) >> 2;\r
+ P = BC.host() ? 1 : 0;\r
+ N = 1;\r
+ // C is not affected\r
+}\r
+\r
+void\r
+Registers::reset()\r
+{\r
+ memset(this, 0, sizeof(Registers));\r
+}\r
+\r
-/** \r
- * Emulated representation of the Z80 CPU registers.\r
- *\r
- * The Z80 register emulation allows efficient access to both the 8-bit ,\r
- * registers an the 16-bit register pairs. This is achieved by modifying the \r
- * structure layout according to the endianess of the host.\r
- *\r
- * The Z80 cpu supports switching between multiple register sets. Each Register\r
- * object only stores the details of a single register set. \r
- *\r
- * Authors: Michael McMaster <email@michaelmcmaster.name>\r
- * Copyright: Michael McMaster <email@michaelmcmaster.name>\r
- */ \r
-#pragma once\r
-\r
-#include "bits/Z80.hh"\r
-\r
-namespace Z80\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>\r
+//\r
+// This file is part of glBoy.\r
+//\r
+// glBoy is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// glBoy is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.\r
+\r
+#ifndef GLBOY_Registers_hh\r
+#define GLBOY_Registers_hh\r
+\r
+#include "glBoy.hh"\r
+#include "DWORD.hh"\r
+\r
+namespace glBoy\r
{\r
\r
class Core;\r
* Z Zero Flag\r
* S Sign Flag. MSB of Flag register\r
*/\r
-struct Flags\r
+struct __attribute__ ((__packed__)) Flags\r
{\r
- unsigned C:1; /// Carry. LSB of Flag register\r
- unsigned N:1; /// Add/Subtract\r
- unsigned P:1; /// (V) Parity/Overflow\r
- unsigned U3:1; /// 3rd bit of last 8bit op that altered flags\r
- unsigned H:1; /// Half-Carry (BCD)\r
- unsigned U5:1; /// 5th bit of last 8bit op that altered flags\r
- unsigned Z:1; /// Zero Flag\r
- unsigned S:1; /// Sign Flag. MSB of Flag register\r
+ union\r
+ {\r
+ struct __attribute__ ((__packed__))\r
+ {\r
+ unsigned C:1; /// Carry. LSB of Flag register\r
+ unsigned N:1; /// Add/Subtract\r
+ unsigned P:1; /// (V) Parity/Overflow\r
+ unsigned U3:1; /// 3rd bit of last 8bit op that altered flags\r
+ unsigned H:1; /// Half-Carry (BCD)\r
+ unsigned U5:1; /// 5th bit of last 8bit op that altered flags\r
+ unsigned Z:1; /// Zero Flag\r
+ unsigned S:1; /// Sign Flag. MSB of Flag register\r
+ };\r
+ reg8_t byte;\r
+ };\r
\r
- Flags();\r
+ // No ctor allowed for POD.\r
+ void reset() { byte = 0; }\r
\r
// Update the flags based on an operands value (eg. for simple assignment)\r
- op8_t operator()(op8_t operand, const Core& context);\r
+ void set(op8_t operand, const Core& context);\r
+\r
+ // Update the flags when changing BC\r
+ void setCounter(DWORD BC);\r
+\r
+ // Set from A - B\r
+ void setSub(op8_t A, op8_t B, DWORD BC);\r
};\r
\r
/** Z80 register access.\r
*/\r
struct Registers\r
{\r
- Registers();\r
+ // No ctor allowed for POD.\r
+ void reset();\r
\r
- // Storage Order allows for 16 bit pairs to be accessed naturally.\r
- // ie. AF, BC, DE, HL\r
- // eg. AF = 0x1234, A will == 0x12.\r
-#ifdef HOST_LITTLE_ENDIAN\r
#define Z80_REG_STRUCT(h,l) \\r
struct \\r
{ \\r
l; \\r
h; \\r
};\r
-#else\r
-#define Z80_REG_STRUCT(h,l) \\r
- struct \\r
- { \\r
- h; \\r
- l; \\r
- };\r
-#endif\r
\r
// Note: The order of these unions/structs is important, as\r
// offsets are taken into the Register struct\r
- reg8_t[0] begin8;\r
- reg16_t[0] begin16;\r
+ reg8_t begin8[0];\r
+ DWORD begin16[0];\r
\r
union\r
{\r
Z80_REG_STRUCT(reg8_t B, reg8_t C);\r
- reg16_t BC;\r
+ DWORD BC;\r
};\r
\r
union\r
{\r
Z80_REG_STRUCT(reg8_t D, reg8_t E);\r
- reg16_t DE;\r
+ DWORD DE;\r
};\r
\r
union\r
{\r
Z80_REG_STRUCT(reg8_t H, reg8_t L);\r
- reg16_t HL;\r
+ DWORD HL;\r
};\r
\r
union\r
{\r
- Z80_REG_STRUCT(reg8_t A, Flags F,);\r
- reg16_t AF;\r
+ Z80_REG_STRUCT(reg8_t A, Flags F);\r
+ DWORD AF;\r
};\r
\r
\r
// Special Purpose\r
reg8_t I; // Interrupt Vector\r
reg8_t R; // Memory Refresh\r
- reg16_t IX; // Index Register\r
- reg16_t IY; // Index Register\r
- reg16_t SP; // Stack Pointer\r
- reg16_t PC; // Program Counter\r
+ DWORD IX; // Index Register\r
+ DWORD IY; // Index Register\r
+ DWORD SP; // Stack Pointer\r
+ DWORD PC; // Program Counter\r
};\r
\r
+} // namespace glBoy\r
+\r
+#endif\r
+\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of glBoy.
+//
+// glBoy is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// glBoy is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
+
+#include "ALU.hh"
+#include "DAA.hh"
+
+#include "stddef.h"
+
+#include <cassert>
+#include <iostream>
+#include <ios>
+
+using namespace glBoy;
+
+void add(uint8_t a, uint8_t b)
+{
+ Flags f;
+ f.reset();
+ uint8_t result(ALU::add(a, b, f));
+
+ uint16_t testFlags;
+ uint8_t testResult;
+ __asm__ (
+ "movb %2, %%al\n\t"
+ "addb %3, %%al\n\t"
+ "pushf\n\t"
+ "mov %%al, %1\n\t"
+ "pop %%ax\n\t"
+ "movw %%ax, %0\n\t"
+ : "=g" (testFlags), "=g" (testResult)
+ : "g" (a), "g" (b)
+ : "eax", "cc"
+ );
+
+ assert(result == testResult);
+ assert(f.S == (testFlags & 0x80) >> 7);
+ assert(f.Z == (testFlags & 0x40) >> 6);
+ assert(f.H == (testFlags & 0x10) >> 4);
+ assert(f.P == (testFlags & 0x800) >> 11); // 0x4 for parity
+ assert(f.C == (testFlags & 0x1));
+}
+
+void sub(uint8_t a, uint8_t b)
+{
+ Flags f;
+ f.reset();
+ uint8_t result(ALU::sub(a, b, f));
+
+ uint16_t testFlags;
+ uint8_t testResult;
+ __asm__ (
+ "movb %2, %%al\n\t"
+ "subb %3, %%al\n\t"
+ "pushf\n\t"
+ "mov %%al, %1\n\t"
+ "pop %%ax\n\t"
+ "movw %%ax, %0\n\t"
+ : "=g" (testFlags), "=g" (testResult)
+ : "g" (a), "g" (b)
+ : "eax", "cc"
+ );
+
+ assert(result == testResult);
+ assert(f.S == (testFlags & 0x80) >> 7);
+ assert(f.Z == (testFlags & 0x40) >> 6);
+ assert(f.H == (testFlags & 0x10) >> 4);
+ assert(f.P == (testFlags & 0x800) >> 11); // 0x4 for parity
+ assert(f.C == (testFlags & 0x1));
+}
+
+void bitwiseAnd(uint8_t a, uint8_t b)
+{
+ Flags f;
+ f.reset();
+ uint8_t result(ALU::bitwiseAnd(a, b, f));
+
+ uint16_t testFlags;
+ uint8_t testResult;
+ __asm__ (
+ "movb %2, %%al\n\t"
+ "andb %3, %%al\n\t"
+ "pushf\n\t"
+ "mov %%al, %1\n\t"
+ "pop %%ax\n\t"
+ "movw %%ax, %0\n\t"
+ : "=g" (testFlags), "=g" (testResult)
+ : "g" (a), "g" (b)
+ : "eax", "cc"
+ );
+
+ assert(result == testResult);
+ assert(f.S == (testFlags & 0x80) >> 7);
+ assert(f.Z == (testFlags & 0x40) >> 6);
+ //assert(f.H == (testFlags & 0x10) >> 4);
+ // H is always set for AND for Z80, but not X86
+ assert(f.H);
+ assert(f.P == (testFlags & 0x04) >> 2); // 0x800 for overflow
+ assert(f.C == (testFlags & 0x1));
+}
+
+void bitwiseOr(uint8_t a, uint8_t b)
+{
+ Flags f;
+ f.reset();
+ uint8_t result(ALU::bitwiseOr(a, b, f));
+
+ uint16_t testFlags;
+ uint8_t testResult;
+ __asm__ __volatile__ (
+ "movb %2, %%al\n\t"
+ "orb %3, %%al\n\t"
+ "pushf\n\t"
+ "mov %%al, %1\n\t"
+ "pop %%ax\n\t"
+ "movw %%ax, %0\n\t"
+ : "=g" (testFlags), "=g" (testResult)
+ : "g" (a), "g" (b)
+ : "eax", "cc"
+ );
+
+ assert(result == testResult);
+ assert(f.S == (testFlags & 0x80) >> 7);
+ assert(f.Z == (testFlags & 0x40) >> 6);
+ assert(f.H == (testFlags & 0x10) >> 4);
+ assert(f.P == (testFlags & 0x04) >> 2); // 0x800 for overflow
+ assert(f.C == (testFlags & 0x1));
+}
+
+void bitwiseXor(uint8_t a, uint8_t b)
+{
+ Flags f;
+ f.reset();
+ uint8_t result(ALU::bitwiseXor(a, b, f));
+
+ uint16_t testFlags;
+ uint8_t testResult;
+ __asm__ __volatile__(
+ "movb %2, %%al\n\t"
+ "xorb %3, %%al\n\t"
+ "pushf\n\t"
+ "mov %%al, %1\n\t"
+ "pop %%ax\n\t"
+ "movw %%ax, %0\n\t"
+ : "=g" (testFlags), "=g" (testResult)
+ : "g" (a), "g" (b)
+ : "eax", "cc"
+ );
+
+ assert(result == testResult);
+ assert(f.S == (testFlags & 0x80) >> 7);
+ assert(f.Z == (testFlags & 0x40) >> 6);
+ assert(f.H == (testFlags & 0x10) >> 4);
+ assert(f.P == (testFlags & 0x04) >> 2); // 0x800 for overflow
+ assert(f.C == (testFlags & 0x1));
+}
+
+uint8_t daa(uint8_t a, uint8_t b)
+{
+ Flags f;
+ f.reset();
+ uint8_t tmp(ALU::add(a, b, f));
+
+ return DAA(tmp, f);
+
+}
+
+uint8_t daaSub(uint8_t a, uint8_t b)
+{
+ Flags f;
+ f.reset();
+ uint8_t tmp(ALU::sub(a, b, f));
+
+ return DAA(tmp, f);
+
+}
+
+int main()
+{
+ // Ensure packed representations are the expected size,
+ // and usable within union types.
+ assert(sizeof(DWORD) == 2);
+ assert(sizeof(Flags) == 1);
+
+ // Ensure 16bit registers are aligned to 16-bit boundaries.
+ assert(offsetof(Registers, BC) == 0);
+ assert(offsetof(Registers, AF) == 6);
+
+ add(0, 0);
+ add(1, 1);
+ add(-1, 1);
+ add(-128, 127);
+ add(127, 1);
+ add(-128, -1);
+ add(5, 5);
+ add(15, 1);
+ add(15, 128);
+
+ Flags f;
+ f.reset();
+ assert(ALU::add(htoz(0x1000), htoz(0x5555), f).host() == 0x6555);
+ assert(!f.C);
+ assert(ALU::add(htoz(0x1000), htoz(0xF555), f).host() == 0x0555);
+ assert(f.C);
+
+
+ sub(0, 0);
+ sub(1, 1);
+ sub(-1, 1);
+ sub(-128, 127);
+ sub(127, 1);
+ sub(-128, -1);
+ sub(5, 5);
+ sub(15, 1);
+ sub(15, 128);
+ sub(0, 0xb);
+
+ bitwiseAnd(0, 0);
+ bitwiseAnd(1, 1);
+ bitwiseAnd(-1, 1);
+ bitwiseAnd(-128, 127);
+ bitwiseAnd(127, 1);
+ bitwiseAnd(-128, -1);
+ bitwiseAnd(5, 5);
+ bitwiseAnd(15, 1);
+ bitwiseAnd(15, 128);
+
+ bitwiseOr(0, 0);
+ bitwiseOr(1, 1);
+ bitwiseOr(-1, 1);
+ bitwiseOr(-128, 127);
+ bitwiseOr(127, 1);
+ bitwiseOr(-128, -1);
+ bitwiseOr(5, 5);
+ bitwiseOr(15, 1);
+ bitwiseOr(15, 128);
+
+ bitwiseXor(0, 0);
+ bitwiseXor(1, 1);
+ bitwiseXor(-1, 1);
+ bitwiseXor(-128, 127);
+ bitwiseXor(127, 1);
+ bitwiseXor(-128, -1);
+ bitwiseXor(5, 5);
+ bitwiseXor(15, 1);
+ bitwiseXor(15, 128);
+
+ assert(daa(0x01, 0x01) == 0x02);
+ assert(daa(0x01, 0x09) == 0x10);
+ assert(daa(0x01, 0x10) == 0x11);
+ assert(daa(0x09, 0x09) == 0x18);
+ assert(daa(0x50, 0x49) == 0x99);
+ assert(daa(0x99, 0x0) == 0x99);
+ assert(daa(0x27, 0x27) == 0x54);
+ assert(daa(0x90, 0x10) == 0x0); // Carry from 0x100
+ assert(daa(0x99, 0x99) == 0x98); // Carry from 0x198
+
+ assert(daaSub(0x10, 0x01) == 0x09);
+ assert(daaSub(0x10, 0x20) == 0x90);
+
+ std::cout << "Tests complete" << std::endl;
+}
-/** \r
- * Z80 CPU emulator\r
- *\r
- * Authors: Michael McMaster <email@michaelmcmaster.name>\r
- * Copyright: Michael McMaster <email@michaelmcmaster.name>\r
- *\r
- * Description goes here\r
- */ \r
-#pragma once\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>\r
+//\r
+// This file is part of glBoy.\r
+//\r
+// glBoy is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// glBoy is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.\r
+\r
+#ifndef GLBOY_HH\r
+#define GLBOY_HH\r
\r
#include <cstdint>\r
+#include <iostream>\r
\r
-namespace Z80\r
+namespace glBoy\r
{\r
- typedef uint8_t u8_t;\r
- typedef uint16_t u16_t;\r
-\r
typedef uint8_t op8_t;\r
typedef uint8_t op16_t;\r
\r
typedef uint16_t reg16_t;\r
\r
typedef uint16_t address_t;\r
- typedef int16_t offset_t;\r
\r
typedef bool bit;\r
\r
- typedef uint64_t cycle_t;\r
+ typedef int64_t cycle_t;\r
\r
#if !(defined(HOST_LITTLE_ENDIAN) || defined(HOST_BIG_ENDIAN))\r
- #warn Assuming little-endian\r
+ #warning Assuming little-endian\r
#define HOST_LITTLE_ENDIAN\r
#endif\r
+\r
}\r
+\r
+#endif\r
+\r
-//hq4x filter demo program\r
+// hq4x OpenGL Shader\r
//----------------------------------------------------------\r
-//Copyright (C) 2003 MaxSt ( maxst@hiend3d.com )\r
-\r
+// Copyright (C) 2003 MaxSt ( maxst@hiend3d.com )\r
+// Copyright 2011 Michael McMaster <michael@codesrc.com>\r
+//\r
//This program is free software; you can redistribute it and/or\r
-//modify it under the terms of the GNU Lesser General Public\r
+//modify it under the terms of the GNU General Public\r
//License as published by the Free Software Foundation; either\r
-//version 2.1 of the License, or (at your option) any later version.\r
+//version 3 of the License, or (at your option) any later version.\r
//\r
//This program is distributed in the hope that it will be useful,\r
//but WITHOUT ANY WARRANTY; without even the implied warranty of\r
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
//Lesser General Public License for more details.\r
//\r
-//You should have received a copy of the GNU Lesser General Public\r
+//You should have received a copy of the GNU General Public\r
//License along with this program; if not, write to the Free Software\r
//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
+//----------------------------------------------------------\r
+//\r
+// Changes from original code:\r
+// - Converted to GLSL, inspired by the XNA port of hqNx by\r
+// Phill Djonov <phill@vec3.net> (http://github.com/pdjonov/hqnx/wiki)\r
+// - Applied clause 3 of LGPL 2.1 to alter license to GNU GPL 3. Updated\r
+// License notice at top of file as required by clause 3.\r
\r
+#define GL_GLEXT_PROTOTYPES\r
\r
-// Converted to grayscale 2010 by Michael McMaster (Michael.McMaster@gmail.com)\r
-// Then converted to generate a lookup table, inspired by\r
-// Phill Djonov <phill@vec3.net> XNA implementation (http://github.com/pdjonov/hqnx/wiki)\r
-// -But, each 3x3 weight matrix is packed into a single 32bit RGBA value,\r
-// instead of 2 RGBA's.\r
+#include "hq4x.hh"\r
\r
-/*\r
-Each weight = number between 0 and 8 inclusive. 4 bits.\r
-Treated as weight/8f\r
+#include <GL/glu.h>\r
+#include <GL/glext.h>\r
\r
-So, for each input pixel, build a 3x3 matrix of surrounding\r
-pixels.\r
- 1 2 3 12bits\r
- 4 5 6 12bits\r
- 7 8 9 12bits\r
- ------\r
- 36 bits.\r
-hmm. Skip the 5th weight. The matrix has to Sum to 8, so we can\r
-make up 1 missing value.\r
- 1 2 3 12bits\r
- 4 _ 6 8bits\r
- 7 8 9 12bits\r
- ------\r
- 32 bits TICK\r
+#include <cassert>\r
+#include <cstdint>\r
+#include <cmath>\r
+#include <cstdlib>\r
+#include <iomanip>\r
+#include <iostream>\r
\r
-We then produce a 4x4 matrix of output pixels.\r
+using namespace glBoy;\r
\r
-Need 256 sets of 16 x 3x3 matrices or,\r
+namespace\r
+{\r
\r
-1) 3x3 matrix fix in 32bits. So just need a 256 sets of 4x4 matrix.\r
-4096 pixels.\r
+const char*\r
+s_VectorSource =\r
+"\\r
+//#version 130\n\\r
+\\r
+uniform vec4 mmTextureSize;\n\\r
+\\r
+varying vec4 c5;\n\\r
+\\r
+void main()\n\\r
+{\n\\r
+ c5 = gl_MultiTexCoord0;\n\\r
+\\r
+ gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n\\r
+}\n\\r
+";\r
\r
-2) There are 4 choices (2^4 outcomes = 16)\r
-Diff (w[2], w[6])\r
-Diff (w[6], w[8])\r
-Diff (w[8], w[4])\r
-Diff (w[4], w[2])\r
+const char*\r
+s_FragmentSource =\r
+"\\r
+// Enable bitwise operators\n\\r
+#version 130\n\\r
+\\r
+uniform sampler2D mmTexture;\n\\r
+uniform sampler2D mmLookup;\n\\r
+uniform vec4 mmTextureSize;\n\\r
+\\r
+in vec4 c5;\n\\r
+\\r
+out vec4 fragColour;\n\\r
+\\r
+vec4 RGB2YUV(in vec4 val)\n\\r
+{\n\\r
+ return mat4(\\r
+ 0.299, 0.587, 0.114, 0,\n\\r
+ -0.14713, -0.28886, 0.436, 0,\n\\r
+ 0.615, -0.51499, -0.10001, 0,\n\\r
+ 0, 0, 0, 0\n\\r
+ ) * val;\\r
+}\n\\r
+\\r
+bool Diff(in vec4 YUV1, in vec4 YUV2)\n\\r
+{\n\\r
+ return any(\n\\r
+ greaterThan(\n\\r
+ abs(YUV1 - YUV2),\n\\r
+ vec4(48.0/255.0, 7.0/255.0, 6.0/255.0, 0)\n\\r
+ ).xyz\n\\r
+ );\n\\r
+}\n\\r
+\\r
+void main()\n\\r
+{\n\\r
+ vec4 c_offsetX = vec4(1.0/(mmTextureSize.x-1), 0, 0, 0);\n\\r
+ vec4 c_offsetY = vec4(0, 1.0/(mmTextureSize.y-1), 0, 0);\n\\r
+\\r
+ vec4 w[9];\n\\r
+ w[0] = texture2DProj(mmTexture, c5 - c_offsetY - c_offsetX);\n\\r
+ w[1] = texture2DProj(mmTexture, c5 - c_offsetY);\n\\r
+ w[2] = texture2DProj(mmTexture, c5 - c_offsetY + c_offsetX);\n\\r
+\\r
+ w[3] = texture2DProj(mmTexture, c5 - c_offsetX);\n\\r
+ w[4] = texture2DProj(mmTexture, c5);\n\\r
+ w[5] = texture2DProj(mmTexture, c5 + c_offsetX);\n\\r
+\\r
+ w[6] = texture2DProj(mmTexture, c5 + c_offsetY - c_offsetX);\n\\r
+ w[7] = texture2DProj(mmTexture, c5 + c_offsetY);\n\\r
+ w[8] = texture2DProj(mmTexture, c5 + c_offsetY + c_offsetX);\n\\r
+\\r
+ vec4 YUV[9];\n\\r
+ YUV[0] = RGB2YUV(w[0]);\n\\r
+ YUV[1] = RGB2YUV(w[1]);\n\\r
+ YUV[2] = RGB2YUV(w[2]);\n\\r
+ YUV[3] = RGB2YUV(w[3]);\n\\r
+ YUV[4] = RGB2YUV(w[4]);\n\\r
+ YUV[5] = RGB2YUV(w[5]);\n\\r
+ YUV[6] = RGB2YUV(w[6]);\n\\r
+ YUV[7] = RGB2YUV(w[7]);\n\\r
+ YUV[8] = RGB2YUV(w[8]);\n\\r
+\\r
+ int pattern = 0;\n\\r
+ int flag = 1;\n\\r
+ int k;\n\\r
+ for (k = 0; k < 9; k++)\n\\r
+ {\n\\r
+ if (k == 4) continue;\n\\r
+\\r
+ if (Diff(YUV[4], YUV[k]))\n\\r
+ {\n\\r
+ pattern = pattern + flag;\n\\r
+ }\n\\r
+ flag = flag * 2;\n\\r
+ }\n\\r
+\\r
+ // k is now diff.\n\\r
+ k = 0;\n\\r
+ if (Diff(YUV[1], YUV[5]))\n\\r
+ {\n\\r
+ k = k + 1;\n\\r
+ }\n\\r
+ if (Diff(YUV[5], YUV[7]))\n\\r
+ {\n\\r
+ k = k + 2;\n\\r
+ }\n\\r
+ if (Diff(YUV[7], YUV[3]))\n\\r
+ {\n\\r
+ k = k + 4;\n\\r
+ }\n\\r
+ if (Diff(YUV[3], YUV[1]))\n\\r
+ {\n\\r
+ k = k + 8;\n\\r
+ }\n\\r
+\\r
+ vec2 lookupMax = vec2(1.0/47.0, 1.0/4095);\n\\r
+ ivec2 thisPixel = ivec2(fract(c5 * mmTextureSize) * 4.0);\n\\r
+ ivec2 lookupIndex = ivec2((thisPixel.y*4+thisPixel.x)*3, (pattern * 16) + k);\n\\r
+ vec4 weight1 = texture2D(mmLookup, lookupIndex * lookupMax);\n\\r
+ vec4 weight2 = texture2D(mmLookup, (lookupIndex + ivec2(1, 0)) * lookupMax);\n\\r
+ vec4 weight3 = texture2D(mmLookup, (lookupIndex + vec2(2, 0)) * lookupMax);\n\\r
+\\r
+ // Dodgy fix for inaccurate lookups :-( Seemed to work on Quadro NVS 290 without this\n\\r
+ float allW = dot(\n\\r
+ vec4(1,1,1,0),\n\\r
+ vec4(\n\\r
+ dot(weight1, vec4(1,1,1,0)),\n\\r
+ dot(weight2, vec4(1,1,1,0)),\n\\r
+ dot(weight3, vec4(1,1,1,0)),\n\\r
+ 0\n\\r
+ )\n\\r
+ );\n\\r
+ fragColour =\n\\r
+ w[0]*weight1.x + w[1]*weight1.y + w[2]*weight1.z +\n\\r
+ w[3]*weight2.x + w[4]*weight2.y + w[5]*weight2.z +\n\\r
+ w[6]*weight3.x + w[7]*weight3.y + w[8]*weight3.z;\n\\r
+ fragColour = fragColour / allW;\n\\r
+\\r
+//if (allW > 1.100) { fragColour = vec4(0.0,0.0,1.0,0); } \n\\r
+//if (allW < 0.9) { fragColour = vec4(0.0,1.0,0.0,0); } \n\\r
+\\r
+}\n\\r
+";\r
\r
-Layout of texture:\r
-Y = pattern # (0 -> 255)\r
-X =\r
- Top 4 bits = diff bitmask\r
- Lower 4 bits = output pixel #\r
-*/\r
+void abortGLError()\r
+{\r
+ std::cerr << "Fatal OpenGL error in hq4x shader: " <<\r
+ gluErrorString(glGetError()) << std::endl;\r
+ abort();\r
+}\r
\r
-#include "hq4x.hh"\r
+void abortShaderError(GLint shader)\r
+{\r
+ std::cerr << "Fatal error using hq4x shader: " <<\r
+ gluErrorString(glGetError()) << std::endl;\r
+\r
+ char buffer[2048];\r
+ int len;\r
+ glGetShaderInfoLog(shader, 2048, &len, &buffer[0]); \r
+ std::cerr << buffer << std::endl;\r
+\r
+ abort();\r
+}\r
+\r
+void abortProgramError(GLint program)\r
+{\r
+ std::cerr << "Fatal error using hq4x program: " <<\r
+ gluErrorString(glGetError()) << std::endl;\r
+\r
+ char buffer[2048];\r
+ int len;\r
+ glGetProgramInfoLog(program, 2048, &len, &buffer[0]); \r
+ std::cerr << buffer << std::endl;\r
+\r
+ abort();\r
+}\r
+\r
+} // namespace\r
\r
-#include <cstdint>\r
-#include <cmath>\r
-#include <cstdlib>\r
-#include <iomanip>\r
-#include <iostream>\r
\r
-enum Diff { Diff_2_6 = 1, Diff_6_8 = 2, Diff_8_4 = 4, Diff_4_2 = 8 };\r
+Hq4x::Hq4x(size_t textureWidth, size_t textureHeight) :\r
+ m_textureWidth(textureWidth),\r
+ m_textureHeight(textureHeight)\r
+{\r
+ m_vertexShader = glCreateShader(GL_VERTEX_SHADER);\r
+ m_fragShader = glCreateShader(GL_FRAGMENT_SHADER);\r
+\r
+ if (!m_vertexShader || !m_fragShader) abortGLError();\r
+\r
+ glShaderSource(m_vertexShader, 1, &s_VectorSource, NULL);\r
+ glShaderSource(m_fragShader, 1, &s_FragmentSource, NULL);\r
+\r
+ glCompileShader(m_vertexShader);\r
+ if (glGetError() != GL_NO_ERROR) abortShaderError(m_vertexShader);\r
+ glCompileShader(m_fragShader);\r
+ if (glGetError() != GL_NO_ERROR) abortShaderError(m_fragShader);\r
+\r
+ m_program = glCreateProgram();\r
+ if (!m_program) abortGLError();\r
+\r
+ glAttachShader(m_program, m_vertexShader);\r
+ if (glGetError() != GL_NO_ERROR) abortProgramError(m_program);\r
+ glAttachShader(m_program, m_fragShader);\r
+ if (glGetError() != GL_NO_ERROR) abortProgramError(m_program);\r
+ glLinkProgram(m_program);\r
+ if (glGetError() != GL_NO_ERROR) abortProgramError(m_program);\r
+\r
+ glGenTextures(1, &m_lookupTexture);\r
+ glActiveTexture(GL_TEXTURE0);\r
+ glBindTexture(GL_TEXTURE_2D, m_lookupTexture);\r
+\r
+ // Only nearest neighbour will work\r
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\r
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\r
+\r
+ // Create the texture image\r
+ glTexImage2D(\r
+ GL_TEXTURE_2D,\r
+ 0,\r
+ GL_RGBA,\r
+ 48,\r
+ 4096,\r
+ 0,\r
+ GL_RGBA,\r
+ GL_UNSIGNED_BYTE,\r
+ getHq4xLookupTable()\r
+ );\r
+ glUseProgram(m_program);\r
+ if (glGetError() != GL_NO_ERROR) abortProgramError(m_program);\r
+\r
+ glActiveTexture(GL_TEXTURE1);\r
+ glBindTexture(GL_TEXTURE_2D, m_lookupTexture);\r
+ GLint tex = glGetUniformLocation(m_program, "mmLookup");\r
+ glUniform1i(tex, 1); // Bind Texture 1\r
+}\r
+\r
+void\r
+Hq4x::prepareShader(GLuint texture)\r
+{\r
+ glUseProgram(m_program);\r
+ if (glGetError() != GL_NO_ERROR) abortProgramError(m_program);\r
+\r
+ glBindFragDataLocationEXT(m_program,0,"fragColor");\r
+\r
+ GLint size(glGetUniformLocation(m_program, "mmTextureSize"));\r
+ glUniform4f(size, m_textureWidth, m_textureHeight, 0, 0);\r
+\r
+ glActiveTexture(GL_TEXTURE0);\r
+ glBindTexture(GL_TEXTURE_2D, texture);\r
+ GLint tex(glGetUniformLocation(m_program, "mmTexture"));\r
+ glUniform1i(tex, 0); // Bind Texture 0\r
+\r
+/*\r
+ glActiveTexture(GL_TEXTURE1);\r
+ glBindTexture(GL_TEXTURE_2D, m_lookupTexture);\r
+ tex = glGetUniformLocation(m_program, "mmLookup");\r
+ glUniform1i(tex, 1); // Bind Texture 1\r
+*/\r
+}\r
+\r
+//--------------------------------------------------\r
+// Lookup Table Generation Below\r
+//--------------------------------------------------\r
\r
struct weight\r
{\r
\r
inline void Interp1(uint32_t& c1, uint32_t& c2)\r
{\r
- c1 = 6;\r
- c2 = 2;\r
+ c1 = 191;\r
+ c2 = 64;\r
}\r
\r
inline void Interp2(uint32_t& c1, uint32_t& c2, uint32_t& c3)\r
{\r
- c1 = 4;\r
- c2 = 2;\r
- c3 = 2;\r
+ c1 = 127;\r
+ c2 = 64;\r
+ c3 = 64;\r
}\r
\r
inline void Interp3(uint32_t& c1, uint32_t& c2)\r
{\r
- c1 = 7;\r
- c2 = 1;\r
+ c1 = 223;\r
+ c2 = 32;\r
}\r
\r
inline void Interp5(uint32_t& c1, uint32_t& c2)\r
{\r
- c1 = 4;\r
- c2 = 4;\r
+ c1 = 127;\r
+ c2 = 128;\r
}\r
\r
inline void Interp6(uint32_t& c1, uint32_t& c2, uint32_t& c3)\r
{\r
- c1 = 5;\r
- c2 = 2;\r
- c3 = 1;\r
+ c1 = 159;\r
+ c2 = 64;\r
+ c3 = 32;\r
}\r
\r
inline void Interp7(uint32_t& c1, uint32_t& c2, uint32_t& c3)\r
{\r
- c1 = 6;\r
- c2 = 1;\r
- c3 = 1;\r
+ c1 = 191;\r
+ c2 = 32;\r
+ c3 = 32;\r
}\r
\r
inline void Interp8(uint32_t& c1, uint32_t& c2)\r
{\r
- c1 = 5;\r
- c2 = 3;\r
+ c1 = 159;\r
+ c2 = 96;\r
}\r
\r
\r
#define p15 weights[3][2]\r
#define p16 weights[3][3]\r
\r
-#define PIXEL00_0 p1.w5 = 8;\r
+#define PIXEL00_0 p1.w5 = 255;\r
#define PIXEL00_11 Interp1(p1.w5, p1.w4);\r
#define PIXEL00_12 Interp1(p1.w5, p1.w2);;\r
#define PIXEL00_20 Interp2(p1.w5, p1.w2, p1.w4);\r
#define PIXEL00_81 Interp8(p1.w5, p1.w4);\r
#define PIXEL00_82 Interp8(p1.w5, p1.w2);\r
\r
-#define PIXEL01_0 p2.w5 = 8;\r
+#define PIXEL01_0 p2.w5 = 255;\r
#define PIXEL01_10 Interp1(p2.w5, p2.w1);\r
#define PIXEL01_12 Interp1(p2.w5, p2.w2);\r
#define PIXEL01_14 Interp1(p2.w2, p2.w5);\r
#define PIXEL01_82 Interp8(p2.w5, p2.w2);\r
#define PIXEL01_83 Interp8(p2.w2, p2.w4);\r
\r
-#define PIXEL02_0 p3.w5 = 8;\r
+#define PIXEL02_0 p3.w5 = 255;\r
#define PIXEL02_10 Interp1(p3.w5, p3.w3);\r
#define PIXEL02_11 Interp1(p3.w5, p3.w2);\r
#define PIXEL02_13 Interp1(p3.w2, p3.w5);\r
#define PIXEL02_81 Interp8(p3.w5, p3.w2);\r
#define PIXEL02_83 Interp8(p3.w2, p3.w6);\r
\r
-#define PIXEL03_0 p4.w5 = 8;\r
+#define PIXEL03_0 p4.w5 = 255;\r
#define PIXEL03_11 Interp1(p4.w5, p4.w2);\r
#define PIXEL03_12 Interp1(p4.w5, p4.w6);\r
#define PIXEL03_20 Interp2(p4.w5, p4.w2, p4.w6);\r
#define PIXEL03_81 Interp8(p4.w5, p4.w2);\r
#define PIXEL03_82 Interp8(p4.w5, p4.w6);\r
\r
-#define PIXEL10_0 p5.w5 = 8;\r
+#define PIXEL10_0 p5.w5 = 255;\r
#define PIXEL10_10 Interp1(p5.w5, p5.w1);\r
#define PIXEL10_11 Interp1(p5.w5, p5.w4);\r
#define PIXEL10_13 Interp1(p5.w4, p5.w5);\r
#define PIXEL10_81 Interp8(p5.w5, p5.w4);\r
#define PIXEL10_83 Interp8(p5.w4, p5.w2);\r
\r
-#define PIXEL11_0 p6.w5 = 8;\r
+#define PIXEL11_0 p6.w5 = 255;\r
#define PIXEL11_30 Interp3(p6.w5, p6.w1);\r
#define PIXEL11_31 Interp3(p6.w5, p6.w4);\r
#define PIXEL11_32 Interp3(p6.w5, p6.w2);\r
#define PIXEL11_70 Interp7(p6.w5, p6.w4, p6.w2);\r
\r
-#define PIXEL12_0 p7.w5 = 8;\r
+#define PIXEL12_0 p7.w5 = 255;\r
#define PIXEL12_30 Interp3(p7.w5, p7.w3);\r
#define PIXEL12_31 Interp3(p7.w5, p7.w2);\r
#define PIXEL12_32 Interp3(p7.w5, p7.w6);\r
#define PIXEL12_70 Interp7(p7.w5, p7.w6, p7.w2);\r
\r
-#define PIXEL13_0 p8.w5 = 8;\r
+#define PIXEL13_0 p8.w5 = 255;\r
#define PIXEL13_10 Interp1(p8.w5, p8.w3);\r
#define PIXEL13_12 Interp1(p8.w5, p8.w6);\r
#define PIXEL13_14 Interp1(p8.w6, p8.w5);\r
#define PIXEL13_82 Interp8(p8.w5, p8.w6);\r
#define PIXEL13_83 Interp8(p8.w6, p8.w2);\r
\r
-#define PIXEL20_0 p9.w5 = 8;\r
+#define PIXEL20_0 p9.w5 = 255;\r
#define PIXEL20_10 Interp1(p9.w5, p9.w7);\r
#define PIXEL20_12 Interp1(p9.w5, p9.w4);\r
#define PIXEL20_14 Interp1(p9.w4, p9.w5);\r
#define PIXEL20_82 Interp8(p9.w5, p9.w4);\r
#define PIXEL20_83 Interp8(p9.w4, p9.w8);\r
\r
-#define PIXEL21_0 p10.w5 = 8;\r
+#define PIXEL21_0 p10.w5 = 255;\r
#define PIXEL21_30 Interp3(p10.w5, p10.w7);\r
#define PIXEL21_31 Interp3(p10.w5, p10.w8);\r
#define PIXEL21_32 Interp3(p10.w5, p10.w4);\r
#define PIXEL21_70 Interp7(p10.w5, p10.w4, p10.w8);\r
\r
-#define PIXEL22_0 p11.w5 = 8;\r
+#define PIXEL22_0 p11.w5 = 255;\r
#define PIXEL22_30 Interp3(p11.w5, p11.w9);\r
#define PIXEL22_31 Interp3(p11.w5, p11.w6);\r
#define PIXEL22_32 Interp3(p11.w5, p11.w8);\r
#define PIXEL22_70 Interp7(p11.w5, p11.w6, p11.w8);\r
\r
-#define PIXEL23_0 p12.w5 = 8;\r
+#define PIXEL23_0 p12.w5 = 255;\r
#define PIXEL23_10 Interp1(p12.w5, p12.w9);\r
#define PIXEL23_11 Interp1(p12.w5, p12.w6);\r
#define PIXEL23_13 Interp1(p12.w6, p12.w5);\r
#define PIXEL23_81 Interp8(p12.w5, p12.w6);\r
#define PIXEL23_83 Interp8(p12.w6, p12.w8);\r
\r
-#define PIXEL30_0 p13.w5 = 8;\r
+#define PIXEL30_0 p13.w5 = 255;\r
#define PIXEL30_11 Interp1(p13.w5, p13.w8);\r
#define PIXEL30_12 Interp1(p13.w5, p13.w4);\r
#define PIXEL30_20 Interp2(p13.w5, p13.w8, p13.w4);\r
#define PIXEL30_81 Interp8(p13.w5, p13.w8);\r
#define PIXEL30_82 Interp8(p13.w5, p13.w4);\r
\r
-#define PIXEL31_0 p14.w5 = 8;\r
+#define PIXEL31_0 p14.w5 = 255;\r
#define PIXEL31_10 Interp1(p14.w5, p14.w7);\r
#define PIXEL31_11 Interp1(p14.w5, p14.w8);\r
#define PIXEL31_13 Interp1(p14.w8, p14.w5);\r
#define PIXEL31_81 Interp8(p14.w5, p14.w8);\r
#define PIXEL31_83 Interp8(p14.w8, p14.w4);\r
\r
-#define PIXEL32_0 p15.w5 = 8;\r
+#define PIXEL32_0 p15.w5 = 255;\r
#define PIXEL32_10 Interp1(p15.w5, p15.w9);\r
#define PIXEL32_12 Interp1(p15.w5, p15.w8);\r
#define PIXEL32_14 Interp1(p15.w8, p15.w5);\r
#define PIXEL32_82 Interp8(p15.w5, p15.w8);\r
#define PIXEL32_83 Interp8(p15.w8, p15.w6);\r
\r
-#define PIXEL33_0 p16.w5 = 8;\r
+#define PIXEL33_0 p16.w5 = 255;\r
#define PIXEL33_11 Interp1(p16.w5, p16.w6);\r
#define PIXEL33_12 Interp1(p16.w5, p16.w8);\r
#define PIXEL33_20 Interp2(p16.w5, p16.w8, p16.w6);\r
\r
\r
\r
-uint32_t* getHq4xLookupTable()\r
+uint32_t*\r
+Hq4x::getHq4xLookupTable() const\r
{\r
static uint32_t* s_table(0);\r
\r
}\r
else\r
{\r
+ s_table = new uint32_t[4096*48];\r
+\r
for (int pattern = 0; pattern < 256; ++pattern)\r
{\r
- weight weights[4][4];\r
-\r
for (int diff = 0; diff < 16; ++diff)\r
{\r
+ weight weights[4][4];\r
+\r
switch (pattern)\r
{\r
case 0:\r
{\r
weight w(weights[pixely][pixelx]);\r
\r
- uint32_t rgba =\r
- (w.w1 << 28) |\r
- (w.w2 << 24) |\r
- (w.w3 << 20) |\r
- (w.w4 << 16) |\r
- (w.w6 << 12) |\r
- (w.w7 << 8) |\r
- (w.w8 << 4) |\r
- w.w9;\r
+ assert(w.w1+w.w2+w.w3+w.w4+w.w5+w.w6+w.w7+w.w8+w.w9 == 255);\r
+ size_t index(\r
+ ((pattern << 4) | diff) * 48 + (pixely*4+pixelx)*3\r
+ );\r
\r
+\r
+// TODO byteorder fixes here!\r
+ s_table[index] =\r
+ (w.w1 << 0) |\r
+ (w.w2 << 8) |\r
+ (w.w3 << 16);\r
+\r
+\r
+ s_table[index + 1] =\r
+ (w.w4 << 0) |\r
+ (w.w5 << 8) |\r
+ (w.w6 << 16);\r
+\r
+ s_table[index + 2] =\r
+ (w.w7 << 0) |\r
+ (w.w8 << 8) |\r
+ (w.w9 << 16);\r
+\r
+//hmm, order appears to be ABGR\r
+/*\r
+ s_table[index] = 0x0090FF10;\r
+ s_table[index+1] = 0x00;\r
+ s_table[index+2] = 0x00;\r
+*/\r
}\r
}\r
}\r
}\r
}\r
\r
- return s_lookup;\r
+ return s_table;\r
}\r
\r
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of glBoy.
+//
+// glBoy is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// glBoy is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef GLBOY_HQ4X_HH
+#define GLBOY_HQ4X_HH
+
+#include <cstdint>
+
+#include <GL/gl.h>
+
+namespace glBoy
+{
+ class Hq4x
+ {
+ public:
+ Hq4x(size_t textureWidth, size_t textureHeight);
+
+ void prepareShader(GLuint texture);
+
+ private:
+ enum Diff { Diff_2_6 = 1, Diff_6_8 = 2, Diff_8_4 = 4, Diff_4_2 = 8 };
+
+ uint32_t* getHq4xLookupTable() const;
+
+ int m_textureWidth;
+ int m_textureHeight;
+
+ GLint m_vertexShader;
+ GLint m_fragShader;
+ GLint m_program;
+ GLuint m_lookupTexture;
+ };
+}
+
+#endif
+
+#!/usr/bin/perl -w
+# Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+#
+# This file is part of glBoy.
+#
+# glBoy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# glBoy is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with glBoy. If not, see <http://www.gnu.org/licenses/>.
+
+use warnings;
+use strict;
+use Carp;
+
+use XML::LibXML::Reader;
+
+# < Ugly Globals >
+my %RegMasks = ();
+my %BitMasks = ();
+my %Instructions = ();
+
+my $VARIANT = "gb80";
+
+# </ Ugly Globals >
+
+my $reader = new XML::LibXML::Reader(location => "InstructionSet.opcode")
+ || die "Cannot read opcode file\n";
+
+open (TABLES, '>InstructionSet_Tables.cc') || die "Could not write to file\n";
+open (CODE, '>InstructionSet_Opcodes.cc') || die "Could not write to file\n";
+
+$reader->read;
+$reader->name eq "opcodes" || die "Invalid xml\n";
+
+my $node = $reader->read;
+while ($node)
+{
+ if ($reader->nodeType == XML_READER_TYPE_ELEMENT)
+ {
+ # Convert subtree to a DOM object.
+ my $subtree = $reader->copyCurrentNode(1);
+
+ if ($reader->name eq "registerMask")
+ {
+ processRegisterMask($subtree);
+ }
+ elsif ($reader->name eq "bitMask")
+ {
+ processBitMask($subtree);
+ }
+ elsif ($reader->name eq "instruction")
+ {
+ processInstruction($subtree)
+ }
+ else
+ {
+ # Ignore unknown XML
+ }
+ }
+
+ $node = $reader->next();
+}
+
+outputInstructionTable();
+
+close TABLES;
+close CODE;
+
+sub outputInstructionTable
+{
+ foreach my $prefix (keys %Instructions)
+ {
+ my $outPrefix = "OpcodeTable$prefix";
+ $outPrefix =~ s/0x//;
+ $outPrefix =~ s/default//;
+ print TABLES "static void* $outPrefix\[256\] =\n{\n";
+
+ my %opcodes = %{$Instructions{$prefix}};
+
+ my $count = 0;
+ while ($count < 256)
+ {
+ if ($Instructions{$prefix}{$count})
+ {
+ print TABLES "&&$Instructions{$prefix}{$count}";
+ }
+ else
+ {
+ print TABLES "&&BAD_OPCODE";
+ }
+
+ $count = $count + 1;
+
+ if ($count != 256)
+ {
+ print TABLES ",\t";
+ }
+ if ($count % 4 == 0)
+ {
+ print TABLES "\n";
+ }
+ }
+ print TABLES "};\n\n";
+ }
+}
+
+
+sub processRegisterMask
+{
+ my $node = shift;
+
+ my $name = $node->findvalue('./@name') || die;
+ my $maskBits = $node->findvalue('./@maskBits') || die;
+ ($maskBits > 0 && $maskBits < 8) || die;
+ my $regBytes = $node->findvalue('./@regBytes') || die;
+ ($regBytes > 0 && $regBytes <= 2) || die;
+
+ my $variant = $node->findvalue('./@variant');
+
+ if ($variant && $variant ne $VARIANT)
+ {
+ return;
+ }
+
+ my $max = 1 << $maskBits;
+ my $count = 0;
+
+ print TABLES "static size_t regMask_$name\[$max\] =\n{\n";
+
+ my @masks = ();
+
+ while ($count < $max)
+ {
+ my $mask = sprintf("%0$maskBits"."b", $count);
+ my $reg = $node->findvalue("./reg[\@mask='$mask']/\@name");
+
+ if ($count != 0) { print TABLES ",\n"; }
+ if ($reg && length $reg != 0)
+ {
+ print TABLES "\toffsetof(glBoy::Registers, $reg) / $regBytes";
+ push @masks, $mask;
+ }
+ else
+ {
+ print TABLES "\tsize_t(-1)";
+ }
+
+ $count = $count + 1;
+ }
+ print TABLES "\n};\n\n";
+
+ $RegMasks{$name}{'masks'} = \@masks;
+ $RegMasks{$name}{'maskBits'} = $maskBits;
+ $RegMasks{$name}{'regBytes'} = $regBytes;
+
+}
+
+sub processBitMask
+{
+ my $node = shift;
+
+ my $name = $node->findvalue('./@name') || die;
+ my $maskBits = $node->findvalue('./@maskBits') || die;
+ ($maskBits > 0 && $maskBits <= 8) || die;
+
+ my $max = 1 << $maskBits;
+ my $count = 0;
+
+ my @masks = ();
+
+ while ($count < $max)
+ {
+ my $mask = sprintf("%0$maskBits"."b", $count);
+ push @masks, $mask;
+ $count = $count + 1;
+ }
+
+ $BitMasks{$name}{'masks'} = \@masks;
+ $BitMasks{$name}{'maskBits'} = $maskBits;
+}
+
+sub expandMask
+{
+ my $mask = shift;
+
+ my @result = ();
+
+ if ($mask =~ /([a-zA-Z])/)
+ {
+ my $match = $1;
+
+ if ($BitMasks{$match})
+ {
+ foreach my $bits (@{$BitMasks{$match}{'masks'}})
+ {
+ my $nextMask = $mask;
+ $nextMask =~ s/$match/$bits/;
+ push @result, expandMask($nextMask);
+ }
+ }
+ elsif ($RegMasks{$match})
+ {
+ foreach my $bits (@{$RegMasks{$match}{'masks'}})
+ {
+ my $nextMask = $mask;
+ $nextMask =~ s/$match/$bits/;
+ push @result, expandMask($nextMask);
+ }
+
+ }
+ $mask =~ s/$match/_/;
+ }
+ else
+ {
+ my $pos = 0;
+ my $value = 0;
+
+ if (length $mask != 8) { croak "Bad Mask $mask\n"; }
+ while ($pos < 8)
+ {
+ my $char = substr($mask, $pos, 1);
+ if ($char == 1)
+ {
+ $value = $value + (1 << (7 - $pos));
+ }
+ $pos = $pos + 1;
+ }
+ push @result, $value;
+ }
+ return @result;
+}
+
+sub createRegReference
+{
+ my $mask = shift;
+
+ my %seenChars = ();
+
+ my @dbgResult = ();
+
+ # This is probably not the fastest way, but who cares.
+ my $pos = 0;
+ my $bitPos = 0;
+ while ($pos < length $mask)
+ {
+ my $char = substr($mask, $pos, 1);
+ if ($char =~ /[a-zA-Z]/ && $RegMasks{$char})
+ {
+ my $outputName = $char;
+ if ($seenChars{$outputName})
+ {
+ $seenChars{$outputName} = $seenChars{$outputName} + 1;
+ $outputName .= $seenChars{$outputName};
+ }
+ else
+ {
+ $seenChars{$outputName} = 1;
+ }
+
+ push @dbgResult, $outputName;
+
+ my $maskBits = $RegMasks{$char}{'maskBits'};
+ my $shiftNum = 8 - ($bitPos + $RegMasks{$char}{'maskBits'});
+ if ($RegMasks{$char}{'regBytes'} == 1)
+ {
+ print CODE "\tuint8_t& $outputName(m_reg.begin8[regMask_$char\[(opcode >> $shiftNum) & (uint8_t(-1) >> (8 - $maskBits)) \]\]);\n";
+ }
+ else
+ {
+ print CODE "\tDWORD& $outputName(m_reg.begin16[regMask_$char\[(opcode >> $shiftNum) & (uint8_t(-1) >> (8 - $maskBits)) \]\]);\n";
+ }
+
+ $bitPos = $bitPos + $maskBits;
+ }
+ elsif ($char =~ /[a-zA-Z]/ && $BitMasks{$char})
+ {
+ my $maskBits = $BitMasks{$char}{'maskBits'};
+ my $shiftNum = 8 - ($bitPos + $BitMasks{$char}{'maskBits'});
+
+ print CODE "uint8_t $char((opcode >> $shiftNum) & (uint8_t(-1) >> (8 - $maskBits)));\n";
+ $bitPos = $bitPos + $maskBits;
+ }
+ else
+ {
+ $bitPos = $bitPos + 1;
+ }
+
+ $pos = $pos + 1;
+ }
+ return @dbgResult;
+}
+
+sub processInstruction
+{
+ my $node = shift;
+
+ my $name = $node->findvalue('./@name') || die;
+ my $clock = $node->findvalue('./@clock');
+ my $mask = $node->findvalue('./opcode/@mask');
+ my $prefix = $node->findvalue('./opcode/@prefix');
+ my $operand = $node->findvalue('./opcode/@operand');
+ my $signedOperand = $node->findvalue('./opcode/@signed_operand');
+ my $variant = $node->findvalue('./@variant');
+
+ if ($variant && $variant ne $VARIANT)
+ {
+ return;
+ }
+ if ($VARIANT eq "gb80" && $prefix && $prefix ne "0xCB")
+ {
+ return;
+ }
+
+ print CODE "// Instruction: $name\n";
+ print CODE "// Clock: $clock\n";
+ my $label = $name;
+ $label =~ tr/ ,'+()/______/;
+ print CODE "$label:\n";
+ print CODE "{\n";
+
+
+ # Note: May have both signed operand (index offset) and operand
+ if ($signedOperand && length $signedOperand == 1)
+ {
+ print CODE "\tint8_t $signedOperand(op8());\n";
+ }
+
+ if ($operand && length $operand == 1)
+ {
+ if (length($prefix) > 4)
+ {
+ print CODE "\t uint8_t& $operand(doublePrefixOperand);\n";
+ }
+ elsif (uc($operand) eq $operand)
+ {
+ print CODE "\tDWORD $operand(op16());\n";
+ }
+ else
+ {
+ print CODE "\tuint8_t $operand(op8());\n";
+ }
+ }
+
+ my @dbgRegs = createRegReference($mask);
+
+ print CODE "#ifdef GLBOY_DEBUG\n";
+ print CODE "std::ostream& trace(log(Log::DBG_TRACE));\n".
+ "trace << ".
+ "opcodePC << \": \";\n".
+ "if (prefix) trace << hex(prefix) << \" \";\n".
+ "trace << hex(opcode) << \" $name\" << ".
+ ($operand ? "\" ($operand=\" << $operand << \")\" << " : "") .
+ ($dbgRegs[0] ? "\" ($dbgRegs[0]=\" << $dbgRegs[0] << \")\" << " : "") .
+ "std::endl;\n";
+ print CODE "#endif\n";
+
+ my $statements = $node->findvalue('./text()');
+ chomp($statements);
+ print CODE $statements."\n";
+
+ print CODE "\tclock += $clock;\n";
+
+ print "Processing $name\n";
+ my @opcodes = expandMask($mask);
+
+ if (!$prefix || length $prefix == 0)
+ {
+ $prefix = "default";
+ }
+
+ foreach my $opcode (@opcodes)
+ {
+ if ($Instructions{$prefix}{$opcode})
+ {
+ die "Duplicate opcode. Prefix = $prefix. Mask = $mask, $name.\n".
+ "Conflicting label = $Instructions{$prefix}{$opcode}\n";
+ }
+ $Instructions{$prefix}{$opcode} = $label;
+ }
+
+ print CODE "\n}\n";
+ print CODE "goto END_INSTRUCTIONS;\n";
+ print CODE "\n\n";
+}
+
-// http://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/loading.php
+uniform sampler2D mmTexture;
+uniform vec4 mmTextureSize;
-#version 130
-
-uniform sampler2D OGL2Texture;
-uniform vec4 OGL2Size;
-
-/*
-void main (void)
-{
- // Every pixel becomes green
- gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
-}
-*/
-
-in vec4 vTexCoord; // supplied from vertex processor
-in vec4 B;
-in vec4 D;
-in vec4 F;
-in vec4 H;
+varying vec4 vTexCoord; // supplied from vertex processor
+varying vec4 B;
+varying vec4 D;
+varying vec4 F;
+varying vec4 H;
void main(void)
vec4 E2;
vec4 E3;
- eColour = texture2DProj(OGL2Texture, vTexCoord);
- bColour = texture2DProj(OGL2Texture, B);
- dColour = texture2DProj(OGL2Texture, D);
- fColour = texture2DProj(OGL2Texture, F);
- hColour = texture2DProj(OGL2Texture, H);
+ vec4 outRow1;
+ vec4 outRow2;
+
+ eColour = texture2DProj(mmTexture, vTexCoord);
+ bColour = texture2DProj(mmTexture, B);
+ dColour = texture2DProj(mmTexture, D);
+ fColour = texture2DProj(mmTexture, F);
+ hColour = texture2DProj(mmTexture, H);
if (bColour != hColour && dColour != fColour) {
E0 = dColour == bColour ? dColour : eColour;
}
// Is this pixel E0,E1,E2 or E3, or somewhere in between ?
- fragmentLocation = fract(vTexCoord * OGL2Size);
- //fragmentLocation = fract(vTexCoord * texturesize(OGL2Texture));
+ // Bi-linear interpolate (may not be exact x2 scale)
+ fragmentLocation = fract(vTexCoord * mmTextureSize);
- // TODO interpolate
- if (fragmentLocation.x < 0.5)
- {
- if (fragmentLocation.y < 0.5)
- {
- gl_FragColor = E0;
- }
- else
- {
- gl_FragColor = E2;
- }
- }
- else
- {
- if (fragmentLocation.y < 0.5)
- {
- gl_FragColor = E1;
- }
- else
- {
- gl_FragColor = E3;
- }
- }
+ outRow1 = mix(E0, E1, fragmentLocation.x);
+ outRow2 = mix(E2, E3, fragmentLocation.x);
+ gl_FragColor = mix(outRow1, outRow2, fragmentLocation.y);
}
-// http://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/loading.php
+uniform vec4 mmTextureSize;
-
-// Built-in vertex attributes (per vertex)
-// gl_Vertex Position of current vertex (vec4)
-// gl_Color Primary colour of vertex (vec4)
-//gl_MultiTexCoord0 Texture Coordinate of texture unit 0
-
-// Built-in variables (passed from vertex to fragment)
-// Will be interpolated between vertex and fragment.
-
-// Scales output, so image only takes up top left of screen (1/4 of image);
-/*
-void main(void)
-{
- vec4 a = gl_Vertex;
- a.x = a.x * 2.0;//0.5;
- a.y = a.y * 0.5;
-
-
- gl_Position = gl_ModelViewProjectionMatrix * a;
- // gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; normal code
-
-}
-*/
-
-#version 130
-
-uniform vec4 OGL2Param;
-
-out vec4 vTexCoord; // Passed to fragment
-out vec4 B; // Passed to fragment
-out vec4 D; // Passed to fragment
-out vec4 F; // Passed to fragment
-out vec4 H; // Passed to fragment
+varying vec4 vTexCoord; // Passed to fragment
+varying vec4 B; // Passed to fragment
+varying vec4 D; // Passed to fragment
+varying vec4 F; // Passed to fragment
+varying vec4 H; // Passed to fragment
void main(void)
vec4 offsetx;
vec4 offsety;
- offsetx = vec4(OGL2Param.x, 0, 0, 0);
- offsety = vec4(0, OGL2Param.y, 0, 0);
+ offsetx = vec4(1.0/mmTextureSize.x, 0, 0, 0);
+ offsety = vec4(0, 1.0/mmTextureSize.y, 0, 0);
B = gl_MultiTexCoord0 - offsety;
D = gl_MultiTexCoord0 - offsetx;
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of glBoy.
+//
+// glBoy is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// glBoy is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef GLBOY_UTIL_HH
+#define GLBOY_UTIL_HH
+
+#include <string>
+namespace glBoy
+{
+ static char hexChars[] =
+ {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
+
+ inline std::string hex(uint8_t val)
+ {
+ std::string result("0xFF");
+ result[2] = hexChars[val >> 4];
+ result[3] = hexChars[val & 0xf];
+ return result;
+ }
+
+ inline std::string hex(int8_t val)
+ {
+ std::string result;
+
+ if (val >= 0)
+ {
+ result = "0x";
+ }
+ else
+ {
+ result = "-0x";
+ }
+ result += hexChars[val >> 4];
+ result += hexChars[val & 0xf];
+ return result;
+ }
+
+ inline std::string hex(uint16_t val)
+ {
+ std::string result("0xFFFF");
+ result[2] = hexChars[val >> 12];
+ result[3] = hexChars[(val >> 8) & 0xf];
+ result[4] = hexChars[(val >> 4) & 0xf];
+ result[5] = hexChars[val & 0xf];
+ return result;
+ }
+}
+#endif
+