Pfad: Home => AVR-Überblick => Programmiertechniken => Befehlsausführung    (This page in English: Flag EN) Logo

Ausführung von Instruktionen in AVRs

1 Der Ablauf von Programminstruktionen in AVRs

Programminstruktionen sind als 16-Bit-Worte im Flashspeicher der AVRs gespeichert, sie werden vom Programmiergerät dort hinein geschrieben.

1.1 Programmzähler

Der Programmspeicher beginnt an der Adresse 0x0000. Zu Beginn und beim Reset wird diese Adresse in den Programmzähler (Program counter, PC) geschrieben, das dort befindliche Instruktionswort ausgelesen, dekodiert und schließlich ausgeführt.

Der Programmzähler wird nach jeder Instruktion um Eins erhöht und die dort stehende Instruktion ausgeführt. Es sei denn, die Instruktion ist ein Sprungbefehl und verbiegt den Programmzähler: dann wird der lineare Programmablauf verändert und es sind Instruktionen außer der Reihe dran.

Was ist, wenn die Instruktionen dann mal "alle" sind? Wenn der AVR dahin gelangt (was es sorgsam zu vermeiden gilt), liest er 0xFFFF aus dem nicht programmierten Speicher. Das ist ein ungültiges Befehlswort und er tut rein gar nix, außer dem Erhöhen des Programmzählers und dem Holen des nächsten Befehls.

Und wenn nun der gesamte Programmspeicher alle ist? Nun, dann beginnt er wieder bei Null. Das macht dann quasi einen Reset, aber ohne das Leermachen der Register und das Setzen der Portregister auf Default-Werte. Das kriegt man aber nur mit einem unprogrammierten AVR oder bei einem ersten Programmfehler hin.

1.2 Dekodierung

LDI Instruktion Dekodierung bedeutet die Auswahl aus den mehr als 100 verschiedenen Arten von Instruktionen, die AVRs beherrschen und ausführen können. So bewirkt z. B. die Instruktion LDI r,c, dass die Konstante c, die sich im Instruktionswort in den Bits 0 bis 4 und 8 bis 11 befindet, an den Eingang1 der arithmetisch/logischen Einheit (arithmentic/logic unit, ALU) geschrieben und beim nächsten Pegelwechsel am Takteingang in das Register r (+16) geschrieben wird.

Die Operation LDI ist dabei in den vier obersten Bits 12 bis 15 des Instruktionswortes mit 0b1110 kodiert. Die Registernummer steht in den Bits 4 bis 7 des Instruktionswortes. Da vier Bits nur 16 verschiedene Werte annehmen können, sind nur 16 der 32 möglichen Register ansteuerbar: es sind die oberen Register R16 bis R31. Die unteren Register können mit dieser Instruktion nicht gesetzt werden.

Ähnlich auf die oberen 16 Register eingeschränkt sind z. B. auch die Instruktionen SUBI r,k, ANDI r,k und
ORI r,k, die von Registerwerten Konstanten abziehen (SUBI) bzw. binär UND- (ANDI) und ODER- (ORI) verknöpfen und das Ergebnis in das Register r zurück schreiben. Viele Instruktionen lassen aber das Verknüpfen aller Register miteinander zu (z. B. SUB rx,ry, AND rx,ry und OR rx,ry), so dass diese Einschränkung auf die oberen Register eher eine Ausnahme darstellt.

Alle Bitkombinationen, aus denen sich das Instruktionswort zusammensetzt, sind in dem Dokument "avr-instruction-set-manual" für alle Instruktionen in der Descrption unter 16-bit-Opcode angegeben. Das Dokument gibt es als PDF auf der Microchip-Webseite zum Download.

Zum Seitenanfang

1.3 Die ALU

Arithmetisch/logische Einheit ALU Viele Instruktionen bewirken arithmetische oder logische Operationen. Das macht in Prozessoren die Arithmetisch/Logische Einheit (Arithmetic/Logic Unit, ALU): sie kann bis zu zwei Werte an ihren Eingängen miteinander verknüpfen und z. B, arithmetisch addieren oder subtrahieren oder binär UND, ODER oder Exklusiv-ODER errechnen. Was diesmal gemacht werden soll, sagt der Instruktions-Dekoder.

Die ALU schreibt das Ergebnis der Operation z. B. in ein Zielregister. Bei der Operation auftretende Ereignisse wie z. B. Überläufe bei arithmetischen Operationen (Carry) oder Nullergebnisse bei binären Verknüpfungen (Zero) werden als Flaggen oder Flags bezeichnet und landen im Statusregister. Dort können diese Flaggen mittels weiterer Instruktionen ausgewertet werden und z. B. in Verzweigungen oder bedingten Sprüngen enden.

