この記事では、次のコードを理解するための基礎知識と、関連するハードウェア・ソフトウェアの概念を解説します。
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_dataやgpio2_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_data | 0x00 | uint32 | 0x42000000 | GPIO1のデータ |
gpio1_control | 0x04 | uint32 | 0x42000004 | GPIO1の制御 |
gpio2_data | 0x08 | uint32 | 0x42000008 | GPIO2のデータ |
gpio2_control | 0x0C | uint32 | 0x4200000C | GPIO2の制御 |
もし配列の要素数を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
- メモリアドレス
0x42000004に0xDEADBEEFを書き込む。
2. 配列全体の操作
要素数が複数の場合(例: np.recarray(10, ...))、各要素にアクセスできます。
複数要素の例
axi_numpy_array = np.recarray(10, axi_gpio_regset, buf=axi_mmap)
print(axi_numpy_array[1].gpio1_data) # 0x42000010 にアクセス
コードの動作の全体像
/dev/memをオープン: システム全体の物理メモリにアクセス可能なファイルを開きます。- メモリマップを作成:
mmapを使い、FPGA上のAXI GPIOレジスタ領域をメモリにマップします。 - レジスタ定義を作成: NumPyの
dtypeを使用して、メモリ領域の構造を定義します。 - 構造体としてアクセス:
recarrayを用いて、ハードウェアレジスタをフィールド名で読み書きします。
まとめ
このコードでは、Pythonを用いてメモリマップされたハードウェアレジスタにアクセスしています。mmap と np.recarray を組み合わせることで、効率的かつ可読性の高い方法でハードウェア制御が可能になります。この手法は、組み込みシステム開発やFPGAデバッグにおいて非常に有用です。
これを参考に、実際のプロジェクトで応用してみてください!