diff --git a/examples/benchmark/src/main.rs b/examples/benchmark/src/main.rs
index b5ca3e94ee584c0f32e89b344fbb24f63e75889b..f590970b62b85a3511e8458d61e96f79c93b1883 100644
--- a/examples/benchmark/src/main.rs
+++ b/examples/benchmark/src/main.rs
@@ -1,65 +1,44 @@
-use chip_ahoyto::{chip8::Chip8, chip8_neo::Chip8Neo, util::read_file};
+use chip_ahoyto::{
+    chip8::Chip8, chip8_classic::Chip8Classic, chip8_neo::Chip8Neo, util::read_file,
+};
 use time::Instant;
 
 const CYCLE_COUNT: u64 = 5_000_000_000;
 
-fn benchmark_chip8() {
-    let rom_path = "./resources/pong.ch8";
-    let rom = read_file(rom_path);
-
-    let mut chip8 = Chip8::new();
-    chip8.reset_hard();
-    chip8.load_rom(&rom);
-
-    let instant = Instant::now();
-
-    let cycles = CYCLE_COUNT;
-
-    println!("[Chip8] Running {} cycles for {}", cycles, rom_path);
-
-    for _ in 0..CYCLE_COUNT {
-        chip8.clock();
-    }
-
-    let duration_s = instant.elapsed().as_seconds_f32();
-    let cycles_second = cycles as f32 / duration_s;
-    let mega_second = cycles_second / 1000.0 / 1000.0;
-
-    println!(
-        "[Chip8] Took {} seconds or {:.2} MHz CPU",
-        duration_s, mega_second
-    );
-}
+fn main() {
+    let chips: Vec<Box<dyn Chip8>> = vec![Box::new(Chip8Classic::new()), Box::new(Chip8Neo::new())];
 
-fn benchmark_chip8_neo() {
     let rom_path = "./resources/pong.ch8";
     let rom = read_file(rom_path);
 
-    let mut chip8 = Chip8Neo::new();
-    chip8.reset_hard();
-    chip8.load_rom(&rom);
-
-    let instant = Instant::now();
+    for mut chip8 in chips {
+        chip8.reset_hard();
+        chip8.load_rom(&rom);
 
-    let cycles = CYCLE_COUNT;
+        let instant = Instant::now();
 
-    println!("[Chip8Neo] Running {} cycles for {}", cycles, rom_path);
+        let cycles = CYCLE_COUNT;
 
-    for _ in 0..CYCLE_COUNT {
-        chip8.clock();
-    }
+        println!(
+            "[{}] Running {} cycles for {}",
+            chip8.name(),
+            cycles,
+            rom_path
+        );
 
-    let duration_s = instant.elapsed().as_seconds_f32();
-    let cycles_second = cycles as f32 / duration_s;
-    let mega_second = cycles_second / 1000.0 / 1000.0;
+        for _ in 0..CYCLE_COUNT {
+            chip8.clock();
+        }
 
-    println!(
-        "[Chip8Neo] Took {} seconds or {:.2} MHz CPU",
-        duration_s, mega_second
-    );
-}
+        let duration_s = instant.elapsed().as_seconds_f32();
+        let cycles_second = cycles as f32 / duration_s;
+        let mega_second = cycles_second / 1000.0 / 1000.0;
 
-fn main() {
-    benchmark_chip8();
-    benchmark_chip8_neo();
+        println!(
+            "[{}] Took {} seconds or {:.2} MHz CPU",
+            chip8.name(),
+            duration_s,
+            mega_second
+        );
+    }
 }
diff --git a/examples/sdl/src/main.rs b/examples/sdl/src/main.rs
index 27899a6bdfc066bf7c67a01e4a13415939e9e178..996de6d3a3ea8aecae2d9069a876673dd86858fa 100644
--- a/examples/sdl/src/main.rs
+++ b/examples/sdl/src/main.rs
@@ -1,6 +1,6 @@
 use chip_ahoyto::{
-    chip8::Chip8, chip8::SCREEN_PIXEL_HEIGHT, chip8::SCREEN_PIXEL_WIDTH, chip8_neo::Chip8Neo,
-    util::read_file,
+    chip8::Chip8, chip8_classic::Chip8Classic, chip8_classic::SCREEN_PIXEL_HEIGHT,
+    chip8_classic::SCREEN_PIXEL_WIDTH, chip8_neo::Chip8Neo, util::read_file,
 };
 use sdl2::{
     audio::AudioCallback, audio::AudioSpecDesired, event::Event, image::LoadSurface,
diff --git a/src/chip8.rs b/src/chip8.rs
index ce6b37e0f77a8e2a87e06d6c937380c706c1da54..fccf0c688555b1fd2e6822294a7acdce314a6901 100644
--- a/src/chip8.rs
+++ b/src/chip8.rs
@@ -1,332 +1,15 @@
-use std::fmt::Display;
-
-use crate::util::random;
-
-#[cfg(feature = "web")]
-use wasm_bindgen::prelude::*;
-
-/// The width of the screen in pixels.
-pub const SCREEN_PIXEL_WIDTH: usize = 64;
-
-/// The height of the screen in pixels.
-pub const SCREEN_PIXEL_HEIGHT: usize = 32;
-
-/// The number of keys to be allocated to the machine.
-const NUM_KEYS: usize = 16;
-
-/// The total number of CPU registers that are going to be used
-/// this value should follow the typical convention.
-const NUM_REGISTERS: usize = 16;
-
-/// The size of the stack, this value is actually a convention
-/// as no specific definition on its size was ever created.
-const STACK_SIZE: usize = 16;
-
-/// The size of the RAM memory in bytes.
-const RAM_SIZE: usize = 4096;
-
-/// The starting address for the ROM loading, should be
-/// the initial PC position.
-const ROM_START: usize = 0x200;
-
-/// Buffer that contains the base CHIP-8 font set that
-/// is going to be used to draw the font in the screen
-static FONT_SET: [u8; 80] = [
-    0xf0, 0x90, 0x90, 0x90, 0xf0, // 0
-    0x20, 0x60, 0x20, 0x20, 0x70, // 1
-    0xf0, 0x10, 0xf0, 0x80, 0xf0, // 2
-    0xf0, 0x10, 0xf0, 0x10, 0xf0, // 3
-    0x90, 0x90, 0xf0, 0x10, 0x10, // 4
-    0xf0, 0x80, 0xf0, 0x10, 0xf0, // 5
-    0xf0, 0x80, 0xf0, 0x90, 0xf0, // 6
-    0xf0, 0x10, 0x20, 0x40, 0x40, // 7
-    0xf0, 0x90, 0xf0, 0x90, 0xf0, // 8
-    0xf0, 0x90, 0xf0, 0x10, 0xf0, // 9
-    0xf0, 0x90, 0xf0, 0x90, 0x90, // A
-    0xe0, 0x90, 0xe0, 0x90, 0xe0, // B
-    0xf0, 0x80, 0x80, 0x80, 0xf0, // C
-    0xe0, 0x90, 0x90, 0x90, 0xe0, // D
-    0xf0, 0x80, 0xf0, 0x80, 0xf0, // E
-    0xf0, 0x80, 0xf0, 0x80, 0x80, // F
-];
-
-#[cfg_attr(feature = "web", wasm_bindgen)]
-pub struct Chip8 {
-    vram: [u8; SCREEN_PIXEL_WIDTH * SCREEN_PIXEL_HEIGHT],
-    ram: [u8; RAM_SIZE],
-    registers: [u8; NUM_REGISTERS],
-    stack: [u16; STACK_SIZE],
-    i: u16,
-    dt: u8,
-    st: u8,
-    pc: u16,
-    sp: u8,
-    beep: bool,
-    last_key: u8,
-    keys: [bool; NUM_KEYS],
-}
-
-#[cfg_attr(feature = "web", wasm_bindgen)]
-impl Chip8 {
-    #[cfg_attr(feature = "web", wasm_bindgen(constructor))]
-    pub fn new() -> Chip8 {
-        let mut chip8 = Chip8 {
-            vram: [0u8; SCREEN_PIXEL_WIDTH * SCREEN_PIXEL_HEIGHT],
-            ram: [0u8; RAM_SIZE],
-            registers: [0u8; NUM_REGISTERS],
-            stack: [0u16; STACK_SIZE],
-            i: 0,
-            dt: 0,
-            st: 0,
-            pc: ROM_START as u16,
-            sp: 0,
-            beep: false,
-            last_key: 0x00,
-            keys: [false; NUM_KEYS],
-        };
-        chip8.load_font(&FONT_SET);
-        chip8
-    }
-
-    pub fn reset(&mut self) {
-        self.vram = [0u8; SCREEN_PIXEL_WIDTH * SCREEN_PIXEL_HEIGHT];
-        self.registers = [0u8; NUM_REGISTERS];
-        self.stack = [0u16; STACK_SIZE];
-        self.i = 0;
-        self.dt = 0;
-        self.st = 0;
-        self.pc = ROM_START as u16;
-        self.sp = 0;
-        self.beep = false;
-        self.last_key = 0x00;
-        self.keys = [false; NUM_KEYS];
-        self.load_font(&FONT_SET);
-    }
-
-    pub fn reset_hard(&mut self) {
-        self.ram = [0u8; RAM_SIZE];
-        self.reset();
-    }
-
-    pub fn name(&self) -> &str {
-        "classic"
-    }
-
-    pub fn pixels(&self) -> Vec<u8> {
-        self.vram.to_vec()
-    }
-
-    pub fn beep(&self) -> bool {
-        self.beep
-    }
-
-    pub fn pc(&self) -> u16 {
-        self.pc
-    }
-
-    pub fn sp(&self) -> u8 {
-        self.sp
-    }
-
-    pub fn load_rom(&mut self, rom: &[u8]) {
-        self.ram[ROM_START..ROM_START + rom.len()].clone_from_slice(rom);
-    }
-
-    pub fn clock(&mut self) {
-        let opcode = self.fetch_opcode();
-        self.process_opcode(opcode);
-    }
-
-    pub fn clock_dt(&mut self) {
-        self.dt = self.dt.saturating_sub(1);
-    }
-
-    pub fn clock_st(&mut self) {
-        self.st = self.st.saturating_sub(1);
-        self.beep = self.st > 0;
-    }
-
-    pub fn key_press(&mut self, key: u8) {
-        if key < NUM_KEYS as u8 {
-            self.last_key = key;
-            self.keys[key as usize] = true;
-        }
-    }
-
-    pub fn key_lift(&mut self, key: u8) {
-        if key < NUM_KEYS as u8 {
-            self.keys[key as usize] = false;
-        }
-    }
-}
-
-impl Chip8 {
-    fn process_opcode(&mut self, opcode: u16) {
-        let id = opcode & 0xf000;
-        let addr = opcode & 0x0fff;
-        let nibble = (opcode & 0x000f) as u8;
-        let x = (opcode >> 8 & 0xf) as usize;
-        let y = (opcode >> 4 & 0xf) as usize;
-        let byte = (opcode & 0x00ff) as u8;
-
-        match id {
-            0x0000 => match byte {
-                0xe0 => self.vram = [0u8; SCREEN_PIXEL_WIDTH * SCREEN_PIXEL_HEIGHT],
-                0xee => self.return_subroutine(),
-                _ => panic!("unknown opcode 0x{:04x}", opcode),
-            },
-            0x1000 => self.pc = addr,
-            0x2000 => self.call_subroutine(addr),
-            0x3000 => self.skip_if(self.registers[x] == byte),
-            0x4000 => self.skip_if(self.registers[x] != byte),
-            0x5000 => self.skip_if(self.registers[x] == self.registers[y]),
-            0x6000 => self.registers[x] = byte,
-            0x7000 => self.registers[x] = self.registers[x].wrapping_add(byte),
-            0x8000 => match nibble {
-                0x0 => self.registers[x] = self.registers[y],
-                0x1 => self.registers[x] |= self.registers[y],
-                0x2 => self.registers[x] &= self.registers[y],
-                0x3 => self.registers[x] ^= self.registers[y],
-                0x4 => self.add(x, y),
-                0x5 => self.registers[x] = self.sub(x, y),
-                0x6 => self.shift_right(x),
-                0x7 => self.registers[x] = self.sub(y, x),
-                0xe => self.shift_left(x),
-                _ => panic!("unknown opcode 0x{:04x}", opcode),
-            },
-            0x9000 => self.skip_if(self.registers[x] != self.registers[y]),
-            0xa000 => self.i = addr,
-            0xb000 => self.pc = addr + self.registers[0] as u16,
-            0xc000 => self.registers[x] = byte & random(),
-            0xd000 => self.draw_sprite(
-                self.registers[x] as usize,
-                self.registers[y] as usize,
-                nibble as usize,
-            ),
-            0xe000 => match byte {
-                0x9e => self.skip_if(self.keys[self.registers[x] as usize]),
-                0xa1 => self.skip_if(!self.keys[self.registers[x] as usize]),
-                _ => panic!("unknown opcode 0x{:04x}", opcode),
-            },
-            0xf000 => match byte {
-                0x07 => self.registers[x] = self.dt,
-                0x0a => self.wait_for_key(x),
-                0x15 => self.dt = self.registers[x],
-                0x18 => self.st = self.registers[x],
-                0x1e => self.i += self.registers[x] as u16,
-                0x29 => self.i = self.registers[x] as u16 * 5,
-                0x33 => self.store_bcd(x),
-                0x55 => self.ram[self.i as usize..=self.i as usize + x]
-                    .clone_from_slice(&self.registers[0..=x]),
-                0x65 => {
-                    self.registers[0..=x]
-                        .clone_from_slice(&self.ram[self.i as usize..=self.i as usize + x]);
-                }
-                _ => panic!("unknown opcode 0x{:04x}", opcode),
-            },
-            _ => panic!("unknown opcode 0x{:04x}", opcode),
-        }
-    }
-
-    #[inline(always)]
-    fn fetch_opcode(&mut self) -> u16 {
-        let opcode =
-            (self.ram[self.pc as usize] as u16) << 8 | self.ram[self.pc as usize + 1] as u16;
-        self.pc += 2;
-        opcode
-    }
-
-    #[inline(always)]
-    fn add(&mut self, x: usize, y: usize) {
-        let (sum, overflow) = self.registers[x].overflowing_add(self.registers[y]);
-        self.registers[0xf] = overflow as u8;
-        self.registers[x] = sum;
-    }
-
-    #[inline(always)]
-    fn sub(&mut self, x: usize, y: usize) -> u8 {
-        self.registers[0xf] = (self.registers[x] > self.registers[y]) as u8;
-        self.registers[x].saturating_sub(self.registers[y])
-    }
-
-    #[inline(always)]
-    fn call_subroutine(&mut self, addr: u16) {
-        self.stack[self.sp as usize] = self.pc;
-        self.sp += 1;
-        self.pc = addr;
-    }
-
-    #[inline(always)]
-    fn return_subroutine(&mut self) {
-        self.sp -= 1;
-        self.pc = self.stack[self.sp as usize];
-    }
-
-    #[inline(always)]
-    fn shift_right(&mut self, x: usize) {
-        self.registers[0xf] = self.registers[x] & 0x01;
-        self.registers[x] >>= 1;
-    }
-
-    #[inline(always)]
-    fn shift_left(&mut self, x: usize) {
-        self.registers[0xf] = (self.registers[x] & 0x80) >> 7;
-        self.registers[x] <<= 1;
-    }
-
-    #[inline(always)]
-    fn store_bcd(&mut self, x: usize) {
-        self.ram[self.i as usize] = self.registers[x] / 100;
-        self.ram[self.i as usize + 1] = (self.registers[x] / 10) % 10;
-        self.ram[self.i as usize + 2] = self.registers[x] % 10;
-    }
-
-    #[inline(always)]
-    fn skip_if(&mut self, skip: bool) {
-        self.pc += if skip { 2 } else { 0 };
-    }
-
-    #[inline(always)]
-    fn wait_for_key(&mut self, x: usize) {
-        if self.keys[self.last_key as usize] {
-            self.registers[x] = self.last_key;
-        } else {
-            self.pc -= 2;
-        }
-    }
-
-    #[inline(always)]
-    fn draw_sprite(&mut self, x0: usize, y0: usize, height: usize) {
-        self.registers[0xf] = 0;
-        for y in 0..height {
-            let sprite_line = self.ram[self.i as usize + y];
-            for x in 0..8 {
-                let xf = (x + x0) % SCREEN_PIXEL_WIDTH;
-                let yf = (y + y0) % SCREEN_PIXEL_HEIGHT;
-                let addr = yf * SCREEN_PIXEL_WIDTH + xf;
-                if (sprite_line & (0x80 >> x)) != 0 {
-                    if self.vram[addr] == 1 {
-                        self.registers[0xf] = 1;
-                    }
-                    self.vram[addr] ^= 1
-                }
-            }
-        }
-    }
-
-    fn load_font(&mut self, font_set: &[u8]) {
-        self.ram[..font_set.len()].clone_from_slice(&font_set);
-    }
-}
-
-impl Default for Chip8 {
-    fn default() -> Chip8 {
-        Chip8::new()
-    }
-}
-
-impl Display for Chip8 {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "PC: 0x{:04x}\nSP: 0x{:04x}", self.pc, self.sp)
-    }
+pub trait Chip8 {
+    fn name(&self) -> &str;
+    fn reset(&mut self);
+    fn reset_hard(&mut self);
+    fn pixels(&self) -> Vec<u8>;
+    fn beep(&self) -> bool;
+    fn pc(&self) -> u16;
+    fn sp(&self) -> u8;
+    fn load_rom(&mut self, rom: &[u8]);
+    fn clock(&mut self);
+    fn clock_dt(&mut self);
+    fn clock_st(&mut self);
+    fn key_press(&mut self, key: u8);
+    fn key_lift(&mut self, key: u8);
 }
diff --git a/src/chip8_classic.rs b/src/chip8_classic.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3cc0f69102206d3193872532aa1b90fabbfe04a0
--- /dev/null
+++ b/src/chip8_classic.rs
@@ -0,0 +1,332 @@
+use std::fmt::Display;
+
+use crate::{chip8::Chip8, util::random};
+
+#[cfg(feature = "web")]
+use wasm_bindgen::prelude::*;
+
+/// The width of the screen in pixels.
+pub const SCREEN_PIXEL_WIDTH: usize = 64;
+
+/// The height of the screen in pixels.
+pub const SCREEN_PIXEL_HEIGHT: usize = 32;
+
+/// The number of keys to be allocated to the machine.
+const NUM_KEYS: usize = 16;
+
+/// The total number of CPU registers that are going to be used
+/// this value should follow the typical convention.
+const NUM_REGISTERS: usize = 16;
+
+/// The size of the stack, this value is actually a convention
+/// as no specific definition on its size was ever created.
+const STACK_SIZE: usize = 16;
+
+/// The size of the RAM memory in bytes.
+const RAM_SIZE: usize = 4096;
+
+/// The starting address for the ROM loading, should be
+/// the initial PC position.
+const ROM_START: usize = 0x200;
+
+/// Buffer that contains the base CHIP-8 font set that
+/// is going to be used to draw the font in the screen
+static FONT_SET: [u8; 80] = [
+    0xf0, 0x90, 0x90, 0x90, 0xf0, // 0
+    0x20, 0x60, 0x20, 0x20, 0x70, // 1
+    0xf0, 0x10, 0xf0, 0x80, 0xf0, // 2
+    0xf0, 0x10, 0xf0, 0x10, 0xf0, // 3
+    0x90, 0x90, 0xf0, 0x10, 0x10, // 4
+    0xf0, 0x80, 0xf0, 0x10, 0xf0, // 5
+    0xf0, 0x80, 0xf0, 0x90, 0xf0, // 6
+    0xf0, 0x10, 0x20, 0x40, 0x40, // 7
+    0xf0, 0x90, 0xf0, 0x90, 0xf0, // 8
+    0xf0, 0x90, 0xf0, 0x10, 0xf0, // 9
+    0xf0, 0x90, 0xf0, 0x90, 0x90, // A
+    0xe0, 0x90, 0xe0, 0x90, 0xe0, // B
+    0xf0, 0x80, 0x80, 0x80, 0xf0, // C
+    0xe0, 0x90, 0x90, 0x90, 0xe0, // D
+    0xf0, 0x80, 0xf0, 0x80, 0xf0, // E
+    0xf0, 0x80, 0xf0, 0x80, 0x80, // F
+];
+
+#[cfg_attr(feature = "web", wasm_bindgen)]
+pub struct Chip8Classic {
+    vram: [u8; SCREEN_PIXEL_WIDTH * SCREEN_PIXEL_HEIGHT],
+    ram: [u8; RAM_SIZE],
+    registers: [u8; NUM_REGISTERS],
+    stack: [u16; STACK_SIZE],
+    i: u16,
+    dt: u8,
+    st: u8,
+    pc: u16,
+    sp: u8,
+    beep: bool,
+    last_key: u8,
+    keys: [bool; NUM_KEYS],
+}
+
+#[cfg_attr(feature = "web", wasm_bindgen)]
+impl Chip8 for Chip8Classic {
+    fn name(&self) -> &str {
+        "classic"
+    }
+
+    fn reset(&mut self) {
+        self.vram = [0u8; SCREEN_PIXEL_WIDTH * SCREEN_PIXEL_HEIGHT];
+        self.registers = [0u8; NUM_REGISTERS];
+        self.stack = [0u16; STACK_SIZE];
+        self.i = 0;
+        self.dt = 0;
+        self.st = 0;
+        self.pc = ROM_START as u16;
+        self.sp = 0;
+        self.beep = false;
+        self.last_key = 0x00;
+        self.keys = [false; NUM_KEYS];
+        self.load_font(&FONT_SET);
+    }
+
+    fn reset_hard(&mut self) {
+        self.ram = [0u8; RAM_SIZE];
+        self.reset();
+    }
+
+    fn pixels(&self) -> Vec<u8> {
+        self.vram.to_vec()
+    }
+
+    fn beep(&self) -> bool {
+        self.beep
+    }
+
+    fn pc(&self) -> u16 {
+        self.pc
+    }
+
+    fn sp(&self) -> u8 {
+        self.sp
+    }
+
+    fn load_rom(&mut self, rom: &[u8]) {
+        self.ram[ROM_START..ROM_START + rom.len()].clone_from_slice(rom);
+    }
+
+    fn clock(&mut self) {
+        let opcode = self.fetch_opcode();
+        self.process_opcode(opcode);
+    }
+
+    fn clock_dt(&mut self) {
+        self.dt = self.dt.saturating_sub(1);
+    }
+
+    fn clock_st(&mut self) {
+        self.st = self.st.saturating_sub(1);
+        self.beep = self.st > 0;
+    }
+
+    fn key_press(&mut self, key: u8) {
+        if key < NUM_KEYS as u8 {
+            self.last_key = key;
+            self.keys[key as usize] = true;
+        }
+    }
+
+    fn key_lift(&mut self, key: u8) {
+        if key < NUM_KEYS as u8 {
+            self.keys[key as usize] = false;
+        }
+    }
+}
+
+impl Chip8Classic {
+    #[cfg_attr(feature = "web", wasm_bindgen(constructor))]
+    pub fn new() -> Chip8Classic {
+        let mut chip8 = Chip8Classic {
+            vram: [0u8; SCREEN_PIXEL_WIDTH * SCREEN_PIXEL_HEIGHT],
+            ram: [0u8; RAM_SIZE],
+            registers: [0u8; NUM_REGISTERS],
+            stack: [0u16; STACK_SIZE],
+            i: 0,
+            dt: 0,
+            st: 0,
+            pc: ROM_START as u16,
+            sp: 0,
+            beep: false,
+            last_key: 0x00,
+            keys: [false; NUM_KEYS],
+        };
+        chip8.load_font(&FONT_SET);
+        chip8
+    }
+
+    fn process_opcode(&mut self, opcode: u16) {
+        let id = opcode & 0xf000;
+        let addr = opcode & 0x0fff;
+        let nibble = (opcode & 0x000f) as u8;
+        let x = (opcode >> 8 & 0xf) as usize;
+        let y = (opcode >> 4 & 0xf) as usize;
+        let byte = (opcode & 0x00ff) as u8;
+
+        match id {
+            0x0000 => match byte {
+                0xe0 => self.vram = [0u8; SCREEN_PIXEL_WIDTH * SCREEN_PIXEL_HEIGHT],
+                0xee => self.return_subroutine(),
+                _ => panic!("unknown opcode 0x{:04x}", opcode),
+            },
+            0x1000 => self.pc = addr,
+            0x2000 => self.call_subroutine(addr),
+            0x3000 => self.skip_if(self.registers[x] == byte),
+            0x4000 => self.skip_if(self.registers[x] != byte),
+            0x5000 => self.skip_if(self.registers[x] == self.registers[y]),
+            0x6000 => self.registers[x] = byte,
+            0x7000 => self.registers[x] = self.registers[x].wrapping_add(byte),
+            0x8000 => match nibble {
+                0x0 => self.registers[x] = self.registers[y],
+                0x1 => self.registers[x] |= self.registers[y],
+                0x2 => self.registers[x] &= self.registers[y],
+                0x3 => self.registers[x] ^= self.registers[y],
+                0x4 => self.add(x, y),
+                0x5 => self.registers[x] = self.sub(x, y),
+                0x6 => self.shift_right(x),
+                0x7 => self.registers[x] = self.sub(y, x),
+                0xe => self.shift_left(x),
+                _ => panic!("unknown opcode 0x{:04x}", opcode),
+            },
+            0x9000 => self.skip_if(self.registers[x] != self.registers[y]),
+            0xa000 => self.i = addr,
+            0xb000 => self.pc = addr + self.registers[0] as u16,
+            0xc000 => self.registers[x] = byte & random(),
+            0xd000 => self.draw_sprite(
+                self.registers[x] as usize,
+                self.registers[y] as usize,
+                nibble as usize,
+            ),
+            0xe000 => match byte {
+                0x9e => self.skip_if(self.keys[self.registers[x] as usize]),
+                0xa1 => self.skip_if(!self.keys[self.registers[x] as usize]),
+                _ => panic!("unknown opcode 0x{:04x}", opcode),
+            },
+            0xf000 => match byte {
+                0x07 => self.registers[x] = self.dt,
+                0x0a => self.wait_for_key(x),
+                0x15 => self.dt = self.registers[x],
+                0x18 => self.st = self.registers[x],
+                0x1e => self.i += self.registers[x] as u16,
+                0x29 => self.i = self.registers[x] as u16 * 5,
+                0x33 => self.store_bcd(x),
+                0x55 => self.ram[self.i as usize..=self.i as usize + x]
+                    .clone_from_slice(&self.registers[0..=x]),
+                0x65 => {
+                    self.registers[0..=x]
+                        .clone_from_slice(&self.ram[self.i as usize..=self.i as usize + x]);
+                }
+                _ => panic!("unknown opcode 0x{:04x}", opcode),
+            },
+            _ => panic!("unknown opcode 0x{:04x}", opcode),
+        }
+    }
+
+    #[inline(always)]
+    fn fetch_opcode(&mut self) -> u16 {
+        let opcode =
+            (self.ram[self.pc as usize] as u16) << 8 | self.ram[self.pc as usize + 1] as u16;
+        self.pc += 2;
+        opcode
+    }
+
+    #[inline(always)]
+    fn add(&mut self, x: usize, y: usize) {
+        let (sum, overflow) = self.registers[x].overflowing_add(self.registers[y]);
+        self.registers[0xf] = overflow as u8;
+        self.registers[x] = sum;
+    }
+
+    #[inline(always)]
+    fn sub(&mut self, x: usize, y: usize) -> u8 {
+        self.registers[0xf] = (self.registers[x] > self.registers[y]) as u8;
+        self.registers[x].saturating_sub(self.registers[y])
+    }
+
+    #[inline(always)]
+    fn call_subroutine(&mut self, addr: u16) {
+        self.stack[self.sp as usize] = self.pc;
+        self.sp += 1;
+        self.pc = addr;
+    }
+
+    #[inline(always)]
+    fn return_subroutine(&mut self) {
+        self.sp -= 1;
+        self.pc = self.stack[self.sp as usize];
+    }
+
+    #[inline(always)]
+    fn shift_right(&mut self, x: usize) {
+        self.registers[0xf] = self.registers[x] & 0x01;
+        self.registers[x] >>= 1;
+    }
+
+    #[inline(always)]
+    fn shift_left(&mut self, x: usize) {
+        self.registers[0xf] = (self.registers[x] & 0x80) >> 7;
+        self.registers[x] <<= 1;
+    }
+
+    #[inline(always)]
+    fn store_bcd(&mut self, x: usize) {
+        self.ram[self.i as usize] = self.registers[x] / 100;
+        self.ram[self.i as usize + 1] = (self.registers[x] / 10) % 10;
+        self.ram[self.i as usize + 2] = self.registers[x] % 10;
+    }
+
+    #[inline(always)]
+    fn skip_if(&mut self, skip: bool) {
+        self.pc += if skip { 2 } else { 0 };
+    }
+
+    #[inline(always)]
+    fn wait_for_key(&mut self, x: usize) {
+        if self.keys[self.last_key as usize] {
+            self.registers[x] = self.last_key;
+        } else {
+            self.pc -= 2;
+        }
+    }
+
+    #[inline(always)]
+    fn draw_sprite(&mut self, x0: usize, y0: usize, height: usize) {
+        self.registers[0xf] = 0;
+        for y in 0..height {
+            let sprite_line = self.ram[self.i as usize + y];
+            for x in 0..8 {
+                let xf = (x + x0) % SCREEN_PIXEL_WIDTH;
+                let yf = (y + y0) % SCREEN_PIXEL_HEIGHT;
+                let addr = yf * SCREEN_PIXEL_WIDTH + xf;
+                if (sprite_line & (0x80 >> x)) != 0 {
+                    if self.vram[addr] == 1 {
+                        self.registers[0xf] = 1;
+                    }
+                    self.vram[addr] ^= 1
+                }
+            }
+        }
+    }
+
+    fn load_font(&mut self, font_set: &[u8]) {
+        self.ram[..font_set.len()].clone_from_slice(&font_set);
+    }
+}
+
+impl Default for Chip8Classic {
+    fn default() -> Chip8Classic {
+        Chip8Classic::new()
+    }
+}
+
+impl Display for Chip8Classic {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "PC: 0x{:04x}\nSP: 0x{:04x}", self.pc, self.sp)
+    }
+}
diff --git a/src/chip8_neo.rs b/src/chip8_neo.rs
index fbcbdc039c123f0707a13d042304ea58186306a0..630d0e5fdf205b514ff03d58ff9522549ac0c41f 100644
--- a/src/chip8_neo.rs
+++ b/src/chip8_neo.rs
@@ -1,4 +1,4 @@
-use crate::util::random;
+use crate::{chip8::Chip8, util::random};
 
 pub const DISPLAY_WIDTH: usize = 64;
 pub const DISPLAY_HEIGHT: usize = 32;
