MMIOとは何か
MMIO(Memory Mapped I/O)とは、周辺機器や ROM・RAM などを「メモリの一部」として扱うための設計手法である。
CPUは基本的に、次の2つのメモリ操作命令しか持たない。
- load(読み出し)
- store(書き込み)
MMIOは、この load / store 命令だけを使って、周辺機器も操作できるようにする仕組みである。
なぜMMIOが必要なのか
メモリ(RAM / ROM)は load / store で操作できるため直感的で分かりやすい。しかし周辺機器(キーボード、UART、タイマなど)も、同じように操作できたほうが都合が良い。
たとえば、
- キーボードで「a」が入力された
- それを CPU が知りたい
このとき、「キーボードの状態を、あるメモリアドレスを load することで取得できる」という形にできれば、CPUやOSは非常にシンプルになる。
昔はポートI/O(in / out 命令)という別系統の仕組みが使われていたが、
- メモリ操作と I/O 操作で命令体系が分かれる
- CPU設計・OS・最適化・仮想化が複雑になる
といった問題があった。
そこで、すべてをメモリアクセスとして統一しようという思想が生まれ、それを実現するのが MMIO である。
メモリマップという考え方
MMIOでは、CPUから見た 1つの大きなアドレス空間 の中に、それぞれ役割の異なる領域を割り当てる。
例として、次のようなメモリマップを考える。
- 0x0000_0000 ~ 0x0FFF_FFFF:ROM
- 0x1000_0000 ~ 0x1FFF_FFFF:RAM
- 0x2000_0000 ~ 0x2000_00FF:キーボード(周辺機器)
CPUは単に、
- このアドレスを load する
- このアドレスに store する
だけでよく、それが メモリなのか周辺機器なのかを意識する必要はない。
周辺機器は「自分のアドレス」を知らない
ここが MMIO を理解するうえで最も重要なポイントである。
たとえば、キーボードが SoC 全体では 0x2000_0000 に配置されていたとしても、キーボード自身はそのことを知らない。
キーボードが理解しているのは、次のような 自分の中のオフセットアドレスだけである。
- 0x0:入力データレジスタ
- 0x4:ステータスレジスタ
したがって、
- 0x2000_0000
- 0x2000_0010
- 0x3000_0000
どこに配置されようと、キーボードは「0番目」「4番目」という ローカルな住所だけを見て動く。
SoC全体のアドレス設計は、完全に設計者側の都合であり、周辺機器や RAM・ROM はそれを一切意識しない。
MMIOの役割(変換と振り分け)
この「CPUが見ている世界」と「各デバイスが見ている世界」の違いをつなぐのが MMIO である。
MMIOは次の役割を持つ。
- CPUから来た物理アドレスを見る
- それが RAM・ROM・周辺機器のどれかを判別する
- 対象デバイスに渡す際に、ベースアドレスを引いてオフセットに変換する
これにより、CPUは1つのアドレス空間だけを意識すればよく、各デバイスは自分の内部アドレスだけを扱えばよくなる。
membus を分ける理由
この設計を実現するために、バス(インターフェース)を分けて考える。
mmio_ram_membus
- CPU / SoC 側の世界
- 物理アドレス(例:0x8000_0010)を扱う
ram_membus
- RAM デバイス側の世界
- RAM 先頭からのオフセット(例:0x10)だけを扱う
この分離により、
- RAMは自分の内部アドレスだけを知っていればよい
- SoCのメモリマップ変更がRAMに影響しない
- RAMや周辺機器を部品(IP)として再利用できる
という設計が可能になる。
全体像を一言でまとめると
MMIOとは、CPUの load / store をアドレスデコードによって適切なデバイスに振り分け、SoC全体の物理アドレスを各デバイスが理解できるローカルなオフセットへ翻訳する仕組みである。