FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

A short User Guide to Chisel勉強中(4)

f:id:msyksphinz:20170806234951p:plain

前回の続き。Chisel勉強中。

このページから。

github.com

パラメータ付き関数

Polymorphism and Parameterization · freechipsproject/chisel3 Wiki · GitHub

より一般的なMuxを定義するために、ビット長をパラメータ化する。

def Mux[T <: Bits](c: Bool, con: T, alt: T): T = { ... }

この場合、TはBitsのサブクラスである必要がある。

Mux(c, UInt(10), UInt(11))

パラメータ付きクラス

Polymorphism and Parameterization · freechipsproject/chisel3 Wiki · GitHub

パラメータ付き関数のように、クラスもパラメータ化することにより、再利用しやすくすることができる。 例えば、Filterクラスを一般化する。

class FilterIO[T <: Data](gen: T) extends Bundle { 
  val x = Input(gen)
  val y = Output(gen)
}

Filterクラスを、Linkの型をコンストラクタとして受け取るクラスとして定義することができる。

class Filter[T <: Data](gen: T) extends Module { 
  val io = IO(new FilterIO(gen))
  ...
}

一般化したFIFOは以下のように定義することができる。

class DataBundle extends Bundle {
  val a = UInt(32.W)
  val b = UInt(32.W)
}

class Fifo[T <: Data](gen: T, n: Int) extends Module {
  val io = IO(new Bundle {
    val enqVal = Input(Bool())
    val enqRdy = Output(Bool())
    val deqVal = Output(Bool())
    val deqRdy = Input(Bool())
    val enqDat = Input(gen)
    val deqDat = Output(gen)
  })
  val enqPtr     = Reg(init = 0.asUInt(sizeof(n).W))
  val deqPtr     = Reg(init = 0.asUInt(sizeof(n).W))
  val isFull     = Reg(init = false.B)
  val doEnq      = io.enqRdy && io.enqVal
  val doDeq      = io.deqRdy && io.deqVal
  val isEmpty    = !isFull && (enqPtr === deqPtr)
  val deqPtrInc  = deqPtr + 1.U
  val enqPtrInc  = enqPtr + 1.U
  val isFullNext = Mux(doEnq && ~doDeq && (enqPtrInc === deqPtr),
                         true.B, Mux(doDeq && isFull, false.B,
                         isFull))
  enqPtr := Mux(doEnq, enqPtrInc, enqPtr)
  deqPtr := Mux(doDeq, deqPtrInc, deqPtr)
  isFull := isFullNext
  val ram = Mem(n)
  when (doEnq) {
    ram(enqPtr) := io.enqDat
  }
  io.enqRdy := !isFull
  io.deqVal := !isEmpty
  ram(deqPtr) <> io.deqDat
}

kここで、DataBundle型を受け取る8要素を格納できるFIFOを以下のようにインスタンスすることができる。

val fifo = Module(new Fifo(new DataBundle, 8))

Ready/Validのインタフェースを以下のようにして一般化することができる。

class DecoupledIO[T <: Data](data: T) extends Bundle {
  val ready = Input(Bool())
  val valid = Output(Bool())
  val bits  = Output(data)
}

以下のようにデータのハンドシェークを定義する。

class DecoupledDemo extends DecoupledIO(new DataBundle)

こうして、FIFOインタフェースを以下のようにして簡単かすることができる。

class Fifo[T <: Data](data: T, n: Int) extends Module {
  val io = IO(new Bundle {
    val enq = Flipped(new DecoupledIO(data))
    val deq = new DecoupledIO(data)
  })
  ...
}

複数クロックドメイン

Multiple Clock Domains · freechipsproject/chisel3 Wiki · GitHub

Chisel3では、以下のように複数クロックをサポートすることができるようになった。 以下のように非同期のFIFOでデータの受け渡しを行うことができる。

class MultiClockModule extends Module {
  val io = IO(new Bundle {
    val clockB = Input(Clock())
    val stuff = Input(Bool())
  })

  // This register is clocked against the module clock.
  val regClock = RegNext(stuff)

  withClock (io.clockB) {
    // In this withClock scope, all synchronous elements are clocked against io.clockB.

    // This register is clocked against io.clockB.
    val regClockB = RegNext(stuff)
  }

