diff --git a/src/apu.rs b/src/apu.rs
index 711e8115e28c80dce8d5af0a479e240ae51372dd..030299b01aa215df4d14c401ae37662a3c6ef3aa 100644
--- a/src/apu.rs
+++ b/src/apu.rs
@@ -1,6 +1,6 @@
 use std::collections::VecDeque;
 
-use crate::{gb::GameBoy, warnln};
+use crate::{gb::GameBoy, mmu::BusComponent, warnln};
 
 const DUTY_TABLE: [[u8; 8]; 4] = [
     [0, 0, 0, 0, 0, 0, 0, 1],
@@ -1076,6 +1076,16 @@ impl Apu {
     }
 }
 
+impl BusComponent for Apu {
+    fn read(&mut self, addr: u16) -> u8 {
+        self.read(addr)
+    }
+
+    fn write(&mut self, addr: u16, value: u8) {
+        self.write(addr, value);
+    }
+}
+
 impl Default for Apu {
     fn default() -> Self {
         Self::new(44100, 2, 1.0, GameBoy::CPU_FREQ)
diff --git a/src/dma.rs b/src/dma.rs
index f3110d26c39a801e86bc8c55356992e0d80cfb88..634cfa301e2dda7e6de8a916a33b34a67c471f79 100644
--- a/src/dma.rs
+++ b/src/dma.rs
@@ -1,5 +1,6 @@
 use crate::{
     consts::{DMA_ADDR, HDMA1_ADDR, HDMA2_ADDR, HDMA3_ADDR, HDMA4_ADDR, HDMA5_ADDR},
+    mmu::BusComponent,
     warnln,
 };
 
@@ -173,6 +174,16 @@ impl Dma {
     }
 }
 
+impl BusComponent for Dma {
+    fn read(&mut self, addr: u16) -> u8 {
+        self.read(addr)
+    }
+
+    fn write(&mut self, addr: u16, value: u8) {
+        self.write(addr, value);
+    }
+}
+
 impl Default for Dma {
     fn default() -> Self {
         Self::new()
diff --git a/src/mmu.rs b/src/mmu.rs
index 3a7c5edfc1126594e83a1c192042e09e0a4e6adb..350abcf7aa5ab34264555a4caec7389b6a1cb835 100644
--- a/src/mmu.rs
+++ b/src/mmu.rs
@@ -21,6 +21,21 @@ pub const BOOT_SIZE_CGB: usize = 2304;
 pub const RAM_SIZE_DMG: usize = 8192;
 pub const RAM_SIZE_CGB: usize = 32768;
 
+pub trait BusComponent {
+    fn read(&mut self, addr: u16) -> u8;
+    fn write(&mut self, addr: u16, value: u8);
+    fn read_many(&mut self, addr: u16, count: usize) -> Vec<u8> {
+        (0..count)
+            .map(|offset| self.read(addr + offset as u16))
+            .collect()
+    }
+    fn write_many(&mut self, addr: u16, values: &[u8]) {
+        for (offset, &value) in values.iter().enumerate() {
+            self.write(addr + offset as u16, value);
+        }
+    }
+}
+
 pub struct Mmu {
     /// Register that controls the interrupts that are considered
     /// to be enabled and should be triggered.
diff --git a/src/pad.rs b/src/pad.rs
index be3a94b697d1fb55c11e3ad0a9360a99453a9a42..37cb34fc6a7751e9e9bbd3e282825e368d1b1883 100644
--- a/src/pad.rs
+++ b/src/pad.rs
@@ -1,6 +1,6 @@
 //! Gamepad related functions and structures.
 
-use crate::warnln;
+use crate::{mmu::BusComponent, warnln};
 
 #[cfg(feature = "wasm")]
 use wasm_bindgen::prelude::*;
@@ -167,6 +167,16 @@ impl Pad {
     }
 }
 
+impl BusComponent for Pad {
+    fn read(&mut self, addr: u16) -> u8 {
+        self.read(addr)
+    }
+
+    fn write(&mut self, addr: u16, value: u8) {
+        self.write(addr, value);
+    }
+}
+
 impl Default for Pad {
     fn default() -> Self {
         Self::new()
diff --git a/src/ppu.rs b/src/ppu.rs
index 196618bd08cfd8920df29f93df7cbb7022f81b86..bd1d06b10df74c902a56b4a319038a770efa9bc2 100644
--- a/src/ppu.rs
+++ b/src/ppu.rs
@@ -11,6 +11,7 @@ use std::{
 
 use crate::{
     gb::{GameBoyConfig, GameBoyMode},
+    mmu::BusComponent,
     util::SharedThread,
     warnln,
 };
@@ -2091,6 +2092,16 @@ impl Ppu {
     }
 }
 
+impl BusComponent for Ppu {
+    fn read(&mut self, addr: u16) -> u8 {
+        self.read(addr)
+    }
+
+    fn write(&mut self, addr: u16, value: u8) {
+        self.write(addr, value);
+    }
+}
+
 impl Default for Ppu {
     fn default() -> Self {
         Self::new(
diff --git a/src/rom.rs b/src/rom.rs
index c5fb6c86a7064aa863f258a4a8088c58501d28b1..541ef6a106054249246d58133943b935e4bbfb41 100644
--- a/src/rom.rs
+++ b/src/rom.rs
@@ -10,6 +10,7 @@ use crate::{
     debugln,
     error::Error,
     gb::GameBoyMode,
+    mmu::BusComponent,
     util::read_file,
     warnln,
 };
@@ -370,7 +371,7 @@ impl Cartridge {
         Self::from_data(&data)
     }
 
-    pub fn read(&self, addr: u16) -> u8 {
+    pub fn read(&mut self, addr: u16) -> u8 {
         match addr & 0xf000 {
             0x0000 | 0x1000 | 0x2000 | 0x3000 | 0x4000 | 0x5000 | 0x6000 | 0x7000 => {
                 (self.handler.read_rom)(self, addr)
@@ -823,6 +824,16 @@ impl Cartridge {
     }
 }
 
+impl BusComponent for Cartridge {
+    fn read(&mut self, addr: u16) -> u8 {
+        self.read(addr)
+    }
+
+    fn write(&mut self, addr: u16, value: u8) {
+        self.write(addr, value);
+    }
+}
+
 impl Default for Cartridge {
     fn default() -> Self {
         Self::new()
diff --git a/src/serial.rs b/src/serial.rs
index 4147e1b1af711df47a6930a4324248c529f2ac59..786e63ad6ba3c40e437c174befe298712a52b103 100644
--- a/src/serial.rs
+++ b/src/serial.rs
@@ -1,4 +1,4 @@
-use crate::warnln;
+use crate::{mmu::BusComponent, warnln};
 
 pub trait SerialDevice {
     /// Sends a byte (u8) to the attached serial connection.
@@ -202,6 +202,16 @@ impl Serial {
     }
 }
 
+impl BusComponent for Serial {
+    fn read(&mut self, addr: u16) -> u8 {
+        self.read(addr)
+    }
+
+    fn write(&mut self, addr: u16, value: u8) {
+        self.write(addr, value);
+    }
+}
+
 impl Default for Serial {
     fn default() -> Self {
         Self::new()
diff --git a/src/timer.rs b/src/timer.rs
index 057cdeb97ead5025f48715710bfdf1af79624811..311c43706b57fac85b4fd556c04854504b8b61ce 100644
--- a/src/timer.rs
+++ b/src/timer.rs
@@ -1,5 +1,6 @@
 use crate::{
     consts::{DIV_ADDR, TAC_ADDR, TIMA_ADDR, TMA_ADDR},
+    mmu::BusComponent,
     warnln,
 };
 
@@ -147,6 +148,16 @@ impl Timer {
     }
 }
 
+impl BusComponent for Timer {
+    fn read(&mut self, addr: u16) -> u8 {
+        self.read(addr)
+    }
+
+    fn write(&mut self, addr: u16, value: u8) {
+        self.write(addr, value);
+    }
+}
+
 impl Default for Timer {
     fn default() -> Self {
         Self::new()