TSystemC Programmer Guide

Version 1.0.0 · Windows x64 · © 2026 Muhammad Anisur Rahman

TSystemC is a SystemC simulator. It implements the SystemC class library of IEEE Std 1666-2023 — kernel, channels, data types, fixed-point types, tracing, TLM-2.0 and TLM-1 — and adds a power modeling library, sc_power. Models written for the standard API compile against TSystemC unchanged.

Besides the C++ library, TSystemC ships:

This guide is organised as follows:

  1. Installation
  2. Your first model
  3. Building models
  4. SystemC essentials
  5. Transaction-level modeling (TLM-2.0)
  6. Power modeling
  7. The tsystemc command line
  8. Embedding: the C host API
  9. Co-simulation and AI agents
  10. Examples
  11. Conformance notes
  12. Troubleshooting
  13. Power API reference

1. Installation

What you need

The tsystemc command line compiles models with g++, so it needs g++ on the PATH, or named in TSYSTEMC_CXX.

Unpacking

Unpack the archive anywhere. For example:

C:\tools\tsystemc-1.0.0\
    bin\        tsystemc.exe, tsystemc.dll
    include\    systemc.h, systemc, tlm.h, tlm, sc_power.h, sc_power_tlm.h, sysc_api.h, ...
    lib\        libsysc.a, sce_model_entry.o, libtsystemc_api.a (g++)
                sysc_msvc.lib, sce_model_entry_msvc.obj (MSVC)
    examples\   runnable examples with their expected output
    docs\       this guide (Markdown and HTML)

Add bin to your PATH. tsystemc finds the headers and libraries from its own location, as the parent of bin. Set TSYSTEMC_HOME only if you move tsystemc.dll away from the rest of the package.

Check the installation:

> tsystemc version
TSystemC 1.0.0 (IEEE 1666-2023 SystemC, TLM-2.0, power modeling; model ABI v2)

Environment variables

VariableMeaning
TSYSTEMC_HOMEInstallation root (the directory holding include and lib). Default: the parent of the directory holding tsystemc.dll.
TSYSTEMC_CXXThe compiler tsystemc runs (default g++).
SYSC_CXXFLAGSExtra compiler flags for models that tsystemc compiles.
SCE_STACK_SIZEDefault stack size of thread processes, in bytes (default 256 KiB).

2. Your first model

Save this as counter.cpp:

#include <systemc.h>

SC_MODULE(Counter) {
    sc_in<bool> clk, reset;
    sc_out<sc_uint<8>> count;

    SC_CTOR(Counter) {
        SC_METHOD(tick);
        sensitive << clk.pos();
    }
    void tick() {
        count.write(reset.read() ? 0 : count.read() + 1);
    }
};

int sc_main(int, char *[]) {
    sc_clock clk("clk", 10, SC_NS);
    sc_signal<bool> reset("reset");
    sc_signal<sc_uint<8>> count("count");
    Counter c("c");
    c.clk(clk);
    c.reset(reset);
    c.count(count);

    sc_trace_file *tf = sc_create_vcd_trace_file("counter");
    sc_trace(tf, clk, "clk");
    sc_trace(tf, count, "count");

    reset = true;
    sc_start(25, SC_NS);
    reset = false;
    sc_start(100, SC_NS);
    std::cout << sc_time_stamp() << ": count = " << count.read() << std::endl;
    sc_close_vcd_trace_file(tf);
    return 0;
}

Run it:

> tsystemc run counter.cpp
125 ns: count = 10

tsystemc run compiles the model the first time and caches the result in %TEMP%\tsystemc_cache. Later runs reuse the cached model until the source file or the library changes. counter.vcd opens in any waveform viewer, such as GTKWave.

To build a normal executable instead:

> tsystemc build counter.cpp -o counter.exe
> counter.exe

or with g++ directly (see the next chapter).


3. Building models

With g++

g++ -std=c++17 -O2 -I%TSYSTEMC_HOME%\include model.cpp %TSYSTEMC_HOME%\lib\libsysc.a -static -o model.exe

With Microsoft Visual C++

From a "x64 Native Tools" command prompt:

cl /std:c++17 /EHsc /MD /I%TSYSTEMC_HOME%\include model.cpp %TSYSTEMC_HOME%\lib\sysc_msvc.lib /link /SUBSYSTEM:CONSOLE

main() lives in the library, so the linker must be told the subsystem explicitly.

Model DLLs

A model DLL is a model compiled together with the engine and a small entry shim (sce_model_entry.o). A host loads it to drive the model; see chapters 7 and 8. tsystemc builds model DLLs for you:

tsystemc compile model.cpp -o model.dll

You only need a model DLL when another program will load the model. To run it yourself, use tsystemc run or an executable.

Headers

IncludeProvides
<systemc.h>SystemC, with the standard names brought into the global namespace (the classic style)
<systemc>SystemC in sc_core / sc_dt only
<tlm.h> / <tlm>TLM-2.0 and TLM-1 (namespace tlm)
<tlm_utils/...>Convenience sockets, payload event queues, quantum keeper, instance-specific extensions
<sc_power.h>The power modeling library (namespace sc_power)
<sc_power_tlm.h>Power modeling plus its TLM-2.0 helpers
<sysc_api.h>The C host API of tsystemc.dll

Fixed-point types are available after #define SC_INCLUDE_FX before the include, as the standard requires.


4. SystemC essentials

This chapter is a working summary of SystemC as TSystemC implements it. IEEE Std 1666-2023 is the normative reference.

4.1 Elaboration and simulation

A SystemC program has two phases.

Calling sc_start(t) repeatedly continues the same simulation. sc_start() with no argument runs until no activity remains, or until a process calls sc_stop().

The scheduler runs in evaluate–update–delta cycles:

  1. Every runnable process runs once (evaluate).
  2. Primitive channels then commit their new values (update).
  3. Processes sensitive to the resulting changes run in the next delta cycle, at the same simulation time.

Time advances only when no delta activity is left.

4.2 Modules and processes

SC_MODULE(Filter) {
    sc_in<bool> clk;
    sc_in<int>  in;
    sc_out<int> out;
    sc_event    wake{"wake"};

    SC_CTOR(Filter) {
        SC_METHOD(compute);          // runs to completion each time it is triggered
        sensitive << in;
        SC_THREAD(control);          // has its own stack; may call wait()
        SC_CTHREAD(sample, clk.pos());   // a clocked thread
    }
    void compute() { out.write(in.read() / 2); }
    void control() {
        for (;;) {
            wait(100, SC_NS);
            wake.notify();
        }
    }
    void sample() {
        for (;;) {
            wait();                      // the next rising clock edge
            ...
        }
    }
};

4.3 Channels

ChannelUse
sc_signal<T>Hardware wire or register. Has value-change, positive-edge and negative-edge events (for bool and sc_logic). Writer policies: SC_ONE_WRITER, SC_MANY_WRITERS, SC_UNCHECKED_WRITERS.
sc_buffer<T>Like sc_signal, but every write is an event, even when the value does not change.
sc_clockPeriodic bool signal with configurable period, duty cycle, start time and first edge.
sc_fifo<T>Bounded FIFO with blocking and non-blocking access.
sc_mutex, sc_semaphoreMutual exclusion and counting semaphores.
sc_signal_resolved, sc_signal_rv<N>Multi-driver resolved logic.
sc_event_queueQueue of notifications.

Ports and exports: sc_in, sc_out, sc_inout, sc_fifo_in, sc_fifo_out, sc_port<IF, N, policy>, and sc_export<IF>. To leave a port unconnected on purpose, use sc_unbound, sc_tie or sc_stub.

4.4 Data types

4.5 Time

sc_time(10, SC_NS), or sc_time("10 ns"). Units go from SC_YS to SC_SEC. The time resolution defaults to 1 ps. Change it with sc_set_time_resolution before the first sc_time is constructed.

4.6 Reporting

SC_REPORT_INFO("my/model", "starting");
SC_REPORT_WARNING("my/model", "FIFO almost full");
SC_REPORT_ERROR("my/model", "illegal address");   // default action: throw
sc_report_handler::set_actions("my/model", SC_WARNING, SC_DO_NOTHING);

Severities, actions, verbosity levels, stop_after limits and custom handlers all work as the standard defines.

4.7 Tracing