  // This register is also clocked against the module clock.
  val regClock2 = RegNext(stuff)
}

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

A short User Guide to Chisel勉強中(3)

f:id:msyksphinz:20170806234951p:plain

前回の続き。Chisel勉強中。このページから。

github.com

ステート記述 (State Elements)

Chiselで記述できる最も簡単なステート記述は、ポジティブエッジのステートマシンで、以下のように記述する。

val reg = RegNext(in)

この場合、regは1サイクル遅れてRegNext(in)を出力するフリップフロップとなる。クロックとリセットは明示的には記述されていないが、暗黙的に必要な場所から接続される。 この場合、初期値が指定されている場合は良いが、初期値が指定されていない場合はリセット信号が入るまでは値が変化しない。

例えば、立ち上がりエッジを検出する記述は、過去の値がfalseで、現在の値がTrueであることを検出すればよいため、以下のようになる。

def risingedge(x: Bool) = x && !RegNext(x)

次に、値をカウントアップしていき最大値に到達するとラップして0に戻るようなアップカウンタを記述する場合は、以下のようになる。

def counter(max: UInt) = {
  val x = Reg(init = 0.asUInt(max.getWidth))
  x := Mux(x === max, 0.U, x + 1.U)
  x
}

まず、リセットが入った時点でinitが参照されカウンタレジスタは0に設定される。:=により、xはインクリメントされ、xがmaxに到達すると0になる。 カウンタは非常に便利に使用することができる。例えば、カウンタが0になるとパルスを生成するようなパルスジェネレータは、以下のような記述で作成できる。

// nサイクル単位でパルスを生成する。
def pulse(n: UInt) = counter(n - 1.U) === 0.U

矩形波を発生するカウンタは、パルスに応じてTrueとFalseを切り替えるようにすればよい。

// 入力があると、内部の状態を反転させる。
def toggle(p: Bool) = {
  val x = Reg(init = false.B)
  x := Mux(p, !x, x)
  x
}
// 指定した周期で矩形波を生成する。
def squareWave(period: UInt) = toggle(pulse(period/2))

メモリ

ChiselはROMとRAMを生成することができる。

ROM

Vecデータ型を用いて、読み込み専用のメモリ(ROM)を生成することができる。

    Vec(inits: Seq[T])
    Vec(elt0: T, elts: T*)

initはROMの初期値を指定する。例えば、それぞれROMの初期値を1,2,4,8がループしているものとして、カウンタをアドレスジェネレータとして以下のように記述できる。

    val m = Vec(Array(1.U, 2.U, 4.U, 8.U))
    val r = m(counter(m.length.U))

次に、正弦関数のn値のルックアップテーブルを以下のようにして記述することができる。ampは、ROMに内蔵する値をスケーリングするために使用している値である。

    def sinTable(amp: Double, n: Int) = {
      val times = 
        (0 until n).map(i => (i*2*Pi)/(n.toDouble-1) - Pi)
      val inits = 
        times.map(t => round(amp * sin(t)).asSInt(32.W))
      Vec(inits)
    }
    def sinWave(amp: Double, n: Int) = 
      sinTable(amp, n)(counter(n.U))

Mem

Chiselはメモリは特殊なモジュールとして処理している。これは、メモリはハードウェアによっていくつかの種類が存在するからである。 例えば、FPGAのメモリはASICのメモリのそれとはまったく異なる。 メモリのVerlilog動作モデルにマップすることのできるメモリの抽象モデルか、外部のIPベンダから入手したメモリジェネレータと接続するためのメモリ抽象モデルを定義することができる。

ChiselはMemデータ型を用いてランダムアクセスメモリを実装することができるが、これは組み合わせ・非同期読み込み、順序・同期書き込みとなる。つまり、Memレジスタバンクのような動作をする。

もう一つ、ChiselはSyncReadMemという 順序・同期読み込み、順序・同期書き込み を行うメモリお定義できる。現代のほとんどのSRAM(ASIC用、FPGA用)は非同期読み込みをサポートしないため、SyncReadMemは現代のSRAMのような動作をすると考えることができる。

