はじめに
AMDがFPGA用に提供するRISC-VコアのMicroBlaze V上でZephyr RTOSを動作させる手順について記述します。
ターゲットボードはDigilent社のArty A7-100Tです。Zephyr RTOSを動作させる手順はZephyr Getting Started Guideに記載されている通りです。
ZephyrはArty A7に実装されているDDR Memory上で動作させます。DDR Memoryを利用したBlock Designの作成には少しクセがあり、そこの作業手順が主な内容になります。
参考記事
今回の作業ではMIG(Memory Interface Generator)の使い方について以下の記事を参考にさせて頂きました。
Arty A7 100TボードとMicroBlazeとMIGでHello Worldと時間計測
Tutorial: MicroBlaze with DDR3 SDRAM
システム概要
以下は実装するBlock Designです。
周辺デバイスはInterrupt Controller, Timer, UARTの3つとなっています。
開発環境
Board
Arty A7-100T
Tools
Vivado 2025.1
Vitis 2025.1
Zephyr RTOS
Base Version - 3.7.0
Xilinx Repository Tag - 2025.1
Host OS
Ubuntu 24.04 Desktop
今回の作業でVitisは使いませんが、System Device Treeを生成する際にVitisに含まれるツールやデータを利用する為、Vitisのインストールが必要です。
構成
Zephyr RTOS
Zephyr RTOSはZephyr ProjectがLTS版としてリリースしている3.7.0にAMDが提供するソースを組み込んだ環境でビルドします。
Vivado作業手順
プロジェクト作成
最初にTCLスクリプトと制約ファイルを使い、プロジェクトの作成を行います。arty-a7-mbv32.tcl
の最初に定義しているパラメータは環境に合わせて変更して下さい。
TCLスクリプトで作成されたプロジェクトは配置配線でエラーになります。プロジェクトを作成した後は、後に記述するGUI操作を実行してMIGの設定を変更する必要があります。
arty-a7-mbv32.tcl - プロジェクト作成
# parameters
set PROJ_NAME arty-a7-mbv32
set PROJ_DIR $env(HOME)/ws/vivado/$PROJ_NAME
set SCRIPT_DIR [file dirname [info script]]
set XDC_FILE $SCRIPT_DIR/$PROJ_NAME.xdc
set BD_FILE $SCRIPT_DIR/${PROJ_NAME}-bd.tcl
# import the local functions
source $SCRIPT_DIR/local-funcs.tcl
# create the project
if {! [local::is_installed digilentinc.com:arty-a7-100:part0:1.1]} {
xhub::refresh_catalog [xhub::get_xstores xilinx_board_store]
xhub::install [xhub::get_xitems digilentinc.com:xilinx_board_store:arty-a7-100:1.1]
}
create_project $PROJ_NAME $PROJ_DIR -part xc7a100tcsg324-1
set_property board_part digilentinc.com:arty-a7-100:part0:1.1 [current_project]
# create the block design
create_bd_design design_1
source $SCRIPT_DIR/$BD_FILE
save_bd_design
# create the wrapper file
make_wrapper -files [get_files $PROJ_DIR/$PROJ_NAME.srcs/sources_1/bd/design_1/design_1.bd] -top
add_files -norecurse $PROJ_DIR/$PROJ_NAME.gen/sources_1/bd/design_1/hdl/design_1_wrapper.v
# add the constraint file
add_files -fileset constrs_1 -norecurse $XDC_FILE
import_files -fileset constrs_1 $XDC_FILE
update_compile_order -fileset sources_1
arty-a7-mbv32-bd.tcl - Block Design作成
# MIG
create_bd_cell -type ip -vlnv xilinx.com:ip:mig_7series:4.2 mig_7series_0
apply_board_connection -board_interface "ddr3_sdram" -ip_intf "mig_7series_0/mig_ddr_interface" -diagram "design_1"
delete_bd_objs [get_bd_nets clk_ref_i_1] [get_bd_ports clk_ref_i]
delete_bd_objs [get_bd_nets sys_clk_i_1] [get_bd_ports sys_clk_i]
local::connect_pins mig_7series_0/ui_addn_clk_0 mig_7series_0/clk_ref_i
local::make_pin_external mig_7series_0/sys_rst sys_resetn
# MIG Clock and Buffer
local::create_xip_cell util_ds_buf:2.2 sys_clock_bufg {
CONFIG.C_BUF_TYPE BUFG
}
local::make_pin_external sys_clock_bufg/BUFG_I sys_clock
local::connect_pins sys_clock_bufg/BUFG_O mig_7series_0/sys_clk_i
# MIG Reset
local::create_xip proc_sys_reset:5.0 mig_reset
local::connect_pins mig_reset mig_7series_0 {
{slowest_sync_clk ui_clk}
{dcm_locked mmcm_locked}
{peripheral_aresetn aresetn}
}
local::create_inline_hdl ilvector_logic:1.0 inv_ui_reset {
CONFIG.C_OPERATION not
CONFIG.C_SIZE 1
}
local::connect_pins mig_7series_0/ui_clk_sync_rst inv_ui_reset/Op1
local::connect_pins mig_reset/ext_reset_in inv_ui_reset/Res
# Memory Interconnect
local::create_xip axi_interconnect:2.1 mem_interconnect {
CONFIG.NUM_MI 1
CONFIG.NUM_SI 2
}
local::connect_ifs mem_interconnect/M00_AXI mig_7series_0/S_AXI
local::connect_pins mem_interconnect/M00_ACLK mig_7series_0/ui_clk
local::connect_pins mem_interconnect/M00_ARESETN mig_reset/peripheral_aresetn
# System Reset and Clock
local::create_xip proc_sys_reset:5.0 system_reset
local::connect_bd_port sys_resetn system_reset/ext_reset_in
local::create_xip clk_wiz:6.0 system_clock {
CONFIG.USE_RESET false
}
local::connect_pins system_clock/clk_in1 sys_clock_bufg/BUFG_O
local::connect_pins system_clock/clk_out1 system_reset/slowest_sync_clk
local::connect_pins system_clock/locked system_reset/dcm_locked
# MicroBlaze V
set mbv [local::create_xip microblaze_riscv:1.0 microblaze_riscv_0]
local::connect_pins microblaze_riscv_0/Clk system_clock/clk_out1
local::connect_pins microblaze_riscv_0/Reset system_reset/mb_reset
apply_bd_automation -rule xilinx.com:bd_rule:microblaze_riscv -config {
axi_intc {1}
axi_periph {Enabled}
debug_module {Debug Enabled}
ecc {None}
local_mem {16KB}
preset {Real-time}
} $mbv
local::connect_ifs microblaze_riscv_0/M_AXI_DC mem_interconnect/S00_AXI
local::connect_ifs microblaze_riscv_0/M_AXI_IC mem_interconnect/S01_AXI
local::connect_pins mem_interconnect system_clock {
{ACLK clk_out1}
{S00_ACLK clk_out1}
{S01_ACLK clk_out1}
}
local::connect_pins mem_interconnect system_reset {
{ARESETN peripheral_aresetn}
{S00_ARESETN peripheral_aresetn}
{S01_ARESETN peripheral_aresetn}
}
# Configure the AXI Interrupt Controller
set_property CONFIG.C_HAS_FAST {0} [get_bd_cells microblaze_riscv_0_axi_intc]
# Configure the AXI Interconnect
set_property CONFIG.NUM_MI {3} [get_bd_cells microblaze_riscv_0_axi_periph]
local::connect_pins microblaze_riscv_0_axi_periph/M01_ACLK system_clock/clk_out1
local::connect_pins microblaze_riscv_0_axi_periph/M02_ACLK system_clock/clk_out1
local::connect_pins microblaze_riscv_0_axi_periph/M01_ARESETN system_reset/peripheral_aresetn
local::connect_pins microblaze_riscv_0_axi_periph/M02_ARESETN system_reset/peripheral_aresetn
local::connect_pins mdm_1/Debug_SYS_Rst mig_reset/mb_debug_sys_rst
# AXI Timer
local::create_xip axi_timer:2.0 axi_timer_0
local::connect_pins axi_timer_0/s_axi_aclk system_clock/clk_out1
local::connect_pins axi_timer_0/s_axi_aresetn system_reset/peripheral_aresetn
local::connect_pins axi_timer_0/interrupt microblaze_riscv_0_xlconcat/In0
local::connect_ifs axi_timer_0/S_AXI microblaze_riscv_0_axi_periph/M01_AXI
# AXI UART Lite
local::create_xip axi_uartlite:2.0 axi_uartlite_0 {
CONFIG.C_BAUDRATE 115200
}
local::connect_pins axi_uartlite_0/s_axi_aclk system_clock/clk_out1
local::connect_pins axi_uartlite_0/s_axi_aresetn system_reset/peripheral_aresetn
local::connect_pins axi_uartlite_0/interrupt microblaze_riscv_0_xlconcat/In1
local::connect_ifs axi_uartlite_0/S_AXI microblaze_riscv_0_axi_periph/M02_AXI
local::make_if_external axi_uartlite_0/UART uart_0
# Epilogue
assign_bd_address
regenerate_bd_layout
arty-a7-mbv32.xdc - 制約ファイル
set_property PACKAGE_PIN C2 [get_ports sys_resetn]
set_property IOSTANDARD LVCMOS33 [get_ports sys_resetn]
set_property PACKAGE_PIN E3 [get_ports {sys_clock[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sys_clock[0]}]
set_property PACKAGE_PIN A9 [get_ports uart_0_rxd]
set_property PACKAGE_PIN D10 [get_ports uart_0_txd]
set_property IOSTANDARD LVCMOS33 [get_ports uart_0_rxd]
set_property IOSTANDARD LVCMOS33 [get_ports uart_0_txd]
local-funcs.tcl - 内部利用関数の定義
#
# local functions
#
namespace eval local {
proc is_installed {board} {
return [llength [get_board_parts -quiet $board]]
}
proc create_xip_cell {ip name args} {
set cell [create_bd_cell -type ip -vlnv xilinx.com:ip:$ip $name]
if {[llength $args] > 0} {
set_property -dict [lindex $args 0] $cell
}
return $cell
}
proc create_inline_hdl {ihdl name args} {
set cell [create_bd_cell -type inline_hdl -vlnv xilinx.com:inline_hdl:$ihdl $name]
if {[llength $args] > 0} {
set_property -dict [lindex $args 0] $cell
}
return $cell
}
proc connect_pins_list {obj_a obj_b pins_list} {
if {[llength $pins_list] > 0} {
set pins [lindex $pins_list 0]
set pin_a $obj_a/[lindex $pins 0]
set pin_b $obj_b/[lindex $pins 1]
connect_pins $pin_a $pin_b
connect_pins_list $obj_a $obj_b [lrange $pins_list 1 end]
}
}
proc connect_pins {src_pin dst_pins args} {
if {[llength $args] > 0} {
connect_pins_list $src_pin $dst_pins [lindex $args 0]
} else {
set len [llength $dst_pins]
if {$len >= 1} {
connect_bd_net [get_bd_pins $src_pin] [get_bd_pins [lindex $dst_pins 0]]
if {$len > 1} {
connect_pins $src_pin [lrange $dst_pins 1 end]
}
}
}
}
proc connect_ifs {if_a if_b} {
connect_bd_intf_net [get_bd_intf_pins $if_a] [get_bd_intf_pins $if_b]
}
proc connect_bd_port {bd_port bd_pins} {
set len [llength $bd_pins]
if {$len >= 1} {
connect_bd_net [get_bd_ports $bd_port] [get_bd_pins [lindex $bd_pins 0]]
if {$len > 1} {
connect_bd_port $bd_port [lrange $bd_pins 1 end]
}
}
}
proc make_pin_external {bd_pin name} {
make_bd_pins_external [get_bd_pins $bd_pin] -name $name
}
proc make_if_external {bd_if name} {
make_bd_intf_pins_external [get_bd_intf_pins $bd_if] -name $name
}
}
全てのファイルを同じフォルダ配下に置いたら、下記コマンドを実行します。実行するとVivadoが立ち上がり、プロジェクトとBlock Designが作成されます。Vivadoの実行環境にArty A7-100Tのボード定義ファイルがインストールされていない場合、ボード情報のダウンロードが行われて多少時間が掛かります。
source /tools/Xilinx/2025.1/Vivado/settings64.sh
vivado -source arty-a7-mbv32.tcl
MIG Configuration
スクリプト実行直後の状態では、まだBitstreamの生成は出来ません。MIGの設定を変更する必要があります。
Block Design右上にあるmig_7series_0
をダブルクリックしてMemory Interface Generator
ウィンドウを表示します。
ウィドウが表示されたらNextボタンを7回クリックします。
FPGA Optionsが表示されたらSystem Clockを [Single-Ended] から [No Buffer] に変更します。変更したらNextボタンを3回クリックします。
Pin Selectionが表示されたらValidateボタンをクリックして、表示されたDRC Validation
ウィンドウのOKボタンをクリックして下さい。その後、Nextボタンを3回クリックして下さい。
Simulation Options
が表示されたらAcceptをチェックしてNextを2回クリックして下さい。
Design Notesが表示されたら、Generateボタンをクリックして終了です。
Bitstream作成
メニューバーから [Flow] -> [Generate Bitstream] を選択します。
Save Project
ウィンドウが表示されたらSaveボタンをクリックします。
No Implementation Results Available
ウィンドウが開いたらYesボタンをクリックします。
Launch RUns
ウィンドウが開きますので、OKをクリックすると論理合成からBitstreamの生成まで実行されます。
Bitstreamが生成されるとBitstream Generation Completed
ウィンドウが表示されますのでCancelボタンをクリックして閉じて下さい。
XSAファイル作成
メニューバーから [File] -> [Export] -> [Export Hardware...] を選択して、ウィンドウが開いたらNextボタンをクリックします。
Include bitstream/binary
をチェックしてNextボタンをクリックします。
XSA File Name
を設定したらFinishボタンをクリックしてXSAファイルを作成します。以降ではファイル名をarty-a7-mbv32.xsaとして記述します。
Zephyrビルド手順
前準備
Zephyr Getting Started Guideに記載されている通り、最初はZephyr 3.7.0 Getting Started Guideの手順を実行してビルド環境を用意して下さい。
Install the Zephyr SDKに記載されている手順まで実行します。最後に記述されているudev rulesのインストールは実行しなくても問題ありません。
次に作業に利用するファイル名、ディレクトリ名を定義します。作業環境に合わせて修正して下さい。
# parameters
PROJ_NAME=arty-a7-mbv32
XSA_FILE=$HOME/ws/vivado/$PROJ_NAME/$PROJ_NAME.xsa
SDT_DIR=$HOME/${PROJ_NAME}_sdt
プロジェクト作成
オリジナルのソースツリーにMicroBlaze V固有のソースツリーを追加します。
本記事ではSDK及びプロジェクトがホームディレクトリ配下にある事を前提に記述します。
最初にphythonのvenvを有効化します。インストール手順の実行直後や実行済みの場合は不要です。
source $HOME/zephyrproject/.venv/bin/activate
次にMicroBlaze V固有のソースツリーを追加します。
cd $HOME/zephyrproject/
mv zephyr zephyr.upstream
git clone https://github.com/Xilinx/zephyr-amd.git -b xlnx_rel_v2025.1 zephyr
west update
west lopper-install
Devicetree更新
プロジェクト用DevicetreeをXSAファイルから抽出したDevicetreeで更新します。
# create the SDT files
. /tools/Xilinx/2025.1/Vivado/settings64.sh
xsct -eval "sdtgen set_dt_param -dir $SDT_DIR -xsa $XSA_FILE ; sdtgen generate_sdt"
# update the devicetree for Zephyr Project
cd $HOME/zephyrproject/
LOPPER_DTC_FLAGS="-b 0 -@" west lopper-command -p microblaze_riscv_0 -s $SDT_DIR/system-top.dts -w $HOME/zephyrproject/zephyr
これでビルドの準備は完了です。
Hello Worldのビルド
最初に定番のHello Worldのビルドと実行を試します。
ビルド手順は以下の通りです。
cd $HOME/zephyrproject/zephyr
west build -p -b mbv32 tests/misc/test_build/
次にHostPCとTarget BoardをUSBで接続した後、下記コマンドを実行します。
cd $HOME/zephyrproject/zephyr
west flash --runner xsdb --elf-file $HOME/zephyrproject/zephyr/build/zephyr/zephyr.elf --bitstream $SDT_DIR/$PROJ_NAME.bit
実行に成功すると、(最初の2024.2の表示が気になりますが)メッセージがシリアルに出力され続けます。
*** Booting Zephyr OS build xilinx_v2024.2-111-g986cf11c05ea ***
threadA: Hello World from riscv!
threadB: Hello World from riscv!
threadA: Hello World from riscv!
threadB: Hello World from riscv!
threadA: Hello World from riscv!
threadB: Hello World from riscv!
threadA: Hello World from riscv!
threadB: Hello World from riscv!
...
その他のテストコードをビルド
Zephyrの開発環境にはHello Worldの他にも色々と試せるテストコードがtest
ディレクトリ配下に用意されています。
例としてtests/benchmarks/latency_measure/
を実行する場合は以下の手順となります。
cd $HOME/zephyrproject/zephyr
west build -p -b mbv32 tests/benchmarks/latency_measure
実行はHello Worldと同じ手順です。
おわりに
初めてZephyrに触れましたが、最近のRTOSはビルドシステム込みで提供されるので、ビルド環境が乱立していたuITRON世代には新鮮でした。
MicroBlaze VはEthernet Driverが未提供なので、早く正式サポートして貰いたいですね。