Drive Strength Detection in SystemVerilog


From time to time I will run across small problems or challenges in my day job which seem as though they should be simple enough but get stuck in my mind until they are fully investigated. This is one such issue I ran into a while ago: How can a verilog model determine the drive strength of a net value in SystemVerilog?

When I initially ran into this problem, I thought it would require a quick search through the LRM and I’d be on my way. However, after some searching and not finding any useful constructs in the language that would directly provide visibility into a net’s drive strength, I ended up on google, and all the recommendations were similar to this post from stack overflow: How to check signal drive strength?. It seemed the recommended solution was to format the net value using the "%v" format string, and then do string comparisons on the formatted value.

Having run into my fair share of string and string formatting performance issues in SystemVerilog in the past, the suggested “solution” was instantly ringing some alarm bells. This is the problem that latched into my mind, and I needed to know if there was a better way… but first, a quick refresher on the drive strength modeling features in SystemVerilog.

Drive Strength Modeling

In SystemVerilog (and Verilog before it), a “net” is a special type of variable construct which is used to structurally model a wire in a circuit. The value of the “net” variable is a combination of a 4-state logical value (0,1,z,x), and a strength value (highz, weak, pull, strong, or supply). The value of a net is determined by “driver” elements connected to the net, which can be either continuous assignment expressions, or gate level modeling primitives. If semantically a SystemVerilog net represents a wire in a circuit, and the logical value represents the voltage on the wire, then the strength of the net represents how much current can that wire can source (or said another way, the impedance of the driver).

For every delta cycle of a simulation, the simulator will determine the logical and strength values of each net, by looking at the value and strength of all driver expressions on the net. If a net has no active drivers, then the value of the net is “high impedance” represented as 'z in SystemVerilog 4-state values. If a net has one driver expression or primitive, then the value and strength of the net are equal to that of the driver. However, if there are more than one driver connected to a single net, the logical value of the net is determined by comparing the strength of each driver on the net. If there is one driver on the net that has a strength lager than the rest, or there are multiple drivers that all have the same largest strength value, and the same logical value, then the logical value of the net is determined by the logical value of the driver with the greatest strength. If there are multiple drivers that all have the same greatest strength on the net, but have different values, then the value of the net is “unknown” or 'x with the strength of the strongest drivers.

Extracted figures from SystemVerilog LRM showing combinations of different strength drivers (left) and resulting net value (right)

Extracted figures from SystemVerilog LRM showing combinations of different strength drivers (left) and resulting net value (right)

If the strength value of a net is determined by the relative strength values of the drivers on the net, then there must be a way to define the strength of a driving expression. In SystemVerilog, by default all driver expressions have a strong strength value, which for a lot of use cases, is all you would ever need. However, the strength of logical 1 and 0 values can be individually set for both continuous assignment expressions and gate primitives by including a token of the form (<strength>1, <strength>0) after the assign keyword or primitive name. For example the expression assign (strong1, weak0) foo = ~bar; will drive the net foo with a logical value of 1 and strength of strong, when bar is a logical 0, and drive a logical value of 0 with strength value weak when bar is 1.

As an example, one might use drive strength modeling when modeling an I2C bus, which uses pull-up resistors to pull the bus high, and open drain drivers in each device to pull it low. In such a model, the pull up resistors on the bus can be simply modeled as a pull1 continuous assignments with a value of 1'b1:

1
assign (pull1, highz0) scl = 1'b1;

Additionally, the multiple I2C IO drivers can be modeled as driving the bus using a continuous assignment expressions with a (highz1, strong0) strength expression:

1
2
assign (highz1, strong0) scl = device0_scl_value;
assign (highz1, strong0) scl = device1_scl_value;

This is is not just nice because it’s a concise way of having the simulator figure out the interactions between devices on the bus, but it does so in a way that structurally mirrors how the circuits work. So if the simulator can do all the work of understanding the strengths of different drivers on a net, and resolving the net to the correct value, why would you need to be able to determine the strength of a net? There’s a few use cases I have actually run into, but continuing the I2C example, one use case would be if you wanted to write an assertion to check the bus is never driven high with a strong driver, since according to the specification, this should never happen.

Three ways of detecting drive strength

As part of this investigation I came up with two new ways for SystemVerilog code to detect or observe the strength of a net’s driver, in addition to the initial format string based solution from Stack Overflow that kicked all this off. For all three of these approaches I will be using them to implement a generic strength detection module, that accepts the target net to detect the strength of as an inout port, and outputs a value from 0-4 indicating the drive strength of the net.

