概要
はじめに
Microchip社のPolarFire® SoCは、不揮発性で低消費電力のミッドレンジSoC FPGAで、64ビットのRISC-V ISAを5コア搭載したプロセッサーと低消費電力FPGAを組み合わせたチップです。
記事の目的
これまでのPolarFire SoCに関する記事では、MSS単体またはFPGA単体での動作に焦点を当てており、それぞれの間の通信については触れてきませんでした。
今回は、MSSとFPGA間の通信に注目し、その設定方法と動作について解説します。
開発の手順
本シリーズでは、MSSとFPGA間の通信確認を目的として、開発を3回に分けて進めていきます。
前回:GPIOによる通信の確立
FPGAファブリックに接続されたスイッチやLEDを、MSSから制御して動作を確認します。
本記事:APB(Advanced Peripheral Bus)を利用したLED制御
MSSからAPB経由で、LED2の点灯を制御します。
次回:PCからの指令によるLED制御
シリアル通信などを通じて、PCからのコマンドでLEDを制御します。

改定履歴
| 公開/変更日 | 変更内容 |
| 25/6/13 | 初版公開 |
リンク
外部リンク
| タイトル | リンク |
| PolarFire SoCプロダクトページ (Microchip) | PolarFire® SoC FPGAs | Microchip Technology |
| PolarFire SoC Discoveryキット (Microchip) | PolarFire® SoC Discovery Kit | Microchip Technology |
| PolarFire SoC MSS テクニカルリファレンスマニュアル | PolarFire SoC MSS Technical Reference Manual (microchip.com) |
| Microprocessor Subsystem (MSS) User’s Guide | PolarFire SoC MSS Technical Reference Manual (microchip.com) |
| PolarFire SoCプロダクトページ (GitHub) | PolarFire-SoC · GitHub |
| ベアメタルプロジェクト (Github) | GitHub – polarfire-soc/polarfire-soc-bare-metal-examples: Bare metal example software projects for PolarFire SoC |
| GPIO Bare Metal Driver (Github) | polarfire-soc-documentation/bare-metal-embedded-software/bare-metal-driver-user-guides/polarfire-soc-mss-driver-user-guides/mss-gpio/mss-gpio-driver-user-guide.md at master · polarfire-soc/polarfire-soc-documentation · GitHub |
内部リンク
| 記事 | リンク | |
| 第1回 | Lチカ_FPGA編 | PolarFire® SoCシリーズ① Lチカ_FPGA編 – ぴくおの電子工作的な何かWP |
| 第2回 | Lチカ1_MSS編 | PolarFire® SoCシリーズ② Lチカ1_MSS編 – ぴくおの電子工作的な何かWP |
| 第3回 | コア間メモリ共有の方法 | PolarFire® SoCシリーズ③ コア間メモリ共有の方法 – ぴくおの電子工作的な何かWP |
| 第4回 | 内部GPIOインターフェースの使い方 | PolarFire® SoCシリーズ④ 内部インターフェースの使い方 – ぴくおの電子工作的な何かWP |
| 第5回 (本記事) | 内部APBインターフェースの使い方 | PolarFire SoCシリーズ⑤ 内部APBインターフェースの使い方 – ぴくおの電子工作的な何かWP |
第2回:APBバスによる通信の確立
今回は、FPGAファブリック上に実装したPWMモジュールをAPBバスを通じて制御し、正常に動作することを確認していきます。

構成
前回の記事と同じく、PolarFire® SoC Discovery Kitを使用し、動作を確認していきます。
PolarFire® SoC Discovery Kit | Microchip Technology

