Chisel 手册 英文版

2023-11-15

Chisel Manual

Jonathan Bachrach, Huy Vo, Krste Asanović 
EECS Department, UC Berkeley 
{jrb|huytbvo|krste}@eecs.berkeley.edu

 

April 10, 2016

Introduction

This document is a manual for Chisel (Constructing Hardware In a Scala Embedded Language). Chisel is a hardware construction language embedded in the high-level programming language Scala. A separate Chisel tutorial document provides a gentle introduction to using Chisel, and should be read first. This manual provides a comprehensive overview and specification of the Chisel language, which is really only a set of special class definitions, predefined objects, and usage conventions within Scala. When you write a Chisel program you are actually writing a Scala program. In this manual, we presume that you already understand the basics of Scala. If you are unfamiliar with Scala, we recommend you consult one of the excellent Scala books ([3], [2]).

 

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:

 
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 
  // return value of lit if litOf is non null 
  def litValue(default: BigInt = BigInt(-1)): BigInt 
}
 

The uppermost levels of the node class hierarchy are shown in Figure 1. The basic categories are:

 

Lit

– constants or literals,

Op

– logical or arithmetic operations,

Updateable

– conditionally updated nodes,

Data

– typed wires or ports,

Reg

– positive-edge-triggered registers, and

Mem

– memories.


pict 

Figure 1: Node hierarchy.


Lits

Raw literals are represented as Lit nodes defined as follows:

 
class Lit extends Node { 
  // original value 
  val inputVal: BigInt 
}
 

Raw literals contain a collection of bits. Users do not create raw literals directly, but instead use type constructors defined in Section 5.

 

Ops

Raw operations are represented as Op nodes defined as follows:

 
class Op extends Node { 
  // op name used during emission 
  val op: String 
}
 

Ops compute a combinational function of their inputs.

 

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 2shows the built-in  Chisel type hierarchy, with Data as the topmost node.


pict 

Figure 2: Chisel type hierarchy.


Built-in scalar types include Bool, SInt, and UInt and built-in aggregate types Bundle and Vec allow the user to expand the set of Chisel datatypes with collections of other types.

Data itself is a node:

 
abstract class Data extends Node { 
  override def cloneType(): this.type = 
    this.getClass.newInstance. 
      asInstanceOf[this.type] 
  // simple conversions 
  def toSInt: SInt 
  def toUInt: UInt 
  def toBool: Bool 
  def toBits: Bits 
  // flatten out to leaves of tree 
  def flatten: Array[(String, Data)] 
  // port direction if leaf 
  def dir: PortDir 
  // change dir to OUTPUT 
  def asOutput: this.type 
  // change dir to INPUT 
  def asInput: this.type 
  // change polarity of dir 
  def flip: this.type 
  // assign to input 
  def :=[T <: Data](t: T) 
  // bulk assign to input 
  def <>(t: Data) 
}
 

The Data class has methods for converting between types and for delegating port methods to its single input. We will discuss ports in Section 10. Finally, users can override the cloneType method in their own type nodes (e.g., bundles) in order to reflect construction parameters that are necessary for cloning.

Data nodes can be used for four purposes:

  • types – UInt(width = 8) – record intermediate types in the graph specifying at minimum bitwidth (described in this section),
  • wires – UInt(width = 8) – serve as forward declarations of data allowing future conditional updates (described in Section 6),
  • ports – UInt(dir = OUTPUT, width = 8) – are specialized wires defining module interfaces, and additionally specify direction (described in Section 10), and
  • literals – UInt(1) or UInt(1, 8) – can be constructed using type object constructors specifying their value and optional width.

5.1 Bits

In Chisel, a raw collection of bits is represented by the Bits type defined as follows:

 
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 
  def apply(value: String, width: Int = -1): Bits 
} 
 
class Bits extends Data with Updateable { 
  // bitwise-not 
  def unary_~(): Bits 
  // bitwise-and 
  def &  (b: Bits): Bits 
  // bitwise-or 
  def |  (b: Bits): Bits 
  // bitwise-xor 
  def ^  (b: Bits): Bits 
  // and-reduction 
  def andR(): Bool 
  // or-reduction 
  def orR():  Bool 
  // xor-reduction 
  def xorR():  Bool 
  // logical NOT 
  def unary_!(): Bool 
  // logical AND 
  def && (b: Bool): Bool 
  // logical OR 
  def || (b: Bool): Bool 
  // equality 
  def ===(b: Bits): Bool 
  // inequality 
  def != (b: Bits): Bool 
  // logical left shift 
  def << (b: UInt): Bits 
  // logical right shift 
  def >> (b: UInt): Bits 
  // concatenate 
  def ## (b: Bits): Bits 
  // extract single bit, LSB is 0 
  def apply(x: Int): Bits 
  // extract bit field from end to start bit pos 
  def apply(hi: Int, lo: Int): Bits 
} 
 
def Cat[T <: Data](elt: T, elts: T*): Bits
 

Bits has methods for simple bit operations. Note that ## is binary concatenation, while Cat is an n-ary concatentation. To avoid colliding with Scala’s builtin ==, Chisel’s bitwise comparison is named ===.

A field of width n can be created from a single bit using Fill:

 
def Fill(n: Int, field: Bits): Bits
    

and two inputs can be selected using Mux:

 
def Mux[T <: Data](sel: Bits, cons: T, alt: T): T
 

 

Constant or literal values are expressed using Scala integers or strings passed to constructors for the types:

 
UInt(1)       // decimal 1-bit lit from Scala Int. 
UInt("ha")    // hex 4-bit lit from string. 
UInt("o12")   // octal 4-bit lit from string. 
UInt("b1010") // binary 4-bit lit from string.
 

producing a Lit as shown in the leftmost subfigure of Figure 3.

Operations return an actual operator node with a type node combining the input type nodes. See Figure 3 for successively more complicated examples.


 

pict pict pict
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.


5.2 Bools

Boolean values are represented as Bools:

 
object Bool { 
  def apply(dir: PortDir = null): Bool 
  // create literal 
  def apply(value: Boolean): Bool 
} 
 
class Bool extends UInt
 

Bool is equivalent to UInt(width = 1).

 

5.3 Nums

Num is a type node which defines arithmetic operations:

 
class Num extends Bits { 
  // Negation 
  def unary_-(): Bits 
  // Addition 
  def +(b: Num): Num 
  // Subtraction 
  def -(b: Num): Num 
  // Multiplication 
  def *(b: Num): Num 
  // Greater than 
  def >(b: Num): Bool 
  // Less than 
  def <(b: Num): Bool 
  // Less than or equal 
  def <=(b: Num): Bool 
  // Greater than or equal 
  def >=(b: Num): Bool 
}
 

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 
} 
 
class SInt extends Num 
 
object UInt { 
  def apply(dir: PortDir = null, 
            width: Int = -1): UInt 
  // create literal 
  def apply(value: BigInt, width: Int = -1): UInt 
  def apply(value: String, width: Int = -1): UInt 
} 
 
class UInt extends Num { 
  // arithmetic right shift 
  override def >> (b: UInt): SInt 
}
 

Signed fixed-point numbers, including integers, are represented using two’s-complement format.

 

5.4 Bundles

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)] 
}
 

The name and type of each element in a Bundle can be obtained with the elements method, and the flatten method returns the elements at the leaves for nested aggregates. Users can define new bundles by subclassing Bundle as follows:

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

Elements are accessed using Scala field access:

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

The names given to a bundle’s elements when they are emitted by a C++ or Verilog backend are obtained from their bundle field names, using Scala introspection.

 

5.5 Vecs

Vecs create an indexable vector of elements:

 
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)(gen: => T): Vec[T] 
  def tabulate[T <: Data](n: Int) 
        (gen: (Int) => T): Vec[T] 
  def tabulate[T <: Data](n1: Int, n2: Int) 
        (gen: (Int, Int) => T): Vec[Vec[T]] 
} 
 
class Vec[T <: Data](n: Int, val gen: () => T) 
    extends Data { 
  def apply(idx: UInt): T 
  def apply(idx: Int): T 
  def forall(p: T => Bool): Bool 
  def exists(p: T => Bool): Bool 
  def contains[T <: Bits](x: T): Bool 
  def count(p: T => Bool): UInt 
  def indexWhere(p: T => Bool): UInt 
  def lastIndexWhere(p: T => Bool): UInt 
}
 

with n elements of type defined with the gen thunk. Users can access elements statically with an Int index 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
 

Read-only memories can be expressed as Vecs of literals:

 
val rom = Vec(UInt(3), UInt(7), UInt(4), UInt(0)) { UInt(width=3) } 
val dout = rom(addr)
 

 

5.6 Bit Width Inference

Users are required to set bit widths of ports and registers, but otherwise, bit widths on nodes are automatically inferred unless set manually by the user (using Extract or Cat). The bit-width inference engine starts from the graph’s input ports and calculates node output bit widths from their respective input bit widths according to the following set of rules:

operation bit width
z = x + y wz = max(wx, wy)
z = x - y wz = max(wx, wy)
z = x & y wz = max(wx, wy)
z = Mux(c, x, y) wz = max(wx, wy)
z = w * y wz = wx + wy
z = x << n wz = wx + maxNum(n)
z = x >> n wz = wx - minNum(n)
z = Cat(x, y) wz = wx + wy
z = Fill(n, x) wz = wx * maxNum(n)
 


where for instance wz is the bit width of wire z, and the & rule applies to all bitwise logical operations.

The bit-width inference process continues until no bit width changes. Except for right shifts by known constant amounts, the bit-width inference rules specify output bit widths that are never smaller than the input bit widths, and thus, output bit widths either grow or stay the same. Furthermore, the width of a register must be specified by the user either explicitly or from the bitwidth of the reset value. From these two requirements, we can show that the bit-width inference process will converge to a fixpoint.

Shouldn't & return bitwidth that is min() of inputs?

 

Updateables

