1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Shrike-LiteでAtCoder問題を解く(42):ABC472B 後編 - 実機エラーをデバッグする

1
Posted at

はじめに

前編ではABC472BをShrike-Liteへ実装し、実機テストを実施しました。

ところが実機テストでこけました。

今回は実機動作不良の原因追及と対策を行います。


最優先のFAILはN=3

最初に実機FAILした3件のテストケースは、

Case N bytes / bursts Result Expected
best_at_first_cut 3 10 / 1 7573991 98
burst_boundary_259 86 259 / 2 1737 1001
deterministic_random_100 100 301 / 2 76912 11636

です。

burst_boundary_259だけならSPI通信の256byte境界を疑えます。

deterministic_random_100だけならN=100固有の問題かもしれません。

しかしbest_at_first_cut=[100,1,1]は、

N=3
10 byte
1 burst

です。

値も小さく17bitに十分収まります。

そこでこのcaseを最優先のデバッグ対象にしました。

正常なら、

total = 102
cut 0:
left  = 100
right = 2
diff  = 98
cut 1:
left  = 101
right = 1
diff  = 100
answer = 98

です。


まずSPI速度を疑う

37.5MHzへ内部clockを落としたことで、4MHz SPIとのタイミング余裕が減った可能性を最初に考えました。

まず最優先case [100,1,1] を使って、SPI通信クロックを変えた次の条件を比較します。

B1: input 4 MHz / answer 4 MHz
B2: input 2 MHz / answer 4 MHz
B3: input 4 MHz / answer 2 MHz
B4: input 2 MHz / answer 2 MHz

さらに、前回のtransaction状態が残っている可能性も疑い、

A1: 最初だけresetして20回連続
A2: 毎回resetして20回

と、reset条件も比較試験に追加します。


Thonnyの実機試験も自動化する

このDebugテストを人間がRunボタンを押しながら繰り返すのは面倒です。

そこでdebug runnerを作り、A1 / A2 / B1~B4 / 代表caseを1回の実行で走査するようにしました。

さらにCodexからThonnyを操作し、

script open
    ↓
F5
    ↓
実機試験
    ↓
Shell全文copy
    ↓
log保存

まで自動化しました。

結果として、1回のrunnerで112件の実機試験を取得できました。


2MHzでも直らない

結果は、

A1: 0 PASS / 20 FAIL
A2: 0 PASS / 20 FAIL
B1: 0 PASS / 10 FAIL
B2: 0 PASS / 10 FAIL
B3: 0 PASS / 10 FAIL
B4: 0 PASS / 10 FAIL

でした。
全体では、

PASS=20
FAIL=92
TOTAL=112
RESULT=FAIL

です。

入力SPIだけ2MHzでもダメ。回答だけ2MHzでもダメ。両方2MHzでもダメ。resetしてもダメ。

raw MISOの先頭dummyは112件すべて0x00で、代表PASSケースは同じ条件でも正常でした。

これで、単純なSPI速度margin、回答4byteの常時alignmentずれ、256byte境界だけの問題、単純な前transaction残留はかなり疑いが薄くなりました。


DistRAMの中身を直接見る

外側から条件を変えても直らないので、内部を観測します。

最初に知りたいのは、distributed RAMとFFの保持データが

RAM[0] = 100
RAM[1] = 1
RAM[2] = 1
total  = 102

になっているかです。

debug用RTLでは、入力3個をshadow FFにも保存し、DistRAMをreadbackしてtotal_sumと一緒に返しました。

[100,1,1]をresetなし10回、毎回reset 10回の合計20回実行すると、

N=3
COUNT=3
BYTE_INDEX=0
SHADOW=100,1,1
RAM=100,1,1
TOTAL=102

を全20回で確認できました。

つまり、少なくともこの最優先caseでは、入力組立は正しい。DistRAM writeも正しい。格納内容も正しい。total_sumも正しい。

入力受信時点では設計通りの動作をしています。


CALC内部を観測したいが、デバッグ回路が入らない

次はさらに内部保持値、例えばCALCが実際に読んだ値、prefix、diff、answerなどをtelemetryで返そうとしました。

ところが今回のShrike-Lite実装は、CLB使用率が95%になるほど、リソースを目いっぱい使っています。

観測回路を増やしてPNRをかけると、

FATAL ERROR: The design cannot fit into the current geometry.
PnR failed

とエラーが出て、回路を追加できません。

デバッグ回路を載せるスペースがない。

これはこれで狭小FPGAらしい話です。


1clock待ってみる

distributed RAM内の主なデータが正しいことは確認ができています。

なので、次は、そのデータが処理される、最終集計回路を疑う事にします。

デバッグ回路の追加は厳しい状況なので、犯人を直接名指しするのは難しそうです。

そこでまずは状況証拠を探します。

最初は、データ読み出しから計算開始までの間に、1クロックのWAITを入れてみて、この区間で何かの問題があるかを見てみます。

CALC_READ
    ↓
CALC_WAIT
    ↓
CALC_EVALUATE

変更はこれだけです。


一発で112/112 PASS

WAIT付きdebug bitstreamを実機へ入れると、

PASS=112
FAIL=0
TOTAL=112

になりました。最優先caseも、

RAW_RX=00:00:00:62
answer=98
PASS

です。

あら、直っちゃった。

さらに、distributed RAMとtotal値を観測する回路を外し、最終productionとして別にSynth / PNRしたbitstreamでも112/112 PASSしました。

