SystemC
Eckhard Kantz
Partner sites:
Website: Ralf-Michael Bath

Website: Klaus Klencke

Informationstechnologie mit SystemC

Rückschau

Mit der von Charles Babbage 1837 entworfenen Rechenmaschine "Analytical Engine" wurden konzeptionelle Grundlagen für universelle, frei programmierbare Computer gelegt. Konrad Zuse hat darauf aufbauend 1941 mit der "Z3" einen funktionsfähigen Rechner entwickelt. In den folgenden Jahrzehnten wurden daraus Großrechner bis hin zur "IBM Blue Gene 2007".

Im Jahre 1981 begann mit dem "IBM-PC" eine Entwicklung hin zum "Personal Computer", wodurch leistungsfähige Rechentechnik an Arbeitsplätzen und zu Hause verfügbar wurde. Basierend auf dem "Intel Pentium" Prozessor und den Prozessoren anderer Hersteller wurde der Einsatz von PCs im Beruf und in der Freizeit zum Standard.

Die Mobilität der Rechentechnik rückte mit der Entwicklung von Notebooks in den Vordergrund und führte mit dem Aufkommen geeigneter Hardware zu Smartphones, die in ihrer Rechenleistung den PCs oft gleichwertig oder teils schon überlegen sind.

In der neuesten Entwicklung eines IoT (Internet of Things) gelangt vernetzte Rechentechnik in alle Bereiche des Lebens, wo Informationen abgefragt oder Vorgänge gesteuert werden sollen. Waren es in der Vergangenheit zunächst industrielle Anlagen, die ferngesteuert abgefragt und kontrolliert wurden, so rückt der Fernzugriff auf Sensoren und steuerbare Geräte nun in Reichweite des Privatanwenders.

Programmierung

Blick zurück

Mit dem Aufkommen frei programmierbarer Rechner wurden Hilfsmittel entwickelt, mit denen diese Rechner frei programiert werden konnten. Dabei war es notwendig, dass in den Programmspeichern der Rechner digitale Informationen (Bits, Bitgruppen) gesetzt wurden, welche dann das "Programm" darstellten, nach welchem der Rechner seine Arbeit verrichtet hat. Es hat sich als zweckmäßig erwiesen, diese Anweisungen für die Rechner zunächst mit lesbaren Zeichen (Buchstaben, Zahlen) zu notieren, um sie anschließend maschinell in den Programmspeicher zu übertragen (Compilieren, Linken, Laden).

Im Laufe der Jahrzehnte wurden viele "Programmiersprachen" und geeignete Compiler dafür entwickelt, nachdem in der Anfangszeit zunächst annähernd auf Maschinenebene in "Assembler" kodiert wurde. Die Programmiersprache "C" und deren Erweiterung "C++" für objektorientierte Programmierung hat sich seit jener Zeit bis heute hin auf allen Systemen als eine von vielen Hochsprachen erhalten und stellt heute einen Industriestandard dar.

Blick nach vorn

Beim "Internet of Things" findet verstärkt eine digitale Hardware Anwendung, welche teils mit existierenden Sprachen programmiert werden kann und welche teils auch vollkommen neue Methoden erfordert. Diese digitale Hardware (FPGA – Field Programmable Gate Array) ist dadurch gekennzeichnet, dass die logischen Grundelemente auf einem Chip beim Einschalten zunächst per Anweisung ("Firmware") miteinander verknüpft werden müssen, bevor sie dann die ihnen zugedachte Funktion ausführen können.

Hersteller bieten oft eine Mischung aus bisheriger (Prozessor-) Technologie und einer frei programmierbaren digitalen Schaltung (FPGA) als "SoC" (System On a Chip) an. Dabei kann der Prozessor in einer Brückenfunktion z.B. die Kommunikation in einem Netzwerk übernehmen während auf dem FPGA mit höchster Geschwindigkeit "Echtzeitaufgaben" bearbeitet werden. In diesem Beitrag ist vor allem die Programmierung des FPGA im Fokus, insbesondere wenn dieser als alleinige programmierbare Einheit für alle Aufgaben einschließlich Kommunikation mit der Außenwelt verantwortlich ist.