メモリのポートは、Uint型のインデックスを持っている。 1024エントリのレジスタファイルで、1ポート書き込み、同期読み込みのレジスタファイルは以下のようにして定義できる。

val width:Int = 32
val addr = Wire(UInt(width.W))
val dataIn = Wire(UInt(width.W))
val dataOut = Wire(UInt(width.W))
val enable = Wire(Bool())

// assign data...

// Create a synchronous-read, synchronous-write memory (like in FPGAs).
val mem = SyncReadMem(1024, UInt(width.W))
// Create one write port and one read port.
mem.write(addr, dataIn)
dataOut := mem.read(addr, enable)

Chiselはこれ以外にも、マスク月のメモリなども生成させることができる。 シングルポートのSRAMは読み込みと書き込みの状態が排他的である必要があり、以下のようにして記述することができる。下記のような場合、同時に読み込みと書き込みが発生すると、読み込みのリクエストは無視され、書き込みが実行される。読み込みデータは無視される。

val mem = SyncReadMem(2048, UInt(32.W))
when (write) { mem.write(addr, dataIn) }
.otherwise { dataOut := mem.read(addr, read) }

MemSyncReadMemも、マスク月の書き込みをサポートしている。マスクビットがセットされた場合の書き込みは、以下のように定義できる。

val ram = Mem(256, UInt(32.W))
when (wen) { ram.write(waddr, wdata, wmask) }

インタフェースとバルク接続

モジュールをより便利に使えるようにするためには、インタフェースクラスを定義して、モジュールのI/Oをインタフェースクラスを通じて接続できるようになれば便利だ。 さらに、インタフェースは再利用することができ、モジュールのインタフェースクラスを統一しておけばモジュール間の接続も便利になる。

次に、インタフェースを定義することにより、送信元のモジュールと受信先のモジュールの接続の記述量を格段に減らすことができる。 最終的に、ユーザはインタフェースを大きく変更しても、インタフェースの要素が増減した場合の変更量を減らすことができるようになる。

ポート: サブクラスとネスト

ユーザはBundleのサブクラスとしてインタフェースを定義することができる。 例えば、以下のようにしてシンプルなハンドシェークのポートを定義する事ができる。

class SimpleLink extends Bundle {
  val data = Output(UInt(16.W))
  val valid = Output(Bool())
}

さらに、SimpleLinkに対して以下のようにパリティを追加することができる。

class PLink extends SimpleLink {
  val parity = Output(UInt(5.W))
}

一般的に、ユーザは継承を使ってインタフェースを構成する。

次に、フィルタインタf-エスを、2つのPLinkを1つの新しいFilterIOインタフェースとして定義する。

class FilterIO extends Bundle {
  val x = Flipped(new PLink)
  val y = new PLink
}

ここでflipはBundleの入出力の方向を反転させる処理である。次に、モジュールを拡張することでフィルタクラスを定義する。

class Filter extends Module {
  val io = IO(new FilterIO)
  ...
}

Bundleのベクタ

単一の要素だけでなく、階層的なインタフェースとして要素のベクタを定義することができる。 例えば、入力ベクタを受け取り、出力ベクタを渡すクロスバースイッチは、以下のようにして定義できる。

class CrossbarIo(n: Int) extends Bundle {
  val in = Vec(n, Flipped(new PLink))
  val sel = Input(UInt(sizeof(n).W)
  val out = Vec(n, new PLink)
}

バルク接続

2つのフィルタを1つのフィルタブロックに集約することができる。

class Block extends Module {
  val io = IO(new FilterIO)
  val f1 = Module(new Filter)
  val f2 = Module(new Filter)
  f1.io.x <> io.x
  f1.io.y <> f2.io.x
  f2.io.y <> io.y
}

<>のバルク接続インタフェースは、モジュール間が同じレイヤに存在すれば右辺と左辺で逆の方向を持ったインタフェースであり、親モジュールと子モジュールの関係であれば、同じ方向のインタフェースである。

バルク接続は、最下層のポートはお互いに同じ名前である必要がある。もし名前が異なっていれば、Chiselは接続を行わない。

マルチプレクサと入力選択

Chiselはいくつかのビルトイン入力選択モジュールを持っている。

Mux

2入力セレクタ。以下のようにしてネストして複数入力にすることも可能。

Mux(c1, a, Mux(c2, b, Mux(..., default)))

MuxCase

n入力のマルチプレクサ。わざわざ上記のMuxを連続させる必要はない。これを使えばよい。c1,c2などで記述された条件が成立した場合に選択。

MuxCase(default, Array(c1 -> a, c2 -> b, ...))

MuxLoopkup

n個をインデックス付きで選択するマルチプレクサ。

MuxLookup(idx, default, 
          Array(0.U -> a, 1.U -> b, ...))

これは、上記のMuxCaseを使って以下のように記述するのと同様である。

MuxCase(default, 
        Array((idx === 0.U) -> a,
              (idx === 1.U) -> b, ...))

Mux1H

ワンホットのセレクタ。下記の例では、io.selectorの各ビットに1が立ったらその値を返すという仕組み。

