[PolarFire SoC] RISC-V と SoC MSS の割込を理解する

(更新

Understanding interrupts of RISC-V and PolarFire SoC MSS.

23日の記事で、PolarFire SoC の RISC-V 組込プログラムを SD カードからブートするところまで確認できました。いままで、mpfs-blinky という、SoftConsole 6.4 に付属のサンプルプログラムを評価してきましたが、その名に反して、LED 点滅の機能確認を放置してきました。その理由は、同プログラムの LED 点滅には、GPIO と割込の理解が必要と考えたからです。

いまや、UART の出力も SD カードからのブートもできるようになったので、そろそろ GPIO と割込の評価に移りましょう。

RISC-V 割込の基本

RISC-V の割込を理解するには、Patterson & Waterman の The RISC-V Reader: An Open Architecture Atlas が参考になります。また技術の詳細については、RISC-V ISA Specification: Volume 2, Privileged Spec v. 20190608 にあたるべきですが、以下にごく簡単に(今回の記事の理解に必要十分な程度に)、RISC-V の割込について説明します。

多くのマイコンアーキテクチャがそうであるように、RISC-V の割込も制御レジスタによって管理されます。RISC-V の制御レジスタ(CSR)の中で、割込関連の重要なレジスタとして mstatus と mie、mip があります。

mstatus レジスタには MIE(Machine Interrupt Enable)というビットがあり(mie レジスタとは別です)、これが 1 のとき、割込がグローバルに有効(許可)となり、0 のときに無効(不許可)となります。

また mip(machine interrupt pending)レジスタにはいくつかの例外/割込要因がビットマップされています。例えば外部要因による割込が発生した場合には mip レジスタの MEIP ビットが 1 になります。同様に、コア内蔵タイマから割込が発生した場合には、mip レジスタの MTIP ビットが 1 になります。

各割込要因の有効/無効を制御するには mie(machine interrupt enable)レジスタを使います。mie は mip と同じビット配列となっていて、外部割込を有効にするには mie レジスタの MEIE ビットを 1 に、またコア内蔵タイマの割込を有効にするには MTIE ビットを 1 にします。

PolarFire SoC MSS の割込

RISC-V では、基本的には外部要因の割込はすべて MEIP, MEIE のそれぞれ 1ビットにマップされており、ここでは外部割込の要因が、例えば UART なのか GPIO なのかを識別することはできません。ここから先の処理は、各マイコンの「実装」に委ねられており、たとえば PolarFire SoC の RISC-V MSS(Microprocessor Subsystem)では、PLIC(Platform-Level Interrupt Controller)というものを使って、複数の割込要因を識別、設定、管理するようになっています。PolarFire SoC MSS では、実に 185 個の外部割込要因があります。分類すると、

  • ペリフェラル(GPIO, UART, SPI など)からの割込: 169種類
  • CPU Core Complex blocks(DMA, L2 キャッシュなど)からの割込: 16種類

で構成されています。(詳しくは、Microsemi: UG0880 User Guide PolarFire SoC FPGA Microprocessor Sub-System(PDF)のセクション 6: Interrupts を参照してください。)

これらの割込は、原則としてすべて一つの MEIP, MEIE で管理されるため、割込ルーチン(正確に言うと例外処理ルーチン)の中では、MEIP が 1 であったならば、PLIC に問い合わせることで、その割込の要因が上述の 185種類のうちいずれかであるかを識別してから、担当する割込処理ルーチンにディスパッチすることになります。(サンプルプログラム mpfs-blinky では、すべての割込をこのやり方で処理しています。)

しかしながら、このやり方ですと、PLIC に問い合わせるだけのオーバーヘッドがあり、高レートで発生する割込を処理するには十分な性能が得られない場合があります。そのため、RISC-V の仕様では、mie/mip レジスタの上位ビット(ビット 16から上)を使って、外部割込要因を直接 mie/mip にマップしても良いことになっています。(上記  RISC-V ISA Specification: Volume 2, Privileged Spec v. 20190608 の 3.1.9 Machine Interrupt Registers (mip and mie) を参考にしてください。)これを、PolarFire SoC MSS では「local interrupts」と呼んでいます。

まとめますと、PolarFire SoC MSS の割込プログラミングをするには、以下の 2つのを理解する必要があります。

  • RISC-V(仕様)の mstatus, mie/mip レジスタ
  • PolarFire SoC MSS(実装)の PLIC(割込コントローラ)

PolarFire SoC MSS 割込プログラミングの実際

それでは、実際に mpfs-blinky のコードを参考にしながら、割込プログラミングを確認していきましょう。ここでは、SoftConsole 6.4 付属のオリジナルコードを見ながら説明していきます。

まずは、application/hart0/e51.c から見ていきます。

Mutex の初期化

割込処理とは直接関係ないのですが、関数 e51_setup() を見ると mss_init_mutex() という関数を呼び出しているのが分かります。ここでは、並行プログラミングにおける mutex(相互排除)機能を実現したベアメタルライブラリを利用しています。この mutex はマルチコア(hart)に対応しており、E51 コアと U54_1 コアの間で、UART0 を同時に操作しないために利用されています。詳細は略します。

ペリフェラルの有効化

続いて、SYSREG->SOFT_RESET_CR というレジスタ定義を用いて、ペリフェラルを有効化しています。HSS(Hart Software Services)を使う場合には、そちらで初期化されるものについては設定不要かもしれません。なお、このコードではレジスタ中のビット位置(オフセット)をマジックナンバーで指定していて分かりづらいです。ヘッダファイルの設計とタイミングが合わなかったのでしょう。現在であれば、platform/mpfs_hal/mss_sysreg.h というファイルの SOFT_RESET_CR__OFFSET という定義を用いるべきだと思います。

GPIO 割込ソースの選択設定

PolarFire SoC MSS では、可能な限りすべての GPIO ピン一つ一つを、個別の割込ソースとして設定できるようになっています。しかしそれでは合計で 64本の割込要因が必要となります。同社でどのような判断があったのかは分かりませんが、これをすべて PLIC のグローバル割込とすることは諦め、2つに分割することになったようです。詳しくは、platform/drivers/mss_gpio/mss_gpio.h というヘッダファイル中の説明を見てください。

サンプルコードでは、SYSREG->GPIO_INTERRUPT_FAB_CR のビットにすべて 0 を割り当てることで、以下の GPIO ピン(計 32本)を割込要因として使えるようにしていますが、上記レジスタのビットを変更することで、他の GPIO に割り当て直すことができます。

  • GPIO0: ビット 0 〜 13
  • GPIO1: ビット 0 〜 17

この機能は、後ほどコードを修正するときに利用したいと思います。

ペリフェラルへのクロック供給

SYSREG->SUBBLK_CLOCK_CR というレジスタを使い、MSS 内蔵のペリフェラルすべてにクロックを供給します。

PLIC の設定

続いて、PLIC(割込コントローラ)を設定しています。

PLIC_init() は、PLIC 自身の初期化です。続いて、PLIC_SetPriority_Threshold(0) という関数を呼んでいます。PLIC では、入力につながる割込要因に優先度(0〜7)を割り当て、さらにスレッシュホールドを設定することで、スレッシュホールドよりも高い優先度の割込要因をまとめて有効にしたり、無効にしたりできます。

ここでは、スレッシュホールドを 0 に設定し、さらに必要な要因(下記)の優先度を 2 に設定しています。(つまり、各要因の割込が PLIC 内で有効になっていれば、このスレッシュホールドと優先度の比較により、優先度のテストにパスする、という訳です。優先度のテストにパスしても、割込が有効になっていなければ割込が発生しないという点に注意してください。)

  • GPIO0: ビット 0 〜 2
  • GPIO0 non-direct 割込
  • GPIO1 non-direct 割込
  • GPIO2 non-direct 割込

non-direct GPIO 割込というのは、先ほどの SYSREG->GPIO_INTERRUPT_FAB_CR の説明に関連します。つまり、同レジスタにより GPIO ピンの個別割込に割込要因線を割り当てていない場合でも、例えば GPIO0 中のいずれかのピンに割込が発生すれば、「GPIO0 non-direct 割込」が発生する、ということになります。つまり、複数のピンを 1つの割込に集線している訳です。

割込の許可

続いて、関数 __enable_irq() を呼ぶことで、前述の mstatus レジスタ MIE ビットに 1 を設定(割込のグローバルな許可)する訳ですが、その前に、(おそらく念のため)ローカル割込(前述の local interrupts)上の UART0 割込要因を不許可にしています。

GPIO の補足説明

続いて、ベアメタルライブラリを使用して GPIO を設定していますが、いくつか重要な点を説明しておきます。

まず最初にコードを見ると GPIO0_LO, GPIO1_LO という記述が出てきます。この LO(あるいはHI)とは何でしょう。PolarFire MSS では、通常 GPIO などのようなペリフェラルを main APB bus(0x2000_00000x203f_ffff)に配置していますが、AMP(非対称マルチプロセシング)などで、例えばコア 1〜2 を Linux、コア 3〜4 を RTOS 用などと割り振った際に、Linux からのバスアクセスにより、RTOS のペリフェラルアクセスに影響(アクセス時間の不均一など)を及ぼす可能性があります。そのため、PolarFire SoC MSS では、同じペリフェラルバスを異なるメモリマップ AMP APB bus(0x2800_00000x2fff_ffff)からもアクセスできるようにしています。(もちろん、2つのメモリ空間にマップされているからといって同じペリフェラルに無秩序にアクセスできる訳でなく、Linux と RTOS から同じペリフェラルにアクセスする場合は、mutex などによる相互排除が必要です。)

つまり、GPIO0_LO や GPIO1_LO というアドレスでアクセスした場合には main API bus を用い、GPIO0_HI や GPIO1_HI でアクセスした場合には、AMP APB bus を用いる、という訳です。Linux と RTOS を併用する AMP 設計のときには思い出すと良いでしょう。

もう一つ、GPIO ピンには動作モードがあります。一つは入出力で、

  • MSS_GPIO_INPUT_MODE
  • MSS_GPIO_OUTPUT_MODE
  • MSS_GPIO_INOUT_MODE

の 3つから選択します。また入力で用いる場合には、さらにビット OR で、次の割込モードを指定します。

  • MSS_GPIO_IRQ_LEVEL_HIGH(レベルトリガ high)
  • MSS_GPIO_IRQ_LEVEL_LOW(レベルトリガ low)
  • MSS_GPIO_IRQ_EDGE_POSITIVE(エッジトリガ pos.)
  • MSS_GPIO_IRQ_EDGE_NEGATIVE(エッジトリガ neg.)
  • MSS_GPIO_IRQ_EDGE_BOTH(エッジトリガ両方)

GPIO の設定と割込の許可

それではコードの説明に戻ります。

MSS_GPIO_init(GPIO0_LO) は、GPIO0 の初期化ですね。APB bus は、main bus を使っています。

MSS_GPIO_config() では、各 GPIO ピンの入出力モードと割込モードを設定しています。

MSS_GPIO_enable_irq() では、GPIO の各割込要因に対して、PLIC を使って global 割込を有効化しています。

最後に、MSS_GPIO_set_outputs() でピン出力を全 0 に設定しています。

UART の初期化

続いて、 MSS_UART_init() という関数で UART0 を初期化しています。ここは自明ですので説明は省略します。なお、このサンプルコードでは UART の割込は使用していないようです。

コア(hart)U54_1 を起こす

最後に、E51 コアは、U54_1 コアを「software interrupt」(他のマイコンで言うところのコア間割込に相当)で起こしています。後ほど、wfi 命令について説明します。

e51_application()

最後にコア E51 は、関数 e51_application() に入ります。ここの内容については、今までの説明でほぼ理解頂けると思うので省略します。

続いて、コア U54_1 用の application/hart0/u54_1.c を見ていきます。

software 割込待ち

コア E51 が初期化処理をしている間、コア U54_1 は関数 u54_1_init_hal() で割込(software interrupt)待ちをします。上で簡単に説明しましたが、software interrupt とは、他のマイコンでコア間割込、プロセッサ間割込と呼ばれるものと同等で、あるコア(例えば E51)が別のコア(例えば U54_1)に割込をかけることができます。

U54_1 コアは初期状態で mstatus の MIE が 0 となっており、外部割込は受け付けませんが、ここで mie レジスタの MSIE(machine software interrupt enable)ビットを 1とすることで、software interrupt があったら起こしてほしい、という意思表示をします。

その後、wfi 命令(wait for interrupt)命令を繰り返し実行します。通常はここでスリープしますが、software interrupt が入ると、wfi 命令を抜け出て、do … while() 文で mip レジスタをチェックします。もし確かに software interrupt が発生していたら、先に進み、__enable_irq() で割込をグローバルに許可します。

ここで「mstatus レジスタの MIE ビットが立っていなくても、wfi 命令から起きられるのか」という疑問をお持ちの方もあるかと思います。これは良い質問です。RISC-V の定義では、mstatus の MIE ビットが立っていなくても、mie レジスタで有効になっている要因が mip でペンディングになったときは、wfi 命令から起きなくてはいけないことになっています。

そして  u54_1_application() へ

最後に U54_1 コアは、関数 u54_1_application() に入ります。こちらも、今までの説明でほぼ理解頂けると思いますので、説明は省略します。

Icicle Kit の参照デザインについて

今日は説明が長々と続きますが、もう少し頑張りましょう。

Icicle Kit を動作させるために、GitHub にて icicle-kit-reference-design というパッケージ(参照デザイン)が配布されています。こちらで配布されているバイナリは、おそらく、この参照デザインから生成されているものだと思います。

実は、mpfs-blinky で利用している GPIO 設定は上記の参照デザインと合致しておらず、そのため mpfs-blinky を実行しても LED は点滅せず、GPIO 割込も受けられません。そこで、mpfs-blinky を Icicle Kit 参照デザインに合わせて変更します。

Icicle Kit の GPIO ポート

Icicle Kit では、GPIO に以下のようにボタンスイッチや LED が接続されています。(スイッチや LED の番号はボード上のマーキングに準じます。回路図や参照デザインの番号とは、数がずれているところがあります。)

  • SW1: GPIO への接続なし(論理反転した後、割込信号 fabric_f2h[0] に接続)
  • SW2: GPIO2_30(論理反転あり。スイッチ閉で 1 入力)
  • SW3: GPIO2_31(論理反転あり。同)
  • SW4: GPIO への接続なし(FPGA ファブリックへの接続もなし)
  • LED1: GPIO2_16(1 出力で点灯)
  • LED2: GPIO2_17(同)
  • LED3: GPIO2_18(同)
  • LED4: GPIO2_19(同)

ただし、これらは直接に接続されている訳ではなく、他の GPIO 信号と OR になっている部分があります。HDL 風に書いたほうが分かりやすいでしょうか。

fabric_f2h[0] = !SW1 | gpio2[28];
gpio2[30] = !SW2 | gpio2[26];
gpio2[31] = !SW3 | gpio2[27];

LED1 = gpio2[16];
LED2 = gpio2[17];
LED3 = gpio2[18];
LED4 = gpio2[19];

プログラムへのパッチ

本来であれば、修正後のコードを GitHub に上げられれば良いのですが、SoftConsole 付属サンプルコードのライセンスが不明確なので(ライセンス自体は MIT のようですが)、今回はパッチ形式で上げさせて頂きます。

ポイントとしては、GPIO2 を使って個別の割込を受けるため、SYSREG->GPIO_INTERRUPT_FAB_CR = 1UL << 30 | 1UL << 31 と設定している点と、PLIC_EnableIRQ(FABRIC_F2H_0_PLIC) によって、MSS_INT_F2M[0] からの PLIC 割込を有効にしている点かと思います。

また、LED の点滅がわかりやすいように、delay_loop_max の値を大きくしてあります。

注意点として、MSS_INT_F2M[0](に限らず PLIC の入力すべて)はレベル high 割込となっているため、SW1 を押し続けている間、割込が連続して発生してしまう、というものです。これを解決するためには、ハードウェア(FPGA ファブリック)にて、割込アクノリッジの処理を入れなくてはならないので、今回はそのままとしておきます。

diff --git a/mpfs-blinky/src/application/hart0/e51.c b/mpfs-blinky/src/application/hart0/e51.c
index ead9a1d..951bb50 100644
--- a/mpfs-blinky/src/application/hart0/e51.c
+++ b/mpfs-blinky/src/application/hart0/e51.c
@@ -14,6 +14,8 @@
 
 #include "inc/common.h"
 
+#if MPFS_HAL_FIRST_HART == 0
+
 uint64_t uart_lock;
 
 
@@ -195,4 +197,4 @@ void e51(void)
     }
 }
 