Spartan 3A FPGA Experimentierboard

Für die Entwicklung von FPGA-Firmware wurde im zurückliegenden Jahrzehnt eine Programmierumgebung "SystemC" entwickelt und standardisiert, welche auf C++ basiert und eine Template-Bibliothek dafür darstellt. Neben VHDL und Verilog als eingeführten Werkzeugen für den Entwurf digitaler Schaltungen scheint "SystemC" auf dem Weg zu sein, auf einer höheren Ebene als wie VHDL und Verilog dies vermögen, ein Industriestandard für die Modellierung digitaler Systeme zu werden.

Demonstrator

Die überlegenen Eigenschaften von frei programmierbarer Hardware (FPGA) gegenüber bisherigen auf Prozessoren basierenden Lösungen sollen anhand eines Demonstrators aufgezeigt werden, welcher mit bisherigen Ansätzen vermutlich nicht geeignet zu realisieren ist.

In Anbetracht der bevorstehenden Einführung von 3D-Transporttechnologie ist eine visuelle Navigation insbesondere bei den Start-/Landevorgängen eine wesentliche Voraussetzung für einen sicheren Betrieb. Dazu eignen sich 3D-Kamerasysteme, welche aus den Bildern von 2 Kameras eine 3D-Szene punktweise berechnen können.


Industrielles 3D-Kamerasystem
z.B. 2x DFK 23UX249 (30fps@1920x1200)

Allen 3D-Kamerasystemen ist gemein, dass hohe Datenmengen mit komplexen Algorithmen verarbeitet werden müssen, bevor eine Aussage in der Art "Hindernis beim Landen im Weg" oder "Landebereich kollisionsfrei" geliefert werden kann. Selbst wenn die schnellsten derzeit verfügbaren Prozessoren Anwendung finden, so liegt die Rechenzeit dafür in der Größenordnung von etwa einer Sekunde.

Die Forderung nach "Echtzeit" bedeutet für ein 3D-Kamerasystem jedoch, dass alle gelieferten Bilder einer 3D-Auswertung unterzogen werden können und dass ein Ergebnis noch vor dem Aufnehmen des nächsten Bildes vorliegt. Bei einer typischen Bildrate von 30 Bildern pro Sekunde und oft auch höher bedeutet dies, dass maximal 33 Millisekunden oder weniger für die Berechnungen zur Verfügung stehen. Dies kann somit als "Demonstrator" angesehen werden, der vermutlich nur mit einem FPGA-Design realisiert werden kann.

Konzept "Prüfung Kollisionsfreiheit"

Für die Realisierung eines Systems zur Prüfung der Kollisionsfreiheit bei Start-/Landevorgängen von 3D-Transporttechnologie stehen folgende Aufgaben im Mittelpunkt:

Es soll versucht werden, für die genannten Aufgaben schrittweise Lösungen basierend auf "SystemC" zu entwickeln, welche auf diversen FPGA-Plattformen für Echtzeitanforderungen implementiert werden können. Dabei kommt ein Datenprotokoll nach der "FTLight"-Spezifikation zu Anwendung:

http://wegalink.eu/development/ftlight/FTLight_DE.pdf


Entwicklungsumgebung

Für den Einstieg in die SystemC-Entwicklung kann das Buch "Modellierung von digitalen Systemen mit SystemC" von Frank Kesel empfohlen werden. Nach den dort gegebenen Hinweisen wurde eine Entwicklungsumgebung mit den folgenden Komponenten aufgesetzt:

Sobald alle Entwicklungswerkzeuge installiert und eingerichtet sind kann mit dem Programmieren in "SystemC" begonnen werden. Als Beispiel zum Einstieg soll ein Frequenzteiler vorgestellt werden, der ein Taktsignal mit der gewünschten Frequenz aus einem Vielfachen dieser Frequenz erzeugen kann.

Header

Die folgende Headerdatei 'FrequencyScaler.h' zeigt bereits einige wesentlichen Elemente, welche in SystemC zur Anwendung kommen:

#include <systemc>

using namespace std;

using namespace sc_core;

/** FrequencyScaler

* A clk input frequency is divided by a constant value 'max_tick'.

* There is one output 'f' with a duty that can be determined by 'duty_tick'.

* A second output 'q' delivers a counter value starting with 'start_tick'.

* The counter is reset to zero when it reaches 'max_tick' or when reset is true.

*/

class FrequencyScaler : public sc_module

{

public:

// ports

sc_in<bool> clk, reset;

sc_out<int> q; // counter

sc_out<bool> f; // wave form with requested duty cycle

// parameters

int max_tick;

int duty_tick;

int start_tick;

// default parameter values

#define DEFAULT_MAX_TICK 10

#define DEFAULT_DUTY_TICK 1

#define DEFAULT_START_TICK 0

// makro for involving threads/methods

SC_HAS_PROCESS(FrequencyScaler);

// constructor

FrequencyScaler(

sc_module_name name,

int max_tick = DEFAULT_MAX_TICK,

float duty_tick = DEFAULT_DUTY_TICK,

int start_tick = DEFAULT_START_TICK);

// behavior function

void process();

};

Ein neues Modul wird in SystemC von der Klasse 'sc_module' abgeleitet. Es besitzt Eingangssignale und Ausgangssignale, welche mit den Templates 'sc_in<..>' und sc_out<..> auf die gewünschten Typen, wie z.B. 'bool' oder 'int' festgelegt werden.

Im Konstruktor eines Moduls steht als erstes Argument stets der Name des Moduls als Typ 'sc_module_name'. Im weiteren können erforderliche Parameter festgelegt werden, welche im vorliegenden Fall alle mit Standardwerten versorgt werden.

Von Hardwarebeschreibungssprachen wie VHDL oder Verilog ist die Bedeutung einer Funktion 'process()' bekannt, in welcher das Verhalten des Moduls programmiert wird. Falls es diese gibt, dann muss ebenfalls das Macro SC_HAS_PROCESS mit dem Namen des Moduls aufgerufen werden, welches intern in SystemC den Rahmen für die Prozessfunktionalität zur Verfügung stellt.

Damit ist dieses einfache Modul mit zwei Eingangssignalen 'clk' und 'reset' und zwei Ausgangssignalen 'q' und 'f' bereits vollständig deklariert.

Implementierung

Eine mögliche Implementierung kann wie folgt ausgeführt werden:

#include <systemc>

#include "FrequencyScaler.h"

FrequencyScaler::FrequencyScaler(sc_module_name name, int max_tick, float duty_cycle, int start_tick)

: sc_module(name), max_tick(max_tick), duty_tick(duty_cycle), start_tick(start_tick)

{

// initialize a trigger for the behavior function

SC_CTHREAD(process, clk.pos());

reset_signal_is(reset, true);

}

void FrequencyScaler::process()

{

int result = start_tick;

// reset section

q.write(result);

f.write(false);

// main loop

while (true){

wait();

// output frequency signal

if ((result == 0 || result < duty_tick) && result != max_tick-1)

f.write(true);

else

f.write(false);

// increment result

result = q.read() + 1;

if (result >= max_tick)

result = 0;

// output result

q.write(result);

}

}

Im Konstruktor werden für alle Parameter als erstes die entsprechenden Konstruktoren aufgerufen. Anschließend wird im Konstruktor mit dem Macro SC_THREAD festgelegt, dass die Funktion 'process()' mit jeder positiven Flanke von 'clk' einmal aktiviert wird. Gleichzeitig wird in der nächsten Zeile das 'reset'-Signal mit dieser Funktion verknüpft.

Die Abarbeitung der Funktion 'process()' bedarf einer Erklärung, da sie nicht in herkömmlicher Weise erfolgt. Dem gewohnten Aufruf entspricht lediglich das 'reset'-Signal, welches eine bedingungslose Abarbeitung der Funktion von deren Beginn bewirkt.

