PythonでFPGAを扱える?Ultra96-V2でPYNQを動かしてみた(2) ~ オーバーレイ作成編

前回、Ultra96-V2上でPYNQ環境を使える状態となりましたが、用意されているオーバーレイ(FPGAデザイン)はボード上のLED点灯制御もできませんでした。 今回はボード上のLEDの点灯制御を行うオーバーレイを作ってみたいと思います。

主にXilinxのハードウェア設計ツールVivado上での作業となります。 使用したバージョンは2020.02となります。

執筆者 : 原田 秀一


Ultra96-V2のLED

Ultra96-V2ボード上にはいくつかのLEDがあり、以下のマニュアルの 5.9 LEDs に記載されています。

Ultra96-V2 Hardware User's Guide

LEDには電源状態やコンフィグレーション完了を示すなど固定の役割のものもありますが、User-controllableの記載があるものは任意に制御可能です。

f:id:harada-va:20210903140848p:plain
マニュアルからLED部分抜粋

LEDの接続先はPS_MIO[17..20]とPL Pin B9, PL Pin A9と記載されています。このPS, PLの部分は接続先のZynq内部ブロックが異なることを表しています。

PSとPL

ZynqではArmコアを含むプロセッサ周辺ブロックをProcessing System(PS)、FGPAブロックをProgrammable Logic(PL)と呼んでいます。 上記LEDのPS_MIOはProcessing System(Armコア部分)のMIO(Multiplexed I/O)への接続、PLはProgrammable Logic(FPGA部分)への接続を意味します。

PS_MIOに接続されたLEDはFPGAを介さずにCPUからGPIOなどのペリフェラルから制御可能、PLに接続されたLEDはFPGAに実装した回路から制御可能となります。 また、PLはPSから制御可能なので、FPGA上にGPIO回路を実装し、CPUからそのGPIOを制御できます。

今回は、このFPGA上にGPIOを追加し、PLに接続されているLEDを制御するPYNQオーバーレイを作成します。

Vivadoでの開発作業

ここからはハードウェア開発環境Vivadoを使ってFPGAの設計を行いビットストリームを作成します。 以下の流れで作業します。

  • プロジェクト作成

    対象ボード(FPGAデバイス)の指定等を行います。

  • 制約ファイル設定

    主にFPGAのピンアサインを行います。

  • IPインテグレーターでの作業

    IP (Intellectual Property, FPGAでは回路のライブラリのようなもの) を配置、設定、接続し回路設計を行います。

  • HDLラッパー作成

    IPインテグレーターで作成した回路を使うために必要なラッパーファイルを作成します。

  • ビットストリーム作成

    設計した回路をFPGAに書き込むためのビットストリームを作成します。

プロジェクト作成

Quick StartからCreate Projectを選択します。

f:id:harada-va:20210906082230p:plain
Create Projectを選択

f:id:harada-va:20210906082404p:plain
プロジェクト作成開始

今回はproject名をled, ディレクトリはhome以下のultra96v2を指定しました。

f:id:harada-va:20210906082439p:plain
プロジェクト名、保存先設定

project typeはRTLを選択します。

f:id:harada-va:20210906082806p:plain
プロジェクトタイプ

Default Part画面ではPartsタブからBoardsタブに切り替えて一覧からUltra96-V2を選択します。

f:id:harada-va:20210906083017p:plain
Partsタブ(デフォルト表示)

f:id:harada-va:20210906083106p:plain
Boardsタブ

  • ボードファイルのインストール

    一覧に対象のボードがない場合、ボードファイルのインストールが必要です。 初期状態ではUltra96-V2のボードファイルは含まれていないため、ダイアログ右上のInstall/Update Boardsを選択し、XHub Storesの画面からボードを選択して右クリックし、installを実行します。

    f:id:harada-va:20210906101517p:plain
    XHub Stores

選択したボードが反映されているか確認してFinishを押して終了します。

私の環境ではバグなのか選択したボードの内容が反映されていないことがあり、Vivadoを再起動して作業しなおして反映されました。

f:id:harada-va:20210906103304p:plain
プロジェクト作成終了

制約ファイル設定

制約ファイル(拡張子xdc)は、入出力ポートの端子へのアサイン、端子の信号レベル設定、タイミングに関する制約等を設定するファイルです。 記述内容はTcl形式となります。