  val hotValue = chisel3.util.oneHotMux(Seq(
    io.selector(0) -> 2.U,
    io.selector(1) -> 4.U,
    io.selector(2) -> 8.U,
    io.selector(4) -> 11.U,
  ))

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

A short User Guide to Chisel勉強中(2)

f:id:msyksphinz:20170806234951p:plain

関数の定義

デザインの再利用のために関数を定義することができる。

def clb(a: UInt, b: UInt, c: UInt, d: UInt): UInt = 
  (a & b) | (~c & d)

a, b, c, dの引数を取って、論理演算を実行した結果を返す。

BundleとVec

BundleVecによって、Chiselのデータ型を拡張することができる。 BundleはC言語のstructのように、複数の名前フィールドを一つの型に集約することができる。これはBundleのサブクラスとして定義する。

class MyFloat extends Bundle {
  val sign        = Bool()
  val exponent    = UInt(8.W)
  val significand = UInt(23.W)
}

val x  = Wire(new MyFloat)
val xs = x.sign

現状の実装では、Bundleのリテラルを作成することができないので、定数を挿入するためには、Bundle型のWireを作成し値を挿入する必要がある。

// Floating point constant.
val floatConst = Wire(new MyFloat)
floatConst.sign := true.B
floatConst.exponent := 10.U
floatConst.significand := 128.U

Vecはインデックス指定可能なベクタエレメントを定義する。

// 23ビットの符号付整数の5要素ベクタ
val myVec = Wire(Vec(5, SInt(23.W)))

// ベクタの1エレメントを接続する。 
val reg3 = myVec(3)

基本クラス(SInt, UInt, Bool)と複合クラス (Bundles, Vec)を含むすべての型は、基本クラスDataから派生しているクラスである。

BundleVecはネストより複雑なデータ型を作ることができる。

class BigBundle extends Bundle {
 // Vector of 5 23-bit signed integers.
 val myVec = Vec(5, SInt(23.W))
 val flag  = Bool()
 // Previously defined bundle.
 val f     = new MyFloat
}

ポート

ポートはハードウェアコンポーネントのインタフェースとして利用される。ポートはDataオブジェクトで定義され、方向を定義すr。

Chiselは方向を定義するためのオブジェクトを用意している。

class Decoupled extends Bundle {
  val ready = Output(Bool())
  val data  = Input(UInt(32.W))
  val valid = Input(Bool())
}

Decoupledは新しい方となり複数のモジュール間のインタフェースとして利用することができる。

モジュール

ChiselはVerilogのモジュール同様に階層的な構造を作成することができる。

階層的なモジュールの名前空間はデバッガ上で階層的に参照することができる。 ユーザの定義したモジュールは「クラス」として定義される。

