SystemC ships a complete set of hardware-aware types on top of standard C++. They give you exact bit widths, 4-value logic, fixed-point arithmetic, and rich bit-manipulation operators that map directly to hardware concepts. Knowing which type to reach for — and when to stay with plain int — is one of the most practical skills in SystemC modelling.

This article covers every type in the hierarchy, how to use bit operations, how the types interact with ports and signals, and ends with a performance ranking so you can make informed trade-offs.

Native C++ vs SystemC Types — When to Switch

Native C++ types (int, bool, double) compile to single CPU instructions and are the fastest option in simulation. Use them unless you specifically need one of the capabilities only SystemC types provide.

NeedUse
General counters, flags, loop variablesint, bool — fastest
Exact hardware bit width (e.g., 12-bit ADC)sc_uint<12>
Tri-state or uninitialized bus linessc_lv<W> or sc_logic
Bit slicing, range extraction, concatenationsc_uint, sc_bv
Widths > 64 bits (e.g., 128-bit hash)sc_biguint<W>
DSP coefficients, fixed-point arithmeticsc_fixed<WL,IWL>
Default rule Start with native C++ types. Switch to SystemC types only when your model requires precise bit widths, 4-value logic, or fixed-point behaviour. Mixing both in the same design is fully supported.

sc_int and sc_uint — Hardware-Width Integers

sc_int<W> and sc_uint<W> are signed and unsigned integers with an exact width W (1–64 bits). They support all arithmetic operators plus hardware-specific operations: bit-select, range extraction, and concatenation.

#include <systemc.h>

sc_int<8>   reg_a  = -5;       /* 8-bit signed:  range −128 to +127 */
sc_uint<16> addr   = 0xBEEF;   /* 16-bit unsigned: range 0 to 65535 */
sc_uint<13> days(4000);        /* 13 bits, initialized to 4000       */

/* Arithmetic — same as C++ integers */
sc_uint<8> x = 200, y = 100;
sc_uint<8> sum = x + y;        /* wraps at 8 bits: 300 % 256 = 44   */

/* Comparison */
if (addr > 0x8000) { /* high address space */ }

/* Converting to/from native types */
int native = reg_a.to_int();
sc_uint<8> back = (sc_uint<8>)native;

Overflow behaviour

Arithmetic on sc_int/sc_uint wraps silently at the declared width — just like hardware registers. A 4-bit counter rolls over from 15 to 0. This is intentional and matches the hardware model.

sc_uint<4> nibble = 14;
nibble += 3;           /* 14 + 3 = 17, wraps to 1 (17 % 16) */
cout << nibble;        /* prints: 1 */

Bit Operations — range(), Bit-Select, Concatenation

This is where SystemC integer types earn their place over plain int. Three operators let you manipulate individual bits and fields directly — no manual masking or shifting needed.

Bit-select — x[i]

sc_uint<8> byte = 0xA5;   /* binary: 1010 0101 */

bool bit7 = byte[7];       /* 1 — MSB */
bool bit0 = byte[0];       /* 1 — LSB */

byte[3] = 1;               /* set bit 3 → 0xAD (1010 1101) */
byte[7] = 0;               /* clear MSB → 0x2D (0010 1101) */

Range extraction — x.range(hi, lo)

sc_uint<8> byte = 0xA5;          /* 1010 0101 */

sc_uint<4> hi_nibble = byte.range(7, 4);  /* → 0xA  (1010) */
sc_uint<4> lo_nibble = byte.range(3, 0);  /* → 0x5  (0101) */

/* range() returns a proxy — can also be assigned to */
byte.range(3, 0) = 0xF;           /* set lower nibble → 0xAF */

Concatenation — (a, b, c)

sc_uint<4> hi = 0xA, lo = 0x5;
sc_uint<8> combined = (hi, lo);   /* → 0xA5 — hi in upper bits */

/* Concatenation on the left side: field assignment */
sc_uint<8> reg = 0;
(reg.range(7,4), reg.range(3,0)) = (0xB, 0xE);  /* reg = 0xBE */
Note range() and bit-select return proxy objects, not plain integers. Assigning to them writes back into the original variable. This is how byte.range(3,0) = 0xF modifies byte in-place.

sc_bv — Two-Value Bit Vector

sc_bv<W> is a W-bit vector that only supports 0 and 1 — no X or Z. It is faster than sc_lv because it skips the 4-value logic overhead. Use it for pure digital signals where you never need tri-state or unknown values.