今回はAvnet提供の制約ファイルを使用します。

Ultra96-V2 | Avnet Boards のTechnical Documents中のConstraints Ultra96-V2 Constraints Rev 1 です。

ダウンロードしたファイルを解凍してUltra96_V2_constraints_190430.xdcを展開しておきます。

プロジェクトに制約ファイルを追加します。

Flow Navigator - PROJECT MANAGER - Add Sourcesを選択します。

Add or create constraintsを選択します。

f:id:harada-va:20210906113323p:plain
制約ファイルの追加(1)

Ultra96-V2用の制約ファイルを追加します。

f:id:harada-va:20210906113700p:plain
制約ファイルの追加(2)
f:id:harada-va:20210906113743p:plain
制約ファイルの追加(3)
f:id:harada-va:20210906113807p:plain
制約ファイルの追加(4)

追加した制約ファイルを開くとA9= RADIO_LED0, B9 = RADIO_LED1 として定義されていることが確認できます。

f:id:harada-va:20210906114107p:plain
制約ファイルの確認

制約ファイル抜粋

# ----------------------------------------------------------------------------
# Bluetooth
# ---------------------------------------------------------------------------- 
set_property PACKAGE_PIN B5   [get_ports {BT_HCI_CTS             }];  # "B5.BT_HCI_CTS"
set_property PACKAGE_PIN B7   [get_ports {BT_HCI_RTS             }];  # "B7.BT_HCI_RTS"
set_property PACKAGE_PIN A9   [get_ports {RADIO_LED0             }];  # "A9.RADIO_LED0"
set_property PACKAGE_PIN B9   [get_ports {RADIO_LED1             }];  # "B9.RADIO_LED1"

IPインテグレーターでの作業

IPインテグレーターでは回路図CADのようにIPを配置してポート間の信号接続を行うことができます。 今回はZynq PSとGPIOのIPを配置、接続しGPIO出力をボード上のLEDに接続します。

Flow Navigator - IP INTEGRATOR - Create Block Designを選択します。

デザイン名はデフォルトのままとします。

f:id:harada-va:20210906141528p:plain
Design name

空のDiagramが開きます。

f:id:harada-va:20210906141644p:plain
空のDiagram

Diagramウィンドウの中央、または上部の + ボタンを押してIPを追加します。 まずZynq UltraScale+ MPSoCを追加します。

f:id:harada-va:20210906142213p:plain
Zynq UltraScale+ MPSoCを選択

追加後、上部に表示されるRun Block Automationを実行します。

f:id:harada-va:20210906142458p:plain
Zynq UltraScale+ MPSoC追加後

デフォルトのままでOK

f:id:harada-va:20210906154441p:plain
Run Block Automation

f:id:harada-va:20210906154734p:plain
Run Block Automation後

次にGPIOを追加します。 画面上部 + ボタンを押して AXI GPIOを追加します。

f:id:harada-va:20210906160247p:plain
AXI GPIO追加

f:id:harada-va:20210906160313p:plain
AXI GPIO追加後

追加されたaxi_gpio_0をダブルクリックしてIPの設定を開きます。 ここではLEDへの信号は2本だけなのでWidthを2, All outputに変更します。

f:id:harada-va:20210906160430p:plain
GPIO IP設定

f:id:harada-va:20210906160816p:plain
GPIO IP設定変更

Run Connection Automationで接続します。

f:id:harada-va:20210906161958p:plain
GPIO Run Connection Automation

f:id:harada-va:20210906184407p:plain
GPIO 接続後

まだZynq IP部のAXI HPM1 FPDは未接続状態になっているため、Run Connection Automationの表示があります。 このインタフェースは使用しないのでZynq IP設定のPS-PL間のインタフェース設定からチェックを外して消しておきます。

f:id:harada-va:20210906162232p:plain
AXI HPM1 FPD削除
f:id:harada-va:20210906162312p:plain
AXI HPM1 FPD削除2

GPIOからの信号をLEDに接続します。

GPIOからは2bit幅の出力となっていますが、1bitずつの信号として扱えるようにするためSlice IPを2つ追加します(LED2つ分の1bit x 2)。

f:id:harada-va:20210906185523p:plain
Slice IP追加

f:id:harada-va:20210906185636p:plain
Slice IP 追加後

