Upgrade to Pro — share decks privately, control downloads, hide ads and more …

8-bit Game emulation

8-bit Game emulation

Avatar for Alex Thissen

Alex Thissen

February 17, 2012
Tweet

More Decks by Alex Thissen

Other Decks in Programming

Transcript

  1. Sequence of opcodes Compiled by an assembler Essentially same today

    Memory addresses Instructions Operands Disassembly
  2. Mikey 65SC02 CPU ROM + timers Suzy Graphics and Math

    coprocessor, I/O RAM Memory Shift registers 16 MHz Oscillator
  3. Interpretation • Reproduce workings of CPU in code • Works

    with self- modifying code • Easiest Static translation • Translate native code to target • Compile time • Limited use with self-modifying code • Harder Dynamic translation • Like static, but runtime • Capabilities of target used best • Hardest to implement High level emulation • Use native hardware to emulate old API or electronics as soon as possible • Limited use
  4. Execute logic Register variables Stack logic public abstract class ProcessorBase

    { public abstract void Initialize(); public abstract void Reset(); public abstract ulong Execute(int instructionsToExecute); public abstract object SignalInterrupt(InterruptType interrupt, params object[] details); } public byte A; // Accumulator (8 bits) public byte X; // X index register (8 bits) public byte Y; // Y index register (8 bits) public byte SP; // Stack Pointer (8 bits) public byte ProcessorStatus public byte Opcode; // Instruction opcode (8 bits) public ushort Address; // Instruction operand address public byte Data; // Instruction data public ushort PC; // Program Counter (16 bits)
  5. public override ulong Execute(int instructionsToExecute) { if (IsSystemIrqActive) { IsAsleep

    = false; if (ActiveInterrupt == InterruptType.Nmi || (!I && ActiveInterrupt == InterruptType.Irq)) RunInterruptSequence(ActiveInterrupt); } // Fetch opcode Opcode = Memory.Peek(PC); SystemClock.CycleCount += 1 + timings[Opcode] * MemoryReadCycle; PC++; ExecuteOpcode(); } // Decode and execute switch (Opcode) { case 0x00: BRK(); break; case 0x01: ZeroPageIndexedIndirectX(); ORA(); break; case 0x05: ZeroPage(); ORA(); break; case 0x06: ZeroPage(); ASL(); break;
  6. CPUs only see data Memory mapped registers public interface IMemoryAccess<TAddress,

    TData> { void Poke(TAddress address, TData value); TData Peek(TAddress address); } public class Ram64KBMemory: IMemoryAccess<ushort, byte>
  7. public class SpriteControlBits0 { public byte ByteData { get; set;

    } // "B5 = H flip, 0 = not flipped" public bool HFlip { get { return (ByteData & HFlipMask) == HFlipMask; } set { ByteData = (byte)(value ? ByteData | HFlipMask : ByteData & (HFlipMask ^ 0xFF)); } } // "B7,B6 = bits/pixel-1 (1,2,3,4)" public byte BitsPerPixel { get { return (byte)((ByteData >> 6) + 1); } } } Other device (Suzy) Other device (Suzy) Memory Mapped Register Two views Bits mean things CPU Poke $FC80,C4
  8. // High level copy Array.Copy(videoMemory, doubleBuffer1); // Low level copy

    IntPtr stream = GetAudioStream(); Marshal.Copy(emulator.AudioBuffer, 0, stream, length); Pixel oriented Concepts Video memory 1 2 3 4 5 6 2 22 5 6 6 6 6 66 6 1 1 1 1 1 4 44 2 4 4 4 1 63 6 6 6 6 6 5 Separate color palette
  9. Logic to emulate Multiple channels mixed together Buffered output public

    void Output(ulong cycleCount, byte sample) { while (audioLastUpdateCycle + AUDIO_SAMPLE_PERIOD < cycleCount) { audioLastUpdateCycle += AUDIO_SAMPLE_PERIOD; // Output audio sample as little endian 16-bit Buffer[audioBufferIndex++] = 0; Buffer[audioBufferIndex++] = sample; if (audioBufferIndex >= Buffer.Length) { OnBufferReady(); audioBufferIndex = 0; } } }
  10. Byte and short operators Struct layout Endianness (little and big)

    [StructLayout(LayoutKind.Explicit)] public struct Word { [FieldOffset(0)] public ushort Value; [FieldOffset(0)] public byte LowByte; [FieldOffset(1)] public byte HighByte; } if (BitConverter.IsLittleEndian) { scb.SCBNEXT.Value = BitConverter.ToUInt16(ramMemory, address); address += 2; } else { scb.SCBNEXT.Value = (ushort)(ramMemory[address++] + (ramMemory[address++] << 8)); }
  11. Performance optimization Inlining by compiler Debug.WriteLine(If) is bad for performance

    Debug.WriteLineIf(GeneralSwitch.TraceVerbose, String.Format("CartAddressStrobe (strobe={0}) shifter=${1:x}", strobe, shiftRegister));
  12. Windowed and full-screen Sprites represented by Texture2D Power of graphics

    engine and video card Separate effects from content
  13. Emulator video buffer (RAM) Sprite data protected override void Draw(GameTime

    gameTime) { lcdScreen.SetData(emulator.LcdScreenDma, 0x0, 0x3FC0); spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null); spriteBatch.Draw(lcdScreen, new Rectangle(0, 0, graphicsWidth, graphicsHeight), new Rectangle(0, 0, Suzy.SCREEN_WIDTH, Suzy.SCREEN_HEIGHT), Color.White); spriteBatch.End(); base.Draw(gameTime); }
  14. SoundEffect API DynamicSoundEffectInstance gunfireSound = Content.Load<SoundEffect>("PewPew"); DynamicSoundEffectInstance Game Buffer 5

    Buffer 4 Buffer 3 Buffer 2 Buffer 1 Buffer 0 SubmitBuffer Low-level Audio Playback Engine Pending Buffers
  15. public const int AUDIO_SAMPLE_FREQ = 22050; public const int AUDIO_BUFFER_SIZE

    = AUDIO_SAMPLE_FREQ / 4; public const int AUDIO_SAMPLE_PERIOD = LynxHandheld.SYSTEM_FREQ / AUDIO_SAMPLE_FREQ; Characteristics B0 B1 B2 B3 B4 B5 B6 B7 Sample Block1 Block2 Block3 Block4 Sample Sample Sample B0 B1 B2 B3 B4 B5 B6 B7 Left Sample Block1 Block2 Right Sample Left Sample Right Sample
  16. private void InitializeAudio() { dynamicSound = new DynamicSoundEffectInstance(22050, AudioChannels.Mono); soundBuffer

    = new byte[dynamicSound.GetSampleSizeInBytes(TimeSpan.FromMilliseconds(250))]; emulator.Mikey.AudioFilter.BufferReady += new EventHandler<BufferEventArgs>(OnAudioFilterBufferReady); dynamicSound.Play(); }
  17. Classes to match input device Static method retrieves state Keyboard

    Mouse Gamepad TouchPanel Accelerometer Microphone public override bool ExitGame { get { KeyboardState keyboard = Keyboard.GetState(PlayerIndex.One); return keyboard.IsKeyDown(Keys.F4); } } GamePadState gamePad = GamePad.GetState(PlayerIndex.One); return gamePad.Buttons.Back == ButtonState.Pressed;
  18. GamePadState gamePad = GamePad.GetState(PlayerIndex.One); JoystickStates joystick = JoystickStates.None; if (gamePad.DPad.Down

    == ButtonState.Pressed) joystick |= JoystickStates.Down; if (gamePad.DPad.Up == ButtonState.Pressed) joystick |= JoystickStates.Up; if (gamePad.DPad.Left == ButtonState.Pressed) joystick |= JoystickStates.Left; if (gamePad.DPad.Right == ButtonState.Pressed) joystick |= JoystickStates.Right; Transfer input device state to emulator core Analog versus digital [Flags] public enum JoystickStates { Up = 0x80, Down = 0x40, Left = 0x20, Right = 0x10 }
  19. Game emulators in managed code are possible Fun to do

    XNA is an excellent game framework
  20. App Hub XNA Game Studio 4.0 Portable Library Tools Codeplex

    Lynx Sharp-64 CrystalBoy Cogwheel Emu7800 My Nes Bonami Spelcomputer museum
  21. Ask them now! OutOfTimeException? Ask them later? Does anyone like

    to participate in writing a Vectrex emulator?!