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.

The failing read, as the waveform shows it
012txn_id411741184119addr004000440048expected3c007frdata3c0X7flog says only "data mismatch"

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_x1000011x1003XX
8'bxxxx_0001xxxx0001x1X
8'b0000_zzzz0000zzzz0zZ
8'bxxxx_xxxxxxxxxxxxxxx

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.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: 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: 0

Check yourself

Predict the outputPredict the exact output.
module tb;
  string s;
  initial begin
    s = "BURST";
    $display("%s %0d", s.substr(1, 3), s.len());
  end
endmodule
Predict the outputPredict the exact output.
module tb;
  initial $display("%h", 8'b1x00_zzzz);
endmodule
ConceptTwo failing runs both log got=3X for the same 8-bit field. What does that tell you?
Conceptstring s; is declared and never assigned. What does s.len() return?
Spot the bugEach option is the body of function automatic bit same_value(logic [7:0] a, logic [7:0] b). It must return 1 only when a and b match bit for bit, including x and z. Which option is wrong?

Remember

  • A string starts empty; indexes start at 0, and substr(i, j) includes both ends.
  • $sformatf returns 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.
  • %h prints X for a partly unknown digit; use %b to see which bits are unknown.
  • Decide pass or fail on values, never on formatted strings.

Practice