sc_trace_file *tf = sc_create_vcd_trace_file("waves");   // writes waves.vcd
sc_trace(tf, signal, "name");
tf->set_time_unit(1, SC_NS);
...
sc_close_vcd_trace_file(tf);

You can trace any signal, port, variable, data type, fixed-point value, sc_time or sc_event. Values that are not integers are written as VCD reals.

4.8 Naming

Every object and event has a hierarchical name, top.cpu.fifo, from the names passed to the constructors. Look objects up with sc_find_object and sc_find_event.

Give events names (sc_event done{"done"};). As IEEE 1666-2023 §5.10.4 requires, two unnamed events in the same module both get the name "event", and the clash produces a warning.


5. Transaction-level modeling (TLM-2.0)

TLM models communication as function calls carrying a transaction (the generic payload), rather than as signal changes. It is the usual level for virtual platforms, architecture exploration and software development before RTL exists, and it runs orders of magnitude faster than signal-level models.

5.1 Sockets and the generic payload

An initiator (CPU, DMA) owns an initiator socket. A target (memory, peripheral) owns a target socket. Binding them connects the forward path (b_transport, nb_transport_fw, get_direct_mem_ptr, transport_dbg) and the backward path (nb_transport_bw, invalidate_direct_mem_ptr).

#include <tlm>
#include <tlm_utils/simple_initiator_socket.h>
#include <tlm_utils/simple_target_socket.h>

struct Memory : sc_module {
    tlm_utils::simple_target_socket<Memory> socket{"socket"};
    unsigned char mem[1024];
    SC_CTOR(Memory) { socket.register_b_transport(this, &Memory::b_transport); }

    void b_transport(tlm::tlm_generic_payload &t, sc_time &delay) {
        if (t.get_address() + t.get_data_length() > sizeof mem) {
            t.set_response_status(tlm::TLM_ADDRESS_ERROR_RESPONSE);
            return;
        }
        if (t.is_read()) memcpy(t.get_data_ptr(), mem + t.get_address(), t.get_data_length());
        else             memcpy(mem + t.get_address(), t.get_data_ptr(), t.get_data_length());
        delay += sc_time(10, SC_NS);              // annotate, do not wait
        t.set_response_status(tlm::TLM_OK_RESPONSE);
    }
};

The generic payload carries the following:

The initiator sets the response status to TLM_INCOMPLETE_RESPONSE before each call. The target sets the final status.

5.2 Loosely timed (LT) modeling

The LT style uses the blocking b_transport with timing annotation. The target adds its latency to delay, and the initiator decides when to synchronise.

Temporal decoupling lets an initiator run ahead of simulation time by up to a global quantum before it yields. This is what makes LT platforms fast:

tlm_utils::tlm_quantumkeeper qk;
tlm::tlm_global_quantum::instance().set(sc_time(1, SC_US));
qk.reset();
...
sc_time delay = qk.get_local_time();
socket->b_transport(t, delay);
qk.set(delay);
if (qk.need_sync()) qk.sync();

DMI (direct memory interface) lets an initiator get a raw pointer into a target's memory and bypass transport calls. Debug transport (transport_dbg) reads and writes without side effects or timing.

examples/tlm_lt shows all four: a CPU with a quantum keeper, a router, DMI and debug access.

5.3 Approximately timed (AT) modeling

The AT style uses the non-blocking nb_transport_fw/nb_transport_bw calls and the four phases of the base protocol:

initiator                      target
   BEGIN_REQ  ----------------->
              <-----------------  END_REQ       (request accepted; the request channel is free)
              <-----------------  BEGIN_RESP    (response ready)
   END_RESP   ----------------->

Each call returns:

ReturnMeaning
TLM_ACCEPTEDThe callee took the phase. The answer comes later, on the other path.
TLM_UPDATEDThe callee moved the transaction to a new phase, given in the arguments.
TLM_COMPLETEDThe transaction is finished. No further phases follow.

Rules that keep AT models correct:

examples/tlm_at follows a transaction through every one of these cases.

5.4 Interconnects

A bus or router has one target socket per initiator and one initiator socket per target, or uses the multi-passthrough sockets. These bind any number of times, and the callbacks receive the index of the binding:

tlm_utils::multi_passthrough_target_socket<Bus>    targ{"targ"};
tlm_utils::multi_passthrough_initiator_socket<Bus> init{"init"};
...
void b_transport(int port, tlm::tlm_generic_payload &t, sc_time &delay) {
    unsigned target = decode(t.get_address());
    t.set_address(t.get_address() - base[target]);
    init[target]->b_transport(t, delay);
}

An interconnect must translate addresses on the way to the target, restore them on the way back, and adjust DMI ranges. examples/tlm_bus adds arbitration with sc_mutex and publishes every transfer through a TLM-1 analysis port.

5.5 Extensions

struct cpu_id : tlm::tlm_extension<cpu_id> {
    unsigned id = 0;
    tlm_extension_base *clone() const override { return new cpu_id(*this); }
    void copy_from(const tlm_extension_base &e) override { id = static_cast<const cpu_id &>(e).id; }
};
t.set_extension(&ext);     // initiator
t.get_extension(p);        // anyone on the path; p == nullptr if absent
t.clear_extension(&ext);   // before ext goes out of scope

Ignorable extensions (an interconnect may pass them through without understanding them) are the standard way to carry side-band information. sc_power::power_extension is one (see 6.7). tlm_utils::instance_specific_extensions attach private data per module.

5.6 TLM-1

tlm_fifo<T>, put/get/peek and transport interfaces, and tlm_analysis_port with tlm_analysis_fifo are all available. Analysis ports are the usual way to feed monitors, scoreboards and coverage collectors.


6. Power modeling

sc_power adds power and energy to any SystemC model, at signal level or transaction level. It is an extension, not part of IEEE 1666. It answers the questions architects ask before silicon exists:

#include <sc_power.h>        // or <sc_power_tlm.h> for the TLM helpers
using namespace sc_power;

6.1 Concepts

Power domain (power_domain) — a supply rail, which has:

Power model (power_model) — the power behaviour of one component:

DVFS clock (dvfs_clock) — an sc_signal<bool> clock that follows its domain's clock period and stops while the domain is off. Use it where an sc_clock would be used.

Power monitor (power_monitor) — the registry of all models and domains. It prints reports, samples power into CSV, and traces it into VCD.

6.2 Units and scaling

Volts, watts, joules and seconds throughout. Every number you give a model is at the domain's nominal voltage Vnom and nominal clock period. When the domain runs at another operating point, the model scales automatically:

QuantityScales asReason
static (leakage) power× V/Vnomleakage current is roughly constant over the DVFS range
state dynamic power× (V/Vnom)² × f/fnomP = αCV²f
operation energy× (V/Vnom)²E = CV² per switching event
transition energynot scaledit is a property of the transition

If a domain has no clock (nominal period zero), frequency scaling is off and only voltage scaling applies. A model with no domain runs at its nominal values and is always on.

Energy is integrated exactly between events: whenever the state, the voltage, the frequency or the on/off status changes, the energy accumulated so far is booked at the old power, and the new power applies from then on. There is no sampling error.

6.3 A first power model

struct Core : sc_module {
    power_domain dom{"dom", 1.0, sc_time(10, SC_NS)};   // 1.0 V, 100 MHz nominal
    power_model  power{"power", dom};
    dvfs_clock   clk{"clk", dom};

    SC_CTOR(Core) {
        power.add_state("run",   2e-3, 18e-3)   // 2 mW leakage + 18 mW dynamic
             .add_state("idle",  2e-3)          // clock gated: leakage only
             .add_state("sleep", 0.2e-3)        // retention
             .add_transition("idle",  "sleep", sc_time(500, SC_NS), 10e-9)
             .add_transition("sleep", "run",   sc_time(2, SC_US),   40e-9)
             .add_operation("instr", 50e-12);   // 50 pJ per instruction
        SC_THREAD(work);
    }
    void work() {
        power.consume("instr", 0, 1000);    // 1000 instructions
        wait(dom.cycles(1000));             // at one per cycle
        power.enter_state("idle");          // charges the transition, waits its latency
        ...
    }
};

6.4 Wake and sleep policies

power.set_wake_state("run");                              // work wakes the model
power.set_idle_policy(sc_time(3, SC_US), "sleep", "idle"); // 3 us in idle -> sleep