異なる配置でも同じく良好な結果になったため、単に「たまたま良い配置になった」だけではなさそうです。

どうやらこの区間で不良が発生していたようです。


原因はDistRAM readからCALCまでの物理境界

今回の実験から、問題がDistRAM readからCALCまでの境界にあることはかなり強く確認できました。

一方で、その物理的な内訳がRAM primitive側の実効read latencyなのか、RAM出力から後段CALCまでのsettle marginなのかは、今回の観測だけでは分離できていません。

つまり、不良が生じる境界と必要な修正はかなり明確になりましたが、その境界の内部で何が支配的だったかまでは断定する証拠は手に入っていません。


最終productionは3clock / candidate

最終版は、メモリ読み出しと集計計算開始の間に1クロックのWAITを挟みます。

CALC_READ
    ↓
CALC_WAIT
    ↓
CALC_EVALUATE

です。
1候補3clockなので、

3 × (N - 1)

最大N=100では、

3 × 99
= 297 clocks

です。内部clockは37.5MHzなので、

297 / 37.5MHz
= 7.92 us

です。

MicroPython側の待ち時間は元から10usなので変更不要ですね。


Icarusも524/524 PASS

最終productionでIcarusを再実行すると、

SUMMARY PASS cases=524 random=512 failures=0 max_calc_clocks=297

でした。全524ケースPASSです。

CALC clock数だけが198から297へ増えました。


最終Synth / PNR

distributed RAM推論も維持されています。

RAM64X1D ×34
Type=M 34/40

最終productionは、

項目 結果
CLB LUT5 737 / 1120
distributed-memory LUT5 272
FF 190
CLB 131 / 140
BRAM 0 / 8
WNS +2.648 ns
TNS 0
Achievable Frequency 41.635 MHz

でした。

面白いことに、FSMへ1状態増やしたのにCLBは134から131へ少し減りました。配置や論理最適化の結果なので、state数と面積は単純には対応しません。

Timing余裕も一時完成版より大きくなっています。


bitstreamから実機まで自動化する

今回の副産物として、開発ループもかなり自動化できました。

ForgeFPGA Workshop
    ↓
Synth / PNR / bitstream生成
    ↓
E:\abc472b.binへcopy + SHA-256確認
    ↓
ThonnyでF5
    ↓
shrike.flash()
    ↓
実機試験
    ↓
Shellログ回収

までCodexが人間操作なしで実行しています。


最終実機試験

最終productionで同じ112件runnerを実行しました。

WORK5_DONE PLAN=ALL PASS=112 FAIL=0 TOTAL=112 RESULT=PASS

resetなし20回、毎回reset20回、input / answer SPIの4/4、2/4、4/2、2/2MHz、代表FAIL / PASSケースをすべてPASSしていました。


実機ログも残す

今回の反省から、最終実機出力そのものも一次資料として保存しました。

hardware_logs/WORK7_SEQ01.log
hardware_logs/hardware_log_abc472b_final.log

GitHubに上げておきますので、読者がお手元のRun結果と比較するときにご利用ください。


最大ケースの処理時間

最後にShrike-Liteとしての処理時間を測りました。

入力は、

N = 100
L_i = 100000 × 100個

として、20回測定し、測定対象には、

  • RP2040側の入力チェック
  • N / L_iの301byte serialize
  • SPI入力転送
  • 10us CALC待ち
  • 24bit answer組立
  • 4byte回答転送

を含めた時間を測定しています。

bitstream flash、FPGA reset、SPI初期化、printは測定外です。

結果は、

BENCHMARK N=100 RUNS=20
MIN_US=4180
AVG_US=4223.1
MAX_US=4728
RESULT=PASS

でした。

平均約4.22msです。

ACがもらえそうですね。

FPGA内部のCALC処理時間は7.92usなので、全体の0.2%未満。今回も処理時間の大部分はRP2040側の処理とSPI通信です。


おまけ:普通にAtCoderへ提出するなら

最後に、いつものAtCoder提出用コードです。

module main;

    integer N;
    integer L [0:99];

    integer i;
    integer total;
    integer left;
    integer right;
    integer diff;
    integer best;
    integer ret;

    initial begin
        ret = $fscanf(32'h8000_0000, "%d", N);

        total = 0;

        for (i = 0; i < N; i = i + 1) begin
            ret = $fscanf(32'h8000_0000, "%d", L[i]);
            total = total + L[i];
        end

        left = 0;
        best = 32'h7fffffff;

        for (i = 0; i < N - 1; i = i + 1) begin
            left = left + L[i];
            right = total - left;

            if (left >= right)
                diff = left - right;
            else
                diff = right - left;

            if (diff < best)
                best = diff;
        end

        $display("%0d", best);
    end

endmodule

こちらなら、distributed RAMもSPIもTimingも気にする必要はありません😂

競技プログラミングとしてはこちらが正解ですね。


まとめ

今回の教訓は、SIMでPASSしても、最後は実機を見る です。

小さなFPGAを限界近くまで使うと、シミュレーションやTiming Reportだけでは終わらないことがあります。

ABC472Bは、そのあたりまで含めてかなりShrike-Liteらしい題材になりました。


前回:
Shrike-LiteでAtCoder問題を解く(41):ABC472B 前編 - DistRAMに100個の長さを覚える

次回:
Interlude: ABC473を見てみるの予定

コード全文と実装資料:
第42回コード全文と実装資料

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?