開発日記

Erlangをダラダラ書きます。

分岐予測

分岐予測

  1. 条件分岐が成立するか不成立化を予測

  2. 分岐先のアドレスを予測

分岐先アドレスの予測

分岐先アドレスキャッシュ(BTAC,BTB)

  • BTACは,分岐先アドレスの予測値を与えてくれる

  • 命令アドレスをインデックスとしてアクセスするキャッシュ

  • 命令が条件分岐命令である & 条件分岐がTaken → 分岐先アドレスを記録

  • 分岐予測と並行してBTACにアクセスし,分岐命令の実行までに,得たアドレスを次の命令アドレスとして命令フェッチを開始

注意①:レジスタの内容を分岐先アドレスとする場合は,分岐先は可変.しかし一般的にプログラム中の条件分岐命令の分岐先は命令アドレスからのオフセットで記述されている.よってBTACを書き換える必要性はない.覚えておこう!

注意②:BTACのエントリ数には限りがある.キャッシュラインの競合で追い出されることや,注意①のようにレジスタ間接分岐(レジスタに格納された値が分岐先となる)の場合は分岐先アドレスを書き換える必要がある.覚えておこう!

注意③:BTACにアクセスすると常に何らかのアドレスが出る.タグマッチを行わないと無駄な命令フェッチが多くなる.条件分岐命令アドレスの上位24ビットをタグとしてBTACに記録しておき,タグマッチを行なってヒットした場合のみ命令フェッチに進むようにするケースが多い.また,上位アドレス全てを用いる必要はない.

注意④:レジスタ間接分岐の場合は,レジスタを読まないと分岐先のアドレスがわからないので,デコード時点では分岐予測は難しい.レジスタ間接分岐が使われる典型的なケースは,関数呼び出し.関数の呼び出し時に特定のアドレス(rd)にCall命令の次の命令を記録しておくケース.(一般的には,レジスタ間接ではなく関数呼び出し時の戻りアドレスはスタックにプッシュして記録することが多い.)

参考文献

Verilog 注意点

always文

  • always文の中で代入する際は,ノンブロッキング代入<=を使用する.

  • always文では,代入する変数はregとして宣言する必要がある.

assign文

  • assign文は,いつでも左辺と右辺が同じであることを記述している.

数値

  • 幅を指定していない10進数の数値のビット幅は,暗黙で32bitになる.

ビット論理演算

  • NAND ... ~&

  • NOR ... ~|

シフト演算

  • 算術論理シフト>>>は,signedな変数にのみ適用可能.

演算の優先順位

  • ==は,&より上.

連結

  • {a, b}

  • 5{a[0]} ... a[0]が5個並ぶ

function文

  • 引数がない関数は作れない

組み合わせ回路

  • 全パターンを記述する.(じゃないと無駄なラッチができる)

  • elseやdefaultを必ず書く.

Verilog function文 注意点

function文の入力信号

function文は入力信号を省略した記述が可能だが,そこには落とし穴がある.

下記の2項目に注意

  • function文で入力信号として定義していない信号を使用すると,モジュール内にある同じ名前の信号を参照する.

  • function文内の評価はfunction文で入力信号として定義した信号のいずれかに変化があった場合に限られる.

→ もし入力信号として定義しなかった信号に変化があってもfunction文の評価は行われない...

例えば,マルチプレクサを定義した時,「選択信号しかfunction文に渡さない」という定義の仕方をすると,入力信号の変化は無視されてしまう. 入力信号の変化も見てほしい!

悪い例

input [1:0] sel;
input [3:0] input0, input1, input2, input3;
output [3:0] out;

function [3:0] mux;
    input [1:0] s;
    begin
        case(s)
            0                    :  mux  =  input0;
            1                    :  mux  =  input1;
            2                    :  mux  =  input2;
            3                    :  mux  =  input3;
            default              :  mux  =  4'd0;
        endcase
    end
endfunction

assign out = mux(sel);

良い例