6.5 DVFS

dom.add_operating_point("turbo",  1.10, sc_time(1, SC_NS))
   .add_operating_point("normal", 0.90, sc_time(1.0 / 0.6, SC_NS))
   .add_operating_point("eco",    0.70, sc_time(1.0 / 0.3, SC_NS));
dom.set_operating_point("eco");

examples/power_dvfs runs the same deadline-driven workload on two cores, one fixed at the top operating point and one with a governor that picks the slowest point that still meets each deadline, and compares their energy.

6.6 Power gating and clock gating

dom.set_switch_energy(150e-9, 20e-9);   // energy to switch on / off
dom.power_off();
...
dom.power_on();

While a domain is off:

Clock gating of a single block without switching the domain off is modelled with states: an idle state with leakage only. To stop the block's own clock signal as well, use dvfs_clock::disable()/enable().

6.7 Power in TLM models

<sc_power_tlm.h> provides two pieces.

charge(model, trans, delay) — call it at the top of a target's b_transport (or when an AT target accepts a request):

void b_transport(tlm::tlm_generic_payload &t, sc_time &delay) {
    if (!sc_power::charge(power, t, delay)) return;   // domain off: error response set
    ... normal target behaviour ...
}

It:

power_extension — an ignorable payload extension. An initiator that attaches it learns what each transaction cost along its whole path:

sc_power::power_extension ext;
t.set_extension(&ext);
socket->b_transport(t, delay);
t.clear_extension(&ext);
// ext.energy        joules charged by every target and interconnect on the path
// ext.wake_latency  wake-up time they added
// ext.charges       how many components charged

Interconnects call charge() too, to account for their own energy per transfer. examples/power_tlm_soc builds a complete SoC this way:

It reports per-frame transaction energy against total system energy.

6.8 Reports, CSV and waveforms

power_monitor &mon = power_monitor::instance();
mon.report(std::cout);         // now
mon.report_at_end();           // automatically at the end of simulation
mon.sample(sc_time(1, SC_US), "power.csv");   // before sc_start
mon.trace(tf);                 // sampled power into a VCD (needs sample())
sc_power::sc_trace(tf, model, "cpu_power");   // a model's background power

The report has one row per model: state, current power, average power, energy, and the static / dynamic / transition split. Then for each model it gives the share of time spent in each state and the count of each operation, and for each domain its voltage, frequency, operating point, power and energy. A * after a state means the model's domain is off.

Power report at 74012 ns
model       state         power       avg power   energy      static      dynamic     transitions
cpu.power   sleep         225 uW      19.8 mW     1.466 uJ    132.7 nJ    1.315 uJ    18 nJ (5)
dram.power  self_refresh  3 mW        13.31 mW    985.4 nJ    519.6 nJ    435.8 nJ    30 nJ (13)
accel.power ready*        0 W         3.719 mW    275.3 nJ    33.84 nJ    241.4 nJ    0 J (6)
total                     4.225 mW    45.71 mW    3.383 uJ  (domain switching 530 nJ)

The CSV has one row per sample period. Each row has the time in seconds, then each model's average power over that period (dynamic operation energy included), then the total, so it plots directly in a spreadsheet. trace(tf) writes the same sampled values into a VCD as reals.

sc_trace(tf, model, name) traces the model's instantaneous background power: static plus state-dynamic, excluding the per-operation energy, which has no duration.

6.9 From the host

Power models and domains are visible to the host tools by their hierarchical names:

tsystemc> power                        full report
tsystemc> power core.power core.dom    one line each
tsystemc> list power
tsystemc> set core.dom eco             switch operating point
tsystemc> set core.dom off             gate the domain
tsystemc> set core.power sleep         force a state

get core.power returns state=run power=20 mW avg=24.08 mW energy=602 nJ. The C API has sysc_power_report(), and the MCP server has a power tool.

6.10 Calibrating a model

The library computes energy exactly from the numbers you give it. The accuracy of the result is the accuracy of those numbers. Typical sources:

Keep each model's numbers at one documented nominal point, and let the domain scale them.

6.11 Limitations


7. The tsystemc command line

