MENU

Pythonでメモリマップを扱う基礎知識:mmap と numpy.recarray の活用方法

この記事では、次のコードを理解するための基礎知識と、関連するハードウェア・ソフトウェアの概念を解説します。

axi_gpio_regset = np.dtype([
    ('gpio1_data'   , 'uint32'),
    ('gpio1_control', 'uint32'),
    ('gpio2_data'   , 'uint32'),
    ('gpio2_control', 'uint32')
])

memory_file_handle = os.open('/dev/mem', os.O_RDWR)
axi_mmap = mmap.mmap(fileno=memory_file_handle, length=mmap.PAGESIZE, offset=0x42000000)
axi_numpy_array = np.recarray(1, axi_gpio_regset, buf=axi_mmap)
axi_array_contents = axi_numpy_array[0]

出典:1.4.2.5. Frequency Counter — Red Pitaya 2.00-35 documentation

目次

ハードウェアの基礎知識

(1) メモリマップI/Oとは

  • メモリマップI/O では、ハードウェアデバイスがメモリ空間に直接マップされ、CPUは通常のメモリアクセスと同様にデバイスを操作できます。
  • ハードウェアの制御やデータ取得のために、特定のアドレスに読み書きする必要があります。

(2) AXI GPIO の役割

  • AXI GPIO は、FPGA内部で構成された汎用入出力ポート(GPIO)を制御するIPコアです。
  • 上記コードでは、gpio1_datagpio2_control などのレジスタにアクセスしています。

Pythonのmmapモジュール

(1) mmapとは

  • mmap モジュールを使用すると、ファイルやデバイスをメモリにマップし、メモリアクセスのようにデータを操作できます。
  • /dev/mem はLinuxでデバイスメモリ全体を公開する特別なファイルで、mmap を使うことで特定のメモリ領域にアクセス可能になります。

(2) 具体例: メモリマップの作成

以下のコードは、/dev/mem を開き、特定のアドレス空間をマップします。

memory_file_handle = os.open('/dev/mem', os.O_RDWR)
axi_mmap = mmap.mmap(fileno=memory_file_handle, length=mmap.PAGESIZE, offset=0x42000000)
  • length=mmap.PAGESIZE: メモリマッピングのサイズをページ単位(通常4KB)で指定します。
  • offset=0x42000000: マップするメモリアドレスの開始位置を指定します。

NumPyのrecarray構造

(1) NumPyの型定義

NumPyでは、dtype を使用して複雑なデータ構造を定義できます。

axi_gpio_regset = np.dtype([
    ('gpio1_data'   , 'uint32'),
    ('gpio1_control', 'uint32'),
    ('gpio2_data'   , 'uint32'),
    ('gpio2_control', 'uint32')
])

このコードでは、次のフィールドを持つ構造体を定義しています。

  • gpio1_data (32ビット符号なし整数)
  • gpio1_control (32ビット符号なし整数)
  • gpio2_data (32ビット符号なし整数)
  • gpio2_control (32ビット符号なし整数)

(2) recarrayによる構造化アクセス

np.recarray を使うと、メモリマップされたデータを構造体のように扱えます。

axi_numpy_array = np.recarray(1, axi_gpio_regset, buf=axi_mmap)
axi_array_contents = axi_numpy_array[0]
  • 1: 配列の要素数(今回は1つだけ)。
  • axi_gpio_regset: 配列の各要素のデータ型。
  • buf=axi_mmap: メモリマップされたデータを指定。

これにより、次のようにフィールド名でアクセスが可能になります。

print(axi_array_contents.gpio1_data)  # gpio1_data の値を取得
axi_array_contents.gpio1_control = 0xDEADBEEF  # gpio1_control に値を書き込む

このコードの重要な部分は、np.recarray を用いてハードウェアのレジスタマッピングを「Pythonオブジェクトとして」扱う仕組みです。

  • axi_gpio_regset: NumPyのデータ型 (dtype) で、メモリ内の構造を定義。
  • axi_numpy_array: メモリマップをデータ構造としてアクセス可能にした配列。
  • axi_array_contents: 配列の1つの要素にアクセスするオブジェクト。

以下は、このコードの具体的なメモリアドレスと recarray のフィールドの対応を示す表です。

フィールド名オフセットデータ型実際のメモリアドレス内容
gpio1_data0x00uint320x42000000GPIO1のデータ
gpio1_control0x04uint320x42000004GPIO1の制御
gpio2_data0x08uint320x42000008GPIO2のデータ
gpio2_control0x0Cuint320x4200000CGPIO2の制御

もし配列の要素数を2にしたときは、2つめのgpio1_data のメモリアドレスは0x42000010となります。

recarray の使い方

1. フィールドの読み書き

axi_array_contents を使うことで、簡単に特定のフィールドを操作できます。

読み出し

gpio1_value = axi_array_contents.gpio1_data
print("GPIO1 Data:", gpio1_value)
  • メモリアドレス 0x42000000 の値を取得。

書き込み

axi_array_contents.gpio1_control = 0xDEADBEEF
  • メモリアドレス 0x420000040xDEADBEEF を書き込む。

2. 配列全体の操作

要素数が複数の場合(例: np.recarray(10, ...))、各要素にアクセスできます。

複数要素の例

axi_numpy_array = np.recarray(10, axi_gpio_regset, buf=axi_mmap)
print(axi_numpy_array[1].gpio1_data)  # 0x42000010 にアクセス

コードの動作の全体像

  1. /dev/mem をオープン: システム全体の物理メモリにアクセス可能なファイルを開きます。
  2. メモリマップを作成: mmap を使い、FPGA上のAXI GPIOレジスタ領域をメモリにマップします。
  3. レジスタ定義を作成: NumPyの dtype を使用して、メモリ領域の構造を定義します。
  4. 構造体としてアクセス: recarray を用いて、ハードウェアレジスタをフィールド名で読み書きします。

まとめ

このコードでは、Pythonを用いてメモリマップされたハードウェアレジスタにアクセスしています。mmapnp.recarray を組み合わせることで、効率的かつ可読性の高い方法でハードウェア制御が可能になります。この手法は、組み込みシステム開発やFPGAデバッグにおいて非常に有用です。


これを参考に、実際のプロジェクトで応用してみてください!

目次