Slice IPの設定でDin Widthを2、Dout Widthを1に設定します。 Din From、Din Down Toは、2つのIPそれぞれ0,0と1, 1に設定します(どのビット位置を出力するかの設定)。

f:id:harada-va:20210906190258p:plain
xlslice_0 設定

f:id:harada-va:20210906190326p:plain
xlslice_1 設定

axi_gpio_0のGPIO + の部分を押してインタフェースを展開し、表示されたgpio_io_o[1:0]からSlice IPのDin[1:0]に接続します。

f:id:harada-va:20210906193237p:plain
GPIO - Slice接続

2つのSlice IPのDoutから右クリックメニューのMakeExternalで外部ポートを作成します。

f:id:harada-va:20210907070447p:plain
外部ポート作成

f:id:harada-va:20210907070901p:plain
外部ポート作成後

作成された外部ポートを選択してプロパティから名前を変更し、制約ファイルに定義されているポート名と一致させます。 ここではDout_0をRADIO_LED0, Dout_1をRADIO_LED1に変更します。

f:id:harada-va:20210907071330p:plain
外部ポート名変更

Diagram画面の右クリックメニューからRegenerate Layoutで整理し、以下のような全体図となります。

f:id:harada-va:20210907072947p:plain
ポート名変更後

Validate Designで問題がないかチェックしておきます。

f:id:harada-va:20210907074258p:plain
Validate Design

f:id:harada-va:20210907074329p:plain
正常終了

制約ファイルを変更してLEDに接続する例

上記では制約ファイルの定義に合わせてボードデザインを作成してLEDに接続しましたが、制約ファイルの定義を変更しボードデザインに合わせて接続することもできます。 これはGPIOからの出力を外部ポート名LEDとして定義した場合、以下のように制約ファイルを変更することで可能です。 この場合、Slice IPを使う必要がなくなり、手間が減ります。

f:id:harada-va:20211007090138p:plain
外部ポート名をLEDとした例

#set_property PACKAGE_PIN A9   [get_ports {RADIO_LED0             }];  # "A9.RADIO_LED0"
#set_property PACKAGE_PIN B9   [get_ports {RADIO_LED1             }];  # "B9.RADIO_LED1"
set_property PACKAGE_PIN A9   [get_ports {LED[0]             }];  # "A9.RADIO_LED0"
set_property PACKAGE_PIN B9   [get_ports {LED[1]             }];  # "B9.RADIO_LED1"

HDLラッパー作成

ボードデザイン(拡張子bd)ができましたがこれを使うためにはHDLラッパーの作成という手順が必要です。 Sourcesからdesign_1を選択し、右クリックメニューからCreate HDL Wrapperを実行します。

f:id:harada-va:20210907082234p:plain
HDLラッパー作成

f:id:harada-va:20210907082315p:plain
Create HDL Wrapperダイアログ

design_1_wrapper.vが生成されます。

f:id:harada-va:20210907082514p:plain
HDLラッパー作成完了

ビットストリーム作成

PROGRAM AND DEBUG から Generate Bitstreamを実行し、ビットストリーム(拡張子bit)を作成します。

f:id:harada-va:20210907085141p:plain
Generate Bitstream

ここまで論理合成、配置配線を行っていないので確認ダイアログが表示されます。 Yesを押して進めればこのタイミングで実行されます。

f:id:harada-va:20210907085232p:plain
No Implementation Results Available

Launch RunsもデフォルトのままOKでビットストリーム作成が開始されます。

f:id:harada-va:20210907085740p:plain
Launch Runs

エラーがなければしばらくしてビットストリームができあがります。 このとき配置配線の結果を開くなどの選択が表示されますが、ここではキャンセルで終了します。

f:id:harada-va:20210907090037p:plain
ビットストリーム作成完了

Vivadoでの作業はここで終了です。

PYNQ上で動作確認

Ultra96-V2のPYNQ環境でできあがったオーバーレイ(FPGAデザイン)を動かしてみます。

作業用にLEDディレクトリを作成しておきます。

f:id:harada-va:20210908114250p:plain
作業用ディレクトリ

ファイル転送

UploadからPYNQ上にファイルを転送します。

