Speicherorte: Die „Segmente“ im RAM

Grundstruktur Datenablage
Ein ausführbares Programm (*.exe) teilt seine Daten in vier Segmente ein: *.code, *.data, *, heap und Stack.

code_data_heap code_data_heap_chinese

Codesegment
Liegt bei der tiefsten Adresse. Hier liegt der Maschinencode. Häufig liegen hier auch die Konstanten. Das Codesegment kann mit einem Schreibschutz versehen werden, also nur gelesen werden.

Datensegment
Hier liegen die initialisierten globalen Variablen sowie wenn eine lokale Variable als static definiert wurde. Sind Variablen nicht initialisiert, so liegen sie im BSS Segment.

Heap
Enthält nur dynamische Variablen, die mit malloc() zur Laufzeit generiert und wieder gelöscht werden.

Stack
Legt alle sich ändernden Daten zur Laufzeit ab und holt sie permanent wieder. Ständiges Wechseln der Daten. Deshalb braucht es einen Stack Pointer, der jeweils die aktuelle Speicherstelle weiss.
Der Stack liegt an der höchsten Adresse.
Im Stack liegen alle lokalen Variablen.

 

 

Pointers in C

Durch das Setzen eines Asterisk vor einer Variable, definiert man, dass die Variable keinen Wert, sondern eine Adresse beinhaltet:

  • Zeigen auf eine Speicheradresse
  • Sie beinhalten eine Adresse
    <type> *<name>, *<name>;

    Adresse übergeben (referenzieren)

    int *pointer;
    pointer = &variable_a;
    

    Die Variable_a wird gelesen, aber nur ihre Adresse wird übergeben.

    Wert übergeben (dereferenzieren, per value)

    *pointer = 10;
       // oder
    var1 = *pointer_b;
    printf(" %d ",  *pointer)

    Der Wert wird ebenfalls über einen Stern zurückgegeben. Das ist verwirrlich, zeigt aber auch, dass es ein Wert von einem Pointer ist…

    —————————————————————————–

    Pointer a

    int *a;
    int b;

    Wohin zeigt a ?

    b = &a;  // b hat die Adresse

    a soll einen Wert erhalten

    *a = 10;

Keyword static in C

static lokale Variable
– Ziel:
Die Variable behält ihren Wert zwischen zwei lokalen Funktionsaufrufen.
Die Initialisierung gilt nur beim ersten Mal (Ablegen auf dem Datensegment).
– Sichtbarkeit:
Der Gültigkeitsbereich bleibt gleich. Der Ablageort der Variable ist auf dem Datensegment anstelle des Stacks.
– Bsp: Zählen von Interrupts

<type> function( ){
.       static int nr_of_calls = 0;
.
.       // do some code
.
       nr_of_calls ++;
.      return <type>;
}

 

static globale Variable
– Ziel:
Wie bei der lokalen statischen Variable. Zwischen den Aufrufen behält die Variable den Wert. Wenn sie global ist, dann behält sie den Wert während der ganzen Programmdauer.
– Sichtbarkeit:
Eine externe Variable mit static ist ausserhalb der Datei unsichtbar!

 

static Funktion
– Sichtbarkeit:
Wird eine Funktion extern und static gesetzt, dann ist sie ausserhalb der Datei nicht sichtbar.

Unit Testing with Catch

Es gibt CATCH, das Testing für C++ ermöglicht. Es gibt einen Blogeintrag, der den Einsatz von CATCH auf hohem Niveau beschreibt. Zum Starten ist das Tutorial sehr gut.

#define CATCH_CONFIG_MAIN  // This tells Catch to provide a main() - only do this in one cpp file
#include "catch.hpp"

unsigned int Factorial( unsigned int number ) {
    return number <= 1 ? number : Factorial(number-1)*number;
}

TEST_CASE( "Factorials are computed", "[factorial]" ) {
    REQUIRE( Factorial(1) == 1 );
    REQUIRE( Factorial(2) == 2 );
    REQUIRE( Factorial(3) == 6 );
    REQUIRE( Factorial(10) == 3628800 );
}