  • Moduleクラスを継承する。
  • モジュールのIO()メソッドでインタフェースをラップする。また、ioという変数にポートフィールドを格納する。

例として、2入力のマルチプレクサの定義を考えてみる。

class Mux2 extends Module {
  val io = IO(new Bundle{
    val sel = Input(UInt(1.W))
    val in0 = Input(UInt(1.W))
    val in1 = Input(UInt(1.W))
    val out = Output(UInt(1.W))
  })
  io.out := (io.sel & io.in1) | (~io.sel & io.in0)
}

インタフェースの定義はBundleを使って定義する。モジュールのインタフェースはフィールド名ioによって定義される。 Mux2では、ioは4つのフィールドで定義され、それぞれマルチプレクサのポートである。

演算子 := はアサイン演算子であり、モジュール定義の中で利用される。Chiselの特殊な演算子であり、左項の配線と右項の配線を接続するための構文である。

モジュールの階層化

回路を改装設計するにあたり、より大きなモジュールを、より小さなモジュールを組み合わせて作成することができる。 以下の例は4入力のマルチプレクサモジュールを、2入力のマルチプレクサMux2を3つ使用して作成している。

class Mux4 extends Module {
  val io = IO(new Bundle {
    val in0 = Input(UInt(1.W))
    val in1 = Input(UInt(1.W))
    val in2 = Input(UInt(1.W))
    val in3 = Input(UInt(1.W))
    val sel = Input(UInt(2.W))
    val out = Output(UInt(1.W))
  })
  val m0 = Module(new Mux2)
  m0.io.sel := io.sel(0) 
  m0.io.in0 := io.in0
  m0.io.in1 := io.in1

  val m1 = Module(new Mux2)
  m1.io.sel := io.sel(0) 
  m1.io.in0 := io.in2
  m1.io.in1 := io.in3

  val m3 = Module(new Mux2)
  m3.io.sel := io.sel(1) 
  m3.io.in0 := m0.io.out
  m3.io.in1 := m1.io.out

  io.out := m3.io.out
}

ブラックボックス

Chiselは外部で定義されたモジュールを「ブラックボックス」として取り扱うことができる。 例えばChiselで定義されていないようなFPGAのIPなどを接続するために便利である。

BlackBoxで定義されたモジュールはVerilogなどで定義されているモジュールであるが、そのモジュールの動作を定義するようなコードは生成されない。

パラメータ化

この機能は実験的なものであり、APIの変更が発生する可能性があります。

BlackBoxパラメータに、Verilogのパラメータを渡すことができる。例えば、XilinxのDifferent Clock Buffer (IBUFDS)をChiselでインスタンス化することを考える。

import chisel3._
import chisel3.experimental._ // To enable experimental features

class IBUFDS extends BlackBox(Map("DIFF_TERM" -> "TRUE",
                                  "IOSTANDARD" -> "DEFAULT")) {
  val io = IO(new Bundle {
    val O = Output(Clock())
    val I = Input(Clock())
    val IB = Input(Clock())
  })
}

Chiselの生成したVerilogコードであh、IBUFDSは以下のように定義されている。

IBUFDS #(.DIFF_TERM("TRUE"), .IOSTANDARD("DEFAULT")) ibufds (
  .IB(ibufds_IB),
  .I(ibufds_I),
  .O(ibufds_O)
);

ブラックボックスに対して実装を提供する

以下の例は2つの実数を加算して返すブラックボックスモジュールだ。数値は64ビット符号なしのChisel整数として定義されている。

class BlackBoxRealAdd extends BlackBox {
  val io = IO(new Bundle() {
    val in1 = Input(UInt(64.W))
    val in2 = Input(UInt(64.W))
    val out = Output(UInt(64.W))
  })
}

実装は、以下のVerilogで定義されている。

module BlackBoxRealAdd(
    input  [63:0] in1,
    input  [63:0] in2,
    output reg [63:0] out
);
  always @* begin
  out <= $realtobits($bitstoreal(in1) + $bitstoreal(in2));
  end
endmodule

リソースファイル中のVerilogを使ったブラックボックス

バックエンドのシミュレータにVerilogのコードを渡すために、Chisel3はchise/firrtl アノテーションシステムをベースにしたツールを提供している。

ブラックボックスモジュールの定義に HasBlackBoxResourceを追加することにより、システムがVerilogファイルを見つけて関数を呼び出すことができる。モジュールは以下のように定義することができる。

class BlackBoxRealAdd extends BlackBox with HasBlackBoxResource {
  val io = IO(new Bundle() {
    val in1 = Input(UInt(64.W))
    val in2 = Input(UInt(64.W))
    val out = Output(UInt(64.W))
  })
  setResource("/real_math.v")
}

インラインVerilogを使用したブラックボックス

