Printing in Chisel
Chisel provides the printf
function for debugging purposes. It comes in two flavors:
Chisel also provides "logging" support for printing to files in addition to the default standard error, see Logging.
Scala-style
Chisel also supports printf in a style similar to Scala's String Interpolation. Chisel provides a custom string interpolator cf
which follows C-style format specifiers (see section C-style below).
Note that the Scala s-interpolator is not supported in Chisel constructs and will throw an error:
class MyModule extends Module {
val in = IO(Input(UInt(8.W)))
printf(s"in = $in\n")
}
// error: The s-interpolator prints the Scala .toString of Data objects rather than the value of the hardware wire during simulation. Use the cf-interpolator instead. If you want an elaboration time print, use println.
// printf(s"in = $in\n")
// ^^^^^^^^^^^^^^^^^^^^^
Instead, use Chisel's cf
interpolator as in the following examples:
val myUInt = 33.U
printf(cf"myUInt = $myUInt") // myUInt = 33
Note that when concatenating cf"..."
strings, you need to start with a cf"..."
string:
// Does not interpolate the second string
val myUInt = 33.U
printf("my normal string" + cf"myUInt = $myUInt")
Simple formatting
Other formats are available as follows:
val myUInt = 33.U
// Hexadecimal
printf(cf"myUInt = 0x$myUInt%x") // myUInt = 0x21
// Binary
printf(cf"myUInt = $myUInt%b") // myUInt = 100001
// Character
printf(cf"myUInt = $myUInt%c") // myUInt = !
Special values
There are special values you can include in your cf
interpolated string:
HierarchicalModuleName
(%m
): The hierarchical name of the current moduleSimulationTime
(%T
): The current simulation time (unlike Verilog's%t
, this does not take an argument)Percent
(%%
): A literal%
printf(cf"hierarchical path = $HierarchicalModuleName\n") // hierarchical path = <verilog.module.path>
printf(cf"hierarchical path = %m\n") // equivalent to the above
printf(cf"simulation time = $SimulationTime\n") // simulation time = <simulation.time>
printf(cf"simulation time = %T\n") // equivalent to the above
printf(cf"100$Percent\n") // 100%
printf(cf"100%%\n") // equivalent to the above
Format modifiers
Chisel supports standard Verilog-style modifiers for %d
, %x
, and %b
between the %
and the format specifier.
Verilog simulators will pad values out to the width of the signal.
With decimal formatting, space is used for padding.
For all other formats, 0
is used for padding.
- A non-negative field width will override the default Verilog sizing of the value.
- Specifying a field width of
0
will always display the value with the minimum width (no zero nor space padding).
val foo = WireInit(UInt(32.W), 33.U)
printf(cf"foo = $foo%d!\n") // foo = 33!
printf(cf"foo = $foo%0d!\n") // foo = 33!
printf(cf"foo = $foo%4d!\n") // foo = 33!
printf(cf"foo = $foo%x!\n") // foo = 00000021!
printf(cf"foo = $foo%0x!\n") // foo = 21!
printf(cf"foo = $foo%4x!\n") // foo = 0021!
val bar = WireInit(UInt(8.W), 5.U)
printf(cf"bar = $bar%b!\n") // foo = 00000101!
printf(cf"bar = $bar%0b!\n") // foo = 101!
printf(cf"bar = $bar%4b!\n") // foo = 0101!
Aggregate data-types
Chisel provides default custom "pretty-printing" for Vecs and Bundles. The default printing of a Vec is similar to printing a Seq or List in Scala while printing a Bundle is similar to printing a Scala Map.
val myVec = VecInit(5.U, 10.U, 13.U)
printf(cf"myVec = $myVec") // myVec = Vec(5, 10, 13)
val myBundle = Wire(new Bundle {
val foo = UInt()
val bar = UInt()
})
myBundle.foo := 3.U
myBundle.bar := 11.U
printf(cf"myBundle = $myBundle") // myBundle = Bundle(a -> 3, b -> 11)
Custom Printing
Chisel also provides the ability to specify custom printing for user-defined Bundles.
class Message extends Bundle {
val valid = Bool()
val addr = UInt(32.W)
val length = UInt(4.W)
val data = UInt(64.W)
override def toPrintable: Printable = {
val char = Mux(valid, 'v'.U, '-'.U)
cf"Message:\n" +
cf" valid : $char%c\n" +
cf" addr : $addr%x\n" +
cf" length : $length\n" +
cf" data : $data%x\n"
}
}
val myMessage = Wire(new Message)
myMessage.valid := true.B
myMessage.addr := "h1234".U
myMessage.length := 10.U
myMessage.data := "hdeadbeef".U
printf(cf"$myMessage")
Which prints the following:
Message:
valid : v
addr : 0x00001234
length : 10
data : 0x00000000deadbeef
Notice the use of +
between cf
interpolated "strings". The results of cf
interpolation can be concatenated by using the +
operator.
C-Style
Chisel provides printf
in a similar style to its C namesake. It accepts a double-quoted format string and a variable number of arguments which will then be printed on rising clock edges. Chisel supports the following format specifiers:
Format Specifier | Meaning |
---|---|
%d | decimal number |
%x | hexadecimal number |
%b | binary number |
%c | 8-bit ASCII character |
%n | name of signal |
%N | full name of signal |
%% | literal percent |
%m | hierarchical name |
%T | simulation time |
%d
, %x
, and %b
support the modifiers described in the Format modifiers section above.
It also supports a small set of escape characters:
Escape Character | Meaning |
---|---|
\n | newline |
\t | tab |
\" | literal double quote |
\' | literal single quote |
\\ | literal backslash |
Note that single quotes do not require escaping, but are legal to escape.
Thus printf can be used in a way very similar to how it is used in C:
val myUInt = 32.U
printf("myUInt = %d", myUInt) // myUInt = 32
Logging
Chisel supports logging via the SimLog
API.
SimLog
provides a way to write simulation logs to files or standard error. It's particularly useful when you need to:
- Write simulation output to specific files.
- Have multiple log files in a single simulation.
- Write reusable code that can target different log destinations.
Basic Usage
The most common use of SimLog
is to write to a file:
class MyModule extends Module {
val log = SimLog.file("logfile.log")
val in = IO(Input(UInt(8.W)))
log.printf(cf"in = $in%d\n")
}
You can also write to standard error using the default file descriptor:
class MyModule extends Module {
val log = SimLog.StdErr
val in = IO(Input(UInt(8.W)))
log.printf(cf"in = $in%d\n")
}
This is the same as standard printf
.
SimLog filenames can themselves be Printable
values:
class MyModule extends Module {
val idx = IO(Input(UInt(8.W)))
val log = SimLog.file(cf"logfile_$idx%0d.log")
val in = IO(Input(UInt(8.W)))
log.printf(cf"in = $in%d\n")
}
It is strongly recommended to use %0d
with UInts in filenames to avoid spaces in the filename.
Be careful to avoid uninitialized registers in the filename.
Writing Generic Code
SimLog
allows you to write code that can work with any log destination. This is useful when creating reusable components:
class MyLogger(log: SimLog) extends Module {
val in = IO(Input(UInt(8.W)))
log.printf(cf"in = $in%d\n")
}
// Use with a file
val withFile = Module(new MyLogger(SimLog.file("data.log")))
// Use with stderr
val withStderr = Module(new MyLogger(SimLog.StdErr))
Flush
SimLog
objects can be flushed to ensure that all buffered output is written.
This is useful in simulations using the logged output as input to a co-simulated components like a checker or golden model.
val log = SimLog.file("logfile.log")
val in = IO(Input(UInt(8.W)))
log.printf(cf"in = $in%d\n")
log.flush() // Flush buffered output right away.
You can also flush standard error:
SimLog.StdErr.flush() // This will flush all standard printfs.