マルチコア対応仮想環境(athrill)を使用して TOPPERS/ATK2 を実行する


概要

athrill(アスリル)は,PC上で組込み系のプログラムを手軽にデバッグできるようにした CPU エミュレータです.前回は athrill を使用して,TOPPERS OS(ASP3) をデバッグする様子をお見せしました.

今回はマルチコア対応した athrill を使用して,TOPPERS/ATK2 の atk2-sc1-mc をデバッグします.なお,マルチコア対応版 athrill の名前は,シングルコア版と区別して『athrill2』と命名しました.

※athrill は誰でも手軽に利用できるように TOPPERS ライセンスで無償公開されています.

動作環境

athrill2 の動作環境は シングルコア版(athrill) と同じ環境でご利用できます.

ダウンロード/インストール方法

シングルコア版(athrill) をダウンロード/インストールすれば,自動的に athrill2 がインストールされますので,特別な作業は不要です.

事前準備

athrill2 で実行できるように移植した atk2-sc1-mc のソース一式は athrill 開発プロジェクト で公開されています.今回は,ビルド済みのサンプル・バイナリ (atk2-sc1-mc) を使用します.

サンプル・バイナリの配置場所は,
 "athrill/sample/os/atk2-sc1-mc_1.4.2/OBJ/atk2-sc1-mc"
です.

※atk2-sc1-mc のクリーンビルドする場合は,AUTOSAR 公式サイト から XMLスキーマを入手する必要があります.

サンプル・バイナリのタスク構成

今回,サンプル・バイナリの動作確認で関係するタスク構成は下図のとおりで,各コア(コア0とコア1)に,それぞれタスクが配置されています.

MainTask は,コア0に配置されており,シリアル入力でコマンド("1a")を受信すると Task1 を起動します.
また Task1 が起動すると,コア1側に配置されている Task9 が起動されます.

athrill2 の周辺デバイス構成

athrill2 周辺デバイス構成は下図のとおり,atk2-sc1-mc を実行する上で必要最小限の構成(割り込みコントローラ,タイマ,シリアルのみ)としています.本構成は athrill の構成を踏襲しつつ,マルチコア構成部分は v850e2m を参考にしました.

各CPUは完全に独立して動作しますが,クロック同期しています※.
なお,ソフトウェアがコア間同期をしたい場合は,コア間割り込みハンドラ処理を実装することで対応することになります.
※athrill はパイプラインの仮想化まではしておりませんので,1クロック=1命令としています.

仮想マルチコア・シミュレータの実装方式/デバッグ方法

シングルコアと比較して,マルチコアの場合は様々なものが×2となります.シングルコアの場合は当たり前と思っていた実装方式/デバッグ方法も,実際にデバッグしてみると様々な気づきがありました.今回,atk2-sc-mc をV850に移植作業を行っている間に気づいた点/検討した内容をまとめます(主要なもの).

実装方式

シングルコアの場合は選択肢はなかったのですが,コア数が2以上になると,各コアの実行順番を決める必要があります.
検討の結果,以下の2案ありましたが,今回はデバッグしやすさを優先して案2(コア0→コア1)を採用しました.

  • 案1.各コアを並列実行させる(マルチスレッドで実装)
  • 案2.各コアを直列実行させる(コア0→コア1 or コア1→コア0 or 実行順番をランダムに決める)

デバッグ方法

シングルコアの場合は自明ですが,コア数が2以上になると,デバッグ対象関数を実行しているコアがどちらであるのかを知る必要性がでてきます.今回は,以下の対応をしました.

  • 対応1.コアIDを命令実行ログに付加しました.
  • 対応2.現在実行中のコア情報を表示するための『core』コマンドを追加しました.