Zum Seitenanfang

1.4 Pre-Fetch-Mechanismus

Der Ablaufmechanismus ist so konstruiert, dass während der Ausführung einer Instruktion bereits die nächste gelesen und dekodiert wird. Nach Abschluss des Dekodierens kann daher sofort die nächste Instruktion ausgeführt werden, weil der Lese- und Dekodiervorgang bereits vorher erfolgt ist.

Dieser Pre-Fetch geht nur dann schief, wenn die Instruktion zu einer &Aunml;nderung des Programmz&aumjl;hlers führt und das schon von der nachfolgenden Adresse gelesene und dekodierte Instruktionswort verworfen werden muss. Das ist der Fall, wenn eine Sprunginstruktion ausgeführt wird. Ist dies ein unbedingter Sprung (JMP oder RJMP) oder ist die Bedingung eines bedingten Sprunges BRxC label oder BRxS label erfüllt, muss das neue Sprungziel eingelesen und dokumentiert werden. Solche Instruktionen benötigen daher zwei statt nur eines Taktes.

Als Beispiel hier das binäre UND zweier 8-Bit-Zahlen in den Registern R16 und R17. Ver-UND-et werden 0b01010101 (0x55) und 0b10101010 (0xAA). Das Ergebnis muss natürlich Null sein. Der Quellcode in Assembler dazu:

  ldi R16,0x55 ; R16 auf hexadezimal 55
  ldi R17,0xAA ; R17 auf hexadezimal AA
  and R16,R17 ; UND, Ergebnis in Register R16

Die Simulationen erfolgten alle mit avr_sim.

Ladeinstruktionen UND-Instruktion Links der Registerinhalt nach den beiden Ladebefehlen: die beiden Hexadezimalzahlen sind in den beiden Registern angekommen. Rechts ist auch die AND-Instruktion ausgeführt: die ALU hat das Ergebnis, Null, in R16 geschrieben.

Statusregister Taktdauer Da das Ergebnis der letzten Operation Null ist, ist die Nullflagge Z im Statusregister (links) auf Eins gesetzt. Damit lässt sich nun weiter arbeiten.

Alle drei Operationen haben bei 1 MHz Takt genau 3 µs lang gedauert: jede dieser Operationen hat eine Mikrosekunde gebraucht. Ohne Pre-Fetch stünden hier sechs Mikrosekunden, also eine Verdoppelung der Ausführungsgeschwindigkeit.

Schließt sich diesem UND ein bedingter Sprung an, der nur dann ausgeführt wird, wenn das Ergebnis Null ist, lautet der Quellcode:

  ldi R16,0x55 ; R16 auf hexadezimal 55
  ldi R17,0xAA ; R17 auf hexadezimal AA
  and R16,R17 ; UND, Ergebnis in Register R16
  breq Label ; Wenn Null (equal) springe nach Label
  nop ; Hier wenn nicht gesprungen
Label: ; Hier wenn gesprungen und wenn nicht gesprungen
Bedingter Sprung Nun hat die Ausführung des gesamten Quellcodes 5 µs gedauert: die ausgeführte Sprunginstruktion BREQ Label hat zwei Mikrosekunden benötigt, weil der Pre-Fetch durch den Sprung schief ging.

Zum Seitenanfang

2 Instruktionskodierung

Instruktionskodierung Um zu verstehen, wie die AVRs ihre Befehle verarbeiten, hier am Beispiel des Addierbefehls dessen detaillierte Ausführung.

Das 16-Bit-Instruktionswort (instruction word) enthält in den Bits 10 bis 15 den Binärwert 0b000011. Das signalisiert der ALU, dass die beiden Register an den ALU-Eingängen IN1 und IN2 zu addieren sind.

In den Bits 0 bis 3 sowie in Bit 9 steht, welches Register von den 32 möglichen an den Eingang IN1 kommt. Analog geben die Bits 4 bis 8 an, welches Register mit IN2 verbunden wird. Beide Auswahlen erfolgen in den Multiplexern MUX.

Die ALU addiert dann beide Registerwerte. Das Rechenergebis in OUT wird dann in das Register geschrieben, das ebenfalls an MUX1 anliegt.

Die ALU stellt ferner fest, ob bei der Addition Alle Instruktionen, die zwei Register miteinander verknüpfen, also z. B. sind in den Instruktionsworten genauso kodiert, nur die obersten sechs Bits Kennzeichen sind, wie zu erkennen, anders zusammengesetzt.