Catch innerhalb eines Files einbauen und ausführen

1. Catch als Header in File einbinden

#define CATCH_CONFIG_RUNNER  
#include "catch.hpp"

2. Funktion, die getetst werden will

double calculate(string calculation) {
    double result = 0.0;
    ts.set(calculation);
    result = expression();
    return result;
}

3. Testfälle aufstellen

TEST_CASE( "r", "[digit]" ) {
    REQUIRE(calculate("4") == 4);
    REQUIRE(calculate("6") == 6);
    REQUIRE(calculate("4+2") == 6);
    REQUIRE(calculate("4+2+2") == 8);
    REQUIRE(calculate("4+2+2+4+2+2+1") == 17);
    REQUIRE(calculate("4-2") == 2);
    REQUIRE(calculate("8-2") == 6);
    REQUIRE(calculate("8-2-2") == 4);
    REQUIRE(calculate("3*2") == 6);
    REQUIRE(calculate("3*3") == 9);
    REQUIRE(calculate("3*2*3") == 18);
    REQUIRE(calculate("3+2*4") == 11);
    REQUIRE(calculate("3*2+4") == 10);
    REQUIRE(calculate("4 + 2") == 6);
    REQUIRE(calculate("10 + 2") == 12);
    REQUIRE(calculate("10 + 123") == 133);
    REQUIRE(calculate("-4 + 5") == 1);
    REQUIRE(calculate("-100 + 5") == -95);
    REQUIRE(calculate("(4 + 5)*2") == 18);
    REQUIRE(calculate("3+(-4 * 5)") == -17);
    REQUIRE(calculate("1.5 * 2") == 3);
}

Am Ende des Files folgend die Testfälle.

4. Datei kompilieren
5. Ausführen über Konsole und Argumente mitgeben
Gibt man keine Argumente mit, werden alle Tests durchgelauften.

Struktur der Testfälle

REQUIRE(calculate("6") == 6);

Nach REQUIRE schreibt man die Funktion, die aufgerufen werden soll und gibt ihr die notwendigen Werte mit.
Nach dem == folgt das zu erwartende Ergebnis.

Basic Error-Library

#include <exception>

Der Standardteil dieser Bibliothek umfasst folgende Exceptions:
Logische Fehler                                 Runtime Fehler
– logic_error                                         – runtime_error
– domain_error                                  – range_error
– invalid_argument                          – overflow_error
– length_error                                    – underflow_error
– out_of_range

In der Funktion

void Token_stream::put_back(Token t) {
    if (full) {
        throw std::overflow_error("buffer already full");
    }
    full = true;
    buffer = t;
}

 

 

own Error-Class (Basics)

class neg_sum{};

int main(){
  try {
.     ...
.     
.     ...
  } catch(neg_sum const & e){
.     std::cout << "negative value"<<"\n";
  }   
}

In der Funktion

double solve_quadratic(int a, int b, int c) {
    double delta = pow(b, 2) - (4 * a * c);
    if (delta < 0) {
        throw Negative_delta{};
    }
.   .....
}

 

VHDL Code von Speicher starten

Datei.pof
Der Speicher braucht ein bistream als Synthese.  Deshalb muss neben der *.sof-Datei auch eine *.pof-Datei erzeugt werden.

Grundeinstellung ändern
Bei der Synthese, soll gleich auch die *.pof-Datei generiert werden.
Assignments/Device     Taste: Device and Pin Options    /Configuration
Auswählen von:           Active Serial
Hacken bei:                     v   Use configuration device:       Auto (oder auswählen)
.                                            v   Generate compressed bitstream


Konvertieren *.sof zu *.pof

