Skip to content
VLSI Mentor

C Part II · Module 20 · DPI-C Type Mapping

2-State and 4-State Values

Every other disagreement in this module is about how to read bits that both languages possess. This one is different: a SystemVerilog logic value can be in a state C has no bits for, and no choice of width or signedness recovers it. The crossing is lossless if you read both words and silently lossy if you read one — measured here, the pattern ZX01 becoming 0101, with Z now indistinguishable from a real zero and X from a real one. The chapter's deliverable is not that information is lost but the three things a model can do about it, and why only one of them is a decision.

Expert22 min readCPart IIVerificationDPI-C

Part II · Module 20 · Chapter 20.5 · 2-State and 4-State Values

1. A Bit That Is Not a Bit

A C bit holds one of two values. A SystemVerilog logic bit holds one of four: 0, 1, X and Z.

They are not decorations. Z means high impedance — nothing is driving this net. X means unknown — the simulator cannot determine a value, because nothing has driven it yet, or because two drivers disagree, or because an unknown propagated into it from somewhere else.

So a logic bit carries two bits of information and a C bit carries one. Measured, stating the obvious precisely because the consequence is not obvious:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  a C uint32_t has 32 bits of value and NO second plane

There is nowhere in a uint32_t to record "this bit is unknown". That is not a limitation of the interface; it is what the type is.

2. How It Crosses

From the LRM: a four-state value crosses as two words rather than one — conventionally named aval and bval — and the pair encodes the four states.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   bval  aval   state
    0     0      0
    0     1      1
    1     0      Z
    1     1      X

The crossing itself is lossless. Both words are present on the C side, and everything SystemVerilog knew is available.

What is lossy is reading only one of them, and the encoding is what makes that silent. Look at the table again by column: with bval ignored, aval = 0 means either 0 or Z, and aval = 1 means either 1 or X.

Measured, modelling the encoding in C:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
struct logic32 { uint32_t aval, bval; };
struct logic32 v = { 0x5u, 0xCu };     /* bit3 Z, bit2 X, bit1 0, bit0 1 */
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  the 4-state value:          ZX01   aval=0x5 bval=0xC
  collapsed, bval discarded:  0101   aval=0x5 bval=0x0

ZX01 became 0101. The Z is now a 0 and the X is now a 1, and nothing distinguishes them from data that was always a 0 and a 1.

The four-state encoding and what each collapse strategy does to it. With bval zero and aval zero the state is 0; bval zero and aval one is 1; bval one and aval zero is Z; bval one and aval one is X. So the value plane alone cannot distinguish a real one from an X, nor a real zero from a Z. The bottom row compares three things a model can do when a four-state value arrives. Discarding bval is the default if you simply read the value word: measured, the pattern ZX01 becomes 0101, so Z silently becomes 0 and X silently becomes 1. Forcing X and Z to zero is a deliberate choice: the same value becomes 0001, the information is still lost but predictably. Checking bval first and refusing is the only option that does not lose anything.bvalavalSystemVerilogstateCan C tell itapart?000yes, from aval011yes, from aval10Zonly if you readbvalaval alone shows 011Xonly if you readbvalaval alone shows 1What a model candoignore bvalZX01 becomes 0101force X and Z to0ZX01 becomes 0001check bval andrefusenothing is lost12
Figure 1 — the four-state encoding and the three things a model can do with it. The top four rows are the encoding, and the right-hand column is the reason the failure is silent: with bval ignored, a Z is indistinguishable from a genuine zero and an X from a genuine one, so a model that reads only the value word gets plausible data and no indication that anything was unknown. The bottom row is the chapter's deliverable. Ignoring bval is what happens if you do nothing and is the only one of the three that is never a decision; forcing X and Z to a defined value is lossy but repeatable; checking bval first is the only option that loses nothing, and section 6 argues it is the right default for a reference model.

3. Two Kinds of Argument

From the LRM: the distinction is in the declaration, and it decides whether the question arises at all.

A 2-state type — bit, and the packed vectors built from it — crosses as a single value. There is no bval, because a bit cannot be X or Z. SystemVerilog converts on the way in: an X assigned to a bit becomes 0.

A 4-state type — logic, reg, and the vectors built from them — crosses as the pair.

So declaring an argument bit rather than logic moves the decision from your C into the SystemVerilog side, where the conversion happens before the call. That is a legitimate strategy and §5 counts it, with the caveat that it hides the same information in a different place.

4. Why This Is Not Only a Reset Problem

The common dismissal is that X happens at time zero and settles, so a model will never see one. It is worth measuring that belief against where unknowns actually come from.

X and Z arise in normal operation from:

  • an undriven net — a bus with no active driver, which is Z by design in a tristate protocol
  • a disabled output — the same thing, deliberately
  • contention — two drivers disagreeing, which is X and is a real bug the model could help find
  • uninitialised state — a RAM that was never written, a register without a reset
  • propagation — any arithmetic on an X produces X, so one unknown spreads

None of those is confined to reset, and two of them — contention and reading uninitialised memory — are exactly the bugs a reference model exists to catch.

Which produces the sharpest form of the problem: a model that silently reads X as 1 will agree with a design that is producing garbage. Chapter 18.2 measured what a model's agreement is worth when the model cannot disagree — 0 mismatches in 4096 inputs — and this is the same failure arriving through the type system rather than through the model's provenance.

5. The Three Strategies

One — check and refuse. Test bval before using the value, and report rather than compute.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
static int has_unknown(const struct logic32 *v) { return v->bval != 0u; }

Measured: for the value above, has_unknown(v) is yes.

Nothing is lost. The model reports "I was given unknowns", which is a true statement and usually a finding. For a reference model this is the right default, for the reason §4 gave: a model that computes on X is producing fiction, and fiction that agrees with the design is worse than a complaint.