When describing the operation of wire and state nodes, it is often useful to give the specification as a series of conditional updates to the output value and to spread out these updates across several separate statements. For example, the output of a Data node can be referenced immediately, but its input can be set later. Updateable represents a conditionally updateable node, which accumulates accesses to the node and which can later generate muxes to combine these accesses in the circuit.

 
abstract class Updateable extends Node { 
  // conditional reads 
  def reads: Queue[(Bool, UInt)] 
  // conditional writes 
  def writes: Queue[(Bool, UInt, Node)] 
  // gen mux integrating all conditional writes 
  def genMuxes(default: Node) 
  override def := (x: Node): this.type 
}
 

Chisel provides conditional update rules in the form of the when construct to support this style of sequential logic description:

 
object when { 
  def apply(cond: Bool)(block: => Unit): when 
} 
 
class when (prevCond: Bool) { 
  def elsewhen (cond: Bool)(block: => Unit): when 
  def otherwise (block: => Unit): Unit 
}
 

when manipulates a global condition stack with dynamic scope. Therefore, when creates a new condition that is in force across function calls. For example:

 
def updateWhen (c: Bool, d: Data) = 
  when (c) { r := d } 
when (a) { 
  updateWhen(b, x) 
}
 

is the same as:

 
when (a) { 
  when (b) { r := x } 
}
 

Chisel provides some syntactic sugar for other common forms of conditional updates:

 
def unless(c: Bool)(block: => Unit) = 
  when (!c) { block )
    

and

 
def otherwise(block: => Unit) = 
  when (Bool(true)) { block }
 

We introduce the switch statement for conditional updates involving a series of comparisons against a common key:

 
def switch(c: UInt)(block: => Unit): Unit 
 
def is(v: Bool)(block: => Unit)
 

 

Forward Declarations

Purely combinational circuits are not allowed to have cycles between nodes, and Chisel will report an error if such a cycle is detected. Because they do not have cycles, legal combinational circuits can always be constructed in a feed-forward manner, by adding new nodes whose inputs are derived from nodes that have already been defined. Sequential circuits naturally have feedback between nodes, and so it is sometimes necessary to reference an output wire before the producing node has been defined. Because Scala evaluates program statements sequentially, we have allowed data nodes to serve as a wire providing a declaration of a node that can be used immediately, but whose input will be set later. For example, in a simple CPU, we need to define the pcPlus4 and brTarget wires so they can be referenced before definition:

 
val pcPlus4  = UInt() 
val brTarget = UInt() 
val pcNext   = Mux(pcSel, brTarget, pcPlus4) 
val pcReg    = RegUpdate(pcNext) 
pcPlus4     := pcReg + UInt(4) 
... 
brTarget    := 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.

 

Regs

The simplest form of state element supported by Chisel is a positive-edge-triggered register defined as follows:

 
object Reg { 
  def apply[T <: Data] 
    (data: T, next: T = null, init: T = null): T 
} 
 
object RegNext { 
  def apply[T <: Data] (next: T, init: T = null): T 
} 
 
object RegInit { 
  def apply[T <: Data] (init: T): T 
} 
 
class Reg extends Updateable
 

where it can be constructed as follows:

 
val r1 = RegUpdate(io.in) 
val r2 = RegReset(UInt(1, 8)) 
val r3 = RegUpdate(io.in, UInt(1)) 
val r4 = Reg(UInt(width = 8))
 

where resetVal is the value a reg takes on when implicit reset is Bool(true).

 

Mems

Chisel supports random-access memories via the Mem construct. Writes to Mems are positive-edge-triggered and reads are either combinational or positive-edge-triggered.

 
object Mem { 
  def apply[T <: Data](depth: Int, gen: => T, 
          seqRead: Boolean = false): Mem 
} 
 
class Mem[T <: Data](gen: () => T, depth: Int, 
      seqRead: Boolean = false) 
    extends Updateable { 
  def apply(idx: UInt): T 
}
 

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:

 
val rf = Mem(32, UInt(width = 64)) 
when (wen) { rf(waddr) := wdata } 
val dout1 = rf(waddr1) 
val dout2 = rf(waddr2)
 

If the optional parameter seqRead is set, Chisel will attempt to infer sequential read ports when a Reg is assigned the output of a Mem. A one-read, one-write SRAM might be described as follows:

 
val ram1r1w = 
  Mem(1024, UInt(width = 32), seqRead = true) 
val dout = Reg(UInt()) 
when (wen) { ram1r1w(waddr) := wdata } 
when (ren) { dout := ram1r1w(raddr) }
 

Single-ported SRAMs can be inferred when the read and write conditions are mutually exclusive in the same when chain:

 
val ram1p = 
  Mem(1024, UInt(width = 32), seqRead = true) 
val dout = Reg(UInt()) 
when (wen) { ram1p(waddr) := wdata } 
.elsewhen (ren) { dout := ram1p(raddr) }
 

If the same Mem address is both written and sequentially read on the same clock edge, or if a sequential read enable is cleared, then the read data is implementation-defined.

Mem also supports write masks for subword writes. A given bit is written if the corresponding mask bit is set.

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

 

10 Ports

Ports are Data derived nodes used as interfaces to hardware modules. A port is a directional version of a primitive Data object. Port directions are defined as follows:

 
trait PortDir 
object INPUT  extends PortDir 
object OUTPUT extends PortDir
 

Aggregate ports can be recursively constructed using either a vec or bundle with instances of Ports as leaves.

 

11 Modules

In Chisel, modules are very similar to modules in Verilog, defining a hierarchical structure in the generated circuit. The hierarchical module namespace is accessible in downstream tools to aid in debugging and physical layout. A user-defined module is defined as a class which:

  • inherits from Module,
  • contains an interface Bundle stored in a field named io, and
  • wires together subcircuits in its constructor.

Users write their own modules by subclassing Module which is defined as follows:

 
abstract class Module { 
  val io: Bundle 
  var name: String = "" 
  def compileV: Unit 
  def compileC: Unit 
}
 

and defining their own io field. For example, to define a two input mux, we would define a module as follows:

 
class Mux2 extends Module { 
  val io = new Bundle{ 
    val sel = Bool(INPUT) 
    val in0 = Bool(INPUT) 
    val in1 = Bool(INPUT) 
    val out = Bool(OUTPUT) 
  } 
  io.out := (io.sel & io.in1) | (~io.sel & io.in0) 
}
 

The := assignment operator, used in the body of a module definition, is a special operator in Chisel that wires the input of left-hand side to the output of the right-hand side. It is typically used to connect an output port to its definition.

The <> operator bulk connects interfaces of opposite gender between sibling modules or interfaces of same gender between parent/child modules. Bulk connections connect leaf ports using pathname matching. Connections are only made if one of the ports is non-null, allowing users to repeatedly bulk-connect partially filled interfaces. After all connections are made and the circuit is being elaborated, Chisel warns users if ports have other than exactly one connection to them.

The names given to the nodes and submodules stored in a module when they are emitted by a C++ or Verilog backend are obtained from their module field names, using Scala introspection. Use the function setName() to set the names for nodes or submodules.

 

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.
  • optionally a subclass of VerilogParameters

For example, one could define a simple ROM blackbox as:

 
class RomIo extends Bundle { 
  val isVal = Bool(INPUT) 
  val raddr = UInt(INPUT,  32) 
  val rdata = UInt(OUTPUT, 32) 
  raddr.setName("RADDR") 
} 
 
class RomParams extends VerilogParameters { 
  val MY_STR = "Test" 
  val MY_INT = 5 
} 
 
class Rom extends BlackBox { 
  val io = new RomIo() 
  val romParams = new RomParams() 
  setVerilogParameters(romParams) 
  renameClock(Driver.implicitClock, "clock_A") 
  renameClock("my_other_clock", "test_clock") 
  renameReset("rst") 
 
  // Define how to use in simulation here 
 
}
 

The parameters are transformed to the verilog parameters with names and values used in the class definition. setVerilogParameters can also take a string directly. The function renameClock can take a Clock object or a string name of the clock to rename the BlackBox output clock. The function renameReset will rename the implicit reset. If other resets need to be named, call setName(). An example of the use of setName() is shown in the io class. Rather than being called io_raddr for the io of the BlackBox, it will be RADDR. The blackbox behaves as a module in c simulation. This means you can implement the functionality of the BlackBox using the io so that you can verify your design.

 

13 Printf and Sprintf

Chisel provides the ability to format and print strings for debugging purposes. The printf and sprintf construct are similar to their C namesakes: they take a format string and a variable number of 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:

 
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 assertion’s 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:

 
val x = Reg(init = UInt(0, 4)) 
x := x + UInt(1) 
assert(x < UInt(10))
 

 

15 Main and Testing

In order to construct a circuit, the user calls chiselMain from their top level main function:

 
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.


 

pict

 

Figure 4: DUT run using a Tester object in Scala with stdin and stdout connected


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:

 
class Tester[T <: Module] 
    (val c: T, val isTrace: Boolean = true) { 
  var t: Int 
  var ok: Boolean 
  val rnd: Random 
  def int(x: Boolean): BigInt 
  def int(x: Int): BigInt 
  def int(x: Bits): BigInt 
  def reset(n: Int = 1) 
  def step(n: Int): Int 
  def pokeAt(data: Mem[T], index: Int, x: BigInt) 
  def poke(data: Bits, x: BigInt) 
  def poke(data: Aggregate, x: Array[BigInt]) 
  def peekAt(data: Mem[T], index: Int) 
  def peek(data: Bits): BigInt 
  def peek(data: Aggregate): Array[BigInt] 
  def expect (good: Boolean, msg: String): Boolean 
  def expect (data: Bits, target: BigInt): Boolean 
}
 

which binds a tester to a module and allows users to write tests using the given debug protocol. In particular, users utilize:

  • poke to set input port and state values,
  • step to execute the circuit one time unit,
  • peek to read port and state values, and
  • expect to compare peeked circuit values to expected arguments.

