Skip to content

Commit b4dac39

Browse files
authored
Support Printable SimLog filenames (#4892)
All runtime-varying filenames via substitutions.
1 parent 28fc08f commit b4dac39

File tree

12 files changed

+121
-41
lines changed

12 files changed

+121
-41
lines changed

core/src/main/scala/chisel3/Data.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -694,12 +694,14 @@ abstract class Data extends HasId with NamedComponent with DataIntf {
694694
}
695695
private[chisel3] def visibleFromBlock: Option[SourceInfo] = MonoConnect.checkBlockVisibility(this)
696696
private[chisel3] def requireVisible()(implicit info: SourceInfo): Unit = {
697+
this.checkVisible.foreach(err => Builder.error(err))
698+
}
699+
// Some is an error message, None means no error
700+
private[chisel3] def checkVisible(implicit info: SourceInfo): Option[String] = {
697701
if (!isVisibleFromModule) {
698-
throwException(s"operand '$this' is not visible from the current module ${Builder.currentModule.get.name}")
699-
}
700-
visibleFromBlock match {
701-
case Some(blockInfo) => MonoConnect.escapedScopeError(this, blockInfo)
702-
case None => ()
702+
Some(s"operand '$this' is not visible from the current module ${Builder.currentModule.get.name}")
703+
} else {
704+
visibleFromBlock.map(MonoConnect.escapedScopeErrorMsg(this, _))
703705
}
704706
}
705707

core/src/main/scala/chisel3/Printable.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package chisel3
44

55
import chisel3.experimental.SourceInfo
6+
import chisel3.internal.Builder
67
import chisel3.internal.firrtl.ir.Component
78
import chisel3.FirrtlFormat.FormatWidth
89

@@ -167,7 +168,7 @@ object Printable {
167168
}
168169
}
169170

