Suchen

Die Suche nach des Pudels Kern Hilfsmittel und Werkzeuge für das Multicore-Debugging

Autor / Redakteur: Jens Braunes * / Sebastian Gerstl

Reichte im Consumer-Markt oft das Aufspüren falsch geschriebener Variablen, ist das Debugging industrieller Multicore-Systeme weitaus komplizierter. Vom Hersteller gelieferte Hilfestellung entfalten oft nur in Kombination mit leistungsstarken Tools volle Wirkung. Ein Beitrag über derzeitige Grenzen und Möglichkeiten verschiedener Lösungsansätze.

Firmen zum Thema

Blockschaltbild der AURIX Multicore-Architektur von Infineon: Ein Beispiel für ein heterogenes Multicore-System, bei dem verschiedene, speziell auf eine Aufgabe zugeschnittene Kerne zum Einsatz kommen. Komplexe Anwendungen aus Industrie- oder Automotive-Bereichen stellen spezielle Anforderungen an Multicore-Architekturen, die sich sich wesentlich vom aus der Windows-, Linux- und Android-Welt bekannten homogenen Multicore-Ansatz unterscheiden.
Blockschaltbild der AURIX Multicore-Architektur von Infineon: Ein Beispiel für ein heterogenes Multicore-System, bei dem verschiedene, speziell auf eine Aufgabe zugeschnittene Kerne zum Einsatz kommen. Komplexe Anwendungen aus Industrie- oder Automotive-Bereichen stellen spezielle Anforderungen an Multicore-Architekturen, die sich sich wesentlich vom aus der Windows-, Linux- und Android-Welt bekannten homogenen Multicore-Ansatz unterscheiden.
(Bild: PLS)

Beim Debugging industrieller Multicore-Systeme geht es nicht mehr nur um das Aufspüren falsch geschriebener Variablen, sondern um die Bewältigung von Verklemmungen, Ressourcenkonflikten oder Timing-Problemen in Echtzeitapplikationen. Dieser Paradigmenwandel stellt für Chiphersteller und Toolanbieter eine gewaltige Herausforderung dar, zumal die heutzutage üblicherweise verfügbaren On-Chip-Debug-Funktionen erst mit entsprechend leistungsfähigen externen Werkzeugen ihre volle Wirksamkeit entfalten.

Wenn man nur den Consumer-Markt betrachtet, sind Multicore-Systeme schon seit zehn Jahren im Mainstream angekommen. Bei den tief eingebetteten Systemen, wie wir sie vor allem in Industrieanwendungen oder bei Automotive-Steuerungen finden, wurde der Technologiewechsel hingegen erst in den letzten Jahren vollzogen, und das auch recht zögerlich. Ein Grund dafür waren sicherlich die hohen Anforderungen an Sicherheit, Zuverlässigkeit und Echtzeit, die in den genannten Bereichen nun einmal absoluten Vorrang genießen.

Aber auch das umfangreiche Portfolio an bewährten und gut getesteten Softwaremodulen für Singlecore-Systeme, deren Portierung auf mehrere heterogene Kerne einen nicht zu unterschätzenden Aufwand bedeutete, bremste hier sicherlich einen schnelleren Vormarsch aus.

Multicore ist nicht gleich Multicore

Beim aus der Windows-, Linux- und Android-Welt bekannten homogenen Multicore-Ansatz können Tasks und Prozesse dynamisch erzeugt werden, die dann je nach Last auf einem beliebigen Kern zur Ausführung kommen. Das ist möglich, weil die verwendeten Prozessoren über identische Rechenkerne verfügen. Jeder Kern ist in der Lage und auch geeignet, zugewiesene Tasks gleichermaßen auszuführen.

Komplexe vernetzte Industrial- und Automotive-Anwendungen erfordern allerdings in der Regel festgelegte Abarbeitungszeiten für einzelne Tasks und garantierte Antwortzeiten. Deshalb kommen hier zumeist heterogene Multicore-Systemen mit mehreren speziell auf eine bestimmte Aufgabe zugeschnittenen Kernen zum Einsatz., wie das Beispiel der AURIX-Mikrocontroller aus dem Hause Infineon verdeutlicht (siehe Bild oben). Zwar stammen die drei Hauptkerne allesamt aus der TriCore-Architekturfamilie, jedoch sind nur zwei davon als sogenannte Performancekerne (P-Cores) für normale rechenintensive Aufgaben vorgesehen. Die dritte, als Economy-Kern (E-Core) bezeichnete Zentraleinheit übernimmt vorrangig die Verwaltung der Peripherals und allgemeine, weniger Rechenleistung erforderliche Aufgaben.