Users connect tester instances to modules using:

 
object chiselMainTest { 
  def apply[T <: Module] 
    (args: Array[String], comp: () => T)( 
     tester: T => Tester[T]): T 
}
 

When --test is given as an argument to chiselMain, a tester instance runs the Design Under Test (DUT) in a separate process with stdin and stdout connected so that debug commands can be sent to the DUT and responses can be received from the DUT as shown in Figure 4.

For example, in the following:

 
class Mux2Tests(c: Mux2) extends Tester(c) { 
  val n = pow(2, 3).toInt 
  for (s <- 0 until 2) { 
    for (i0 <- 0 until 2) { 
      for (i1 <- 0 until 2) { 
        poke(c.io.sel, s) 
        poke(c.io.in1, i1) 
        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) 
}
 

Finally, command arguments for chiselMain* are as follows: 

--targetDir target pathname prefix
--genHarness generate harness file for C++
--debug put all wires in C++ class file
--compile compiles generated C++
--test runs tests using C++ app
--backend v generate verilog
--backend c generate C++ (default)
--vcd enable vcd dumping
 

16 C++ Emulator

The C++ emulator is based on a fast multiword library using C++ templates. A single word is defined by val_t as follows:

 
typedef uint64_t val_t; 
typedef int64_t sval_t; 
typedef uint32_t half_val_t;
 

and multiwords are defined by dat_t as follows:

 
template <int w> 
class dat_t { 
 public: 
  const static int n_words; 
  inline int width ( void ); 
  inline int n_words_of ( void ); 
  inline bool to_bool ( void ); 
  inline val_t lo_word ( void ); 
  inline unsigned long to_ulong ( void ); 
  std::string to_str (); 
  dat_t<w> (); 
template <int sw> 
  dat_t<w> (const dat_t<sw>& src); 
  dat_t<w> (const dat_t<w>& src); 
  dat_t<w> (val_t val); 
template <int sw> 
  dat_t<w> mask(dat_t<sw> fill, int n); 
template <int dw> 
  dat_t<dw> mask(int n); 
template <int n> 
  dat_t<n> mask(void); 
  dat_t<w> operator + ( dat_t<w> o ); 
  dat_t<w> operator - ( dat_t<w> o ); 
  dat_t<w> operator - ( ); 
  dat_t<w+w> operator * ( dat_t<w> o ); 
  dat_t<w+w> fix_times_fix( dat_t<w> o ); 
  dat_t<w+w> ufix_times_fix( dat_t<w> o ); 
  dat_t<w+w> fix_times_ufix( dat_t<w> o ); 
  dat_t<1> operator < ( dat_t<w> o ); 
  dat_t<1> operator > ( dat_t<w> o ); 
  dat_t<1> operator >= ( dat_t<w> o ); 
  dat_t<1> operator <= ( dat_t<w> o ); 
  dat_t<1> gt ( dat_t<w> o ); 
  dat_t<1> gte ( dat_t<w> o ); 
  dat_t<1> lt ( dat_t<w> o ); 
  dat_t<1> lte ( dat_t<w> o ); 
  dat_t<w> operator ^ ( dat_t<w> o ); 
  dat_t<w> operator & ( dat_t<w> o ); 
  dat_t<w> operator | ( dat_t<w> o ); 
  dat_t<w> operator ~ ( void ); 
  dat_t<1> operator ! ( void ); 
  dat_t<1> operator && ( dat_t<1> o ); 
  dat_t<1> operator || ( dat_t<1> o ); 
  dat_t<1> operator == ( dat_t<w> o ); 
  dat_t<1> operator == ( datz_t<w> o ); 
  dat_t<1> operator != ( dat_t<w> o ); 
  dat_t<w> operator << ( int amount ); 
  dat_t<w> operator << ( dat_t<w> o ); 
  dat_t<w> operator >> ( int amount ); 
  dat_t<w> operator >> ( dat_t<w> o ); 
  dat_t<w> rsha ( dat_t<w> o); 
  dat_t<w>& operator = ( dat_t<w> o ); 
  dat_t<w> fill_bit(val_t bit); 
  dat_t<w> fill_byte 
    (val_t byte, int nb, int n); 
template <int dw, int n> 
  dat_t<dw> fill( void ); 
template <int dw, int nw> 
  dat_t<dw> fill( dat_t<nw> n ); 
template <int dw> 
  dat_t<dw> extract(); 
template <int dw> 
  dat_t<dw> extract(val_t e, val_t s); 
template <int dw, int iwe, int iws> 
  dat_t<dw> extract 
    (dat_t<iwe> e, dat_t<iws> s); 
template <int sw> 
  dat_t<w> inject 
    (dat_t<sw> src, val_t e, val_t s); 
template <int sw, int iwe, int iws> 
  dat_t<w> inject 
    (dat_t<sw> src, 
     dat_t<iwe> e, dat_t<iws> s); 
template <int dw> 
  dat_t<dw> log2(); 
  dat_t<1> bit(val_t b); 
  val_t msb(); 
template <int iw> 
  dat_t<1> bit(dat_t<iw> b) 
}
 
 
template <int w, int sw> 
  dat_t<w> DAT(dat_t<sw> dat); 
template <int w> 
  dat_t<w> LIT(val_t value); 
template <int w> dat_t<w> 
  mux ( dat_t<1> t, dat_t<w> c, dat_t<w> a )
 

where w is the bit width parameter.

The Chisel compiler compiles top level modules into a single flattened mod_t class that can be created and executed:

 
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) { }; 
};
 

Either the Chisel compiler can create a harness or the user can write a harness themselves. The following is an example of a harness for a CPU module:

 
#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 Verilog

Chisel generates Verilog when the v argument is passed into chiselMain. For example, from SBT, the following

 
run --v
 

would produce a single Verilog file named module-name.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 reused.

 

18 Multiple Clock Domains

Chisel 2.0 introduced support of multiple clock domains.

 

18.1 Creating Clock domains

In order to use multiple clock domains, users must create multiple clocks. In Chisel, clocks are first class nodes created with a reset signal parameter and defined as follows:

 
class Clock (reset: Bool) extends Node { 
  def reset: Bool // returns reset pin 
}
 

In Chisel there is a builtin implicit clock that state elements use by default:

 
var implicitClock = new Clock( implicitReset )
 

The clock for state elements and modules can be defined using an additional named parameter called clock:

 
Reg(... clock: Clock = implicitClock) 
Mem(... clock: Clock = implicitClock) 
Module(... clock: Clock = implicitClock)
 

 

18.2 Crossing Clock Domains

There are two ways that circuits can be defined to send data between clock domains. The first and most primitive way is by using a synchronizer circuit comprised of two registers as follows:

 
// signalA is in clock domain clockA, 
// want a version in clockB as signalB 
val s1 = Reg(init = UInt(0), clock = clockB) 
val s2 = Reg(init = UInt(0), clock = clockB) 
s1      := signalA 
s2      := s1; 
signalB := s2
    

Due to metastability issues, this technique is limited to communicating one bit data between domains.

The second and more general way to send data between domains is by using an asynchronous fifo:

 
class AsyncFifo[T<:Data](gen: T, entries: Int, enq_clk: Clock, deq_clock: Clock) 
  extends Module
 

We can then get a version of signalA from clock domains clockA to clockB by specifying the standard fifo parameters and the two clocks and then using the standard decoupled ready/valid signals:

 
val fifo = new AsyncFifo(Uint(width = 32), 2, clockA, clockB) 
fifo.io.enq.bits  := signalA 
signalB           := fifo.io.deq.bits 
fifo.io.enq.valid := condA 
fifo.io.deq.ready := condB 
...
 

 

18.3 Backend Specific Multiple Clock Domains

Clock domains can be mapped to both the C++ and Verilog backends in a domain-specific manner. For the purposes of showing how to drive a multi clock design, consider the example of hardware with two modules communicating using an AsyncFifo with each module on separate clocks: fastClock and slowClock.

 

18.3.1 C++

In the C++ backend, for every clock i there is a

  • uint64_t clk_i field representing the clock i’s period,
  • uint63_t clk_i_cnt field representing the clock i’s current count,
  • clock_lo_i and clock_hi_i,
  • int reset() function which ensures that all clock_lo and clock_hi functions are called at least once, and
  • int clock(reset) function which computes min delta, invokes appropriate clock_lo and clock_hi’s and returns min delta used.

In order to set up a C++ simulation, the user

  • initializes all period fields to desired period
  • initializes all count fields to desired phase,
  • calls reset and then
  • repeated calls clock to step the simulation.