170-
private[chisel3] def checkScope(message: Printable)(implicit info: SourceInfo): Unit = {
171+
private[chisel3] def checkScope(message: Printable, msg: String = "")(implicit info: SourceInfo): Unit = {
171172
def getData(x: Printable): Seq[Data] = {
172173
x match {
173174
case y: FirrtlFormat => Seq(y.bits)
@@ -177,7 +178,11 @@ object Printable {
177178
case _ => Seq() // Handles subtypes PString and Percent
178179
}
179180
}
180-
getData(message).foreach(_.requireVisible())
181+
for (data <- getData(message)) {
182+
for (err <- data.checkVisible) {
183+
Builder.error(msg + err)
184+
}
185+
}
181186
}
182187

183188
/** Extract the Data that will be arguments to Firrtl

core/src/main/scala/chisel3/SimLog.scala

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,19 @@ import chisel3.internal.Builder
88
import chisel3.experimental.SourceInfo
99

1010
/** A file or I/O device to print to in simulation
11-
*
11+
*
1212
* {{{
13-
* val fd = SimLog.file("logfile.log")
14-
* fd.printf(cf"in = $in%0d\n")
13+
* val log = SimLog.file("logfile.log")
14+
* log.printf(cf"in = $in%0d\n")
15+
*
16+
* val stderr = SimLog.StdErr
17+
* stderr.printf(cf"in = $in%0d\n")
18+
*
19+
* // SimLog filenames themselves can be Printable.
20+
* // Be careful to avoid uninitialized registers.
21+
* val idx = Wire(UInt(8.W))
22+
* val log2 = SimLog.file(cf"logfile_$idx%0d.log")
23+
* log2.printf(cf"in = $in%0d\n")
1524
* }}}
1625
*/
1726
sealed trait SimLog extends SimLogIntf {
@@ -34,8 +43,8 @@ sealed trait SimLog extends SimLogIntf {
3443
this.printfWithReset(pable)(sourceInfo)
3544
}
3645

37-
// Eventually this might be a richer type but for now Some[String] is filename, None is Stderr
38-
protected def _filename: Option[String]
46+
// Eventually this might be a richer type but for now Some[Printable] is filename, None is Stderr
47+
protected def _filename: Option[Printable]
3948

4049
private[chisel3] def printfWithReset(
4150
pable: Printable
@@ -57,7 +66,8 @@ sealed trait SimLog extends SimLogIntf {
5766
val clock = Builder.forcedClock
5867
val printfId = new chisel3.printf.Printf(pable)
5968

60-
Printable.checkScope(pable)
69+
Printable.checkScope(pable, "printf ")
70+
_filename.foreach(Printable.checkScope(_, "SimLog filename "))
6171

6272
layer.block(layers.Verification, skipIfAlreadyInBlock = true, skipIfLayersEnabled = true) {
6373
Builder.pushCommand(Printf(printfId, sourceInfo, _filename, clock.ref, pable))
@@ -74,21 +84,25 @@ sealed trait SimLog extends SimLogIntf {
7484
printfWithoutReset(Printable.pack(fmt, data: _*))
7585
}
7686

77-
// Uses firrtl fprintf with a String filename
78-
private case class SimLogFile(filename: String) extends SimLog {
79-
override protected def _filename: Option[String] = Some(filename)
87+
// Uses firrtl fprintf with a Printable filename
88+
private case class SimLogFile(filename: Printable) extends SimLog {
89+
override protected def _filename: Option[Printable] = Some(filename)
8090
}
8191

8292
// Defaults to firrtl printf
8393
private object StdErrSimLog extends SimLog {
84-
override protected def _filename: Option[String] = None
94+
override protected def _filename: Option[Printable] = None
8595
}
8696

8797
object SimLog {
8898

8999
/** Print to a file given by `filename`
90100
*/
91101
def file(filename: String)(implicit sourceInfo: SourceInfo): SimLog = {
102+
new SimLogFile(PString(filename))
103+
}
104+
105+
def file(filename: Printable)(implicit sourceInfo: SourceInfo): SimLog = {
92106
new SimLogFile(filename)
93107
}
94108

core/src/main/scala/chisel3/internal/MonoConnect.scala

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,8 @@ private[chisel3] object MonoConnect {
5252
MonoConnectException(
5353
s"""${formatName(sink)} cannot be written from module ${source.parentNameOpt.getOrElse("(unknown)")}."""
5454
)
55-
def escapedScopeError(data: Data, blockInfo: SourceInfo)(implicit lineInfo: SourceInfo): Unit = {
56-
val msg = s"'$data' has escaped the scope of the block (${blockInfo.makeMessage()}) in which it was constructed."
57-
Builder.error(msg)
55+
def escapedScopeErrorMsg(data: Data, blockInfo: SourceInfo) = {
56+
s"'$data' has escaped the scope of the block (${blockInfo.makeMessage()}) in which it was constructed."
5857
}
5958
def UnknownRelationException =
6059
MonoConnectException("Sink or source unavailable to current module.")
@@ -284,12 +283,12 @@ private[chisel3] object MonoConnect {
284283
}
285284

286285
checkBlockVisibility(sink) match {
287-
case Some(blockInfo) => escapedScopeError(sink, blockInfo)
286+
case Some(blockInfo) => Builder.error(escapedScopeErrorMsg(sink, blockInfo))
288287
case None => ()
289288
}
290289

291290
checkBlockVisibility(source) match {
292-
case Some(blockInfo) => escapedScopeError(source, blockInfo)
291+
case Some(blockInfo) => Builder.error(escapedScopeErrorMsg(source, blockInfo))
293292
case None => ()
294293
}
295294

@@ -516,7 +515,7 @@ private[chisel3] object checkConnect {
516515
// Import helpers and exception types.
517516
import MonoConnect.{
518517
checkBlockVisibility,
519-
escapedScopeError,
518+
escapedScopeErrorMsg,
520519
UnknownRelationException,
521520
UnreadableSourceException,
522521
UnwritableSinkException
@@ -594,12 +593,12 @@ private[chisel3] object checkConnect {
594593
else throw UnknownRelationException
595594

596595
checkBlockVisibility(sink) match {
597-
case Some(blockInfo) => escapedScopeError(sink, blockInfo)(sourceInfo)
596+
case Some(blockInfo) => Builder.error(escapedScopeErrorMsg(sink, blockInfo))(sourceInfo)
598597
case None => ()
599598
}
600599

601600
checkBlockVisibility(source) match {
602-
case Some(blockInfo) => escapedScopeError(source, blockInfo)(sourceInfo)
601+
case Some(blockInfo) => Builder.error(escapedScopeErrorMsg(source, blockInfo))(sourceInfo)
603602
case None => ()
604603
}
605604
}

core/src/main/scala/chisel3/internal/firrtl/Converter.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,10 @@ private[chisel3] object Converter {
187187
fir.Stop(convert(info), ret, convert(clock, ctx, info), firrtl.Utils.one, e.name)
188188
case e @ Printf(_, info, filename, clock, pable) =>
189189
val mkPrintf = filename match {
190-
case None => fir.Print.apply _
191-
case Some(f) => fir.Fprint.apply(_, f, _, _, _, _, _)
190+
case None => fir.Print.apply _
191+
case Some(f) =>
192+
val (ffmt, fargs) = unpack(f, ctx, info)
193+
fir.Fprint.apply(_, fir.StringLit(ffmt), fargs.map(a => convert(a, ctx, info)), _, _, _, _, _)
192194
}
193195
val (fmt, args) = unpack(pable, ctx, info)
194196
mkPrintf(

core/src/main/scala/chisel3/internal/firrtl/IR.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -492,8 +492,13 @@ private[chisel3] object ir {
492492

493493
case class Port(id: Data, dir: SpecifiedDirection, sourceInfo: SourceInfo)
494494

495-
case class Printf(id: printf.Printf, sourceInfo: SourceInfo, filename: Option[String], clock: Arg, pable: Printable)
496-
extends Definition
495+
case class Printf(
496+
id: printf.Printf,
497+
sourceInfo: SourceInfo,
498+
filename: Option[Printable],
499+
clock: Arg,
500+
pable: Printable
501+
) extends Definition
497502

498503
case class ProbeDefine(sourceInfo: SourceInfo, sink: Arg, probe: Arg) extends Command
499504
case class ProbeForceInitial(sourceInfo: SourceInfo, probe: Arg, value: Arg) extends Command

core/src/main/scala/chisel3/internal/firrtl/Serializer.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,11 @@ private[chisel3] object Serializer {
255255
val (fmt, args) = unpack(pable, ctx, info)
256256
if (filename.isEmpty) b ++= "printf("; else b ++= "fprintf(";
257257
serialize(clock, ctx, info); b ++= ", UInt<1>(0h1), ";
258-
filename.foreach { f => b ++= "\""; b ++= f; b ++= "\", " }
258+
filename.foreach { fable =>
259+
val (ffmt, fargs) = unpack(fable, ctx, info)
260+
b ++= fir.StringLit(ffmt).escape; b ++= ", "
261+
fargs.foreach { a => serialize(a, ctx, info); b ++= ", " }
262+
}
259263
b ++= fir.StringLit(fmt).escape;
260264
args.foreach { a => b ++= ", "; serialize(a, ctx, info) }; b += ')'
261265
val lbl = e.name

docs/src/explanations/printing.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,23 @@ class MyModule extends Module {
232232
This is the same as standard `printf`.
233233
:::
234234

235+
SimLog filenames can themselves be `Printable` values:
236+
237+
```scala mdoc:compile-only
238+
class MyModule extends Module {
239+
val idx = IO(Input(UInt(8.W)))
240+
val log = SimLog.file(cf"logfile_$idx%0d.log")
241+
val in = IO(Input(UInt(8.W)))
242+
log.printf(cf"in = $in%d\n")
243+
}
244+
```
245+
246+
It is strongly recommended to use `%0d` with UInts in filenames to avoid spaces in the filename.
247+
248+
:::warning
249+
Be careful to avoid uninitialized registers in the filename.
250+
:::
251+
235252
### Writing Generic Code
236253

237254
`SimLog` allows you to write code that can work with any log destination. This is useful when creating reusable components:

firrtl/src/main/scala/firrtl/ir/IR.scala

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -437,13 +437,14 @@ case class Print(
437437

438438
@deprecated("All APIs in package firrtl are deprecated.", "Chisel 7.0.0")
439439
case class Fprint(
440-
val info: Info,
441-
val filename: String,
442-
val string: StringLit,
443-
val args: Seq[Expression],
444-
val clk: Expression,
445-
val en: Expression,
446-
val name: String = ""
440+
val info: Info,
441+
val filename: StringLit,
442+
val filenameArgs: Seq[Expression],
443+
val string: StringLit,
444+
val args: Seq[Expression],
445+
val clk: Expression,
446+
val en: Expression,
447+
val name: String = ""
447448
) extends Statement
448449
with HasInfo
449450
with IsDeclaration

firrtl/src/main/scala/firrtl/ir/Serializer.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,10 @@ object Serializer {
284284
b ++= "printf("; s(clk); b ++= ", "; s(en); b ++= ", "; b ++= string.escape
285285
if (args.nonEmpty) b ++= ", "; s(args, ", "); b += ')'
286286
sStmtName(print.name); s(info)
287-
case print @ Fprint(info, filename, string, args, clk, en, _) =>
288-
b ++= "fprintf("; s(clk); b ++= ", "; s(en); b ++= ", \""; b ++= filename; b ++= "\", "; b ++= string.escape
287+
case print @ Fprint(info, filename, fargs, string, args, clk, en, _) =>
288+
b ++= "fprintf("; s(clk); b ++= ", "; s(en); b ++= ", "; b ++= filename.escape; b ++= ", "
289+
s(fargs, ", "); if (fargs.nonEmpty) b ++= ", ";
290+
b ++= string.escape
289291
if (args.nonEmpty) b ++= ", "; s(args, ", "); b += ')'
290292
sStmtName(print.name); s(info)
291293
case IsInvalid(info, expr) => b ++= "invalidate "; s(expr); s(info)

firrtl/src/test/scala/firrtlTests/SerializerSpec.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -412,13 +412,14 @@ class SerializerSpec extends AnyFlatSpec with Matchers {
412412
Serializer.serialize(
413413
Fprint(
414414
NoInfo,
415-
"filename",
415+
StringLit("filename_%0d"),
416+
Seq(Reference("x")),
416417
StringLit("hello %x"),
417418
Seq(Reference("arg")),
418419
Reference("clock"),
419420
Reference("enable"),
420421
"label"
421422
)
422-
) should include("""fprintf(clock, enable, "filename", "hello %x", arg) : label""")
423+
) should include("""fprintf(clock, enable, "filename_%0d", x, "hello %x", arg) : label""")
423424
}
424425
}

src/test/scala-2/chiselTests/SimLogSpec.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,34 @@ class SimLogSpec extends AnyFlatSpec with Matchers with FileCheck with ChiselSim
5757
)
5858
}
5959

60+
it should "support Printable filenames" in {
61+
class MyModule extends Module {
62+
val idx = IO(Input(UInt(8.W)))
63+
val fd = SimLog.file(cf"logfile_$idx%0d.log")
64+
fd.printf(cf"An exact string")
65+
}
66+
ChiselStage
67+
.emitCHIRRTL(new MyModule)
68+
.fileCheck()(
69+
"""CHECK: fprintf(clock, UInt<1>(0h1), "logfile_%0d.log", idx, "An exact string")"""
70+
)
71+
}
72+
73+
it should "check scope for Printable filenames" in {
74+
class Child(log: SimLog) extends Module {
75+
val bar = IO(Input(UInt(8.W)))
76+
log.printf(cf"bar = $bar%0d\n")
77+
}
78+
class MyModule extends Module {
79+
val foo = Wire(UInt(8.W))
80+
val log = SimLog.file(cf"logfile_$foo%0d.log")
81+
val child = Module(new Child(log))
82+
child.bar := foo
83+
}
84+
val e = the[ChiselException] thrownBy ChiselStage.emitCHIRRTL(new MyModule, Array("--throw-on-first-error"))
85+
(e.getMessage should include).regex("SimLog filename operand '.*' is not visible from the current module Child")
86+
}
87+
6088
it should "support writing to a file in simulation" in {
6189
val testdir = implicitly[HasTestingDirectory].getDirectory
6290
val logfile = testdir.resolve("workdir-verilator").resolve("logfile.log").toFile

0 commit comments

Comments
 (0)