Chapter 13: Modeling Best Practices

Modeling Best Practices: Abstraction

How to decide what to model, what to approximate, what to document, and how to keep SystemC examples production-like.

Modeling Best Practices: Abstraction

The most important SystemC skill is not knowing every API. It is choosing the right abstraction level. The IEEE 1666 LRM provides mechanisms ranging from bit-accurate, delta-cycle-level RTL modeling all the way up to Loosely Timed (LT) Transaction Level Modeling (TLM-2.0).

A good architect uses the simplest, fastest abstraction that still answers the system's design questions.

The Spectrum of Abstraction

  1. Cycle-Accurate (RTL Level): Uses sc_signal, sc_logic, clocks, and delta cycles.

    • Pros: Matches actual hardware perfectly.
    • Cons: Extremely slow simulation (KHz range).
    • When to use: Hardware validation, HLS (High-Level Synthesis) generation.
  2. Approximately Timed (AT) TLM: Uses TLM-2.0 Non-Blocking Transport (nb_transport), modeling multiple protocol phases (Request, End-Request, Response) with annotated delays.

    • Pros: Highly accurate bus contention and performance profiling.
    • Cons: Harder to write, moderate simulation speed.
    • When to use: Interconnect performance analysis, cache coherency studies.
  3. Loosely Timed (LT) TLM (Virtual Platforms): Uses TLM-2.0 Blocking Transport (b_transport), temporal decoupling, and quantum keepers.

    • Pros: Blistering fast simulation speed (100s of MHz). Capable of booting Linux in seconds.
    • Cons: Timing is approximate; cannot find race conditions on the bus.
    • When to use: Firmware development, early software bring-up, OS porting.

Avoid Accidental RTL

If you are building a Virtual Platform (LT), avoid mixing RTL-style modeling inside it. If you model every clock edge of a UART transmitter using sc_signal<bool> and wait(), your entire platform's speed will be bottlenecked by that one UART.

Instead, model the UART at a high level: when software writes a byte, wait a bulk block of time (wait(byte_delay)), and then raise an interrupt.

Complete Example: Abstracting a Timer

This complete sc_main demonstrates the difference between an RTL-style timer (bad for VPs) and an abstract TLM-style timer (good for VPs).

#include <systemc>
#include <iostream>
 
// ---------------------------------------------------------
// BAD for Virtual Platforms: Cycle-Accurate Timer (RTL Style)
// ---------------------------------------------------------
SC_MODULE(RtlTimer) {
    sc_core::sc_in<bool> clk{"clk"};
    sc_core::sc_out<bool> irq{"irq"};
    
    int counter = 0;
    int limit = 1000;
 
    SC_CTOR(RtlTimer) {
        SC_METHOD(tick);
        sensitive << clk.pos();
    }
 
    void tick() {
        // Wakes up on EVERY SINGLE CLOCK CYCLE! Very slow simulation.
        counter++;
        if (counter >= limit) {
            irq.write(true);
            counter = 0;
        } else {
            irq.write(false);
        }
    }
};
 
// ---------------------------------------------------------
// GOOD for Virtual Platforms: Abstract Timer (TLM Style)
// ---------------------------------------------------------
SC_MODULE(AbstractTimer) {
    sc_core::sc_event irq_event;
    sc_core::sc_time clock_period;
    int limit = 1000;
 
    SC_CTOR(AbstractTimer) : clock_period(10, sc_core::SC_NS) {
        SC_THREAD(timer_process);
    }
 
    void timer_process() {
        while (true) {
            // Wakes up ONLY when the interrupt is actually due!
            // Skips 1000 clock cycles instantly. High simulation speed.
            sc_core::sc_time wait_time = clock_period * limit;
            wait(wait_time);
            
            std::cout << "@ " << sc_core::sc_time_stamp() 
                      << " [AbstractTimer] Interrupt Fired!\n";
            
            irq_event.notify(sc_core::SC_ZERO_TIME);
        }
    }
};
 
int sc_main(int argc, char* argv[]) {
    // We only simulate the AbstractTimer for this demonstration.
    AbstractTimer t_abs("abstract_timer");
 
    std::cout << "Starting Virtual Platform simulation...\n";
    sc_core::sc_start(25, sc_core::SC_US);
    
    return 0;
}

Explanation of the Execution

Starting Virtual Platform simulation...
@ 10 us [AbstractTimer] Interrupt Fired!
@ 20 us [AbstractTimer] Interrupt Fired!

The RtlTimer would require the SystemC kernel to evaluate the tick() method 2,500 times to simulate 25 microseconds (assuming a 10ns clock).

The AbstractTimer requires the SystemC kernel to evaluate timer_process() exactly 2 times. By abstracting away the clock signal and calculating the bulk time jump, simulation performance increases by orders of magnitude, making firmware development practical.

Comments and Corrections