diff --git a/Source/Core/VideoBackends/Software/SWCommandProcessor.cpp b/Source/Core/VideoBackends/Software/SWCommandProcessor.cpp
index 06876a2035..e2f402a040 100644
--- a/Source/Core/VideoBackends/Software/SWCommandProcessor.cpp
+++ b/Source/Core/VideoBackends/Software/SWCommandProcessor.cpp
@@ -9,6 +9,7 @@
 #include "Core.h"
 #include "CoreTiming.h"
 #include "HW/Memmap.h"
+#include "HW/MMIO.h"
 #include "HW/ProcessorInterface.h"
 
 #include "VideoBackend.h"
@@ -124,147 +125,77 @@ void RunGpu()
 	}
 }
 
-void Read16(u16& _rReturnValue, const u32 _Address)
+void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
 {
-	u32 regAddr = (_Address & 0xFFF) >> 1;
-
-	DEBUG_LOG(COMMANDPROCESSOR, "(r): 0x%08x : 0x%08x", _Address, ((u16*)&cpreg)[regAddr]);
-
-	if (regAddr < 0x20)
-		_rReturnValue = ((u16*)&cpreg)[regAddr];
-	else
-		_rReturnValue = 0;
-}
-
-void Write16(const u16 _Value, const u32 _Address)
-{
-	INFO_LOG(COMMANDPROCESSOR, "(write16): 0x%04x @ 0x%08x",_Value,_Address);
-
-	switch (_Address & 0xFFF)
+	// Directly map reads and writes to the cpreg structure.
+	for (size_t i = 0; i < sizeof (cpreg) / sizeof (u16); ++i)
 	{
-	case STATUS_REGISTER:
-		{
-			ERROR_LOG(COMMANDPROCESSOR,"\t write to STATUS_REGISTER : %04x", _Value);
-		}
-		break;
+		u16* ptr = ((u16*)&cpreg) + i;
+		mmio->Register(base | (i * 2),
+			MMIO::DirectRead<u16>(ptr),
+			MMIO::DirectWrite<u16>(ptr)
+		);
+	}
 
-	case CTRL_REGISTER:
-		{
-			cpreg.ctrl.Hex = _Value;
+	// Bleh. Apparently SWCommandProcessor does not know about regs 0x40 to
+	// 0x64...
+	for (size_t i = 0x40; i < 0x64; ++i)
+	{
+		mmio->Register(base | i,
+			MMIO::Constant<u16>(0),
+			MMIO::Nop<u16>()
+		);
+	}
 
-			DEBUG_LOG(COMMANDPROCESSOR,"\t write to CTRL_REGISTER : %04x", _Value);
-			DEBUG_LOG(COMMANDPROCESSOR, "\t GPREAD %s | CPULINK %s | BP %s || BPIntEnable %s | OvF %s | UndF %s"
-				, cpreg.ctrl.GPReadEnable ?				"ON" : "OFF"
-				, cpreg.ctrl.GPLinkEnable ?				"ON" : "OFF"
-				, cpreg.ctrl.BPEnable ?					"ON" : "OFF"
-				, cpreg.ctrl.BreakPointIntEnable ?		"ON" : "OFF"
-				, cpreg.ctrl.FifoOverflowIntEnable ?	"ON" : "OFF"
-				, cpreg.ctrl.FifoUnderflowIntEnable ?	"ON" : "OFF"
-				);
-		}
-		break;
+	// The low part of MMIO regs for FIFO addresses needs to be aligned to 32
+	// bytes.
+	u32 fifo_addr_lo_regs[] = {
+		FIFO_BASE_LO, FIFO_END_LO, FIFO_WRITE_POINTER_LO,
+		FIFO_READ_POINTER_LO, FIFO_BP_LO, FIFO_RW_DISTANCE_LO,
+	};
+	for (u32 reg : fifo_addr_lo_regs)
+	{
+		mmio->RegisterWrite(base | reg,
+			MMIO::DirectWrite<u16>(((u16*)&cpreg) + (reg / 2), 0xFFE0)
+		);
+	}
 
-	case CLEAR_REGISTER:
-		{
-			UCPClearReg tmpClear(_Value);
+	// The clear register needs to perform some more complicated operations on
+	// writes.
+	mmio->RegisterWrite(base | CLEAR_REGISTER,
+		MMIO::ComplexWrite<u16>([](u32, u16 val) {
+			UCPClearReg tmpClear(val);
 
 			if (tmpClear.ClearFifoOverflow)
 				cpreg.status.OverflowHiWatermark = 0;
 			if (tmpClear.ClearFifoUnderflow)
 				cpreg.status.UnderflowLoWatermark = 0;
+		})
+	);
+}
 
-			INFO_LOG(COMMANDPROCESSOR,"\t write to CLEAR_REGISTER : %04x",_Value);
-		}
-		break;
+void Read16(u16& _rReturnValue, const u32 _Address)
+{
+	// HACK: Remove this function when the new MMIO interface is used.
+	Memory::mmio_mapping->Read(_Address, _rReturnValue);
+}
 