input [1:0] sel;
input [3:0] input0, input1, input2, input3;
output [3:0] out;

function [3:0] mux;
    input [1:0] s;
 input [3:0] in0, in1, in2, in3;
    begin
        case(s)
            0                    :  mux  =  in0;
            1                    :  mux  =  in1;
            2                    :  mux  =  in2;
            3                    :  mux  =  in3;
            default              :  mux  =  4'd0;
        endcase
    end
endfunction

assign out = mux(sel, input0, input1, input2, input3);

今回の教訓: 出力に関係する信号は全て定義しよう!

System verilog 文法メモ

前置き

  • どの部分が並列に実行されているか(always)

  • どの部分は常に繋がっているか(alwaysの外)

  • 値はどのように初期化するか(initial)

  • C言語でいう変数はどうやって作るか(regとかlogic,幅を忘れずに)

  • 計算で意識することは何か(幅を合わせて計算すること)

  • デバッグはどうする(テストベンチとシミュレータ(vivado, verilator ...))

  • テストベンチはどう書く(timescale, $display, #時間)

  • 数字の指定は(ビット数,クウォーテーション,フォーマット,値)

  • vivadoのシミュレータは?(xvlog, xelob, xsim)

yuyargon.hatenablog.com

文法

reg
  • regはクロックと同期して値が変化

  • regはinitialで初期化(for文でも可能,ただし同時に実行される)

  • regはalways, function, taskなどで代入処理し値を更新可能

  • regは代入は <=

  • regは定義の時は値を指定しない

  • regは1クロックにつき1回しか代入できない(並列処理のため)

  • regはalways文の場合,読み出しが書き込みより先に起こる

  • regは多次元配列も可能

wire
  • wireは常に値が変化(するかも)

  • wireは一度定義(配線)したら変更することはない

  • wireはmoduleの入出力に関しては,assignで配線を指定可能

  • wireは定義(配線)は =

  • wireは一文で必ず定義(三項演算子多用)

logic
  • logicはwireにもregにもなれる
parameter, localparam
  • parameterとlocalparamはパラメータ(定数)設定に用いる

  • parameterは外部のmoduleからも参照可能(localparamは参照不可)

  • parameterはモジュール間で受け渡し可能(#(...))

struct
  • structは構造体を作成する(typedef struct { } 名前;)

  • structは . でメンバ変数にアクセス可能

module
  • moduleはポート宣言が必要

  • moduleはparameterの設定も可能

task
  • taskは複数の処理をまとめて記述できる(always内で使用可能)

  • taskは引数を指定可能

  • taskはoutput(返り値)は指定できない

function
  • functionは複数の処理をまとめて記述できる(always内で使用可能)

  • functionは引数を指定可能

  • functionはoutput(返り値)を指定可能

  • functionは中でtaskを呼び出しは不可

演算
  • 演算はビット長を常に意識

  • 演算はビット演算などを行うときは桁数に余裕を持たせる(繰り上がり注意)

  • 括弧を常に使用(優先順位を間違えないように)

符号あり
  • 符号ありは$signedを使用
算術右シフト
  • 算術右シフトは$signedと>>>を組み合わせて使用

Verilogのシミュレーション(Vivado),メモ

コマンド

  • xvlog

  • xelab

  • xsim

GUI

xsimでguiオプションをつけて表示.

  1. 波形を選択

  2. 右クリックからAdd to Wave Window

  3. ドラッグ&ドロップで他にも信号を追加

  4. Run All でシミュレーション実行

  5. 波形確認

  6. 信号追加

  7. Restart から Run All

  8. 波形を右クリック,Radixから16進数を変更できる.

Chisel FPGA メモリ

Memクラス

val mem = Mem(2048, UInt(8.W))
  • 同期書き込み,非同期読み出しを持つメモリ生成

  • FPGAでは直接利用できない

  • フリップフロップから構築される(分散RAM??)

SyncReadMemクラス

val mem = SyncReadMem(2048, UInt(8.W))
  • 同期書き込み,同期読み出しを持つメモリ生成

  • おそらくBRAMを使用する

  • FPGAは通常同期メモリをサポートしている.同期メモリはアドレスを入力してから,1クロック後に読み出しデータを利用できる.

  • 同じクロックサイクルで新しい値が読み出されたのと同じアドレスに書き込まれた場合,Chiselは読み出しデータは未定義(FPGAに依存)

  • 新しく書き込んだ値を読み出したい場合は,アドレスが等しいことを条件に書き込みデータを送信する「フォワード回路(転送回路)」を構築すれば良い.

val forwardReg = RegNext(wrAddr === rdAddr && wen) // true or false

rdData := Mux(forwardReg, ..., ...)

Regクラス

  • メモリをレジスタの集まりとして構築

  • ハードウェア資源をたくさん使うので非推奨 -> SRAMで構築したい

Chisel, Scala メモ

val フィールド名: 型 = 値の式

初期化後に二度と代入できないフィールド ... val

再代入できるフィールド ... var

クラスの外部に公開したくない ... private

クラスのメンバーにはフィールドとメソッド

def 関数名(引数): 戻り型 = {}

UInt という戻りの型は,他の言語でのvoid,意味のある値を返さないことを宣言

引数を取らず値を返すだけで,値の変更や画面への表示のような副作用を持たないメソッドは,括弧を省略する習慣

引数を受け取らないコンストラクタの場合は,new 型名()の括弧は省略する習慣

scalaの関数はreturn文を省略できる(最後に計算された文の結果を返す)

文の区切りのセミコロンは,文の終了であることが明確なら省略可能

Scalaはstaticメンバが存在しない-> シングルトンオブジェクト

シングルトンオブジェクトはobjectで宣言.classにも同じ名前があるならコンパニオンオブジェクトと呼ばれる

こめんとは// // / */

Scalaのプログラムはmainメソッドを持つシングルトンオブジェクトから開始する

sbt "runMain オブジェクト名"で実行

シングルトンオブジェクトのmainメソッドは,Scala組み込みのAppトレイから継承することで代用可能

import chisel3._ でchjiselの基本のパッケージをインポート

Chiselでは,ハードウェアをModuleを継承したクラスで宣言

Moduleを継承したクラスは,valで定義されたioとう名称の入出力ポートフィールドを持つ必要がある

Chiselではループ処理の一時変数にはvarを使えるが,ハードウェアの回路に変換される部分の変数はvalである必要がある.

val io = IO(new Bundle {})

applyメソッドのようにインスタンス生成用のメソッドをファクトリーメソッドと呼ぶ

ファクトリーメソッドを持つ必シングルトンオブジェクトはファクトリーオブジェクトと呼ぶ

applyメソッドの記述は省略できる

Chiselではtrueの値が割り当てられると,そのポートの電位が高くなり電流が流れる

Chiselでの論理演算はビット演算子で行う.NOT, AND, OR, XOR

ベクトル可(配列)はvecオブジェクトを使用する

Vec(数, T)

ベクトルの要素のアクセスは()

Scalaでは,すべての演算はメソッドの呼び出し

Scalaでは,整数や配列すべてがメソッドを持つオブジェクト

ChiselはScalaの言語内のDSL

Scalaではすべてのメソッドが中置演算子として書ける

ChiselではtrueとfalseはBool型

Chiselで符号なし整数はUInt, 符号あり整数はSInt

Scalaではtrue,falseはBoolean型

Scalaでは整数値型はByte, Short, Int, Long, Char, 数値型はFloat, Double

2.U ... 符号なしの2ビット幅の2 ... ビット幅はその数値を表現できる最小のビット幅になる

Chiselのパッケージをインポートすることで,.U とか .Bとかscalaの値を変換できる

整数を書くときにビット幅を指定したい場合は,UやSメソッドの引数にビット幅を指定する

4ビットをニブルと呼ぶ

Chisel独自の等値演算子,===, =/=

Scalaは==, !=

下記はリダクション演算子(縮退演算子

.orR ... どれかのビットが1ならtrue

.andR ... すべてのビットが1ならtrue

.xorR ... すべてのビットでxorの結果

ビット演算の組み合わせでマルチプレクサを実現

定義したハードウェアモジュールを使うときは,Module(new モジュール名())

他のモジュールを内部に含み,最終的にFPGAチップ外部と接続するモジュールのことをトップモジュールと呼ぶ

モジュールを使うときは,同一のパッケージで定義する必要がある.もしくは他のパッケージをインポートしてくるか

Scalaではファイルの切り替えには頭を使うから悪であるという思想

モジュールの関数化 ... objectを作って,applyメソッドに関数の記述

ChiselのMuxはプログラムのif文と同じ順番,一般に販売されているチップのマルチプレクサと違う

ビット連接はCatオブジェクト(chisel3.util._)

ChiselではMuxCaseオブジェクトは,switch, case文の役割を果たす

Muxcase(デフォルト, Seq( 条件 -> 結果, 条件 -> 結果, ...))

タプルはscalaのデータ型の一つで,Seq, Listと異なり異なる型のデータ型を保存できる

タプルで2つの関係のときは ->

7segはアノードで場所を選択肢(Highの電位),カソードで数値を表現(Lowの電位)

1ビットの加算機...半加算器(真理値表を合計とcarry outに分けて考える)

下からの繰り上がりの桁を入力の持つ加算器を全加算器と呼ぶ

全加算器を複数並べれば,複数ビットの加算器ができる

Scalaのfor式... for (要素 <- コレクション フィルター) ... コレクションには0 until 4とか0 to 4とかSeqとか

Seq.fillメソッド ... Seq.fill(4) { "foo" } ... List{foo, foo, foo, foo}

VecInitは,Seqのインスタンスを引数にしてその値で初期化する.

モジュールの中で一時的な接続処理をしたいときは,Wireオブジェクトを使う.

UIntの型は,ビット単位での読み取りは可能だが,ビット単位での割当ができない -> VecのWireを生成してからasUIntで型変換して代入するのがいい処理

例:val tmp = Wire(Vec(4, UInt(4.W))); io.tmp := tmp.asUInt;

.Uは定数の変換,.asUIntは変数の変換推奨

コンストラクタに引数を渡せる(クラスの引数)...基本コンストラク

def this(...)のコンストラクタを補助コンストラク

Scalaの型とChiselの型の使い分け ... 入出力ポートの最終的にハードウェアに流れる信号になるものはUInt型,一時的な変数やビット幅などはScala

2進数の世界では,負の数を表現するのに2の補数表現を使う

01111..111 ... 最大値

10000..000 ... 最小値

1111...1111 ... マイナス1

10000000 ... 11111111, 00000000, ... 01111111

-232, ..., -1, 0, ... 232-1

正の数から負の数 ... 全部のビットのNOT演算をして1を足す

比較演算は組み合わせ !(< | ==) ... >

シフト演算はマルチプレクサで実現

乗算器 ... 筆算の考え方(掛ける数の各ビットのANDをとってから,その結果のビットをずらしながら足し算していく)

for式は基本的には値を返さないが,{}の前にyieldをつけるとコレクションを返す

算術演算の結果のビット幅は自動的に調整される.(代入先のビット幅が指定されていたら,上位のビットは切り捨てられる)

メソッドの先頭文字で優先順位が決まる

算術演算や論理演算を行うモジュールをALUという

パターンを使った定義 ... val (.. , ..) = (.., ..)

信号がループしている組み合わせ回路の合成は禁止されている.

SRラッチ ... resetとsetがfalseで保持,resetとsetがtrueで出力が変になる

--no-check-comb-loops

Dラッチ ... SRラッチを組み合わせて作成

Dラッチ ... ボタンを押しているときのスイッチの値を記憶

Dフリップフロップ .... Dラッチは値を覚えるときにしんごうをへんかさせてはいけない. ... Dラッチを2つよういしてクロック信号がLowからHighに立ち上がる瞬間の値を覚える記憶するようにする

フリップフロップは他にもTフリップフロップや,JKフリップフロップがある

リセット機能付きフリップフロップ ... 電源投入直後は,いつも決まった値にしておきたい

くろっくにあわせてりせっとされる同期式リセット

くろっくにかんけいなくリセットされる非同期式リセット

Chiselでは同期式リセット

CPU Resetボタンはボタンの片方がグランドに接続されている.そのためボタンを押すとポート側からグランドに電流が流れて,ポートはLowになる.CPU Resetのポートはボタンを押していない状態でHigh, 押している状態でLowってこと.

reset 信号は反転させて入力する(負論理だから)

フリップフロップのような順序回路を挟んだループはおっけい(出力がクロックごとにしかきりかわらないから)

フリップフロップを複数まとめたものをレジスタ

Reg(リセット機能なし)

RegInit(リセット機能つきレジスタ,初期化できる)

RegNext(リセット,入力指定)... 初期化して,入力信号の値を保持して出力し続ける.もちろんクロックの立ち上がりで入力信号によって出力をかえる.

RegEnable(リセット,入力指定,いネーブル付き)... enable信号がtrueのときに入力信号の値を保持して出力し続ける.

CPU reset ボタンにこだわらないなら,他のボタンをリセットに割り当てて,そのまま正論理で使用できる.

レジスタの変数が右辺にある場合,クロックの立ち上がりごとに変化する

OutputやWire等の変数が右辺にある場合,右辺の変数が変わったら即座に左辺の変数も変化する

couter := counter + 1.U はクロックごとにしか1増えていく

when で条件指定

.otherwise でそれ以外

chisel の Counterモジュール ... Counter(true.B, 1000000000) ... 100Mクロックカウントごとに(カウント数,true.B)を返す(つまり100MHzでうごく回路だったら,1秒に一回trueを返す)

バウンス,チャタリング(金属同士の接触でバウンドしてるやつ)

時計もストップウォッチもキッチンタイマーもできる

イベントごとにステートを変えて回路の動作をかえるものをステートマシンという

デジタル回路は状態の数は有限,だからFSM, 有限状態機械とよばれる

ステートマシンの中で複雑なもの,究極系がCPU

ステートマシンの中でUMLとかくときは,管理するクラスと制御するクラスをかんがえる

MVCデザインパターンにのっとって作ったりする(状態を管理Model, 表示を管理View,イベント管理Control)

状態の列挙はEnum (UIntを返す)

Scalaでは:: でListを生成,リストの末尾にはNilをつける

Chiselではswitch() is()

Scalaではmatch()

複数のビットのやり取りは,VecやUIntをつかってふくすうのしんごうせんを並列に処理.パラレル通信

もしくは,一本の信号線で連続的にシリアルに複数のクロックを使ってそうしんもできる.シリアル通信

シリアルパラレル変換(直列並列変換器)

クロックを使って同期して通信するのを同期式通信

シリアルからパラレルへの変換はシフトレジスタで実現

クロックの立ち上がりで値を取り込むが,取り込む信号が変化の途中のとき,レジスタの出力はメタステーブル状態(HighとLowの中間の不安定な状態)になる

最終的には0,1になるが,どちらになるかわからない

レジスタを二段重ねると回避できたりする(2つのレジスタで入力を受け取る)

キーボードのスキャンコード(ASCIIではない)

USBのパケットをシリアルデータに変換するUSB-UARTブリッジがFPGAボードにはだいたいある

送信用 TXD

受信用 RXD

送信と受信が同時にできるから全二重通信

UARTでクロック信号はない.信号のデータに同期するための信号を追加して,同期を釣る

この同期の仕方を,調歩同期式(非同期式)という

Decoupled ... データにvalidとreadyを付加する

Chiselのバルク接続 <>

ChiselでBlackBoxクラスを継承したクラスをつくると,Verilogで作成してあるIPを利用できる