Skip to content

Commit 1cfea4a

Browse files
committed
Refine expect failure formatting API
1 parent b2c2be4 commit 1cfea4a

5 files changed

Lines changed: 388 additions & 203 deletions

File tree

docs/src/cookbooks/testing.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,23 @@ for downstream reporting tools.
6161

6262
## How do I customize `expect` failure value formatting?
6363

64-
Pass a format directly to `expect`.
64+
Pass a format directly to `expect`. `Hex` groups pairs of digits by byte, and `Bin` groups bits in fours.
6565

6666
```scala
6767
import chisel3.simulator.ExpectationValueFormat
6868

6969
simulate(new Foo) { dut =>
70+
// Render failures value in hexadecimal
7071
dut.io.out.expect(0xfe.U, ExpectationValueFormat.Hex)
72+
73+
// Render failures value in binary
7174
dut.io.out.expect(0xff.U, ExpectationValueFormat.Bin)
7275

7376
val jumpInst = BigInt("0000006f", 16) // jal x0, 0
7477
val retInst = BigInt("00008067", 16) // jalr x0, x1, 0 (ret)
78+
79+
// A custom formatter can inspect the raw bits through `value.unsignedValue` and
80+
// render a domain-specific label without changing the comparison semantics.
7581
val custom = ExpectationValueFormat.Custom { value =>
7682
val mnemonic = value.unsignedValue match {
7783
case `jumpInst` => "jump"
@@ -80,6 +86,8 @@ simulate(new Foo) { dut =>
8086
}
8187
s"riscv($mnemonic)"
8288
}
89+
90+
// This still checks the exact value; only the failure message rendering is customized.
8391
dut.io.out.expect(jumpInst.U(32.W), custom)
8492
}
8593
```

docs/src/explanations/testing.md

Lines changed: 53 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -106,55 +106,64 @@ These APIs are summarized below:
106106
#### Formatting `expect` Failure Values
107107

108108
When `expect` fails, ChiselSim throws a `FailedExpectationException` that
109-
prints observed and expected values. You can configure how these values are
110-
rendered for each `expect` call.
109+
prints observed and expected values in decimal by default. You can override
110+
that per call with `ExpectationValueFormat.Dec`, `Hex`, `Bin`, or a custom
111+
formatter. `Hex` groups pairs of digits by byte, and `Bin` groups bits in fours. A custom formatter can either render each value independently or
112+
build a message from both observed and expected values.
111113

112-
Available formats are:
113-
114-
- `ExpectationValueFormat.Default` (existing behavior)
115-
- `ExpectationValueFormat.Hex` (numeric part printed as `0x...`)
116-
- `ExpectationValueFormat.Bin` (numeric part printed as `0b...`)
117-
- `ExpectationValueFormat.Custom(fn)` (user-defined formatting function)
118-
119-
Format is selected per call by passing an extra argument to `expect`:
120-
121-
- `expect(expected, format)`
122-
- `expect(expected, message, format)`
114+
Inside a `simulate { dut => ... }` block, for example:
123115

124116
```scala
125-
import chisel3._
126-
import chisel3.simulator._
127-
import chisel3.simulator.scalatest.ChiselSim
128-
import org.scalatest.funspec.AnyFunSpec
129-
130-
class ExpectFormatExample extends AnyFunSpec with ChiselSim {
131-
class Foo extends Module {
132-
val in = IO(Input(UInt(32.W)))
133-
val out = IO(Output(UInt(32.W)))
134-
out := in
135-
}
117+
dut.in.poke(3.U)
118+
// failure shows UInt<32>(0x00 00 00 03) vs UInt<32>(0x00 00 00 05)
119+
dut.out.expect(5.U, ExpectationValueFormat.Hex)
120+
// failure shows UInt<32>(0b0000 0000 0000 0000 0000 0000 0000 0011) vs UInt<32>(0b0000 0000 0000 0000 0000 0000 0000 0101)
121+
dut.out.expect(5.U, ExpectationValueFormat.Bin)
122+
123+
val jumpInst = BigInt("0000006f", 16) // jal x0, 0
124+
val retInst = BigInt("00008067", 16) // jalr x0, x1, 0 (ret)
125+
def decode(value: ExpectationValueFormat.Value): String = value.unsignedValue match {
126+
case `jumpInst` => "jump"
127+
case `retInst` => "ret"
128+
case inst => s"unknown(0x${inst.toString(16)})"
129+
}
130+
val riscv = ExpectationValueFormat.Custom { value =>
131+
s"riscv(${decode(value)})"
132+
}
133+
dut.in.poke(retInst.U(32.W))
134+
// failure shows riscv(ret) vs riscv(jump)
135+
dut.out.expect(jumpInst.U(32.W), riscv)
136+
```
136137

137-
it("formats failed expect values") {
138-
simulate(new Foo) { dut =>
139-
dut.in.poke(3.U)
140-
dut.out.expect(5.U, ExpectationValueFormat.Hex) // failure shows UInt<32>(0x3) vs UInt<3>(0x5)
141-
dut.out.expect(5.U, ExpectationValueFormat.Bin) // failure shows UInt<32>(0b11) vs UInt<3>(0b101)
142-
143-
val jumpInst = BigInt("0000006f", 16) // jal x0, 0
144-
val retInst = BigInt("00008067", 16) // jalr x0, x1, 0 (ret)
145-
val custom = ExpectationValueFormat.Custom { value =>
146-
val mnemonic = value.unsignedValue match {
147-
case `jumpInst` => "jump"
148-
case `retInst` => "ret"
149-
case inst => s"unknown(0x${inst.toString(16)})"
150-
}
151-
s"riscv($mnemonic)"
152-
}
153-
dut.in.poke(retInst.U(32.W))
154-
dut.out.expect(jumpInst.U(32.W), custom)
155-
}
156-
}
138+
```scala
139+
def bits(value: ExpectationValueFormat.Value): String =
140+
value.unsignedValue.toString(2).reverse.padTo(value.bitWidth, '0').reverse.mkString
141+
142+
val bitDiff = ExpectationValueFormat.Custom.message(
143+
ExpectationValueFormat.Custom.values(bits)
144+
) { (observed, expected) =>
145+
val observedBits = bits(observed)
146+
val expectedBits = bits(expected)
147+
val markers = observedBits.zip(expectedBits).map {
148+
case (observedBit, expectedBit) => if (observedBit == expectedBit) ' ' else '^'
149+
}.mkString
150+
val diffBits = observedBits.zip(expectedBits).zipWithIndex.collect {
151+
case ((observedBit, expectedBit), idx) if observedBit != expectedBit =>
152+
observedBits.length - 1 - idx
153+
}.sorted
154+
val indent = " " * "Observed value: '".length
155+
156+
s"""|$indent$markers
157+
|Diff Bit: ${diffBits.mkString(",")}""".stripMargin
157158
}
159+
160+
dut.in.poke("b101111".U)
161+
// failure shows:
162+
// Observed value: '101111'
163+
// Expected value: '100101'
164+
// ^ ^
165+
// Diff Bit: 1,3
166+
dut.out.expect("b100101".U, bitDiff)
158167
```
159168

160169
For more information see the [Chisel API

0 commit comments

Comments
 (0)