intel 8049 / MCS-48 Hardware-Debugger & Monitor
Die DeMon48 Monitor-Software für MCS-48 funktioniert stark vereinfacht folgendermaßen :
Monitor AufrufDer 8049-µC ist jetzt gestoppt, im Windows-GUI können durch den Anwender Inhalte von PC, A, PSW, interne Registern, internes/externes RAM, Ports etc. geändert werden. Wird durch den Anwender das Ausführen des nächsten, durch den "Program Counter" adressierten Befehls durch Klick auf die entsprechende Schaltfläche ausgelöst, werden die folgenden Schritte ausgeführt :
Monitor-Ausgang(Der Umgang mit Monitor-Aufruf und Monitor-Ausgang wird durch die Firmware möglichst intelligent gehandhabt : Wird bspw. die Ausführung eines Einzelschritts angefordert ohne dass der Monitor aktiv ist, wird zunächst nur der Monitor-Aufruf ausgeführt - erst bei einer erneuten Einzelschritt-Anforderung wird der Einzelschritt dann ausgeführt.)
Durch Zusammenspiel zwischen externem RAM, Debugger-Firmware, und kleinen MCS-48-Programmen, die dynamisch durch den Debug-Mikrocontroller geladen und unter dessen Kontrolle vom 8049 ausgeführt werden, wurde es möglich, eine Monitorsoftware zu entwickeln, die tatsächlich keine On-Chip-Ressource des 8049 blockiert oder nach Verlassen der Monitorsoftware in einem geänderten Zustand zurücklässt.
Der Transport der Inhalte der internen Register nach Aussen kann nur mit "MOVX @Ri,A"-Befehlen durchgeführt werden, aber beim Aufruf des Monitors sind die Inhalte der Indexregister (Ri) R0 in Registerbank 0, R1 in Registerbank 0, R0 in Registerbank 1, R1 in Registerbank 1, des PSW ("Program Status Word") mit dem BS ("Bank Select")-Bit, des PC ("Program Counter") sowie von A ("Accumulator") unbekannt und dürfen vor ihrer Rettung auch nicht verändert werden.
Während der Monitor aktiv ist und Monitor-Routinen durch den 8049 ausgeführt werden, hat der 8049, wenn überhaupt, nur über die unteren 8 Adressleitungen A0 bis A7 Kontrolle. A8 wird über die MMU mit dem "PSEN"-Signal verbunden, so dass maximal 256 Byte als Programmspeicher und 256 Byte als Datenspeicher angesprochen werden können. Die Umschaltung zwischen Programm- und Datenspeicherbereich erfolgt über die Adressleitung A8. Die übrigen Adress-Signale werden vom Debug-µC bereitgestellt.
Beim Aufruf der Monitor-Software wird zunächst geprüft, ob sich der µC bereits im "HALT"-Zustand der Single-Step-Hardware befindet und, sollte dies nicht der Fall sein, die Single-Step-Hardware aktiviert und gewartet, bis der 8049 µC alle zum gerade ausgeführten Befehl gehörenden Befehlszyklen abgearbeitet hat.
Nun wird der durch den 8049 µC an BUS und dem unteren Nibble (den niederwertigen 4-Bit) von Port2 (P2) ausgebene Stand des Programmzählers (PC) gelesen und gespeichert, der der Adresse des nächsten auszuführenden Befehls im Anwenderprogramm entspricht. Im gleichen Schritt werden die Inhalte des Program Bank Registers (PBR) und Data Bank Registers (DBR) gelesen und gespeichert.
Die Firmware des Debuggers fängt Hardware-Interrupts, also solche, die durch einen Timer-/Counter-Überlauf oder durch L-Pegel am *INT-Eingang des 8049 ausgelöst wurden, vor Ausführung jedes auf die erste "JMP 009"-Anweisung (siehe "4.1.2 Monitor-Start") folgenden Befehls des durch den 8049 auszuführenden Teils der Monitor-Software ab, manipuliert den Stack des 8049-Mikrocontrollers (weil der jeweilige Hardware-Interrupt während der Ausführung der Monitor-Software ausgelöst wurde, wurde natürlich auch eine Adresse in der Monitor-Software auf dem Stack abgelegt, die durch die Monitor-Software nach Rettung des PSW/des Stackpointers (s.u.) durch die Adresse des 8049-Anwender-Programms ersetzt wird, zu der nach Beenden der Interrupt-Service-Routine im 8049-Anwender-Programm durch den "RETR"-Befehl zurück gesprungen wird) und die Rücksprungadresse nach Beenden der Monitor-Software in das 8049-Anwender-Programm (also zum Timer-/Counter-Interrupt-Vektor 0x007 oder 0x003 im Fall eines durch L-Pegel am *INT-Eingang ausgelösten Interrupts). Der 8049-Mikrocontroller verhält sich so, als wäre ein entsprechender Interrupt ohne Monitor-Software aufgetreten. Auch beim gleichzeitigen Auslösen eines Timer-/Counter-Interrupts und eines Hardware-Interrupts durch L-Pegel am *INT-Eingang wird zunächst der höher priorisierte, also der durch L-Pegel am *INT-Eingang ausgelöste, Hardware-Interrupt bedient und nach Beenden der zugehörigen ISR per "RETR"-Befehl zum Interrupt-Vektor des noch anstehenden Timer-/Counter-Interrupts gesprungen.
Das Abfangen eines Hardware-Interrupts durch die Monitor-Software wurde realisiert, indem vor der Ausführung eines Befehls der Adressbus auf 0x003 (Interruptvektor für durch L-Pegel am *INT-Eingang ausgelöste Interrupts) und 0x007 (Interruptvektor für Timer-/Counter-Interrupts) geprüft wird.
Wird von den unteren 12 Bit des Adressbus 0x007 (Interruptvektor für Timer-/Counter-Interrupts) oder 0x003 (Interruptvektor für durch L-Pegel am *INT-Eingang ausgelöste Interrupts) als Adresse des nächsten auszuführenden Befehls gelesen, dann wird ab Adresse 0x007 bzw. 0x003 ein "JMP 009" Befehl geladen und ausgeführt, um den PC wieder definiert auf die Start-Adresse des jeweiligen Monitor-Programms zu setzen und eine bequeme Wiederholung desselben zu ermöglichen, schließlich ist nicht bekannt, wie weit das jeweilige Monitor-Programm vor der Interrupt-Anforderung ausgeführt werden konnte und es würde bei den kompakten Monitor-Routinen einen unverhältnismäßigen Mehraufwand bedeuten, darauf zu optimieren :
(Adressen in Klammern gelten für den Timer-/Counter-Interrupt)
Ausserdem wird ein Flag-Bit gesetzt, um nach erfolger Rettung des PSW (s.u.) den Stack so zu manipulieren, dass beim Beenden der Interrupt-Service-Routine im Anwenderprogramm durch den "RETR"-Befehl an die auf den letzten Befehl des Anwenderprogramms vor dem Wirksamwerden eines Hardware-Interrupts (der ja an einer Adresse der Monitor-Software wirksam und dort abgefangen wurde) folgenden Adresse gesprungen wird.
Beispiel :
Für die Ausführung jedes im Folgenden beschriebenen Codes durch den 8049 gilt, dass vor jedem Befehl eine Überprüfung der unteren 12 Bit des Adressbusses auf einen der Interrupt-Vektoren erfolgt, erkannte IRQs abgefangen werden und gegebenenfalls der Stack sowie die Rücksprungadresse aus der Monitor-Software korrigiert wird.
Nach den unter "Monitor-Start" beschriebenen Vorgängen wird ermittelt, ob ein Bankwechsel per "SEL MB0" oder "SEL MB1" gewählt, aber noch nicht durch einen unbedingten Sprungbefehl ("JMP"/"CALL") als Bit11 des PC zur Geltung gekommen ist (pending bank switch). Das geschieht automatisch mit Hilfe eines der bereits ausgeführten unbedingten Sprungbefehle in der Monitor-Software. Es steht ein "SEL MB0" an (MB-FlipFlop ist zurückgesetzt), wenn beim erstmaligen Lesen des Adressbusses das Bit11 gesetzt war und nach einem unbedingten Sprungbefehl zurückgesetzt ist und ein "SEL MB1" (MB-FlipFlop ist gesetzt) steht an, wenn beim erstmaligen Lesen des Adressbusses das Bit11 zurückgesetzt war und nach einem unbedingten Sprungbefehl gesetzt ist. Jeglichen Problemen mit Bankswitching etc. wird während der Ausführung der Monitor-Software aus dem Weg gegangen, indem für alle Monitor-Programmteile, die dynamisch vom Debug-Mikrocontroller in das RAM am 8049 geladen werden, wie beschrieben nur die unteren 8 Bit des PC des 8049 genutzt werden.
Die Bankswitch-Erkennung und -Wiederherstellung in der "DeMon48"-Firmware funktioniert auch im Zusammenhang mit Interrupts, während deren Ausführung (also vor der Ausführung des am Ende der Interrupt-Service-Routine stehenden "RETR"-Befehls (Return And Restore PSW)) durch die im 8049 enthaltene Interrupt-Hardware Bit11 des Programmzählers auf "0" gezwungen wird (in der intel-Literatur wird in diesem Zusammenhang dringend von der Verwendung von "SEL MBx"-Befehlen zum Wechsel der Speicherbank in einer Interrupt-Service-Routine abgeraten: es wird kaum so, wie vom Programmersteller gedacht, funktionieren).
Wird ein "pending SEL MBx" erkannt, wird ein entsprechendes Flag-Bit gesetzt, um beim Verlassen der Monitor-Software sowohl die tatsächlich aktive Speicherbank durch Bit11 des Programmzählers (PC) auszuwählen und auch das Vorbereitungs-FlipFlop ("MB-FlipFlop") in den Zustand vor Aufruf der Monitor-Software zu versetzen.
Nun werden nacheinander die Inhalte von des Akkumulators (A), des Timer-/Counter-Registers (T) und des Prozessor-Zustands-Wortes (Program Status Word, PSW) gesichert, indem während der Ausführung der an die Adresse 0x09 des Debug-RAM geladenen, dafür genutzten "MOVX @R0,A"-Anweisung durch den Debug-µC die Adressleitungen A0 bis A7 und A9 bis A16 auf dem Wert 0x1FE09 gehalten werden, so dass, unabhängig vom bisher unbekannten Inhalt des R0-(R0'-)Registers, der Akkumulatorinhalt des 8049 an Adresse 0x09 im Datenbereich des Debugger-RAM gespeichert wird. A8 bleibt über die MMU unter Kontrolle des 8049, da sie zur Umschaltung zwischen Programm- und Datenbereich des Debugger-RAM durch den 8049 µC benötigt wird. Auf dieselbe Art wird anschließend der Inhalt von R0 in der Registerbank 0 gesichert und am Ende dieser Routine steht endlich ein Indexregister, dessen Inhalt schadlos zerstört werden kann - beispielsweise für die Verwendung in einer "MOVX @Ri,A"-Anweisung - zur Verfügung.
Laden :
Akkumulator retten
T-Register retten
PSW retten
R0RB0 (R0 in Registerbank0) retten
PC auf 0x009 setzen
Jetzt können der Zustand von P1, P2, F1 sowie der Zustand der T0-, T1- und INT-Eingänge ausgelesen und über den Datenbereich des Debugger-RAM gerettet werden.
Laden :
Nach Ausführen der Befehle in den Adressen 0x09 bis 0x28 hat der Programmzähler den Inhalt 0x009 und es kann der Inhalt von P1 aus RAM-Adresse 0x1FF00, der Inhalt von P2 aus RAM-Adresse 0x1FF01 und der Zustand von INT (Bit3), F1 (User Flag 1) (Bit2), T1 (Bit1) und T0 (Bit0) aus RAM-Adresse 0x1FF02 gelesen werden.
Wenn vor der Ausführung eines der Befehle in den Adressen 0x09 bis 0x28 ein neuer IRQ erkannt wurde, dann :
Anschließend wird der MCS-48 Code zum Kopieren des internen RAMs (Adresse 0x01 bis 0x7F, Adresse 0x00 wurde ja bereits mit R0, RB0 als DBG_R0RB0 gesichert) in das externe Debug-RAM geladen.
Laden :
Nach Ausführen der Befehle in den Adressen 0x09 bis 0x0F hat der Programmzähler den Inhalt 0x009 und die Inhalte des internen RAMs des 8049 von Adresse 0x01 bis Adresse 0x7F können aus den Adressen 0x1FF01 bis 0x1FF7F des Debugger-Datenbereichs gelesen und anschließend gesichert werden.
Wenn vor der Ausführung eines der Befehle in den Adressen 0x09 bis 0x0F ein neuer IRQ erkannt wurde, dann :
Falls während der Ausführung der Monitor-Software bis zu diesem Zeitpunkt ein Hardware-Interrupt (Timer-/Counter-Interrupt oder L-Pegel am *INT-Anschluss) abgefangen wurde, wird diese Routine ausgeführt.
Laden :
Dazu wird der vorstehende Code geladen und anschließend durch die Monitor-Firmware an Adresse 0x0A die berechnete Adresse (StackIndex = (StackPointer - 1) * 2 + 8) des unteren Bytes der zuletzt vom 8049 beschriebenen Stack-Speicherstellen, an Adresse 0x0C das niederwertige Byte der Rücksprungadresse in das 8049-Anwenderprogramm und an Adresse 0x10 die restlichen 4 Bit der Rücksprungadresse in das 8049-Anwenderprogramm, mit den höherwertigen 4 Bit des durch den Debugger gespeicherten PSW kombiniert, eingetragen. Schließlich werden noch die vom Debug-µC gesicherten Werte für R0RB0 in Adresse 0x13 und für den Akkumulator in Adresse 0x15 geladen, da die Inhalte dieser beiden durch das Programm zerstört werden.
Nach Ausführen der Adressen 0x09 bis 0x16 ist auf dem Stack die korrekte Rücksprungadresse inklusive PSW-Bits abgelegt und der Programmzähler (PC) des 8049 hat den Inhalt 0x009.
Während der Ausführung des Codes findet keine Überprüfung auf einen neuen IRQ statt - der wurde schließlich bereits erkannt und dadurch wurde die Modifikation des Stacks angestoßen.
Es ist nun eine Art "Meta-Zustand" erreicht, in dem der Zustand des 8049 nach dem zuletzt ausgeführten Befehl des Anwenderprogramms vollständig bekannt ist und vor dem Fortsetzen des Anwender-Programms über das GUI und den Debug-µC beliebig geändert werden kann.
Laden :
Nach Laden des Programmcodes, Beschreiben der Adressen 0x1FF01 bis 0x1FF7F des Debugger-RAM (Datenbereich) mit den gewünschten Inhalten des internen RAM des 8049 und Ausführen der Befehle in den Adressen 0x09 bis 0x0F hat der Programmzähler (PC) des 8049 den Inhalt 0x009 und das interne RAM des 8049 ist mit den neuen Inhalten von Adresse 0x01 bis 0x7F beschrieben.
Wenn vor der Ausführung eines der Befehle in den Adressen 0x09 bis 0x0F ein neuer IRQ erkannt wurde, dann :
Laden :
Nach dem Laden des Programmcodes werden vom Debug-µC in Adresse 0x0A der neue Wert für das Program Bank Register (PBR), in Adresse 0x0D der neue Wert für das Data Bank Register, in Adresse 0x10 der neue Wert für P2, in Adresse 0x13 der neue Wert für P1 und für ein zurückgesetztes User-Flag1 (F1) in Adresse 0x16 der 8049-Opcode für "NOP" (0x00) oder für ein gesetztes User-Flag1 (F1) "CPL F1" (0xB5) eingetragen und anschließend die Befehle von Adresse 0x09 bis 0x17 ausgeführt. Danach hat der Programmzähler (PC) des 8049 den Inhalt 0x009 und Port1 (P1), Port2 (P2), Program Bank Register (PBR), Data Bank Register (DBR) und User-Flag1 (F1) haben die neuen Werte übernommen.
Wenn vor der Ausführung eines der Befehle in den Adressen 0x09 bis 0x17 ein neuer IRQ erkannt wurde, dann :
Das Zurückschreiben der beim Monitor-Aufruf geretteten Inhalte (Register, Flags, Bits) erfolgt in der umgekehrten Reihenfolge der Rettung. PBR, DBR, P1, P2, F1 und internes RAM werden nur beschrieben, wenn sie zwischenzeitlich durch den Anwender im GUI geändert wurden (siehe oben : "4.2.1 Internes RAM beschreiben" und "4.2.2 PBR, DBR, P1, P2 und F1 beschreiben") oder der Stackinhalt durch die Debugger-Firmware im Debugger-Datenbereich manipuliert werden musste. R0, PSW, A, PC, aktive Speicherbank und der Zustand des MB-Flipflops sind durch die Ausführung der Monitor-Programme durch den 8049 µC stets als "zerstört" anzusehen, so dass sie, im Fall einer Änderung durch den Nutzer im GUI gemeinsam mit dem T-Register, beim Verlassen der Monitor-Software zurück in den 8049 µC geschrieben werden.
Laden :
Nachdem der vorstehende Code und der Inhalt des Registers R0 in Registerbank 0 (R0, RB0) aus DBG_R0RB0 in Adresse 0x0A, der Inhalt des Prozessor-Status-Wortes (PSW) aus DBG_PSW in Adresse 0x0C, der Inhalt des Timer-/Counter-Registers (T) aus DBG_T in Adresse 0x0F und der Inhalt des Akkumulators (A) aus DBG_A in Adresse 0x12 des Programmspeicherbereichs im Debugger-RAM geladen und die Befehle in Adresse 0x09 bis 0x13 ausgeführt wurden, sind R0, PSW, T und A mit den im Debug-µC gespeicherten Werten beschrieben und der Programmzähler (PC) des 8049 hat den Inhalt 0x009. Durch Einfügen des 8049-Opcodes für "NOP" (0x00) an Adresse 0x10 kann das Beschreiben des T-Registers durch den Debug-µC unterdrückt werden, wenn keine Änderung des Inhalts des T-Registers durch den Nutzer im GUI vorliegt.
Wenn vor der Ausführung eines der Befehle in den Adressen 0x09 bis 0x13 ein neuer IRQ erkannt wurde, dann :
Siehe "4.1.8 8049-Stack modifizieren".
Laden :
Nachdem damit die Vorbereitungen abgeschlossen sind, kann mit der Ausführung des geladenen Programms begonnen werden. Der 8049 hat dabei die Kontrolle über A0 bis A7, D0 bis D7, *RAM_OE, *RAM_WE. A8 bis A16 werden vom Debug-µC auf 0x1FE00 gehalten.
(*) : Wenn ReturnAddress-1 = 0x003, dann ist die Adresse des nächsten auszuführenden Befehls im Anwenderprogramm 0x004 - das zweite Byte einer ISR für externe Interrupts (L-Pegel an *INT). Es ist höchst unwahrscheinlich, dass an dieser Stelle ein externer Interrupt auch bei minimalistischster ISR ausgelöst werden soll (ausgenommen, die ISR besteht nur aus einem "RETR" - aber dann ergibt der ganze Interrupt keinen Sinn (s.u.)) :
Versagen würde die Erkennung externer Interrupts (L-Pegel an *INT) nur bei folgender Konstruktion und ReturnAddress=0x004 - und sogar in diesem Fall nur dann, wenn zwischen der Ausführung des "JMP ReturnAddress-1" und des letzten "SEL MBx"-Befehls in der Monitor-Software eine externe Interrupt-Anforderung erzeugt wird und nur sehr kurz anliegt :
Analog dazu beim Timer-/Counter-Interrupt :
Wenn ReturnAddress-1 = 0x007, dann ist die Adresse des nächsten auszuführenden Befehls im Anwenderprogramm 0x008 - das zweite Byte einer ISR für Timer-/Counter-Interrupts. Es ist höchst unwahrscheinlich, dass an dieser Stelle ein Timer-/Counter-Interrupt auch bei minimalistischster ISR ausgelöst werden soll (ausgenommen, die ISR besteht nur aus einem "RETR" - aber dann ergibt der ganze Interrupt keinen Sinn (s.u.)) :
Versagen würde die Erkennung von Timer-/Counter-Interrupts nur bei folgender Konstruktion und ReturnAddress=0x008 - und sogar in diesem Fall nur dann, wenn zwischen der Ausführung des "JMP ReturnAddress-1" und des letzten "SEL MBx"-Befehls in der Monitor-Software eine Timer-/Counter-Interrupt-Anforderung erzeugt wird und nur sehr kurz anliegt :