Da für jedes der beiden Quellregister 5 Bits im Instruktionswort zur Verfügung stehen, kann jedes der 32 Register mit jedem anderen verknüpft werden. Also auch mit sich selbst. Das gibt lustige Effekte, denn bei UND und ODER mit sich selbst kommt nur dann Null heraus, wenn alle Bits im Register Null sind. ATMEL hat dem UND eines Registers mit sich selbst ein weiteres Mnemonic spendiert, es heißt TST r und setzt das Z-Flag wenn das Register r Null ist. Wer noch eine andere Variante probieren möchte, nimmt stattdessen OR r,r, das tut zwar dasselbe, erzeugt nur anderen Binärcode als TST r.

Einen ähnlichen Effekt hat ADC mit sich selbst: alle Bits im Register werden dadurch um eine Bitposition nach links geschoben, das unterste Bit wird aus dem Carry bezogen und das oberste Bit 7 landet im Carry. Das ist dasselbe wie die Instruktion ROL (Rotate left), die ebenfalls ein eigenes Mnemonic spendiert bekommen hat.

Noch so eine Pseudoinstruktion: EOR r,r löscht alle gesetzten Bits im Register und macht sie Null, weil Exklusiv-ODER alle Bits zu Null macht, die entweder beide Null oder beide Eins waren. Auch diesem wurde ein eigenes Mnemonic spendiert: CLR r, es produziert aber das gleiche Instruktionswort wie EOR r,r.

So kann man die Anzahl Instruktionen auch erhöhen, ohne dass man der ALU neue Befehle beibringen muss.

Man kann es aber nicht oft genug betonen: alle beschriebenen Vorgänge beim ADD laufen in einem einzigen Taktzyklus der CPU ab (und nicht in mehreren wie bei den PICs). Und dabei sind keine verlangsamenden SRAM-Zugriffe nötig, alles steht in den leicht zugänglichen 32 Registern.

Zum Seitenanfang

Beispiel: Ein 16-Bit-Addierer

Als Beispiel für den Ablauf der Instruktionsverarbeitung in AVRs abschließend noch ein 16-Bit-Addierer mit Überlauf-Erkennung. Sein Quellcode lautet:

.equ n16bitZahl1 = 12345 ; Die erste 16-Bit-Zahl, kein Takt
.equ n16bitZahl2 = 45678 ; Die zweite 16-Bit-Zahl, kein Takt
  ldi R16,Low(n16bitZahl1) ; Das LSB von Zahl 1 in R16, ein Takt
  ldi R17,High(n16bitZahl1) ; Dessen MSB in R17, ein Takt
  ldi R18,Low(n16bitZahl2) ; Das LSB von Zahl 2 in R18, ein Takt
  ldi R19,High(n16bitZahl2) ; Dessen MSB in R19, ein Takt
  add R16,R18 ; Addiere die beiden LSB, ein Takt
  adc R17,R19 ; Und die beiden MSB mit dem Ueberauf, ein Takt
  brcc Fertdisch ; Wenn kein Ueberlauf: springe, zwei bei Sprung
  ldi R16,0xFF ; Setze Ergebnis auf hoechsten Wert, LSB
  ldi R17,0xFF ; dto., MSB
Ferdisch:
Laden der 16-Bit-Zahl Laden und Addieren Links sind die vier Ladebefehle, ihr Ergebnis in den Registern und die Dauer von 4 µs zu sehen. Rechts ist zusätzlich auch noch die Addition und der Sprung erfolgt. Die Übertragsflagge C war nicht gesetzt, also erfolgte der Sprung und das Ergebnis ist nach 8 µs fertig.

Provozieren wir einen Überlauf, indem wir die erste 16-Bit-Zahl auf 23456 setzen, dann sieht das Ergenis etwas anders aus.

Andere 16-Bit-Zahl Andere Zahl addiert Jetzt ist R17:R16 0x5BA0, die andere erste Zahl (links). Rechts ist zu sehen: nach dem Addieren ist die C-Flagge gesetzt, weil ein Überlauf aufgetreten ist. Die beiden Register sind daher auf 0xFFFF zu setzen.

Ergebnis auf hoechsten Wert Hier ist das Ergebnis entsprechend auf den Höchstwert 0xFFFF korrigiert. Es sind nun 9üµs vergangen, also nur ein Takt mehr als bei der ersten Zahl. Aus den zwei Takten, die im ersten Fall mit dem Sprung zugebracht wurden, ist ein Takt geworden. Das hat den ersten Setzbefehl auf 0xFF kompensiert, so dass nur der zweite Setzbefehl verlängernd zu Buche schlägt.

Alle Taktprobleme lassen sich mit den hier gezeigten Methoden auseinandernehmen und analysieren. Der Taktablauf bleibt im Griff und ist einfach zu verstehen.

Zum Seitenanfang

©2019 by http://avr-asm-tutorial.net