PolarFire® SoCシリーズ⑤ 内部APBインターフェースの使い方

APBインターフェースの使い方

概要

はじめに

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 GuidePolarFire 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
(fig.1-2)各リンク集

内部リンク

記事リンク
第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
(fig.1-3)内部リンク

第2回:APBバスによる通信の確立

今回は、FPGAファブリック上に実装したPWMモジュールをAPBバスを通じて制御し、正常に動作することを確認していきます。

(fig.2-1)記事の構成

構成

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

(fig.2-2)PolarFire® SoC Discovery Kit 外観

APBバスについて

APBバスとは?

APBバス(Advanced Peripheral Bus)とは、Arm社が定義したAMBA(Advanced Microcontroller Bus Architecture)規格の一部で、主に低速ペリフェラル(周辺機器)との通信に使われるバスです。以下のような特徴を持っています。

APBバスとは
  • 低帯域・低消費電力向け
    高速なデータ転送を必要としない、タイマ、UART、GPIOなどのペリフェラルに最適です。
  • シンプルな設計
    アドレス/データバスの構造が簡単で、制御信号も少ないため、FPGAやSoCへの実装が容易です。
  • 読み書きが非パイプライン化されている
    1回のリード/ライトで1命令だけを扱うシーケンシャルな動作(= 高速性よりも確実性重視)。

APBバスの種類

APBバスのバージョン(APB2, APB3, APB4, APB5)は、ArmのAMBA仕様に基づく世代の違いを示しています。そして、PolarFire SoCに搭載されているAPBはAPB3仕様がベースとなっているようです。

AMBA仕様の勉強 | APB5とAPB4、APB3インターフェース差分 : すきま研究所日誌

APB3 の主な信号一覧と役割

信号名方向ビット数役割
PCLK入力1bitAPBバスのクロック信号。すべての転送はこのクロックで同期される。
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.待機状態のない書き込みの場合:
  1. マスタ:PSEL=1, PWRITE=1, PADDR にアドレス、PWDATA にデータ
  2. 次クロックで PENABLE=1 に → スレーブがデータを受け取る
  3. スレーブ:PREADY=1 で応答、必要なら PSLVERR を1に
(fig.2-3)書込みシーケンス。AMBA3 APB Protocol(p.2-2)から抜粋 AMBA APB Protocol Specification Version B
2.待機状態がある書込みの場合
  1. マスタ:PSEL=1, PWRITE=1, PADDR にアドレス、PWDATA にデータ
  2. 次クロックで PENABLE=1 に → スレーブがデータを受け取る
  3. スレーブ:PREADY=0でWait
  4. スレーブ:PREADY=1 で応答、必要なら PSLVERR を1に
(fig.2-4)書込みシーケンス。AMBA3 APB Protocol(p.2-3)から抜粋 AMBA APB Protocol Specification Version B
3.待機状態が無い読み出しの場合
  1. マスタ:PSEL=1, PWRITE=0, PADDR にアドレス
  2. 次クロックで PENABLE=1
  3. スレーブ:PRDATA にデータ、PREADY=1 で応答
(fig.2-5)読込シーケンス。AMBA3 APB Protocol(p.2-4)から抜粋 AMBA APB Protocol Specification Version B
4.待機状態が有る読み出しの場合
  1. マスタ:PSEL=1, PWRITE=0, PADDR にアドレス
  2. 次クロックで PENABLE=1
  3. スレーブ:PREADY=0 でWait
  4. スレーブ:PRDATA にデータ、PREADY=1 で応答
(fig.2-6)読込シーケンス。AMBA3 APB Protocol(p.2-5)から抜粋 AMBA APB Protocol Specification Version B

[Step 3-1] Libero SoC でFPGAデザインを読込み

1.Libero SoCを起動しプロジェクトを読込み

①Libero SoCを起動し、「Project」>「Open Project」をクリック

②前回作成したプロジェクトを読み込みます。