Für sicherheitskritische Tasks wurde einer der P-Cores und der E-Core mit einem zusätzlichen speziellen Lockstep-Core ausgestattet, der im Hintergrund die gleichen Operationen wie der eigentliche Kern ausführt. Mittels Vergleich der Ergebnisse wird die Zuverlässigkeit der Berechnungen so ständig kontrolliert. Tritt eine Abweichung auf, muss die Applikation, das Steuergerät, und/oder das sicherheitskritische System gegebenenfalls in einen sicheren Zustand zurückversetzt werden.

Komplexe Zeitsteueralgorithmen sowie die effiziente und parallele Verarbeitung von Signalen unterstützt ein vierter „Kern“, das sogenannte Generic Timer Module (GTM). Völlig andersartig als die TriCore-Kerne, ist das GTM dennoch über einen eigenen Befehlssatz programmierbar. Dadurch können auch Tasks darauf ausgeführt werden.

Bei so einem komplexen System sollte man hinsichtlich der Verteilung der Applikationslasten auf die Rechenkerne logiscehrweise nicht allein auf das Betriebssystem vertrauen. Vielmehr muss bereits während des Softwareentwurfs klar sein, welcher Kern welche Aufgaben zu übernehmen hat.

Tief eingebettetes Debugging

ARM Core-Sight Debug- und Trace-Architektur mit Cross-Trigger-Einheiten: Moderne Multicore-Architekturen liefern bereits auf dem Chip selbst hardwarenahe Möglichkeiten zum Debugging mit.
ARM Core-Sight Debug- und Trace-Architektur mit Cross-Trigger-Einheiten: Moderne Multicore-Architekturen liefern bereits auf dem Chip selbst hardwarenahe Möglichkeiten zum Debugging mit.
(Bild: PLS)

Vor allem Applikationen, die ihre einzelnen Aufgaben mit hohen Echtzeitanforderungen und verteilt auf unterschiedliche Rechenkerne abarbeiten, stellen für Debugging, Test und Systemanalyse oftmals eine große Herausforderung dar. So wirken sich die im Normalfall zumeist ziemlich großen Abhängigkeiten zwischen den auf unterschiedlichen Kernen ausgeführten Tasks natürlich auf das Stop-Go-Debugging aus. Man kann nicht einfach unbedacht einen Kern anhalten, während alle anderen weiterlaufen.

Mitunter müssen auch die restlichen Kerne und die Peripherals gleichzeitig angehalten werden, damit die Applikation nicht gänzlich außer Trittin einen undefinierten Zustand gerät. Dumm nur, dass bei heterogenen Kernen mit unterschiedlicher Taktung und Ausführungs-Pipelines ein gleichzeitiges Anhalten gar nicht so einfach ist. Deshalb wird es in der Praxis immer einen gewissen Zeitversatz geben, mit dem man als Entwickler leben muss. Manchmal kann das Anhalten eines kompletten Multicore-Systems sogar fatale Folgen haben, zum Beispiel, wenn parallel noch andere Anwendungen laufen, die zu diesen Zeitpunkt nicht debuggt werden sollen oder dürfen. Die genannten Anwendungsszenarien lassen erahnen, wie wichtig ein flexibles, synchrones Run-Control für die Multicore-Debug-Infrastruktur ist.

Ein zweiter, bedeutsamer Aspekt ist die Analyse des Laufzeitverhaltens, und zwar ohne dabei selbiges zu beeinflussen. Diese nicht-intrusive Systembeobachtung spielt nicht nur bei echtzeitkritischen Anwendungen, sondern auch bei Profiling-Aufgaben oder der Kommunikationsbeobachtung zwischen den Kernen eine wichtige Rolle. Oftmals ist es wünschenswert, den jeweiligen Systemzustand zu einem bestimmten Zeitpunkt mit Hilfe des extern angeschlossenen Debuggers aus dem Target-System auslesen zu können. Ein Anhalten der Applikation würde das Systemverhalten unter Umständen allerdings so grundlegend verändern, dass es nichts mehr mit dem Zustand ohne angeschlossenen Debugger zu tun hätte. Daraus folgt: Für eine effiziente nicht-intrusive Systembeobachtung ist Tracen unerlässlich.

On-Chip Debugger

Doch zuerst noch einmal zurück zum Thema synchrones Run-Control. Hierfür sind schnelle Signalwege zwischen den Kernen not¬wendig, die sich nur mit Debug-Hardware direkt auf dem Chip realisieren lassen. Stop- und Go-Signale von extern über die Debug-Schnittstelle zu übermitteln, würde bei den heutzutage üblichen hohen Taktfrequenzen viel zu lange dauern. Die Applikation käme unweigerlich außer Tritt.

