Strings and useful diagnostics
Build failure messages with $sformatf that say which transaction failed, and choose a format that shows unknown bits.
- Lesson
- 1.5
- Time
- 20 min
- Runs on
- Your browser
- Check
- 5 questions
A nightly regression of 40,000 transactions fails with a single line: ERROR: data mismatch. The message names no transaction, no address and no values. The engineer reruns the whole test with full waveform dumping, which takes three hours, just to find out which read went wrong. A message built from the scoreboard's own variables would have answered that question in the log.
Every fact the log needed was in a variable at the moment of failure.
Strings hold text of any length
A string variable holds a sequence of characters whose length can change at run time. A new string is empty, so its length is 0 until you assign it.
Strings are for names, paths and messages rather than bus data. A DUT field is a fixed number of bits, so keep it in a packed type and turn it into text only when you print it.
| Expression, with s = "WRITE" | Result |
|---|---|
s.len() | 5 |
s[0] | "W", as a byte (8'h57) |
s.substr(1, 3) | "RIT" |
s[9] | 0 (index past the end) |
{s, "_OK"} | "WRITE_OK" |
s == "WRITE" | 1 |
Common string operations.
Indexes start at 0, and substr(i, j) includes both character i and character j. An index past the end returns 0, and an out-of-range substr returns an empty string, so bad indexes fail quietly.
Strings compare with the usual operators. == is true when both strings hold the same characters, and < orders them by character code, which is handy for sorting names in a report.
Build the message once with $sformatf
$sformatf formats text exactly like $display, but it returns the text as a string instead of printing it. You can then store the string, pass it to $error or write it to a log file.
A useful mismatch message answers four questions: which transaction failed, at which address, what was expected and what arrived. The scoreboard already holds all four in its variables at the moment of failure.
Use %0d for counters and IDs, because plain %d pads the number with spaces to the widest value the type can hold. An int with value 7 prints as ten spaces followed by the 7.
A message with no facts
if (got !== exp)
$error("data mismatch");
You know that a read failed, but not which one, where, or how.
A message built from the scoreboard state
string msg;
if (got !== exp) begin
msg = $sformatf("txn %0d addr %h: expected %b got %b",
id, addr, exp, got);
$error("%s", msg);
end
The message reads txn 4118 addr 0044: expected 00000000 got 0000x000.
Report through a severity task
Where $display only prints, the severity tasks $info, $warning, $error and $fatal add the severity, the source file and line, and the simulation time to your message. Icarus Verilog, for example, prints ERROR: tb.sv:42: followed by your text, the time and the scope.
Use $error for a failed check, because the word ERROR and the file location make the failure easy to find in a long log. Regression scripts search for exactly that word.
$fatal reports the same way and then ends the simulation. It suits a broken setup, such as a missing configuration file, that would make every later result meaningless.
Messages printed with $display should carry the simulation time, using $time and the %0t format. A failure stamped at 41,180 ns tells you where to open the waveform, so you never need to dump the whole run.
Pick a format that shows x
Formats differ in how they show unknown bits. %b prints one character per bit, so every x and z stays visible in its own position.
The %h format prints one digit for every 4 bits. A digit whose 4 bits are all x prints as a lowercase x, while a digit with only some x bits prints as an uppercase X. High impedance follows the same rule with z and Z.
The %d format cannot show part of a value at all. If any bit is x, it prints a single X, or a lowercase x when every bit is unknown.
| Value | %b | %h | %0d |
|---|---|---|---|
8'b0011_x100 | 0011x100 | 3X | X |
8'bxxxx_0001 | xxxx0001 | x1 | X |
8'b0000_zzzz | 0000zzzz | 0z | Z |
8'bxxxx_xxxx | xxxxxxxx | xx | x |
The same values in three formats, as printed by Icarus Verilog 12.
For four-state data, print %b in error messages, or print both %h and %b. A message that says got 3X tells you that something is unknown, but not which bit.
Keep the check apart from the message
Decide pass or fail on the values first, and only then format the message. Never compare formatted strings to decide a result.
Two different values can print identically. 8'b0011_x100 and 8'b0011_0x00 both print as 3X with %h, so their %h strings are equal, while === on the values correctly reports that they differ. A scoreboard that compares %h text against an expected string has this bug built in.
Keeping the two apart also makes messages safe to edit. You can reword or reformat a message without any risk of changing whether the test passes.
Try it
The %h message shows that something is unknown in the low digit. The %b message shows exactly which bit: bit 3. The decimal form hides everything except the fact that a bit is unknown.
The last line compares two different values. Their %h strings match, while === says they differ. Change got_data to 8'b0011_1100 and run it again.
len=5 first=W substr(1,3)=RIT txn 4118 addr 0044: expected 3c got 3X txn 4118 addr 0044: expected 00111100 got 0011x100 as decimal: got=X same %h text: 1, same value: 0Check yourself
Remember
- A
stringstarts empty; indexes start at 0, andsubstr(i, j)includes both ends. $sformatfreturns formatted text, so you can build a message once and send it anywhere.- A mismatch message names the transaction, the address, the expected value and the actual value.
%hprintsXfor a partly unknown digit; use%bto see which bits are unknown.- Decide pass or fail on values, never on formatted strings.
Practice
- Catch the broken formatters Break it · Easy