-	// Fifo Registers
-	case FIFO_TOKEN_REGISTER:
-		cpreg.token = _Value;
-		DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_TOKEN_REGISTER : %04x", _Value);
-		break;
-
-	case FIFO_BASE_LO:
-		WriteLow ((u32 &)cpreg.fifobase, _Value & 0xFFE0);
-		DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_BASE_LO. FIFO base is : %08x", cpreg.fifobase);
-		break;
-	case FIFO_BASE_HI:
-		WriteHigh((u32 &)cpreg.fifobase, _Value);
-		DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_BASE_HI. FIFO base is : %08x", cpreg.fifobase);
-		break;
-	case FIFO_END_LO:
-		WriteLow ((u32 &)cpreg.fifoend, _Value & 0xFFE0);
-		DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_END_LO. FIFO end is : %08x", cpreg.fifoend);
-		break;
-	case FIFO_END_HI:
-		WriteHigh((u32 &)cpreg.fifoend, _Value);
-		DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_END_HI. FIFO end is : %08x", cpreg.fifoend);
-		break;
-
-	case FIFO_WRITE_POINTER_LO:
-		WriteLow ((u32 &)cpreg.writeptr, _Value & 0xFFE0);
-		DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_WRITE_POINTER_LO. write ptr is : %08x", cpreg.writeptr);
-		break;
-	case FIFO_WRITE_POINTER_HI:
-		WriteHigh ((u32 &)cpreg.writeptr, _Value);
-		DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_WRITE_POINTER_HI. write ptr is : %08x", cpreg.writeptr);
-		break;
-	case FIFO_READ_POINTER_LO:
-		WriteLow ((u32 &)cpreg.readptr, _Value & 0xFFE0);
-		DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_READ_POINTER_LO. read ptr is : %08x", cpreg.readptr);
-		break;
-	case FIFO_READ_POINTER_HI:
-		WriteHigh ((u32 &)cpreg.readptr, _Value);
-		DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_READ_POINTER_HI. read ptr is : %08x", cpreg.readptr);
-		break;
-
-	case FIFO_HI_WATERMARK_LO:
-		WriteLow ((u32 &)cpreg.hiwatermark, _Value);
-		DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_HI_WATERMARK_LO. hiwatermark is : %08x", cpreg.hiwatermark);
-		break;
-	case FIFO_HI_WATERMARK_HI:
-		WriteHigh ((u32 &)cpreg.hiwatermark, _Value);
-		DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_HI_WATERMARK_HI. hiwatermark is : %08x", cpreg.hiwatermark);
-		break;
-	case FIFO_LO_WATERMARK_LO:
-		WriteLow ((u32 &)cpreg.lowatermark, _Value);
-		DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_LO_WATERMARK_LO. lowatermark is : %08x", cpreg.lowatermark);
-		break;
-	case FIFO_LO_WATERMARK_HI:
-		WriteHigh ((u32 &)cpreg.lowatermark, _Value);
-		DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_LO_WATERMARK_HI. lowatermark is : %08x", cpreg.lowatermark);
-		break;
-
-	case FIFO_BP_LO:
-		WriteLow ((u32 &)cpreg.breakpt, _Value & 0xFFE0);
-		DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_BP_LO. breakpoint is : %08x", cpreg.breakpt);
-		break;
-	case FIFO_BP_HI:
-		WriteHigh ((u32 &)cpreg.breakpt, _Value);
-		DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_BP_HI. breakpoint is : %08x", cpreg.breakpt);
-		break;
-
-	case FIFO_RW_DISTANCE_LO:
-		WriteLow ((u32 &)cpreg.rwdistance, _Value & 0xFFE0);
-		DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_RW_DISTANCE_LO. rwdistance is : %08x", cpreg.rwdistance);
-		break;
-	case FIFO_RW_DISTANCE_HI:
-		WriteHigh ((u32 &)cpreg.rwdistance, _Value);
-		DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_RW_DISTANCE_HI. rwdistance is : %08x", cpreg.rwdistance);
-		break;
-	}
-
-	RunGpu();
+void Write16(const u16 _Value, const u32 _Address)
+{
+	// HACK: Remove this function when the new MMIO interface is used.
+	Memory::mmio_mapping->Write(_Address, _Value);
 }
 
 void Read32(u32& _rReturnValue, const u32 _Address)
 {
-	_rReturnValue = 0;
-	_dbg_assert_msg_(COMMANDPROCESSOR, 0, "Read32 from CommandProcessor at 0x%08x", _Address);
+	// HACK: Remove this function when the new MMIO interface is used.
+	Memory::mmio_mapping->Read(_Address, _rReturnValue);
 }
 
 void Write32(const u32 _Data, const u32 _Address)
 {
-	_dbg_assert_msg_(COMMANDPROCESSOR, 0, "Write32 at CommandProcessor at 0x%08x", _Address);
+	// HACK: Remove this function when the new MMIO interface is used.
+	Memory::mmio_mapping->Write(_Address, _Data);
 }
 
 void STACKALIGN GatherPipeBursted()
