FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages

"Creating an LLVM Backend for the Cpu0 Architecture"をやってみる(3. BackEndの追加)

f:id:msyksphinz:20181123225150p:plain

LLVMにCpu0アーキテクチャを追加するチュートリアル。次はBackEndの追加を行う。参考にしたのは以下。

  • Tutorial: Creating an LLVM Backend for the Cpu0 Architecture : Backend structure

Backend structure — Tutorial: Creating an LLVM Backend for the Cpu0 Architecture

チュートリアルLLVMのバージョンはどうも古いようで、きちんとパッチが当たらない。 いろいろ改造したので、GitHubに7.0.0に対応したものを構築した。

github.com

まずは簡単にバックエンドを追加。ビルドすると以下のような結果になった。Chapter-3_0の部分を追加した。 AssertionがFailとなる。

$ ./bin/llc -march=cpu0 -mcpu=help
Available CPUs for this target:

  cpu032I  - Select the cpu032I processor.
  cpu032II - Select the cpu032II processor.

Available features for this target:

  ch10_1   - Enable Chapter instructions..
  ch11_1   - Enable Chapter instructions..
  ch11_2   - Enable Chapter instructions..
  ch12_1   - Enable Chapter instructions..
  ch3_1    - Enable Chapter instructions..
...
llc: /home/msyksphinz/work/riscv/llvm-msyksphinz/lib/CodeGen/LLVMTargetMachine.cpp:60: void llvm::LLVMTargetMachine::initAsmInfo(): Assertion `TmpAsmInfo && "MCAsmInfo not initialized. " "Make sure you include the correct TargetSelect.h" "and that InitializeAllTargetMCs() is being invoked!"' failed.
...

次にAssertion Failを直す。Chapter3_2を追加する。

github.com

一点、CPURegSizeの実装が古かったので、いろいろ調べて以下のように変更した。

  • 変更前
  unsigned CPURegSize = Cpu0::CPURegsRegClass.getSize();
  • 変更後
  unsigned CPURegSize = TRI->getRegSizeInBits(Cpu0::CPURegsRegClass) / 8;

変更を実装後にビルドを行うと、Assertion Failは出なくなった。

$ ./bin/llc -march=cpu0 -mcpu=help
Available CPUs for this target:

  cpu032I  - Select the cpu032I processor.
  cpu032II - Select the cpu032II processor.

Available features for this target:

  ch10_1   - Enable Chapter instructions..
  ch11_1   - Enable Chapter instructions..
  ch11_2   - Enable Chapter instructions..
  ch12_1   - Enable Chapter instructions..
  ch3_1    - Enable Chapter instructions..
  ch3_2    - Enable Chapter instructions..
  ch3_3    - Enable Chapter instructions..
  ch3_4    - Enable Chapter instructions..
  ch3_5    - Enable Chapter instructions..
  ch4_1    - Enable Chapter instructions..
  ch4_2    - Enable Chapter instructions..
  ch5_1    - Enable Chapter instructions..
  ch6_1    - Enable Chapter instructions..
  ch7_1    - Enable Chapter instructions..
  ch8_1    - Enable Chapter instructions..
  ch8_2    - Enable Chapter instructions..
  ch9_1    - Enable Chapter instructions..
  ch9_2    - Enable Chapter instructions..
  ch9_3    - Enable Chapter instructions..
  chall    - Enable Chapter instructions..
  cmp      - Enable 'cmp' instructions..
  cpu032I  - Cpu032I ISA Support.
  cpu032II - Cpu032II ISA Support (slt).
  slt      - Enable 'slt' instructions..

Chiselのパラメタライズについて調査(2. Advanced Parameterization Manualの続き)

Chiselについて少しずつ勉強を進めているが、SystemVerilogと同様のことが実現できないと、マスターしたとは言えないだろう。

Verilogでも多く使われるパラメータ化、これをどのようにしてChiselで使用するのか、Rocket-Chipのデザインを見ても良く分からないので調査したい。

このあたりについて非常に細かく記述してある資料 Advanced Parameterization Manual を翻訳して調べてみた。 まずは簡単に翻訳した結果を掲載しておきたい。 今回は後半。前半はこちら

3. 例

パラメータ化には3つの目標がある。

  1. 全ての検索できるパラメータは、トップレベルから参照できる。
  2. 異なるポイントから評価を実行しても、ソースコードは変更されない。
  3. 新しいパラメータを追加してもソースコードの変更は最小限で済む。

これから紹介する例について説明した後、3つの目標のいずれにも違反せずに、望ましいデザインスペースをサポートするもっとも簡単なパラメータ化の方法を示す。例が複雑になるにつれて、現在の高度なパラメータ化のメソッドに到達するまではもっとも単純なパラメータ化の方法も必要だ。

3.1 単純なパラメータ

pict

図4. 幾つかのパラメータを渡す簡単な方法は、コンストラクタの引数に直接渡すことだ。

シンプルなデザインでは、コア特有のパラメータや、キャッシュのためだけのパラメータなどが存在する。もっとも単純なパラメータ化の方法は、全てのパラメータをTileのコンストラクタの引数として渡すことだ。これらの値は、それぞれのコンストラクタを経由してCoreCacheに渡される。

class Tile (val fpu:Boolean, val ic_sets:Int, val ic_ways:Int, val dc_sets:Int, val dc_ways:Int) extends Module { 
  val core = Module(new Core(fpu)) 
  val icache = Module(new Cache(ic_sets,ic_ways) 
  val dcache = Module(new Cache(dc_sets,dc_ways)) 
  ... 
} 
class Core (val fpu:Boolean) {...} 
class Cache(val sets:Int, val ways:Int) extends Module {...}

パラメータを参照するためのソースコードの変更は必要なく、全ての検索可能なパラメータは、Topから参照できる。さらに、新しいパラメータを追加するのは、このパラメータがシンプルであるため、ソースコードの変更は最小限で済む。

3.2 パラメータセットの分離

pict

図5. パラメータセットを分離することによって、パラメータセットをコンフィグレーションオブジェクトにグループ化してコンストラクタの引数に渡すことができる。

次のデザインでは、チップないに異なるコアをインスタンスし、それぞれで異なるパラメータを設定する。上記の単純なソリューションを適用すれば、Tileのコンストラクタに対して、パラメータの数だけ値を設定する必要がある(さらに、2コアからコア数が増えると、さらに設定する項目が多くなる!)

より良い解決章のためには、パラメータのグループをコンフィグレーションオブジェクトに分割することである。例えば、BigCoreに必要なすべてのパラメータをBigCoreConfigcaseクラスに設定し、SmallCoreのパラメータをSmallCoreConfigクラスに設定し、どちらのクラスもCoreConfigクラスの継承として定義する。さらに、キャッシュとTileのコンフィグのためにCacheConfigTileConfigを用意して、コンストラクタに渡す。

abstract class CoreConfig {} 
case class BigCoreConfig(iq_depth:Int, lsq_depth:Int) extends CoreConfig 
case class SmallCoreConfig(fpu:Boolean) extends CoreConfig 
case class CacheConfig(sets:Int, ways:Int) 
case class TileConfig(cc:CoreConfig, icc:CacheConfig, dcc:CacheConfig) 
 
class Tile (val tc:TileConfig) extends Module { 
  val core = tc.cc match { 
    case bcc:BigCoreConfig => Module(new BigCore(tc.bcc)) 
    case scc:SmallCoreConfig => Module(new SmallCore(tc.scc)) 
  } 
  val icache = Module(new Cache(tc.icc) 
  val dcache = Module(new Cache(tc.dcc)) 
  ... 
} 
...

3.3 Location-Independentなパラメータ

pict

図6. Location-Independentなパラメータでは、全てのモジュールはパラメータディレクトリを持ち、パラメータをコピーするか、値を変更して子供のモジュールに渡す。

ネストされたコンフィグレーションオブジェクトが脆弱である微妙な理由は、ネストされたコンフィグレーションオブジェクトがモジュールの改装をエンコードするからである。図6に示す新しいデザインを見ると、BigCoreがIQとLSQを含んでおり、icacheとdcacheがメモリモジュールをインスタンスしている。このメモリモジュールはwidthパラメータを含んでおり、昨日を正しく構成するために、全てのメモリの幅は同一に設定されなければならない。この要求を満たすために、以下のようなコードを記述する:

case class MemConfig(size:Int, banks:Int, width:Int) 
case class CacheConfig(sets:Int, ways:Int, mc:MemConfig) 
case class QueueConfig(depth:Int, mc:MemConfig) 
case class BigCoreConfig(iqc:QueueConfig, lsqc:QueueConfig, mc:MemConfig) 
 
case class TileConfig(cc:CoreConfig, icc:CacheConfig, dcc:CacheConfig) 
 
class Tile (val tc:TileConfig) extends Module { 
  val core = tc.cc match { 
    case bcc:BigCoreConfig => Module(new BigCore(tc.bcc)) 
    case scc:SmallCoreConfig => Module(new SmallCore(tc.scc)) 
  } 
  val icache = Module(new Cache(tc.icc) 
  val dcache = Module(new Cache(tc.dcc)) 
 
  require(tc.dcc.mc.width == tc.icc.mc.width) 
  require(tc.bcc.iqc.mc.width == tc.bcc.lsqc.mc.width) 
  require(tc.dcc.mc.width == tc.bcc.lsqc.mc.width) 
  ... 
} 
...

require構文は非常に脆弱であり、デザイン中の改装の任意の変更によりこれらの構文を書き直す必要がある。このrequire構文を取り除くことは不可能である; この構文は基本的なデザインの要求を満たすために必要である。

コンフィグレーションオブジェクトの問題は、カスタムパラメータ化の解決方法の最初のきおぬ、つまりタイプパラメータのコピー/変更につながる。私たちは、key-value構造(mapもしくは辞書)により、モジュールのパラメータを格納する方法である。

図6. のデザインをパラメタライズするためには、暗黙的にParameterオブジェクトを渡すか、パラメータの変更が必要ならばPartialFunctionをモジュールファクトリに提供するという方法がある。第2章のMyConfig(ChiselConfigの継承)を思い出すと、Chiselコンパイラ--configInstanceフラグを使用してトップレベルのパラメータを指定する:

class DefaultConfig() extends ChiselConfig { 
  val top:World.TopDefs = { 
    (pname,site,here) => pname match { 
      case IQ_depth => 10 
      case LSQ_depth =>10 
      case Ic_sets => 128 
      case Ic_ways => 2 
      case Dc_sets => 512 
      case Dc_ways => 4 
      case Width => 64 
      // since any module querying Width should return 64, the name should NOT be unique to modules 
    } 
  } 
} 
class Tile extends Module { 
  val core = Module(new Core)(params) 
  val ic_sets = params(Ic_sets) 
  val ic_ways = params(Ic_ways) 
  val icache = Module(new Cache, {case Sets => ic_sets; case Ways => ic_ways}) 
  // we can rename Ic_sets to Sets, effectively isolating Cache's query keys from any design hierarchy dependence 
  val dc_sets = params(Dc_sets) 
  val dc_ways = params(Dc_ways) 
  val dcache = Module(new Cache, {case Sets => dc_sets; case Ways => dc_ways}) 
  // similarly we rename Dc_sets to Sets and Dc_ways to Ways 
} 
class Core extends Module { 
  val iqdepth = params(IQ_depth) 
  val iq = Module(new Queue, {case Depth => iqdepth}) 
  val lsqdepth = params(LSQ_depth) 
  val lsq = Module(new Queue, {case Depth => lsqdepth}) 
  ... 
} 
class Queue extends Module { 
  val depth = params(Depth) 
  val mem = Module(new Memory,{case Size => depth}) 
  ... 
} 
class Cache extends Module { 
  val sets = params(Sets) 
  val ways = params(Ways) 
  val mem = Module(new Memory,{case Size => sets*ways}) 
} 
class Memory extends Module { 
  val size = params(Size) 
  val width = params(Width) 
}

3.4 Local-Specific パラメータ

pict

図7. Location-dependentパラメータでは、siteのメカニズムを使用してトップレベルでパラメータをカスタマイズすることができる。

前の節で見たように、パラメータオブジェクトをコピーして変更することは上古府である。ECCパラメータをメモリモジュールに追加したい場合、メモリをどこにインスタンスしたかにより依存することになる。複数の親に対してパラメータを名前変更するようなソースコードを記述することになる(例えば、ECC_icache=>ECC)。

図7.に示したデザインでは、パラメータに対してsiteの機能を使用して、Location-Specificな情報を取得するようにする。そしてLocation-Specificな値を返すようにする。Location-Specificな情報を記述した後、コード変更の必要性が大幅に削減される:

class DefaultConfig() extends ChiselConfig { 
  val top:World.TopDefs = { 
    (pname,site,here) => pname match { 
      case Depth => site(Queue_type) match { 
        case 'iq' => 20 
        case 'lsq' => 10 
      } 
      case Sets => site(Cache_type) match { 
        case 'i' => 128 
        case 'd' => 512 
      } 
      case Ways => site(Cache_type) match { 
        case 'i' => 2 
        case 'd' => 4 
      } 
      case Width => 64 
      // since any module querying Width should return 64, the name should NOT be unique to modules 
      case ECC => site(Location) match { 
        'incore' => false 
        'incache' => true 
      } 
    } 
  } 
} 
class Tile (val params:Parameters) extends Module { 
  val core = Module(new Core,{Location => 'incore'}) 
  // we can give core and its child modules a location identifier 
 
  val cacheparams = params.alter({Location => 'incache'}) 
  // we can give both caches and all their child modules a location identifier 
  val icache = Module(new ICache)(cacheparams) 
  val dcache = Module(new DCache)(cacheparams) 
} 
class Core extends Module { 
  val iq = Module(new IQ) 
  val lsq = Module(new LSQ) 
  ... 
} 
class IQ extends Module { 
  val depth = params(Depth) 
  val mem = Module(new Memory, {Size = depth}) 
  // in some cases, using copy/alter is preferred instead of \code{site} (see Design Heuristics for more details) 
  ... 
} 
class LSQ extends Module { 
  val depth = params(Depth) 
  val mem = Module(new Memory, {Size = depth}) 
  ... 
} 
class ICache extends Module { 
  val sets = params(Sets) 
  val ways = params(Ways) 
  val mem = Module(new Memory,{Size => sets*ways}) 
} 
class DCache extends Module { 
  val sets = params(Sets) 
  val ways = params(Ways) 
  val mem = Module(new Memory, {Size => sets*ways}) 
} 
class Memory extends Module { 
  val size = params(Size) 
  val ecc = params(ECC) 
}

3.5 派生パラメータ(Derivative Parameters)

pict

他のトップレベルパラメータからパラメータを派生させるためには、here機能を使用してパラメータ値を複製する機能を使用できる。

図8では、ROBは常に物理レジスタの数とアーキテクチャレジスタの数の差分の4/3が定義される。これをMyConfig.topで表現するためには、以下のように実装できる:

case object NUM_arch_reg extends Field[Int] 
case object NUM_phy_reg extends Field[Int] 
case object ROB_size extends Field[Int] 
class DefaultConfig() extends ChiselConfig { 
  val top:World.TopDefs = { 
    (pname,site,here) => pname match { 
      case NUM_arch_reg => 32 
      case NUM_phy_reg => 64 
      case ROB_size => 4*(64-32)/3 
  } 
}

しかし、もし後で物理レジスタの数を増やすとなると、ROBサイズの制約を常に思い出して更新する必要がある。この潜在的な間違いを防ぐためには、'here'の機能を使用して同じグループのパラメータの値を取得することができる:

class DefaultConfig() extends ChiselConfig { 
  val top:World.TopDefs = { 
    (pname,site,here) => pname match { 
      case NUM_arch_reg => 32 
      case NUM_phy_reg => 64 
      case ROB_size => 4*(here(NUM_phy_reg) - here(NUM_arch_reg))/3 
  } 
}

3.6 パラメータのリネーミング(Renaming Parameters)

pict

図9. 前の値に従って、パラメータをリネームしたり、コードに従って値を変えるためには、upのメカニズム使ってパラメータオブジェクトの親の値を参照することができる。

図9では、cacheモジュールはsetsパラメータを参照する。しかし、Tileはic_setsdc_setsをパラメータを持っている。パラメータをリネームするためには、親の値と読み込み、子供のパラメータオブジェクトを変更することができる:

class Tile extends Module { 
  val ic_sets = params(Ic_sets) 
  val ic = Module(new Cache,{case Sets => ic_sets}) 
  val dc_sets = params(Ic_sets) 
  val dc = Module(new Cache,{case Sets => dc_sets}) 
    ... 
}

他の方法として、Parameters.alterメソッド中の'up'メカニズムを使用して親のモジュールのParameterオブジェクトを参照することができる:

class Tile extends Module { 
  val ic_params = params.alter( 
    (pname,site,here,up) => pname match { 
      case Sets => up(Ic_sets) 
    } 
  ) 
  val ic = Module(new Cache)(ic_params) 
  ... 
}

一般的に、'up'メカニズムは冗長になるため使用するべきではない。しかし、3つの中心的なメカニズム(up, site, here)にアクセスできるので、全ての変更にParameter.alterメソッドが含まれている可能性があるため、親が子のParameterオブジェクトを大幅に変更している場合に役に立つ。

4. 外部インタフェース

これまで、本ドキュメントはパラメータをトップレベルクラス(ChiselConfig)で変更する方法について説明してきた。しかし実際に複数のC++Verilogのデザインを生成する場合、私たちはこれらのパラメータを手動で変更する必要がある。

デザインの制約(パラメータの範囲、依存、制約)を表現する場合、特定のデザインの実際のインスタンスを有効なデザインスペースの表現から分離する場合に好まれる。

このような理由から、ChiselはKnobと呼ばれるデザインスペースを俯瞰するために特別に作成されたパラメータの概念をベースとした機能が存在する。本章では、Knobとその使用方法、オブジェクトダンプ、パラメータ・Knobへの制約の追加、Chiselコンパイラの2種類の実行モード: --configCollect--configInstanceについて説明する。

4.1 Knob

ジェネレータには固定されたいくつかのパラメータと、生成される特定の設計ポイントを指示するものがある。Knobと呼ばれるこれらのジェネレータレベルのパラメータには、外部プログラムとユーザーが簡単に値を上書きできるように、追加のキーと値のマッピングが存在する。

KnobはChiselConfigのサブクラスのトップ定義にのみインスタンスすることができる。

package example 
class MyConfig extends ChiselConfig { 
  val topDefinitions:World.TopDefs = { 
    (pname,site,here) => pname match { 
      case NTiles => Knob('NTILES') 
      case .... => .... // other non-generator parameters go here 
    } 
  } 
  override val knobValues:Any=>Any = { 
    case 'NTILES' => 1 // generator parameter assignment 
  } 
}

NTilesがtopDefinitions内でマッチした場合、Knob('NTILES')が返される。内部では、ChiselはMyConfig.knobValuesからNTILES探索し、1が返される。2.5節で説明したように、特定のconfigを使ってGeneratorを実行するためにはフラグが必要である:

sbt run ... --configInstance example.MyConfig

新しいデザインで2つのタイルをインスタンスしたい場合: Scalaクラスの継承とknobValuesメソッドのオーバーライトを使用する:

package example 
class MyConfig2 extends MyConfig { 
  override val knobValues:Any=>Any = { 
    case 'NTILES' => 2 // will generate new design with 2 tiles 
  } 
}

どちらのクラスもソースコード中に共存することができる、したがってどちらのデザインもコマンドライン中からインスタンスすることができる。2つのタイルを含んだ新しいデザインは、以下のようにして呼び出すことができる:

sbt run ... --configInstance example.MyConfig2

4.2 ダンプ

Chiselよりも下流では、他のツールが特定のparameter/Knobアサインの情報が必要になる場合がある。そうであれば、Knob/valueをダンプオブジェクトに渡すことにより、名前とその値をファイルに書き出し、そしてKnob/valueを返す:

package example 
class MyConfig extends ChiselConfig { 
  val topDefinitions:World.TopDefs = { 
    (pname,site,here) => pname match { 
      case Width => Dump('Width',64) // will return 64. Requires naming the parameter as the 1st argument 
      case NTiles => Dump(Knob('NTILES')) // will return Knob('NTILES'), no name needed 
    } 
  } 
  override val knobValues:Any=>Any = { 
    case 'NTILES' => 1 // generator parameter assignment 
  } 
}

ダンプその値は*.knbファイルに書き出される。書き出すディレクトリは--targetDirパスで指定できる。

4.3 制約

外部のプログラムやユーザがコンフィグレーションのknobValueメソッドを上書きしたい場合があり、Knobに有効な範囲での値を定義するメカニズムを定義されている。ChiselConfigではtopConstraintsにより他のメソッドを上書きすることができる:

package example 
class MyConfig extends ChiselConfig { 
  val topDefinitions:World.TopDefs = { 
    (pname,site,here) => pname match { 
      case NTiles => Knob('NTILES') 
    } 
  } 
  override val topConstraints:List[ViewSym=>Ex[Boolean]] 
    = List( { ex => ex(NTiles) >  0 }, 
            { ex => ex(NTiles) <= 4 }) 
  override val knobValues:Any=>Any = { 
    case 'NTILES' => 1 // generator parameter assignment 
  } 
}

もし誰かがデザインを以下のようにしてインスタンスしようとすると、エラーが発生する:

package example 
class BadConfig extends ChiselConfig { 
  override val knobValues:Any=>Any = { 
    case 'NTILES' => 5 // would violate our constraint, throws an error 
  } 
} 
 
// throws 'Constriant failed' error 
sbt run ... --configInstance example.BadConfig

パラメータの制約メソッドを呼び出すことで、トップレベルだけでなく、制約はデザイン中のどこにで組み込むことができる:

package example 
class MyConfig extends ChiselConfig { 
  val topDefinitions:World.TopDefs = { 
    (pname,site,here) => pname match { 
      case NTiles => Knob('NTILES') 
    } 
  } 
  override val knobValues:Any=>Any = { 
    case 'NTILES' => 1 // generator parameter assignment 
  } 
} 
class Tile extends Module { 
  params.constrain( ex => ex(NTiles) >  0 ) 
  params.constrain( ex => ex(NTiles) <= 4 ) 
} 
object Run { 
  def main(args: Array[String]): Unit = { 
    chiselMain.run(args, () => new Tile()) 
  } 
} 
 
sbt runMain example.Run ... --configInstance example.MyConfig

最後に、もしデザイナがデザインの制約を知りたければ、Chiselに--configCollect project_name.config_nameオプションを追加すればよい。これにより--targetDirパスのディレクトリにすべての制約を*.cstファイルとしてダンプする:

sbt runMain example.Run ... --configCollect example.MyConfig --targetDir <path>

5. デザインヒューリスティクス

TODO

Chiselを使ってCPUを作ろう(15. デザインのパラメタライズ)

f:id:msyksphinz:20181123005953p:plain

Chiselを使って、非常にシンプルなCPUを作ってみるプロジェクト。

CPUコアのバスサイズ、Debug情報のON/OFFなどをパラメータにより調整する。

github.com

デザインのパラメタライズ

パラメータ化する項目は、以下のようにして1つのクラスにまとめた。

RVConfigを継承して、RV64IConfigクラスを作成して、これをシミュレーション時に適用する。

  • src/main/scala/cpu/config.scala
abstract class RVConfig()
{
  val xlen = 32
  val bus_width = 16
  val debug = false
}

case class RV64IConfig() extends RVConfig
{
  override val xlen = 64
  override val bus_width = 16
  override val debug = true
}
  • src/test/scala/cpu/CpuTests.scala
class CpuTopTests [Conf <: RVConfig](c: CpuTop[Conf], hexname: String, pipename: String) extends PeekPokeTester(c)
{
  val fp = Source.fromFile(hexname)
  val lines = fp.getLines

...
class Tester extends ChiselFlatSpec {
  "Basic test using Driver.execute" should "be used as an alternative way to run specification" in {
    iotesters.Driver.execute(Array(), () => new CpuTop(new RV64IConfig)) {
      c => new CpuTopTests(c, "test.hex", "pipetrace.log")
    } should be (true)
  }
}

パラメータを各モジュールに適用させるのは、他のクラスと同様にパラメータを追加すればよい。

  • src/main/scala/cpu/cpu.scala
class Cpu [Conf <: RVConfig](conf: Conf) extends Module {
  val io = IO (new CpuIo(conf))
...

ちなみに、Bundle内でFlippedを使っている場合、デフォルトだとパラメータのコピーがうまくいかずにコンパイルに失敗する。

[error] (run-main-0) chisel3.core.AutoClonetypeException: Unable to automatically infer cloneType on class cpu.InstBus: constructor has parameters (conf) that are not both immutable and accessible. Either make all parameters immutable and accessible (vals) so cloneType can be inferred, or define a custom cloneType method.
[error] chisel3.core.AutoClonetypeException: Unable to automatically infer cloneType on class cpu.InstBus: constructor has parameters (conf) that are not both immutable and accessible. Either make all parameters immutable and accessible (vals) so cloneType can be inferred, or define a custom cloneType method.

したがって、Bundleに対してcloneTypeのカスタム関数を追加する必要があった。

class InstBus [Conf <: RVConfig](conf: Conf) extends Bundle {
  override def cloneType: this.type =
    new InstBus(conf).asInstanceOf[this.type]
...

デバッグ時のポート削除をパラメータで実現する

Verilogを生成時に、余計なデバッグポートを削除する。 これもParameterにより制御する。CPUのVerilog生成時には、RV64IConfigの代わりにRV64ISynthパラメータを適用する。

  • src/main/scala/cpu/cpu.scala
object CpuTop extends App {
  chisel3.Driver.execute(args, () => new CpuTop(new RV64ISynth))
}

conf.debug = falseに設定されているので、CpuDbgMonitorの信号が全て0ビットに設定され、削除される。 この方法は、 Chiselを使ってCPUを作ろう(5. トレース記述をどう作る?) のコメントで教えてもらった。

  • src/main/scala/cpu/cpu.scala
class CpuDebugMonitor [Conf <: RVConfig](conf: Conf) extends Bundle {
  val inst_valid = if (conf.debug == true) Output(Bool())             else Output(UInt(0.W))
  val inst_addr  = if (conf.debug == true) Output(UInt(32.W))         else Output(UInt(0.W))
  val inst_hex   = if (conf.debug == true) Output(UInt(32.W))         else Output(UInt(0.W))
...

これで、Verilog生成時のみデバッグポートを削除できる。

Chiselを使ってCPUを作ろう(14. 2段パイプラインのデザインをGitHubに公開しました)

f:id:msyksphinz:20181123005953p:plain

Chiselを使って、非常にシンプルなCPUを作ってみるプロジェクト。 とりあえず、rv64ui-pのリグレッションテストがすべてPass するようになったのでGitHub上に公開した。

といっても、2段ステージのパイプラインで非常にしょぼいものだが。

ただし、このデザインの凄いところは一切Verilogを使っていないこと。だいたいのChiselのデザインはメモリのところにVerilogを使ったり、 CPUの動作制御の部分にVerilogを使ったりするのだが、これは全てChiselで記述してある。 したがって、Verilogに変換することなくシミュレーションを走らせることができ、Verilogシミュレータを使うことなく高速にシミュレーションできる。

github.com

リグレッションテストを流してみるが、せっかくなのですべてのテストをScala環境で流せるようにしたい。

まず、実施したいテスト向けにテストコードを自動生成するようなスクリプトを作成した。 以下のようなファイルが、テストパタンの数だけ生成される。

cd tests/riscv-tests/isa
make  # テストパタンを生成する。
ruby ./gen_test_class.rb
  • src/test/scala/cpu/Test_rv64ui-p-add.scala
package cpu

import chisel3.iotesters
import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}
class Tester_rv64ui_p_add extends ChiselFlatSpec {
  "Basic test using Driver.execute" should "be used as an alternative way to run specification" in {
    implicit val conf = RV64IConf()
    iotesters.Driver.execute(Array(), () => new CpuTop()) {
      c => new CpuTopTests(c, "tests/riscv-tests/isa/rv64ui-p-add.hex", "pipetrace.rv64ui_p_add.log")
    } should be (true)
  }
}

Makefileはテストパタンの数だけターゲットが作られる。

  • riscv64_make.mk
...
test_run_rv64ui_p_add:
        sbt 'testOnly cpu.Tester_rv64ui_p_add -- -z Basic'
test_run_rv64ui_p_addi:
        sbt 'testOnly cpu.Tester_rv64ui_p_addi -- -z Basic'
test_run_rv64ui_p_addiw:
        sbt 'testOnly cpu.Tester_rv64ui_p_addiw -- -z Basic'
test_run_rv64ui_p_addw:
        sbt 'testOnly cpu.Tester_rv64ui_p_addw -- -z Basic'
test_run_rv64ui_p_and:
        sbt 'testOnly cpu.Tester_rv64ui_p_and -- -z Basic'

これにより、make test_run_rv64ui_p_addにより単体のテストを流すことができるようになった。結果のログはpipetrace.rv64ui_p_add.logに保存されるので、spike-dasmによりディスアセンブルを行う。

spike-dasm < pipetrace.rv64ui_p_add.log > pipetrace.rv64ui_p_add.dasm.log

あと、全てテストパタンを流すためにはmake regressionを入力することでテストできる。

今のところ、64ビットモードの整数テストは全てPassしている。 次は、これをパイプラインを挿入して5段くらいまで拡張して、徐々に性能を上げていきたい。

RISC-Vのフォーマル検証環境riscv-formalを試す

https://content.riscv.org/wp-content/uploads/2018/06/Symbiotic-EDA-300x66.png

RISC-V Summitで発表があったRISC-VのFormalツールに、riscv-formalというものがある。

内容はまだ未確認だが、どうやらRISC-Vのプロセッサに対してFormal検証をかけることができるツールらしい。

github.com

もともとこのツールはVerilogなど通常のデザインに対して有効である。

例えば、以下のような検証ができる。

  • demo.sv
module demo (
  input clk,
  output [5:0] counter
);
  reg [5:0] counter = 0;

  always @(posedge clk) begin
    if (counter == 15)
      counter <= 0;
    else
      counter <= counter + 1;
  end

`ifdef FORMAL
  always @(posedge clk) begin
    assert (counter < 32);
  end
