Chapter 13: Modeling Best Practices

Modeling Best Practices: API Docs and Doxygen

How to comment SystemC modules, sockets, registers, CCI parameters, callbacks, and examples so generated docs help real users.

Modeling Best Practices: API Docs and Doxygen

A SystemC Virtual Platform is a software product. Like any software library, if the APIs are not documented, they are unusable. In SystemC, the "APIs" are your module constructors, TLM sockets, CCI parameters, and memory-mapped register contracts.

The industry standard for SystemC documentation is Doxygen. Doxygen comments should explain contracts and abstractions, not just repeat the C++ syntax.

What to Document

When distributing a SystemC IP block, the following elements MUST be documented:

  • Module Abstraction Level: What does it model? (RTL, AT, LT/VP). What is intentionally left out?
  • TLM Sockets: Which protocols do they support? Do they support DMI? What is the expected bus width?
  • Registers: Base offsets, bitfields, reset values, and side-effects of reads/writes (e.g., "Reading this register clears the interrupt").
  • CCI Parameters: Name, type, default value, and mutability rules.
  • Report Message Types (msg_type): The string IDs used in SC_REPORT_ERROR so the integrator knows what they can filter.

Doxygen Commenting Style

Use the standard Doxygen /** ... */ syntax.

Module and Abstraction Comment

/**
 * @class Uart
 * @brief Memory-mapped UART model for VP firmware bring-up.
 *
 * Models TX/RX FIFOs, status flags, and interrupt generation.
 * @note Bit-level serial waveform timing is intentionally abstracted.
 * Data is transferred instantaneously when the TX FIFO drains.
 */
class Uart : public sc_core::sc_module {

TLM Socket Comment

/**
 * @brief Target socket receiving memory-mapped register transactions.
 * 
 * Supports standard TLM-2.0 b_transport. DMI is NOT supported for 
 * memory-mapped peripheral registers. Expected payload width is 32 bits.
 */
tlm_utils::simple_target_socket<Uart> target_socket{"target_socket"};

CCI Parameter Comment

/**
 * @brief Approximate per-byte transmit delay.
 *
 * Mutable during simulation. Changing this affects future bytes only.
 * If set to SC_ZERO_TIME, the UART operates in zero-delay mode.
 */
cci::cci_param<sc_core::sc_time> tx_delay{"tx_delay", sc_core::sc_time(1, sc_core::SC_US)};

Complete Example: A Fully Documented IP Block

This complete sc_main demonstrates how a professionally documented SystemC IP block should look. It includes Doxygen groupings, parameter documentation, and register definitions.

#include <systemc>
#include <iostream>
 
/**
 * @defgroup vp_timer Timer IP Block
 * @brief Abstract timer model for Loosely Timed (LT) Virtual Platforms.
 * @{
 */
 
/**
 * @class VpTimer
 * @brief A 32-bit countdown timer with interrupt generation.
 * 
 * This model uses SystemC SC_THREADs to abstract away clock cycles. 
 * It calculates the exact future time an interrupt should fire and waits 
 * for that duration, maximizing simulation speed.
 */
SC_MODULE(VpTimer) {
    /**
     * @brief Interrupt output signal.
     * Active HIGH. Level-triggered.
     */
    sc_core::sc_out<bool> irq_out{"irq_out"};
 
    /**
     * @name Register Offsets
     * Memory map offsets relative to the module base address.
     * @{
     */
    static constexpr uint32_t REG_CTRL  = 0x00; ///< Control register. Bit 0: Enable.
    static constexpr uint32_t REG_LIMIT = 0x04; ///< Value to countdown from.
    static constexpr uint32_t REG_ACK   = 0x08; ///< Write any value to clear IRQ.
    /** @} */
 
    /**
     * @brief Constructs the VpTimer.
     * @param name The SystemC hierarchical name.
     */
    SC_CTOR(VpTimer) {
        SC_THREAD(timer_process);
    }
 
private:
    void timer_process() {
        wait(10, sc_core::SC_NS); // Dummy logic for compilation
        irq_out.write(true);
    }
};
 
/** @} */ // End vp_timer group
 
int sc_main(int argc, char* argv[]) {
    // Instantiate the documented IP block
    sc_core::sc_signal<bool> irq_sig{"irq_sig"};
    VpTimer timer("my_timer");
    timer.irq_out(irq_sig);
 
    std::cout << "Starting Simulation of Documented IP...\n";
    sc_core::sc_start(1, sc_core::SC_US);
    
    return 0;
}

Explanation of the Execution

While running this code simply executes the dummy logic, running the doxygen tool against this source file will generate a professional HTML manual.

The generated documentation will group the VpTimer class, its irq_out port, and its register offsets into a cohesive "Timer IP Block" page. When a firmware engineer needs to write a driver for this VP, they will look at the Doxygen output to find that REG_ACK = 0x08 and that the IRQ is "Active HIGH, Level-triggered", completely eliminating guesswork.

Comments and Corrections