Nun bietet jeder Chip-Hersteller erfahrungsgemäß seine eigene On-Chip Debug-Lösung an. Infineon beispielsweise nennt sie OCDS. Ein wichtiger Bestandteil dieses OCDS ist der implementierte Trigger-Switch, der Halt- und Suspend-Signale einzeln konfigurierbar systemweit verteilt. Der Trigger-Switch erlaubt es, einzelne Rechenkerne und Periphiereieeinheiten ohne Beeinfussung der restlichen Funktionsgruppen ganz gezielt gleichzeitig anzuhalten bzw. wieder zu starten. Zusätzlich können einzelne Trigger-Leitungen des Trigger-Switches auch über Pins nach außen geführt werden. Die ermöglicht interessante Optionen wie beispielsweise den Anschluss eines Oszilloskops oder die externe Auslösung eines Breaks.

Neben der AURIX-Familie von Infineon gibt es natürlich noch eine Vielzahl anderer Multicore-Mikrocontroller, die den Industrial- und Automotive Sektor abdecken, darunter beispielsweise SoCs auf Basis der ARM-Cortex-R-Architektur oder Freescales MPC57xx-Familie. Werfen wir als erstes einen Blick auf den CoreSight-Baukasten [1] von ARM.

Für die Verteilung der Break- und Go-Signale zwischen den Kernen wird eine sogenannte Cross-Trigger-Matrix (CTM) mit daran angeschlossenen Cross-Trigger-Interfaces (CTI) genutzt (siehe Bild). Kanäle in der CTM leiten die Signale im Broadcast-Modus an die angeschlossenen CTIs weiter. Diese sind wiederum direkt mit den Kernen verbunden und so konfigurierbar, dass sie die Signale für das Run-Control zwischen Kern und CTM wahlweise entweder weiterleiten oder blockieren. Aufgrund der notwendigen Handshake-Mechanismen zwischen den beteiligten Komponenten kommt es dabei üblicherweise zu Signalverzögerungen von mehreren Takten. Wie groß diese Verzögerungen tatsächlich sind, hängt von der jeweiligen Implementierung und natürlich der Taktung der einzelnen Komponenten ab.

Komplett vermeiden lässt sich der beim synchronen Anhalten entstehende Schlupf von einigen wenigen, typischerweise im einstelligen Bereich angesiedelten Befehlen jedoch auch durch die CTM nicht. Außerdem ist es dem jeweiligen Chip-Hersteller seitens ARM freigestellt, ob er die notwendigen CoreSight-Komponenten überhaupt auf dem Chip implementiert.

Auch von den Power-Architecture-basierten Controllern der MPC57xx-Familie wird das synchrone Run-Control bereits hardwareseitig unterstützt. Die dafür verantwortliche Einheit heißt DCI (Debug and Calibration Interface). Der Vorteil gegenüber der ARM-Lösung: Wie beim Trigger-Switch des AURIX sind auch beim DFI die Peripherieeinheiten gleich mit angeschlossen, was ein Anhalten des gesamten Systems und nicht nur der Kerne erlaubt.

Multi-Core Run Control Manger: Die UDE ermöglicht eine Konfiguration des synchronen Haltens und Startens für einzelne Cores oder Core-Gruppen.
Multi-Core Run Control Manger: Die UDE ermöglicht eine Konfiguration des synchronen Haltens und Startens für einzelne Cores oder Core-Gruppen.
(Bild: PLS)

Entwicklern wäre es erfahrungsgemäß freilich am liebsten, wenn sie sich mit solchen unterschiedlichen Gesichtspunkten erst gar nicht im Detail auseinandersetzten müssten. Deshalb kommen für das Debugging von Multicore-Systemen auch zunehmend stark visualisierende Werkzeuge wie beispielsweise die Universal Debug Engine (UDE) von PLS zum Einsatz, bei denen die unnötigen Details bezüglich der Konfiguration des synchronen Run-Controls durch den Debugger verborgen bleiben. Mit Hilfe des integrierten Multi-Core-Run-Controllers der UDE lassen sich nicht nur schnell und einfach beliebige Run-Control-Gruppen definieren (siehe Bild) – die zugehörigen Kerne können auch synchron angehalten und wieder gestartet werden.

Spuren mit Trace verfolgen

Gerade wenn es um Echtzeitanwendungen geht, gibt es neben dem synchronen Run-Control noch eine weitere wichtige Grundvoraussetzung für eine genaue und zuverlässige Systemanalyse: Den On-Chip-Trace. Natürlich steht diese Technologie auch bei den oben genannten Multicore-Controllern zur Verfügung. Freescale beispielsweise setzt für seine MPC57xx-Familie auf Nexus [2], Infineon nutzt dafür die bereits in anderem Zusammenhang erwähnte Multi-Core-Debug Solution (MCDS) und ARM das bereits bekannte CoreSight. Allen gemein ist die Möglichkeit, Trace für mehrere Kerne parallel aufzuzeichnen, bei der MCDS ist dies allerdings auf maximal zwei auswählbare Kerne limitiert. Zeitstempel erlauben die zeitliche Zuordnung der Trace-Daten, um die genaue Abfolge von Ereignissen zu rekonstruieren. Damit lassen sich zum einen Verklemmungen und Race-Conditions aufspüren, zum anderen aber auch Flaschenhälse in der Kommunikation.