ただ,上記対応を行って実際にデバッグを行ってみると,各コアの実行が交互に行われるので非常にデバッグしづらいことに気づきました.以下に実際にステップ実行したログ示しますが,コア0は MainTask を実行しており,次のステップ実行するとコア1は全く違う関数を実行しているため,非常にデバッグしづらいことに気づきました….

      1 [DONE> core0 pc=0xa2a TaskMainMainTask(+0) 0xa2a: ADDI imm5(-36),r3(100763480) r3(100763480):100763444
      2 [DONE> core1 pc=0x77bc TOPPERS_enaint(+c) 0x77bc: ST.W r6(0x8), disp16(0) r29(0x60199ac):0x8
      3 [DONE> core0 pc=0xa2e TaskMainMainTask(+4) 0xa2e: ST.W r31(0xf9e4), disp16(32) r3(0x6018734):0xf9e4
      4 [DONE> core1 pc=0x77c0 TOPPERS_enaint(+10) 0x77c0: LD.W disp16(0),r29(0x60199ac), r10(0x77b0):0x8
      5 [DONE> core0 pc=0xa32 TaskMainMainTask(+8) 0xa32: ST.W r29(0x601b578), disp16(28) r3(0x6018734):0x601b578
      6 [DONE> core1 pc=0x77c4 TOPPERS_enaint(+14) 0x77c4: ANDI imm5(32),r10(8) r10(8):0

そのため,今回はデバッグしたいコアに注力できるように,デバッグ対象コアを選択できるようにしました.

サンプル・バイナリを実行する

athrill2 を起動する

それでは,athrill2 を使用して,サンプル・バイナリを実行してみましょう.

$ athrill2 -i -d device_config.txt -m memory.txt atk2-sc1-mc 2> core2.log

athrill2 起動後,coreコマンドを実行すると,コア0が 0x0番地で停止していることがわかります.

HIT break:0x0
[NEXT> pc=0x0 Os_Lcfg_asm.S 8
core
* Core0 debug mode=TRUE
- Core1 debug mode=TRUE

ステップ実行のため,nコマンドを実行すると,コア0が 0x0番地のJR命令を実行したことがわかります.

[DBG>n
[DONE> core0 pc=0x0 null(null) 0x0: JR disp22(2000):0x7d0

ここまでは,シングルコアと同じ流れです.
しかし,次のステップでは,コア1 が 0x0番地で停止し,コア0と同じ流れで命令実行されることがわかります.

HIT break:0x0
[NEXT> pc=0x0 Os_Lcfg_asm.S 8
[DBG>core
- Core0 debug mode=TRUE
* Core1 debug mode=TRUE
[DBG>n
[DONE> core1 pc=0x0 null(null) 0x0: JR disp22(2000):0x7d0
[NEXT> pc=0x7d0 start.S 63

atk2-sc1-mc を起動する

cコマンドを実行して,atk2-sc1-mc のサンプルプログラムを起動すると,起動ログが出力され,シリアル入力待ちになることがわかります.

c
[CPU>
TOPPERS/ATK2-SC1-MC Release 1.4.2 for RH850F1H_PB (Aug 14 2018, 15:54:15)

StartupHook @ core0
activate MainTask! @ core0
Input Command:

周期アラームコールバック起動確認(500ms周期)

athrillのシリアル入力機能を使用して,「N」を入力し,処理継続させます.

[DBG>S 0 N
[DBG>c
[CPU>N
Call SetRelAlarm(CallBackArm, 9000, 5000)
Input Command:
CallBackArm Expire
CallBackArm Expire
CallBackArm Expire

上記の通り,周期アラームコールバック関数が定周期で起動していることがわかります.

アラーム実行をキャンセル

次に,周期起動しているアラームをキャンセルさせるために,シリアルで「h」コマンドを入力します.

q
[NEXT> pc=0x6f34 prc_support.S 1216
[DBG>S 0 h
[DBG>c
[CPU>h
Call CancelAlarm(CallBackArm)
Input Command:

上記のとおり,「Call CancelAlarm(CallBackArm)」というメッセージ出力後,周期起動していたアラームコールバック関数は実行されなくなりました.

Task1→Task9 を起動する

次に,シリアル入力で"1a"を入力すると,Task1が起動され,その後Task9が起動します.
その流れをデバッグしてみましょう.

Task1 を起動する

まずは,q コマンドを実行し,Task1 と Task9 にブレークポイントを設定します.

q
[NEXT> pc=0x6f24 prc_support.S 1216
[DBG>b TaskMainTask1
break TaskMainTask1 0x1114
[DBG>b TaskMainTask9
break TaskMainTask9 0x3e90

さらに,シリアル入力で"1a"をインプット※し,実行継続します.
※「S」は,シリアル入力コマンドです.0は,シリアルチャネル番号です.

[DBG>S 0 1a
[DBG>c

実行継続結果,Task1 メッセージ出力後,Task1 の関数先頭でブレークされることがわかります.
ちなみに,Task1は,コア0で実行されていることが core コマンド結果からわかります.

[CPU>Input Command:
1
Input Command:
a
Call ActivateTask(Task1)

HIT break:0x1114 TaskMainTask1(+0x0)
[NEXT> pc=0x1114 sample1.c 954
core
* Core0 debug mode=TRUE
- Core1 debug mode=TRUE

Task9 を起動する

さらに実行継続すると,Task1 が Task9 を起動しますので,Task9 の関数先頭でブレークされることがわかります.

[DBG>c
[CPU>
HIT break:0x3e90 TaskMainTask9(+0x0)
[NEXT> pc=0x3e90 sample1.c 1993
core
- Core0 debug mode=TRUE
* Core1 debug mode=TRUE

最後に,lコマンドを実行して,Task9の関数を参照します.
これで,マルチコア環境での基本的なデバッグができることがわかりました.

[DBG>l

関連記事