Writer Policies and Resolved Signals
Single-writer checks, many-writer policies, resolved logic, and when sc_signal_rv is the right model.
Writer Policies and Resolved Signals
Signals look simple until two processes try to drive the same one. The IEEE 1666 LRM strictly regulates how concurrent writes to signals are handled via writer policies and resolved signal types.
The rule of thumb is simple: if the real hardware has one driver, model it with a single writer policy. If the real hardware has a resolved bus (tri-state, open-drain), model resolution intentionally using resolved signals.
Writer Policies (sc_writer_policy)
The sc_core::sc_signal<T, POL> class template takes a second argument: the writer policy. The LRM defines the sc_core::sc_writer_policy enum with the following rules:
SC_ONE_WRITER(Default): The kernel tracks which process writes to the signal. If a second process attempts to write to the signal during simulation, the kernel instantly throws anSC_REPORT_ERROR. This prevents accidental multi-driver bugs that cause non-deterministic behavior depending on process scheduling order.SC_MANY_WRITERS: Allows multiple processes to write to the signal in the same delta cycle. However, the last process to write "wins" and overwrites the others. This is non-deterministic unless you explicitly control process execution order!SC_UNCHECKED_WRITERS: Disables writer checking entirely (used for performance optimization in legacy models, highly discouraged).
When to use SC_MANY_WRITERS?
Almost never. If a signal has many writers in real hardware, it is usually a multiplexer, an arbiter, or a resolved bus. Use proper structural modeling for muxes/arbiters.
Resolved Logic (sc_signal_resolved and sc_signal_rv)
For true hardware bus resolution (tri-state logic), SystemC provides four-state logic data types (sc_core::sc_logic and sc_core::sc_lv<W>) containing '0', '1', 'Z' (high-impedance), and 'X' (unknown).
When multiple processes drive an sc_core::sc_signal_resolved (or sc_core::sc_signal_rv for vectors), the kernel applies a strict resolution table during the update phase:
- Driving 'Z' and '1' resolves to '1'.
- Driving '0' and '1' resolves to 'X' (short circuit!).
- Driving 'Z' and 'Z' resolves to 'Z'.
Complete Example: Single vs. Many vs. Resolved Writers
This complete sc_main example demonstrates the default single-writer failure, how SC_MANY_WRITERS works, and how sc_signal_resolved properly models a tri-state bus.
#include <systemc>
#include <iostream>
SC_MODULE(BusDemo) {
// 1. Default: SC_ONE_WRITER (Will error if driven by multiple processes)
sc_core::sc_signal<bool> single_driver_sig{"single_driver_sig"};
// 2. SC_MANY_WRITERS: Last writer wins (Dangerous, non-deterministic)
sc_core::sc_signal<bool, sc_core::SC_MANY_WRITERS> many_driver_sig{"many_driver_sig"};
// 3. Resolved signal: Hardware-accurate tri-state bus
sc_core::sc_signal_resolved tri_state_bus{"tri_state_bus"};
SC_CTOR(BusDemo) {
SC_METHOD(driver_a);
SC_METHOD(driver_b);
SC_METHOD(monitor);
sensitive << single_driver_sig << many_driver_sig << tri_state_bus;
dont_initialize();
}
void driver_a() {
// Drive values
many_driver_sig.write(true);
tri_state_bus.write(sc_core::SC_LOGIC_1); // Drive HIGH
// Uncommenting this line and the one in driver_b causes a runtime kernel error!
// single_driver_sig.write(true);
}
void driver_b() {
// Drive competing values
many_driver_sig.write(false);
tri_state_bus.write(sc_core::SC_LOGIC_Z); // Drive High-Impedance (Z)
// single_driver_sig.write(false);
}
void monitor() {
std::cout << "@ " << sc_core::sc_time_stamp() << "\n"
<< " many_driver_sig : " << many_driver_sig.read() << " (Last writer won)\n"
<< " tri_state_bus : " << tri_state_bus.read() << " (1 and Z resolved to 1)\n";
}
};
int sc_main(int argc, char* argv[]) {
BusDemo demo("demo");
std::cout << "Starting simulation...\n";
sc_core::sc_start(10, sc_core::SC_NS);
return 0;
}Explanation of the Execution
When run, the output shows:
Starting simulation...
@ 0 s
many_driver_sig : 0 (Last writer won)
tri_state_bus : 1 (1 and Z resolved to 1)
In many_driver_sig, driver_b executed after driver_a (due to kernel scheduling), so it overwrote true with false. This is a classic race condition.
In tri_state_bus, regardless of scheduling order, the kernel's update phase resolved SC_LOGIC_1 and SC_LOGIC_Z correctly to SC_LOGIC_1, perfectly modeling a hardware bus where a driver asserts '1' while another driver disconnects ('Z').
Comments and Corrections