Entwicklung FPGA-basierender Systeme Die selbsttestende Testbench

Die Kombination eines FPGAs mit Peripheriekomponenten wie A/D-Wandlern, Speichern oder Prozessoren ist sehr häufig anzutreffen. Der Aufwand zur Verifikation solcher komplexen Systeme kann leicht ausufern und dann dementsprechend lange dauern. Der vorliegende Artikel beschreibt ein universelles, in VHDL beschriebenes Mikrocontrollermodell und damit einen zeitsparenden und effektiven Ansatz zur Verifikation solcher komplexen Systeme.

Zur Demonstration des universellen Mikrocontrollermodells kommt der ARM-basierende Mikrocontroller »OMAP-L138« von Texas Instruments zum Einsatz. Ein vereinfachtes Modell der Kombination eines FPGAs mit einem Prozessor sowie weiteren Peripherieeinheiten ist in Bild 1 dargestellt. Dabei steht der Hardwareentwickler vor einer besonderen Aufgabenstellung: Er muss dem Softwareentwickler eine Hardwareplattform zur Verfügung stellen, deren Funktion verifiziert ist.

Beispielsweise muss im vorliegenden Fall sichergestellt sein, dass der Datentransfer von beziehungsweise zum Prozessor und FPGA ordnungsgemäß funktioniert. Weiterhin soll natürlich auch die Kommunikation mit beispielsweise einem externen Analog/Digital-Wandler oder anderen Peripheriebausteinen verifiziert werden. Daraus folgt der Bedarf an einer aussagekräftigen Testumgebung, anhand derer sich die Funktion der Hardware überprüfen lässt. Entsprechend ergeben sich bestimmte Anforderungen an die Testumgebung beziehungsweise die Testbench:

  • Verwendung externer Modelle. Diese Modelle implementieren sowohl die Funktionen als auch das Timing mit entsprechenden Timing-Checks.
  • Modularität, d.h. jedes Modell ist eine eigenständige Komponente, die beliebig oft instantiiert werden kann.
  • Einfache, aber doch flexible Programmierung des Prozessors, um verschiedene Abläufe/Algorithmen implementieren zu können. Idealerweise erzeugt der Prozessor zusätzlich eine Tracedatei, um den Programmablauf/Softwarefluss verifizieren zu können.
  • Jedes Modell erzeugt während des Simulationslaufs eindeutige Log-Dateien, um anschließend die Funktion einfach überprüfen zu können. Daher kann das mühevolle Analysieren einer in der Regel komplexen Wellenform enorm verkürzt werden oder ganz entfallen.

Berücksichtigt man diese Anforderungen, so stellt sich die Testumgebung aus Bild 1 wie in Bild 2 gezeigt dar. Einem ungeübten Anwender mag der Aufwand einer solchen Testbench als sehr aufwendig erscheinen. Da die Hardware-Beschreibungssprachen (HDL) diese Art von Testumgebungen weitgehend unterstützen, ist der tatsächliche Aufwand zur Erstellung externer Modelle und damit der Testbench häufig sehr gering, zumindest aber überschaubar. Gemäß der Darstellung in Bild 2 lassen sich die Anforderungen an das Prozessormodell wie folgt formulieren:

  • Flexibles Programmieren mit einer BFL (Bus Functional Language). Bereitstellung eines Befehlssatzes mit logischen und arithmetischen Funktionen. Weiterhin Befehle zur Steuerung des Programmablaufs wie zum Beispiel bedingte oder unbedingte Call/Return- oder Jump-Befehle.
  • Erzeugen einer Tracedatei zur Kontrolle des Programmflusses.
  • Verarbeiten von wahlweise flankengesteuerten oder pegelgesteuerten Interrupts und Ausführen von entsprechenden Interrupt-Serviceroutinen (ISR).
  • Flexibles Einstellen und Überprüfen der Timing-Parameter wie zum Beispiel Pulsbreiten sowie Setup- und Hold-Zeiten.
  • Einfache Modifikation des Modells zur Anpassung an andere, unter Umständen gänzlich unterschiedliche Prozessoren.
Bilder: 5

Die selbsttestende Testbench

Entwicklung FPGA-basierender Systeme

Das hier beschriebene universelle Modell eines Mikrocontrollers besitzt die in Bild 3 gezeigte Struktur. Das Modell hat ein internes Statusregister mit den Flags Overflow, Carry und Negativ sowie 15 frei verwendbare Register R0 bis RF und besitzt zusätzlich einen parametrisierbaren Interrupt-Controller. Dieser lässt sich wahlweise entweder als flankengesteuert oder pegelgesteuert mit beliebiger Polarität (high- oder low-aktiv) konfigurieren. Die in Bild 3 gezeigte Struktur des Modells wird durch verschiedene VHDL-Packages konfiguriert, die im Referenzdesign dokumentiert sind.

