A SystemVerilog string is a variable-length byte sequence that grows and shrinks at runtime. It compares lexicographically, concatenates with braces, and indexes per character, with each element a byte.
Two semantics points trip people up. substr(i, j) takes start and end indexes, both inclusive, not start and length. Assignment copies the contents, so strings are value types with no aliasing and no null handle.
Expression | Value |
|---|---|
| 11 for |
| "AXI4_MASTER" |
| "AXI4" |
| 65, the code for |
| 42, with |
real is an IEEE-754 double and shortreal a float, both testbench-only in practice. They earn their keep in bandwidth statistics, reference models, and timing math with realtime.
Conversions need care: assigning real to integer rounds to nearest while $rtoi truncates, and integer division drops the remainder before any real context sees it, so cast one operand first. Never compare computed reals with ==; compare against an epsilon.
chandle is an opaque pointer-sized handle used to hold a pointer that C code returned. SystemVerilog only stores it, passes it back and compares it with null. It only matters once you call C from SystemVerilog, which DPI-C covers.
event is the named synchronization primitive: trigger with ->ev and wait with @(ev) or wait(ev.triggered). The latter wins when trigger and wait can land in the same timestep, because the triggered state persists while a bare @ that starts waiting too late misses the pulse.
Event Scheduling explains why the same-timestep case is a race. The bench below uses fork ... join, which runs the two blocks inside it at the same time and waits for both; Part 2 teaches it properly.
Build messages with $sformatf, which returns the string, and print enums with .name(). Bare %d pads to the full type width, so %0d and %0h strip the padding in log lines.
s holds "AXI4_master". What does s.substr(0, 3) return?"AXI4". The second argument is the inclusive end index, not a length, so indexes 0 to 3 are four characters.
String methods and conversions, realtime bandwidth math with rounding rules, and the race-free event wait.
module tb_strings;
string s = "AXI4_master";
string t;
int errors = 0;
initial begin
if (s.len() != 11) errors++;
if (s.toupper() != "AXI4_MASTER") errors++;
if (s.substr(0, 3) != "AXI4") errors++; // end index, inclusive
if (s.getc(0) != 65) errors++;
t = s;
t.putc(0, 97); // value copy: s keeps its A
if (t != "aXI4_master") errors++;
if (s != "AXI4_master") errors++;
t = "42";
if (t.atoi() != 42) errors++;
t = "";
t.itoa(255);
if (t != "255") errors++;
t.hextoa(255);
if (t != "ff") errors++;
if ($sformatf("mst_%0d", 3) != "mst_3") errors++;
if (s.substr(0, 2) != "AXI") errors++;
if (errors != 0) $fatal(1, "FAIL: string checks saw %0d unexpected results", errors);
$display("PASS: len, case, substr, getc, putc, atoi, itoa and sformatf all agreed");
$finish;
end
endmodulePASS: len, case, substr, getc, putc, atoi, itoa and sformatf all agreedmodule tb_realtime;
timeunit 1ns;
timeprecision 1ps;
realtime t_start, t_end, dur;
real bw, exp_bw = 409.6;
int bytes_moved = 4096;
int errors = 0;
initial begin
t_start = $realtime;
#10;
t_end = $realtime;
dur = t_end - t_start; // already in ns from the timeunit
if (dur != 10.0) errors++;
bw = real'(bytes_moved) / dur; // cast first, then divide
if ((bw < exp_bw - 0.001) || (bw > exp_bw + 0.001)) errors++;
if (int'(2.5) !== 3) errors++; // static cast rounds
if ($rtoi(2.5) !== 2) errors++; // rtoi truncates
if ($itor(7) / 2 != 3.5) errors++;
if (errors != 0) $fatal(1, "FAIL: real-math checks saw %0d unexpected results", errors);
$display("PASS: realtime measured 10 ns, bandwidth matched, rounding rules held");
$finish;
end
endmodulePASS: realtime measured 10 ns, bandwidth matched, rounding rules heldmodule tb_event;
timeunit 1ns; timeprecision 1ps;
event done_ev;
bit seen = 1'b0;
int errors = 0;
initial begin
fork
begin
wait (done_ev.triggered); // safe even if the trigger already fired
seen = 1'b1;
end
begin
-> done_ev;
end
join
#1;
if (seen !== 1'b1) errors++;
if (errors != 0) $fatal(1, "FAIL: event wait saw %0d unexpected results", errors);
$display("PASS: triggered wait caught the same-timestep event");
$finish;
end
endmodulePASS: triggered wait caught the same-timestep eventa and b hold the same text, and the code changes b.putc(0, 97). Does a change?No. Assigning a string copies its contents, so strings have no aliasing and no null value, unlike class handles.
bw as real'(bytes) / dur with dur measured at 10 ns for 4096 bytes. Why cast before dividing?Two ints would divide as integers and drop the fraction first. The cast lifts the division into reals so the fractional bandwidth survives.
Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. Icarus 13 cannot run the string bench (no toupper) or the event bench (no .triggered); the real-math bench runs on both.
chandle is described only; DPI-C, which uses it, is run in its own lesson.