sc_bv<8>  mask    = "10110011";   /* string initialisation */
sc_bv<8>  data    = 0xA5;

/* Bitwise operations */
sc_bv<8>  result  = mask & data;   /* AND */
sc_bv<8>  ored    = mask | data;   /* OR  */
sc_bv<8>  flipped = ~mask;         /* NOT */

/* Reduction operators */
sc_logic any_set  = mask.or_reduce();   /* SC_LOGIC_1 if any bit is 1 */
sc_logic all_set  = mask.and_reduce();  /* SC_LOGIC_1 if all bits are 1 */
sc_logic parity   = mask.xor_reduce();  /* XOR of all bits */

/* Range and bit-select — same syntax as sc_uint */
sc_bv<4> upper = mask.range(7, 4);    /* → "1011" */

sc_logic and sc_lv — Four-Value Logic

Hardware buses are not always driven to a clean 0 or 1. Undriven lines float (Z), contention produces unknown states (X), and uninitialised memory reads return X in simulation. sc_logic (single bit) and sc_lv<W> (W-bit vector) model all four values.

ValueConstantMeaning
'0'SC_LOGIC_0Logic low — actively driven to 0
'1'SC_LOGIC_1Logic high — actively driven to 1
'X'SC_LOGIC_XUnknown — contention, uninitialised, or don't-care
'Z'SC_LOGIC_ZHigh-impedance — bus not driven (tri-state)
#include <systemc.h>

/* Single-bit 4-value logic */
sc_logic pin = SC_LOGIC_Z;       /* undriven — floating */
pin = SC_LOGIC_1;                /* driven high */

bool is_driven = pin.is_01();    /* true if 0 or 1, false for X/Z */

/* Multi-bit 4-value bus */
sc_lv<8> bus = "01XZ0101";      /* each char is one bit */

sc_lv<4> upper = bus.range(7,4);   /* "01XZ" */
sc_logic  b3    = bus[3];           /* SC_LOGIC_0 */

/* 4-value AND: 0&X=0, 1&X=X, Z&1=X */
sc_lv<4> a = "01XZ", b = "1101";
sc_lv<4> result = a & b;            /* "01X0" */
Performance note sc_lv is significantly slower than sc_bv — each bit needs 2 storage bits plus extra logic for X/Z propagation. Use sc_bv for internal signals you control; use sc_lv only for bus interfaces that genuinely need tri-state or uninitialized modelling.

sc_bigint and sc_biguint — Beyond 64 Bits

sc_bigint<W> and sc_biguint<W> extend the integer types to arbitrary widths. They support all the same operators as their 64-bit counterparts but are considerably slower due to software multi-word arithmetic.

sc_biguint<128> hash_val;         /* 128-bit hash register */
sc_biguint<72>  checksum;         /* 72-bit CRC accumulator */

/* Mixed arithmetic requires explicit cast to avoid type issues */
sc_bigint<70> g = 100, h = 200, k = 300;
sc_bigint<70> total = sc_bigint<70>(g) + h + k;

/* Range and bit-select work identically */
sc_biguint<128> x = hash_val;
sc_uint<32> lo_word = x.range(31, 0).to_uint();   /* extract low 32 bits */
Use sparingly Only use sc_bigint when W genuinely exceeds 64. For anything ≤ 64 bits, sc_uint<W> is much faster. Mixing sc_bigint with sc_uint in arithmetic without casting can produce unexpected widths — always cast the first operand explicitly.

sc_fixed — Fixed-Point Arithmetic

sc_fixed<WL, IWL> models fixed-point numbers used in DSP, audio, and signal processing pipelines. WL is the total word length in bits; IWL is the number of integer bits (including sign). The remaining WL - IWL bits are fractional.

/* Enable fixed-point — MUST appear before #include <systemc> */
#define SC_INCLUDE_FX
#include <systemc.h>

/* sc_fixed<8, 3>: 8 total bits, 3 integer bits (signed)
   Layout: [sign | I I | F F F F F]
   Range:  -4.00 to +3.75
   Step:   0.03125 (1/32 = 2^-5) */
sc_fixed<8, 3>  coeff = 1.5;
sc_fixed<8, 3>  gain  = -0.75;

sc_fixed<8, 3>  result = coeff * gain;   /* -1.125 → rounds to nearest */

/* Unsigned fixed-point */
sc_ufixed<8, 4> level = 3.25;   /* 0 to 15.9375 in steps of 0.0625 */