`endif
endmodule

FORMALで記述されているassert文が検証対象となる。

  • demo.sby
[options]
mode bmc
depth 100

[engines]
smtbmc

[script]
read -formal demo.sv
prep -top demo

[files]
demo.sv

同じように、32-bit RISC-Vプロセッサについてフォーマル検証が可能だ。

まずは、riscv-formalを使うための環境をビルドする。基本的には、以下のサイトを参照に構築すればよい。

Getting Started — SymbiYosys 0.1 documentation

ただし、一つだけabcのビルドには、リポジトリのアップデートが必要だった。extavyのサブモジュールであるabcを最新のリビジョン(c935e76 : 2018/12/07時点) にアップデートする。

次に、picorv32をダウンロードして、Formal検証ツールを流してみよう。

  git clone https://github.com/SymbioticEDA/riscv-formal.git
  cd riscv-formal/cores/picorv32/
  wget -O picorv32.v https://raw.githubusercontent.com/cliffordwolf/picorv32/master/picorv32.v
  python3 ../../checks/genchecks.py
  make -C checks -j$(nproc)

これで60本程度のテストパタンが流れ始める。エラー無く終了すればOK。

SBY  0:24:03 [insn_xori_ch0] summary: Elapsed clock time [H:MM:SS (secs)]: 0:01:35 (95)
SBY  0:24:03 [insn_xori_ch0] summary: Elapsed process time [H:MM:SS (secs)]: 0:01:33 (93)
SBY  0:24:03 [insn_xori_ch0] summary: engine_0 (smtbmc boolector) returned PASS
SBY  0:24:03 [insn_xori_ch0] DONE (PASS, rc=0)
SBY  0:25:04 [insn_sw_ch0] engine_0: ##   0:03:21  Status: PASSED
SBY  0:25:04 [insn_sw_ch0] engine_0: finished (returncode=0)
SBY  0:25:04 [insn_sw_ch0] engine_0: Status returned by engine: PASS
SBY  0:25:04 [insn_sw_ch0] summary: Elapsed clock time [H:MM:SS (secs)]: 0:03:27 (207)
SBY  0:25:04 [insn_sw_ch0] summary: Elapsed process time [H:MM:SS (secs)]: 0:03:20 (200)
SBY  0:25:04 [insn_sw_ch0] summary: engine_0 (smtbmc boolector) returned PASS
SBY  0:25:04 [insn_sw_ch0] DONE (PASS, rc=0)
make: Leaving directory '/home/msyksphinz/work/riscv/riscv-formal/cores/picorv32/checks'
$

picorv32.vを見てみると、FORMAL用の外部に出力する信号が定義してあった。

module picorv32 #(
        parameter [ 0:0] ENABLE_COUNTERS = 1,
...
        // IRQ Interface
        input      [31:0] irq,
        output reg [31:0] eoi,

`ifdef RISCV_FORMAL
        output reg        rvfi_valid,
        output reg [63:0] rvfi_order,
        output reg [31:0] rvfi_insn,
        output reg        rvfi_trap,
        output reg        rvfi_halt,
        output reg        rvfi_intr,
        output reg [ 1:0] rvfi_mode,
        output reg [ 4:0] rvfi_rs1_addr,
        output reg [ 4:0] rvfi_rs2_addr,
        output reg [31:0] rvfi_rs1_rdata,
        output reg [31:0] rvfi_rs2_rdata,
        output reg [ 4:0] rvfi_rd_addr,
        output reg [31:0] rvfi_rd_wdata,
        output reg [31:0] rvfi_pc_rdata,
        output reg [31:0] rvfi_pc_wdata,
        output reg [31:0] rvfi_mem_addr,
        output reg [ 3:0] rvfi_mem_rmask,
        output reg [ 3:0] rvfi_mem_wmask,
        output reg [31:0] rvfi_mem_rdata,
        output reg [31:0] rvfi_mem_wdata,
