Könnte ein ARM (ARM7TDMI) Verzweigungsbefehl 6 Zyklen dauern?

Ich habe festgestellt, dass eine ARM-Verzweigungsanweisung 6 Zyklen zu dauern scheint, um auf einem ARM7TDMI-Prozessor ausgeführt zu werden. Es scheint, dass dies nicht passieren sollte, da in allen Referenzen, die ich gefunden habe, eine ARM7TDMI-Verzweigungsanweisung nur 3 Zyklen dauern sollte. Aber:

Die C-Funktion:

start_time = TC;
for (int i=0; i<120; i++) {
  __asm("NOP");
}
end_time = TC;

Die Disassemblierung zeigt die Schleife als: (Update: Befehlsadressen hinzugefügt):

0x120             MOV R1, 0
0x124             B LOC0
            start:    
0x128             NOP
0x12C             ADD R1, R1, 1
            LOC0:     
0x130             CMP R1, 120
0x134             BLT start

Das Ergebnis zeigt nun, dass die Schleife 1080 Zyklen benötigt (umgerechnet von einem in TC eingegebenen Timer-Zähler), dh 9 Zyklen pro Schleifenkern. Da NOP, ADD, CMPalle Einzelzyklusbefehle sind, BLTmüssen 6 Zyklen sein.

Ich vermute einmal, ob meine Zeitmessungsmethode Fehler hat. NOPAber wenn ich im Schleifenkern 1 hinzufüge , würde die Zeiterhöhung genau 1 Zyklus betragen.

Was ist hier los?

(Update: Fix: Der ursprüngliche Disassemblierungscode war falsch geschrieben ADD R1, R1, 1als ADD R1, R1)

Update: Antwort akzeptiert: Flash-Zugriffsverzögerung verursacht die 3 zusätzlichen Zyklen

Vielen Dank für die hilfreichen Antworten und Kommentare, insbesondere @supercat, @Dzarda, @DaveTweed, @IgorSkochinsky, @WoutervanOoijen. Ich lasse Code vom Blitz laufen. Die CPU ist eine LPC23xx. Laut Benutzerhandbuch enthält es ein Memory Acceleration Module (MAM) für den gepufferten Flash-Zugriff. Und die vorgeschlagenen Flash-Fetch-Zyklen unter meiner CPU-Geschwindigkeit betragen genau 3 Zyklen.

Der startim obigen Penalized-Loop-Kernel richtet sich an einer 8-Byte-Grenze aus. Wenn ich die Ausrichtung auf starteine 16-Byte-Grenze ändere, verschwindet die Strafe von 3 zusätzlichen Zyklen. Dies kann durch die Flash-Prefetch-Puffergröße meiner CPU von 128 Bit (16 Byte) erklärt werden.

(@WoutervanOoijen) Beachten Sie, dass die 3-Zyklen-MAM-Flash-Abrufzeit nicht von der ARM-CPU ausgeführt wird, sondern vom MAM-Modul, das die Flash-Daten parallel zur CPU vorab abruft. In meinem Code mit startAusrichtung an der 8-Byte-Grenze CMPbefindet sich also die erste Anweisung im 128-Bit-MAM-Prefetch-Puffer (4-Anweisungen). Wenn die ARM-CPU ausführt BLT, dauert es den ersten Zyklus, um die Anweisung zu "verstehen". Dann versucht er, einen Befehl abzurufen NOP, der sich nicht im MAM-Vorabrufpuffer befindet. Das sollte der Moment sein, in dem die zusätzlichen 3 Zyklen passieren, wenn das MAM auf den Flash zugreift. Wenn sich die Anweisung im Puffer befindet (zusammen mit 3 anderen Anweisungen in der 32-Byte-Flash-Zeile), kann die ARM-CPU die Pipeline durch Abrufen (5. Zyklus) und Decodieren NOPtatsächlich wieder auffüllenNOPNOP(6. Zyklus). Daraus ergeben sich die insgesamt 6 Zyklen.

Die Antwort auf meine Frage lautet also Ja, ein 6-Zyklus-Verzweigungsbefehl ist möglich, wenn ein Flash-Zugriffsstillstand vorliegt.

Letzte ungelöste Frage

Wie @WoutervanOoijen betont, hat die obige Argumentation einen Fehler. Das Memory Acceleration Module des LPC23xx verfügt über einen zusätzlichen Branch-Trail-Buffer, der diese Art von wiederholten Re-Fetch-Loop-Verzweigungen vermeiden soll. Im LPC23XX-Benutzerhandbuch heißt es:

Der Verzweigungsspurpuffer erfasst die Zeile, an der eine solche nicht sequentielle Unterbrechung auftritt. Wenn dieselbe Verzweigung erneut genommen wird, wird die nächste Anweisung aus dem Verzweigungsspurpuffer genommen