File/Convert Programming Files…/    Folgende EinstellungeProgramming file type:        Programmer Object File (.pof)
Configuration device:           (siehe Namen auf Board)
File name:                                  <pfad>/name.pof
Input files to convert:            – Anklicken SOF Data
.                                                      – Add File..
.                                                      – outputfile/datei.sof  aus Ordner Synthese
Generate

Programmer Einstellungen
Mode:                                          Active Serial Programming
File:                                              *.sof löschen
.                                                      *.pof adden
Device:                                          <name des Speichers> ! nicht des FPGA

Auf Board
!!!!! Schalter auf Programm !!! nur so entsteht Verbindung zu Speicher.

CUDA Speicher-Verwaltung

Über Pointers kann von einem Speicher (CPU), auf den anderen (GPU) zugewiesen werden.

  • Device-Pointer zeigen auf Speicher im GPU
    – Kann im host-code gebraucht, aber nicht dereferenziert werden
  • Host-Pointer zeigen auf den Speicher der CPU. Kann vom GPU Code gebraucht, aber nicht dereferenziert werden.

    Funktionen
    cudaMalloc( Wert, Grösse);
    cudaFree();
    cudaMemcpy();

Bsp. aus der Einführung

int main( void ) {
int a, b, c;                  // host: a, b, c
int*dev_a, *dev_b, *dev_c;    // device copies of a, b, c
intsize =sizeof(int);         

// allocate device copies of a, b, c
cudaMalloc( (void**)&dev_a, size );
cudaMalloc( (void**)&dev_b, size );
cudaMalloc( (void**)&dev_c, size );

a = 2;
b = 7;

// copy inputs to device
cudaMemcpy(dev_a, &a, size,cudaMemcpyHostToDevice);
cudaMemcpy(dev_b, &b, size,cudaMemcpyHostToDevice);

// launch add() kernel on GPU, passing parameters
add<<< 1, 1 >>>(dev_a,dev_b,dev_c);

// copy device result back to host copy of c
cudaMemcpy( &c,dev_c, size,cudaMemcpyDeviceToHost);

cudaFree(dev_a);
cudaFree(dev_b);
cudaFree(dev_c);
return 0;
}

 

CUDA Introduction: Sprache

Cuda ist von Nvidia eine Sprache wie auch Architektur zur GPU-Verarbeitung. Die Sprache wird im Buch CUDA_by_Example. ausführlich beschrieben. Einen ersten Üblick gibt folgender Vortrag, der vom Autor desselben Buches ebd. über Cuda_Introduction gegeben wurde.

Kompiler
nvcc

Trennung: Ablage in GPU – CPU
Der nvcc-Kompiler trennt den Code in CPU-Speicher und GPU-Speicher.
– Device-Funktionen werden im kernel()  bearbeitet und im GPU-Speicher abgelegt.
– Normale Funktionen werden im der CPU (host) im main()  bearbeitet und ihre Variablen liegen im Speicher der CPU. Kompiliert wird mit  gcc.

int main( void) {
    kernel <<< 1, 1 >>> ();
    printf("hallo \n");
    return 0;
}


Von CPU auf GPU

__global__ :             cuda Schlüssewort für Funktionen die auf GPU laufen,
.                                    Diese Funktionen müssen  von CPU – im main() – aufgerufen
.                                    werden
<funktion> <<< :     Befehl zum Ausführen der Funktion auf der GPU
>>> <parameter> :   Mitgabe der Argumente für die GPU-Funktion.

Bsp.

__global__ void kernel( void ) { 
} 

__global__ void add (int *a, int *b, int *c)
{
 *c = *a + *b;
}

Dies funktioniert nur, wenn man im main() die Funktion auf die GPU verschiebt.

int main( void) {
    int a, b, c;
    int *dev_a, *dev_b, *dev_c;

    // Funktion auf GPU   // Argumente mitgeben
    add <<< 1, 1 >>> (dev_a, dev_b, dev_c);
    printf("hallo \n");
    return 0;
}

CPU (host) ruft GPU (device) auf. Ist notwendig, um eine Funktion auf der GPU aufzurufen.