-
+#endif /* if MPFS_HAL_FIRST_HART == 0 */
diff --git a/mpfs-blinky/src/application/hart2/u54_2.c b/mpfs-blinky/src/application/hart2/u54_2.c
new file mode 100644
index 0000000..dbc0c29
--- /dev/null
+++ b/mpfs-blinky/src/application/hart2/u54_2.c
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2019 Microchip Corporation.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * MPFS HAL Embedded Software example
+ *
+ */
+
+#include "mpfs_hal/mss_hal.h"
+
+#include "drivers/mss_gpio/mss_gpio.h"
+#include "drivers/mss_mmuart/mss_uart.h"
+
+#include "inc/common.h"
+
+uint64_t uart_lock;
+
+
+uint8_t fabric_f2h_0_plic_IRQHandler(void)
+{
+       MSS_UART_polled_tx_string(&g_mss_uart0_lo,
+                       "\r\nSetting output 0 to high\r\n");
+
+       MSS_GPIO_set_output(GPIO2_LO, MSS_GPIO_16, 1);
+       return EXT_IRQ_KEEP_ENABLED;
+}
+
+
+uint8_t gpio1_bit16_or_gpio2_bit30_plic_30_IRQHandler(void)
+{
+       MSS_UART_polled_tx_string(&g_mss_uart0_lo,
+                       "\r\nSetting output 1 to high\r\n");
+
+       MSS_GPIO_set_output(GPIO2_LO, MSS_GPIO_17, 1);
+       MSS_GPIO_clear_irq(GPIO2_LO, MSS_GPIO_30);
+       return EXT_IRQ_KEEP_ENABLED;
+}
+
+
+uint8_t gpio1_bit17_or_gpio2_bit31_plic_31_IRQHandler(void)
+{
+       MSS_UART_polled_tx_string(&g_mss_uart0_lo,
+                       "\r\nSetting output 2 to high\r\n");
+
+       MSS_GPIO_set_output(GPIO2_LO, MSS_GPIO_18, 1);
+       MSS_GPIO_clear_irq(GPIO2_LO, MSS_GPIO_31);
+       return EXT_IRQ_KEEP_ENABLED;
+}
+
+
+uint8_t gpio0_non_direct_plic_IRQHandler(void)
+{
+       return EXT_IRQ_KEEP_ENABLED;
+}
+
+
+uint8_t gpio1_non_direct_plic_IRQHandler(void)
+{
+       return EXT_IRQ_KEEP_ENABLED;
+}
+
+
+uint8_t gpio2_non_direct_plic_IRQHandler(void)
+{
+       return EXT_IRQ_KEEP_ENABLED;
+}
+
+
+void u54_2_setup(void)
+{
+    /*This mutex is used to serialize accesses to UART0 when all harts want to
+     * TX/RX on UART0. This mutex is shared across all harts.*/
+    mss_init_mutex((uint64_t)&uart_lock);
+
+    /* Bring the UART0, GPIO0, GPIO1 and GPIO2 out of Reset */
+    SYSREG->SOFT_RESET_CR &= ~((1u << 0u) | (1u << 4u) | (1u << 5u)
+            | (1u << 19u) | (1u << 20u) | (1u << 21u) | (1u << 22u)
+            | (1u << 23u) | (1u << 28u));
+
+
+    /* Making sure that the default GPIO0 and GPIO1 are used on interrupts.
+     * No Non-direct interrupts enabled 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 = 1UL << 30   // GPIO2 bit 30
+                                  | 1UL << 31;  // GPIO2 bit 31
+
+    /* All clocks on */
+    SYSREG->SUBBLK_CLOCK_CR = 0xffffffff;
+
+    /*************************************************************************/
+    PLIC_init();
+    PLIC_SetPriority_Threshold(0);
+    PLIC_SetPriority(FABRIC_F2H_0_PLIC, 2);
+    PLIC_SetPriority(GPIO1_BIT16_or_GPIO2_BIT30_PLIC_30, 2);
+    PLIC_SetPriority(GPIO1_BIT17_or_GPIO2_BIT31_PLIC_31, 2);
+
+    PLIC_SetPriority(GPIO0_NON_DIRECT_PLIC, 2);
+    PLIC_SetPriority(GPIO1_NON_DIRECT_PLIC, 2);
+    PLIC_SetPriority(GPIO2_NON_DIRECT_PLIC, 2);
+
+    /* Fabric Interrupts */
+    PLIC_EnableIRQ(FABRIC_F2H_0_PLIC);
+
+    __enable_irq();
+
+    MSS_GPIO_init(GPIO2_LO);
+
+    /* GPIO2 Inputs */
+    MSS_GPIO_config(GPIO2_LO, MSS_GPIO_30,
+        MSS_GPIO_INPUT_MODE | MSS_GPIO_IRQ_EDGE_POSITIVE);
+
+    MSS_GPIO_config(GPIO2_LO, MSS_GPIO_31,
+        MSS_GPIO_INPUT_MODE | MSS_GPIO_IRQ_EDGE_POSITIVE);
+
+    MSS_GPIO_enable_irq(GPIO2_LO, MSS_GPIO_30);
+    MSS_GPIO_enable_irq(GPIO2_LO, MSS_GPIO_31);
+
+    /* GPIO2 Outputs */
+    MSS_GPIO_config(GPIO2_LO, MSS_GPIO_16, MSS_GPIO_OUTPUT_MODE);
+    MSS_GPIO_config(GPIO2_LO, MSS_GPIO_17, MSS_GPIO_OUTPUT_MODE);
+    MSS_GPIO_config(GPIO2_LO, MSS_GPIO_18, MSS_GPIO_OUTPUT_MODE);
+
+    /* Set all outputs of GPIO1 to 0 */
+    MSS_GPIO_set_outputs(GPIO2_LO, 0x0);
+
+    MSS_UART_init(&g_mss_uart0_lo, MSS_UART_115200_BAUD,
+            MSS_UART_DATA_8_BITS | MSS_UART_NO_PARITY);
+
+    /* Synchronizing the hart's applications because they have interdependencies
+     *
+     * Waking up hart 1 from sleep. It needs to be sent after the hart1 entered
+     * sleep. Atomic orchestration needs to be implemented, or a delay
+     * introduced when hart0 init takes less time to execute than hart init
+     * which is trying to synch with. This can happen when hart0 is not
+     * using/initializing many peripherals. Configuring GPIO/UART/IRQ will take
+     * enough time for the hart1 to be in sleep when this point of code is
+     * reached. */
+    raise_soft_interrupt(1u);
+}
+
+
+void u54_2_application(void)
+{
+    safe_MSS_UART0_polled_tx_string("Hello World from u54_2 (hart 0).\r\n");
+
+    while (1)
+    {
+        // Stay in the infinite loop, never return from main
+
+        const uint64_t delay_loop_max = 1000000;
+        volatile uint64_t delay_loop_sum = 0;
+
+        for (uint64_t i = 0; i< delay_loop_max; i++) {
+            delay_loop_sum = delay_loop_sum + i;
+        }
+
+        safe_MSS_UART0_polled_tx_string("Setting outputs 0, 1 and 2 to high\r\n");
+
+        MSS_GPIO_set_output(GPIO2_LO, MSS_GPIO_16, 1);
+        MSS_GPIO_set_output(GPIO2_LO, MSS_GPIO_17, 1);
+        MSS_GPIO_set_output(GPIO2_LO, MSS_GPIO_18, 1);
+
+        for (uint64_t i = 0; i< delay_loop_max; i++) {
+            delay_loop_sum = delay_loop_sum + i;
+        }
+
+        safe_MSS_UART0_polled_tx_string("Setting outputs 0, 1 and 2 to low\r\n");
+        MSS_GPIO_set_output(GPIO2_LO, MSS_GPIO_16, 0);
+        MSS_GPIO_set_output(GPIO2_LO, MSS_GPIO_17, 0);
+        MSS_GPIO_set_output(GPIO2_LO, MSS_GPIO_18, 0);
+    }
+}
+
+
+/* Main function for the HART0(E51 processor).
+ * Application code running on HART0 is placed here.
+ * UART0 PLIC interrupt is enabled on hart0.*/
+void u54_2(void)
+{
+    u54_2_setup();
+    u54_2_application();
+
+    /* Shouldn't never reach this point */
+    while (1)
+    {
+       volatile static uint64_t counter = 0U;
+
+       /* Added some code as gdb hangs when stepping through an empty infinite loop */
+       counter = counter + 1;
+    }
+}
diff --git a/mpfs-blinky/src/boards/icicle-kit-es/mpfs_hal_config/mss_sw_config.h b/mpfs-blinky/src/boards/icicle-kit-es/mpfs_hal_config/mss_sw_config.h
index 6368366..02eb8ed 100644
--- a/mpfs-blinky/src/boards/icicle-kit-es/mpfs_hal_config/mss_sw_config.h
+++ b/mpfs-blinky/src/boards/icicle-kit-es/mpfs_hal_config/mss_sw_config.h
@@ -42,11 +42,11 @@
  * through some other method
  */
 #ifndef MPFS_HAL_FIRST_HART