@@ -46,27 +46,12 @@ pub struct Chip8Neo {
 }
 
 #[cfg_attr(feature = "web", wasm_bindgen)]
-impl Chip8Neo {
-    #[cfg_attr(feature = "web", wasm_bindgen(constructor))]
-    pub fn new() -> Chip8Neo {
-        let mut chip8 = Chip8Neo {
-            ram: [0u8; RAM_SIZE],
-            vram: [0u8; DISPLAY_WIDTH * DISPLAY_HEIGHT],
-            stack: [0u16; STACK_SIZE],
-            regs: [0u8; REGISTERS_SIZE],
-            pc: ROM_START as u16,
-            i: 0x0,
-            sp: 0x0,
-            dt: 0x0,
-            st: 0x0,
-            keys: [false; KEYS_SIZE],
-            last_key: 0x0,
-        };
-        chip8.load_default_font();
-        chip8
+impl Chip8 for Chip8Neo {
+    fn name(&self) -> &str {
+        "neo"
     }
 
-    pub fn reset(&mut self) {
+    fn reset(&mut self) {
         self.vram = [0u8; DISPLAY_WIDTH * DISPLAY_HEIGHT];
         self.stack = [0u16; STACK_SIZE];
         self.regs = [0u8; REGISTERS_SIZE];
@@ -80,20 +65,16 @@ impl Chip8Neo {
         self.load_default_font();
     }
 
-    pub fn reset_hard(&mut self) {
+    fn reset_hard(&mut self) {
         self.ram = [0u8; RAM_SIZE];
         self.reset();
     }
 
-    pub fn name(&self) -> &str {
-        "neo"
-    }
-
-    pub fn pixels(&self) -> Vec<u8> {
+    fn pixels(&self) -> Vec<u8> {
         self.vram.to_vec()
     }
 
-    pub fn clock(&mut self) {
+    fn clock(&mut self) {
         // fetches the current instruction and increments
         // the PC (program counter) accordingly
         let instruction =
@@ -220,15 +201,15 @@ impl Chip8Neo {
         }
     }
 
-    pub fn clock_dt(&mut self) {
+    fn clock_dt(&mut self) {
         self.dt = self.dt.saturating_sub(1)
     }
 
-    pub fn clock_st(&mut self) {
+    fn clock_st(&mut self) {
         self.st = self.st.saturating_sub(1)
     }
 
-    pub fn key_press(&mut self, key: u8) {
+    fn key_press(&mut self, key: u8) {
         if key >= KEYS_SIZE as u8 {
             return;
         }
@@ -236,28 +217,49 @@ impl Chip8Neo {
         self.last_key = key;
     }
 
-    pub fn key_lift(&mut self, key: u8) {
+    fn key_lift(&mut self, key: u8) {
         if key >= KEYS_SIZE as u8 {
             return;
         }
         self.keys[key as usize] = false;
     }
 
-    pub fn load_rom(&mut self, rom: &[u8]) {
+    fn load_rom(&mut self, rom: &[u8]) {
         self.ram[ROM_START..ROM_START + rom.len()].clone_from_slice(&rom);
     }
 
-    pub fn beep(&self) -> bool {
+    fn beep(&self) -> bool {
         self.st > 0
     }
 
-    pub fn pc(&self) -> u16 {
+    fn pc(&self) -> u16 {
         self.pc
     }
 
-    pub fn sp(&self) -> u8 {
+    fn sp(&self) -> u8 {
         self.sp
     }
+}
+
+impl Chip8Neo {
+    #[cfg_attr(feature = "web", wasm_bindgen(constructor))]
+    pub fn new() -> Chip8Neo {
+        let mut chip8 = Chip8Neo {
+            ram: [0u8; RAM_SIZE],
+            vram: [0u8; DISPLAY_WIDTH * DISPLAY_HEIGHT],
+            stack: [0u16; STACK_SIZE],
+            regs: [0u8; REGISTERS_SIZE],
+            pc: ROM_START as u16,
+            i: 0x0,
+            sp: 0x0,
+            dt: 0x0,
+            st: 0x0,
+            keys: [false; KEYS_SIZE],
+            last_key: 0x0,
+        };
+        chip8.load_default_font();
+        chip8
+    }
 
     fn load_font(&mut self, position: usize, font_set: &[u8]) {
         self.ram[position..position + font_set.len()].clone_from_slice(&font_set);
diff --git a/src/lib.rs b/src/lib.rs
index 906a030a2ea6efafe48d498e6e7fd56ed8e45ebf..8dbb219100e47a047cf519d605fbbd2f2e9b462a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,4 @@
 pub mod chip8;
+pub mod chip8_classic;
 pub mod chip8_neo;
 pub mod util;