`endif

        // Trace Interface

例えばADD命令を検証するためのsbyは以下のようになっている。

  • checks/insn_add_ch0.sby
[options]
mode bmc
append 0
tbtop wrapper.uut
depth 21
skip 20

[engines]
smtbmc boolector

[script]
read_verilog -sv insn_add_ch0.sv
read_verilog -sv /home/msyksphinz/work/riscv/riscv-formal/cores/picorv32/../../cores/picorv32/wrapper.sv
read_verilog /home/msyksphinz/work/riscv/riscv-formal/cores/picorv32/../../cores/picorv32/picorv32.v
prep -flatten -nordff -top rvfi_testbench
...
[file insn_add_ch0.sv]
`include "defines.sv"
`include "rvfi_channel.sv"
`include "rvfi_testbench.sv"
`include "rvfi_insn_check.sv"
`include "insn_add.v"
  • checks/insn_add_ch0/src/insn_add.v
module rvfi_insn_add (
  input                                 rvfi_valid,
  input  [`RISCV_FORMAL_ILEN   - 1 : 0] rvfi_insn,
  input  [`RISCV_FORMAL_XLEN   - 1 : 0] rvfi_pc_rdata,
  input  [`RISCV_FORMAL_XLEN   - 1 : 0] rvfi_rs1_rdata,
  input  [`RISCV_FORMAL_XLEN   - 1 : 0] rvfi_rs2_rdata,
  input  [`RISCV_FORMAL_XLEN   - 1 : 0] rvfi_mem_rdata,
`ifdef RISCV_FORMAL_CSR_MISA
  input  [`RISCV_FORMAL_XLEN   - 1 : 0] rvfi_csr_misa_rdata,
  output [`RISCV_FORMAL_XLEN   - 1 : 0] spec_csr_misa_rmask,
`endif

  output                                spec_valid,
  output                                spec_trap,
  output [                       4 : 0] spec_rs1_addr,
  output [                       4 : 0] spec_rs2_addr,
  output [                       4 : 0] spec_rd_addr,
  output [`RISCV_FORMAL_XLEN   - 1 : 0] spec_rd_wdata,
  output [`RISCV_FORMAL_XLEN   - 1 : 0] spec_pc_wdata,
  output [`RISCV_FORMAL_XLEN   - 1 : 0] spec_mem_addr,
  output [`RISCV_FORMAL_XLEN/8 - 1 : 0] spec_mem_rmask,
  output [`RISCV_FORMAL_XLEN/8 - 1 : 0] spec_mem_wmask,
  output [`RISCV_FORMAL_XLEN   - 1 : 0] spec_mem_wdata
);
...
  // ADD instruction
  wire [`RISCV_FORMAL_XLEN-1:0] result = rvfi_rs1_rdata + rvfi_rs2_rdata;
  assign spec_valid = rvfi_valid && !insn_padding && insn_funct7 == 7'b 0000000 && insn_funct3 == 3'b 000 && insn_opcode == 7'b 0110011;
  assign spec_rs1_addr = insn_rs1;
  assign spec_rs2_addr = insn_rs2;
  assign spec_rd_addr = insn_rd;
  assign spec_rd_wdata = spec_rd_addr ? result : 0;
  assign spec_pc_wdata = rvfi_pc_rdata + 4;

ここには、ADD命令のパタンが書き込まれている。これに基づいて検証が行われているわけか。

Chiselのパラメタライズについて調査(1. Advanced Parameterization Manualを読む)

Chiselについて少しずつ勉強を進めているが、SystemVerilogと同様のことが実現できないと、マスターしたとは言えないだろう。

Verilogでも多く使われるパラメータ化、これをどのようにしてChiselで使用するのか、Rocket-Chipのデザインを見ても良く分からないので調査したい。

このあたりについて非常に細かく記述してある資料 Advanced Parameterization Manual を翻訳して調べてみた。 まずは簡単に翻訳した結果を掲載しておきたい。

1. イントロダクション

このドキュメントは、Chisel内で高度なパラメータライブラリを使用するためのマニュアルである。ハードウェア構築言語としてのChiselに関する一般的な情報については、Getting Startedのドキュメントを参照すること。

ハードウェア設計が複雑になるにつれて、メンテナンスと検証のためにモジュール性が必要になる。Chiselの主な使用事例は、多様で高度にパラメータ化されたハードウェアジェネレータを記述することであり、従来のパラメータ化手法は、脆弱性を設計のソースコードに強制し、コンポーネントの再利用を制限していた。

このドキュメントの概要は以下の通りである。第2章では、高度なパラメータ化メカニズムの基本的なオブジェクトとメソッド、およびそれを使用するために必要な定型文について説明する。第3章では、ますます複雑化する一連の設計パターンについて説明する。各例では、問題を解決する最も簡単なパラメータ化スキームを提案します。例が複雑になるにつれて、前述の高度なパラメータ化メカニズムに到達するまで、パラメータ化要件も同様になる。第4章では、ノブの概念と、設計制約とパラメータオブジェクトとの関係について説明する。最後に第5章では、高度なパラメータ化を使用する際に従うべき複数の設計ヒューリスティックについて説明します。

2. 高度なパラメタライズ

すべてのChiselのモジュールには、モジュール間でパラメータを渡すためのメカニズムを提供するクラスパラメータのメンバパラメータがあります。このセクションでは、以下の機能について説明します。

  1. Parametersクラスおよび関連するメソッド/メンバー
  2. 基本的な使用モデル。
  3. シンタックスシュガー;
  4. パラメータを外部ユーザ/プログラムに公開する定型文。
  5. Views経由の高度な機能 (site, here, up)

2.1 クラスとメソッド

パラメータクラスは、以下のベースメソッドを持っている。

class Parameters { 
  // T型の値を返す。
  def apply[T](key:Any):T 
 
  // 新しいParameterクラスを返す。
  def alter(mask:(Any,View,View,View)=>Any):Parameters 
 
  // モジュールのパラメータインスタンスを返す。
  def params:Parameters 
}

Viewは、ベースメソッドを含んでいるクラスである。

class View { 
  // T型の値を返す。
  def apply[T](key:Any):T 
}

Parametersは、1つの基本メソッドを含んでいるファクトリオブジェクトを持っている。

object Parameters { 
  // 空のパラメータインスタンスを返す。
  def empty:Parameters 
}

モジュールオブジェクトファクトリは、applyメソッドを持っている。

object Module { 
  // T型の新しいモジュールを返す。このモジュールは_p != Noneならば、パラメータで初期化されている。
  def apply[T<:Module](c: =>T)(implicit _p: Option[Parameters] = None):T 
}

2.2 基本的な使用モデル

以下の例は

  1. パラメータの問い合わせ(quering)
  2. パラメータオブジェクトの変更(altering)
  3. パラメータオブジェクトをモジュールに渡す

の基本的な使用方法の例である。

class Tile extends Module { 
  val width = params[Int]('width') 
} 
object Top { 
  val parameters = Parameters.empty 
  val tile_parameters = parameters.alter( (key,site,here,up) => { 
    case 'width' => 64 
  }) 
  def main(args:Array[String]) = { 
    chiselMain(args,()=>Module(new Tile)(Some(tile_parameters))) 
  } 
}

Tileモジュールでは、paramsメンバはキーの値とParameters.applyで呼ばれることによって、その値を返している。

Topモジュールでは、Parameters.empyが呼ばれることによって空のパラメータが作成され、(Any, View, View, View)=>Anyの関数にalterされる。この関数は新しいパラメータインスタンスを返し、tile_parametersに渡される。

tile_parametersSome:Option[parameters]にラッピングされた後、モジュールの2番目の引数に渡され、chiselMainに渡される。

2.3 シンタックスシュガー: Field[T]

単純な例ではapplyメソッドで返す型(Int)を付加しなければならない。これを除去するとコンパイラがエラーを返す:

class Tile extends Module { 
  val width = params[Int]('width') 
}

一方で、Field[T]を継承する形で各パラメータのcase objectを作成することができる。この場合は、paramsから直接applyメソッドを渡すことができる。これは、Fieldが返す方の情報を持っているからであり、それ以上型の情報を渡す必要がないからである。

case object Width extends Field[Int] 
class Tile extends Module { 
  val width = params(Width) 
}

以降のドキュメントでは、全ての問い合わせのキーは、Field[T]クラスの継承でcase classが作成されているものとする。

2.4 シンタックスシュガー: PassingとAltering

pict

図1. メモリのkey/valueチェインとフラットマップ

モジュールが階層化されていると、Parametersオブジェクトは親のモジュールkらこのモジュールへと渡される。これはプログラマによって指定され、これらのオブジェクトはインスタンスされた子供へとコピーされ変更される。

パラメータが変更されると、Chiselは内部で既存のチェインのkey/valueマッピングをコピーし、チェインの最後に新たなkey/valueマッピングとして接続する。クエリが評価されると、チェインの最後尾のkey/valueの値がクエリされる。もしそのクエリがマッチしなければ、クエリはチェイン中の次のkey/valueの値を評価していく。もしクエリがチェインのトップに到達し、それ以上マッチしなければChiselはParameterUndefinedException例外を発行する。

子供をインスタンスする場合、親はParameterを2つの方法で渡すことができる。

  1. パラメータオブジェクトを、子供の引数としてモジュールファクトリに明示的に渡すもの。Option[Parameters]:でラッピングする。
class Tile extends Module { 
  val width = params(Width) 
  val core = Module(new Core)(Some(params)) 
  // TileのパラメータをCoreに明示的に渡す。
}
  1. パラメータオブジェクトを子供に暗黙的に渡す。
class Tile extends Module { 
  val width = params(Width) 
  val core = Module(new Core) 
  // TileのパラメータをCoreに暗黙的に渡す。
}

もし親が子供の辞書にパラメータをコピー/変更したい場合、2種類の方法がある。

  1. PartialFunctionマッピングを使用して、引数をモジュールファクトリに渡す。内部的には、Chiselは親のパラメータオブジェクトをコピーして、AlterをApplyする。
class Tile extends Module { 
  val width = params(Width) 
  val core = Module(new Core,{case Width => 32}) 
  // PartialFuncitonをモジュールのファクトリコンストラクタに渡して、Coreの\code{Parameters}`を変更する。
}

2.Parameter.alter関数を使用して、新しいパラメータオブジェクトを返す。このアプローチでは、プログラマは新しいパラメータオブジェクトに置くセス氏、site, here, upを使用することができる。

class Tile extends Module { 
  val width = params(Width) 
  val core_params = params.alter( 
    (pname,site,here,up) => pname match { 
      case Width => 32 
    }) 
  val core = Module(new Core)(Some(core_params)) 
  // Parameter.alterを使用して、変換したパラメータオブジェクトを返す。site, here, upメカニズムが必要な時のみ使用する。
}

図1. で示したより複雑な例は、以下のようにして表現できる。

class Tile extends Module { 
  ... 
  val core = Module(new Core, {case FPU => true; case QDepth => 20; case Width => 64}) 
} 
class Core extends Module { 
  val fpu = params(FPU) 
  val width = params(Width) 
  val depth = params(Depth) 
  val queue = Module(new Queue,{case Depth => depth*2; case Width => 32}) 
} 
class Queue extends Module { 
  val depth = params(Depth) 
  val width = params(Width) 
  val mem = Module(new Memory,{case Size => depth * width}) 
} 
class Memory extends Module { 
  val size = params(Size) 
  val width = params(Width) 
}

2.5 ChiselConfigとBoilerPlate

Chiselのメカニズムで、とっぷれえbるのパラメータをChiselConfigオブジェクトを用いてシードする。ChiselConfig.topDefinitionsでは最高位のパラメータの定義を以下の構成で保持している。

case object Width extends Field[Int] 
class DefaultConfig extends ChiselConfig { 
  val topDefinitions:World.TopDefs = { 
    (pname,site,here) => pname match { 
      case Width => 32 
    } 
  } 
}

通常、デザインはchiselMain.applyを使用してデザインをインスタンスする。Chiselのパラメータメカニズムを使用して正確にChiselConfigをシードするためには、デザインがモジュールファクトリに囲まれていない状態でchiselMain.runを呼び出す必要がある。この理由は既存のデザインに対するバックワードの互換性を守るためであるが、この制約は将来解消する予定である。

chiselMain.runを呼び出す例である。

object Run { 
  def main(args: Array[String]): Unit = { 
    chiselMain.run(args, () => new Tile()) 
  } 
}

特定のChiselConfigを用いてデザインをインスタンスしたい場合、単純にChiselのコンパイラ--configInstance project_name.configClass_nameの引数を付けて呼び出せばよい。

2.6 siteの使用

pict

図2(a). site(Location)はCoreを返す。

pict

図2(b). site(Location)はCacheを返す。

パラメータ間の依存を表現したい場合、siteのメカニズムを使用することができる。この機能を理解するために、クエリ化されたモジュールのパラメータを最初に見たときに、チェイン中の最後尾のkey/valueマッピングが呼び出されることを思い出そう。もしマッチングしなければ、次のチェインへと移っていく。

以下のような、複数のモジュールがあったとしよう。

class Core extends Module { 
  val data_width = params(Width) 
  ... 
} 
class Cache extends Module { 
  val line_width = params(Width) 
  ... 
}

残念ながら、Widthのパラメータは名前は同一だが文法的には別の意味を持っている。Coreの中ではWidthはワードサイズを示しているが、Cache中ではキャッシュラインの意味を持っている。これらについて、簡単にパラメータを識別できるようになりたい。

siteのメカニズムを使用すれば、チェインの途中のkey/valueマッピングを使用することができる。

以下の例を考える。

class DefaultConfig extends ChiselConfig { 
  val top:World.TopDefs = { 
    (pname,site,here) => pname match { 
       case Width => site(Location) match { 
         case 'core' => 64 // data width 
         case 'cache' => 128  // cache line width 
       } 
    } 
  } 
} 
class Tile extends Module { 
  val core = Module(new Core, {case Location => 'core'}) 
  val cache = Module(new Cache, {case Location => 'cache'}) 
}

トップレベルでのkey/valueマッピングは、siteを使用してLocationのチェインを使用している。なんの値が返されるか('core'もしくは'cache')によって、トップレベルのkey/valueマッピングでは異なる値が返される(図2)。

2.7 hereの使用

pict

図3. 128 or 4の数値を直接使うのではなく、here(Sets)とhere(Ways)を使用することができる。

パラメータが、key/valueマッピングチェインの同一グループ中の他のパラメータに対する決定的な関数であれば、値を複製してしまうと、多くの変更が伴ってしまうため、それは避けたい。その代わりに、クエリが同一グループのkey/valueマッピングである場合に、hereを使用することができる:

class Tile extends Module { 
  val cache_params = params.alter( 
    (pname, site, here, up) => pname match { 
      case Sets => 128 
      case Ways => 4 
      case Size => here(Sets)*here(Ways) 
    }) 
  val cache = Module(new Cache)(cache_params) 
}

2.8 upの使用

upのメカニズムは、ユーザがkey/valueマッピングの親のグループをクエリしたいときに有効である。Parameters.applyを直接読んだものと同一であるが、さらにParameters.alterを読んだものと同じ意味になる。3.6章の例を参考にすること。

Chiselを使ってCPUを作ろう(13. リグレッションテストを流してみる)

f:id:msyksphinz:20181123005953p:plain

Chiselを使って、非常にシンプルなCPUを作ってみるプロジェクト。 とりあえず一旦リグレッションテストを全部流してみるとどのようになるのだろうか。

リグレッションテストは、ホームディレクトリにあるtest.hexを書き換える形で行う。 本当はファイルオープンする記述を改造してリンクを差し替えなくてもよくしたいけど、Scalaのやり方が分からな過ぎて諦めた。 おかげで今のところシーケンシャルにしか実行できない。

ホームディレクトリにあるtest.hexシンボリックリンクを書き換えて対応させる。

for hex in `ls -1 tests/riscv-tests/isa/*.hex`; do rm -f test.hex; ln -s ${hex} test.hex; make cpu_run && spike-dasm < pipetrace.log > pipetrace.`basename ${hex}`.log; done

これで、pipetrace.basename ${hex}.log が生成されるので、最後の行がPASSかFAILかを見れば一応リグレッションテストになる。 この環境はすぐにでもアップデートしたい。

実行結果は以下のようになった。まだかなり落ちてるな!修正しなければ。

RV64のパタン229本のうち、Passしているのはわずか26本だった...

pipetrace.rv64mi-p-breakpoint.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-add.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-addi.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-addiw.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-addw.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-andi.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-jal.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-lb.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-lbu.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-ld.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-lh.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-lhu.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-lui.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-ori.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-simple.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-slli.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-slt.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-slti.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-sltiu.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-sltu.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-srai.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-srli.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-srliw.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-sub.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-subw.hex.log: PASS : Simulation Finished
pipetrace.rv64ui-p-xori.hex.log: PASS : Simulation Finished