Verilog記述をScala中に直接記述することでブラックボックスを定義することもできる。 HasBlackBoxResourceの代わりに、HasBlackBoxInlineを指定し、setResourceの代わりにsetInlineを指定することで実現できる。

class BlackBoxRealAdd extends BlackBox with HasBlackBoxResource {
  val io = IO(new Bundle() {
    val in1 = Input(UInt(64.W))
    val in2 = Input(UInt(64.W))
    val out = Output(UInt(64.W))
  })
  setInline("BlackBoxRealAdd.v",
    s"""
      |module BlackBoxRealAdd(
      |    input  [15:0] in1,
      |    input  [15:0] in2,
      |    output [15:0] out
      |);
      |always @* begin
      |  out <= $realtobits($bitstoreal(in1) + $bitstoreal(in2));
      |end
      |endmodule
    """.stripMargin)
}

内部構成について

Verilogファイルと連結してバックエンドと接続する機構は、Chisel/Firrtlアノテーションによって実現している。 inlineとresrouceの2つのアノテーションは、現在はsetInlineもしくはsetResourceメソッドを呼び出すことで実現している。 これらのアノテーションはchisel-testsを通じてfirrtlに順番に渡される。 デフォルトのfirrtl verilogコンパイラアノテーションを検出する機構を持っており、ファイルもしくはインラインコードをビルドディレクトリに渡す。 渡された各ファイルはblack_box_verilog_files.f に追記される。 このファイルはVerilatorもしくはVCSに渡されるファイルリストである。 dsptools projectは、実数のシミュレーションをビルドするためにこの機構を使用している良い例である。

インタプリタ

firrtl インタプリタは、ユーザがブラックボックスScala実装を行うことができるように分離したシステムを利用している。 Scalaの実装コードはBlackBoxFactoryを利用して、実行ハーネスに渡される。 インタプリタScalaのシミュレーションテスタである。 上記で紹介したdsptools projectはこの機構を使用しているプロジェクトの良い例である。

前回の記事はこちら。

msyksphinz.hatenablog.com

A Short Users Guide to Chisel 勉強中(1)

Chiselの勉強をすべく、githubのChiselプロジェクトについているWikiを読んで勉強中。

f:id:msyksphinz:20170806234951p:plain

github.com

Chiselは、「Constructing Hardware In a Scala Embedded Language」の略。 Chiselでハードウェアを設計するときは、Scalaのプログラムを利用してハードウェアのデータフローグラフを設計することになる。 本章では、Scala自身の内容については必要に応じて解説することにするが、よりChiselをシンプルかつ再利用できるような構造にするためには、Scalaの本を一冊買って、しっかり勉強することをお勧めする。

Chiselを開発したモチベーションとして、これまでのハードウェア記述言語(Verilog, VHDLなど)はハードウェアシミュレーション言語だが、後にハードウェア合成の基本となる言語となった。 これらの構文はどのようにして実際のハードウェアにマッピングされるか直感的ではなく、誤って使用すると非効率なハードウェアにマッピングされる可能性がある。

それでも、新しいハードウェア設計言語を作成しようとした動機は、エレクトロニクスシステムデザインの手法を変えようと思う我々の思いがあるからである。 学生にとっては、ハードウェア設計言語を習得することと共に、回路"ジェネレータ"を作成する手法を学ぶことも重要である。

ここでScalaを選んだ理由は、回路生成を行うのに必要ないくつかの機能をScalaが含んでいたという理由だけでなく、ドメイン特化言語のために必要な基本的な機能が搭載されていたからである。

Chiselのデータタイプ

Chiselではビットコレクションを表現するためにBitsデータタイプを持っている。 符号有り、符号なし整数はそれぞれSInt, UIntで表現される。 Boolean素はBool型にて表現される。

さらに、ChiselはBundlesというデータタイプを定義し、名前付きフィールド(他の言語ではいわゆる構造体)を定義できるようにしている。 Vecsはインデックス参照の可能な値を定義できる。

1.U       // decimal 1-bit lit from Scala Int.
"ha".U    // hexadecimal 4-bit lit from string.
"o12".U   // octal 4-bit lit from string.
"b1010".U // binary 4-bit lit from string.