tsystemc run     <model.cpp|dll> [--time T] [--vcd F] [--trace names] [-- args...]
tsystemc build   <model.cpp> [-o model.exe] [--cxxflags "..."]
tsystemc compile <model.cpp> [-o model.dll] [--cxxflags "..."]
tsystemc script  <file.scs>          run a command script (exit code = failures)
tsystemc -c "<cmd>; <cmd>; ..."      inline script
tsystemc                             interactive prompt
tsystemc mcp                         MCP server on stdio
tsystemc version

7.1 Scripts and the prompt

The commands work the same in scripts, in -c, at the prompt and through MCP:

CommandEffect
open <model> [args]Load (compile if needed) and elaborate: runs sc_main until its first sc_start().
run [T]Advance simulated time by T (e.g. 100 ns). With no T, run until no activity remains.
continueLet sc_main resume until its next sc_start().
finishLet sc_main run to completion.
get <name>...Print signal, port, clock, power model or power domain values.
set <name> <value>Write a signal (visible next delta), set a power state, or switch a power domain.
expect <name> <value>Compare; prints PASS or FAIL. Failures count towards the exit code.
power [name...]Power report, or individual power models and domains.
list [signals|ports|modules|processes|power|all]List objects with their kinds.
trace <file.vcd> [names|*]Trace signals from now on.
time, delta, statusSimulation time, delta count, session status.
output, capture on|offModel console output.
echo, close, quit—

Commands are separated by newlines or ;. A # starts a comment.

Example regression script:

open counter.cpp
run 100 ns
expect count 0
set reset 0
run 50 ns
expect count 5
power
finish

7.2 How hosting works

open runs sc_main until it calls sc_start() for the first time, which is the point where elaboration is complete. sc_main then parks inside that call, and the host steps the kernel with run.

continue lets the parked sc_start run for its own duration and returns at the next sc_start. finish lets sc_main run to its end.

A model written specifically for hosting can call sc_start() once with no duration and leave all stepping to the host.


8. Embedding: the C host API

tsystemc.dll exports a plain C API, declared in include/sysc_api.h. Link with lib/libtsystemc_api.a, or load the DLL with LoadLibrary.

#include <sysc_api.h>

int h = sysc_open("model.cpp", "");       /* .cpp: compiled on demand and cached */
if (h <= 0) { char e[512]; sysc_last_error(0, e, sizeof e); puts(e); return 1; }
sysc_set(h, "din", "5");
sysc_run(h, "10 ns");
char v[64];
sysc_get(h, "sum", v, sizeof v);
int n = sysc_power_report(h, NULL, 0);    /* length; then call again with a buffer */
sysc_close(h);
FunctionPurpose
sysc_versionVersion string.
sysc_compile, sysc_build_exeBuild a model DLL or an executable.
sysc_open, sysc_closeLoad and elaborate a model (.cpp or DLL); release it.
sysc_run, sysc_continue, sysc_finishAdvance time; resume sc_main.
sysc_get, sysc_setRead and write objects by hierarchical name.
sysc_time, sysc_time_seconds, sysc_delta_count, sysc_statusInspect the simulation.
sysc_listList objects by kind.
sysc_traceVCD-trace signals.
sysc_power_reportThe power report.
sysc_capture, sysc_outputCapture the model's console output.
sysc_exit_code, sysc_last_errorResults and errors.

String results. Functions that return text return the full length and write as much as fits into your buffer, always NUL-terminated. Call once with a NULL buffer to size it.

Errors. Errors are negative:

CodeMeaning
SYSC_ERRGeneral error
SYSC_ERR_HANDLEInvalid handle
SYSC_ERR_NOT_FOUNDNo object with that name
SYSC_ERR_FINISHEDsc_main has already returned

sysc_last_error explains the most recent error.

Several models. Several models can be open at once, each with its own handle and its own copy of the engine. A model DLL stays mapped until the host process exits, because the statically linked runtime registers thread-exit hooks inside it. Reopening a model loads a fresh copy.


9. Co-simulation and AI agents

9.1 Verilog co-simulation through DPI-C

sysc_api.h also declares a DPI-friendly subset that takes only int, longint and string arguments:

A SystemVerilog test bench imports them and steps the SystemC model in lock-step with the RTL:

import "DPI-C" function int     sysc_dpi_open(string model, string args);
import "DPI-C" function int     sysc_dpi_run_ps(int h, longint ps);
import "DPI-C" function longint sysc_dpi_get_int(int h, string name);
import "DPI-C" function int     sysc_dpi_set_int(int h, string name, longint value);

Point the simulator's DPI library list at bin\tsystemc.dll. For trsim, set TUWASIM_DPI_LIBS=<install>\bin\tsystemc.dll. examples/cosim holds a complete test bench.

9.2 MCP server

tsystemc mcp speaks the Model Context Protocol (JSON-RPC 2.0 over stdio). An AI agent, or any MCP client, can then open a model, run it, inspect and change signals and power states, and read the power report. The tools are:

Model console output never mixes with the protocol stream. It is captured and returned in the tool results.

Example client configuration:

{ "mcpServers": { "tsystemc": { "command": "C:\\tools\\tsystemc-1.0.0\\bin\\tsystemc.exe", "args": ["mcp"] } } }

ICPP connects the same way:

h = icpp_mcp_connect("C:/tools/tsystemc-1.0.0/bin/tsystemc.exe mcp");

10. Examples

Every example builds with the commands in chapter 3 and runs from its own directory. Each one has a .golden file with its expected output.

ExampleShows
counterSignals, a clocked method, reset, VCD tracing. Also used by the CLI scripts.
tlm_ltLoosely-timed platform: CPU with quantum keeper, router, RAM/ROM, DMI, debug transport, error responses.
tlm_atApproximately-timed base protocol: four phases, all return paths, memory manager, PEQs, back-pressure, an extended phase.
tlm_busTwo CPUs on a shared bus: multi-passthrough sockets, arbitration, address decoding, analysis-port monitor, extensions, a UART.
power_statesA core with run/idle/sleep, transitions, auto-sleep and auto-wake, a gated dvfs_clock, VCD power trace, report.
power_dvfsA DVFS governor against a fixed-frequency core on the same workload: operating points, scaling, CSV trace, energy comparison.
power_tlm_socA power-aware LT SoC: CPU with DVFS and sleep, bus, DRAM with self-refresh, power-gated accelerator, charge(), power_extension.
cosimA model driven from a SystemVerilog test bench (DPI-C) and from ICPP (MCP).
scriptstsystemc scripts: counter.scs, power.scs.

To run one:

cd examples\power_dvfs
tsystemc run power_dvfs.cpp

To check one against its expected output:

g++ -std=c++17 -I..\..\include power_dvfs.cpp ..\..\lib\libsysc.a -static -o power_dvfs.exe
power_dvfs.exe > out.txt
fc out.txt power_dvfs.golden

11. Conformance notes

TSystemC implements IEEE Std 1666-2023 clauses 4 to 17. That covers the kernel, the predefined channels, the data types including fixed point, utilities, TLM-2.0 including the utilities, and TLM-1.

Where the standard and the widely used Accellera reference implementation behave differently, TSystemC follows the standard. The differences you are most likely to notice:

sc_power is an extension. Its names are in namespace sc_power, and it does not change the behaviour of any standard class.


12. Troubleshooting

tsystemc run says "compile failed". The message includes the exact compiler command. Common causes:

"SCE library not found". tsystemc.dll cannot find lib\libsysc.a. Keep the package layout, or set TSYSTEMC_HOME.

Warnings "event already exists ... renamed to event_0". Two unnamed events share a module. Give them names (§4.8).

A thread process crashes with deep recursion or large local arrays. Increase its stack with set_stack_size() in the constructor, or globally with SCE_STACK_SIZE.

SC_POWER/off warnings. A model called consume() while its domain was off. Either the model should check powered() first, or the domain is switched off too early.

The power CSV is empty. Call power_monitor::sample() before sc_start(). The file is complete once the simulation ends (sc_stop(), or sc_main returning).

A changed model still behaves the old way under tsystemc run. The cache key is the file path, size and modification time. Touch the file, or delete %TEMP%\tsystemc_cache.


13. Power API reference

All in namespace sc_power. Units: V, W, J, s.

power_domain (an sc_object, kind "sc_power_domain")