diff --git a/Source/Core/VideoBackends/Software/SWCommandProcessor.h b/Source/Core/VideoBackends/Software/SWCommandProcessor.h
index 043ff85d0e..1144b66b67 100644
--- a/Source/Core/VideoBackends/Software/SWCommandProcessor.h
+++ b/Source/Core/VideoBackends/Software/SWCommandProcessor.h
@@ -7,6 +7,7 @@
 #include "Common.h"
 
 class PointerWrap;
+namespace MMIO { class Mapping; }
 
 extern volatile bool g_bSkipCurrentFrame;
 extern u8* g_pVideoData;
@@ -123,6 +124,8 @@ namespace SWCommandProcessor
 	void Shutdown();
 	void DoState(PointerWrap &p);
 
+	void RegisterMMIO(MMIO::Mapping* mmio, u32 base);
+
 	bool RunBuffer();
 	void RunGpu();
 
diff --git a/Source/Core/VideoBackends/Software/SWPixelEngine.cpp b/Source/Core/VideoBackends/Software/SWPixelEngine.cpp
index 4d2f44f96f..fafa87e015 100644
--- a/Source/Core/VideoBackends/Software/SWPixelEngine.cpp
+++ b/Source/Core/VideoBackends/Software/SWPixelEngine.cpp
@@ -11,6 +11,7 @@
 #include "ChunkFile.h"
 #include "CoreTiming.h"
 #include "ConfigManager.h"
+#include "HW/MMIO.h"
 #include "HW/ProcessorInterface.h"
 
 #include "SWPixelEngine.h"
@@ -60,30 +61,22 @@ void Init()
 	et_SetFinishOnMainThread = CoreTiming::RegisterEvent("SetFinish", SetFinish_OnMainThread);
 }
 