2.PWMモジュールの配置

①タブを「Catalog」に変更し、「Peripherals」直下の「Core PWM」をドラッグアンドドロップで配置します。

(fig.3-1-2-1)CorePWMの追加

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

(fig.3-1-2-2-2)PWM設定

3.CoreAPBモジュールの配置と配線

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

(fig.3-1-3-1)

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

(fig.3-1-3-2)APBバスコンフィグレータの設定
項目説明今回の設定値
Data Width ConfigurationAPB Master Data バス幅32bit
Number of Address bit driven by masterFICや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ブロックを接続します。

(fig.3-1-3-3)MSSブロックとCorePWMブロックの接続

④PWM出力をTopレベルへ表示させます

4.CLock Conditioning Circuitry(CCC)の追加と配線

PWMの高分解能化を実現するために、入力クロックをCCC(Clock Conditioning Circuitry)を使って逓倍し、高速クロックを生成します。これにより、より細かいデューティ比の制御が可能になります。
ボード上には50MHzのクロック源が搭載されており、これを500MHzに逓倍します。

①タブを「Catalog」に変更し、「Clock & Management」直下の「CLock Conditioning Circuitry(CCC)」をドラッグアンドドロップで配置します。

(fig.3-1-4-1)CCCの追加

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

(fig.3-1-4-2)CCCの設定

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

(fig.3-1-4-3)CCCの設定

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

(fig.3-1-4-4)クロックの配線

5.メモリマップの確認

上部メニューの「View Memory Map」をクリックします。

(fig.3-1-5-1)View Memory Mapボタン

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

(fig.3-1-5-2)メモリ割り当ての確認

6.HDLコードの自動生成

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

(fig.3-1-6-1)HDLコードの生成

7. 論理合成

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

(fig.3-1-7-1)論理合成準備

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

(fig.3-1-7-2)論理合成実行

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

(fig.3-1-7-3)論理合成完了

8. ピンのアサイン

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

(fig.3-1-8-1)ピンアサインを開く

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

(fig.3-1-8-2)ピンアサイン画面編集

③以下のように各ポートをピンにアサインします。

Port NameI/O StandardPin Number
GPIO_2_F2M_2LVCMOS18T19
GPIO_2_M2F_0LVCMOS18T18
PADLVCMOS18R18
A_0LVCMOS18U17
PWM[0]LVCMOS18V17
(fig.3-1-8-3)ピンアサイン表

(fig.3-1-8-4)PWMの割り当て

④設定後、保存してウィンドウを閉じます。

9.配置配線の実行

①メニュー下部の「Place abd Route」ボタンをクリックし、配置配線を実行します。

(fig.3-1-9-1)配置配線実行

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

(fig.3-1-9-2)配置配線完了

10. プログラムのフラッシュ

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

(fig.3-1-10-1)書込み実行

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

(fig.3-1-10-2)書込み完了

[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
(fig.3-2-1-1)ドライバフォルダ

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」をダブルクリックするとデバッグプログラムが書き込まれます

(fig.3-2-5-1)書込み

[Step 3-3]動作確認

LED2の抵抗の片端をオシロスコープでモニターすると、計算上は31.2kHzの出力が得られるはずですが、実際には500kHzのPWMが出力されています。
これは、PWMのハードウェア設定で「Fixed Prescale」にチェックが入っているためです。この設定が有効な場合、APB経由での書き込みアクセスが無効となり、Prescaleの値が反映されません。その結果、500kHzのPWMが出力されているのです。

項目記号/式数値単位
入力クロックFin50MHz
PLL逓倍M10
PWMモジュール入力クロックFclk = Fin × M500MHz
プリスケーラPRE16
周期設定PERIOD100
PWM周波数Fclk / ((PRE + 1) *PPERIOD)31.2kHz
(fig.3-3-1-1)PWM波形

編集後記

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


コメント

タイトルとURLをコピーしました