対象はVivadoプロジェクトディレクトリled内の以下のファイルです。 led/led.gen/sources_1/bd/design_1/hw_handoff/design_1.hwh led/led.gen/sources_1/bd/design_1/hw_handoff/design_1_bd.tcl led/led.runs/impl_1/design_1_wrapper.bit

f:id:harada-va:20210908114005p:plain
upload
転送したらリネームしてベースネームを統一しておきます。 ここではdesign_1.*とします。

f:id:harada-va:20210908114106p:plain
リネーム後

テスト実行

NewからPython3を選択してノートを作成します。 (以下はLED Testにリネームしています)

セルに以下のPythonコードを記述します。

from pynq import Overlay
import time

ol = Overlay('design_1.bit')

for i in range(10) :
    ol.axi_gpio_0.write(0,(i & 3))
    time.sleep(1)

f:id:harada-va:20210908113130p:plain
セルにコードを入力した状態

このセルを実行すると2個のLEDが点滅します。

PYNQからのFPGA制御詳細

from pynq import Overlay

ol = Overlay('design_1.bit')

ビットストリームを指定してOverlayクラスのインスタンスを作成します。

メモリマップされているIPを以下のようにして表示できます。

 for key in ol.ip_dict :
    print(key)

実行結果

axi_gpio_0
zynq_ultra_ps_e_0

これらの識別名は、Vivadoで作成したブロックデザイン中の各IPインスタンスの名前に対応します。

f:id:harada-va:20210907140125p:plain
IPの識別名

helpを使って確認するとread(), write()といったメソッドを持つことがわかります。

help(ol.axi_gpio_0)

help実行結果(クリックで展開)

Help on AxiGPIO in module pynq.lib.axigpio object:

class AxiGPIO(pynq.overlay.DefaultIP)
 |  Class for interacting with the AXI GPIO IP block.
 |  
 |  This class exposes the two banks of GPIO as the `channel1` and
 |  `channel2` attributes. Each channel can have the direction and
 |  the number of wires specified.
 |  
 |  The wires in the channel can be accessed from the channel using
 |  slice notation - all slices must have a stride of 1. Input wires
 |  can be `read` and output wires can be written to, toggled, or
 |  turned off or on. InOut channels combine the functionality of
 |  input and output channels. The tristate of the pin is determined
 |  by whether the pin was last read or written.
 |  
 |  Method resolution order:
 |      AxiGPIO
 |      pynq.overlay.DefaultIP
 |      builtins.object
 |  
 |  Methods defined here:
 |  
 |  __getitem__(self, idx)
 |  
 |  __init__(self, description)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  setdirection(self, direction, channel=1)
 |      Sets the direction of a channel in the controller
 |      
 |      Must be one of AxiGPIO.{Input, Output, InOut} or the string
 |      'in', 'out' or 'inout'
 |  
 |  setlength(self, length, channel=1)
 |      Sets the length of a channel in the controller
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  Channel = <class 'pynq.lib.axigpio.AxiGPIO.Channel'>
 |      Class representing a single channel of the GPIO controller.
 |      
 |      Wires are and bundles of wires can be accessed using array notation
 |      with the methods on the wires determined by the type of the channel::
 |      
 |          input_channel[0].read()
 |          output_channel[1:3].on()
 |      
 |      This class instantiated not used directly, instead accessed through
 |      the `AxiGPIO` classes attributes. This class exposes the wires
 |      connected to the channel as an array or elements. Slices of the
 |      array can be assigned simultaneously.
 |  
 |  InOut = <class 'pynq.lib.axigpio.AxiGPIO.InOut'>
 |      Class representing wires in an inout channel.
 |      
 |      This class should be passed to `setdirection` to indicate the
 |      channel should be used for both input and output. It should not
 |      be used directly.
 |  
 |  Input = <class 'pynq.lib.axigpio.AxiGPIO.Input'>
 |      Class representing wires in an input channel.
 |      
 |      This class should be passed to `setdirection` to indicate the
 |      channel should be used for input only. It should not be used
 |      directly.
 |  
 |  Output = <class 'pynq.lib.axigpio.AxiGPIO.Output'>
 |      Class representing wires in an output channel.
 |      
 |      This class should be passed to `setdirection` to indicate the
 |      channel should be used for output only. It should not be used
 |      directly.
 |  
 |  bindto = ['xilinx.com:ip:axi_gpio:2.0']
 |  
 |  ----------------------------------------------------------------------
 |  Methods inherited from pynq.overlay.DefaultIP:
 |  
 |  call(self, *args, **kwargs)
 |  
 |  read(self, offset=0)
 |      Read from the MMIO device
 |      
 |      Parameters
 |      ----------
 |      offset : int
 |          Address to read
 |  
 |  start(self, *args, **kwargs)
 |      Start the accelerator
 |      
 |      This function will configure the accelerator with the provided
 |      arguments and start the accelerator. Use the `wait` function to
 |      determine when execution has finished. Note that buffers should be
 |      flushed prior to starting the accelerator and any result buffers
 |      will need to be invalidated afterwards.
 |      
 |      For details on the function's signature use the `signature` property.
 |      The type annotations provide the C types that the accelerator
 |      operates on. Any pointer types should be passed as `ContiguousArray`
 |      objects created from the `pynq.Xlnk` class. Scalars should be passed
 |      as a compatible python type as used by the `struct` library.
 |  
 |  start_ert(self, *args, waitfor=(), **kwargs)
 |      Start the accelerator using the ERT scheduler
 |      
 |      This function will use the embedded scheduler to call the accelerator
 |      with the provided arguments - see the documentation for ``start`` for
 |      more details. An optional ``waitfor`` parameter can be used to
 |      schedule dependent executions without using the CPU.
 |      
 |      Parameters
 |      ----------
 |      waitfor : [WaitHandle]
 |          A list of wait handles returned by other calls to ``start_ert``
 |          which must complete before this execution starts
 |      
 |      Returns
 |      -------
 |      WaitHandle :
 |          Object with a ``wait`` call that will return when the execution
 |          completes
 |  
 |  start_none(self, *args, **kwargs)
 |  
 |  start_sw(self, *args, ap_ctrl=1, waitfor=None, **kwargs)
 |      Start the accelerator
 |      
 |      This function will configure the accelerator with the provided
 |      arguments and start the accelerator. Use the `wait` function to
 |      determine when execution has finished. Note that buffers should be
 |      flushed prior to starting the accelerator and any result buffers
 |      will need to be invalidated afterwards.
 |      
 |      For details on the function's signature use the `signature` property.
 |      The type annotations provide the C types that the accelerator
 |      operates on. Any pointer types should be passed as `ContiguousArray`
 |      objects created from the `pynq.Xlnk` class. Scalars should be passed
 |      as a compatible python type as used by the `struct` library.
 |  
 |  write(self, offset, value)
 |      Write to the MMIO device
 |      
 |      Parameters
 |      ----------
 |      offset : int
 |          Address to write to
 |      value : int or bytes
 |          Data to write
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from pynq.overlay.DefaultIP:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  register_map
 |  
 |  signature
 |      The signature of the `call` method

上記のLED点滅プログラムでは、writeメソッドを使用していますが、この時引数となるアドレスオフセットはIPごとに定義されているレジスタのマッピングに従います。

AXI-GPIOの仕様書(pdf) 10/34pageのRegister Spaceから抜粋

f:id:harada-va:20210907143712p:plain
AXI GPIO - Register SpaceRegister Space

今回の設計ではGPIOは1チャネル、出力ポートのみの設定にしてあり、GPIO_DATAレジスタ値が出力ポートに反映されます。 GPIO_DATAのアドレスオフセットは0なので、これを引数としてwriteメソッドでレジスタ値を設定してLEDの点灯状態を変化させています。

レジスタアクセスの別の方法としてレジスタ名称を使用してアクセスすることもできます。

register_mapでレジスタ名称とレジスタの状態を参照できます。

print( ol.axi_gpio_0.register_map )

実行結果

RegisterMap {
  GPIO_DATA = Register(Channel_1_GPIO_DATA=1),
  GPIO_TRI = Register(Channel_1_GPIO_TRI=3),
  GPIO2_DATA = Register(Channel_2_GPIO_DATA=0),
  GPIO2_TRI = Register(Channel_2_GPIO_TRI=4294967295),
  GIER = Register(Global_Interrupt_Enable=1),
  IP_ISR = Register(Channel_1_Interrupt_Status=1, Channel_2_Interrupt_Status=0),
  IP_IER = Register(Channel_1_Interrupt_Enable=0, Channel_2_Interrupt_Enable=0)
}