APBバスについて
APBバスとは?
APBバス(Advanced Peripheral Bus)とは、Arm社が定義したAMBA(Advanced Microcontroller Bus Architecture)規格の一部で、主に低速ペリフェラル(周辺機器)との通信に使われるバスです。以下のような特徴を持っています。
- 低帯域・低消費電力向け
高速なデータ転送を必要としない、タイマ、UART、GPIOなどのペリフェラルに最適です。 - シンプルな設計
アドレス/データバスの構造が簡単で、制御信号も少ないため、FPGAやSoCへの実装が容易です。 - 読み書きが非パイプライン化されている
1回のリード/ライトで1命令だけを扱うシーケンシャルな動作(= 高速性よりも確実性重視)。
APBバスの種類
APBバスのバージョン(APB2, APB3, APB4, APB5)は、ArmのAMBA仕様に基づく世代の違いを示しています。そして、PolarFire SoCに搭載されているAPBはAPB3仕様がベースとなっているようです。
AMBA仕様の勉強 | APB5とAPB4、APB3インターフェース差分 : すきま研究所日誌
APB3 の主な信号一覧と役割
| 信号名 | 方向 | ビット数 | 役割 |
PCLK | 入力 | 1bit | APBバスのクロック信号。すべての転送はこのクロックで同期される。 |
PRESETn | 入力 | 1bit | 非同期リセット(Lowアクティブ) |
PADDR | 入力 | 任意 | 転送対象のアドレス(スレーブ内レジスタの選択に使われる) |
PSEL | 入力 | 1bit | スレーブ選択。1のときこのスレーブが選択されている。 |
PENABLE | 入力 | 1bit | 転送の2サイクル目を示す。PSELと組み合わせて有効。 |
PWRITE | 入力 | 1bit | 書き込み(1)か読み出し(0)かを示す。 |
PWDATA | 入力 | 32bit | マスタからスレーブへの書き込みデータ |
PRDATA | 出力 | 32bit | スレーブからマスタへの読み出しデータ |
PREADY | 出力 | 1bit | スレーブが転送完了可能かどうかを示す(1で完了) |
PSLVERR | 出力 | 1bit | 転送中にエラーがあったことを示す(1でエラー) |
転送シーケンスの簡単な流れ
1.待機状態のない書き込みの場合:
- マスタ:
PSEL=1,PWRITE=1,PADDRにアドレス、PWDATAにデータ - 次クロックで
PENABLE=1に → スレーブがデータを受け取る - スレーブ:
PREADY=1で応答、必要ならPSLVERRを1に

2.待機状態がある書込みの場合
- マスタ:
PSEL=1,PWRITE=1,PADDRにアドレス、PWDATAにデータ - 次クロックで
PENABLE=1に → スレーブがデータを受け取る - スレーブ:
PREADY=0でWait - スレーブ:
PREADY=1で応答、必要ならPSLVERRを1に

3.待機状態が無い読み出しの場合
- マスタ:
PSEL=1,PWRITE=0,PADDRにアドレス - 次クロックで
PENABLE=1に - スレーブ:
PRDATAにデータ、PREADY=1で応答

4.待機状態が有る読み出しの場合
- マスタ:
PSEL=1,PWRITE=0,PADDRにアドレス - 次クロックで
PENABLE=1に - スレーブ:
PREADY=0でWait - スレーブ:
PRDATAにデータ、PREADY=1で応答

[Step 3-1] Libero SoC でFPGAデザインを読込み
1.Libero SoCを起動しプロジェクトを読込み
①Libero SoCを起動し、「Project」>「Open Project」をクリック
②前回作成したプロジェクトを読み込みます。
2.PWMモジュールの配置
①タブを「Catalog」に変更し、「Peripherals」直下の「Core PWM」をドラッグアンドドロップで配置します。

②設定ウィンドウが立ち上がりますので、「APB Data Bus … 」を32に設定し、「OK」をクリックします。

3.CoreAPBモジュールの配置と配線
①生成した CorePWM モジュールと FIC3_APB_INITIATOR を接続しようとすると、レジスタアドレス信号(PADDR)のビット幅が異なるため、そのままでは直接接続できません。
そこでAPB(Advanced Peripheral Bus)規格に準拠したAMBAバススレーブ・スイッチ(デコーダ)IPコアを追加します。

②下記のようなコンフィギュレータが起動します。
画像の設定を確認し、「OK」をクリックします。

| 項目 | 説明 | 今回の設定値 |
| Data Width Configuration | APB Master Data バス幅 | 32bit |
| Number of Address bit driven by master | FICやCoreAPB3がスレーブに出力するアドレスのビット幅(例:28bit = 256MB) | 28bit |
| Position in slave address of upper 4 bits | マスタの上位アドレスビットをスレーブアドレスのどこに割り当てるか | [27:24] |
| Indirect Addressing | 通常のアドレス直接指定モードか否か(通常は Not in use) | Not in use |
| Allocate memory space to combined reagion slave | 各スレーブスロットにメモリ空間を割り当てる設定 | 無し |
| Enabled APB Slave Slot | スレーブスロットの有効化 | Slot0 , Slot 1 |
③MSSブロックとCorePWMブロックを接続します。