Diese Aussage scheint nicht sehr klar darüber zu sein, was genau in den Branch Trail-Puffer geschrieben wird. Es könnte die letzte vorab abgerufene Flash-Zeile oder die letzte Flash-Zeile des Verzweigungsziels sein. In beiden Fällen hätte die Flash-Zugriffsstrafe nicht passieren dürfen, da die Flash-Zeile (0x120 ~ 0x12F) einschließlich der Verzweigungszielanweisung ( ) NOPbereits im Branch Trail-Puffer sein sollte, wenn BLTsie ausgeführt wird (zumindest ab dem zweiten Mal). .

(Übrigens, ich habe überprüft, dass das MAM in den vollständig aktivierten Modus versetzt wird, dh MAM_mode_control ist 2.)

Ich werde diese Frage aktualisieren, nachdem ich weitere Informationen dazu gefunden habe. Und ich würde es schätzen, wenn Sie irgendwelche Kommentare dazu haben, was hier passieren könnte, oder welche Tests durchgeführt werden können, um nach Hinweisen zu suchen.

Meine Wette wäre, dass es etwas mit Cache-Fehlschlägen oder so etwas in diesem Baseballstadion zu tun hat. Wie auch immer, ARM ist eine ziemlich komplexe Architektur und Sie sollten keine zyklusgenaue Ausführung erwarten. Es gibt viele Dinge, die normale Ingenieure über das Ding nicht wissen (nicht wissen müssen).
Sind Sie sicher, dass die Demontage korrekt ist? Es sieht so aus, als sollte es R1 bei jeder Iteration verdoppeln (nicht erhöhen). addZweitens kann es zu einem durch die Datenabhängigkeit zwischen den und den Befehlen verursachten Pipelinestillstand kommen cmp. Schließlich könnten 3 Zyklen bltnur ein Mindestwert sein; zusätzliche Takte könnten erforderlich sein, wenn Teile der Dekodierpipeline geleert werden müssen, wenn die Verzweigung genommen wird.
Es ist sehr schwierig, Taktimpulse für Befehlszyklen auf einem modernen Pipeline-Kern zu zählen. Ich bin mir nicht sicher, ob es überhaupt deterministisch ist
Um welchen Chip handelt es sich genau? (NXP?) Hat es einen Flash-Beschleuniger/Cache? Berücksichtigen Sie die Flash-Zugriffszeit? Bekommst du ein anderes Ergebnis, wenn du vom RAM aus läufst?
Für einen ARM7, der nicht warten muss (z. B. auf Operandenabrufe), sollte diese Schleife tatsächlich 1 + 1 + 1 + 3 Zyklen betragen. 1) Überprüfen Sie, ob es wirklich dieser Code ist, der ausgeführt wird (der Nop scheint an der falschen Stelle zu sein, und wie David sagte, scheint die Schleifenzählung falsch zu sein). 2) Überprüfen Sie, ob die CPU auf Befehlsabrufe warten muss (Flash hat oft einen zusätzlichen Wartezyklus).
@Dzarda Interessanterweise hat ARM7TDMI laut dieser Seite keinen Cache: en.wikipedia.org/wiki/List_of_ARM_microarchitectures
@xiaobai der ARM7TDMI ist eine CPU, der Chiphersteller fügt den Speicher (RAM, FLash) hinzu, der eine Form von Caching oder Pufferung beinhalten kann (der LPC2148 hat zum Beispiel einen primitiven Flash-Puffer). Warum sagst du uns also nicht genau, welchen Chip du verwendest und mit welchen Einstellungen?
@DaveTweed Entschuldigung, Fehler beim Disassemblierungscode behoben.
Ich glaube, Sie haben die falschen Schlüsse gezogen. Ein 3-Zyklus-Flash-Zugriff bedeutet 2 zusätzliche Zyklen (1-Zyklus-Zugriff bedeutet keine Unterbrechungen). Sie scheinen einen Overhead von 6 Zyklen zu haben, was mit 3 Flash-Lesevorgängen von 64 Bit vereinbar wäre, was der Fall wäre, wenn sich das Label „Start“ in der zweiten Hälfte eines 64-Bit-Langworts befindet. Es ist nicht die BLT selbst, die den 6-Zyklus-Overhead erhält, sondern das Abrufen der 4 Befehle.
@WoutervanOoijen Tatsächlich wird die 3-Zyklen-Flash-Abrufzeit nicht von der ARM-CPU durchgeführt, sondern vom MAM-Modul, das die Flash-Daten parallel zur CPU vorab abruft. Bitte lesen Sie mein Update für Details darüber, wie die insgesamt 6 Zyklen zustande kommen könnten.
@xiaobai Ihre Argumentation wäre für das einfachere MAM eines LPC21 richtig gewesen, aber der LPC22 verfügt über einen "Branch Trail Buffer", der in diesem Fall die Zeile mit dem Ziel des Brachs (dem NOP) ohne zusätzliche Verzögerung liefern sollte.
@WoutervanOoijen Du hast recht. Ich weiß noch nicht, warum Branch Buffer das Schleifenziel nicht puffert.

