MPFS-DISCO-KIT 向け Ubuntu 22.04 で Lチカ(UIO 編)


## はじめに

筆者は MPFS-DISCO-KIT(Microchip PolarFire SoC FPGA Discovery Kit) で動作する Ubuntu 22.04 を独自に構築しています。この記事では、この MPFS-DISCO-KIT 向け Ubuntu 22.04 で USER LED を UIO を使って制御する方法を説明します。

### MPFS-DISCO-KIT とは

MPFS-DISCO-KIT(Microchip PolarFire SoC FPGA Discovery Kit) は、Microchip Technology 社の PolarFire SoC FPGA(MPFS095T_1FCSG325E) を搭載した開発用のキットです。

 * PolarFire SoC FPGA with 95k LEs
 * 1GByte main memory
 * 1x Gigabit Ethernet
 * 3x UART
 * 1x micro-SD interface
 * Form factor 4.1" x3.3"
 * 8x USER LED
 * 8x DIP SWITCH

詳細は次の URL を参照してください。

 * https://www.microchip.com/en-us/development-tool/mpfs-disco-kit

### MPFS-DISCO-KIT 向け Ubuntu 22.04 とは

筆者は MPFS-DISCO-KIT(Microchip PolarFire SoC FPGA Discovery Kit) で動作する Ubuntu 22.04 を独自に構築しています。以下の URL で公開しています。

 * https://github.com/ikwzm/MPFS-FPGA-Ubuntu22.04

なお、これらはオフシャルなものではなく、筆者の魔改造がはいっています。ご使用の際はこの点にご留意してください。

## 準備

### polarfire-soc-discovery-kit-reference-design 

この記事では FPGA(Fabric) にプログラムするデザインとして、polarfire-soc-discovery-kit-reference-design を利用します。これは MPFS-DISCO-KIT 用に Fabric部(FPGA部)に SRAM、I2C、GPIO、DMAなどを実装したリファレンスデザインです。この記事では USER LED の制御に Fabric部(FPGA部) にある GPIO を使います。