Eine große Herausforderung besteht nun darin, die aufgezeichneten Trace-Daten zum Debugger zu übertragen, der dann die weitere Analyse vornimmt. Entweder werden diese auf dem Chip in einem Trace-Buffer zwischengespeichert und per Debug-Schnittstelle ausgelesen, oder aber über ein breitbandiges Interface während der Aufzeichnung übertragen. Ersteres bietet natürlich eine viel höhere Bandbreite, aber nur eine sehr begrenzte Speicherkapazität. Letzteres erlaubt zwar eine theoretisch unbegrenzte Beobachtungsdauer, dafür kann es aber häufiger zu Überläufen kommen, wenn mehr Trace-Daten anfallen, als übertragen werden können. In beiden Fällen schaffen ausgeklügelte Filter- und Trigger-Mechanismen Abhilfe, welche die Menge der Trace-Daten einschränken.

Auch sogenanntes Cross-Triggering ist damit möglich. Damit lässt sich beispielsweise der Trace für einen Kern starten, wenn eine Bedingung für einen anderen Kern wahr wird. Hilfreich ist diese Funktion beispielsweise, um Kommunikationen zwischen den Kernen zu debuggen. Bei der MCDS und bei CoreSight zählt das Cross-Triggering zu den Standardfunktionen. Allerdings konkurriert das Cross-Triggering bei CoreSight mit dem synchronen Run-Control, da beide die gleichen Hardwareressourcen verwenden. Freescale hingegen musste seine Nexus-Implementierung extra um eine proprietäre Einheit, die sogenannte Sequence Processing Unit (SPU) erweitern, da im Nexus-Standard Cross-Triggering nicht vorgesehen ist.

Auch bei der zielgerichteten Beobachtung des Systemverhaltens mittels Trace sowie der anschließenden Auswertung und Analyse sind die Fähigkeiten des Debuggers gefragt. Für die Erstellung von Trace-Aufgaben stellt die UDE beispielsweise ein grafisches Werkzeug zur Verfügung, mit dem sich selbst komplexe Cross-Trigger recht einfach konfigurieren lassen. Das Ganze funktioniert für verschiedenste On-Chip-Trace-Systeme, ohne dass sich der Anwender um die technischen Details kümmern muss. Verschiedene Ansichten, die beispielsweise die parallele Ausführung von Code auf mehreren Kernen visualisieren, erleichtern die Trace-Auswertung. Falls gewünscht oder benötigt, lassen sich mit Hilfe des Debuggers auch Profiling-Informationen gewinnen oder Code-Coverages bestimmen. Diese beiden Optionen sind jedoch nicht Multicore-spezifisch, sondern vielmehr für die Systemanalyse und –optimierung von allgemeinem Nutzen.

Fazit

Ohne die Unterstützung durch geeignete Hardware auf dem Chip wäre das Multi-core-Debugging für tief eingebettete Systeme nur sehr mühsam zu bewerkstelligen. Auch ein moderner Debugger stößt unweigerlich an seine Grenzen, wenn es um synchrones Anhalten und Starten von mehreren Kernen geht. Wirklich synchrones Run-Control wird sogar überhaupt erst durch geeignete On-Chip-Debug-Hardware mit konfigurierbaren Cross-Triggern möglich. Ähnliches gilt für die umfassende Systemanalyse von Multicore-Applikationen. Ohne On-Chip-Trace sind auch hier die Grenzen des Machbaren schon schnell erreicht. Die gute Nachricht: Auch wenn die Chiphersteller bzgl. des On-Chip-Debugging keinem einheitlichen Standard folgen, kommen moderne Debugger damit durchaus gut damit zurecht. Und moderne Debugger wie die UDE erlauben dem Entwickler zudem eine einfache Nutzung der Funktionen, so dass er sich nur selten mit den Chip-spezifischen Besonderheiten beschäftigen muss.

Literaturhinweise:

[1] ARM Ltd: CoreSight Debug and Trace; http://www.arm.com/products/system-ip/debug-trace/

[2] Nexus 5001 Forum: IEEE-ISTO 5001-2012, The Nexus 5001 Forum Standard for a Global Embedded Processor Debug Interface; http://nexus5001.org

* Jens Braunes ist Software-Architekt bei der PLS Programmierbare Logik & Systeme GmbH in Lauta. Sein Fokus liegt dabei auf dem Design von Softwarekonzepten zur Anwendung von On-Chip-Emulatoren.

(ID:43763028)