Anschließend pausiert die Funktion bei jedem Erreichen der 'wait()'-Funktion innerhalb der 'while'-Schleife bis ein nächstes Triggersignal eintrift. Als Triggersignal wurde im Konstruktor die positive Flanke des 'clk'-Signals festgelegt, wodurch die 'wait()'-Funktion bei jedem Eintreffen eines solchen Triggersignals verlassen wird.

Hervorzuheben sind weiterhin die Memberfunktionen 'read()' und 'write()' für alle Signale, welche deren Wert abrufen oder neu setzen. Alle Programmvariablen werden weiterhin so wie dies üblich ist geeignet deklariert und verwendet. Im Fall eines 'clocked thread', wie mit dem SC_CTHREAD für die 'process()'-Funktion festgelegt, wirkt die lokale Variable 'result' jedoch über alle Aufrufe hinweg und kann daher wie eine Member-Variable zum Zählen der Eingangsimpulse verwendet werden.

Testbench

Eine hervorstechende Eigenschaft von SystemC sind die integrierten Testmöglichkeiten. Diese werden durch Erzeugen einer ablauffähigen .exe Datei mittels eines Testbench-Moduls nutzbar gemacht, welches alle zur Anwendung gehörigen Module mit frei definierbaren Testsignalen beaufschlagt und die Ausgangssignale der involvierten Module dazu berechnet:

#include "FrequencyScaler/FrequencyScaler.h"

#include <systemc>

using namespace std;

using namespace sc_core;

class Testbench : public sc_module

{

public:

// ports

sc_in<bool> clk;

sc_in<int> q;

sc_in<bool> f;

sc_out<bool> reset;

SC_HAS_PROCESS(Testbench);

Testbench(sc_module_name name) : sc_module(name)

{

SC_CTHREAD(generate, clk.neg());

}

void generate()

{

// initialize DUT (device under test)

reset.write(true);

// run test sequence

for (int t = 0;; t++){

// reset

if (t%156 == 0) reset.write(true);

// wait for next cycle

wait();

reset.write(false);

}

}

};

class top : public sc_module

{

public:

FrequencyScaler *fs;

Testbench *tb;

top(sc_module_name name) : sc_module(name),

clock("clock", 200, SC_NS)

{

// parameters

const int max_tick = 100;

const float duty_cycle = 42;

fs = new FrequencyScaler("FrequencyScaler1", max_tick, duty_cycle);

fs->clk(clock);

fs->reset(resetSignal);

fs->q(qSignal);

fs->f(fSignal);

tb = new Testbench("Testbench1");

tb->clk(clock);

tb->reset(resetSignal);

tb->q(qSignal);

tb->f(fSignal);

}

private:

// signals

sc_clock clock;

sc_signal<int> qSignal;

sc_signal<bool> fSignal;

sc_signal<bool> resetSignal;

};


int sc_main(int, char *[]) {

// create top module

top top1("Top1");

// create trace file

sc_trace_file *fpTrace;

fpTrace = sc_create_vcd_trace_file("../traces");

sc_trace(fpTrace, top1.tb->clk, "clk");

sc_trace(fpTrace, top1.tb->reset, "reset");

sc_trace(fpTrace, top1.tb->q, "q");

sc_trace(fpTrace, top1.tb->f, "f");


// run simulation

sc_start(80000, SC_NS);

// close trace file

sc_close_vcd_trace_file(fpTrace);

return 0;

}

Die Testbench enthält die 'sc_main()'-Funktion, welche von den jeweils festgelegten Signalen alle Signaländerungen in einer 'trace'-Datei abspeichert. Diese 'trace'-Datei kann anschließend durch ein grafisches Tool für Signaldarstellungen wie z.B. GTKWave visualisiert und in allen Einzelheiten analysiert werden. Die 'sc_main()'-Funktion implementiert für diese Aufgabe lediglich ein 'top'-Modul, welches seinerseits die zu untersuchenden Module sowie ein Testbench-Modul instanziert.

