Chapter 1: Foundations

Build SystemC and Write a First Model

How a SystemC program is compiled, linked, elaborated, and started.

Listen to this lessonAudiobook mode

How to Read This Lesson

Read this like a conversation between normal C++ and the SystemC kernel. Whenever something looks like magic, ask: what C++ object did that macro or constructor register?

A SystemC model is a normal C++ program linked with the SystemC library. That makes the workflow familiar: include headers, compile sources, link against the library, and run the executable.

The official downloads and release material live under Accellera's SystemC resources, while active source development is public in the Accellera GitHub repository. In a production environment, pin a SystemC version the same way you would pin a compiler or simulator.

Standard and source context

This first runnable model is where the standard stops being abstract. Use Docs/LRMs/SystemC_LRM_1666-2023.pdf to check what the kernel promises during elaboration and simulation startup, then compare that with the Accellera source path around sc_module, sc_object, sc_module_name, sc_simcontext, and process registration. The useful question is not "where is the magic?" but "which C++ object did this line register with the kernel?"

Minimal Build Shape

The exact commands vary by installation, but the structure is consistent:

c++ -std=c++17 main.cpp \
  -I/path/to/systemc/include \
  -L/path/to/systemc/lib \
  -lsystemc \
  -o sim
./sim

Many teams wrap this with CMake:

cmake_minimum_required(VERSION 3.20)
project(counter_systemc CXX)
 
set(CMAKE_CXX_STANDARD 17)
find_package(SystemCLanguage CONFIG REQUIRED)
 
add_executable(sim main.cpp)
target_link_libraries(sim SystemC::systemc)

A Clocked Counter

This example introduces a module with input and output ports:

#include <systemc>
using namespace sc_core;
 
SC_MODULE(Counter) {
  sc_in<bool> clk{"clk"};
  sc_out<unsigned> value{"value"};
  unsigned internal = 0;
 
  void tick() {
    value.write(++internal);
  }
 
  SC_CTOR(Counter) {
    SC_METHOD(tick);
    sensitive << clk.pos();
    dont_initialize();
  }
};
 
int sc_main(int, char*[]) {
  sc_clock clk{"clk", 10, SC_NS};
  sc_signal<unsigned> count{"count"};
 
  Counter counter{"counter"};
  counter.clk(clk);
  counter.value(count);
 
  sc_start(50, SC_NS);
  return 0;
}

sc_clock is a channel that provides a clock signal. sc_signal<unsigned> is a channel that stores a value and notifies readers when it changes. The Counter module exposes ports, and the top level binds those ports to channels.

The Three Phases You Should Name

SystemC execution is easier to understand if you separate it into three phases:

  1. Construction: C++ constructors allocate modules, channels, and local state.
  2. Elaboration: SystemC finalizes hierarchy, port bindings, process registration, and object names.
  3. Simulation: sc_start() lets the kernel run processes according to events and time.

Many confusing errors come from doing phase-specific work in the wrong place. Binding belongs before simulation. wait() belongs in a thread process during simulation. Creating a process dynamically is possible, but you should first learn the static model.

Practical Advice

Keep the first model small. Build one clock, one signal, one module, and one print statement. Once that is working, add hierarchy. Then add events. Then add TLM. A SystemC environment is just C++, but debugging gets much easier when each layer has been proven independently.

Lesson self-check

Can you answer these clearly?

Keep moving when you can answer each question without looking back at the lesson.

Comments and Corrections