このリファレンスデザインのダウンロード、ビルド、MPFS-DISCO-KIT へのプログラムに関しては、以下の記事を参照してください。

 * [『MPFS-DISCO-KIT 向け Ubuntu 22.04 の構築(HSS編)』@Qiita](https://qiita.com/ikwzm/items/3d6d0a645dd60aba90d2)

### MPFS-DISCO-KIT 向け Ubuntu 22.04 の起動

SD-Card に MPFS-DISCO-KIT 向け Ubuntu 22.04 を書き込んで MPFS-DISCO-KIT を起動します。
詳細は以下の記事を参照してください。

 * [『MPFS-DISCO-KIT 向け Ubuntu 22.04 の構築(SD-Card 作成編)』@Qiita](https://qiita.com/ikwzm/items/19af3dd7a34ea5364683)
 * [『MPFS-DISCO-KIT 向け Ubuntu 22.04 の構築(SD-Card 起動編)』@Qiita](https://qiita.com/ikwzm/items/42a1aa7b355734a813f8)

### デバイスツリーの用意

MPFS-DISCO-KIT 向け Ubuntu 22.04 では、FPGA(Fabric) にプログラムした IP を制御するためのデバイスドライバは Device Tree Overlay を使って Linux 起動後に動的にロードします。polarfire-soc-discovery-kit-reference-design では、USER LED は CoreGPIO という gpio コンポーネントに接続されています。そこで、次のような DTS(Device Tree Blob Source) を用意します。この DTS は gpio コンポーネントを UIO を使って制御するためのものです。

```dts:core-gpio-uio-0.dts
/dts-v1/; /plugin/;

/ {
	fragment@0 {
		target-path = "/fabric-bus@40000000";
		#address-cells = <2>;
		#size-cells = <2>;

		__overlay__ {
			#address-cells = <2>;
			#size-cells = <2>;
			core-gpio-uio-0 {
				compatible = "generic-uio";
				reg = <0x0 0x40000100 0x0 0x0100>;
			};
		};
	};
};
```

これを dtc(Device Tree Compiler) を使って DTB(Device Tree Blob) に変換します。

```console
shell$ dtc -I dts -O dtb -o core-gpio-uio-0.dtb core-gpio-uio-0.dts
```

### デバイスツリーのオーバーレイ

core-gpio-uio-0.dtb を Device Tree に Overlay します。

```console
shell$ sudo mkdir /sys/kernel/config/device-tree/overlays/core-gpio-uio-0
shell$ sudo cp core-gpio-uio-0.dtb /sys/kernel/config/device-tree/overlays/core-gpio-uio-0/dtbo
```

これで /sys/class/uio に uioX (X は任意の数字) が見えるようなるはずです。以下の例では uio0 として見えています。

```console
shell$ ls -la /sys/class/uio/
total 0
drwxr-xr-x  2 root root 0 Nov 22  2023 .
drwxr-xr-x 48 root root 0 Nov 22  2023 ..
lrwxrwxrwx  1 root root 0 Dec 27 09:18 uio0 -> ../../devices/platform/fabric-bus@40000000/40000100.core-gpio-uio-0/uio/uio0
```

## 動作

### uio.py の紹介(2025年1月18日追記)

ここでは、UIO の制御に python と C で記述した mmapio モジュールを使います。これについては以下の記事に説明しています。

 * [『メモリマップド I/O レジスタにアクセスする Python モジュール を C で記述』@Qiita](https://qiita.com/ikwzm/items/c9d13045aac50e32ea35)
 * [『Python と mmapio モジュール で UIO を制御』@Qiita](https://qiita.com/ikwzm/items/01a3c7d58c340db57b2c)
 
上の記事で説明している uio.py は以下のようなものです。

<details><summary>mmapio/uio.py (長いので折りたたみ)</summary>

```python:mmapio/uio.py
#!/usr/bin/env python3
# SPDX-License-Identifier: BSD-2-Clause
# Copyright (c) 2025 Ichiro Kawazome

import mmap
import os
import glob
import select
import re
from .mmapio import MemoryMappedIO

class Uio:
    """A simple uio class"""

    @staticmethod
    def find_device_by_name(name):
        device_name = None
        r = re.compile("/sys/class/uio/(.*)/name")
        for uio_name_file in glob.glob("/sys/class/uio/uio*/name"):
            f = open(uio_name_file, "r")
            uio_name = f.readline().strip()
            if uio_name == name:
                m = r.match(uio_name_file)
                device_name = m.group(1)
            f.close()
        return device_name
        
    def __init__(self, name=None, device_name=None):
        if   device_name != None:
            self.device_name = device_name
            self.name        = self.read_class_attribute("name")
        elif name != None:
            self.name        = name
            self.device_name = Uio.find_device_by_name(self.name)
            if self.device_name == None:
                raise ValueError("[{}.(name={})] Invalid device_name={} ".format(self.__class__.__name__, name, self.device_name))
        self.device_file = os.open('/dev/%s' % self.device_name, os.O_RDWR | os.O_SYNC)
        self.memmap_dict = {}

    def class_attribute_file_name(self, name):
        return '/sys/class/uio/%s/%s' % (self.device_name, name)

    def read_class_attribute(self, name):
        file_name = self.class_attribute_file_name(name)
        with open(file_name, "r") as file:
            return file.readline().strip()
        return None
        
    def read_class_integer(self, name):
        file_name = self.class_attribute_file_name(name)
        with open(file_name, "r") as file:
            value = file.readline().strip()
        if   value.startswith("0x") or value.startswith("0X"):
            return int(value, 16)
        elif value.isdigit():
            return int(value, 10)
        raise ValueError("[{}.read_class_integer(name={})] Invalid value {} ".format(self.__class__.__name__, name, value))
        
    def get_map_addr(self, index=0):
        return self.read_class_integer('maps/map%d/addr'   % index)

    def get_map_size(self, index=0):
        return self.read_class_integer('maps/map%d/size'   % index)

    def get_map_offset(self, index=0):
        return self.read_class_integer('maps/map%d/offset' % index)

    def get_map_info(self, index=0):
        map_addr   = self.get_map_addr(index)
        map_size   = self.get_map_size(index)
        map_offset = self.get_map_offset(index)
        return dict(addr=map_addr, size=map_size, offset=map_offset)
        
    def irq_on(self):
        os.write(self.device_file, bytes([1, 0, 0, 0]))

    def irq_off(self):
        os.write(self.device_file, bytes([0, 0, 0, 0]))
        
    def wait_irq(self, timeout=None):
        ready, _, _ = select.select([self.device_file], [], [], timeout)
        if not ready:
            return None
        else:
            bytes = os.read(self.device_file, 4)
            count = int.from_bytes(bytes, byteorder="little", signed=True)
            return count

    def regs(self, index=0, offset=0, length=None):
        if index in self.memmap_dict.keys():
            memmap_info = self.memmap_dict[index]
            memmap      = memmap_info['memmap']
            mmap_offset = memmap_info['offset']
            mmap_size   = memmap_info['size']
            mmap_addr   = memmap_info['addr']
        else:
            page_size   = os.sysconf("SC_PAGE_SIZE")
            map_info    = self.get_map_info(index)
            mmap_addr   = map_info['addr']
            mmap_offset = ((map_info['addr'] + map_info['offset'])        ) % page_size
            mmap_size   = ((map_info['size'] + page_size - 1) // page_size) * page_size
            memmap      = mmap.mmap(self.device_file,
                                    mmap_size,
                                    mmap.MAP_SHARED,
                                    mmap.PROT_READ | mmap.PROT_WRITE,
                                    index*page_size)
            memmap_info = {'memmap': memmap, 'size': mmap_size, 'addr': mmap_addr, 'offset': mmap_offset}
            self.memmap_dict[index] = memmap_info
        regs_offset = mmap_offset + offset
        if   length == None:
            regs_length = mmap_size - regs_offset
        elif regs_offset + length <= mmap_size:
            regs_length = length
        else:
            raise ValueError("[{}.regs(index={},offset={},length={})] region range error ".format(self.__class__.__name__, index, offset, length))
        return MemoryMappedIO(memmap, regs_offset, regs_length)
```

</details>
<br>

mmapio/uio.py を使った例を示します。

```console
shell$ sudo python3
Python 3.10.12 (main, Nov  6 2024, 20:22:13) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from mmapio import Uio
>>> core_gpio_uio  = Uio('core-gpio-uio-0')
>>> core_gpio_regs = core_gpio_uio.regs()
>>> core_gpio_regs.write_byte(0xA0, 0x00)
>>> core_gpio_regs.write_byte(0xA0, 0x7F)
>>> core_gpio_regs.write_byte(0xA0, 0x01)
>>> exit()
```

アドレス 0xA0 は CoreGPIO の出力データを書き込むレジスタです。
このレジスタに 0x00 を書き込むことで全ての USER LED が消灯します。
このレジスタに 0x7F を書き込むことで全ての USER LED が点灯します。
このレジスタに 0x01 を書き込むことで USER LED1 が点灯し、残りは消灯します。

### uio.py の紹介(2025年1月18日以前)

ここでは、UIO の制御に python の numpy を使います。これについては以下の記事に説明しています。

 * [『Python と Numpy で UIO を制御』@Qiita](https://qiita.com/ikwzm/items/d7559858d099c86c350c)

上の記事で説明している uio.py は以下のようなものです。

<details><summary>uio.py (長いので折りたたみ)</summary>

```python:uio.py
#!/usr/bin/env python3
# SPDX-License-Identifier: BSD-2-Clause
# Copyright (c) 2024 ikwzm

import numpy as np
import mmap
import os
import glob
import re

class Uio:
    """A simple uio class"""

    @staticmethod
    def find_device_file(device_name):
        device_file = None
        r = re.compile("/sys/class/uio/(.*)/name")
        for uio_device_name_file in glob.glob("/sys/class/uio/uio*/name"):
            f = open(uio_device_name_file, "r")
            uio_device_name = f.readline().strip()
            if uio_device_name == device_name:
                m = r.match(uio_device_name_file)
                device_file = m.group(1)
            f.close()
        return device_file
        
    def __init__(self, name):
        self.name        = name
        self.device_name = Uio.find_device_file(self.name)
        self.device_file = os.open('/dev/%s' % self.device_name, os.O_RDWR | os.O_SYNC)
        self.memmap_dict = {}

    def sys_class_file_name(self, name):
        return '/sys/class/uio/%s/%s' % (self.device_name, name)

    def read_class_integer(self, name):
        file_name = self.sys_class_file_name(name)
        with open(file_name, "r") as file:
            value = file.readline().strip()
        if   value.startswith("0x") or value.startswith("0X"):
            return int(value, 16)
        elif value.isdigit():
            return int(value, 10)
        raise ValueError("Invalid value %s in %s " % (value, file_name))
        
    def get_map_addr(self, index=0):
        return self.read_class_integer('maps/map%d/addr'   % index)

    def get_map_size(self, index=0):
        return self.read_class_integer('maps/map%d/size'   % index)

    def get_map_offset(self, index=0):
        return self.read_class_integer('maps/map%d/offset' % index)

    def get_map_info(self, index=0):
        map_addr   = self.get_map_addr(index)
        map_size   = self.get_map_size(index)
        map_offset = self.get_map_offset(index)
        return dict(addr=map_addr, size=map_size, offset=map_offset)
        
    def irq_on(self):
        os.write(self.device_file, bytes([1, 0, 0, 0]))

    def irq_off(self):
        os.write(self.device_file, bytes([0, 0, 0, 0]))
        
    def wait_irq(self):
        os.read(self.device_file, 4)

    def regs(self, index=0, offset=0, length=None):
        if index in self.memmap_dict.keys():
            memmap_info = self.memmap_dict[index]
            memmap      = memmap_info['memmap']
            mmap_offset = memmap_info['offset']
            mmap_size   = memmap_info['size']
            mmap_addr   = memmap_info['addr']
        else:
            page_size   = os.sysconf("SC_PAGE_SIZE")
            map_info    = self.get_map_info(index)
            mmap_addr   = map_info['addr']
            mmap_offset = ((map_info['addr'] + map_info['offset'])        ) % page_size
            mmap_size   = ((map_info['size'] + page_size - 1) // page_size) * page_size
            memmap      = mmap.mmap(self.device_file,
                                    mmap_size,
                                    mmap.MAP_SHARED,
                                    mmap.PROT_READ | mmap.PROT_WRITE,
                                    index*page_size)
            memmap_info = {'memmap': memmap, 'size': mmap_size, 'addr': mmap_addr, 'offset': mmap_offset}
            self.memmap_dict[index] = memmap_info
        regs_offset = mmap_offset + offset
        if   length == None:
            regs_length = mmap_size - regs_offset
        elif regs_offset + length <= mmap_size:
            regs_length = length
        else:
            raise ValueError("region range error")
        return Uio.Regs(memmap, regs_offset, regs_length)

    class Regs:
        
        def __init__(self, memmap, offset, length):
            self.memmap = memmap
            self.offset = offset
            self.length = length
            self.alloc_word_array()
            self.alloc_byte_array()

        def alloc_word_array(self):
            self.word_array  = np.frombuffer(self.memmap, np.uint32, self.length>>2, self.offset)

        def read_word(self, offset):
            return int(self.word_array[offset>>2])

        def write_word(self, offset, data):
            self.word_array[offset>>2] = np.uint32(data)

        def alloc_half_array(self):
            self.half_array  = np.frombuffer(self.memmap, np.uint16, self.length>> 1, self.offset)

        def read_half(self, offset):
            return int(self.half_array[offset>>1])

        def write_half(self, offset, data):
            self.half_array[offset>>1] = np.uint16(data)

        def alloc_byte_array(self):
            self.byte_array  = np.frombuffer(self.memmap, np.uint8 , self.length>>0, self.offset)

        def read_byte(self, offset):
            return int(self.byte_array[offset>>0])

        def write_byte(self, offset, data):
            self.byte_array[offset>>0] = np.uint8(data)

```

</details>
<br>

uio.py を使った例を示します。

```console
shell$ sudo python3
Python 3.10.12 (main, Nov  6 2024, 20:22:13) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from uio import Uio
>>> core_gpio_uio  = Uio('core-gpio-uio-0')
>>> core_gpio_regs = core_gpio_uio.regs()
>>> core_gpio_regs.write_byte(0xA0, 0x00)
>>> core_gpio_regs.write_byte(0xA0, 0x7F)
>>> core_gpio_regs.write_byte(0xA0, 0x01)
>>> exit()
```

アドレス 0xA0 は CoreGPIO の出力データを書き込むレジスタです。
このレジスタに 0x00 を書き込むことで全ての USER LED が消灯します。
このレジスタに 0x7F を書き込むことで全ての USER LED が点灯します。
このレジスタに 0x01 を書き込むことで USER LED1 が点灯し、残りは消灯します。

### sample.py

uio.py を使ったサンプルとして次のような sample.py を用意します。

```python:sample.py
#!/usr/bin/env python3
# SPDX-License-Identifier: BSD-2-Clause
# Copyright (c) 2024 ikwzm

from mmapio import Uio
import time

if __name__ == '__main__':
    core_gpio_uio  = Uio('core-gpio-uio-0')
    core_gpio_regs = core_gpio_uio.regs()
    led_pattern_1  = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02]
    led_pattern_2  = [0x41, 0x22, 0x18, 0x22]

    for i in range(60):
        data = led_pattern_1[i % len(led_pattern_1)]
        core_gpio_regs.write_byte(0xA0, data)
        time.sleep(0.1)
    for i in range(60):
        data = led_pattern_2[i % len(led_pattern_2)]
        core_gpio_regs.write_byte(0xA0, data)
        time.sleep(0.1)
    core_gpio_regs.write_byte(0xA0, 0x7F)
        
```

この sample.py を走らせると、USER LED がイルミネーションのように点灯します。

```console
shell$ sudo python3 sample.py
```

## 参考

### MPFS-FPGA-Example-1-DISCO-KIT

ここで紹介した DTS(Device Tree Blob Source) と python スクリプトは以下の URL で公開しています。

 * https://github.com/ikwzm/MPFS-FPGA-Example-1-DISCO-KIT
   + python/
     - [uio.py](https://github.com/ikwzm/MPFS-FPGA-Example-1-DISCO-KIT/blob/main/python/uio.py)  (2025年1月18日以前)
     - mmapio/ (2025年1月18日以降)
       + [uio.py](https://github.com/ikwzm/MPFS-FPGA-Example-1-DISCO-KIT/blob/main/python/mmapio/uio.py)
       + [mmapio.py](https://github.com/ikwzm/MPFS-FPGA-Example-1-DISCO-KIT/blob/main/python/mmapio/mmapio.py)
       + [mmapio.c](https://github.com/ikwzm/MPFS-FPGA-Example-1-DISCO-KIT/blob/main/python/mmapio/mmapio.c)
   + examples/core-gpio-uio/
     - [sample.py](https://github.com/ikwzm/MPFS-FPGA-Example-1-DISCO-KIT/blob/main/examples/core-gpio-uio/sample.py)
     - [core-gpio-uio-0.dts](https://github.com/ikwzm/MPFS-FPGA-Example-1-DISCO-KIT/blob/main/examples/core-gpio-uio/core-gpio-uio-0.dts)

### MPFS-DISCO-KIT 向け Ubuntu 22.04 に関する記事

 * [『MPFS-DISCO-KIT 向け Ubuntu 22.04 の構築(イントロ編)』@Qiita](https://qiita.com/ikwzm/items/1866dc3bb5292fd1f6e1)
 * [『MPFS-DISCO-KIT 向け Ubuntu 22.04 の構築(HSS編)』@Qiita](https://qiita.com/ikwzm/items/3d6d0a645dd60aba90d2)
 * [『MPFS-DISCO-KIT 向け Ubuntu 22.04 の構築(SD-Card 作成編)』@Qiita](https://qiita.com/ikwzm/items/19af3dd7a34ea5364683)
 * [『MPFS-DISCO-KIT 向け Ubuntu 22.04 の構築(SD-Card 起動編)』@Qiita](https://qiita.com/ikwzm/items/42a1aa7b355734a813f8)
 * https://github.com/ikwzm/MPFS-FPGA-Ubuntu22.04

### MPFS-DISCO-KIT 関連

 * https://www.microchip.com/en-us/development-tool/mpfs-disco-kit

### CoreGPIO 関連

 * https://www.microchip.com/en-us/products/fpgas-and-plds/ip-core-tools/coregpio
 * https://ww1.microchip.com/downloads/aemdocuments/documents/fpga/ProductDocuments/SupportingCollateral/CoreGPIO_HB.pdf

### UIO 関連

 * [『Python と mmapio モジュール で UIO を制御』@Qiita](https://qiita.com/ikwzm/items/01a3c7d58c340db57b2c)
 * [『Python と Numpy で UIO を制御』@Qiita](https://qiita.com/ikwzm/items/d7559858d099c86c350c)

### その他

 * [『FPGA+SoC+LinuxでDevice Tree Overlayを試してみた』@Qiita](https://qiita.com/ikwzm/items/ec514e955c16076327ce)
 * [『MPFS-DISCO-KIT 向け Ubuntu 22.04 で Lチカ』@Qiita](https://qiita.com/ikwzm/items/708945c06db1605a1596)