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.
How to Read This Lesson
This best-practice lesson is written for code reviews. Use it to decide what should be portable standard behavior, what is an implementation detail, and what needs a project rule.
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 C++ documentation is Doxygen. Doxygen comments should explain contracts and abstractions, not just repeat the C++ syntax. Furthermore, Accellera provides programmatic ways to expose this metadata directly into the simulation kernel.
Source and LRM Trail
Best-practice lessons should be traceable. Use Docs/LRMs/SystemC_LRM_1666-2023.pdf, the domain LRMs for AMS/CCI/UVM when relevant, .codex-src/systemc, .codex-src/cci, .codex-src/uvm-systemc, and .codex-src/systemc-common-practices. Mark what is portable, what is source insight, and what is project policy.
What to Document and Expose to the Kernel
When distributing a SystemC IP block, the following elements MUST be documented, and where possible, registered with the Accellera kernel APIs:
- 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 (
cci_param): Name, type, default value, and mutability rules. When instantiatingcci_param, you should also usecci_param::set_description()so that tools querying thecci_broker_ifcan extract the Doxygen string at runtime via the JSON-basedcci_valueAST. - Report Message Types (
msg_type): The string IDs used inSC_REPORT_ERROR. You should document these so integrators can configure thesc_report_handlerto suppress or escalate specific warnings.
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 (with Kernel Registration)
/**
* @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)};
// Inside SC_CTOR, push the documentation to the CCI Broker:
// tx_delay.set_description("Approximate per-byte transmit delay. Mutable.");Complete Example: A Fully Documented IP Block
Here is a complete sc_main demonstrates how a professionally documented SystemC IP block should look. It includes Doxygen groupings, parameter documentation, and programmatic registration.
#include <systemc>
#include <cci_configuration>
#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"};
/**
* @brief Frequency of the timer.
* Accessible via cci_broker_if.
*/
cci::cci_param<int> frequency_hz{"frequency_hz", 1000000};
/**
* @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) {
// Register the documentation string with the Accellera CCI broker
frequency_hz.set_description("Clock frequency of the timer in Hz.");
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);
// Query the CCI Broker to demonstrate runtime metadata extraction
cci::cci_broker_handle broker = cci::cci_get_broker();
cci::cci_param_handle param = broker.get_param_handle("my_timer.frequency_hz");
std::cout << "Runtime Param Description: " << param.get_description() << "\n";
std::cout << "Starting Simulation of Documented IP...\n";
sc_core::sc_start(1, sc_core::SC_US);
return 0;
}Explanation of the Execution
Runtime Param Description: Clock frequency of the timer in Hz.
Starting Simulation of Documented IP...
While running this code executes the simulation logic, running the doxygen tool against this source file will generate a professional HTML manual.
By combining static Doxygen comments with runtime cci_broker_if metadata (via set_description()), your models become fully introspectable. A firmware engineer can read the generated HTML to find that REG_ACK = 0x08, while an automated VP configuration tool can query the cci_broker_if at runtime to generate a GUI tooltip for the frequency_hz parameter.
Deep Dive: Accellera Source for sc_signal and update()
The sc_signal<T> channel perfectly illustrates the Evaluate-Update paradigm of SystemC. In the Accellera source (src/sysc/communication/sc_signal.cpp), sc_signal inherits from sc_prim_channel.
The write() Implementation
When you call write(const T&), the signal does not immediately change its value. Instead, it stores the requested value in m_new_val and registers itself with the kernel:
template<class T>
inline void sc_signal<T>::write(const T& value_) {
if( !(m_new_val == value_) ) {
m_new_val = value_;
this->request_update(); // Inherited from sc_prim_channel
}
}The request_update() call appends the channel to sc_simcontext::m_update_list.
The update() Phase
After the Evaluate phase finishes (all ready processes have run), the kernel iterates over m_update_list and calls the update() virtual function on each primitive channel. For sc_signal, this looks like:
template<class T>
inline void sc_signal<T>::update() {
if( !(m_new_val == m_cur_val) ) {
m_cur_val = m_new_val;
m_value_changed_event.notify(SC_ZERO_TIME); // Notify processes sensitive to value_changed_event()
}
}This guarantees that all concurrent processes see the same old value until the delta cycle advances, perfectly mimicking hardware register delays.
Comments and Corrections