The idea being if you wanted to write an assertion to check that an I2C bus was never driven high with a strong driver, you could use the module to get the drive strength, and then put a simple assertion on that value, as shown below:

1
2
3
wire scl_strength;
strength_detect(.sig(scl), .strength(scl_strength));
assert property(@(scl) not(scl == 1 && scl_strength != 2));

The String Formatting Approach: Google me up some code

As already mentioned, if you start looking into how to detect the drive strength of a net in SystemVerilog, all the recommendations I could find on the first page of Google results suggested to use the $sformatf() function with the "%v" format string, which creates a string representation of the net value, including the drive strength. For example a net being driven to a value of 1 with a strong driver, when passed to the "%v" format string results in the string “St1”.

Generalizing this approach into a module might look something like the following code. Every time the inout signal sig changes, we format the value of sig to a string, and then do a bunch of string comparisons to figure out the drive strength of the net.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
module str_strength_detect (
  inout sig,
  output logic [2:0] strength
);
  always @(sig) begin
    string stren_str;
    stren_str = $sformatf("%v", sig);
    case (stren_str)
      "HiZ": strength = 0;
      "We0", "We1", "WeZ": strength = 1;
      "Pu0", "Pu1", "PuZ": strength = 2;
      "St0", "St1", "StZ": strength = 3;
      "Su0", "Su1", "SuZ": strength = 4;
    endcase
  end
endmodule
str_strength_detect.sv

Now, this does work, but I had performance concerns. SystemVerilog is good at some things, but string manipulation is not one of it’s strong suits, and I have seen simulations crawl to a halt because of particularly large or complex string formatting and manipulation tasks. We will discuss the performance of this later, but suffice it to say, when I saw this as the recommended approach to detect the strength of a net’s driver, I couldn’t help but think: There has to be a better way!

“Sense Net” Approach: Using the simulator against itself

After re-reading most of the LRM a few times, I eventually convinced myself that within the confines of the SystemVerilog language, there is no syntax or API that returns an integer value with a net’s drive strength. However, while we can’t directly query the drive strength, we can observe the effect that the strength of a driver has on net value resolution! The basic idea is to connect drivers with various strengths and opposite value to the net which we are measuring the strength of, and observe if the net value changes. The simulator’s drive strength resolution will tell us if strength of the net is stronger, equal, or weaker than the applied driver based on if the value changes, goes to 'x, or remains the same.

The trick with this, of course, is we need to do this without actually ever changing the value of the net, because that would change the result of the simulation. It turns out we can get around this by using the cmos gate primitive, which is a unidirectional model element which (when enabled) will drive it’s output net with the same strength and logical value as it’s input net. This allows us to essentially create a “duplicate” net with the same driver values (logical and strength) as that of our target net, but which we can add additional drivers to without effect the real target net.

