Ports, Interfaces, and Hardware Boundaries
How HLS interprets SystemC ports and pins at the hardware boundary.
Ports, Interfaces, and Hardware Boundaries
When you write a SystemC module for simulation, ports are just pointers to channels. However, when you pass that SystemC module to an HLS tool to generate RTL (Verilog/VHDL), those ports become physical wires and pins on a silicon chip.
Pin-Level Interfaces
The most basic and universally supported synthesizable ports are sc_in<T> and sc_out<T>. When an HLS tool sees sc_in<sc_uint<8>> data_in;, it generates an 8-bit wide input wire bus on the Verilog module.
Clocks and Resets
Clocks and resets must be explicitly identified. HLS tools use these to schedule the Finite State Machine (FSM).
sc_in_clk(orsc_in<bool>) is used for clocks.- You must use
reset_signal_is()during theSC_CTORto explicitly tell the HLS tool which port is the reset, and whether it is active-high or active-low.
Array of Ports and HLS
Arrays of ports are synthesizable as long as the array size is a constant, statically determinable integer. Dynamically allocated arrays of ports using sc_vector are supported by modern HLS tools only if the vector size is completely fixed and known during elaboration.
Here is a complete compilable example demonstrating static port arrays and explicit reset binding:
#include <systemc>
#include <sysc/datatypes/int/sc_uint.h>
using namespace sc_core;
SC_MODULE(PortArrayDemo) {
sc_in_clk clk{"clk"};
sc_in<bool> rst{"rst"};
// Supported: Generates 4 distinct 8-bit input buses in RTL
sc_in<sc_dt::sc_uint<8>> data_bus[4];
sc_out<sc_dt::sc_uint<10>> sum_out{"sum_out"};
SC_CTOR(PortArrayDemo) {
SC_CTHREAD(compute_sum, clk.pos());
reset_signal_is(rst, true);
}
void compute_sum() {
sum_out.write(0);
wait();
while (true) {
sc_dt::sc_uint<10> temp_sum = 0;
// Static loops can be unrolled by HLS tools
for (int i = 0; i < 4; ++i) {
temp_sum += data_bus[i].read();
}
sum_out.write(temp_sum);
wait();
}
}
};
int sc_main(int argc, char* argv[]) {
sc_clock clk("clk", 10, SC_NS);
sc_signal<bool> rst("rst");
sc_signal<sc_dt::sc_uint<8>> bus_signals[4];
sc_signal<sc_dt::sc_uint<10>> sum_signal("sum_signal");
PortArrayDemo demo("demo");
demo.clk(clk);
demo.rst(rst);
demo.sum_out(sum_signal);
for (int i = 0; i < 4; ++i) {
demo.data_bus[i](bus_signals[i]);
}
sc_start(50, SC_NS);
return 0;
}Custom Channels and Interfaces
In the SystemC simulation world, you can write custom hierarchical channels that implement complex interfaces.
In the Synthesis world, this is heavily restricted. Most HLS tools require all module boundaries to eventually resolve down to standard pin-level ports (sc_in, sc_out). You cannot easily pass a complex C++ object or a generic custom interface across a physical hardware boundary without explicitly defining the wire protocol.
To get around this, modern HLS tools have introduced specialized synthesis pragmas or compiler directives that instruct the tool on how to map a C++ function call into a hardware bus protocol (like AXI4).
In the next tutorial, we will discuss the holy grail of system-level design: Synthesizing TLM-2.0.
Comments and Corrections