The following is a C++ example of a main function for the slowClock / fastClock example:

 
int main(int argc, char** argv) { 
  ClkDomainTest_t dut; 
  dut.init(1); 
  dut.clk = 2; 
  dut.clk_cnt = 1; 
  dut.fastClock = 4; 
  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 Verilog

In Verilog,

  • Chisel creates a new port for each clock / reset,
  • Chisel wires all the clocks to the top module, and
  • the user must create an always block clock driver for every clock i.

The following is a Verilog example of a top level harness to drive the slowClock / fastClock example circuit:

 
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 simulating clocks in Verilog.

 

19 Extra Stuff

 
def ListLookup[T <: Bits] 
  (addr: UInt, default: List[T], 
   mapping: Array[(UInt, List[T])]): List[T] 
 
def Lookup[T <: Data] 
  (addr: UInt, default: T, 
   mapping: Seq[(UInt, T)]): T 
 
// n-way multiplexor 
def MuxCase[T <: Data] 
  (default: T, mapping: Seq[(Bool, T)]): T 
 
// n-way indexed multiplexer: 
def MuxLookup[S <: UInt, T <: Data] 
  (key: S, default: T, mapping: Seq[(S, T)]): T
    
 
// create n enum values of given type 
def Enum[T <: UInt] 
  (n: Int)(gen: => T): List[T] 
 
// create enum values of given type and names 
def Enum[T <: UInt] 
  (l: Symbol *)(gen: => T): Map[Symbol, T] 
 
// create enum values of given type and names 
def Enum[T <: UInt] 
  (l: List[Symbol])(gen: => T): Map[Symbol, T]
 

 

20 Standard Library

 

20.1 Math

 
// Returns the log base 2 of the input 
// Scala Integer rounded up 
def log2Up(in: Int): Int 
 
// Returns the log base 2 of the input 
// Scala Integer rounded down 
def log2Down(in: Int): Int 
 
// Returns true if the input Scala Integer 
// is a power of 2 
def isPow2(in: Int): Boolean 
 
// linear feedback shift register 
def LFSR16(increment: Bool = Bool(true)): UInt
 

 

20.2 Sequential

 
// Returns the n-cycle delayed version 
// of the input signal 
// Has an optional enable signal defaulting to true 
def ShiftRegister[T <: Data](in: T, n: Int, en = Bool(true)): T 
 
def Counter(cond: Bool, n: Int) = { 
  val c = RegReset(UInt(0, log2Up(n))) 
  val wrap = c === UInt(n-1) 
  when (cond) { 
    c := Mux(Bool(!isPow2(n)) && wrap, UInt(0), 
             c + UInt(1)) 
  } 
  (c, wrap && cond) 
}
 

 

20.3 UInt

 
// Returns the number of bits set in the 
// input signal. Causes an exception if 
// the input is wider than 32 bits. 
def PopCount(in: UInt): UInt 
 
// Returns the reverse the input signal 
def Reverse(in: UInt): UInt 
 
// returns the one hot encoding of 
// the input UInt 
def UIntToOH(in: UInt, width: Int): UInt 
 
// does the inverse of UIntToOH 
def OHToUInt(in: UInt): UInt 
def OHToUInt(in: Seq[Bool]): UInt 
 
// Builds a Mux tree out of the input 
// signal vector using a one hot encoded 
// select signal. Returns the output of 
// the Mux tree 
def Mux1H[T <: Data] 
      (sel: UInt, in: Vec[T]): T 
def Mux1H[T <: Data] 
      (sel: Vec[Bool], in: Vec[T]): T 
 
// Builds a Mux tree under the 
// assumption that multiple 
// select signals can be enabled. 
// Priority is given to the first 
// select signal. Returns the output 
// of the Mux tree. 
def PriorityMux[T <: Data] 
      (sel: UInt, in: Seq[T]): T 
def PriorityMux[T <: Data] 
      (sel: Seq[UInt], in: Seq[T]): T 
 
// Returns the bit position of the 
// trailing 1 in the input vector with 
// the assumption that multiple bits of 
// the input bit vector can be set 
def PriorityEncoder(in: UInt): UInt 
def PriorityEncoder(in: Seq[Bool]): UInt 
 
// Returns the bit position of the 
// trailing 1 in the input vector with 
// the assumption that only one bit in 
// the input vector can be set 
def PriorityEncoderOH(in: UInt): UInt 
def PriorityEncoderOH(in: Seq[Boo]): UInt
 

 

20.4 Decoupled

 
// Adds a ready-valid handshaking 
// protocol to any interface. The 
// standard used is that the 
// consumer uses the flipped 
// interface. 
class DecoupledIO[+T <: Data](gen: T) 
    extends Bundle { 
  val ready = Bool(INPUT) 
  val valid = Bool(OUTPUT) 
  val bits  = gen.cloneType.asOutput 
} 
 
// Adds a valid protocol to any 
// interface. The standard used is 
// that the consumer uses the 
// fliped interface. 
class ValidIO[+T <: Data](gen: T) 
    extends Bundle { 
  val valid = Bool(OUTPUT) 
  val bits  = gen.cloneType.asOutput 
} 
 
// 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](gen: 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](gen: 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] 
    (gen: T, entries: Int, 
     pipe: Boolean = false, 
     flow: 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

Chisel Manual

Jonathan Bachrach, Huy Vo, Krste Asanović 
EECS Department, UC Berkeley 
{jrb|huytbvo|krste}@eecs.berkeley.edu

 

April 10, 2016

1 Introduction

This document is a manual for Chisel (Constructing Hardware In a Scala Embedded Language). Chisel is a hardware construction language embedded in the high-level programming language Scala. A separate Chisel tutorial document provides a gentle introduction to using Chisel, and should be read first. This manual provides a comprehensive overview and specification of the Chisel language, which is really only a set of special class definitions, predefined objects, and usage conventions within Scala. When you write a Chisel program you are actually writing a Scala program. In this manual, we presume that you already understand the basics of Scala. If you are unfamiliar with Scala, we recommend you consult one of the excellent Scala books ([3], [2]).

 

2 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:

 
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 
  // return value of lit if litOf is non null 
  def litValue(default: BigInt = BigInt(-1)): BigInt 
}
 

The uppermost levels of the node class hierarchy are shown in Figure 1. The basic categories are:

 

Lit

– constants or literals,

Op

– logical or arithmetic operations,

Updateable

– conditionally updated nodes,

Data

– typed wires or ports,

Reg

– positive-edge-triggered registers, and

Mem

– memories.


 

pict 

Figure 1: Node hierarchy.


3 Lits

Raw literals are represented as Lit nodes defined as follows:

 
class Lit extends Node { 
  // original value 
  val inputVal: BigInt 
}
 

Raw literals contain a collection of bits. Users do not create raw literals directly, but instead use type constructors defined in Section 5.

 

4 Ops

Raw operations are represented as Op nodes defined as follows:

 
class Op extends Node { 
  // op name used during emission 
  val op: String 
}
 

Ops compute a combinational function of their inputs.

 

5 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 2shows the built-in  Chisel type hierarchy, with Data as the topmost node.


 

pict 

Figure 2: Chisel type hierarchy.


Built-in scalar types include Bool, SInt, and UInt and built-in aggregate types Bundle and Vec allow the user to expand the set of Chisel datatypes with collections of other types.

Data itself is a node:

 
abstract class Data extends Node { 
  override def cloneType(): this.type = 
    this.getClass.newInstance. 
      asInstanceOf[this.type] 
  // simple conversions 
  def toSInt: SInt 
  def toUInt: UInt 
  def toBool: Bool 
  def toBits: Bits 
  // flatten out to leaves of tree 
  def flatten: Array[(String, Data)] 
  // port direction if leaf 
  def dir: PortDir 
  // change dir to OUTPUT 
  def asOutput: this.type 
  // change dir to INPUT 
  def asInput: this.type 
  // change polarity of dir 
  def flip: this.type 
  // assign to input 
  def :=[T <: Data](t: T) 
  // bulk assign to input 
  def <>(t: Data) 
}
 

The Data class has methods for converting between types and for delegating port methods to its single input. We will discuss ports in Section 10. Finally, users can override the cloneType method in their own type nodes (e.g., bundles) in order to reflect construction parameters that are necessary for cloning.

Data nodes can be used for four purposes:

  • types – UInt(width = 8) – record intermediate types in the graph specifying at minimum bitwidth (described in this section),
  • wires – UInt(width = 8) – serve as forward declarations of data allowing future conditional updates (described in Section 6),
  • ports – UInt(dir = OUTPUT, width = 8) – are specialized wires defining module interfaces, and additionally specify direction (described in Section 10), and
  • literals – UInt(1) or UInt(1, 8) – can be constructed using type object constructors specifying their value and optional width.

5.1 Bits

In Chisel, a raw collection of bits is represented by the Bits type defined as follows:

 
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 
  def apply(value: String, width: Int = -1): Bits 
} 
 
class Bits extends Data with Updateable { 
  // bitwise-not 
  def unary_~(): Bits 
  // bitwise-and 
  def &  (b: Bits): Bits 
  // bitwise-or 
  def |  (b: Bits): Bits 
  // bitwise-xor 
  def ^  (b: Bits): Bits 
  // and-reduction 
  def andR(): Bool 
  // or-reduction 
  def orR():  Bool 
  // xor-reduction 
  def xorR():  Bool 
  // logical NOT 
  def unary_!(): Bool 
  // logical AND 
  def && (b: Bool): Bool 
  // logical OR 
  def || (b: Bool): Bool 
  // equality 
  def ===(b: Bits): Bool 
  // inequality 
  def != (b: Bits): Bool 
  // logical left shift 
  def << (b: UInt): Bits 
  // logical right shift 
  def >> (b: UInt): Bits 
  // concatenate 
  def ## (b: Bits): Bits 
  // extract single bit, LSB is 0 
  def apply(x: Int): Bits 
  // extract bit field from end to start bit pos 
  def apply(hi: Int, lo: Int): Bits 
} 
 
def Cat[T <: Data](elt: T, elts: T*): Bits
 

Bits has methods for simple bit operations. Note that ## is binary concatenation, while Cat is an n-ary concatentation. To avoid colliding with Scala’s builtin ==, Chisel’s bitwise comparison is named ===.

A field of width n can be created from a single bit using Fill:

 
def Fill(n: Int, field: Bits): Bits
    

and two inputs can be selected using Mux:

 
def Mux[T <: Data](sel: Bits, cons: T, alt: T): T
 

 

Constant or literal values are expressed using Scala integers or strings passed to constructors for the types:

 
UInt(1)       // decimal 1-bit lit from Scala Int. 
UInt("ha")    // hex 4-bit lit from string. 
UInt("o12")   // octal 4-bit lit from string. 
UInt("b1010") // binary 4-bit lit from string.
 

producing a Lit as shown in the leftmost subfigure of Figure 3.

Operations return an actual operator node with a type node combining the input type nodes. See Figure 3 for successively more complicated examples.

 


 

pict pict pict
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.


5.2 Bools

Boolean values are represented as Bools:

 
object Bool { 
  def apply(dir: PortDir = null): Bool 
  // create literal 
  def apply(value: Boolean): Bool 
} 
 
class Bool extends UInt
 

Bool is equivalent to UInt(width = 1).

 

5.3 Nums