-#define MPFS_HAL_FIRST_HART  0
+#define MPFS_HAL_FIRST_HART  1
 #endif
 
 #ifndef MPFS_HAL_LAST_HART
-#define MPFS_HAL_LAST_HART   4
+#define MPFS_HAL_LAST_HART   2
 #endif
 
 /*------------------------------------------------------------------------------
diff --git a/mpfs-blinky/src/platform/config/linker/mpfs-lim.ld b/mpfs-blinky/src/platform/config/linker/mpfs-lim.ld
index 0bf574e..c3aa067 100644
--- a/mpfs-blinky/src/platform/config/linker/mpfs-lim.ld
+++ b/mpfs-blinky/src/platform/config/linker/mpfs-lim.ld
@@ -102,7 +102,7 @@ MEMORY
     eNVM_SEC2 (rx) : ORIGIN  = 0x20220000, LENGTH = 8k
     eNVM_SEC0_1_3 (rx) : ORIGIN  = 0x20222000, LENGTH = 120k
     /* LIM - we place code here in this example */
-    ram (rwx) :     ORIGIN  = 0x08000000, LENGTH = 1920k
+    ram (rwx) :     ORIGIN  = 0x08020000, LENGTH = 1792k
     ram_DTIM (rwx) : ORIGIN  = 0x01000000, LENGTH = 7k
     scratchpad(rwx):  ORIGIN = 0x0A000000, LENGTH = 512k
     /* This 1k of DTIM is used to run code when switching the eNVM clock */
@@ -140,7 +140,7 @@ SECTIONS
 {
 
 /* text: test code section */
-  . = 0x08000000;
+  . = 0x08020000;
   .text : ALIGN(0x10)
   {
     __text_load = LOADADDR(.text);

無事に LED 点滅を確認できましたでしょうか。

今日はここまで。おつかれさまでした!

お問い合わせはお気軽に!

お問い合わせを頂いた後、継続して営業活動をしたり、ニュースレター等をお送りしたりすることはございません。
御返答は 24時間以内(営業時間中)とさせて頂いております。必ず返信致しますが、時々アドレス誤りと思われる返信エラーがございます。返答が届かない場合、大変お手数ではございますが別のメールアドレスで督促頂けますと幸いです。