-void Read16(u16& _uReturnValue, const u32 _iAddress)
+void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
 {
-	DEBUG_LOG(PIXELENGINE, "(r16): 0x%08x", _iAddress);
-
-	u16 address = _iAddress & 0xFFF;
-
-	if (address <= 0x2e)
-		_uReturnValue = ((u16*)&pereg)[address >> 1];
-}
-
-void Write32(const u32 _iValue, const u32 _iAddress)
-{
-	WARN_LOG(PIXELENGINE, "(w32): 0x%08x @ 0x%08x",_iValue,_iAddress);
-}
-
-void Write16(const u16 _iValue, const u32 _iAddress)
-{
-	u16 address = _iAddress & 0xFFF;
-
-	switch (address)
+	// Directly map reads and writes to the pereg structure.
+	for (size_t i = 0; i < sizeof (pereg) / sizeof (u16); ++i)
 	{
-	case PE_CTRL_REGISTER:
-		{
-			UPECtrlReg tmpCtrl(_iValue);
+		u16* ptr = (u16*)&pereg + i;
+		mmio->Register(base | (i * 2),
+			MMIO::DirectRead<u16>(ptr),
+			MMIO::DirectWrite<u16>(ptr)
+		);
+	}
+
+	// The control register has some more complex logic to perform on writes.
+	mmio->RegisterWrite(base | PE_CTRL_REGISTER,
+		MMIO::ComplexWrite<u16>([](u32, u16 val) {
+			UPECtrlReg tmpCtrl(val);
 
 			if (tmpCtrl.PEToken)	g_bSignalTokenInterrupt = false;
 			if (tmpCtrl.PEFinish)	g_bSignalFinishInterrupt = false;
@@ -93,15 +86,27 @@ void Write16(const u16 _iValue, const u32 _iAddress)
 			pereg.ctrl.PEToken = 0;		// this flag is write only
 			pereg.ctrl.PEFinish = 0;	// this flag is write only
 
-			DEBUG_LOG(PIXELENGINE, "(w16): PE_CTRL_REGISTER: 0x%04x", _iValue);
 			UpdateInterrupts();
-		}
-		break;
-	default:
-		if (address <= 0x2e)
-			((u16*)&pereg)[address >> 1] = _iValue;
-		break;
-	}
+		})
+	);
+}
+
+void Read16(u16& _uReturnValue, const u32 _iAddress)
+{
+	// HACK: Remove this function when the new MMIO interface is used.
+	Memory::mmio_mapping->Read(_iAddress, _uReturnValue);
+}
+
+void Write32(const u32 _iValue, const u32 _iAddress)
+{
+	// HACK: Remove this function when the new MMIO interface is used.
+	Memory::mmio_mapping->Write(_iAddress, _iValue);
+}
+
+void Write16(const u16 _iValue, const u32 _iAddress)
+{
+	// HACK: Remove this function when the new MMIO interface is used.
+	Memory::mmio_mapping->Write(_iAddress, _iValue);
 }
 
 bool AllowIdleSkipping()
diff --git a/Source/Core/VideoBackends/Software/SWPixelEngine.h b/Source/Core/VideoBackends/Software/SWPixelEngine.h
index 5e4018a03c..e8d0a3d6c0 100644
--- a/Source/Core/VideoBackends/Software/SWPixelEngine.h
+++ b/Source/Core/VideoBackends/Software/SWPixelEngine.h
@@ -8,6 +8,7 @@
 #include "VideoCommon.h"
 
 class PointerWrap;
+namespace MMIO { class Mapping; }
 
 namespace SWPixelEngine
 {
@@ -200,6 +201,8 @@ namespace SWPixelEngine
 	void Init();
 	void DoState(PointerWrap &p);
 
+	void RegisterMMIO(MMIO::Mapping* mmio, u32 base);
+
 	// Read
 	void Read16(u16& _uReturnValue, const u32 _iAddress);
 
diff --git a/Source/Core/VideoBackends/Software/SWmain.cpp b/Source/Core/VideoBackends/Software/SWmain.cpp
index f7c4856b61..4886d2b221 100644
--- a/Source/Core/VideoBackends/Software/SWmain.cpp
+++ b/Source/Core/VideoBackends/Software/SWmain.cpp
@@ -362,12 +362,12 @@ void VideoSoftware::Video_AbortFrame(void)
 
 void VideoSoftware::RegisterCPMMIO(MMIO::Mapping* mmio, u32 base)
 {
-	// TODO
+	SWCommandProcessor::RegisterMMIO(mmio, base);
 }
 
 void VideoSoftware::RegisterPEMMIO(MMIO::Mapping* mmio, u32 base)
 {
-	// TODO
+	SWPixelEngine::RegisterMMIO(mmio, base);
 }
 
 readFn16 VideoSoftware::Video_CPRead16()