Num is a type node which defines arithmetic operations:

 
class Num extends Bits { 
  // Negation 
  def unary_-(): Bits 
  // Addition 
  def +(b: Num): Num 
  // Subtraction 
  def -(b: Num): Num 
  // Multiplication 
  def *(b: Num): Num 
  // Greater than 
  def >(b: Num): Bool 
  // Less than 
  def <(b: Num): Bool 
  // Less than or equal 
  def <=(b: Num): Bool 
  // Greater than or equal 
  def >=(b: Num): Bool 
}
 

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 
} 
 
class SInt extends Num 
 
object UInt { 
  def apply(dir: PortDir = null, 
            width: Int = -1): UInt 
  // create literal 
  def apply(value: BigInt, width: Int = -1): UInt 
  def apply(value: String, width: Int = -1): UInt 
} 
 
class UInt extends Num { 
  // arithmetic right shift 
  override def >> (b: UInt): SInt 
}
 

Signed fixed-point numbers, including integers, are represented using two’s-complement format.

 

5.4 Bundles

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)] 
}
 

The name and type of each element in a Bundle can be obtained with the elements method, and the flatten method returns the elements at the leaves for nested aggregates. Users can define new bundles by subclassing Bundle as follows:

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

Elements are accessed using Scala field access:

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

The names given to a bundle’s elements when they are emitted by a C++ or Verilog backend are obtained from their bundle field names, using Scala introspection.

 

5.5 Vecs

Vecs create an indexable vector of elements:

 
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)(gen: => T): Vec[T] 
  def tabulate[T <: Data](n: Int) 
        (gen: (Int) => T): Vec[T] 
  def tabulate[T <: Data](n1: Int, n2: Int) 
        (gen: (Int, Int) => T): Vec[Vec[T]] 
} 
 
class Vec[T <: Data](n: Int, val gen: () => T) 
    extends Data { 
  def apply(idx: UInt): T 
  def apply(idx: Int): T 
  def forall(p: T => Bool): Bool 
  def exists(p: T => Bool): Bool 
  def contains[T <: Bits](x: T): Bool 
  def count(p: T => Bool): UInt 
  def indexWhere(p: T => Bool): UInt 
  def lastIndexWhere(p: T => Bool): UInt 
}
 

with n elements of type defined with the gen thunk. Users can access elements statically with an Int index 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
 

Read-only memories can be expressed as Vecs of literals:

 
val rom = Vec(UInt(3), UInt(7), UInt(4), UInt(0)) { UInt(width=3) } 
val dout = rom(addr)
 

 

5.6 Bit Width Inference

Users are required to set bit widths of ports and registers, but otherwise, bit widths on nodes are automatically inferred unless set manually by the user (using Extract or Cat). The bit-width inference engine starts from the graph’s input ports and calculates node output bit widths from their respective input bit widths according to the following set of rules:

operation bit width
z = x + y wz = max(wx, wy)
z = x - y wz = max(wx, wy)
z = x & y wz = max(wx, wy)
z = Mux(c, x, y) wz = max(wx, wy)
z = w * y wz = wx + wy
z = x << n wz = wx + maxNum(n)
z = x >> n wz = wx - minNum(n)
z = Cat(x, y) wz = wx + wy
z = Fill(n, x) wz = wx * maxNum(n)
 


where for instance wz is the bit width of wire z, and the & rule applies to all bitwise logical operations.

The bit-width inference process continues until no bit width changes. Except for right shifts by known constant amounts, the bit-width inference rules specify output bit widths that are never smaller than the input bit widths, and thus, output bit widths either grow or stay the same. Furthermore, the width of a register must be specified by the user either explicitly or from the bitwidth of the reset value. From these two requirements, we can show that the bit-width inference process will converge to a fixpoint.

Shouldn't & return bitwidth that is min() of inputs?

 

6 Updateables

When describing the operation of wire and state nodes, it is often useful to give the specification as a series of conditional updates to the output value and to spread out these updates across several separate statements. For example, the output of a Data node can be referenced immediately, but its input can be set later. Updateable represents a conditionally updateable node, which accumulates accesses to the node and which can later generate muxes to combine these accesses in the circuit.

 
abstract class Updateable extends Node { 
  // conditional reads 
  def reads: Queue[(Bool, UInt)] 
  // conditional writes 
  def writes: Queue[(Bool, UInt, Node)] 
  // gen mux integrating all conditional writes 
  def genMuxes(default: Node) 
  override def := (x: Node): this.type 
}
 

Chisel provides conditional update rules in the form of the when construct to support this style of sequential logic description:

 
object when { 
  def apply(cond: Bool)(block: => Unit): when 
} 
 
class when (prevCond: Bool) { 
  def elsewhen (cond: Bool)(block: => Unit): when 
  def otherwise (block: => Unit): Unit 
}
 

when manipulates a global condition stack with dynamic scope. Therefore, when creates a new condition that is in force across function calls. For example:

 
def updateWhen (c: Bool, d: Data) = 
  when (c) { r := d } 
when (a) { 
  updateWhen(b, x) 
}
 

is the same as:

 
when (a) { 
  when (b) { r := x } 
}
 

Chisel provides some syntactic sugar for other common forms of conditional updates:

 
def unless(c: Bool)(block: => Unit) = 
  when (!c) { block )
    

and

 
def otherwise(block: => Unit) = 
  when (Bool(true)) { block }
 

We introduce the switch statement for conditional updates involving a series of comparisons against a common key:

 
def switch(c: UInt)(block: => Unit): Unit 
 
def is(v: Bool)(block: => Unit)
 

 

7 Forward Declarations

Purely combinational circuits are not allowed to have cycles between nodes, and Chisel will report an error if such a cycle is detected. Because they do not have cycles, legal combinational circuits can always be constructed in a feed-forward manner, by adding new nodes whose inputs are derived from nodes that have already been defined. Sequential circuits naturally have feedback between nodes, and so it is sometimes necessary to reference an output wire before the producing node has been defined. Because Scala evaluates program statements sequentially, we have allowed data nodes to serve as a wire providing a declaration of a node that can be used immediately, but whose input will be set later. For example, in a simple CPU, we need to define the pcPlus4 and brTarget wires so they can be referenced before definition:

 
val pcPlus4  = UInt() 
val brTarget = UInt() 
val pcNext   = Mux(pcSel, brTarget, pcPlus4) 
val pcReg    = RegUpdate(pcNext) 
pcPlus4     := pcReg + UInt(4) 
... 
brTarget    := 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.

 

8 Regs

The simplest form of state element supported by Chisel is a positive-edge-triggered register defined as follows:

 
object Reg { 
  def apply[T <: Data] 
    (data: T, next: T = null, init: T = null): T 
} 
 
object RegNext { 
  def apply[T <: Data] (next: T, init: T = null): T 
} 
 
object RegInit { 
  def apply[T <: Data] (init: T): T 
} 
 
class Reg extends Updateable
 

where it can be constructed as follows:

 
val r1 = RegUpdate(io.in) 
val r2 = RegReset(UInt(1, 8)) 
val r3 = RegUpdate(io.in, UInt(1)) 
val r4 = Reg(UInt(width = 8))
 

where resetVal is the value a reg takes on when implicit reset is Bool(true).

 

9 Mems

Chisel supports random-access memories via the Mem construct. Writes to Mems are positive-edge-triggered and reads are either combinational or positive-edge-triggered.

 
object Mem { 
  def apply[T <: Data](depth: Int, gen: => T, 
          seqRead: Boolean = false): Mem 
} 
 
class Mem[T <: Data](gen: () => T, depth: Int, 
      seqRead: Boolean = false) 
    extends Updateable { 
  def apply(idx: UInt): T 
}
 

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:

 
val rf = Mem(32, UInt(width = 64)) 
when (wen) { rf(waddr) := wdata } 
val dout1 = rf(waddr1) 
val dout2 = rf(waddr2)
 

If the optional parameter seqRead is set, Chisel will attempt to infer sequential read ports when a Reg is assigned the output of a Mem. A one-read, one-write SRAM might be described as follows:

 
val ram1r1w = 
  Mem(1024, UInt(width = 32), seqRead = true) 
val dout = Reg(UInt()) 
when (wen) { ram1r1w(waddr) := wdata } 
when (ren) { dout := ram1r1w(raddr) }
 

Single-ported SRAMs can be inferred when the read and write conditions are mutually exclusive in the same when chain:

 
val ram1p = 
  Mem(1024, UInt(width = 32), seqRead = true) 
val dout = Reg(UInt()) 
when (wen) { ram1p(waddr) := wdata } 
.elsewhen (ren) { dout := ram1p(raddr) }
 

If the same Mem address is both written and sequentially read on the same clock edge, or if a sequential read enable is cleared, then the read data is implementation-defined.

Mem also supports write masks for subword writes. A given bit is written if the corresponding mask bit is set.

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

 

10 Ports

Ports are Data derived nodes used as interfaces to hardware modules. A port is a directional version of a primitive Data object. Port directions are defined as follows:

 
trait PortDir 
object INPUT  extends PortDir 
object OUTPUT extends PortDir
 

Aggregate ports can be recursively constructed using either a vec or bundle with instances of Ports as leaves.

 

11 Modules

In Chisel, modules are very similar to modules in Verilog, defining a hierarchical structure in the generated circuit. The hierarchical module namespace is accessible in downstream tools to aid in debugging and physical layout. A user-defined module is defined as a class which:

  • inherits from Module,
  • contains an interface Bundle stored in a field named io, and
  • wires together subcircuits in its constructor.

Users write their own modules by subclassing Module which is defined as follows:

 
abstract class Module { 
  val io: Bundle 
  var name: String = "" 
  def compileV: Unit 
  def compileC: Unit 
}
 

and defining their own io field. For example, to define a two input mux, we would define a module as follows:

 
class Mux2 extends Module { 
  val io = new Bundle{ 
    val sel = Bool(INPUT) 
    val in0 = Bool(INPUT) 
    val in1 = Bool(INPUT) 
    val out = Bool(OUTPUT) 
  } 
  io.out := (io.sel & io.in1) | (~io.sel & io.in0) 
}
 