5.S    // signed decimal 4-bit lit from Scala Int.
-8.S   // negative decimal 4-bit lit from Scala Int.
5.U    // unsigned decimal 3-bit lit from Scala Int.

true.B // Bool lits from Scala lits.
false.B

デフォルトでは、Chiselのコンパイラは固定数値については値を保持するための最小のビット幅を定義するようになっている。 しかし以下のようにビット幅を明示的に定義することができる。

"ha".asUInt(8.W)     // hexadecimal 8-bit lit of type UInt
"o12".asUInt(6.W)    // octal 6-bit lit of type UInt
"b1010".asUInt(12.W) // binary 12-bit lit of type UInt

5.asSInt(7.W) // signed decimal 7-bit lit of type SInt
5.asUInt(8.W) // unsigned decimal 8-bit lit of type UInt

もし定数を保持するための必要ビット幅が、指定されたビット幅よりも大きい場合、Chiselはエラーを出力する。

組み合わせ回路の記述

Chiselで組み合わせ回路を記述するためには、普通のプログラミング言語と同様に演算子を使って表現することができる。 例えば、以下のような簡単な組み合わせ回路を、以下のような記述で実現できる。

(a & b) | (~c & d)

他にも、組み合わせ回路を記述するのに、有行非巡回グラフ(Directed Acyclic Graph)を使って表現できる。 配線は変数を使って宣言することができ、以下のようなマルチプレクサの記述を定義できる。

val sel = a | b
val out = (sel & in1) | (~sel & in0)

配線

Chiselは他のノードとの接続や、定数との接続のための配線をサポートしている。

val myNode = Wire(UInt(8.W))
when (isReady) {
  myNode := 255.U
} .elsewhen {
  myNode := 0.U
}

また、Chiselでは最後接続された記述が有効である。例えば、以下の2つのChisel回路は等価である。

val myNode = Wire(UInt(8.W))
myNode := 10.U
myNode := 0.U
val myNode = Wire(UInt(8.W))
myNode := 0.U

ビルトイン演算子

