Chisel Book
Chisel Book
Chisel Book
with Chisel
Martin Schoeberl
Digital Design with Chisel
Digital Design with Chisel
Martin Schoeberl
Copyright
c 2016–2019 Martin Schoeberl
This work is licensed under a Creative Commons Attribution-ShareAlike
4.0 International License. http://creativecommons.org/licenses/
by-sa/4.0/
Email: martin@jopdesign.com
Visit the source at https://github.com/schoeberl/chisel-book
Published 2019 by Kindle Direct Publishing,
https://kdp.amazon.com/
Preface ix
1 Introduction 1
1.1 Installing Chisel and FPGA Tools . . . . . . . . . . . . . . . . . . . . 2
1.2 Hello World . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 Chisel Hello World . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4 An IDE for Chisel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.5 Source Access and eBook Features . . . . . . . . . . . . . . . . . . . . 5
1.6 Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.7 Exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2 Basic Components 9
2.1 Signal Types and Constants . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2 Combinational Circuits . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.2.1 Multiplexer . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.3 Registers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.3.1 Counting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.4 Structure with Bundle and Vec . . . . . . . . . . . . . . . . . . . . . . 15
2.5 Chisel Generates Hardware . . . . . . . . . . . . . . . . . . . . . . . . 17
2.6 Exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
I
C ONTENTS
4 Components 29
4.1 Components in Chisel are Modules . . . . . . . . . . . . . . . . . . . . 29
4.2 An Arithmetic Logic Unit . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.3 Bulk Connections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.4 Lightweight Components with Functions . . . . . . . . . . . . . . . . . 35
7 Finite-State Machines 57
7.1 Basic Finite-State Machine . . . . . . . . . . . . . . . . . . . . . . . . 57
7.2 Faster Output with a Mealy FSM . . . . . . . . . . . . . . . . . . . . . 61
7.3 Moore versus Mealy . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
7.4 Exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
9 Hardware Generators 81
9.1 Configure with Parameters . . . . . . . . . . . . . . . . . . . . . . . . 81
9.1.1 Simple Parameters . . . . . . . . . . . . . . . . . . . . . . . . 81
9.1.2 Functions with Type Parameters . . . . . . . . . . . . . . . . . 82
9.1.3 Modules with Type Parameters . . . . . . . . . . . . . . . . . . 83
9.1.4 Parametrize Bundles . . . . . . . . . . . . . . . . . . . . . . . 84
II Contents
C ONTENTS
10 Example Designs 93
10.1 FIFO Buffer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
10.2 A Serial Port . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
10.3 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
10.3.1 Explore FIFO Variations . . . . . . . . . . . . . . . . . . . . . 103
10.3.2 The UART . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
13 Summary 119
B Chisel 2 123
Bibliography 127
Contents III
List of Figures
2.1 Logic for the expression (a & b) | c. The wires can be a single bit or
multiple bits. The Chisel expression, and the schematics are the same. . 11
2.2 A basic 2:1 multiplexer. . . . . . . . . . . . . . . . . . . . . . . . . . . 13
8.1 The light flasher split into a Master FSM and a Timer FSM. . . . . . . . 70
IV
L IST OF F IGURES
8.2 The light flasher split into a Master FSM, a Timer FSM, and a Counter
FSM. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
8.3 A state machine with a datapath. . . . . . . . . . . . . . . . . . . . . . 74
8.4 State diagram for the popcount FSM. . . . . . . . . . . . . . . . . . . . 75
8.5 Datapath for the popcount circuit. . . . . . . . . . . . . . . . . . . . . 76
8.6 The ready-valid flow control. . . . . . . . . . . . . . . . . . . . . . . . 77
Contents V
List of Tables
VI
Listings
VII
L ISTINGS
VIII Contents
Preface
This book is an introduction to digital design with the focus on using the hardware
construction language Chisel. Chisel brings advances from software engineering, such
as object-orientated and functional languages, into digital design.
This book addresses hardware designers and software engineers. Hardware designers,
with knowledge of Verilog or VHDL, can upgrade their productivity with a modern
language for their next ASIC or FPGA design. Software engineers, with knowledge of
object-oriented and functional programming, can leverage their knowledge to program
hardware, for example, FPGA accelerators executing in the cloud.
The approach of this book is to present small to medium-sized typical hardware com-
ponents to explore digital design with Chisel.
Acknowledgements
I want to thank everyone who has worked on Chisel for creating such a cool hard-
ware construction language. Chisel is so joyful to use and therefore worth writing a
book about. I am thankful to the whole Chisel community, which is so welcoming and
friendly and never tired to answer questions on Chisel.
I would also like to thank my students in the last years of an advanced computer
architecture course where most of them picked up Chisel for the final project. Thank
you for moving out of your comfort zone and taking up the journey of learning and using
a bleeding-edge hardware description language. Many of your questions have helped to
shape this book.
IX
1 Introduction
This book is an introduction to digital system design using a modern hardware construc-
tion language, Chisel [2]. In this book, we focus on a higher abstraction level than usual
in digital design books, to enable you to build more complex, interacting digital systems
in a shorter time.
This book and Chisel are targeting two groups of developers: (1) hardware designers
and (2) software programmers. Hardware designers who are fluid in VHDL or Verilog
and using other languages such as Python, Java, or Tcl to generate hardware can move
to a single hardware construction language where hardware generation is part of the
language and Software programmers may become interested in hardware design, e.g.,
as future chips from Intel will include programmable hardware to speed up programs. It
is perfectly fine to use Chisel as your first hardware description language.
Chisel brings advances in software engineering, such as object-orientated and func-
tional languages, into digital design. Chisel does not only allow to express hardware at
the register-transfer level but allows you to write hardware generators.
Hardware is now commonly described with a hardware description language. The
time of drawing hardware components, even with CAD tools, is over. Some high-level
schematics can give an overview of the system but are not intended to describe the sys-
tem. The two most common hardware description languages are Verilog and VHDL.
Both languages are old, contain many legacies, and have a moving line of what con-
structs of the language are synthesizable to hardware. Do not get me wrong: VHDL
and Verilog are perfectly able to describe a hardware block that can be synthesized into
an ASIC. For hardware design in Chisel, Verilog serves as an intermediate language for
testing and synthesis.
This book is not a general introduction to hardware design and the fundamentals of
it. For an introduction of the basics in digital design, such as how to build a gate out
of CMOS transistors, refer to other digital design books. However, this book intends to
teach digital design at an abstraction level that is current practice to describe ASICs or
designs targeting FPGAs.1 As prerequisites for this book, we assume basic knowledge
of Boolean algebra and the binary number system. Furthermore, some programming ex-
1 Asthe author is more familiar with FPGAs than ASICs as target technology, some design optimizations
shown in this book are targeting FPGA technology.
1
1 I NTRODUCTION
For Ubuntu, which is based on Debian, programs are usually installed from a Debian file
(.deb). However, as of the time of this writing sbt is not available as a ready to install
package. Therefore, the installation process is a little bit more involved:
echo "deb https://dl.bintray.com/sbt/debian /" | \
sudo tee -a /etc/apt/sources.list.d/sbt.list
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 \
--recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823
sudo apt-get update
sudo apt-get install sbt
2 Contents
1.2 H ELLO W ORLD
Chisel and Scala can also be installed and used under Windows. sbt can be installed
with a Windows installer, see: Installing sbt on Windows.
To build hardware for an FPGA, you need a synthesize tool. The two major FPGA
vendors, Intel2 and Xilinx, provide free versions of their tools that cover small to medium-
sized FPGAs. Those medium-sized FPGAs are large enough to build multicore RISC
style processors. Intel provides the Quartus Prime Lite Edition and Xilinx the Vivado
Design Suite, WebPACK Edition.
However, is this Chisel? Is this hardware generated to print a string? No, this is plain
Scala code and not a representative Hello World program for a hardware design.
Contents 3
1 I NTRODUCTION
and we need a counter to derive timing in the Hz range to achieve a visible blinking. In
the above example, we count from 0 up to 25000000-1 and then toggle the blinking sig-
nal (blkReg := ˜blkReg) and restart the counter (cntReg := 0.U). That hardware then
blinks the LED at 1 Hz.
4 Contents
1.5 S OURCE ACCESS AND E B OOK F EATURES
$ sbt eclipse
and import that project into Eclipse.3 In IntelliJ you can create a new project from
existing sources and then select from build.sbt.
The official Chisel documentation and further documents are available online:
• The Chisel home page is the official starting point to download and learn Chisel.
• The Chisel Tutorial provides a ready setup project containing small exercises with
testers and solutions.
• The Chisel Wiki contains a short users guide to Chisel and links to further infor-
mation.
3 This function needs the Eclipse plugin for sbt.
Contents 5
1 I NTRODUCTION
• The Chisel Testers are in their repository that contains a Wiki documentation.
1.7 Exercise
Each chapter ends with a hands-on exercise. For the introduction exercise, we will
use an FPGA board to get one LED blinking. As a first step clone (or fork) the chisel-
examples repository from GitHub. The Hello World example is in the folder hello-world,
set up as a minimal project. You can explore the Chisel code of the blinking LED in
src/main/scala/Hello.scala. Compile the blinking LED with the following steps:
After some initial downloading of Chisel components, this will produce the Verilog
file Hello.v. Explore this Verilog file. You will see that it contains two inputs clock
and reset and one output io led. When you compare this Verilog file with the Chisel
module, you will notice that the Chisel module does not contain clock or reset. Those
signals are implicitly generated, and in most designs, it is convenient not to need to
deal with these low-level details. Chisel provides register components, and those are
connected automatically to clock and reset (if needed).
The next step is to set up an FPGA project file for the synthesize tool, assign the pins,
and compile4 the Verilog code, and configure the FPGA with the resulting bitfile. We
cannot provide the details of these steps. Please consult the manual of your Intel Quartus
or Xilinx Vivado tool. However, the examples repository contains some ready to use
Quartus projects in folder quartus for several popular FPGA boards (e.g., DE2-115). If
the repository contains support for your board, start Quartus, open the project, compile
it by pressing the Play button, and configure the FPGA board with the Programmer
button and one of the LEDs should blink.
Gratulation! You managed to get your first design in Chisel running in an
FPGA!
4 The real process is more elaborated with following steps: synthesizing the logic, performing place and
route, performing timing analysis, and generating a bitfile. However, for the purpose of this introduction
example we simply call it “compile” your code.
6 Contents
1.7 E XERCISE
If the LED is not blinking, check the status of reset. On the DE2-115 configuration,
the reset input is connected to SW0.
Now change the blinking frequency to a slower or a faster value and rerun the build
process. Blinking frequencies and also blinking patterns communicate different “emo-
tions”. E.g., a slow blinking LED signals that everything is ok, a fast blinking LED
signals an alarm state. Explore which frequencies express best those two different emo-
tions.
As a more challenging extension to the exercise, generate the following blinking pat-
tern: the LED shall be on for 200 ms every second. For this pattern, you might decouple
the change of the LED blinking from the counter reset. You will need a second con-
stant where you change the state of the blkReg register. What kind of emotion does this
pattern produce? Is it alarming or more like a sign-of-live signal?
Contents 7
2 Basic Components
In this section, we introduce the basic components for digital design: combinational
circuits and flip-flops. These essential elements can be combined to build larger, more
interesting circuits.
The width of a vector of bits is defined by a Chisel width type (Width). The following
expression casts the Scala integer n to a Chisel width, which is used for the definition of
the Bits vector:
n.W
Bits(n.W)
Constants can be defined by using a Scala integer and converting it to a Chisel type:
0.U // defines a UInt constant of 0
-3.S // defines a SInt constant of -3
Constants can also be defined with a width, by using the Chisel width type:
1 Thetype Bits in the current version of Chisel is missing operations and therefore not very useful for user
code.
9
2 BASIC C OMPONENTS
If you find the notion of 8.U and 4.W a little bit funny, consider it as a variant of an
integer constant with a type. This notation is similar to 8L, representing a long integer
constant in C, Java, and Scala.
Chisel benefits from Scala’s type inference and in many places type information can
be left out. The same is also valid for bit widths. In many cases, Chisel will auto-
matically infer the correct width. Therefore, a Chisel description of hardware is more
concise and better readable than VHDL or Verilog.
For constants defined in other bases than decimal, the constant is defined in a string
with a preceding h for hexadecimal (base 16), o for octal (base 8), and b for binary
(base 2). The following example shows the definition of constant 255 in different bases.
In this example we omit the bit width and Chisel infers the minimum width to fit the
constants in, in this case 8 bits.
"hff".U // hexadecimal representation of 255
"o377".U // octal representation of 255
" b1111_1111 ".U // binary representation of 255
The above code shows how to use an underscore to group digits in the string that repre-
sents a constant. The underscore is ignored.
To represent logic values, Chisel defines the type Bool. Bool can represent a true or
false value. The following code shows the definition of type Bool and the definition
of Bool constants, by converting the Scala Boolean constants true and false to Chisel
Bool constants.
Bool ()
true.B
false .B
10 Contents
2.2 C OMBINATIONAL C IRCUITS
a
AND
b
OR logic
c
Figure 2.1: Logic for the expression (a & b) | c. The wires can be a single bit or
multiple bits. The Chisel expression, and the schematics are the same.
Figure 2.1 shows the schematic of this combinatorial expression. Note that this circuit
may be for a vector of bits and not only single wires that are combined with the AND
and OR circuits.
In this example, we do not define the type nor the width of signal logic. Both are
inferred from the type and width of the expression. The standard logic operations in
Chisel are:
val and = a & b // bitwise and
val or = a | b // bitwise or
val xor = a ˆ b // bitwise xor
val not = ˜a // bitwise negation
The resulting width of the operation is the maximum width of the operators for addition
and subtraction, the sum of the two widths for the multiplication, and usually the width
of the numerator for divide and modulo operations.2
A signal can also first be defined as a Wire of some type. Afterward, we can assign a
value to the wire with the := update operator.
val w = Wire(UInt ())
w := a & b
Contents 11
2 BASIC C OMPONENTS
Table 2.2 shows the full list of operators (see also builtin operators). The Chisel
operator precedence is determined by the evaluation order of the circuit, which follows
the Scala operator precedence. If in doubt, it is always a good praxis to use parentheses.3
Table 2.2 shows various functions defined on and for Chisel data types.
2.2.1 Multiplexer
A multiplexer is a circuit that selects between alternatives. In the most basic form, it
selects between two alternatives. Figure 2.2 shows such a 2:1 multiplexer, or mux for
3 The operator precedence in Chisel is a side effect of the hardware elaboration when the tree of hardware
nodes is created by executing the Scala operators. The Scala operator precedence is similar but not identi-
cal to Java/C. Verilog has the same operator precedence as C, but VHDL has a different one. Verilog has
precedence ordering for logic operations, but in VHDL those operators have the same precedence and are
evaluated from left to right.
12 Contents
2.2 C OMBINATIONAL C IRCUITS
sel
a
y
b
short. Depending on the value of the select signal (sel) signal y will represent signal a
or signal b.
A multiplexer can be built from logic. However, as multiplexing is such a standard
operation, Chisel provides a multiplexer,
val result = Mux(sel , a, b)
where a is selected when the sel is true.B, otherwise b is selected. The type of sel is
a Chisel Bool; the inputs a and b can be any Chisel base type or aggregate (bundles or
vectors) as long as they are the same type.
With logical and arithmetical operations and a multiplexer, every combinational cir-
cuit can be described. However, Chisel provides further components and control ab-
stractions for a more elegant description of a combinational circuit, which are described
in a later chapter.
The second basic component needed to describe a digital circuit is a state element,
also called register, which is described next.
Contents 13
2 BASIC C OMPONENTS
2.3 Registers
Chisel provides a register, which is a collection of D flip-flops. The register is implicitly
connected to a global clock and is updated on the rising edge. When an initialization
value is provided at the declaration of the register, it uses a synchronous reset connected
to a global reset signal. A register can be any Chisel type that can be represented as a
collection of bits. Following code defines an 8-bit register, initialized with 0 at reset:
val reg = RegInit (0.U(8.W))
An input is connected to the register with the := update operator and the output of the
register can be used just with the name in an expression:
reg := d
val q = reg
A register can also be connected to its input and a constant as initial value at the defini-
tion:
val bothReg = RegNext (d, 0.U)
2.3.1 Counting
Counting is a fundamental operation in digital systems. On might count events. How-
ever, more often counting is used to define a time interval. Counting the clock cycles
and triggering an action when the time interval has expired.
A simple approach is counting up to a value. However, in computer science, and
digital design, counting starts at 0. Therefore, if we want to count till 10, we count from
0 to 9. The following code shows such a counter that counts till 9 and wraps around to
0 when reaching 9.
val cntReg = RegInit (0.U(8.W))
14 Contents
2.4 S TRUCTURE WITH B UNDLE AND V EC
To use a bundle, we create it with new and wrap it into a Wire. The fields are accessed
with the dot notation:
val ch = Wire(new Channel ())
ch.data := 123.U
ch. valid := true.B
A Chisel Vec represents a collection of signals of the same type (a vector). Each
element can be accessed by an index. A Chisel Vec is similar to array data structures
in other programing languages.4 A Vec is created by calling the constructor with two
parameters: the number of elements and the type of the elements. A combinational Vec
4 The name Array is already used in Scala.
Contents 15
2 BASIC C OMPONENTS
A vector wrapped into a Wire is a multiplexer. We can also wrap a vector into a
register to define an array of registers. Following example defines a register file for a
processor; 32 registers each 32-bits wide, as for a classic 32-bit RISC processor, like the
32-bit version of RISC-V.
val registerFile = Reg(Vec (32, UInt (32.W)))
An element of that register file is accessed with an index and used as a normal register.
registerFile (idx) := dIn
val dOut = registerFile (idx)
We can freely mix bundles and vectors. When creating a vector with a bundle type,
we need to pass a prototype for the vector fields. Using our Channel, which we defined
above, we can create a vector of channels with:
val vecBundle = Wire(Vec (8, new Channel ()))
When we want a register of a bundle type that needs a reset value, we first create a
Wire of that bundle, set the individual fields as needed, and then passing this bundle to
a RegInit:
val initVal = Wire(new Channel ())
16 Contents
2.5 C HISEL G ENERATES H ARDWARE
With combinations of Bundles and Vecs we can define our own data structures, which
are powerful abstractions.
2.6 Exercise
In the introduction you implemented a blinking LED on an FPGA board (from chisel-
examples), which is a reasonable hardware Hello World example. It used only internal
state, a single LED output, and no input. Copy that project into a new folder and extend
it by adding some inputs to the io Bundle with val sw = Input(UInt(2.W)).
val io = IO(new Bundle {
val sw = Input(UInt (2.W))
val led = Output (UInt (1.W))
})
Contents 17
2 BASIC C OMPONENTS
For those switches, you also need to assign the pin names for the FPGA board. You can
find examples of pin assignments in the Quartus project files of the ALU project (e.g.,
for the DE2-115 FPGA board).
When you have defined those inputs and the pin assignment, start with a simple test:
drop all blinking logic from the design and connect one switch to the LED output;
compile and configure the FPGA device. Can you switch the LED on an off with the
switch? If yes, you have now inputs available. If not, you need to debug your FPGA
configuration. The pin assignment can also be done with the GUI version of the tool.
Now use two switches and implement one of the basic combinational functions, e.g.,
AND two switches and show the result on the LED. Change the function. The next step
involves three input switches to implement a multiplexer: one acts as a select signal, and
the other two are the two inputs for the 2:1 multiplexer.
Now you have been able to implement simple combinational functions and test them
in real hardware in an FPGA. As a next step, we will take a first look at how the build
process works to generate an FPGA configuration. Furthermore, we will also explore a
simple testing framework from Chisel, which allows you to test circuits without config-
uring an FPGA and toggle switches.
18 Contents
3 Build Process and Testing
To get started with more interesting Chisel code we first need to learn how to compile
Chisel programs, how to generate Verilog code for execution in an FPGA, and how to
write tests for debugging and to verify that our circuits are correct.
Chisel is written in Scala, so any build process that supports Scala is possible with
a Chisel project. One popular build tool for Scala is sbt, which stands for the Scala
interactive build tool. Besides driving the build and test process, sbt also downloads the
correct version of Scala and the Chisel libraries.
19
3 B UILD P ROCESS AND T ESTING
project
src
main
scala
package
sub-package
test
scala
package
target
generated
and test containing testers. Chisel inherits from Scala, which inherits from Java the or-
ganization of source in packages. Packages organize your Chisel code into namespaces.
Packages can also contain sub-packages. The folder target contains the class files and
other generated files. I recommend to also use a folder for generated Verilog files, which
is usually call generated.
To use the facility of namespaces in Chisel, you need to declare that a class/module
is defined in a package, in this example in mypacket:
package mypack
import chisel3 ._
Note that in this example we see the import of the chisel3 packet to use Chisel classes.
To use the module Abc in a different context (packet name space), the components of
packet mypacket need to be imported. The underscore ( ) acts as wildcard, meaning that
all classes of mypacket are imported.
import mypack ._
20 Contents
3.1 B UILDING YOUR P ROJECT WITH SBT
It is also possible to not import all types from mypacket, but use the fully qualified name
mypack.Abc to refer to the module Abc in packet mypack.
It is also possible to import just a single class and create an instance of it:
import mypack .Abc
$ sbt run
This command will compile all your Chisel code from the source tree and searches
for classes that contain an object that includes a main method, or simpler that extends
App. If there is more than one such object, all objects are listed and one can be selected.
You can also directly specify the object that shall be executed as a parameter to sbt:
Per default sbt searches only the main part of the source tree and not the test part.2
However, Chisel testers, as described here, contain a main, but shall be placed in the
2 This is a convention form Java/Scala that the test folder contains unit tests and not objects with a main.
Contents 21
3 B UILD P ROCESS AND T ESTING
test part of the source tree. To execute a main in the tester tree use following sbt
command:
Now that we know the basic structure of a Chisel project and how to compile and run
it with sbt, we can continue with a simple testing framework.
Testing a circuit contains (at least) three components: (1) the device under test (often
called DUT), (2) the testing logic, also called test bench, and (3) the tester objects that
contains the main function to start the testing.
The following code shows our simple design under test. It contains two input ports
and one output port, all with a 2-bit width. The circuit does a bit-wise AND to it returns
on the output:
class DeviceUnderTest extends Module {
val io = IO(new Bundle {
val a = Input(UInt (2.W))
val b = Input(UInt (2.W))
val out = Output (UInt (2.W))
})
22 Contents
3.2 T ESTING WITH C HISEL
The test bench for this DUT extends PeekPokeTester and has the DUT as a parameter
for the constructor:
class TesterSimple (dut: DeviceUnderTest ) extends
PeekPokeTester (dut) {
poke(dut.io.a, 0.U)
poke(dut.io.b, 1.U)
step (1)
println (" Result is: " + peek(dut.io.out). toString )
poke(dut.io.a, 3.U)
poke(dut.io.b, 2.U)
step (1)
println (" Result is: " + peek(dut.io.out). toString )
}
A PeekPokeTester can set input values with poke() and read back output values with
peek(). The tester advances the simulation by one step (= one clock cycle) with step(1).
We can print the values of the outputs with println().
The test is created and run with the following tester main:
object TesterSimple extends App {
chisel3 . iotesters . Driver (() => new DeviceUnderTest ()) { c =>
new TesterSimple (c)
}
}
When you run the test, you will see the results printed to the terminal (besides other
information):
Contents 23
3 B UILD P ROCESS AND T ESTING
poke(dut.io.a, 3.U)
poke(dut.io.b, 1.U)
step (1)
expect (dut.io.out , 1)
poke(dut.io.a, 2.U)
poke(dut.io.b, 0.U)
step (1)
expect (dut.io.out , 0)
}
Executing this test does not print out any values from the hardware, but that all tests
passed as all expect values are correct.
A failed test, when either the DUT or the test bench contains an error, produces an
error message describing the difference between the expected and actual value. In the
following, we changed the test bench to expect a 4, which is an error:
In this section, we described the basic testing facility with Chisel for simple tests.
However, in Chisel, the full power of Scala is available to write testers.
24 Contents
3.3 E XERCISES
$ sbt test
Although Chisel testing is more heavyweight than unit testing of Scala programs, we
can wrap a Chisel test into a ScalaTest class. For the Tester shown before this is:
class SimpleSpec extends FlatSpec with Matchers {
The main benefit of this exercise is to be able to run all tests with a simple sbt test
(instead of a running main). You can run just a single test with sbt, as follows:
3.3 Exercises
For this exercise, we will revisit the blinking LED from chisel-examples and explore
Chisel testing.
Contents 25
3 B UILD P ROCESS AND T ESTING
hardware description of the blinking LED (class Hello) and an App that generates the
Verilog code.
Each file starts with the import of Chisel and related packages:
import chisel3 ._
Then follows the hardware description, as shown in Listing 1.1. To generate the Verilog
description, we need an application. A Scala object that extends App is an application
that implicitly generates the main function where the application starts. The only action
of this application is to create a new HelloWorld object and pass it to the Chisel driver
execute function. The first argument is an array of Strings, where build options can be
set (e.g., the output folder). The following code will generate the Verilog file Hello.v.
object Hello extends App {
chisel3 . Driver . execute (Array[ String ]() , () => new Hello ())
}
and explore the generated Hello.v with an editor. The generated Verilog code may
not be very readable, but we can find out some details. The file starts with a module
Hello, which is the same name as our Chisel module. We can identify our LED port
as output io led. Pin names are the Chisel names with a prepended io . Besides our
LED pin, the module also contains clock and reset input signals. Those two signals
are added automatically by Chisel.
Furthermore, we can identify the definition of our two registers cntReg and blkReg.
We may also find the reset and update of those registers at the end of the module defini-
tion. Note, that Chisel generates a synchronous reset.
For sbt to be able to fetch the correct Scala compiler and the Chisel library, we need
a build.sbt:
scalaVersion := " 2.11.7 "
26 Contents
3.3 E XERCISES
Note that in this example, we have a concrete Chisel version number to avoid checking
on each run for a new version (which will fail if we are not connected to the Internet,
e.g., when doing hardware design during a flight). Change the build.sbt configuration
to use the latest Chisel version by changing the library dependency to
libraryDependencies += "edu. berkeley .cs" %% " chisel3 " %
" latest . release "
and rerun the build with sbt. Is there a newer version of Chisel available and will it be
automatically downloaded?
For convenience, the project also contains a Makefile. It just contains the sbt com-
mand, so we do not need to remember it and can generate the Verilog code with:
make
Besides a README file, the example project also contains project files for different
FPGA board. E.g., in quartus/altde2-115 you can find the two project files to define a
Quartus project for the DE2-115 board. The main definitions (source files, device, pin
assignments) can be found in a plain text file hello.qsf. Explore the file and find out
which pins are connected to which signals. If you need to adapt the project to a different
board, there is where the changes are applied. If you have Quartus installed, open that
project, compile with the green Play button, and then configure the FPGA.
Note that the Hello World is a minimal Chisel project. More realistic projects have
their source files organized in packages and contain testers. The next exercise will ex-
plore such a project.
Contents 27
3 B UILD P ROCESS AND T ESTING
Spartan).
28 Contents
4 Components
A larger digital design is structured into a set of components, often in a hierarchical
way. Each component has an interface with input and output wires, usually called ports.
These are similar to input and output pins on an integrated circuit (IC). Components are
connected by wiring up the inputs and outputs. Components may contain subcompo-
nents to build the hierarchy. The outermost component, which is connected to physical
pins on a chip, is called the top-level component.
Figure 4.1 shows an example design. Component C has three input ports and two
output ports. The component itself is assembled out of two subcomponents: B and C,
which are connected to the inputs and outputs of C. One output of A is connected to an
input of B. Component D is at the same hierarchy level as component C and connected
to it.
In this chapter, we will explain how components are described in Chisel and pro-
vide several examples of standard components. Those standard components serve two
purposes: (1) they provide examples of Chisel code and (2) they provide a library of
components ready to be reused in your design.
29
4 C OMPONENTS
CompA
CompB CompD
CompC
})
// function of A
}
// function of B
}
Component A has two inputs, named a and b, and two outputs, named x and y. For
the ports of component B we chose the names in1, in2, and out. All ports use an
unsigned integer (UInt) with a bit width of 8. As this example code is about connecting
components and building a hierarchy, we do not show any implementation within the
components. The implementation of the component is written at the place where the
comments states “function of X”. As we have no function associated with those example
components, we used generic port names. For a real design use descriptive port names,
such as data, valid, or ready.
Component C has three input and two output ports. It is built out of components A
30 Contents
4.1 C OMPONENTS IN C HISEL ARE M ODULES
and B. We show how A and B are connected to the ports of C and also the connection
between an output port of A and an input port of B:
class CompC extends Module {
val io = IO(new Bundle {
val in_a = Input(UInt (8.W))
val in_b = Input(UInt (8.W))
val in_c = Input(UInt (8.W))
val out_x = Output (UInt (8.W))
val out_y = Output (UInt (8.W))
})
// connect A
compA .io.a := io.in_a
compA .io.b := io.in_b
io. out_x := compA.io.x
// connect B
compB .io.in1 := compA.io.y
compB .io.in2 := io.in_c
io. out_y := compB.io.out
}
Components are created with new, e.g., new CompA(), and need to be wrapped into
a call to Module(). The reference to that module is stored in a local variable, in this
example val compA = Module(new CompA()).
With this reference, we can access the IO ports by dereferencing the io field of the
module and the individual fields of the IO Bundle.
The simplest component in our design has just an input port, named in, and an output
port named out.
class CompD extends Module {
val io = IO(new Bundle {
val in = Input(UInt (8.W))
val out = Output (UInt (8.W))
})
// function of D
}
Contents 31
4 C OMPONENTS
The final missing piece of our example design is the top-level component, which itself
is assembled out of components C and D:
class TopLevel extends Module {
val io = IO(new Bundle {
val in_a = Input(UInt (8.W))
val in_b = Input(UInt (8.W))
val in_c = Input(UInt (8.W))
val out_m = Output (UInt (8.W))
val out_n = Output (UInt (8.W))
})
// create C and D
val c = Module (new CompC ())
val d = Module (new CompD ())
// connect C
c.io.in_a := io.in_a
c.io.in_b := io.in_b
c.io.in_c := io.in_c
io. out_m := c.io.out_x
// connect D
d.io.in := c.io.out_y
io. out_n := d.io.out
}
32 Contents
4.2 A N A RITHMETIC L OGIC U NIT
fn
A
ALU Y
Contents 33
4 C OMPONENTS
In this example, we use a new Chisel construct, the switch/is construct to describe the
table that selects the output of our ALU. To use this utility function, we need to import
another Chisel package:
import chisel3 .util._
34 Contents
4.4 L IGHTWEIGHT C OMPONENTS WITH F UNCTIONS
To connect all three stages we need just two <> operators. We can also connect the
port of a submodule with the parent module.
val fetch = Module (new Fetch ())
val decode = Module (new Decode ())
val execute = Module (new Execute )
We can then create two adders by simply calling the function adder.
val x = adder(a, b)
// another adder
val y = adder(c, d)
Contents 35
4 C OMPONENTS
Note that this is a hardware generator. You are not executing any add operation during
elaboration, but create two adders (hardware instances). The adder is an artificial ex-
ample to keep it simple. Chisel has already an adder generation function, like +(that:
UInt).
Functions as lightweight hardware generators can also contain state (including a reg-
ister). Following example returns a one clock cycle delay element (a register). If a
function has just a single statement, we can write it in one line and omit the curly braces
().
def delay (x: UInt) = RegNext (x)
By calling the function with the function itself as parameter, this generated a two clock
cycle delay.
val delOut = delay(delay(delIn))
Again, this is a too short example to be useful, as RegNext() already is that function
creating the register for the delay.
Functions can be declared as part of a Module. However, functions that shall be used
in different modules are better placed into a Scala object that collects utility functions.
36 Contents
5 Combinational Building Blocks
In this chapter, we explore various combinational circuits, basic building blocks that
we can use to construct more complex systems. In principle, all combinational circuits
can be described with Boolean equations. However, more often, a description in the
form of a table is more efficient. We let the synthesize tool extract and minimize the
Boolean equations. Two basic circuits, best described in a table form, are a decoder and
an encoder.
The Boolean expression is given a name (e) by assigning it to a Scala value. The ex-
pression can be reused in other expressions:
val f = ˜e
37
5 C OMBINATIONAL B UILDING B LOCKS
w := 0.U
when (cond) {
w := 3.U
}
The logic of the circuit is a multiplexer, where the two inputs are the constants 0 and
3 and the condition cond the select signal. Keep in mind that we describe hardware
circuits and not a software program with conditional execution.
The Chisel condition construct when also has a form of else, it is called otherwise.
With assigning a value under any condition we can omit the default value assignment:
val w = Wire(UInt ())
when (cond) {
w := 1.U
} otherwise {
w := 2.U
}
when (cond) {
w := 1.U
} . elsewhen ( cond2) {
w := 2.U
} otherwise {
w := 3.U
}
Note the ‘.’ in .elsewhen that is needed to chain methods in Scala. Those .elsewhen
branches can be arbitrary long. However, if the chain of conditions depends on a single
signal, it is better to use the switch statement, which is introduced in the following
subsection with a decoder circuit.
For more complex combinational circuits it might be practical to assign a default
value to a Wire. A default assignment can be combined with the wire declaration with
38 Contents
5.2 D ECODER
b0
a0 b1
Decoder
a1 b2
b3
WireDefault.1
when (cond) {
w := 3.U
}
// ... and some more complex conditional assignments
One might question why using when, .elsewhen, and otherwise when Scala has if,
else if, and else? Those statements are for conditional execution of Scala code, not
generating Chisel (multiplexer) hardware. Those Scala conditionals have their use in
Chisel when we write circuit generators, which take parameters to conditionally gener-
ate different hardware instances.
5.2 Decoder
A decoder converts a binary number of n bits to an m-bit signal, where m ≤ 2n . The
output is one-hot encoded (where exactly one bit is one).
Figure 5.1 shows a 2-bit to 4-bit decoder. We can describe the function of the decoder
with a truth table, such as Table 5.2.
A Chisel switch statement describes the logic as a truth table. The switch statement
is not part of the core Chisel language. Therefore, we need to include the elements of
the package chisel.util.
import chisel3 .util._
1 In
the current version of Chisel it is called WireInit, but will change to WireDefault with the release of
Chisel 3.2
Contents 39
5 C OMBINATIONAL B UILDING B LOCKS
a b
00 0001
01 0010
10 0100
11 1000
The following code introduces the switch statement of Chisel to describe a decoder:
result := 0.U
switch (sel) {
is (0.U) { result := 1.U}
is (1.U) { result := 2.U}
is (2.U) { result := 4.U}
is (3.U) { result := 8.U}
}
The above switch statement lists all possible values of the sel signal and assigns the
decoded value to the result signal. Note that even if we enumerate all possible input
values, Chisel still needs us to assign a default value, as we do by assigning 0 to result.
This assignment will never be active and therefore optimized away by the backend tool.
It is intended to avoid situations with incomplete assignments for combinational cir-
cuits (in Chisel a Wire) that will result in unintended latches in hardware description
languages such as VHDL and Verilog. Chisel does not allow incomplete assignments.
In the example before we used unsigned integers for the signals. Maybe a clearer
representation of an encode circuit uses the binary notation:
switch (sel) {
is ("b00".U) { result := "b0001".U}
is ("b01".U) { result := "b0010".U}
is ("b10".U) { result := "b0100".U}
is ("b11".U) { result := "b1000".U}
}
A table gives a very readable representation of the decoder function but is also a little
bit verbose. When examining the table, we see a regular structure: a 1 is shifted left by
the number represented by sel. Therefore, we can express a decoder with the Chisel
40 Contents
5.3 E NCODER
a0
a1 b0
Encoder
a2 b1
a3
a b
0001 00
0010 01
0100 10
1000 11
???? ??
Decoders are used as a building block for a multiplexer by using the output as an
enable with an AND gate for the multiplexer data input. However, in Chisel, we do not
need to construct a multiplexer, as a Mux is available in the core library. Decoders can
also be used for address decoding, and then the outputs are used as select signals, e.g.,
different IO devices connected to a microprocessor.
5.3 Encoder
An encoder converts a one-hot encoded input signal into a binary encoded output signal.
The encoder does the inverse operation of a decoder.
Figure 5.2 shows a 4-bit one-hot input to a 2-bit binary output encoder, and Table 5.3
shows the truth table of the encode function. However, an encoder works only as ex-
pected when the input signal is one-hot coded. For all other input values, the output is
undefined. As we cannot describe a function with undefined outputs, we use a default
Contents 41
5 C OMBINATIONAL B UILDING B LOCKS
5.4 Exercise
Describe a combinational circuit to convert a 4-bit binary input to the encoding of a 7-
segment display. You can either define the codes for the decimal digits, which was the
initial usage of a 7-segment display or additionally, define encodings for the remaining
bit pattern to be able to display all 16 values of a single digit in hexadecimal. When
you have an FPGA board with a 7-segment display, connect 4 switches or buttons to the
input of your circuit and the output to the 7-segment display.
42 Contents
6 Sequential Building Blocks
Sequential circuits are circuits where the output depends on the input and previous
values. As we are interested in synchronous design (clocked designs), we mean syn-
chronous sequential circuits when we talk about sequential circuits.1 To build sequential
circuits, we need elements that can store state: the so-called registers.
6.1 Registers
The fundamental elements for building sequential circuits are registers. A register is a
collection of D flip-flops. A D flip-flop captures the value of its input at the rising edge
of the clock and stores it at its output. Alternatively, in other words: the register updates
its output with the value of the input on the rising edge of the clock.
Figure 6.1 shows the schematic symbol of a register. It contains an input D and an
output Q. Each register also contains an input for a clock signal. As this global clock
signal is connected to all registers in a synchronous circuit, it is usually not drawn in
the schematic. The little triangle on the bottom of the box symbolizes the clock input
and tells us that this is a register. We omit the clock signal in the following drawings.
The omission of the global clock signal is also reflected by Chisel where no explicit
connection of a signal to the register’s clock input is needed.
1 We can also build sequential circuits with asynchronous logic and feedback, but this is a specific niche topic
D Q
clock
43
6 S EQUENTIAL B UILDING B LOCKS
Note that we do not need to connect a clock to the register, Chisel implicitly does this.
A register’s input and output can be arbitrary complex types made out of a combination
of vectors and bundles.
A register can also be defined and used in two steps:
val delayReg = Reg(UInt (4.W))
delayReg := delayIn
First, we define the register and give it a name. Second, we connect the signal delayIn
to the input of the register. Note also that the name of the register contains the string
Reg. To easily distinguish between combinational circuits and sequential circuits, it is
common practice to have the marker Reg as part of to the name. Also, note that names
in Scala (and therefore also in Chisel) are usually in CamelCase. Variable names start
with lowercase and classes start with upper case.
A register can also be initialized on reset. The reset signal is, as the clock signal,
implicit in Chisel. We supply the reset value, e.g., zero, as a parameter to the register
constructor RegInit. The input for the register is connected with a Chisel assignment
statement.
val valReg = RegInit (0.U(4.W))
valReg := inVal
44 Contents
6.1 R EGISTERS
reset
init
D Q
data
clock
reset
inVal 3 5 2 7 4
regVal 0 5 2 7
A B C D E F
Contents 45
6 S EQUENTIAL B UILDING B LOCKS
enable
data
D Q
Figure 6.3 shows a waveform for the register with a reset and some input data applied
to it. Time advances from left to right. On top of the figure, we see the clock that drives
our circuit. In the first clock cycle, before a reset, the register is undefined. In the second
clock cycle reset is asserted high, and on the rising edge of this clock cycle (labeled B)
the register captures the initial value of 0. Input inVal is ignored. In the next clock cycle
reset is 0, and the value of inVal is captured on the next rising edge (labeled C). From
then on reset stays 0, as it should be, and the register output follows the input signal
with one clock cycle delay.
Waveforms are an excellent tool to specify the behavior of a circuit graphically. Es-
pecially in more complex circuits where many operations happen in parallel and data
move pipelined through the circuit, timing diagrams are convenient. Chisel testers can
also produce waveforms during testing that can be displayed with a waveform viewer
and used for debugging.
A typical design pattern is a register with an enable signal. Only when the enable
signal is true (high), the register captures the input; otherwise, it keeps its old value.
The enable can be implemented, similar to the synchronous reset, with a multiplexer at
the input of the register. One input to the multiplexer is the feedback of the output of the
register.
Figure 6.4 shows the schematics of a register with enable. As this is also a com-
mon design pattern, modern FPGA flip-flops contain a dedicated enable input, and no
additional resources are needed.
Figure 6.5 shows an example waveform for a register with enable. Most of the time,
enable it high (true) and the register follows the input with one clock cycle delay. Only
in the fourth clock cycle enable is low, and the register keeps its value (5) at rising edge
D.
46 Contents
6.2 C OUNTERS
clock
enable
inVal 2 3 5 2 7 4
regEnable 2 3 5 2 7
A B C D E F
A register with an enable can be described in a few lines of Chisel code with a condi-
tional update:
val enableReg = Reg(UInt (4.W))
when ( enable ) {
enableReg := inVal
}
when ( enable ) {
resetEnableReg := inVal
}
A register can also be part of an expression. Following circuit detects the rising edge
of a signal by comparing its current value with the one from the last clock cycle.
val risingEdge = din & ! RegNext (din)
Now that we have explored all basic uses of a register, we put those registers to good
use and build more interesting sequential circuits.
Contents 47
6 S EQUENTIAL B UILDING B LOCKS
1
+ D Q
6.2 Counters
One of the most basic sequential circuits is a counter. In its simplest form, a counter is
a register where the output is connected to an adder and the adder’s output is connected
to the input of the register. Figure 6.6 shows such a free-running counter.
A free-running counter with a 4-bit register counts from 0 to 15 and then wraps around
to 0 again. A counter shall also be reset to a known value.
val cntReg = RegInit (0.U(4.W))
48 Contents
6.2 C OUNTERS
If we are in the mood of counting down, we start (reset the counter register) with the
maximum value and reset the counter to that value when reaching 0.
val cntReg = RegInit (N)
As we are writing and using more counters, we can define a function with a parameter
to generate a counter for us.
// This function returns a counter
def genCounter (n: Int) = {
val cntReg = RegInit (0.U(8.W))
cntReg := Mux( cntReg === n.U, 0.U, cntReg + 1.U)
cntReg
}
The last statement of the function genCounter is the return value of the function, in this
example, the counting register cntReg.
Note, that in all the examples our counter had values between 0 and N, including N.
If we want to count 10 clock cycles we need to set N to 9. Setting N to 10 would be a
classic example of an off-by-one error.
Contents 49
6 S EQUENTIAL B UILDING B LOCKS
clock
reset
tick
counter 0 1 2 0 1 2 0 1
Figure 6.7: A waveform diagram for the generation of a slow frequency tick.
as blinking a LED at some frequency as we have shown in the Chisel “Hello World”
example.
A common practice is to generate single-cycle ticks with a frequency ftick that we
need in our circuit. That tick occurs every n clock cycles, where n = fclock / ftick and the
tick is precisely one clock cycle long. This tick is not used as a derived clock, but as
an enable signal for registers in the circuit that shall logically operate at frequency ftick .
Figure 6.7 shows an example of a tick generated every 3 clock cycles.
In the following circuit, we describe a counter that counts from 0 to the maximum
value of N - 1. When the maximum value is reached, the tick is true for a single
cycle, and the counter is reset to 0. When we count from 0 to N - 1, we generate one
logical tick every N clock cycles.
val tickCounterReg = RegInit (0.U(4.W))
val tick = tickCounterReg === (N -1).U
This logical timing of one tick every n clock cycles can then be used to advance other
parts of our circuit with this slower, logical clock. In the following code, we use just
another counter that increments by 1 every n clock cycles.
val lowFrequCntReg = RegInit (0.U(4.W))
when (tick) {
lowFrequCntReg := lowFrequCntReg + 1.U
}
50 Contents
6.2 C OUNTERS
Examples of the usage of this slower logical clock are: blinking an LED, generating
the baud rate for a serial bus, generating signals for 7-segment display multiplexing, and
subsampling input values for debouncing of buttons and switches.
Although width inference should size the registers, it is better to explicitly specify the
width with the type at register definition or with the initialization value. Explicit width
definition can avoid surprises when a reset value of 0.U results in a counter with a width
of a single bit.
Many of us feel like being a nerd, sometimes. For example, we want to design a highly
optimized version of our counter/tick generation. A standard counter needs following
resources: one register, one adder (or subtractor), and a comparator. We cannot do much
about the register or the adder. If we count up, we need to compare against a number,
which is a bit string. The comparator can be built out of inverters for the zeros in the
bit string and a large AND gate. When counting down to zero, the comparator is a large
NOR gate, which might be a little bit cheaper than the comparator against a constant in
an ASIC. In an FPGA ,where logic is built out of lookup tables, there is no difference
between comparing against a 0 or 1 bit. The resource requirement is the same for the up
and down counter.
However, there is still one more trick a clever hardware designer can pull off. Count-
ing up or down needed a comparison against all counting bits, so far. What if we count
from N-2 down to -1? A negative number has the most significant bit set to 1, and a
positive number has this bit set to 0. We need to check this bit only to detect that our
counter reached -1. Here it is, the counter created by a nerd:
val MAX = (N - 2).S(8.W)
val cntReg = RegInit (MAX)
io.tick := false.B
Contents 51
6 S EQUENTIAL B UILDING B LOCKS
rdAddr rdData
wrAddr
wrData
wrEna
Memory
6.3 Memory
A memory can be built out of a collection of registers, in Chisel a Reg of a Vec. However,
this is expensive in hardware, and larger memory structures are built as SRAM. For
an ASIC, a memory compiler constructs memories. FPGAs contain on-chip memory
blocks, also called block RAMs. Those on-chip memory blocks can be combined for
larger memories. Memories in an FPGA usually have one read and one write port, or
two ports where the direction can be switched.
FPGAs (and also ASICs) usually support synchronous memories. Synchronous mem-
ories have registers on their inputs (read and write address, write data, and write enable).
That means the read data is available one clock cycle after setting the address.
Figure 6.8 shows the schematics of such a synchronous memory. The memory is
dual-ported with one read port and one write port. The read port has a single input, the
read address (rdAddr) and one output, the read data (rdData). The write port has three
inputs: the address (wrAddr), the data to be written (wrData), and a write enable (wrEna).
Note that for all inputs, there is a register within the memory showing the synchronous
behavior.
52 Contents
6.3 M EMORY
when(io. wrEna) {
mem. write (io.wrAddr , io. wrData )
}
}
Contents 53
6 S EQUENTIAL B UILDING B LOCKS
=
AND
rdAddr rdData
dout
wrAddr
wrData
wrEna
Memory
Figure 6.9: A synchronous memory with forwarding for a defined read-during-write be-
havior.
54 Contents
6.4 E XERCISE
when(io. wrEna) {
mem. write (io.wrAddr , io. wrData )
}
compare the two input addresses (wrAddr and rdAddr) and check if wrEna is true for the
forwarding condition. That condition is also delayed by one clock cycle. A multiplexer
selects between the forwarding (write) data or the read data from memory.
Chisel also provides Mem, which represents a memory with synchronous write and
an asynchronous read. As this memory type is usually not directly available in an
FPGA, the synthesize tool will build it out of flip-flops. Therefore, we recommend
using SyncReadMem.
6.4 Exercise
Use the 7-segment encoder from the last exercise and add a 4-bit counter as input to
switch the display from 0 to F. When you directly connect this counter to the clock
Contents 55
6 S EQUENTIAL B UILDING B LOCKS
of the FPGA board, you will see all 16 numbers overlapped (all 7 segments will light
up). Therefore, you need to slow down the counting. Create a second counter that can
generate a single-cycle tick signal every 500 milliseconds. Use that signal as enable
signal for the 4-bit counter.
56 Contents
7 Finite-State Machines
A finite-state machine (FSM) is a basic building block in digital design. An FSM can be
described as a set of states and conditional (guarded) state transitions between states.
An FSM has an initial state, which is set on reset. FSMs are also called synchronous
sequential circuits.
An implementation of an FSM consists of three parts: (1) a register that holds the
current state, (2) combinational logic that computes the next state that depends on the
current state and the input, and (3) combinational logic that computes the output of the
FSM.
In principle, every digital circuit that contains a register or other memory elements
to store state can be described as a single FSM. However, this might not be practical,
e.g., try to describe your laptop as a single FSM. In the next chapter, we describe how
to build larger systems out of smaller FSMs by combining them into communicating
FSMs.
state
Next
Ouput
state next_state out
logic
logic
in
57
7 F INITE -S TATE M ACHINES
reset red/
green orange
ring bell
clear
clear
computes the output (out). As the output depends on the current state only, this state
machine is called a Moore machine.
A state diagram describes the behavior of such an FSM visually. In a state diagram,
individual states are depicted as circles labeled with the state names. State transitions
are shown with arrows between states. The guard (or condition) when this transition is
taken is drawn as a label for the arrow.
Figure 7.2 shows the state diagram of a simple example FSM. The FSM has three
states: green, orange, and red, indicating a level of alarm. The FSM starts at the green
level. When a bad event happens the alarm level is switched to orange. On a second
bad event, the alarm level is switched to red. In that case, we want to ring a bell; ring
bell it the only output of this FSM. We add the output to the red state. The alarm can be
reset with a clear signal.
Although a state diagram may be visually pleasing and the function of an FSM can be
grasped quickly, a state table may be quicker to write down. Table 7.1 shows the state
table for our alarm FSM. We list the current state, the input values, the resulting next
state, and the output value for the current state. In principle, we would need to specify
all possible inputs for all possible states. This table would have 3 × 4 = 12 rows. We
simplify the table by indicating that the clear input is a don’t care when a bad event
happens. That means bad event has priority over clear. The output column has some
repetition. If we have a larger FSM and/or more outputs, we can split the table into two,
one for the next state logic and one for the output logic.
Finally, after all the design of our warning level FSM, we shall code it in Chisel.
Listing 7.1 shows the Chisel code for the alarm FSM. Note, that we use the Chisel type
Bool for the inputs and the output of the FSM. To use Enum and the switch control
instruction, we need to import chisel3.util. .
The complete Chisel code for this simple FSM fits into one page. Let us step through
the individual parts. The FSM has two input and a single output signal, captured in a
58 Contents
7.1 BASIC F INITE -S TATE M ACHINE
import chisel3 ._
import chisel3 .util._
// Output logic
io. ringBell := stateReg === red
}
Contents 59
7 F INITE -S TATE M ACHINES
Chisel Bundle:
val io = IO(new Bundle {
val badEvent = Input(Bool ())
val clear = Input(Bool ())
val ringBell = Output (Bool ())
})
Quite some work has been spent in optimal state encoding. Two common options are
binary or one-hot encoding. However, we leave those low-level decisions to the syn-
thesize tool and aim for readable code.1 Therefore, we use an enumeration type with
symbolic names for the states:
val green :: orange :: red :: Nil = Enum (3)
The individual state values are described as a list where the individual elements are
concatenated with the :: operator; Nil represents the end of the list. An Enum instance
is assigned to the list of states. The register holding the state is defined with the green
state as the reset value:
val stateReg = RegInit (green)
The meat of the FSM is in the next state logic. We use a Chisel switch on the state
register to cover all states. Within each is branch we code the next state logic, which
depends on the inputs, by assigning a new value for our state register:
1 In the current version of Chisel the Enum type represents states in binary encoding. If we want a different
encoding, e.g., one-hot encoding, we can define Chisel constants for the state names.
60 Contents
7.2 FASTER O UTPUT WITH A M EALY FSM
switch ( stateReg ) {
is ( green ) {
when(io. badEvent ) {
stateReg := orange
}
}
is ( orange ) {
when(io. badEvent ) {
stateReg := red
} . elsewhen (io.clear) {
stateReg := green
}
}
is (red) {
when (io.clear) {
stateReg := green
}
}
}
Last, but not least, we code our ringing bell output to be true when the state is red.
io. ringBell := stateReg === red
Note that we did not introduce a next state signal for the register input, as it is
common practice in Verilog or VHDL. Registers in Verilog and VHDL are described in
a special syntax and cannot be assigned (and reassigned) within a combinational block.
Therefore, the additional signal, computed in a combinational block, is introduced and
connected to the register input. In Chisel a register is a base type and can be freely used
within a combinational block.
Contents 61
7 F INITE -S TATE M ACHINES
AND risingEdge
din NOT
state
Next
state next_state
logic Ouput
out
in logic
Figure 7.7 shows the schematic of the rising edge detector. The output becomes 1 for
one clock cycle when the current input is 1 and the input in the last clock cycle was 0.
The state register is just a single D flip-flop where the next state is just the input. We
can also consider this as a delay element of one clock cycle. The output logic compares
the current input with the current state.
When the output depends also on the input, i.e., there is a combinational path between
the input of the FSM and the output, this is called a Mealy machine.
Figure 7.4 shows the schematic of a Mealy type FSM. Similar to the Moore FSM, the
register contains the current state, and the next state logic computes the next state value
(next state) from the current state and the input (in). On the next clock tick, state
becomes next state. The output logic computes the output (out) from the current state
and the input to the FSM.
Figure 7.5 shows the state diagram of the Mealy FSM for the edge detector. As the
state register consists just of a single D flip-flop, only two states are possible, which
we name zero and one in this example. As the output of a Mealy FSM does not only
depend on the state, but also on the input, we cannot describe the output as part of the
62 Contents
7.3 M OORE VERSUS M EALY
0/0 1/0
1/1
reset
zero one
0/0
Figure 7.5: The state diagram of the rising edge detector as Mealy FSM.
state circle. Instead, the transitions between the states are labeled with the input value
(condition) and the output (after the slash). Note also that we draw self transitions, e.g.,
in state zero when the input is 0 the FSM stays in state zero, and the output is 0. The
rising edge FSM generates the 1 output only on the transition from state zero to state
one. In state one, which represents that the input is now 1, the output is 0. We only want
a single (cycle) puls for each rising edge of the input.
Listing 7.2 shows the Chisel code for the rising edge detection with a Mealy machine.
As in the previous example, we use the Chisel type Bool for the single-bit input and
output. The output logic is now part of the next state logic; on the transition from
zero to one, the output is set to true.B. Otherwise, the default assignment to the output
(false.B) counts.
One can ask if a full-blown FSM is the best solution for the edge detection circuit,
especially, as we have seen a Chisel one-liner for the same functionality. The hardware
consumptions is similar. Both solutions need a single D flip-flop for the state. The
combinational logic for the FSM is probably a bit more complicated, as the state change
depends on the current state and the input value. For this function, the one-liner is easier
to write and easier to read, which is more important. Therefore, the one-liner is the
preferred solution.
We have used this example to show one of the smallest possible Mealy FSMs. FSMs
shall be used for more complex circuits with three and more states.
Contents 63
7 F INITE -S TATE M ACHINES
import chisel3 ._
import chisel3 .util._
64 Contents
7.3 M OORE VERSUS M EALY
1 1
Figure 7.6: The state diagram of the rising edge detector as Moore FSM.
clock
din
risingEdge Mealy
risingEdge Moore
Figure 7.7: Mealy and a Moore FSM waveform for rising edge detection.
states in the Mealy version. The state puls is needed to produce the single-cycle puls.
The FSM stays in state puls just one clock cycle and then proceeds either back to the
start state zero or to the one state, waiting for the input to become 0 again. We show
the input condition on the state transition arrows and the FSM output within the state
representing circles.
Listing 7.3 shows the Moore version of the rising edge detection circuit. Is uses
double the number of D flip-flops than the Mealy or direct coded version. The resulting
next state logic is therefore also larger than the Mealy or direct coded version.
Figure 7.7 shows the waveform of a Mealy and a Moore version of the rising edge
detection FSM. We can see that the Mealy output closely follows the input rising edge,
while the Moore output rises after the clock tick. We can also see that the Moore output
is one clock cycle wide, where the Mealy output is usually less than a clock cycle.
From the above example, one is tempted to find Mealy FSMs the better FSMs as
they need less state (and therefore logic) and react faster than a Moore FSM. However,
the combinational path within a Mealy machine can cause trouble in larger designs.
First, with a chain of communicating FSM (see next chapter), this combinational path
can become lengthy. Second, if the communicating FSMs build a circle, the result is
Contents 65
7 F INITE -S TATE M ACHINES
import chisel3 ._
import chisel3 .util._
// Output logic
io. risingEdge := stateReg === puls
}
66 Contents
7.4 E XERCISE
7.4 Exercise
In this chapter, you have seen many examples of very small FSMs. Now it is time to
write some real FSM code. Pick a little bit more complex example and implement the
FSM and write a test bench for it.
A classic example for a FSM is a traffic light controller (see [3, Section 14.3]). A
traffic light controller has to ensure that on a switch from red to green there is a phase
in between where both roads in the intersection have a no-go light (red and orange).
To make this example a little bit more interesting, consider a priority road. The minor
road has two car detectors (on both entries into the intersection). Switch to green for the
minor road only when a car is detected and then switch back to green for the priority
road.
Contents 67
8 Communicating State Machines
A problem is often too complex to describe it with a single FSM. In that case, the
problem can be divided into two or more smaller and simpler FSMs. Those FSMs then
communicate with signals. One FSMs output is another FSMs input, and the FSM
watches the output of the other FSM. When we split a large FSM into simpler ones, this
is called factoring FSMs. However, often communicating FSMs are directly designed
from the specification, as often a single FSM would be infeasible large.
• when start is high for one clock cycle, the flashing sequence starts;
• where the light goes on for six clock cycles, and the light goes off for four
clock cycles between flashes;
• after the sequence, the FSM switches the light off and waits for the next start.
The FSM for a direct implementation1 has 27 states: one initial state that is waiting
for the input, 3 × 6 states for the three on states and 2 × 4 states for the off states. We do
not show the code for this simple-minded implementation of the light flasher.
The problem can be solved more elegantly by factoring this large FSM into two
smaller FSMs: the master FSM implements the flashing logic, and the timer FSM im-
plements the waiting. Figure 8.1 shows the composition of the two FSMs.
The timer FSM counts down for 6 or 4 clock cycles to produce the desired timing.
The timer specification is as follows:
1 The state diagram is shown in [3, p. 376].
69
8 C OMMUNICATING S TATE M ACHINES
start light
Master FSM
timerSelect
timerLoad
timerDone
Timer
Figure 8.1: The light flasher split into a Master FSM and a Timer FSM.
• when timerLoad is asserted, the timer loads a value into the down counter, inde-
pendent of the state;
• timerDone is asserted when the counter completed the countdown and remains
asserted;
70 Contents
8.1 A L IGHT F LASHER E XAMPLE
// Timer connection
val timerLoad = WireInit (false.B) // start timer with a load
val timerSelect = WireInit (true.B) // select 6 or 4 cycles
val timerDone = Wire(Bool ())
timerLoad := timerDone
// Master FSM
switch ( stateReg ) {
is(off) {
timerLoad := true.B
timerSelect := true.B
when ( start) { stateReg := flash1 }
}
is ( flash1 ) {
timerSelect := false.B
light := true.B
when ( timerDone ) { stateReg := space1 }
}
is ( space1 ) {
when ( timerDone ) { stateReg := flash2 }
}
is ( flash2 ) {
timerSelect := false.B
light := true.B
when ( timerDone ) { stateReg := space2 }
}
is ( space2 ) {
when ( timerDone ) { stateReg := flash3 }
}
is ( flash3 ) {
timerSelect := false.B
light := true.B
when ( timerDone ) { stateReg := off }
}
}
Contents 71
8 C OMMUNICATING S TATE M ACHINES
start light
Master FSM
timerSelect
timerLoad
timerDone
cntLoad
cntDecr
cntDone
Timer Counter
Figure 8.2: The light flasher split into a Master FSM, a Timer FSM, and a Counter FSM.
Note, that the counter is loaded with 2 for 3 flashes, as it counts the remaining flashes
and is decremented in state space when the timer is done. Listing 8.2 shows the master
FSM for the double refactored flasher.
Besides having a master FSM that is reduced to just three states, our current solution
is also better configurable. No FSM needs to be changed if we want to change the length
of the on or off intervals or the number of flashes.
In this section, we have explored communicating circuits, especially FSM, that only
72 Contents
8.1 A L IGHT F LASHER E XAMPLE
// Timer connection
val timerLoad = WireInit (false.B) // start timer with a load
val timerSelect = WireInit (true.B) // select 6 or 4 cycles
val timerDone = Wire(Bool ())
// Counter connection
val cntLoad = WireInit (false.B)
val cntDecr = WireInit (false.B)
val cntDone = Wire(Bool ())
timerLoad := timerDone
switch ( stateReg ) {
is(off) {
timerLoad := true.B
timerSelect := true.B
cntLoad := true.B
when ( start) { stateReg := flash }
}
is ( flash ) {
timerSelect := false.B
light := true.B
when ( timerDone & ! cntDone ) { stateReg := space }
when ( timerDone & cntDone ) { stateReg := off }
}
is ( space ) {
cntDecr := timerDone
when ( timerDone ) { stateReg := flash }
}
}
Contents 73
8 C OMMUNICATING S TATE M ACHINES
dinValid popCntValid
din popCnt
Datapath
exchange control signals. However, circuits can also exchange data. For the coordinated
exchange of data, we use handshake signals. The next section describes the ready-valid
interface for flow control of unidirectional data exchange.
74 Contents
8.2 S TATE M ACHINE WITH DATAPATH
Idle Valid
Result read
Done Count
Finished
are connected to the FSM. The FSM is connected with the datapath with control signals
towards the datapath and with status signals from the datapath.
As a next step, we can design the FSM, starting with a state diagram, shown in Fig-
ure 8.4. We start in state Idle, where the FSM waits for input. When data arrives,
signaled with a valid signal, the FSM advances to state Load to load a shift register. The
FSM proceeds to the next state Count, there the number of ‘1’s is counted sequentially.
We use a shift register, an adder, an accumulator register, and a down counter to perform
the computation. When the down counter reaches zero, we are finished and the FSM
moves to state Done. There the FSM signals with a valid signal that the popcount value
is ready to be consumed. On a ready signal from the receiver, the FSM moves back to
the Idle state, ready to compute the next popcount.
The top level component, shown in Listing 8.3 instantiates the FSM and the datapath
components and connects them with bulk connections.
Figure 8.5 shows the datapath for the popcount circuit. The data is loaded into the shf
register. On the load also the cnt register is reset to 0. To count the number of ‘1’s, the
shf register is shifted right, and the least significant bit is added to cnt each clock cycle.
A counter, not shown in the figure, counts down until all bits have been shifted through
the least significant bit. When the counter reaches zero, the popcount has finished. The
FSM switches to state Done and signals the result by asserting popCntReady. When the
result is read, signaled by asserting popCntValid the FSW switches back to Idle.
On a load signal, the regData register is loaded with the input, the regPopCount
register reset to 0, and the counter register regCount set to the number of shifts to be
performed.
Otherwise, the regData register is shifted to the right, the least significant bit of the
Contents 75
8 C OMMUNICATING S TATE M ACHINES
data.io.din := io.din
io. popCnt := data.io. popCnt
data.io.load := fsm.io.load
fsm.io.done := data.io.done
}
0
0 count
shf cnt
din
+
76 Contents
8.3 R EADY-VALID I NTERFACE
valid
ready
Sender Receiver
data
regData register added to the regPopCount register, and the counter decremented until
it is 0. When the counter is 0, the output contains the popcount. Listing 8.4 shows the
Chisel code for the datapath of the popcount circuit.
The FSM starts in state idle. On a valid signal for the input data (dinValid) it
switches to the count state and waits till the datapath has finished counting. When the
popcount is valid, the FSM switches to state done and waits till the popcount is read
(signaled by popCntReady). Listing 8.5 shows the code of the FSM.
Contents 77
8 C OMMUNICATING S TATE M ACHINES
when(io.load) {
dataReg := io.din
popCntReg := 0.U
counterReg := 8.U
}
// debug output
printf ("%x %d\n", dataReg , popCntReg )
78 Contents
8.3 R EADY-VALID I NTERFACE
io.load := false.B
switch ( stateReg ) {
is(idle) {
io. dinReady := true.B
when(io. dinValid ) {
io.load := true.B
stateReg := count
}
}
is( count ) {
when(io.done) {
stateReg := done
}
}
is(done) {
io. popCntValid := true.B
when(io. popCntReady ) {
stateReg := idle
}
}
}
}
Contents 79
9 Hardware Generators
The strength of Chisel is that it allows us to write so-called hardware generators. With
older hardware description languages, such as VHDL and Verilog, we usually use an-
other language, e.g., Java or Python, to generate hardware. The author has often written
small Java programs to generate VHDL tables. In Chisel, the full power of Scala (and
Java libraries) is available at hardware construction. Therefore, we can write our hard-
ware generators in the same language and execute them as part of the Chisel circuit
generation.
81
9 H ARDWARE G ENERATORS
Chisel allows parameterizing functions with types, in our case with Chisel types. The
expression in the square brackets [T <: Data] defines a type parameter T set is Data
or a subclass of Data. Data is the root of the Chisel type system.
Our multiplexer function has three parameters: the boolean condition, one parameter
for the true path, and one parameter for the false path. Both path parameters are of type
T, an information that is provided at function call. The function itself is straight forward:
we define a wire with the default value of fPath and change the value is the condition
is true to the tPath. This condition is a classic multiplexer function. At the end of the
function, we return the multiplexer hardware.
We can use our multiplexer function with simple types such as UInt:
val resA = myMux(selA , 5.U, 10.U)
The types of the two multiplexer paths need to be the same. Following wrong usage of
the multiplexer results in a runtime error:
val resErr = myMux(selA , 5.U, 10.S)
82 Contents
9.1 C ONFIGURE WITH PARAMETERS
We can define Bundle constants by first creating a Wire and then setting the subfields.
Then we can use our parameterized multiplexer with this complex type.
val tVal = Wire(new ComplexIO )
tVal.b := true.B
tVal.d := 42.U
val fVal = Wire(new ComplexIO )
fVal.b := false.B
fVal.d := 13.U
In our initial design of the function, we used WireInit to create a wire with the type
T with a default value. If we need to create a wire just of the Chisel type without using
a default value, we can use fPath.cloneType to get the Chisel type. Following function
shows the alternative way to code the multiplexer.
def myMuxAlt [T <: Data ]( sel: Bool , tPath: T, fPath: T): T = {
Contents 83
9 H ARDWARE G ENERATORS
To use our router, we first need to define the data type we want to route, e.g., as a Chisel
Bundle:
We create a router by passing an instance of the user-defined Bundle and the number of
ports to the constructor of the router:
val router = Module (new NocRouter (new Payload , 2))
In the router example, we used two different vectors of fields for the input of the router:
one for the address and one for the data, which was parameterized. A more elegant
solution would be to have a Bundle that itself is parametrized. Something like:
class Port[T <: Data ](dt: T) extends Bundle {
val address = UInt (8.W)
val data = dt. cloneType
}
The Bundle has a parameter of type T, which is a subtype of Chisel’s Data type.
Within the bundle, we define a field data by invoking cloneType on the parameter.
However, when we use a constructor parameter, this parameter becomes a public field
of the class. When Chisel needs to clone the type of the Bundle, e.g., when it is used in
a Vec, this public field is in the way. A solution (workaround) to this issue is to make
the parameter field private:
84 Contents
9.2 G ENERATE C OMBINATIONAL L OGIC
and instantiate that router with a Port that takes a Payload as a parameter:
val router = Module (new NocRouter2 (new Port(new Payload ), 2))
A Scala Array can be implicitly converted to a sequence (Seq), which supports the map-
ping function map. map invokes a function on each element of the sequence and returns a
sequence of the return value of the function. Our function .U(8.W) represents each Int
value from the Scala array as a and performs the conversion from a Scala Int value to
a Chisel UInt literal, with a size of 8-bits. The Chisel object VecInit creates a Chisel
Vec from a sequence Seq of Chisel types.
We can use the full power of Scala to generate our logic (tables). E.g., generate a
table of fixpoint constants to represent a trigonometric function, compute constants for
digital filters, or writing a small assembler in Scala to generate code for a microprocessor
Contents 85
9 H ARDWARE G ENERATORS
import chisel3 ._
import scala .io. Source
// convert the Scala integer array into the Chisel type Vec
val table = VecInit (array.map(_.U(8.W)))
86 Contents
9.2 G ENERATE C OMBINATIONAL L OGIC
import chisel3 ._
written in Chisel. All those functions are in the same code base (same language) and
can be executed during hardware generation.
We can write a Java program that computes the table to convert binary to BCD. That
Java program prints out VHDL code that can be included in a project. The Java program
is about 100 lines of code; most of the code generating VHDL strings. The key part of
the conversion is just two lines.
With Chisel, we can compute this table directly as part of the hardware generation.
Listing 9.2 shows the table generation for the binary to BCD conversion.
Contents 87
9 H ARDWARE G ENERATORS
val N = (n -1).U
Listing 9.3 shows a first implementation of that abstract class with a counter, counting
up, for the tick generation.
We can test all different versions of our ticker logic with a single test bench. We just
need to define the test bench to accept subtypes of Ticker. Listing 9.4 shows the Chisel
code for the tester. The TickerTester has several parameters: (1) the type parameter [T
<: Ticker] to accept a Ticker or any class that inherits from Ticker, (2) the design
under test, being of type T or a subtype thereof, and (3) the number of clock cycles we
88 Contents
9.3 U SE I NHERITANCE
expect for each tick. The tester waits for the first occurrence of a tick (the start might be
different for different implementations) and then checks that tick repeats every n clock
cycles.
With a first, easy implementation of the ticker, we can test the tester itself, probably
with some println debugging. When we are confident that the simple ticker and the
tester are correct, we can proceed and explore two more versions of the ticker. List-
ing 9.5 shows the tick generation with a counter counting down to 0. Listing 9.6 shows
the nerd version of counting down to -1 to use less hardware by avoiding the comparator.
We can test all three versions of the ticker by using ScalaTest specifications, creating
instances of the different versions of the ticker and passing them to the generic test
bench. Listing 9.7 shows the specification. We run only the ticker tests with:
sbt " testOnly TickerSpec "
Contents 89
9 H ARDWARE G ENERATORS
// -1 is the notion that we have not yet seen the first tick
var count = -1
for (i <- 0 to n * 3) {
if ( count > 0) {
expect (dut.io.tick , 0)
}
if ( count == 0) {
expect (dut.io.tick , 1)
}
val t = peek(dut.io.tick)
// On a tick we reset the tester counter to N-1,
// otherwise we decrement the tester counter
if (t == 1) {
count = n -1
} else {
count -= 1
}
step (1)
}
}
90 Contents
9.3 U SE I NHERITANCE
val N = (n -1).U
val N = n
Contents 91
9 H ARDWARE G ENERATORS
92 Contents
10 Example Designs
In this section, we explore some small size digital designs, such as a FIFO buffer, which
are used as building blocks for a larger design. As another example, we design a serial
interface (also called UART), which itself may use the FIFO buffer.
write read
full empty
Writer FIFO Reader
din dout
93
10 E XAMPLE D ESIGNS
We start by defining the IO signals for the writer and the reader side. The size of the
data is configurable with size. The write data are din and a write is signaled by write.
The signal full performs the flow control at the writer side.
class WriterIO (size: Int) extends Bundle {
val write = Input(Bool ())
val full = Output (Bool ())
val din = Input(UInt(size.W))
}
The reader side provides data with dout and the read is initiated with read. The empty
signal is responsible for the flow control at the reader side.
class ReaderIO (size: Int) extends Bundle {
val read = Input(Bool ())
val empty = Output (Bool ())
val dout = Output (UInt(size.W))
}
Listing 10.1 shows a single buffer. The buffer has a enqueueing port enq of type
WriterIO and a dequeueing port deq of type ReaderIO. The state elements of the buffer
is one register that holds the data (dataReg and one state register for the simple FSM
(stateReg). The FSM has only two states: either the buffer is empty or full. If the
buffer is empty, a write will register the input data and change to the full state. If the
buffer is full, a read will consume the data and change to the empty state. The IO ports
full and empty represent the buffer state for the writer and the reader.
Listing 10.2 shows the complete FIFO. The complete FIFO has the same IO interface
as the individual FIFO buffers. BubbleFifo has as parameters the size of the data word
and depth for the number of buffer stages. We can build an depth stages bubble FIFO
out of depth FifoRegisters. We crate the stages by filling them into a Scala Array.
The Scala array has no hardware meaning, it just provides us with a container to have
references to the created buffers. In a Scala for loop we connect the individual buffers.
The first buffers enqueueing side is connected to the enqueueing IO of the complete
FIFO and the last buffer’s dequeueing side to the dequeueing side of the complete FIFO.
94 Contents
10.2 A S ERIAL P ORT
Contents 95
10 E XAMPLE D ESIGNS
b0 b1 b2 b3 b4 b5 b6 b7
significant bit first, and then one or two stop bits (1). When no data is transmitted, the
output is 1. Figure 10.2 shows the timing diagram of one byte transmitted.
We design our UART in a modular way with minimal functionality per module. We
present a transmitter (TX), a receiver (RX), a buffer, and then usage of those base com-
ponents.
First, we need an interface, a port definition. For the UART design, we use a ready/-
valid handshake interface, with the direction as seen from the transmitter.
class Channel extends Bundle {
val data = Input(Bits (8.W))
val ready = Output (Bool ())
val valid = Input(Bool ())
}
The convention of a ready/valid interface is that the data is transferred when both
ready and valid are asserted.
96 Contents
10.2 A S ERIAL P ORT
io. channel . ready := ( cntReg === 0.U) && ( bitsReg === 0.U)
io.txd := shiftReg (0)
cntReg := BIT_CNT
when( bitsReg =/= 0.U) {
val shift = shiftReg >> 1
shiftReg := Cat (1.U, shift (9, 0))
bitsReg := bitsReg - 1.U
}. otherwise {
when(io. channel .valid) {
// two stop bits , data , one start bit
shiftReg := Cat(Cat (3.U, io. channel .data), 0.U)
bitsReg := 11.U
}. otherwise {
shiftReg := 0x7ff.U
}
}
}. otherwise {
cntReg := cntReg - 1.U
}
}
Contents 97
10 E XAMPLE D ESIGNS
Listing 10.3 shows a bare-bone serial transmitter (Tx). The IO ports are the txd port,
where the serial data is sent and a Channel where the transmitter can receive the charac-
ters to serialize and send. To generate the correct timing, we compute a constant for by
computing the time in clock cycles for one serial bit.
We use three registers: (1) register to shift the data (serialize them) (shiftReg), (2) a
counter to generate the correct baud rate (cntReg), and (3) a counter for the number of
bits that still need to be shifted out. No additional state register of FSM is needed, all
state is encoded in those three registers.
Counter cntReg is continuously running (counting down to 0 and reset to the start
value when 0). All action is only done when cntReg is 0. As we build a minimal
transmitter, we have only the shift register to store the data. Therefore, the channel is
only ready when cntReg is 0 and no bits are left to shift out.
The IO port txd is directly connected to the least significant bit of the shift register.
When there are more bits to shift out (bitsReg =/= 0.U), we shift the bits to the right
and fill with 1 from the top (the idle level of a transmitter). If no more bits need to be
shifted out, we check if the channel contains data (signaled with the valid port). If so,
the bit string to be shifted out is constructed with one start bit (0), the 8-bit data, and
two stop bits (1). Therefore, the bit count is set to 11.
This very minimal transmitter has no additional buffer and can accept a new character
only when the shift register is empty and at the clock cycle when cntReg is 0. Accept-
ing new data only when cntReg is 0 also means that the ready flag is also de-asserted
when there would be space in the shift register. However, we do not want to add this
“complexity” to the transmitter but delegate it to a buffer.
Listing 10.4 shows a single byte buffer, similar to the FIFO register for the bubble
FIFO. The input port is a Channel interface, and the output is the Channel interface with
flipped directions. The buffer contains the minimal state machine to indicate empty or
full. The buffer driven handshake signals (in.ready and out.valid depend on the state
register.
When the state is empty, and data on the input is valid, we register the data and
switch to state full. When the state is full, and the downstream receiver is ready, the
downstream data transfer happens, and we switch back to state empty.
With that buffer we can extend our bare-bone transmitter. Listing 10.5 shows the
combination of the transmitter Tx with a single-buffer in front. This buffer now relaxes
the issue that Tx was ready only for single clock cycles. We delegated the solution of
this issue to the buffer module. An extension of the single word buffer to a real FIFO
can easily be done and needs no change in the transmitter or the single byte buffer.
Listing 10.6 shows the code for the receiver (Rx). A receiver is a little bit tricky, as it
needs to reconstruct the timing of the serial data. The receiver waits for the falling edge
of the start bit. From that event, the receiver waits 1.5 bit times to position itself into
98 Contents
10.2 A S ERIAL P ORT
Contents 99
10 E XAMPLE D ESIGNS
the middle of bit 0. Then it shifts in the bits every bit time. You can observe these two
waiting times as START CNT and BIT CNT. For both times, the same counter (cntReg) is
used. After 8 bits are shifted in, valReg signals an available byte
Listing 10.7 shows the usage of the serial port transmitter by sending a friendly mes-
sage out. We define the message in a Scala string (msg) and converting it to a Chisel Vec
of UInt. A Scala string is a sequence that supports the map method. The map method
takes as argument a function literal, applies this function to each element, and builds a
sequence of the functions return values. If the function literal shall have only one ar-
gument, as it is in this case, the argument can be represented by . Our function literal
calls the Chisel method .U to convert the Scala Char to a Chisel UInt. The sequence is
then passed to VecInint to construct a Chisel Vec. We index into the vector text with
the counter cntReg to provide the individual characters to the buffered transmitter. With
each ready signal we increase the counter until the full string is sent out. The sender
keeps valid asserted until the last character has been sent out.
Listing 10.8 shows the usage of the receiver and the transmitter by connecting them
together. This connection generates an Echo circuit where each received character is
sent back (echoed).
10.3 Exercises
This exercise section is a little bit longer as it contains two exercises: (1) exploring
the bubble FIFO and implement a different FIFO design; and (2) exploring the UART
100 Contents
10.3 E XERCISES
Contents 101
10 E XAMPLE D ESIGNS
io.txd := tx.io.txd
102 Contents
10.3 E XERCISES
and extending it. Source code for both exercises is included in the chisel-examples
repository.
Contents 103
10 E XAMPLE D ESIGNS
the FPGA board and the serial port on your laptop. Start a terminal program, e.g.,
Hyperterm on Windows or gtkterm on Linux:
$ gtkterm &
Configure your port to use the correct device, with a USB UART this is often something
like /dev/ttyUSB0. Set the baud rate to 115200 and no parity or flow control (hand-
shake). With the following command you can create the Verilog code for the UART:
$ make uart
Then use your synthesize tool to synthesize the design. The repository contains a Quar-
tus project for the DE2-115 FPGA board. With Quartus use the play button to synthesize
the design and then configure the FPGA. After configuration, you should see a greeting
message in the terminal.
Extend the blinking LED example with a UART and write 0 and 1 to the serial line
when the LED is off and on. Use the BufferedTx, as in the Sender example.
With the slow output of characters (two per second), you can write the data to the
UART transmit register and can ignore the read/valid handshake. Extend the example
by writing repeated numbers 0-9 as fast as the baud rate allows. In this case, you have
to extend your state machine to poll the UART status to check if the transmit buffer is
free.
The example code contains only a single buffer for the Tx. Feel free to add the FIFO
that you have implemented to add buffering to the transmitter and receiver.
104 Contents
11 Design of a Processor
As one of the last chapters in this book, we present a medium size project: the design,
simulation, and testing of a microprocessor. To keep this project manageable, we design
a simple accumulator machine. The processor is called Leros [8] and is available in
open source at https://github.com/leros-dev/leros. We would like to mention
that this is an advanced example and some computer architecture knowledge is needed
to follow the presented code examples.
Leros is designed to be simple, but still a good target for a C compiler. The descrip-
tion of the instructions fits on one page, see Table 11.1. In that table A represents the
accumulator, PC is the program counter, i is an immediate value (0 to 255), Rn a register
n (0 to 255), o a branch offset relative to the PC, and AR an address register for memory
access.
An ALU usually has two operand inputs (call them a and b), an operation op (or opcode)
input to select the function and an output y. Listing 11.1 shows the ALU.
We first define shorter names for the three inputs. The switch statement defines the
logic for the computation of res. Therefore, it gets a default assignment of 0. The
switch statement enumerates all operations and assigns the expression accordingly. All
operations map directly to a Chisel expression. In the end, we assign the result res to
the ALU output y
For the testing, we write the ALU function in plain Scala, as shown in Listing 11.2.
105
11 D ESIGN OF A P ROCESSOR
106 Contents
11.1 S TART WITH AN ALU
val op = io.op
val a = io.a
val b = io.b
val res = WireInit (0.S(size.W))
switch (op) {
is(add) {
res := a + b
}
is(sub) {
res := a - b
}
is(and) {
res := a & b
}
is(or) {
res := a | b
}
is(xor) {
res := a ˆ b
}
is (shr) {
// the following does NOT result in an unsigned shift
// res := (a. asUInt >> 1). asSInt
// work around
res := (a >> 1) & 0 x7fffffff .S
}
is(ld) {
res := b
}
}
io.y := res
}
Contents 107
11 D ESIGN OF A P ROCESSOR
op match {
case 1 => a + b
case 2 => a - b
case 3 => a & b
case 4 => a | b
case 5 => a ˆ b
case 6 => b
case 7 => a >>> 1
case _ => -123 // This shall not happen
}
}
While this duplication of hardware written in Chisel by a Scala implementation does not
detect errors in the specification; it is at least some sanity check. We use some corner
case values as the test vector:
// Some interesting corner cases
val interesting = Array (1, 2, 4, 123, 0, -1, -2, 0x80000000 ,
0 x7fffffff )
108 Contents
11.2 D ECODING I NSTRUCTIONS
Full, exhaustive testing for 32-bit arguments is not possible, which was the reason we
selected some corner cases as input values. Beside testing against corner cases, it is also
useful to test against random inputs:
val randArgs = Seq.fill (100)(scala.util. Random . nextInt )
test( randArgs )
You can run the tests within the Leros project with
object Constants {
val NOP = 0x00
val ADD = 0x08
val ADDI = 0x09
val SUB = 0x0c
val SUBI = 0x0d
val SHR = 0x10
val LD = 0x20
val LDI = 0x21
val AND = 0x22
val ANDI = 0x23
val OR = 0x24
val ORI = 0x25
val XOR = 0x26
Contents 109
11 D ESIGN OF A P ROCESSOR
For the decode component, we define a Bundle for the output, which is later fed partially
into the ALU.
class DecodeOut extends Bundle {
val ena = Bool ()
val func = UInt ()
val exit = Bool ()
}
Decode takes as input an 8-bit opcode and delivers the decoded signals as output. Those
driving signals are assigned a default value with WireInit.
class Decode () extends Module {
val io = IO(new Bundle {
val din = Input(UInt (8.W))
val dout = Output (new DecodeOut )
})
io.dout.exit := false.B
The decoding itself is just a large switch statement on the part of the instruction that
represents the opcode (in Leros for most instructions the upper 8 bits.)
switch (io.din) {
is(ADD.U) {
f := add
ena := true.B
}
is(ADDI.U) {
f := add
imm := true.B
ena := true.B
110 Contents
11.3 A SSEMBLING I NSTRUCTIONS
}
is(SUB.U) {
f := sub
ena := true.B
}
is(SUBI.U) {
f := sub
imm := true.B
ena := true.B
}
is(SHR.U) {
f := shr
ena := true.B
}
// ...
Contents 111
11 D ESIGN OF A P ROCESSOR
in the first pass. Therefore, we call assemble twice with a parameter to indicate which
pass it is.
def getProgram (prog: String ) = {
assemble (prog)
}
The assemble function starts with reading in the source file1 and defining two helper
functions to parse the two possible operands: (1) an integer constant (allowing decimal
or hexadecimal notation) and (2) to read a register number.
def assemble (prog: String , pass2: Boolean ): Array[Int] = {
Listing 11.3 shows the core of the assembler for Leros. A Scala match expression
1 This
function does not actually read the source file, but for this discussion we can consider it as the reading
function.
112 Contents
11.4 E XERCISE
11.4 Exercise
This exercise assignment in one of the last Chapters is in a very free form. You are at
the end of your learning tour through Chisel and ready to tackle design problems that
you find interesting.
One option is to reread the chapter and read along with all the source code in the
Leros repository, run the test cases, fiddle with the code by breaking it and see that tests
fail.
Contents 113
11 D ESIGN OF A P ROCESSOR
114 Contents
12 Contributing to Chisel
Chisel is an open-source project under constant development and improvement. There-
fore, you can also contribute to the project. Here we describe how to set up your envi-
ronment for Chisel library development and how to contribute to Chisel.
$ cd chisel3
$ sbt compile
$ sbt publishLocal
Watch out during the publish local command for the version string of the published
library, which contains the string SNAPSHOT. If you use the tester and the published ver-
sion is not compatible with the Chisel SNAPSHOT, fork and clone the chisel-tester repo
as well and publish it locally.
To test your changes in Chisel, you probably also want to set up a Chisel project, e.g.,
by forking/cloning an empty Chisel project, renaming it, and removing the .git folder
from it.
Change the build.sbt to reference the locally published version of Chisel. Further-
more, at the time of this writing, the head of Chisel source uses Scala 2.12, but Scala
2.12 has troubles with anonymous bundles. Therefore, you need to add the following
Scala option: "-Xsource:2.11". The build.sbt should look similar to:
115
12 C ONTRIBUTING TO C HISEL
scalaVersion := "2.12.6"
scalacOptions := Seq("-Xsource:2.11")
libraryDependencies +=
"edu.berkeley.cs" %% "chisel3" % "3.2-SNAPSHOT"
libraryDependencies +=
"edu.berkeley.cs" %% "chisel-iotesters" % "1.3-SNAPSHOT"
Compile your Chisel test application and take a close look if it picks up the local
published version of the Chisel library (there is also a SNAPSHOT version published,
so if, e.g., the Scala version is different between your Chisel library and your application
code, it picks up the SNAPSHOT version from the server instead of your local published
library.)
See also some notes at the Chisel repo.
12.2 Testing
When you change the Chisel library, you should run the Chisel tests. In an sbt based
project, this is usually run with:
$ sbt test
Furthermore, if you add functionality to Chisel, you should also provide tests for the
new features.
116 Contents
12.4 E XERCISE
12.4 Exercise
Invent a new operator for the UInt type, implement it in the Chisel library, and write
some usage/test code to explore the operator. It does not need to be a useful operator;
just anything will be good, e.g., a ? operator that delivers the lefthand side if it is
different from 0 otherwise the righthand side. Sounds like a multiplexer, right? How
many lines of code did you need to add?1
As simple as this was, please be not tempted to fork the Chisel project and add your
little extensions. Changes and extension shall be coordinated with the main developers.
This exercise was just a simple exercise to get you started.
If you are getting bold, you could pick one of the open issues and try to solve it.
Then contribute with a pull request to Chisel. However, probably first watch the style of
development in Chisel by watching the GitHub repositories. See how changes and pull
requests are handled in the Chisel open-source project.
1A quick and dirty implementation needs just two lines of Scala code.
Contents 117
13 Summary
This book presented an introduction to digital design using the hardware construction
language Chisel. We have seen several simple to medium-sized digital circuits described
in Chisel. Chisel is embedded in Scala and therefore inherits the powerful abstraction
of Scala. As this book is intended as an introduction, we have restricted our examples
to simple uses of Scala. A next logical step is to learn a few basics of Scala and apply
them to your Chisel project.
I would be happy to receive feedback on the book, as I will further improve it and
will publish new editions. You can contact me at mailto:masca@dtu.dk, or with an
issue request on the GitHub repository. I am also happily accepting pull requests for the
book repository for any fixes and improvements.
Source Access
This book is available in open source. The repository also contains slides for a Chisel
course and all Chisel examples: https://github.com/schoeberl/chisel-book
A collection of medium-sized examples, which most are referenced in the book, is
also available in open source. This collection also contains projects for various popular
FPGA boards: https://github.com/schoeberl/chisel-examples
119
A Chisel Projects
Chisel is not (yet) used in many projects. Therefore, open-source Chisel code to learn
the language and the coding style is rare. Here we list several projects we are aware of
that use Chisel and are in open source.
Rocket Chip is a RISC-V [13] processor-complex generator that comprises the Rocket
microarchitecture and TileLink interconnect generators. Originally developed at
UC Berkeley as the first chip-scale Chisel project [1], Rocket Chip is now com-
mercially supported by SiFive.
Sodor is a collection of RISC-V implementations intended for educational use. It con-
tains 1, 2, 3, and 5 stages pipeline implementations. All processors use a simple
scratchpad memory shared by instruction fetch, data access, and program loading
via a debug port. Sodor is mainly intended to be used in simulation.
Patmos is an implementation of a processor optimized for real-time systems [10]. The
Patmos repository includes several multicore communication architectures, such
as a time-predictable memory arbiter [7], a network-on-chip [9] a shared scratch-
pad memory with an ownership [11]. At the time of this writing, Patmos is still
described in Chisel 2.
FlexPRET is an implementation of a precision timed architecture [14]. FlexPRET im-
plements the RISC-V instruction set and has been updated to Chisel 3.1.
Lipsi is a tiny processor intended for utility functions on a system-on-chip [6]. As
the code base of Lipsi is very small, it can serve as an easy starting point for
processor design in Chisel. Lipsi also showcases the productivity of Chisel/Scala.
It took me 14 hours to describe the hardware in Chisel and run it on an FPGA,
write an assembler in Scala, write a Lipsi instruction set simulator in Scala for
co-simulation, and write a few test cases in Lipsi assembler.
OpenSoC Fabric is an open-source NoC generator written in Chisel [5]. It is intended
to provide a system-on-chip for large-scale design exploration. The NoC itself
is a state-of-the-art design with wormhole routing, credits for flow control, and
virtual channels. OpenSoC Fabric is still using Chisel 2.
121
A C HISEL P ROJECTS
RoCC is a neural network accelerator that integrates with the RISC-V Rocket proces-
sor [4]. RoCC supports inference and learning.
If you know an open-source project that uses Chisel, please drop me a note so I can
include it in a future edition of the book.
122 Contents
B Chisel 2
This book covers version 3 of Chisel. Moreover, Chisel 3 is recommended for new
designs. However, there is still Chisel 2 code out in the wild, which has not yet been
converted to Chisel 3. There is documentation available on how to convert a Chisel 2
project to Chisel 3:
• Towards Chisel 3
However, you might get involved in a project that still uses Chisel 2, e.g., the Pat-
mos [10] processor. Therefore, we provide here some information on Chisel 2 coding
for those who have started with Chisel 3.
First, all documentation on Chisel 2 has been removed from the web sites belong-
ing to Chisel. We have rescued those PDF documents and put them on GitHub at
https://github.com/schoeberl/chisel2-doc. You can use the Chisel 2 tutorial
by switching to the Chisel 2 branch:
$ git clone https:// github .com/ucb -bar/chisel - tutorial .git
$ cd chisel - tutorial
$ git checkout chisel2
The main visible difference between Chisel 3 and 2 are the definitions of constants,
bundles for IO, wires, memories, and probably older forms of register definitions.
Chisel 2 constructs can be used, to some extent, in a Chisel 3 project by using the
compatibility layer using as package Chisel instead of chisel3. However, using this
compatibility layer should only be used in a transition phase. Therefore, we do not
cover it here.
Here are two examples of basic components, the same that have been presented for
Chisel 3. A module containing combinational logic:
import Chisel ._
123
B C HISEL 2
val a = UInt(INPUT , 1)
val b = UInt(INPUT , 1)
val c = UInt(INPUT , 1)
val out = UInt(OUTPUT , 1)
}
Note that the Bundle for the IO definition is not wrapped into an IO() class. Further-
more, the direction of the different IO ports is defined as part of the type definition, in
this example as INPUT and OUTPUT as part of UInt. The width is given as the second
parameter.
The 8-bit register example in Chisel 2:
import Chisel ._
io.out := reg
}
Here you see a typical register definition with a reset value passed in as a UInt to the
named parameter init. This form is still valid in Chisel 3, but the usage of RegInit
and RegNext is recommended for new Chisel 3 designs. Note also here the constant
definition of an 8-bit wide 0 as UInt(0, 8).
Chisel based testing C++ code and Verilog code are generated by calling chiselMainTest
and chiselMain. Both “main” functions take a String array for further parameters.
import Chisel ._
poke(c.io.a, 1)
poke(c.io.b, 0)
124 Contents
poke(c.io.c, 1)
step (1)
expect (c.io.out , 1)
}
object LogicTester {
def main(args: Array[ String ]): Unit = {
chiselMainTest (Array("--genHarness ", "--test",
"--backend ", "c",
"--compile ", "--targetDir ", " generated "),
() => Module (new Logic ())) {
c => new LogicTester (c)
}
}
}
import Chisel ._
object LogicHardware {
def main(args: Array[ String ]): Unit = {
chiselMain (Array("--backend ", "v"), () => Module (new
Logic ()))
}
}
A memory with sequential registered read and write ports is defined in Chisel 2 as:
val mem = Mem(UInt(width = 8), 256, seqRead = true)
val rdData = mem(Reg(next = rdAddr ))
when( wrEna ) {
mem( wrAddr ) := wrData
}
Contents 125
Bibliography
[1] Krste Asanović, Rimas Avizienis, Jonathan Bachrach, Scott Beamer, David Bian-
colin, Christopher Celio, Henry Cook, Daniel Dabbelt, John Hauser, Adam
Izraelevitz, Sagar Karandikar, Ben Keller, Donggyu Kim, John Koenig, Yunsup
Lee, Eric Love, Martin Maas, Albert Magyar, Howard Mao, Miquel Moreto, Al-
bert Ou, David A. Patterson, Brian Richards, Colin Schmidt, Stephen Twigg,
Huy Vo, and Andrew Waterman. The rocket chip generator. Technical Report
UCB/EECS-2016-17, EECS Department, University of California, Berkeley, Apr
2016.
[2] Jonathan Bachrach, Huy Vo, Brian Richards, Yunsup Lee, Andrew Waterman, Ri-
mas Avizienis, John Wawrzynek, and Krste Asanovic. Chisel: constructing hard-
ware in a Scala embedded language. In Patrick Groeneveld, Donatella Sciuto, and
Soha Hassoun, editors, The 49th Annual Design Automation Conference (DAC
2012), pages 1216–1225, San Francisco, CA, USA, June 2012. ACM.
[3] William J. Dally, R. Curtis Harting, and Tor M. Aamodt. Digital design using
VHDL: A systems approach. Cambridge University Press, 2016.
[4] Schuyler Eldridge, Amos Waterland, Margo Seltzer, and Jonathan Ap-
pavooand Ajay Joshi. Towards general-purpose neural network computing. In
2015 International Conference on Parallel Architecture and Compilation (PACT),
pages 99–112, Oct 2015.
[5] Farzaf Fatollahi-Fard, David Donofrio, George Michelogiannakis, and John Shalf.
Opensoc fabric: On-chip network generator. In 2016 IEEE International Sympo-
sium on Performance Analysis of Systems and Software (ISPASS), pages 194–203,
April 2016.
[6] Martin Schoeberl. Lipsi: Probably the smallest processor in the world. In Archi-
tecture of Computing Systems – ARCS 2018, pages 18–30. Springer International
Publishing, 2018.
[7] Martin Schoeberl, David VH Chong, Wolfgang Puffitsch, and Jens Sparsø. A time-
predictable memory network-on-chip. In Proceedings of the 14th International
127
B IBLIOGRAPHY
[8] Martin Schoeberl and Morten Borup Petersen. Leros: The return of the accumu-
lator machine. In Martin Schoeberl, Thilo Pionteck, Sascha Uhrig, Jürgen Brehm,
and Christian Hochberger, editors, Architecture of Computing Systems - ARCS
2019 - 32nd International Conference, Proceedings, pages 115–127. Springer, 1
2019.
[9] Martin Schoeberl, Luca Pezzarossa, and Jens Sparsø. A minimal network interface
for a simple network-on-chip. In Martin Schoeberl, Thilo Pionteck, Sascha Uhrig,
Jürgen Brehm, and Christian Hochberger, editors, Architecture of Computing Sys-
tems - ARCS 2019, pages 295–307. Springer, 1 2019.
[10] Martin Schoeberl, Wolfgang Puffitsch, Stefan Hepp, Benedikt Huber, and Daniel
Prokesch. Patmos: A time-predictable microprocessor. Real-Time Systems,
54(2):389–423, Apr 2018.
[11] Martin Schoeberl, Tórur Biskopstø Strøm, Oktay Baris, and Jens Sparsø. Scratch-
pad memories with ownership. In 2019 Design, Automation and Test in Europe
Conference Exhibition (DATE), 2019.
[12] Bill Venners, Lex Spoon, and Martin Odersky. Programming in Scala, 3rd Edition.
Artima Inc, 2016.
[13] Andrew Waterman, Yunsup Lee, David A. Patterson, and Krste Asanovic. The
risc-v instruction set manual, volume i: Base user-level isa. Technical Report
UCB/EECS-2011-62, EECS Department, University of California, Berkeley, May
2011.
[14] Michael Zimmer. Predictable Processors for Mixed-Criticality Systems and
Precision-Timed I/O. PhD thesis, EECS Department, University of California,
Berkeley, Aug 2015.
128 Contents