Der Befehlssatz

Der Befehlssatz wurde so gewählt, dass sich viele der gängigen Softwarestrukturen einfach beschreiben lassen. Als Syntax kommt hier eine allgemeine Assemblersyntax zum Einsatz. Damit können auch ungeübte Anwender den Prozessor programmieren, und das unabhängig von der eigentlichen späteren Programmierung wie zum Beispiel in C.

Die Kommandos sind in den Kästen »I/O-Befehle«, »Registerbefehle« und »Verzweigungsbefehle« aufgelistet.

I/O-Befehle    
Diese Befehle dienen zum Datentransfer von/zu Peripherikomponenten, die am Prozessorbus angschlossen sind.   

MOVE #aaa,#hhh; schreibt den hexadezimalen Wert hhhh auf die hexadezimale Adresse aaaa.
MOVE #aaaa,Rn; schreibt den im Register Rn gespeicherten Wert auf die hexadezimale Adresse aaaa.
MOVE (Rx),Rn; schreibt den im Register Rn gespeicherten Wert auf die im Rx gespeicherte Adresse.
MOVE Rn,(Rx); liest Wert aus der hexadezimalen Adresse in Rx und schreibt diesen in das Register Rn.
I/O-Befehle
Registerbefehle    
Diese Befehle dienen zum Laden/Modifizieren der Registerinhalte. Die Registerbefehle selbst sind in logische und arithmetische Befehle unterteilt.    

MOVE Rn,#hhhh; schreibt den hexadezimalen Wert hhhh in Register Rn, also +hhhh -> Rn
   MOVE Rx,Rn; schreibt den Wert von Register Rn in Register Rx, also Rn -> Rx
AND Rx,Rn; bitweise AND-Verknüpfung von Rx und Rn und Schreiben des Ergebnisses in Rx, also Rx and Rn -> Rx
 
OR Rx,Rn; bitweise OR-Verknüpfung und Rx und Rn und Schreiben des Ergebnisses in Rx, also Rx or Rn -> Rx
 
XOR Rx,Rn; bitweise XOR-Verknüpfung von Rx und Rn und Schreiben des Ergebnisses in Rx, also Rx xor Rn -> Rx
 
ADD Rx,Rn; addiert Werte aus Rn und Rx und schreibt das Ergebnis in Rx, also Rn + Rx -> Rx. Bei der Addition werden die Werte als Integerwerte im Bereich von -32768 bis +32567 interpretiert.
 
SUB Rx,Rn; subtrahiert Werte aus Rx und Rn und schreibt das Ergebnis in Rx. Rx - Rn -> Rx. Bei der Subtraktion werden die Werte als Integerwerte im Bereich von -32768 bis +32567 interpretiert.
CMP Rx,Rn; vergleicht Werte aus Rx und Rn.
Registerbefehle
Verzweigungsbefehle    
Diese Befehle dienen zur Definition der Programmstruktur selbst. Der Anwender kann zwischen bedingten und unbedingten Verzweigungen wählen. Zur Implementierung von Unterfunktionen sind zusätzlich bedingte bzw. unbedingte Call/Return-Befehle vorhanden.   
 
CALL LABEL; Unterprogrammaufruf der durch LABEL definierten Subroutine.
 
CALLZ LABEL; Unterprogrammaufruf der Subroutine LABEL, wenn ZERO FLAG gesetzt ist.
 
CALLNZ LABEL; Unterprogrammaufruf der Subroutine LABEL, wenn ZERO FLAG nicht gesetzt ist.
JUMP LABEL; unbedingter Sprung zum Programmlabel LABEL.
 
JUMPZ LABEL; Sprung zum Programmlabel LABEL, wenn ZERO FLAG gesetzt.
 
JUMPNZ LABEL; Sprung zum Programmlabel LABEL, wenn ZERO FLAG nicht gesetzt.
JUMPOV LABEL; Sprung zum Programmlabel LABEL, wenn OVERFLOW FLAG gesetzt.
 
JUMPNG LABEL; Sprung zum Programmlabel LABEL, wenn NEGATIV FLAG gesetzt.
 
RETURN; Rücksprung aus der Subroutine aufgerufen mit CALLxx.
 
RETURNI; Rücksprung aus der Interrupt Routine.
Verzweigungsbefehle