/* Overflow and quantization modes (optional template params) */
sc_fixed<8, 3, SC_RND, SC_SAT> safe_coeff = 100.0;  /* saturates at +3.75 */

/* Printing */
cout << coeff.to_double() << endl;   /* 1.5 */

When to use sc_fixed

Workflow tip Always prototype with double first — it runs 10–100× faster than sc_fixed. Switch to sc_fixed only for accuracy validation or when bit-exact overflow/rounding behaviour matters.

Type Conversion and to_string()

All SystemC integer and vector types support explicit conversion methods and a configurable to_string():

sc_int<5> val = -13;

/* to_string with base */
cout << val.to_string(SC_BIN)  << endl;   /* "0b10011" */
cout << val.to_string(SC_HEX)  << endl;   /* "0xf3"    */
cout << val.to_string(SC_DEC)  << endl;   /* "-13"     */
cout << val.to_string(SC_OCT)  << endl;   /* "063"     */

/* to_int / to_uint / to_int64 / to_double */
int  n = val.to_int();          /* -13 */
long l = val.to_int64();

/* sc_lv / sc_bv — convert to string directly */
sc_lv<4> bus = "01XZ";
cout << bus.to_string() << endl;   /* "01XZ" */

Using Types with Ports and Signals

SystemC types are the template parameter for ports and signals. The type must match exactly between a port and its bound signal — the compiler enforces this at build time.

/* Signal declarations in sc_main or parent module */
sc_signal<sc_uint<8>>  data_sig;
sc_signal<sc_lv<4>>    bus_sig;
sc_signal<bool>         clk_sig;    /* native bool — fine for clock */

/* Port declarations inside SC_MODULE */
SC_MODULE(Adder) {
  sc_in<sc_uint<8>>   a, b;
  sc_out<sc_uint<9>>  sum;   /* 9 bits to hold carry */

  SC_CTOR(Adder) {
    SC_METHOD(add);
    sensitive << a << b;
  }

  void add() {
    sum.write(a.read() + b.read());
  }
};

/* Binding */
sc_signal<sc_uint<8>> sig_a, sig_b;
sc_signal<sc_uint<9>> sig_sum;

Adder adder("adder");
adder.a(sig_a);
adder.b(sig_b);
adder.sum(sig_sum);

Performance Ranking

Simulation speed matters — a complex SoC model may run millions of events per second. Choose the slowest type that meets your modelling accuracy requirement, not the most precise one by default.

TypeSpeedUse when
int, bool, double⚡⚡⚡⚡⚡ FastestInternal state, loop variables, no bit-width constraint
sc_int<W>, sc_uint<W>⚡⚡⚡⚡ FastExact hardware widths ≤ 64, need bit ops
sc_bv<W>⚡⚡⚡ MediumPure 2-value bus, need reduction ops
sc_logic, sc_lv<W>⚡⚡ SlowerTri-state buses, X/Z propagation, uninitialised modelling
sc_bigint<W>, sc_biguint<W>⚡ SlowWidths > 64 bits only
sc_fixed<WL,IWL>🐢 SlowestDSP accuracy validation, fixed-point overflow/rounding

Quick Reference

TypeWidthValuesKey opsHeader requirement
sc_int<W>1–64Signed int[], range(), to_int()#include <systemc>
sc_uint<W>1–64Unsigned int[], range(), to_uint()#include <systemc>
sc_bv<W>Any0, 1[], range(), &|~, _reduce()#include <systemc>
sc_logic10, 1, X, Zis_01(), to_bool()#include <systemc>
sc_lv<W>Any0, 1, X, Z[], range(), &|~#include <systemc>
sc_bigint<W>>64Signed intSame as sc_int#include <systemc>
sc_biguint<W>>64Unsigned intSame as sc_uint#include <systemc>
sc_fixed<WL,IWL>AnyFixed-pointArithmetic, to_double()#define SC_INCLUDE_FX first
sc_ufixed<WL,IWL>AnyUnsigned fixedArithmetic, to_double()#define SC_INCLUDE_FX first

📬 Get new articles in your inbox

Deep dives on SystemC, C++, and embedded systems — no spam, unsubscribe any time.

No spam, unsubscribe any time. Privacy Policy

Aditya Gaurav

Aditya Gaurav

Embedded systems engineer specializing in SystemC, ARM architecture, and C/C++ internals. Writing deep technical dives for VLSI and embedded engineers.