The := assignment operator, used in the body of a module definition, is a special operator in Chisel that wires the input of left-hand side to the output of the right-hand side. It is typically used to connect an output port to its definition.

The <> operator bulk connects interfaces of opposite gender between sibling modules or interfaces of same gender between parent/child modules. Bulk connections connect leaf ports using pathname matching. Connections are only made if one of the ports is non-null, allowing users to repeatedly bulk-connect partially filled interfaces. After all connections are made and the circuit is being elaborated, Chisel warns users if ports have other than exactly one connection to them.

The names given to the nodes and submodules stored in a module when they are emitted by a C++ or Verilog backend are obtained from their module field names, using Scala introspection. Use the function setName() to set the names for nodes or submodules.

 

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.
  • optionally a subclass of VerilogParameters

For example, one could define a simple ROM blackbox as:

 
class RomIo extends Bundle { 
  val isVal = Bool(INPUT) 
  val raddr = UInt(INPUT,  32) 
  val rdata = UInt(OUTPUT, 32) 
  raddr.setName("RADDR") 
} 
 
class RomParams extends VerilogParameters { 
  val MY_STR = "Test" 
  val MY_INT = 5 
} 
 
class Rom extends BlackBox { 
  val io = new RomIo() 
  val romParams = new RomParams() 
  setVerilogParameters(romParams) 
  renameClock(Driver.implicitClock, "clock_A") 
  renameClock("my_other_clock", "test_clock") 
  renameReset("rst") 
 
  // Define how to use in simulation here 
 
}
 

The parameters are transformed to the verilog parameters with names and values used in the class definition. setVerilogParameters can also take a string directly. The function renameClock can take a Clock object or a string name of the clock to rename the BlackBox output clock. The function renameReset will rename the implicit reset. If other resets need to be named, call setName(). An example of the use of setName() is shown in the io class. Rather than being called io_raddr for the io of the BlackBox, it will be RADDR. The blackbox behaves as a module in c simulation. This means you can implement the functionality of the BlackBox using the io so that you can verify your design.

 

13 Printf and Sprintf

Chisel provides the ability to format and print strings for debugging purposes. The printf and sprintf construct are similar to their C namesakes: they take a format string and a variable number of 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:

 
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 assertion’s 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:

 
val x = Reg(init = UInt(0, 4)) 
x := x + UInt(1) 
assert(x < UInt(10))
 

 

15 Main and Testing

In order to construct a circuit, the user calls chiselMain from their top level main function:

 
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.


 

 

pict

 

Figure 4: DUT run using a Tester object in Scala with stdin and stdout connected


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:

 
class Tester[T <: Module] 
    (val c: T, val isTrace: Boolean = true) { 
  var t: Int 
  var ok: Boolean 
  val rnd: Random 
  def int(x: Boolean): BigInt 
  def int(x: Int): BigInt 
  def int(x: Bits): BigInt 
  def reset(n: Int = 1) 
  def step(n: Int): Int 
  def pokeAt(data: Mem[T], index: Int, x: BigInt) 
  def poke(data: Bits, x: BigInt) 
  def poke(data: Aggregate, x: Array[BigInt]) 
  def peekAt(data: Mem[T], index: Int) 
  def peek(data: Bits): BigInt 
  def peek(data: Aggregate): Array[BigInt] 
  def expect (good: Boolean, msg: String): Boolean 
  def expect (data: Bits, target: BigInt): Boolean 
}
 

which binds a tester to a module and allows users to write tests using the given debug protocol. In particular, users utilize:

  • poke to set input port and state values,
  • step to execute the circuit one time unit,
  • peek to read port and state values, and
  • expect to compare peeked circuit values to expected arguments.

Users connect tester instances to modules using:

 
object chiselMainTest { 
  def apply[T <: Module] 
    (args: Array[String], comp: () => T)( 
     tester: T => Tester[T]): T 
}
 

When --test is given as an argument to chiselMain, a tester instance runs the Design Under Test (DUT) in a separate process with stdin and stdout connected so that debug commands can be sent to the DUT and responses can be received from the DUT as shown in Figure 4.

For example, in the following:

 
class Mux2Tests(c: Mux2) extends Tester(c) { 
  val n = pow(2, 3).toInt 
  for (s <- 0 until 2) { 
    for (i0 <- 0 until 2) { 
      for (i1 <- 0 until 2) { 
        poke(c.io.sel, s) 
        poke(c.io.in1, i1) 
        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) 
}
 

Finally, command arguments for chiselMain* are as follows: 

--targetDir target pathname prefix
--genHarness generate harness file for C++
--debug put all wires in C++ class file
--compile compiles generated C++
--test runs tests using C++ app
--backend v generate verilog
--backend c generate C++ (default)
--vcd enable vcd dumping
 

16 C++ Emulator

The C++ emulator is based on a fast multiword library using C++ templates. A single word is defined by val_t as follows:

 
typedef uint64_t val_t; 
typedef int64_t sval_t; 
typedef uint32_t half_val_t;
 

and multiwords are defined by dat_t as follows:

 
template <int w> 
class dat_t { 
 public: 
  const static int n_words; 
  inline int width ( void ); 
  inline int n_words_of ( void ); 
  inline bool to_bool ( void ); 
  inline val_t lo_word ( void ); 
  inline unsigned long to_ulong ( void ); 
  std::string to_str (); 
  dat_t<w> (); 
template <int sw> 
  dat_t<w> (const dat_t<sw>& src); 
  dat_t<w> (const dat_t<w>& src); 
  dat_t<w> (val_t val); 
template <int sw> 
  dat_t<w> mask(dat_t<sw> fill, int n); 
template <int dw> 
  dat_t<dw> mask(int n); 
template <int n> 
  dat_t<n> mask(void); 
  dat_t<w> operator + ( dat_t<w> o ); 
  dat_t<w> operator - ( dat_t<w> o ); 
  dat_t<w> operator - ( ); 
  dat_t<w+w> operator * ( dat_t<w> o ); 
  dat_t<w+w> fix_times_fix( dat_t<w> o ); 
  dat_t<w+w> ufix_times_fix( dat_t<w> o ); 
  dat_t<w+w> fix_times_ufix( dat_t<w> o ); 
  dat_t<1> operator < ( dat_t<w> o ); 
  dat_t<1> operator > ( dat_t<w> o ); 
  dat_t<1> operator >= ( dat_t<w> o ); 
  dat_t<1> operator <= ( dat_t<w> o ); 
  dat_t<1> gt ( dat_t<w> o ); 
  dat_t<1> gte ( dat_t<w> o ); 
  dat_t<1> lt ( dat_t<w> o ); 
  dat_t<1> lte ( dat_t<w> o ); 
  dat_t<w> operator ^ ( dat_t<w> o ); 
  dat_t<w> operator & ( dat_t<w> o ); 
  dat_t<w> operator | ( dat_t<w> o ); 
  dat_t<w> operator ~ ( void ); 
  dat_t<1> operator ! ( void ); 
  dat_t<1> operator && ( dat_t<1> o ); 
  dat_t<1> operator || ( dat_t<1> o ); 
  dat_t<1> operator == ( dat_t<w> o ); 
  dat_t<1> operator == ( datz_t<w> o ); 
  dat_t<1> operator != ( dat_t<w> o ); 
  dat_t<w> operator << ( int amount ); 
  dat_t<w> operator << ( dat_t<w> o ); 
  dat_t<w> operator >> ( int amount ); 
  dat_t<w> operator >> ( dat_t<w> o ); 
  dat_t<w> rsha ( dat_t<w> o); 
  dat_t<w>& operator = ( dat_t<w> o ); 
  dat_t<w> fill_bit(val_t bit); 
  dat_t<w> fill_byte 
    (val_t byte, int nb, int n); 
template <int dw, int n> 
  dat_t<dw> fill( void ); 
template <int dw, int nw> 
  dat_t<dw> fill( dat_t<nw> n ); 
template <int dw> 
  dat_t<dw> extract(); 
template <int dw> 
  dat_t<dw> extract(val_t e, val_t s); 
template <int dw, int iwe, int iws> 
  dat_t<dw> extract 
    (dat_t<iwe> e, dat_t<iws> s); 
template <int sw> 
  dat_t<w> inject 
    (dat_t<sw> src, val_t e, val_t s); 
template <int sw, int iwe, int iws> 
  dat_t<w> inject 
    (dat_t<sw> src, 
     dat_t<iwe> e, dat_t<iws> s); 
template <int dw> 
  dat_t<dw> log2(); 
  dat_t<1> bit(val_t b); 
  val_t msb(); 
template <int iw> 
  dat_t<1> bit(dat_t<iw> b) 
}
 
 
template <int w, int sw> 
  dat_t<w> DAT(dat_t<sw> dat); 
template <int w> 
  dat_t<w> LIT(val_t value); 
template <int w> dat_t<w> 
  mux ( dat_t<1> t, dat_t<w> c, dat_t<w> a )
 

where w is the bit width parameter.

The Chisel compiler compiles top level modules into a single flattened mod_t class that can be created and executed:

 
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) { }; 
};
 

Either the Chisel compiler can create a harness or the user can write a harness themselves. The following is an example of a harness for a CPU module:

 
#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 Verilog

Chisel generates Verilog when the v argument is passed into chiselMain. For example, from SBT, the following

 
run --v
 

would produce a single Verilog file named module-name.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 reused.

 

18 Multiple Clock Domains

Chisel 2.0 introduced support of multiple clock domains.

 

18.1 Creating Clock domains

In order to use multiple clock domains, users must create multiple clocks. In Chisel, clocks are first class nodes created with a reset signal parameter and defined as follows:

 
class Clock (reset: Bool) extends Node { 
  def reset: Bool // returns reset pin 
}
 