④PWM出力をTopレベルへ表示させます
4.CLock Conditioning Circuitry(CCC)の追加と配線
PWMの高分解能化を実現するために、入力クロックをCCC(Clock Conditioning Circuitry)を使って逓倍し、高速クロックを生成します。これにより、より細かいデューティ比の制御が可能になります。
ボード上には50MHzのクロック源が搭載されており、これを500MHzに逓倍します。
①タブを「Catalog」に変更し、「Clock & Management」直下の「CLock Conditioning Circuitry(CCC)」をドラッグアンドドロップで配置します。

②設定ウィンドウが立ち上がりますので、「Input Freqency」を50に設定。

③「Output Freqency」を500に設定し「OK」をクリック。

②追加したCCCの「OUT0_FABCLK_0」出力を「PCLK」ピンに、「CLKBUF_0」ピンの出力をCCCの「REF_CLK_0」入力に接続します。
「PLL_POWERDOWN_N_0」はHighに固定します。

5.メモリマップの確認
上部メニューの「View Memory Map」をクリックします。

corepwmが「0x40000000~0x40FFFFFF」に割り当てられている事が確認できます。

6.HDLコードの自動生成
①「Build Hierarchy」ボタンをクリックして階層構造を構築した後、
続けて「Generate」ボタンをクリックして、各モジュールの接続や設定に基づいたHDLコードの自動生成を行います。

7. 論理合成
①Smart Designファイルを右クリックし「Set as a Root」を選択します。

②タブを「Design Flow」に切り替え、「Implement Design」セクションの中にある「Synthesize」を右クリックします。
表示されたメニューから「Run」を選択して、論理合成を実行します。

③処理が正常に完了すると、ステップの左側にチェックマーク(✔)が表示されます。
これにより、合成が問題なく終了したことを確認できます。

8. ピンのアサイン
①「Constraints」セクションの中にある「Manage Constraints」を右クリックします。
表示されたメニューから「Open Constraint Manager view」を選択して、ピンアサインビューを表示します。

②「Edit」ボタンの横の▼マークをクリックします。表示されたメニューから「Edit with I/O Editor」をクリックします。

③以下のように各ポートをピンにアサインします。
| Port Name | I/O Standard | Pin Number |
| GPIO_2_F2M_2 | LVCMOS18 | T19 |
| GPIO_2_M2F_0 | LVCMOS18 | T18 |
| PAD | LVCMOS18 | R18 |
| A_0 | LVCMOS18 | U17 |
| PWM[0] | LVCMOS18 | V17 |

④設定後、保存してウィンドウを閉じます。
9.配置配線の実行
①メニュー下部の「Place abd Route」ボタンをクリックし、配置配線を実行します。

②処理が正常に完了すると、「Place and Route」の左側にチェックマーク(✔)が表示されます。
これにより、配置配線が問題なく終了したことを確認できます。

10. プログラムのフラッシュ
①「Program Design」セクションの中にある「Run PROGRAM Action」を右クリックします。
表示されたメニューから「Run」をクリックして、書込みを実行します。

②処理が正常に完了すると、「Program Design」セクションに配置されている、各ステップの左側にチェックマーク(✔)が表示されます。
これにより、書込みが問題なく終了したことを確認できます。

[Step 3-2] SoftConsoleで制御部を作成
1.ドライバーのコピー
PWM制御用のドライバファイルがプロジェクトに入っていない場合は、例えば以下のようなfpga-ipの中から見つけてプロジェクトに追加します。
..\..\03_Bare-metal_example\driver-examples\fpga-ip\CorePWM\mpfs-corepwm-slow-blink\src\platform\drivers\fpga_ip