Antworten (2)

Führen Sie Code aus dem RAM oder aus dem Flash aus? ARM-Prozessoren, die Code aus dem Flash ausführen, erfordern zumindest unter bestimmten Umständen häufig Wartezustände. Solche Prozessoren enthalten oft Hardware, die die meisten Wartezustände im gemeinsamen Code eliminieren kann, aber solche Hardware kann so einfach wie ein Einzelzeilenpuffer sein, der einen Zugriff auf dieselbe Flash-Zeile wie beim vorherigen Zugriff ermöglicht, um den Wartezustand zu vermeiden. Wenn das Verzweigungsziel das letzte Wort einer Flash-Zeile ist, würde der Flash zwei oder drei Zyklen benötigen, um dieses Wort abzurufen, und zwei oder drei Zyklen, um das folgende Wort abzurufen. Wenn einer der Zyklen gleichzeitig mit einer anderen CPU-Operation ausgeführt wird, würde dies eine Strafe von drei Zyklen hinterlassen.

Vielen Dank für die Antwort. Ich lasse Code vom Blitz laufen. Die CPU ist eine LPC23xx. Laut Benutzerhandbuch enthält es ein Memory Acceleration Module (MAM) für den gepufferten Flash-Zugriff. Und die vorgeschlagenen Flash-Fetch-Zyklen unter meiner CPU-Geschwindigkeit betragen genau 3 Zyklen.
@xiaobai: Habe ich die Ausrichtung relativ zur Cache-Zeile richtig erraten oder war ich um ein Byte daneben? Würde das Verschieben des Codes, um bei einem Vielfachen von 8 Wörtern (32 Bytes) zu beginnen, ihn schneller laufen lassen?
Ja, Sie haben Recht, dass der Loop-Kernel an der Zeilengröße des Flash-Puffers ausgerichtet werden muss. In meinem Fall beträgt der Flash-Prefetch-Puffer 128 Bit (16 Byte). Ich habe überprüft, dass die zusätzlichen Zyklen nicht auftreten, wenn sie startan der 16-Byte-Grenze ausgerichtet sind. Zuvor ist es in der 8-Byte-Grenze.

Werfen Sie einen Blick in das ARM-Infocenter, Abbildung 2 , und denken Sie daran, dass Sie mit der ARM7-Pipeline und nicht mit der dreistufigen M3-Pipeline arbeiten. Der Punkt bleibt gültig.

Zwischen Abruf und Ausführung können Zyklen liegen. Es ist sehr schwierig, Taktimpulse für Befehlszyklen auf einem modernen Pipeline-Kern zu zählen. Ich bin mir nicht sicher, ob es überhaupt deterministisch ist

Ich frage mich, ob die Pipeline an jeder Verzweigung neu beginnen muss. Sie könnten erwägen, eine Reihe dieser NOPs zu stapeln, anstatt zu verzweigen, um zu sehen, ob Ihr resultierendes Verhalten als Debugging-Schritt deterministischer ist.

In der Tat wurde ich aus diesem Grund davor gewarnt, NOPs für präzise Verzögerungen auf ARM-Plattformen zu verwenden.

Geben Sie hier die Bildbeschreibung ein

-1: Ihre Antwort basiert auf Zeitdiagrammen für M3- und M4-CPUs (16 Bit), aber die Frage bezieht sich auf den (32-Bit) ARM7.
@WoutervanOoijen Ich ging nur davon aus, dass die 8-stufige A7-Pipeline eine noch schwierigere Situation sein würde als die 3-stufige M-Pipeline, und konnte selbst nach einigem Suchen keine gute Illustration für die A7 finden. Ich freue mich, das Bild und den Link zu ersetzen, wenn Sie mich auf ein besseres verweisen können, um den Punkt zu veranschaulichen, den ich zu machen versuche.
@WoutervanOoijen, M3 und M4 sind 32-Bit-CPUs. Meinst du den Befehlssatz?
Entschuldigung für die Formulierung, was ich meinte, ist, dass die Mx (meistens) 16-Bit-Befehls-CPUs sind, während sich die Frage auf ARM7TDMI im ARM-Modus bezieht, was eine völlig andere CPU ist, die sogar einen anderen Befehl mit hat. (Aber die gleiche Breite ist nicht genug, M0 und M0+ haben zum Beispiel den gleichen Befehlssatz, aber unterschiedliche Pipelines). Ich bin mir nicht sicher, was Sie mit A7 meinen, ARM7 ist kein Cortex A7! Ohne Verzögerungen ist die ARM7-Pipeline Fetch-Decode-Excute, mit einer Strafe von 2 Zyklen für das Ändern des PCs.
NOP@ScottSeidman Danke für den Vorschlag, anstelle von Schleifen zu verwenden . Die Verwendung NOPdes Ergebnisses wäre richtig.