In Chisel there is a builtin implicit clock that state elements use by default:

 
var implicitClock = new Clock( implicitReset )
 

The clock for state elements and modules can be defined using an additional named parameter called clock:

 
Reg(... clock: Clock = implicitClock) 
Mem(... clock: Clock = implicitClock) 
Module(... clock: Clock = implicitClock)
 

 

18.2 Crossing Clock Domains

There are two ways that circuits can be defined to send data between clock domains. The first and most primitive way is by using a synchronizer circuit comprised of two registers as follows:

 
// signalA is in clock domain clockA, 
// want a version in clockB as signalB 
val s1 = Reg(init = UInt(0), clock = clockB) 
val s2 = Reg(init = UInt(0), clock = clockB) 
s1      := signalA 
s2      := s1; 
signalB := s2
    

Due to metastability issues, this technique is limited to communicating one bit data between domains.

The second and more general way to send data between domains is by using an asynchronous fifo:

 
class AsyncFifo[T<:Data](gen: T, entries: Int, enq_clk: Clock, deq_clock: Clock) 
  extends Module
 

We can then get a version of signalA from clock domains clockA to clockB by specifying the standard fifo parameters and the two clocks and then using the standard decoupled ready/valid signals:

 
val fifo = new AsyncFifo(Uint(width = 32), 2, clockA, clockB) 
fifo.io.enq.bits  := signalA 
signalB           := fifo.io.deq.bits 
fifo.io.enq.valid := condA 
fifo.io.deq.ready := condB 
...
 

 

18.3 Backend Specific Multiple Clock Domains

Clock domains can be mapped to both the C++ and Verilog backends in a domain-specific manner. For the purposes of showing how to drive a multi clock design, consider the example of hardware with two modules communicating using an AsyncFifo with each module on separate clocks: fastClock and slowClock.

 

18.3.1 C++

In the C++ backend, for every clock i there is a

  • uint64_t clk_i field representing the clock i’s period,
  • uint63_t clk_i_cnt field representing the clock i’s current count,
  • clock_lo_i and clock_hi_i,
  • int reset() function which ensures that all clock_lo and clock_hi functions are called at least once, and
  • int clock(reset) function which computes min delta, invokes appropriate clock_lo and clock_hi’s and returns min delta used.

In order to set up a C++ simulation, the user

  • initializes all period fields to desired period
  • initializes all count fields to desired phase,
  • calls reset and then
  • repeated calls clock to step the simulation.