Two — force to a defined value. Decide what X and Z mean to your model and apply it consistently.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
uint32_t v2 = v.aval & ~v.bval;     /* X and Z both become 0 */

Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  collapsed, X/Z forced to 0: 0001

Still lossy, and predictable — the same input always produces the same output, and the rule is written in the code. Appropriate when the design is permitted to present unknowns in normal operation and the model must keep going anyway. Say which way you forced them and why.

Three — ignore bval. Read only the value word.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  collapsed, bval discarded:  0101

This is what happens if you do nothing, and it is the only one of the three that is never a decision. It is also the one that produces §4's failure: plausible data, no indication, and a model that agrees with a broken design.

6. Choosing

Three questions, in order.

Can the design legitimately present an unknown here? If no — a data bus during a valid transfer, a decoded field in a well-formed packet — then an X is a finding, and strategy one is correct. Report it.

If yes, does the model's answer depend on the unknown bits? A checksum over a payload containing Z has no meaningful answer; a length field that is fully known can be used even if padding bytes are not. Strategy one for the first, strategy two with a stated rule for the second.

Is the argument 4-state at all? If the SystemVerilog side can reasonably declare it bit, the conversion happens there and your C is simpler — §3's option. The information is still discarded; it is discarded in a place where a SystemVerilog reader can see it, which is an improvement over discarding it invisibly in C.

The one combination to avoid is the one that happens by default: a logic argument, a C model that reads aval only, and no comment saying so.

7. The Misconception: X Only Happens at Reset

"Unknowns are a start-up artefact. Once the design is initialised there are no X values, so the model will never see one."

§4 listed five routes and only one of them is reset. The two that matter most are the ones a model exists to catch: contention, where two drivers disagree, and reading state that was never initialised — which is Chapter 9.12's bug class in hardware form.

And the belief is self-confirming in a specific way. A model that ignores bval cannot report an unknown, so a project using such a model accumulates evidence that unknowns never reach the model — because the model never says so. The absence of reports is taken as absence of unknowns, and the measurement in §2 shows why that inference fails: X arrived and was read as 1.

The corrected position:

Unknowns are ordinary values in a simulation, not an error state. Your model will receive them, and whether you find out is decided by whether you look at bval.

And the one-line test of any existing model: does it read bval anywhere? If not, it has been making strategy three's non-decision on every call.

8. What You Should Be Able to Say Now

  1. A logic bit carries four states and two bits of information; a C bit carries one. Measured: a uint32_t has 32 bits of value and no second plane.
  2. Z means undriven, X means unknown — neither is an error state in a simulation.
  3. The crossing is lossless: four-state values cross as two words, aval and bval.
  4. Reading one word is silently lossy. The encoding makes Z look like 0 and X look like 1. Measured: ZX01 → 0101.
  5. A bit argument has no bval — the conversion happens on the SystemVerilog side instead.
  6. Unknowns are not only a reset artefact: undriven nets, disabled outputs, contention, uninitialised state, and propagation.
  7. The sharpest consequence: a model that reads X as 1 agrees with a design producing garbage — 18.2's failure, arriving through the type system.
  8. Three strategies: check and refuse (lossless) · force to a defined value (lossy, predictable) · ignore bval (the default, and never a decision).
  9. Choosing: can an unknown legitimately appear here? · does the answer depend on the unknown bits? · could the argument be bit instead?
  10. No tool catches this. C's indeterminate values (9.12) are caught by sanitizers; an X read as a 1 is a valid uint32_t read correctly. The defence is a line of code.

9. Exercises

  1. Implement the aval/bval encoding and decode a value containing all four states. Confirm ZX01 collapses to 0101 when bval is discarded.
  2. Write has_unknown and a force_zero collapse. Apply both to the same value and show the two results.
  3. For a block you know, list three places where a Z or an X could legitimately reach a reference model during normal operation.
  4. §6 asks whether the argument could be bit. For a DPI import you have seen with a logic argument, say whether bit would have been acceptable and what would be lost.
  5. Write the comment you would put beside a model that uses strategy two, stating which way unknowns are forced and why.
  6. §7 says the belief is self-confirming. Describe the evidence a project would see over a year of using a strategy-three model, and what it would conclude.
  7. Take a reference model you have access to. Does it read bval anywhere? What does the answer tell you about every result it has ever produced?

10. Summary

  • Four states, two bits of information per bit — and a C word has no second plane.
  • The crossing is lossless (two words); reading one word is silently lossy — measured ZX01 → 0101, with Z now a 0 and X a 1.
  • A bit argument moves the decision to the SystemVerilog side; the information is still discarded, more visibly.
  • Unknowns are normal: undriven nets · disabled outputs · contention · uninitialised state · propagation.
  • A model that reads X as 1 agrees with garbage — 18.2's failure through the type system.
  • Three strategies: check and refuse (lossless, the right default for a model) · force predictably (state the rule) · ignore bval — the default and never a decision.
  • No tool catches it — unlike C's indeterminate values (9.12), the aval word is a valid object read correctly.
  • The one-line audit: does the model read bval anywhere?

Four-state values arrive as two words, and both of them are words — which is fine for a value that fits in one. A SystemVerilog vector can be any width at all, and once it is wider than the scalar types the question becomes how an arbitrary number of bits is laid out across an array of them.

Standards & specifications

Governing standard
IEEE Std 1800 (SystemVerilog)(opens IEEE in a new tab)

Defines SystemVerilog language semantics — syntax, data types, scheduling and the behaviour a conforming simulator must produce. It does not define tool-specific synthesis support or vendor methodology.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C Programming curriculum.