Im 'top'-Modul werden zum Verbinden von zu testendem Modul mit dem Testbench-Modul eine Reihe von Signalen mit dem Template 'sc_signal' deklariert sowie ein 'sc_clock'-Signal, welches die Modellierung der Taktversorgung übernimmt.

Die Testbench ist mit ihren Signalen im wesentlichen das Gegenstück zu den Signalen des zu testenden Moduls. Alle Eingangssignale im zu testenden Modul werden somit durch Ausgangssignale der Testbench angesteuert und umgekehrt. Es müssen alle Signale geeignet verbunden sein bevor die Testbench erfolgreich compiliert und gestartet werden kann.

Ein entscheidender Vorteil der Testbench in SystemC ist deren hohe Simulationsgeschwindigkeit, welche unvergleichlich weit über den Simulatoren in Hardwarebeschreibungssprachen liegt. Die Testbench in SystemC liefert jedoch bedingt durch ihren Ansatz keine auf Gatterlaufzeiten bezogenen Ergebnisse, sondern bleibt in ihrer Aussage auf der Ebene der Taktfrequenz. Es ist damit die implizite Annahme verbunden, dass die Summe aller Gatterlaufzeiten von jeder Operation kleiner als die Taktzeit ist.

Testsignale

Alle von der Testbench erzeugten Signale stehen in der 'trace'-Datei zu einer anschließenden Analyse zur Verfügung. Die folgende Ansicht zeigt ein Testbench-Ergebnis für einen 10:1 Teiler:

Das vollständige VisualStudio-Projekt steht unter folgendem Link zum Download bereit:

FrequencyScaler.zip

Firmware

Nachdem mit der Testbench das erwartete Verhalten von Modulen erzielt wurde, können diese in eine Firmware umgesetzt und in den FPGA geladen werden. Die dafür erforderlichen Werkzeuge hängen vom Typ der Zielhardware (FPGA) ab. Für einen "Xilinx Spartan 3A" wurde dementsprechend das "ISE WebPACK" verwendet.

Dieses Entwicklungstool bietet noch keine direkte Verwendung von SystemC-Sourcen bei der Synthese eines FPGA-Designs an. Es muss daher zunächst eine manuelle Umsetzung von SystemC in VHDL oder einfacher in Verilog erfolgen. Bei den kostenpflichtigen Varianten dieser FPGA-Entwicklungsumgebung können SystemC-Sourcen jedoch zusammen mit VHDL und Verilog Dateien für eine FPGA-Plattform synthetisiert werden. Dabei kommen dann auch die Optimierungen zur Anwendung, welche das Synthesetool für C und SystemC basierte Sourcen anbietet.

Soweit die SystemC-Sourcen auf der Ebene von RTL (Register Transfer Level) bleiben ist es jedoch sehr einfach, diese manuell in Verilog zu konvertieren:

Für den oben dargestellten Frequenzteiler sieht eine Verilog-Implementierung "FrequencyScaler.v" folgendermaßen aus:

module FrequencyScaler(

// ports

input clk, reset,

output f // wave form with requested duty cycle

);

// parameters

parameter max_tick = 10;

parameter duty_cycle = 2;

parameter start_tick = 0;

// register assigned to output ports

reg f_reg;

assign f = f_reg;

// local variable

integer result;

// initialization

initial result = start_tick;

// process

always@(posedge clk)

begin

// reset

if (reset)

result = start_tick;

// output frequency signal

if ((result == 0 || result < duty_cycle) && result != max_tick - 1)

f_reg = 1;

else

f_reg = 0;

// increment result

result = result + 1;

if (result >= max_tick)

result = 0;

end

endmodule

Ein vollständiges Projekt für einen "Spartan 3A" mit dem "ISE WebPACK" kann von folgendem Link erhalten werden:

FrequencyScaler für Spartan-3A

Nach dem Hochladen der Firmware auf den "Spartan-3A" FPGA wird eine Frequenz von ca. 33 Mhz durch 10 geteilt und ergibt auf einem Oszilloskop DS4024 die folgende Ansicht:


Totally registered visits on the WegaLink website.

Started: 1997 by Eckhard Kantz