Operation Explanation
ビット演算子 SInt, UInt, Bool で有効
val invertedX = ~x NOTビット演算
val hiBits = x & UInt(“h_ffff_0000”) ANDビット演算
val flagsOut = flagsIn | overflow ORビット演算
val flagsOut = flagsIn ^ toggle XORビット演算
ビットリダクション SInt and UIntで有効。Bool型を返す。
val allSet = x.andR AND リダクション
val anySet = x.orR OR リダクション
val parity = x.xorR XOR リダクション
比較演算子 SInt, UInt, and Boolで有効。Bool型を返す。
val equ = x === y 等価
val neq = x =/= y 非等価
シフト演算子 SInt and UIntで有効。
val twoToTheX = 1.S << x 論理左シフト
val hiBits = x >> 16.U 右シフト (Uintでは符号無しシフト、SIntでは符号付シフト)。
ビットフィールド操作 SInt, UInt Bool型で有効
val xLSB = x(0) 1ビット抽出。
val xTopNibble = x(15, 12) endからstartまでのビットを抽出。
val usDebt = Fill(3, “hA”.U) ビット列を複数繰り返す
val float = Cat(sign, exponent, mantissa) 左から順にビット列を接続する。
論理演算 Bool型で有効。
val sleep = !busy 論理否定
val hit = tagMatch && valid 論理AND
`val stall = src1busy || valid 論理OR
val out = Mux(sel, inTrue, inFalse) selがBool型として接続されたMux。
算術演算 SIntとUInt型で有効。
val sum = a + b or val sum = a +% b 加算(ビット拡張無し)
val sum = a +& b 加算(ビット拡張有り)
val diff = a - b or val diff = a -% b 減算(ビット拡張無し)
val diff = a -& b 減算(ビット拡張有り)
val prod = a * b 乗算
val div = a / b 除算
val mod = a % b 剰余
算術比較 SIntとUint型で有効。Boolを返す。
val gt = a > b 大なり
val gte = a >= b 以上
val lt = a < b 小なり
val lte = a <= b 以下

演算後のビット幅

Chiselの演算では、オペランドのビット幅に対して演算後のビット幅は以下のようなルールが存在する。

operation bit width
z = x + y or z = x +% y w(z) = max(w(x), w(y))
z = x +& y w(z) = max(w(x), w(y)) + 1
z = x - y or z = x -% y w(z) = max(w(x), w(y))
z = x -& y w(z) = max(w(x), w(y)) + 1
z = x & y w(z) = min(w(x), w(y))
z = Mux(c, x, y) w(z) = max(w(x), w(y))
z = w * y w(z) = w(x) + w(y)
z = x << n w(z) = w(x) + maxNum(n)
z = x >> n w(z) = w(x) - minNum(n)
z = Cat(x, y) w(z) = w(x) + w(y)
z = Fill(n, x) w(z) = w(x) * maxNum(n)

この中で、もしオペランド内にビット幅が不明な変数が入っている場合、これらの演算はエラーとなる。

Maker Faire Tokyo 2017見学

Maker Fair Tokyo, いつも面白そうだなと思いながらネットで見てて、でも毎年当日に気が付くので行けないのだが、今年は珍しく8/5(土)に気が付いたので8/6(日)に見学に行ってきた。

f:id:msyksphinz:20170806212949p:plain

せっかく写真を撮ったのにブレブレである。

印象としては、やはり3Dプリンタのデモが多くみられた。3DプリンタはMakerにとっては最高の道具なんだろうなあ。。 自分の好きなように、好きな方にの部品が作れるというのは、一昔前は大学の工場などに行って、自分で図面を引いて作らなければならなかった。

今は自分の好きな部品を作る、というだけでなく、3Dプリンタをレゴで制作してより別の用途で使ったりなどいろいろ発展させているのが面白かった。

レゴと言えば、レゴでいろんな作品を作っていたり(ラテアートを作ったりとか!)、子供の教育用という観点からみても、相変わらずの人気だった。 まあ積み木と同じ要領で、いろいろ組み合わせることができる遊びなので、手を付けやすいし、発展もしやすい。

個人的に感動したのは実物大R2-D2。これ、動くぞ!

f:id:msyksphinz:20170806213818p:plain

その他にも、ヘボいロボコン通称ヘボコンとか、画像認識を使ったマジックハンドの制御、ホバークラフトなど大がかりなものから小さなものまで本当に幅広かった。

次回は是非、出店する側で。。。時間とネタはあるかしら。

RocketChip周辺のモジュール接続図を作成した

RocketChipを改造しようにも、どこがどうなっているのか全く分からなかったので、頑張ってエディタで配線を追いかけながら接続構成図を作った。

だいたい3本のバスがRocketChipにつながっている。メインのバスと、L2に接続するためのバス、コヒーレント用のバスだ。

f:id:msyksphinz:20170805014844p:plain

VivadoでZynqのブロックデザインをTCLで生成する(3. FPGAでの動作確認)

前回の続き。bitファイルが完成したので、boot.binをSDカードコピーして動作させてみた。

本当に動作しているかどうかを確認するために、BlockRAMのデザインに少しだけギミックを入れた。

  always @ (posedge s_axi_aclk) begin
    up_rack_s      <= up_rreq_s;
    blockram_rdata <= reg_mem[up_raddr_s[9:0]];
  end

  always @ (posedge s_axi_aclk) begin
    up_raddr_s2 <= up_raddr_s;
  end

  assign up_rdata_s = (up_raddr_s2 == 14'h0) ? 32'hdeadbeef : blockram_rdata;

github.com

つまり、当該ブロックの先頭アドレスにリードアクセスをすると、0xdeadbeefが返されるはずだ。

当該ブロックのアドレスは、design_1_bd.tclで定義している。

ad_cpu_interconnect 0x43c00000 axi_blockram

さて、さっそく実行してみた。

f:id:msyksphinz:20170803012041p:plain

まず、当該ブロックの先頭へのリードを実行する。

devmem2 0x43c00000 word

0xdeadbeefが返ってきた。成功だ。

次に、次のワードに対して書き込みを実行する。書き込む値は0x12345678だ。

devmem2 0x43c00004 word 0x12345678

次に同じアドレスに対してリードをする。ちゃんと0x12345678が返ってきた。成功だ。

devmem2 0x43c00000 word