2.プロジェクトを開く
SoftConsoleアプリケーションを起動し、前回のプロジェクトを開きます。
3. LED点灯制御部の追加
前回のプロジェクトの一部を、以下のソースコードのように変更します。
青色で示した部分が、今回追加・修正した箇所です。
#include <stdio.h>
#include <string.h>
#include "mpfs_hal/mss_hal.h"
#include "drivers/mss/mss_gpio/mss_gpio.h"
#include "drivers/mss/mss_mmuart/mss_uart.h"
#include "drivers/fpga_ip/CorePWM/core_pwm.h"
#include "inc/uart_mapping.h"
extern struct mss_uart_instance* p_uartmap_u54_1;
/******************************************************************************
* Instruction message. These message will be displayed on the UART terminal
when the program starts.
*****************************************************************************/
uint8_t g_message2[] =
"\r\n\r\n\r\n **** PolarFire SoC MSS GPIO example ****\r\n\r\n\r\n\
This program is running on u54_1.\r\n\r\n\
Observe the LEDs blinking. LEDs toggle every time the SYSTICK timer expires\r\n\
\r\n\
Press 1 to generate interrupt on GPIO2 pin 30.\r\n\
Press 2 to generate interrupt on GPIO2 pin 31.\r\n\
Press 3 to generate interrupt on F2M_0 signal.\r\n";
#define RX_BUFF_SIZE 64U
uint8_t g_rx_buff[RX_BUFF_SIZE] = {0};
volatile uint8_t g_rx_size = 0U;
/* Main function for the hart1(U54 processor).
* Application code running on hart1 is placed here.
* On Icicle kit, apart from the UART menu, you can also use push button
* switches to generate GPIO interrupts. The mapping is as follows
* push button SW1 - MSS_INT_F2M[0]
* push button SW2 - GPIO2_30
* push button SW3 - GPIO2_31
*/
//ここから挿入
#define PWM_PRESCALE 16
#define PWM_PERIOD 1000
#define COREPWM_BASE_ADDR 0x40000000
pwm_instance_t the_pwm;
//ここまで
void u54_1(void)
{
uint64_t mcycle_start = 0U;
uint64_t mcycle_end = 0U;
uint64_t delta_mcycle = 0U;
uint64_t hartid = read_csr(mhartid);
uint8_t cnt = 16U, int_num = 0U;
/* Clear pending software interrupt in case there was any.
* Enable only the software interrupt so that the E51 core can bring this
* core out of WFI by raising a software interrupt In case of external,
* bootloader not present
*/
clear_soft_interrupt();
set_csr(mie, MIP_MSIP);
#if (IMAGE_LOADED_BY_BOOTLOADER == 0)
/*Put this hart into WFI.*/
do
{
__asm("wfi");
}while(0 == (read_csr(mip) & MIP_MSIP));
/* The hart is out of WFI, clear the SW interrupt. Hear onwards Application
* can enable and use any interrupts as required */
clear_soft_interrupt();
#endif
PLIC_init();
__enable_irq();
/* Reset the peripherals turn on the clocks */
(void)mss_config_clk_rst(MSS_PERIPH_MMUART_U54_1, (uint8_t) MPFS_HAL_FIRST_HART, PERIPHERAL_ON);
(void)mss_config_clk_rst(MSS_PERIPH_MMUART_U54_2, (uint8_t) MPFS_HAL_FIRST_HART, PERIPHERAL_ON);
(void)mss_config_clk_rst(MSS_PERIPH_MMUART_U54_3, (uint8_t) MPFS_HAL_FIRST_HART, PERIPHERAL_ON);
(void)mss_config_clk_rst(MSS_PERIPH_GPIO0, (uint8_t) MPFS_HAL_FIRST_HART, PERIPHERAL_ON);
(void)mss_config_clk_rst(MSS_PERIPH_GPIO1, (uint8_t) MPFS_HAL_FIRST_HART, PERIPHERAL_ON);
(void)mss_config_clk_rst(MSS_PERIPH_GPIO2, (uint8_t) MPFS_HAL_FIRST_HART, PERIPHERAL_ON);
(void)mss_config_clk_rst(MSS_PERIPH_CFM, (uint8_t) MPFS_HAL_FIRST_HART, PERIPHERAL_ON);
(void)mss_config_clk_rst(MSS_PERIPH_FIC3, (uint8_t) MPFS_HAL_FIRST_HART, PERIPHERAL_ON);
/* mmuart1 initialization */
mss_enable_fabric();
MSS_UART_init( p_uartmap_u54_1,
MSS_UART_115200_BAUD,
MSS_UART_DATA_8_BITS | MSS_UART_NO_PARITY | MSS_UART_ONE_STOP_BIT);
MSS_UART_polled_tx_string(p_uartmap_u54_1,
g_message2 );
mcycle_start = readmcycle();
/* Configure Systick. The tick rate is configured in mss_sw_config.h */
SysTick_Config();
/* Making sure that the GPIO2 interrupts are routed to the PLIC instead of
* GPIO0 and GPIO1.
* Please see the mss_gpio.h for more description on how GPIO interrupts
* are routed to the PLIC */
SYSREG->GPIO_INTERRUPT_FAB_CR = 0xFFFFFFFFUL;
PLIC_SetPriority_Threshold(0);
for (int_num = 0u; int_num <= GPIO2_NON_DIRECT_PLIC; int_num++)
{
PLIC_SetPriority(GPIO0_BIT0_or_GPIO2_BIT0_PLIC_0 + int_num, 2u);
}
//ここから挿入
uint64_t led_cnt;
uint32_t duty_cycle = PWM_PERIOD / 2;
MSS_GPIO_config(GPIO2_LO, MSS_GPIO_0, MSS_GPIO_OUTPUT_MODE);
MSS_GPIO_config(GPIO2_LO, MSS_GPIO_1, MSS_GPIO_OUTPUT_MODE);
MSS_GPIO_config(GPIO2_LO, MSS_GPIO_2, MSS_GPIO_INPUT_MODE);
MSS_GPIO_config(GPIO2_LO, MSS_GPIO_3, MSS_GPIO_INPUT_MODE);
PWM_init( &the_pwm, COREPWM_BASE_ADDR, PWM_PRESCALE, PWM_PERIOD ) ;
PWM_set_duty_cycle( &the_pwm, PWM_1, 50);
PWM_enable(&the_pwm, PWM_1);
while(1u)
{
if ((MSS_GPIO_get_inputs(GPIO2_LO) >> 2) == 0)
{
MSS_GPIO_set_output(GPIO2_LO, MSS_GPIO_1, 1u);
}
else
{
MSS_GPIO_set_output(GPIO2_LO, MSS_GPIO_1, 0u);
}
if ((MSS_GPIO_get_inputs(GPIO2_LO) >> 3) == 1)
{
led_cnt ++;
if (led_cnt < 500000)
{
MSS_GPIO_set_output(GPIO2_LO, MSS_GPIO_0, 1u);
// led_cnt = 1;
}
else if (led_cnt < 1000000)
{
MSS_GPIO_set_output(GPIO2_LO, MSS_GPIO_0, 0u);
// led_cnt = 0;
}
else
{
led_cnt = 0;
}
}
// shared_var1 = led_cnt;
// write_to_shared_memory = led_cnt ;
}
5.書込み
「Launch Configration」タブの「GDB OpenOCD Debugging」直下の「mpfs-gpio-interrupt hw all-hart debug」をダブルクリックするとデバッグプログラムが書き込まれます

[Step 3-3]動作確認
LED2の抵抗の片端をオシロスコープでモニターすると、計算上は31.2kHzの出力が得られるはずですが、実際には500kHzのPWMが出力されています。
これは、PWMのハードウェア設定で「Fixed Prescale」にチェックが入っているためです。この設定が有効な場合、APB経由での書き込みアクセスが無効となり、Prescaleの値が反映されません。その結果、500kHzのPWMが出力されているのです。
| 項目 | 記号/式 | 数値 | 単位 |
| 入力クロック | Fin | 50 | MHz |
| PLL逓倍 | M | 10 | |
| PWMモジュール入力クロック | Fclk = Fin × M | 500 | MHz |
| プリスケーラ | PRE | 16 | |
| 周期設定 | PERIOD | 100 | |
| PWM周波数 | Fclk / ((PRE + 1) *PPERIOD) | 31.2 | kHz |

編集後記
正直なところ、まだわからないことが多く、手探りで実験を進めている状態です。ドキュメントは多く揃っているものの、その分資料やページの量が膨大で初めて扱う内容も多いため、情報の整理に苦労しています。


コメント