これを使って以下のようにGPIO_DATAレジスタに値を設定できます。

ol.axi_gpio_0.register_map.GPIO_DATA = 3

PYNQではこれらレジスタなどIPの構成情報をビットストリームに対応するhwhファイルから取得してRegisterMapなどを生成するようです。

hwhの抜粋

  <MODULES>
    <MODULE COREREVISION="24" FULLNAME="/axi_gpio_0" HWVERSION="2.0" INSTANCE="axi_gpio_0" IPTYPE="PERIPHERAL" IS_ENABLE="1" MODCLASS="PERIPHERAL" MODTYPE="axi_gpio" VLNV="xilinx.com:ip:axi_gpio:2.0">
      <DOCUMENTS>
        <DOCUMENT SOURCE="http://www.xilinx.com/cgi-bin/docs/ipdoc?c=axi_gpio;v=v2_0;d=pg144-axi-gpio.pdf"/>
      </DOCUMENTS>
      <ADDRESSBLOCKS>
        <ADDRESSBLOCK ACCESS="read-write" INTERFACE="S_AXI" NAME="Reg" RANGE="4096" USAGE="register">
          <REGISTERS>
            <REGISTER NAME="GPIO_DATA">
              <PROPERTY NAME="DESCRIPTION" VALUE="Channel-1 AXI GPIO Data register"/>
              <PROPERTY NAME="ADDRESS_OFFSET" VALUE="0x0"/>
              <PROPERTY NAME="SIZE" VALUE="2"/>
              <PROPERTY NAME="ACCESS" VALUE="read-write"/>
              <PROPERTY NAME="IS_ENABLED" VALUE="true"/>
              <PROPERTY NAME="RESET_VALUE" VALUE="0x0"/>
              <FIELDS>
                <FIELD NAME="Channel_1_GPIO_DATA">
                  <PROPERTY NAME="DESCRIPTION" VALUE="AXI GPIO Data Register.&#xA;For each I/O bit programmed as input&#xA;  R - Reads value on the input pin.&#xA;  W - No effect.&#xA;For each I/O bit programmed as output&#xA;  R - Reads value on GPIO_O pins&#xA;  W - Writes value to the corresponding AXI GPIO &#xA;      data register bit and output pin&#xA;"/>
                  <PROPERTY NAME="ADDRESS_OFFSET" VALUE="0"/>
                  <PROPERTY NAME="ACCESS" VALUE="read-write"/>
                  <PROPERTY NAME="MODIFIED_READ_VALUES" VALUE=""/>
                  <PROPERTY NAME="WRITE_CONSTRAINT" VALUE="0"/>
                  <PROPERTY NAME="READ_ACTION" VALUE=""/>
                  <PROPERTY NAME="BIT_OFFSET" VALUE="0"/>
                  <PROPERTY NAME="BIT_WIDTH" VALUE="2"/>
                </FIELD>
              </FIELDS>
            </REGISTER>

まとめ

PYNQオーバーレイ作成として

  1. FPGA設計し、ビットストリームを作る
  2. PYNQ環境に1で生成したファイル群を配置し、Pythonで操作する

をやってみました。

今回FPGA設計部分はIPを並べて接続することしかやっていませんが一般的なハードウェアとしてのFPGA設計フローに沿ったものだろうと思います。 実用的な機能を実装するにはHDL記述や、C/C++記述と高位合成などを用いて機能ブロックの実装を行い、PSブロックとのインタフェースを設計する等のより細かなハードウェア設計が必要となりそうです。

オーバーレイの作成についてPYNQのドキュメントでは以下のように記載されています。

Project Goalsより抜粋

Creating a new overlay still requires engineers with expertise in designing programmable logic circuits.

Overlay Design Methodologyより抜粋

A software programmer can use an overlay, but will not usually create overlay, as this usually requires a high degree of hardware design expertise.

オーバーレイ開発はハードウェア設計の領域で通常はソフトウェア開発者が設計するものではないようですね。

このようにPYNQ環境はソフトウェア技術者だけでFPGAを使用するシステム全体の開発を行うことは難しそうですが、従来のシステム開発でFPGAなどのハードウェアをC言語などで開発したファームウェアで制御していた部分をPythonで行えればソフトウェア技術者も含め様々な技術者が開発しやすくなるのではないでしょうか。