Member
power_domain(name, nominal_voltage = 1.0, nominal_period = SC_ZERO_TIME)A top-level domain. A zero period means no clock.
power_domain(name, parent, nominal_voltage, nominal_period = SC_ZERO_TIME)A child domain: off whenever the parent is off.
add_operating_point(name, volts, period)Add or redefine an operating point; chainable. The nominal point is "nominal".
set_operating_point(name)DVFS: switch voltage and clock period.
operating_point_name(), operating_points()Current point; all points.
set_voltage(v), set_clock_period(p)Direct control (point becomes "custom").
voltage(), nominal_voltage(), clock_period(), nominal_period(), frequency()Current and nominal values; frequency in Hz (0 without a clock).
cycles(n)n × current clock period.
power_off(), power_on(), is_on()Power gating; is_on() is false if any ancestor is off.
set_switch_energy(on_J, off_J), switch_count(), switching_energy()Switching costs.
energy(), power()Totals over its models, child domains and switching.
models(), children(), parent()Structure.
change_event()Notified (delta) on every change.

Host value: on 900 mV 600 MHz op=normal power=1.2 mW energy=3.4 uJ. Values that can be set: on, off, or an operating point name.

power_model (an sc_object, kind "sc_power_model")

Member
power_model(name, domain) / power_model(name, &domain) / power_model(name)With or without a domain.
add_state(name, static_W, dynamic_W = 0)Chainable. The first state added is the initial state. Without states there is one state "on" drawing nothing.
add_transition(from, to, latency, J = 0)Chainable.
add_operation(name, J, J_per_byte = 0)Chainable.
set_initial_state(s)Chainable.
set_wake_state(s)Auto-wake target for consume().
set_idle_policy(timeout, sleep, from = wake state)Auto-sleep.
set_state(s) → sc_timeChange state now, charge the transition energy, return its latency.
enter_state(s)set_state, then wait(latency). Threads only.
state(), has_state(s), state_names(), state_event()State queries; state_event() is notified on each change.
consume(op, bytes = 0, count = 1) → sc_timeCharge an operation; returns the wake latency.
operation_energy(op, bytes = 0)Energy one call would cost now (scaled).
add_energy(J)Book arbitrary energy (unscaled).
powered()False while the domain (or an ancestor) is off.
energy(), static_energy(), dynamic_energy(), transition_energy()Energy up to now.
power(), average_power()Background power now; energy ÷ elapsed time.
time_in_state(s), operation_count(op), transition_count()Statistics.
power_variable()A const double& of the background power, for sc_trace.

Host value: state=run power=20 mW avg=24.08 mW energy=602 nJ. The value that can be set is a state name.

dvfs_clock (an sc_signal<bool>, kind "sc_clock")

Member
dvfs_clock(name, domain, duty = 0.5)Follows the domain: its period, and stops while it is off.
dvfs_clock(name, period, duty = 0.5)Free-standing.
set_period(p), period()Takes effect at the next rising edge.
enable(), disable(), enabled()Clock gating. Stops low after the current cycle; restarts in the next delta.
cycle_count()Rising edges so far.

Bind it like any clock: port(clk), sensitive << clk.posedge_event().

power_monitor (singleton)

Member
instance()The monitor.
models(), domains(), find_model(name)Registry.
total_energy(), total_power()Whole system, switching energy included.
report(os), report()The report, to a stream or as a string.
report_at_end(on = true)Print the report to stdout at the end of simulation.
sample(period, csv_file)Sampled average power to CSV. Call before sc_start.
trace(tf)Sampled power of each model and the total into a VCD. Needs sample().

TLM helpers (<sc_power_tlm.h>)

Member
bool charge(model, trans, delay)Charge "read"/"write"/"access" with the data length, add the wake latency to delay, and fill a power_extension if one is present. While the domain is off: TLM_GENERIC_ERROR_RESPONSE, returns false.
std::string operation_for(model, trans)The operation charge() would use ("" if none).
struct power_extensionenergy (J), wake_latency, charges; reset().

Free functions

Function
format_si(value, unit, digits = 4)"1.25 mW", "3.4 uJ".
sc_trace(tf, model, name)Trace a model's background power as a VCD real.