Skip to content

Commit 43bba60

Browse files
authored
Add Simlog.flush (#4895)
Add SimLog.flush, an API to flush prints to their files/file descriptors.
1 parent b4dac39 commit 43bba60

File tree

10 files changed

+92
-7
lines changed

10 files changed

+92
-7
lines changed

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
package chisel3
44

55
import chisel3._
6-
import chisel3.internal.firrtl.ir.Printf
6+
import chisel3.internal.firrtl.ir.{Flush, Printf}
77
import chisel3.internal.Builder
88
import chisel3.experimental.SourceInfo
99

@@ -43,6 +43,18 @@ sealed trait SimLog extends SimLogIntf {
4343
this.printfWithReset(pable)(sourceInfo)
4444
}
4545

46+
/** Flush any buffered output immediately */
47+
def flush()(implicit sourceInfo: SourceInfo): Unit = {
48+
val clock = Builder.forcedClock
49+
_filename.foreach(Printable.checkScope(_, "SimLog filename "))
50+
51+
when(!Module.reset.asBool) {
52+
layer.block(layers.Verification, skipIfAlreadyInBlock = true, skipIfLayersEnabled = true) {
53+
Builder.pushCommand(Flush(sourceInfo, _filename, clock.ref))
54+
}
55+
}
56+
}
57+
4658
// Eventually this might be a richer type but for now Some[Printable] is filename, None is Stderr
4759
protected def _filename: Option[Printable]
4860

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ private[chisel3] object MonoConnect {
5353
s"""${formatName(sink)} cannot be written from module ${source.parentNameOpt.getOrElse("(unknown)")}."""
5454
)
5555
def escapedScopeErrorMsg(data: Data, blockInfo: SourceInfo) = {
56-
s"'$data' has escaped the scope of the block (${blockInfo.makeMessage()}) in which it was constructed."
56+
s"operand '$data' has escaped the scope of the block (${blockInfo.makeMessage()}) in which it was constructed."
5757
}
5858
def UnknownRelationException =
5959
MonoConnectException("Sink or source unavailable to current module.")

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,10 @@ private[chisel3] object Converter {
201201
firrtl.Utils.one,
202202
e.name
203203
)
204+
case e @ Flush(info, filename, clock) =>
205+
val (fmt, args) = filename.map(unpack(_, ctx, info)).getOrElse(("", Seq.empty))
206+
val fn = Option.when(fmt.nonEmpty)(fir.StringLit(fmt))
207+
fir.Flush(convert(info), fn, args.map(a => convert(a, ctx, info)), convert(clock, ctx, info))
204208
case e @ ProbeDefine(sourceInfo, sink, probeExpr) =>
205209
fir.ProbeDefine(convert(sourceInfo), convert(sink, ctx, sourceInfo), convert(probeExpr, ctx, sourceInfo))
206210
case e @ ProbeForceInitial(sourceInfo, probe, value) =>

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,12 @@ private[chisel3] object ir {
500500
pable: Printable
501501
) extends Definition
502502

503+
case class Flush(
504+
sourceInfo: SourceInfo,
505+
filename: Option[Printable],
506+
clock: Arg
507+
) extends Command
508+
503509
case class ProbeDefine(sourceInfo: SourceInfo, sink: Arg, probe: Arg) extends Command
504510
case class ProbeForceInitial(sourceInfo: SourceInfo, probe: Arg, value: Arg) extends Command
505511
case class ProbeReleaseInitial(sourceInfo: SourceInfo, probe: Arg) extends Command

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,14 @@ private[chisel3] object Serializer {
265265
val lbl = e.name
266266
if (lbl.nonEmpty) { b ++= " : "; b ++= legalize(lbl) }
267267
serialize(e.sourceInfo)
268+
case e @ Flush(info, filename, clock) =>
269+
b ++= "fflush("; serialize(clock, ctx, info); b ++= ", UInt<1>(0h1)";
270+
filename.foreach { fable =>
271+
val (ffmt, fargs) = unpack(fable, ctx, info)
272+
b ++= ", "; b ++= fir.StringLit(ffmt).escape
273+
fargs.foreach { a => b ++= ", "; serialize(a, ctx, info) }
274+
}
275+
b += ')'; serialize(info)
268276
case e @ ProbeDefine(sourceInfo, sink, probeExpr) =>
269277
b ++= "define "; serialize(sink, ctx, sourceInfo); b ++= " = "; serialize(probeExpr, ctx, sourceInfo);
270278
serialize(sourceInfo)

docs/src/explanations/printing.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,4 +264,22 @@ val withFile = Module(new MyLogger(SimLog.file("data.log")))
264264

265265
// Use with stderr
266266
val withStderr = Module(new MyLogger(SimLog.StdErr))
267+
```
268+
269+
### Flush
270+
271+
`SimLog` objects can be flushed to ensure that all buffered output is written.
272+
This is useful in simulations using the logged output as input to a co-simulated components like a checker or golden model.
273+
274+
```scala mdoc:compile-only
275+
val log = SimLog.file("logfile.log")
276+
val in = IO(Input(UInt(8.W)))
277+
log.printf(cf"in = $in%d\n")
278+
log.flush() // Flush buffered output right away.
279+
```
280+
281+
You can also flush standard error:
282+
283+
```scala mdoc:compile-only
284+
SimLog.StdErr.flush() // This will flush all standard printfs.
267285
```

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,12 @@ case class Fprint(
450450
with IsDeclaration
451451
with UseSerializer
452452

453+
@deprecated("All APIs in package firrtl are deprecated.", "Chisel 7.0.0")
454+
case class Flush(val info: Info, val filename: Option[StringLit], args: Seq[Expression], val clk: Expression)
455+
extends Statement
456+
with HasInfo
457+
with UseSerializer
458+
453459
@deprecated("All APIs in package firrtl are deprecated.", "Chisel 7.0.0")
454460
case class ProbeDefine(info: Info, sink: Expression, probeExpr: Expression) extends Statement with UseSerializer
455461
@deprecated("All APIs in package firrtl are deprecated.", "Chisel 7.0.0")

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,10 @@ object Serializer {
290290
b ++= string.escape
291291
if (args.nonEmpty) b ++= ", "; s(args, ", "); b += ')'
292292
sStmtName(print.name); s(info)
293+
case flush @ Flush(info, filename, args, clk) =>
294+
b ++= "fflush("; s(clk); b ++= ", UInt<1>(0h1)";
295+
filename.foreach { f => b ++= ", "; b ++= f.escape; b ++= ", "; s(args, ", ") }
296+
b += ')'; s(info)
293297
case IsInvalid(info, expr) => b ++= "invalidate "; s(expr); s(info)
294298
case DefWire(info, name, tpe) => b ++= "wire "; b ++= legalize(name); b ++= " : "; s(tpe); s(info)
295299
case DefRegister(info, name, tpe, clock) =>

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,5 +421,12 @@ class SerializerSpec extends AnyFlatSpec with Matchers {
421421
"label"
422422
)
423423
) should include("""fprintf(clock, enable, "filename_%0d", x, "hello %x", arg) : label""")
424+
info("flush okay!")
425+
Serializer.serialize(
426+
Flush(NoInfo, Some(StringLit("filename_%0d")), Seq(Reference("x")), Reference("clock"))
427+
) should include("""fflush(clock, UInt<1>(0h1), "filename_%0d", x)""")
428+
Serializer.serialize(
429+
Flush(NoInfo, None, Seq.empty, Reference("clock"))
430+
) should include("""fflush(clock, UInt<1>(0h1))""")
424431
}
425432
}

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

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ class SimLogSpec extends AnyFlatSpec with Matchers with FileCheck with ChiselSim
1919
class MyModule extends Module {
2020
val fd = SimLog.file("logfile.log")
2121
fd.printf(cf"An exact string")
22+
fd.flush()
2223
}
2324
ChiselStage
2425
.emitCHIRRTL(new MyModule)
2526
.fileCheck()(
26-
"""CHECK: fprintf(clock, UInt<1>(0h1), "logfile.log", "An exact string")"""
27+
"""|CHECK: fprintf(clock, UInt<1>(0h1), "logfile.log", "An exact string")
28+
|CHECK: fflush(clock, UInt<1>(0h1), "logfile.log")""".stripMargin
2729
)
2830
}
2931

@@ -44,16 +46,19 @@ class SimLogSpec extends AnyFlatSpec with Matchers with FileCheck with ChiselSim
4446
class MyModule(fd: SimLog) extends Module {
4547
val in = IO(Input(UInt(8.W)))
4648
fd.printf(cf"in = $in%0d\n")
49+
fd.flush()
4750
}
4851
ChiselStage
4952
.emitCHIRRTL(new MyModule(SimLog.file("logfile.log")))
5053
.fileCheck()(
51-
"""CHECK: fprintf(clock, UInt<1>(0h1), "logfile.log", "in = %0d\n", in)"""
54+
"""|CHECK: fprintf(clock, UInt<1>(0h1), "logfile.log", "in = %0d\n", in)
55+
|CHECK: fflush(clock, UInt<1>(0h1), "logfile.log")""".stripMargin
5256
)
5357
ChiselStage
5458
.emitCHIRRTL(new MyModule(SimLog.StdErr))
5559
.fileCheck()(
56-
"""CHECK: printf(clock, UInt<1>(0h1), "in = %0d\n", in)"""
60+
"""|CHECK: printf(clock, UInt<1>(0h1), "in = %0d\n", in)
61+
|CHECK: fflush(clock, UInt<1>(0h1))""".stripMargin
5762
)
5863
}
5964

@@ -62,15 +67,17 @@ class SimLogSpec extends AnyFlatSpec with Matchers with FileCheck with ChiselSim
6267
val idx = IO(Input(UInt(8.W)))
6368
val fd = SimLog.file(cf"logfile_$idx%0d.log")
6469
fd.printf(cf"An exact string")
70+
fd.flush()
6571
}
6672
ChiselStage
6773
.emitCHIRRTL(new MyModule)
6874
.fileCheck()(
69-
"""CHECK: fprintf(clock, UInt<1>(0h1), "logfile_%0d.log", idx, "An exact string")"""
75+
"""|CHECK: fprintf(clock, UInt<1>(0h1), "logfile_%0d.log", idx, "An exact string")
76+
|CHECK: fflush(clock, UInt<1>(0h1), "logfile_%0d.log", idx)""".stripMargin
7077
)
7178
}
7279

73-
it should "check scope for Printable filenames" in {
80+
it should "check scope for Printable filenames on printf" in {
7481
class Child(log: SimLog) extends Module {
7582
val bar = IO(Input(UInt(8.W)))
7683
log.printf(cf"bar = $bar%0d\n")
@@ -85,6 +92,19 @@ class SimLogSpec extends AnyFlatSpec with Matchers with FileCheck with ChiselSim
8592
(e.getMessage should include).regex("SimLog filename operand '.*' is not visible from the current module Child")
8693
}
8794

95+
it should "check scope for Printable filenames on flush" in {
96+
class MyModule extends Module {
97+
var log: SimLog = null
98+
when(true.B) {
99+
val foo = Wire(UInt(8.W))
100+
log = SimLog.file(cf"logfile_$foo%0d.log")
101+
}
102+
log.flush()
103+
}
104+
val e = the[ChiselException] thrownBy ChiselStage.emitCHIRRTL(new MyModule, Array("--throw-on-first-error"))
105+
(e.getMessage should include).regex("SimLog filename operand '.*' has escaped the scope of the block")
106+
}
107+
88108
it should "support writing to a file in simulation" in {
89109
val testdir = implicitly[HasTestingDirectory].getDirectory
90110
val logfile = testdir.resolve("workdir-verilator").resolve("logfile.log").toFile

0 commit comments

Comments
 (0)