The following is a C++ example of a main function for the slowClock / fastClock example:

 
int main(int argc, char** argv) { 
  ClkDomainTest_t dut; 
  dut.init(1); 
  dut.clk = 2; 
  dut.clk_cnt = 1; 
  dut.fastClock = 4; 
  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 Verilog

In Verilog,

  • Chisel creates a new port for each clock / reset,
  • Chisel wires all the clocks to the top module, and
  • the user must create an always block clock driver for every clock i.

The following is a Verilog example of a top level harness to drive the slowClock / fastClock example circuit:

 
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 simulating clocks in Verilog.

 

19 Extra Stuff

 
def ListLookup[T <: Bits] 
  (addr: UInt, default: List[T], 
   mapping: Array[(UInt, List[T])]): List[T] 
 
def Lookup[T <: Data] 
  (addr: UInt, default: T, 
   mapping: Seq[(UInt, T)]): T 
 
// n-way multiplexor 
def MuxCase[T <: Data] 
  (default: T, mapping: Seq[(Bool, T)]): T 
 
// n-way indexed multiplexer: 
def MuxLookup[S <: UInt, T <: Data] 
  (key: S, default: T, mapping: Seq[(S, T)]): T
    
 
// create n enum values of given type 
def Enum[T <: UInt] 
  (n: Int)(gen: => T): List[T] 
 
// create enum values of given type and names 
def Enum[T <: UInt] 
  (l: Symbol *)(gen: => T): Map[Symbol, T] 
 
// create enum values of given type and names 
def Enum[T <: UInt] 
  (l: List[Symbol])(gen: => T): Map[Symbol, T]
 

 

20 Standard Library

 

20.1 Math

 
// Returns the log base 2 of the input 
// Scala Integer rounded up 
def log2Up(in: Int): Int 
 
// Returns the log base 2 of the input 
// Scala Integer rounded down 
def log2Down(in: Int): Int 
 
// Returns true if the input Scala Integer 
// is a power of 2 
def isPow2(in: Int): Boolean 
 
// linear feedback shift register 
def LFSR16(increment: Bool = Bool(true)): UInt
 

 

20.2 Sequential

 
// Returns the n-cycle delayed version 
// of the input signal 
// Has an optional enable signal defaulting to true 
def ShiftRegister[T <: Data](in: T, n: Int, en = Bool(true)): T 
 
def Counter(cond: Bool, n: Int) = { 
  val c = RegReset(UInt(0, log2Up(n))) 
  val wrap = c === UInt(n-1) 
  when (cond) { 
    c := Mux(Bool(!isPow2(n)) && wrap, UInt(0), 
             c + UInt(1)) 
  } 
  (c, wrap && cond) 
}
 

 

20.3 UInt

 
// Returns the number of bits set in the 
// input signal. Causes an exception if 
// the input is wider than 32 bits. 
def PopCount(in: UInt): UInt 
 
// Returns the reverse the input signal 
def Reverse(in: UInt): UInt 
 
// returns the one hot encoding of 
// the input UInt 
def UIntToOH(in: UInt, width: Int): UInt 
 
// does the inverse of UIntToOH 
def OHToUInt(in: UInt): UInt 
def OHToUInt(in: Seq[Bool]): UInt 
 
// Builds a Mux tree out of the input 
// signal vector using a one hot encoded 
// select signal. Returns the output of 
// the Mux tree 
def Mux1H[T <: Data] 
      (sel: UInt, in: Vec[T]): T 
def Mux1H[T <: Data] 
      (sel: Vec[Bool], in: Vec[T]): T 
 
// Builds a Mux tree under the 
// assumption that multiple 
// select signals can be enabled. 
// Priority is given to the first 
// select signal. Returns the output 
// of the Mux tree. 
def PriorityMux[T <: Data] 
      (sel: UInt, in: Seq[T]): T 
def PriorityMux[T <: Data] 
      (sel: Seq[UInt], in: Seq[T]): T 
 
// Returns the bit position of the 
// trailing 1 in the input vector with 
// the assumption that multiple bits of 
// the input bit vector can be set 
def PriorityEncoder(in: UInt): UInt 
def PriorityEncoder(in: Seq[Bool]): UInt 
 
// Returns the bit position of the 
// trailing 1 in the input vector with 
// the assumption that only one bit in 
// the input vector can be set 
def PriorityEncoderOH(in: UInt): UInt 
def PriorityEncoderOH(in: Seq[Boo]): UInt
 

 

20.4 Decoupled

 
// Adds a ready-valid handshaking 
// protocol to any interface. The 
// standard used is that the 
// consumer uses the flipped 
// interface. 
class DecoupledIO[+T <: Data](gen: T) 
    extends Bundle { 
  val ready = Bool(INPUT) 
  val valid = Bool(OUTPUT) 
  val bits  = gen.cloneType.asOutput 
} 
 
// Adds a valid protocol to any 
// interface. The standard used is 
// that the consumer uses the 
// fliped interface. 
class ValidIO[+T <: Data](gen: T) 
    extends Bundle { 
  val valid = Bool(OUTPUT) 
  val bits  = gen.cloneType.asOutput 
} 
 
// 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](gen: 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](gen: 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] 
    (gen: T, entries: Int, 
     pipe: Boolean = false, 
     flow: 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] 
    (gen: T, latency: Int = 1) extends Module
 

 

 pipe = new Pipe(UInt()) 
//    pipe.io.enq <> produce.io.out 
//    consumer.io.in <> pipe.io.deq 
class Pipe[T <: Data] 
    (gen: T, latency: Int = 1) extends Module
 

 

References

 

[1]   Bachrach, J., Vo, H., Richards, B., Lee, Y., Waterman, A., Avižienis, Wawrzynek, J., Asanović Chisel: Constructing Hardware in a Scala Embedded Language in DAC ’12.

[2]   Odersky, M., Spoon, L., Venners, B. Programming in Scala by Artima.

[3]   Payne, A., Wampler, D. Programming Scala by O’Reilly books.


Copyright © 2012-2014 The Regents of the University of California ‐ All Rights Reserved

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Chisel 手册 英文版 的相关文章

  • 作物叶片病害识别系统

    介绍 由于植物疾病的检测在农业领域中起着重要作用 因为植物疾病是相当自然的现象 如果在这个领域不采取适当的护理措施 就会对植物产生严重影响 进而影响相关产品的质量 数量或产量 植物疾病会引起疾病的周期性爆发 导致大规模死亡 这些问题需要在初
  • 如何快速申请GPT账号?

    详情点击链接 如何快速申请GPT账号 一OpenAI 1 最新大模型GPT 4 Turbo 2 最新发布的高级数据分析 AI画图 图像识别 文档API 3 GPT Store 4 从0到1创建自己的GPT应用 5 模型Gemini以及大模型
  • 人工智能 AI 如何让我们的生活更加便利

    每个人都可以从新技术中获益 一想到工作或生活更为便利 简捷且拥有更多空余时间 谁会不为之高兴呢 借助人工智能 每天能够多一些空余时间 或丰富自己的业余生活 为培养日常兴趣爱好增添一点便利 从电子阅读器到智能家居 再到植物识别应用和智能室内花
  • 史上最全自动驾驶岗位介绍

    作者 自动驾驶转型者 编辑 汽车人 原文链接 https zhuanlan zhihu com p 353480028 点击下方 卡片 关注 自动驾驶之心 公众号 ADAS巨卷干货 即可获取 点击进入 自动驾驶之心 求职交流 技术交流群 本
  • 考虑光伏出力利用率的电动汽车充电站能量调度策略研究(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码 数据
  • 基于节点电价的电网对电动汽车接纳能力评估模型研究(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码 数据
  • 基于节点电价的电网对电动汽车接纳能力评估模型研究(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码 数据
  • RISC-V 中的旋转位

    嘿 我对 RISC V 还算陌生 我的练习题之一是 将 0x0000000000000123 的值右移 4 位 预期结果为 0x3000000000000012 即所有十六进制数字向右移动一位 而最右边的一位移动到前面 到目前为止 我了解了
  • 如何使用 don't cares 参数化 case 语句?

    我有一条称为输入的电线 我想检测前导的数量 我正在尝试创建一个模块 该模块使用下面的 case 语句根据前导零的数量更改输出数据 然而 输入的大小是可参数化的 如果 X 是固定值 4 我将创建一个 case 语句 case input 4
  • verilog $readmemh 对于 50x50 像素 RGB 图像花费太多时间

    我正在尝试编译用于 FPGA 编程的 verilog 代码 我将在其中实现 VGA 应用程序 我使用 QuartusII 和 Altera 我正在尝试正确使用 readmemh 来逐像素获取图片 现在 我已经使用 matlab 将图片转换为
  • GDB 中断命令不会在命令文件中执行

    我有一个调试脚本 它执行以下操作 设置两个断点 让我们称呼他们吧start and end断点 启动后 脚本将继续执行 直到start命中断点 once start命中断点 我执行单步执行 直到end命中断点 这是我的命令文件 end br
  • 向左旋转 verilog 大小写

    我的任务是用verilog编写一个16位ALU 当我做需要旋转操作数和进行2的补码加法和减法的部分时 我发现了困难 我知道如何用纸和铅笔解决这个问题 但我无法弄清楚如何在 Verilog 中做到这一点 例如 A表示为a15 a14 a13
  • 在 Verilog 设计中产生时钟故障

    我正在使用 Verilog 设计芯片 我有一个 3 位计数器 我希望当计数器处于第 8 次循环时 应该有一个时钟故障 之后就可以正常工作了 在 Verilog 设计中产生时钟故障的可能方法是什么 在时钟信号上注入毛刺的一种方法是使用forc
  • 标识符必须用端口模式声明:busy。 (Verilog)

    我有如下所示的 Verilog 代码 当我编译它时 我收到以下错误消息 并且代码的第一行突出显示 Error 标识符必须用端口模式声明 busy Code module main clk rst start busy ready cnt s
  • Verilog 中的 If 语句和分配连线

    我试图弄清楚基于组合逻辑分配电线的基础知识 I have wire val wire x wire a wire b always begin if val 00 I want to assign x a if val 01 I want
  • 在 Verilog 程序中使用连续分配?

    在 Verilog 程序中使用连续赋值是否可能和 或有用 例如 是否有任何理由将assign里面一个always堵塞 例如这段代码 always begin assign data in Data end 此外 是否可以用这种方法生成顺序逻
  • VIM 高亮匹配开始/结束

    我正在尝试找到一个插件 它将突出显示与 Verilog 匹配的开始 结束语句 VIM 可以使用花括号 方括号 但不能使用它的开始 结束 我希望 VIM 突出显示正确的开始到正确的结束 在我看来 最好的选择是使用 matchit 该脚本是 v
  • 我怎样才能让我的verilog移位器更通用?

    这里我有一个移位器 但现在它最多只能工作 3 位 我一直在寻找 但不知道如何让它工作最多 8 位 module shifter a b out input 7 0 a b output 7 0 out wire 7 0 out1 out2
  • reg 声明中的位顺序

    如果我需要使用 4 个 8 位数字 我会声明以下 reg reg 7 0 numbers 3 0 我对第一个和第二个声明 7 0 和 3 0 之间的区别感到很困惑 他们应该按什么顺序来 第一个是保留数字的大小 而第二个是保留数字的数量 还是
  • 系统 verilog 中没有类型的输入

    我在一个系统 verilog 代码的输入和输出的示例中遇到过module没有说明它们的类型 例如logic wire module mat to stream input 2 0 2 0 2 0 a b input newdata inpu

随机推荐

  • 在教学中常被问到的几个vue3.x与typescript的问题,统一解答

    在教学当中 学生在学习vue3 x时 常常会问到typescript和vue3 x之间的关系 感觉这两个技术总是绑在一起的 下面老赵来统一解答一下 那学vue3 x 为什么要求也要掌握typescript Vue 3 x是一个使用TypeS
  • python计算矩阵间的欧式距离

    背景 计算一个M D的二维矩阵与一个N D的二维矩阵各个行向量之间的距离 不用循环来做 如果计算两个一维向量P C的维数相同 则它们的欧氏距离为 化简之后为 我们继而推广 一个一维向量与一个二维矩阵各个行向量的欧氏距离为 再继续推广到一个二
  • Java选择排序

    1 选择排序 选择排序是一种简单直观的排序算法 其基本原理是每一次从待排序的数组里找到最小值 最大值 的下标 然后将最小值 最大值 跟待排序数组的第一个进行交换 然后再从剩余的未排序元素中寻找到最小 大 元素 然后放到已排序的序列的末尾 反
  • 2.Node.js的安装 及 小案例——实现Hello World

    1 编辑器 初期 记事本Ediplus Nodepad 后期 Webstrom Hbuilder 2 Node js的下载 安装 测试 1 下载 http nodejs cn 2 安装 在windows版本操作系统下 直接点击 下一步 即可
  • vue+element在el-table表头添加搜索框实现模糊查询

    页面初始展示 实现模糊查询 gt
  • 如何理解邮件中的“CC、PS、FYI”等英文缩写?

    文章目录 2015年皮卡丘大学毕业初 加入一家总部在荷兰的外企 刚入职邮件中的英文缩写把皮卡丘折磨的晕头转向 比如OOO CC FYI OMW 你知道这些缩写表达的意思吗 01 OOO 是什么意思 OOO千万别理解成 哦哦哦 OOO Out
  • 安装虚拟机VMWare后再装LINUX系统(含避坑指南)

    一 下载 到官网下载虚拟机VMware Download VMware Workstation Pro 二 安装前的电脑设置 不要着急装 不设置好是安装不了的 还会引起暴躁 1 BIOS模式开启intel vt x 一般是禁用的 即 Int
  • 基于Native.js实现Android文件的读写操作

    Native js技术 简称NJS 是一种将手机操作系统的原生对象转义 映射为JS对象 在JS里编写原生代码的技术 我们在进行APP混合开发过程中免不了需要调用Andriod底层的一些API 也免不了可能会涉及到文件读取的操作之类的功能 这
  • Devops

    最近老是碰到这个名词 所以想了解一下这个到底是撒玩意 DevOps Development和Operations的组合词 是一组过程 方法与系统的统称 用于促进开发 应用程序 软件工程 技术运营和质量保障 QA 部门之间的沟通 协作与整合
  • windows利用kubectl命令和vscode远程操作kubenetes(k8s)

    windows 中安装kubecltl 命令 下载地址 http pwittrock github io docs tasks tools install kubectl install with chocolatey on windows
  • java 判断两个list是否相等的方法

    以下示例可以当做一个反面教材 实际是错误的 public static boolean isEquals List
  • 用apache实现禁止IP段或者主机对某个目录的访问

    Allow 指令 说明 控制哪些主机能够访问服务器的一个区域 语法 Allow from all host env env variable host env env variable 上下文 目录 htaccess 覆盖项 Limit 状
  • LayUI系列(一)之layui介绍以及登录功能的实现

    文章目录 一 layui简介 1 1 layui介绍 1 2 主要构成 1 3 迭代历程 1 4 layui easyui和bootstrap对比 1 4 1 layui与bootstrap对比 这两个都属于ui渲染框架 1 4 2 lay
  • TensorFlow框架做实时人脸识别小项目(一)

    人脸识别是深度学习最有价值也是最成熟的的应用之一 在研究环境下 人脸识别已经赶上甚至超过了人工识别的精度 一般来说 一个完整的人脸识别项目会包括两大部分 人脸检测与人脸识别 下面就我近期自己练习写的一个 粗糙 的人脸识别小项目讲起 也算是做
  • 二、关系模型

    关系模型 关系模型由关系数据结构 关系操作集合和关系完整性约束三部分组成 关系数据结构 关系模型的数据结构非常简单 只包含单一的数据结构 关系 域 一组具有相同数据类型的值的集合 笛卡儿积 在域上的一种集合运算 例如A 1 2 B a b
  • NNDL 作业7:第五章课后题(1×1 卷积核

    习题一 证明卷积具有交换性 即证明公式 首先 宽卷积定义为 其中 表示宽卷积运算 我们不妨先设一个二维图像和一个二维卷积核 然后对该二维图像X进行零填充 两端各补U 1 和V 1 个零 得到全填充的图像 现有 根据宽卷积定义 为了让x的下标
  • 学 Python 这么久,终于把类函数 & 成员函数 & 静态函数给整明白了!

    前言 学过 Python 的小伙伴应该知道 在类定义过程中 会依据对具体需求的分析 对类函数 成员函数 静态函数进行声明与定义 Python 基础稍弱的同学可能会对这三个函数的名称有些陌生 但你很有可能曾经见到过他们仨 只是没认出来而已 开
  • el-menu动态渲染多级菜单

    思路 创建子菜单递归组件 这里我命名为MySubMenu vue 父组件 引用自定义组件MySubMenu 将菜单数组传递给子组件
  • qt 信号量 linux 信号量 semctl,Linux信号量 共享内存和消息队列

    Linux信号量 共享内存和消息队列 Linux信号量 共享内存和消息队列 1 信号量 使用信号量可以实现进程间同步 主要函数定义 include int semctl int sem id int sem num int command
  • Chisel 手册 英文版

    Chisel Manual Jonathan Bachrach Huy Vo Krste Asanovi EECS Department UC Berkeley jrb huytbvo krste eecs berkeley edu Apr