After some optimization, I ended up up with with the following module with the same ports as the str_strength_detect module above, but doesn’t use any strings:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module sense_net_strength_detect (
  inout sig,
  output [2:0] strength
);
  wire strong_sense;
  wire pull_sense;
  wire weak_sense;
  
  assign (strong1, strong0) strong_sense = (sig === 1'bx) ? 0 : ~sig;
  assign (pull1  , pull0  ) pull_sense   = (sig === 1'bx) ? 0 : ~sig;
  assign (weak1  , weak0  ) weak_sense   = (sig === 1'bx) ? 0 : ~sig;
  
  cmos strong_gate(strong_sense, sig, 1, 0);
  cmos pull_gate  (pull_sense  , sig, 1, 0);
  cmos weak_gate  (weak_sense  , sig, 1, 0);

  assign strength = (
    (sig === 1'bz) ? 0 :
    (sig === 1'bx) ? (
      (pull_sense   !== 1'bx) ? 1 :
      (strong_sense !== 1'bx) ? 2 : 3
    ) : (
      (weak_sense   === 1'bx) ? 1 :
      (pull_sense   === 1'bx) ? 2 : 3
    )
  );
endmodule
sense_net_strength_detect.sv

Now, while this solution is almost perfect, it has one limitation: It cannot observe the difference between supply and strong strength values. The reason for this is that the cmos primitive has one special case where the strength value is not passed through, if the input drive strength is supply the output drive strength is only strong. There’s a lot of cases where this limitation doesn’t matter (including the problem that got this whole thing started), and in those cases I actually really liked this solution because it’s simple and pure SystemVerilog. However, I wanted to know if there was a way around this limitation…

DPI-C API Method: Going even deeper

Now, as already mentioned, I believe SystemVerilog has no way to directly query the strength of a driver, however, what it does have is a well defined C interface API specification. The “Direct Programming Interface” chapters of the SystemVerilog specification defines a C API that in theory allows C code to directly interact with any SystemVerilog code running in any (complaint) simulator. One thing this API provides is a method to query the value of a net, including strength value information, using the vpi_get_value() function. Since you cannot call the DPI C directly from SystemVerilog (even though you can call other linked C functions), this approach was initially less interesting to me because I would need to add yet another language to my code stack, but I was curious how this solution would look.

While the SystemVerilog specification may define the DPI, it’s far from clear how the API is intended to be used. After more experimentation than I would like to admit, I ended up with the following solution: Define two C helper methods that my SystemVerilog code can call, one to get a chandle (pointer) to the net of interest, and one to return an integer representation of the net’s drive strength from the DPI call:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
vpiHandle get_handle(char *path) {
  return vpi_handle_by_name(path, NULL);
}

int unsigned get_strength(vpiHandle mod) {
  s_vpi_value v = {vpiStrengthVal};
  vpi_get_value(mod, &v);
  switch (v.value.strength->s0 > v.value.strength->s1 ? v.value.strength->s0 : v.value.strength->s1) {
    case vpiSupplyDrive : return 4;
    case vpiStrongDrive : return 3;
    case vpiPullDrive   : return 2;
    case vpiWeakDrive   : return 1;
    case vpiHiZ         : return 0;
    default             : return 0;
  }
}
dpi_strength_detect.c
Note that the vpi_get_value() function actually returns two strength values for a net: The strongest driver on the net driving a 1 value, and the strongest value on the net driving a 0 value. To get the actual drive strength that the simulator will resolve the net to, we need to return the larger strength of s1 and s0.

Using these two helper C methods, we can make a simple module that calls the get_strength() C function every time the net value changes as shown below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
module dpi_strength_detect (
  inout sig,
  output logic [2:0] strength
);
  import "DPI-C" get_handle = function chandle get_handle(string path);
  import "DPI-C" get_strength = function int unsigned get_strength(chandle handle);
  chandle h;
  initial begin
    h = get_handle($sformatf("%m.sig"));
  end
  always @(sig) begin
    strength = get_strength(h);
  end
endmodule
dpi_strength_detect.sv

This solution works as intended, and can even differentiate between supply and strong drivers, but does have the complexity downside of needing to deal with and compile in C code. Most simulators provide relatively simple ways of compiling in DPI-C code, but it’s not as clean of a solution as I would have liked. If I could find a way of directly calling the DPI-C API functions from SystemVerilog code, this solution would have been more appealing.

Performance

I started digging into this issue because I had a hunch that the string formatting approach would have too much overhead, so does this really matter? Is there really any benefit to avoiding strings?

I built a very simple test bench which would cycle through all possible drive strengths with both 0 and 1 logical values 5 million times. Without a strength detection module in the model, only the multi-strength driver use to generate the signal, 5 million iterations took 14 seconds to simulate with the Xcellium Cadence simulator. The table below shows the simulation time when one strength detection module is added to the simulation for each approach I have discussed.

No Strength Detection Format String Method Sense Net Method DPI-C Method
Simulation Duration 14 Seconds 44 Seconds 25.6 Seconds 32 Seconds
Simulation Throughput 8.571 ms/s 2.737 ms/s 4.675 ms/s 3.75 ms/s
Relative Slowdown - 3.14x 1.83x 2.28x

The data does indeed prove out my initial expectation that string manipulation is slow. However, I was quite surprised to see that the DPI-C method was slower than my initial SystemVerilog only approach. I would have thought that the additional nets and value resolution work the simulator would have to do for the sense net approach would have out-weighed any overhead in calling out to a C library.

Conclusion

Overall this was an interesting deep dive into a little explored part of the SystemVerilog language. If you can get away with not needing to differentiate strong from supply strength values, then I would definitely recommend the “sense net” detection approach as it’s not only the fastest, but also pure SystemVerilog. If you really need to detect supply strengths and the addition of two DPI-C functions isn’t problematic, then the DPI-C approach is a good choice. Either way, is better than the commonly recommended approach of using formatting the net value as a string.