Chisel Manual
Chisel Manual
Chisel Manual
Introduction
Node
Nodes
Any hardware design in Chisel is ultimately represented by a graph of node objects. User code in
Chisel generate this graph of nodes, which is then
passed to the Chisel backends to be translated into
Verilog or C++ code. Nodes are defined as follows:
Lit
class Node {
// name assigned by user or from introspection
var name: String = ""
// incoming graph edges
def inputs: ArrayBuffer[Node]
// outgoing graph edges
def consumers: ArrayBuffer[Node]
// node specific width inference
def inferWidth: Int
// get width immediately inferrable
def getWidth: Int
// get first raw node
def getRawNode: Node
// convert to raw bits
def toBits: Bits
// convert to raw bits
def fromBits(x: Bits): this.type
// return lit value if inferrable else null
def litOf: Lit
Op
Updateable
Data
Reg
Mem
Lits
Ops
Types
A Chisel graph representing a hardware design contains raw and type nodes. The Chisel type system
is maintained separately from the underlying Scala
type system, and so type nodes are interspersed
between raw nodes to allow Chisel to check and respond to Chisel types. Chisel type nodes are erased
before the hardware design is translated into C++ or
Verilog. The getRawNode operator defined in the base
Node class, skips type nodes and returns the first
raw node found. Figure 2 shows the built-in Chisel
type hierarchy, with Data as the topmost node.
Data
UInt
Bits
Aggregate
Num
Bundle
ports UInt(dir = OUTPUT, width = 8) are specialized wires defining module interfaces, and additionally specify direction (described in Section 10), and
Vec
literals UInt(1) or UInt(1, 8) can be constructed using type object constructors specifying their value and optional width.
SInt
5.1
Bool
Bits
object Bits {
def apply(dir: PortDir = null,
width: Int = -1): Bits
// create literal from BigInt or Int
def apply(value: BigInt, width: Int = -1): Bits
// create literal from String using
// base_char digit+ string format
5.2
Bool
5.3
= 1).
Nums
Signed and unsigned integers are considered subsets of fixed-point numbers and are represented by
types SInt and UInt respectively:
object SInt {
def apply (dir: PortDir = null,
width: Int = -1): SInt
// create literal
def apply (value: BigInt, width: Int = -1): SInt
def apply (value: String, width: Int = -1): SInt
}
is equivalent to UInt(width
Num
UInt(1)
UInt("ha")
UInt("o12")
UInt("b1010")
Bools
object UInt {
def apply(dir: PortDir = null,
width: Int = -1): UInt
// create literal
def apply(value: BigInt, width: Int = -1): UInt
UInt
Op(|)
UInt
UInt
UInt
Op(&)
Op(&)
Lit(3)
UInt
UInt
UInt
UInt
UInt
Lit(1)
Lit(1)
Lit(2)
Lit(1)
Lit(2)
a = UInt(1)
b = a & UInt(2)
b | UInt(3)
Figure 3: Chisel Op/Lit graphs constructed with algebraic expressions showing the insertion of type nodes.
def apply(value: String, width: Int = -1): UInt
5.4
5.5
Bundles
object Vec {
def apply[T <: Data](elts: Seq[T]): Vec[T]
def apply[T <: Data](elt0: T, elts: T*): Vec[T]
def fill[T <: Data](n: Int)(type: => T): Vec[T]
def tabulate[T <: Data](n: Int)
(type: (Int) => T): Vec[T]
def tabulate[T <: Data](n1: Int, n2: Int)
(type: (Int, Int) => T): Vec[Vec[T]]
}
Bundles group together several named fields of potentially different types into a coherent unit, much
like a struct in C:
class Bundle extends Data {
// shallow named bundle elements
def elements: ArrayBuffer[(String, Data)]
}
Vecs
dex or dynamically using a UInt index, where dynamic access creates a virtual type node (representing a read port) that records the read using the
given address. In either case, users can wire to the
result of a read as follows:
v(a) := d
5.6
bit width
z
z
z
z
z
z
z
z
z
wz
wz
wz
wz
wz
wz
wz
wz
wz
=
=
=
=
=
=
=
=
=
x + y
x - y
x & y
Mux(c, x, y)
w * y
x << n
x >> n
Cat(x, y)
Fill(n, x)
=
=
=
=
=
=
=
=
=
max(wx, wy)
max(wx, wy)
max(wx, wy)
max(wx, wy)
wx + wy
wx + maxNum(n)
wx - minNum(n)
wx + wy
wx * maxNum(n)
manipulates a global condition stack with dynamic scope. Therefore, when creates a new condition that is in force across function calls. For example:
when
and
Updateables
val
val
val
val
Forward Declarations
=
=
=
=
RegUpdate(io.in)
RegReset(UInt(1, 8))
RegUpdate(io.in, UInt(1))
Reg(UInt(width = 8))
Mems
UInt()
UInt()
Mux(pcSel, brTarget, pcPlus4)
RegUpdate(pcNext)
pcReg + UInt(4)
Ports into Mems are created by applying a UInt index. A 32-entry register file with one write port and
two combinational read ports might be expressed
as follows:
addOut
The wiring operator := is used to wire up the connection after pcReg and addOut are defined. After
all assignments are made and the circuit is being
elaborated, it is an error if a forward declaration is
unassigned.
r1
r2
r3
r4
Regs
val ram1r1w =
Mem(UInt(width = 32), 1024, seqRead = true)
val dout = Reg(UInt())
when (wen) { ram1r1w(waddr) := wdata }
when (ren) { dout := ram1r1w(raddr) }
object Reg {
def apply[T <: Data]
(type: T, next: T = null, init: T = null): T
}
object RegNext {
def apply[T <: Data] (next: T, init: T = null): T
}
val ram1p =
Mem(UInt(width = 32), 1024, seqRead = true)
val dout = Reg(UInt())
when (wen) { ram1p(waddr) := wdata }
.elsewhen (ren) { dout := ram1p(raddr) }
object RegInit {
def apply[T <: Data] (init: T): T
}
val
val
val
val
Bool(INPUT)
Bool(INPUT)
Bool(INPUT)
Bool(OUTPUT)
Ports
Aggregate ports can be recursively constructed using either a vec or bundle with instances of Ports as
leaves.
11
=
=
=
=
}
io.out := (io.sel & io.in1) | (~io.sel & io.in0)
10
sel
in0
in1
out
Modules
12
BlackBox
Black boxes allow users to define interfaces to circuits defined outside of Chisel. The user defines:
a module as a subclass of BlackBox and
an io field with the interface.
class
val
val
val
}
RomIo
isVal
raddr
rdata
extends Bundle {
= Bool(INPUT)
= UInt(INPUT, 32)
= UInt(OUTPUT, 32)
13
arguments, then print or return a string, respectively. During simulation, printf prints the formatted string to the console on rising clock edges.
sprintf, on the other hand, returns the formatted
string as a bit vector.
Supported format specifiers are %b (binary number), %d (decimal number), %x (hexadecimal number),
and %s (string consisting of a sequence of 8-bit extended ASCII characters). (%% specifies a literal %.)
Unlike in C, there are no width modifiers: the bit
width of the corresponding argument determines
the width in the string representation.
The following example prints the line "0x4142
16706 AB" on cycles when c is true:
inputs
Chisel
outputs
val x = Bits(0x4142)
val s1 = sprintf("%x %s", x, x);
when (c) { printf("%d %s\n", x, s1); }
14
Assert
Runtime assertions are provided by the assert construct. During simulation, if an assertions argument is false on a rising clock edge, an error is
printed and simulation terminates. For example,
the following will terminate simulation after ten
clock cycles:
15
DUT
poke
step
peek
object chiselMainTest {
def apply[T <: Module]
(args: Array[String], comp: () => T)(
tester: T => Tester[T]): T
}
object chiselMain {
def apply[T <: Module]
(args: Array[String], comp: () => T): T
}
which when run creates C++ files named module_name.cpp and module_name.h in the directory specified with --targetDir dir_name argument.
Testing is a crucial part of circuit design, and
thus in Chisel we provide a mechanism for testing
circuits by providing test vectors within Scala using
subclasses of the Tester class:
poke(c.io.in0, i0)
step(1)
expect(c.io.out, (if (s == 1) i1 else i0))
}
}
}
}
assignments for each input of Mux2 is set to the appropriate values using poke. For this particular example, we are testing the Mux2 by hardcoding the
inputs to some known values and checking if the
output corresponds to the known one. To do this,
on each iteration we generate appropriate inputs to
the module and tell the simulation to assign these
values to the inputs of the device we are testing c,
step the circuit, and test the expected value. Finally,
the following shows how the tester is invoked:
chiselMainTest(args + "--test", () => new Mux2()){
c => new Mux2Tests(c)
}
16
C++ Emulator
reused.
18
18.1
class mod_t {
public:
// initialize module
virtual void init (void) { };
// compute all combinational logic
virtual void clock_lo (dat_t<1> reset) { };
// commit state updates
virtual void clock_hi (dat_t<1> reset) { };
// print printer specd node values to stdout
virtual void print (FILE* f) { };
// scan scanner specd node values from stdin
virtual bool scan (FILE* f) { return true; };
// dump vcd file
virtual void dump (FILE* f, int t) { };
};
#include "cpu.h"
int main (int argc, char* argv[]) {
cpu_t* c = new cpu_t();
int lim = (argc > 1) ? atoi(argv[1]) : -1;
c->init();
for (int t = 0; lim < 0 || t < lim; t++) {
dat_t<1> reset = LIT<1>(t == 0);
if (!c->scan(stdin)) break;
c->clock_lo(reset);
c->clock_hi(reset);
c->print(stdout);
}
}
17
18.2
Verilog
would produce a single Verilog file named modulename.v in the target directory. The file will contain
one module per module defined as submodules of
the top level module created in chiselMain. Modules
with the same interface and body are cached and
10
dut.fastClock_cnt = 0;
dut.slowClock = 6;
dut.slowClock_cnt = 0;
for (int i = 0; i < 12; i ++)
dut.reset();
for (int i = 0; i < 96; i ++)
dut.clock(LIT<1>(0));
18.3.2
18.3
In Verilog,
Verilog
C++
module emulator;
reg fastClock = 0, slowClock = 0,
resetFast = 1, resetSlow = 1;
wire [31:0] add, mul, test;
always #2 fastClock = ~fastClock;
always #4 slowClock = ~slowClock;
initial begin
#8
resetFast = 0;
resetSlow = 0;
#400
$finish;
end
ClkDomainTest dut (
.fastClock(fastClock),
.slowClock(slowClock),
.io_resetFast(resetFast),
.io_resetSlow(resetSlow),
.io_add(add), .io_mul(mul), .io_test(test));
endmodule
See
http://www.asic-world.com/verilog/
verifaq2.html for more information about
19
Extra Stuff
11
20
20.1
Standard Library
Math
20.2
Sequential
20.4
20.3
Decoupled
UInt
12
}
// Hardware module that is used to
// sequence n producers into 1 consumer.
// Priority is given to lower
// producer
// Example usage:
//
val arb = new Arbiter(UInt(), 2)
//
arb.io.in(0) <> producer0.io.out
//
arb.io.in(1) <> producer1.io.out
//
consumer.io.in <> arb.io.out
class Arbiter[T <: Data](type: T, n: Int)
extends Module
// Hardware module that is used to
// sequence n producers into 1 consumer.
// Producers are chosen in round robin
// order
// Example usage:
//
val arb = new RRArbiter(UInt(), 2)
//
arb.io.in(0) <> producer0.io.out
//
arb.io.in(1) <> producer1.io.out
//
consumer.io.in <> arb.io.out
class RRArbiter[T <: Data](type: T, n: Int)
extends Module
// Generic hardware queue. Required
// parameter entries controls the
// depth of the queues. The width of
// the queue is determined from the
// inputs.
// Example usage:
//
val q = new Queue(UInt(), 16)
//
q.io.enq <> producer.io.out
//
consumer.io.in <> q.io.deq
class Queue[T <: Data]
(type: T, entries: Int,
pipe: Boolean = false,
flow: Boolean = false
flushable: Boolean = false)
extends Module
// A hardware module that delays data
// coming down the pipeline by the
// number of cycles set by the
// latency parameter. Functionality
// is similar to ShiftRegister but
// this exposes a Pipe interface.
// Example usage:
//
val pipe = new Pipe(UInt())
//
pipe.io.enq <> produce.io.out
//
consumer.io.in <> pipe.io.deq
class Pipe[T <: Data]
(type: T, latency: Int = 1) extends Module
References
[1] Bachrach, J., Vo, H., Richards, B., Lee, Y., Waterman, A., Aviienis, Wawrzynek, J., Asanovic
Chisel: Constructing Hardware in a Scala Embedded Language in DAC 12.
[2] Odersky, M., Spoon, L., Venners, B. Programming in Scala by Artima.
13