CPU 実験でハマったところ

2015-12-13 | [Programming]

CPU 実験 Advent Calendar 2015 の記事です. コア係をしていてハマったところなどについて書きます.

VHDL

適当に VHDL を書いているとラッチが発生することがあります.(例えば,組み合わせ回路において,if 文などで代入が行われないコントロールパスが存在する場合) ラッチが発生したからといって論理的に正しくない結果になることは少ないですが,ラッチはタイミング解析において想定と異なる結果を引き起こすことがよくあります. (どうやら,ラッチはフリップフロップと同様,クリティカルパスを途中で打ち切る作用を持つらしい?) 大抵のラッチは実際には不要なもの (すべてのコントロールパスにおいて,何らかの値が代入されるようにすれば消せます) なので,よほどのことがない限り ラッチは撲滅 しましょう. 幸い,xst はラッチが発生したときに warning にその旨表示してくれます.

(ラッチが発生する例)

process (foo, bar, hoge)
begin
  if foo = '1' then
    bar <= hoge + "00001";
  end if;
end process;

もちろん,クロック同期の process 文の中では代入が起きないコントロールパスが存在しても大丈夫です.

たまに「1 クロックで動くのにクリティカルパスが数 ns の fadd ができる」ことがありますが,その場合は大抵ラッチができていてタイミング解析が狂っているので (1 クロックで動作する fadd はかなりクリティカルパスが長くなる傾向にあります),とりあえず warning で「latch」などで検索してラッチができていないかなど確かめましょう.

また,process 文の variable は便利ですが,variable の値は process 文の処理を始めるたびに初期化されるのではなく,process 文が終了した後も次の開始の際に引き継がれ ます. この性質は,process 文の中で variable を最初に使う前に初期化をしていないと (ここでは,variable 宣言の初期化子は関係ありません) 問題になります.すると想定外の動作をしたり,クリティカルパスが異常に伸びたりします. さらに,その variable に代入が行われないコントロールパスが存在すると,上で述べた「ラッチの発生」まで起きてしまいます.すると本当は長いクリティカルパスが検出されず,シミュレータで動くが実機で動かない回路の出来上がりです. 最初この variable の性質をよく理解していなかったために大変な目に遭いました.なので,variable は必ず初期化 しましょう.

なお,VHDL を書く上で twoproc というデザインパターンがありますが,これを使えば基本ラッチはできませんし variable の初期化忘れもだいぶ減らせると思うので是非使いましょう.

I/O

ハードウェア実験で I/O を実装しますが,このとき基板との相性があるのか,入力信号を一度フリップフロップに受けなくても動くことがあります. が,(ここ にも書いてある通り) 入力信号をそのまま使うとかなり動作が不安定になるので (実際夏学期のハードウェア実験ではこれにだいぶ苦しめられた),一度フリップフロップに受ける ようにしましょう. どうせ I/O は 1 バイトに数千クロックかかるのでここで 1 クロック節約する意味はまったくありません.

あと,出力バッファは別に付けなくても動きますが (I/O がビジーなら CPU 全体をストールさせる) 付けたほうがよいです. というのも,課題プログラムでは「出力たくさん」「計算たくさん」が交互にやってくる感じになっているので,バッファを付けないと「出力たくさん」部分で猛烈にストールしてしまうためです. (実際,出力バッファを付けるだけで平気で 10 秒縮まりました)

アウトオブオーダー実行

動的スケジューリングのアウトオブオーダー実行を実装する場合,Tomasulo のアルゴリズムを実装することが多いようです. その際,リオーダーバッファ (ROB) は できるだけ付けましょう.(性能向上を目的に実装する場合,普通付けるとは思いますが)

付けなくても普通に動かすことはできますが,

なので,わざわざ ROB なしで実装するメリットは少ないです.

デバッグ

コアを書いていると「小さいプログラムではなんともないが課題のレイトレを動かしたときだけバグる」ことがよく起きてつらい気分になります. こうなったときは,「問題を引き起こす小さいプログラム」を探すのも手ですが,いっそ「レイトレを回路シミュレータ上で 20ms くらい走らせる」とかやると問題を見つけられることがあります. 特に,アウトオブオーダー実行になると,発生条件が絶妙なバグが原因で命令実行が完全にストップしてしまう,などがよく起き,この場合はシミュレータを使うと問題の発生箇所をかなり容易に突き止めることができます.

ただし,前提として「シミュレータ上で I/O が高速」「大きな入力プログラムでもシミュレータ上ですぐにシミュレートできる」ことが必要になります. そのため,コア本体の (論理合成時にトップレベルに来る) コンポーネントにおいて,generic などを使って「シミュレータ上用のパラメータ」を設定できるようにするとよいです. また,プログラムローダーを用いて実行直前に I/O 経由でプログラムを送信可能とする場合も多いと思いますが,その場合でもシミュレート時にはプログラムローダーを経由せず直接プログラムを書き込んだ状態にできるとよいです.