ハードウェアの接続状態を記述して、
コンパイルして実行するとverilogコードを吐いてくれる
Chiselをもうちょっと触ってみた。
Scalaをよく知らないので、変な所があればそういうことだと思ってください。
信号のデータ型
チュートリアルに載っている
Chiselで定義済みのデータ型は
-
UInt
:符号なし整数 -
SInt
:符号あり整数 -
Bool
:ブーリアン
UInt
とSInt
は生成時にビット幅を指定できる
他にもFlo
(単精度浮動小数点数型)もあるっぽい
定数信号宣言
val foo = UInt(5) // 3bit
ポート宣言
val in = UInt(INPUT,8) // 8bit input port
ちなみにデフォルトで加算などの演算子が定義されている。
Bundle
を継承することでデータ型を作成することができる。
class Validatable[T <: Data] (dataSignal: T, validSignal : Bool= Bool(OUTPUT)) extends Bundle {
val data = dataSignal
val valid = validSignal
}
val foo = new Validatable(UInt(width = 8))
Vec
で配列を作成できる。
val foo = Vec.fill(5){UInt(width=8)} // 8bit x 5
val bar = foo(3)
Vec
に対してmap
とか使える
val io = new Bundle{
val x = Vec.fill(3){UInt(INPUT,width = 8)}
val y = Vec.fill(3){UInt(OUTPUT,width = 8)}
}
io.y := io.x.map(x => x + UInt(10))
/*
1 4
in ----2-------5----->
3 6
11 14
out----12------15----->
13 16
*/
Enum
で列挙型が定義できる
val s_even :: s_odd :: Nil = Enum(UInt(), 2)
io.even := s_even
io.odd := s_odd
モジュール
Module
を継承して作成。
class Mux2 extends Module {
val io = new Bundle{
val sel = UInt(INPUT, 1)
val in0 = UInt(INPUT, 1)
val in1 = UInt(INPUT, 1)
val out = UInt(OUTPUT, 1)
}
io.out := (io.sel & io.in1) | (~io.sel & io.in0)
}
:=
演算子は verilogのassign
に相当。
io.out := ...
の部分は
assign io_out = (io_sel & io_in1) | (~io_sel & io_in0)
に相当する。
(実際の変換は一時wireが定義されるのでこの通りにはならない)
サブモジュール
class TopModule extends Module {
val io = new Bundle{
val in = UInt(INPUT,1)
val out = UInt(OUTPUT,1)
}
val subModule = Module(new SubModule())
subModule.io.in := io.in
io.out := subModule.io.out
}
verilogと似たようなものだが
サブモジュール同士をつなぐ時に一々信号線を用意しなくて済む。
(Rx的な)filter,mapっぽいものを作ってみる
信号線をストリームとみなして、filterやらmapやら作ってみる。
先ほどのValidatable
に
class Validatable[T <: Data] (dataSignal: T, validSignal : Bool= Bool(OUTPUT)) extends Bundle {
val data = dataSignal
val valid = validSignal
}
こんなものを付け足して
def filter(f: T => Bool) : Validatable[T] ={
new Validatable[T](this.data, f(this.data) & this.valid)
}
def map[S <: Data](f: T => S): Validatable[S] ={
new Validatable[S](f(this.data), this.valid)
}
試しに入力ストリームから偶数を通して+10して出力ストリームに流す素敵なモジュールを作ってみる。
class NiceModule extends Module {
val io = new Bundle{
val in = new Validatable(UInt(width=8)).asInput
val out = new Validatable(UInt(width=8)).asOutput
}
io.out := io.in.filter(x=> x(0) === UInt(0)).map(x => x + UInt(10))
}
テストを書いて
class NiceModuleTester(c: NiceModule) extends Tester(c) {
poke(c.io.in.valid,1)
poke(c.io.in.data,2)
step(1)
expect(c.io.out.data, 12)
expect(c.io.out.valid, 1)
poke(c.io.in.data,3)
step(1)
expect(c.io.out.data, 13)
expect(c.io.out.valid, 0)
poke(c.io.in.valid,0)
poke(c.io.in.data,2)
step(1)
expect(c.io.out.data, 12)
expect(c.io.out.valid, 0)
}
/*
input:
data ---2----3----2---->
valid---T----T----F---->
expected:
data ---12---13---X---->
valid---T----F----F---->
X: don't care
*/
テストが通るのを確認して、verilogに変換すると
module NiceModule(
input [7:0] io_in_data,
input io_in_valid,
output[7:0] io_out_data,
output io_out_valid
);
wire T0;
wire T1;
wire T2;
wire[7:0] T3;
assign io_out_valid = T0;
assign T0 = T1 & io_in_valid;
assign T1 = T2 == 1'h0;
assign T2 = io_in_data[1'h0:1'h0];
assign io_out_data = T3;
assign T3 = io_in_data + 8'ha;
endmodule
自動で定義された内部信号T0~3
を、試しに手動で削ってみると
module NiceModule(
input [7:0] io_in_data,
input io_in_valid,
output[7:0] io_out_data,
output io_out_valid
);
wire isEven ;
assign isEven = (io_in_data[0] == 1'h0);
assign io_out_valid = isEven & io_in_valid;
assign io_out_data = io_in_data + 8'ha;
endmodule
一応意味はあっている。
まあ趣味で個人的に使う分には面白いのではないかと。