Hallo,
ich bin auf ein Problem in der aktuellen WINAVR Version
(20090313) gestoßen.
Bei der XMega Serie wird das Rampz/Rampy/Rampx für den Zugriff auf den
Datenspeicher verwendet.
Hat man nun also eine Schleife in der ein Pointer inkrementiert wird bis
zur Adresse 0xFFFF, wird dies vom GCC in einen Assemblercode umgesetzt
der oft so etwas in der Art enthält:
War nun jedoch die letzte Adresse in Z 0xFFFF, so wird Z auf 0 gesetzt
und beim XMega das RAMPZ Register um 1 inkrementiert.
Alle danach folgenden Zugriffe auf den Datenspeicher gehen dann jedoch
in leere, da der Xmega denkt, er soll auf eine Adresse jenseits der
64KiB zugreifen.
Also anstatt auf Adresse 0x0010 würde er dann auf 0x010010 zugreifen.
Das Problem ist hier, dass der GCC offensichtlich beim initialisieren
der Pointer Z,X,Y die jeweiligen RAMPZ,RAMPX,RAMPY Register nicht mit
initialisiert.
Hat jemand schon einmal Erfahrung damit sammeln können? Gibt es schon
ein Patch (Hab bis jetzt noch nichts gefunden) oder einen geeigneten
Workaround?
Vielen Dank im Voraus,
Netbandit
A. N. wrote:
> Hat jemand schon einmal Erfahrung damit sammeln können? Gibt es schon
> ein Patch (Hab bis jetzt noch nichts gefunden) oder einen geeigneten
> Workaround?
avg-gcc 4.5 verwendet die angesprochenen SFRs nicht, man muss also von
Hand dafür sorgen, daß die die richtigen Werte haben.
Die Adressen von Objekten liegen idR erst zur Linkzeit fest, der
Compiler hat keine Vorstellung davon. Der RAMP-Mechanismus gibt AVR ein
segmentiertes Speicherlayout, das in der AVR-Design-Phase aufgrund von
Empfehlungen von IAR jedoch nicht umgesetzt wurde: AVR erhiehlt einen
linearen Adressraum von 16 Bit, die RAMP-SFRs machen Zeiger jedoch
effektiv zu 24 Bit.
Natürlich wäre es möglich, in avr-gcc für alle direkten Zugriffe
funktionierenden Code zu erzeugen, wenn die RAMP-Register angepasst
würden. Aber das würde einen riesigen Overhead bedeuten, weil vor
jedem Speicherzugruff die SFRs angepasst werdem müssten. Überleg dir
zB den Fall wenn ein int oder long ab Adresse 0xffff anfangen würde.
Aber für indirekte Zugriffe bräuchte man 24-Bit Pointer, was bedeuten
würde, das avr-Backend im gcc kompett aufzubohren und inkompatibel mit
bestehenden Anwendungen und Bibliotheken zu werden.
Man muss also von Hand dafür sorgen, daß diese SFRs beim Zugriff richtig
besetzt sind, indem man per Linkerscript oder sonstigen Mechanismen
dafür sorgt, daß Objekte nicht über Segmentgrenzen hinweg liegen. Das
einzige, was avr-gcc für dich erledigt, ist daß das ganze IRQ-sicher
ist: Im Prolog/Epilog der ISRs wird RAMPZ gesichert/restauriert falls Z
verwendet wird.
Ich seh auch nicht, daß sich das in absehbarer Zeit in avr-gcc ändern
wird, und wüsste auch nicht wie das konsistent von statten gehen sollte.
Ganz zu schweigen von Performance-Betrachtungen.
Johann
Naja, es würde ja auch keiner auf die Idee kommen statt 16bit Adressen
nur 8bit Adressen zu benutzen und das obere Byte manuell umschaltbar zu
machen, nur weil man damit in vielen Fällen eine Registerzuweisung
spart. Wenn die Xmega µCs jetzt nun einmal 24bit Adressen nutzen, dann
muss der Compiler dem Rechnung tragen. Im Notfall über ein
Kommandozeilenbefehl, dann kann jeder selbst entscheiden ob er den
erweiterten Adressraum linear ansprechen möchte oder nicht.
Das Problem ist ja nicht die Segmentierung selbst, damit kann jeder
leben die manuell zu steuern. Das Problem ist ja, dass diese Register
per Hardware automatisch verändert werden, sobald man über ein Segment
hinaus geht. Je nach Optimierungsgrad des Compilers können dann leicht
unbeabsichtigte Segmentumschaltungen entstehen.
A. N. wrote:
> Naja, es würde ja auch keiner auf die Idee kommen statt 16bit Adressen
> nur 8bit Adressen zu benutzen und das obere Byte manuell umschaltbar zu
> machen
Ähmmm - wann hast du dir das letzte Mal eine PIC Architektur angesehen?
Deine Beschreibung passt exakt auf die PIC18.
Ja ich kenne das vom PIC16, das war einer der Gründe warum ich auf AVR
umgestiegen bin. Ich hätte nicht gedacht, dass der PIC18 dieses Manko
immer noch hat.
Der Unterschied beim PIC16 ist jedoch, dass sich hier die Bänke nicht
automatisch per Hardware umschalten. Sondern man muss es bewusst machen
und wenn ich einen Befehl dazu eingebe, dann weiß ich auch in welcher
Bank ich gerade bin und kann damit rechnen. Wenn jedoch je nachdem ob
ich für die selbe Sache eine for oder while Schleife verwende eine
unbewusste Bankumschaltung verursache, dann ist das eben nicht so schön
:)
A. N. wrote:
> Das Problem ist ja nicht die Segmentierung selbst, damit kann jeder
> leben die manuell zu steuern. Das Problem ist ja, dass diese Register
> per Hardware automatisch verändert werden, sobald man über ein Segment
> hinaus geht. Je nach Optimierungsgrad des Compilers können dann leicht
> unbeabsichtigte Segmentumschaltungen entstehen.
Das kann doch nur passieren, wenn ein Objekt Segmentgrenzen
überschreitet. Ansonsten sehe ich nicht, wie das passieren könnte. Wenn
ein Objekt grenzen überschreitet, dann hat man ohnehin Probleme --
unanhängig davon, ob RAMP bei AutoInc/-Dec angepasst wird oder nicht.
Das Beispiel (int*) 0xffff, das du oben genannt hast, bereitet immer
Probleme weil es über eine Grenze geht: entweder beim Lesen/Schreiben
selbst (wenn RAMP nicht angepasst würde) oder danach (weil es unter der
Hand erhöht/erniedrigt wird).
Es ist also in jedem Fall dafür zu sorgen, daß eine Segmentgrenze nicht
überschritten wird. Das Speichermodell ist segmentiert -- gleich, ob die
Hardware RAMP nachführt oder nicht.
Johann
@Johann:
Nein eben nicht, das kann halt auch passieren, wenn man an der "Grenze"
der 64KiB etwas schreibt und ließt ohne dabei die Grenze zu
überspringen.
Hier ein Codebeispiel:
1 | #include <avr/io.h>
| 2 |
| 3 | int main(void) {
| 4 | uint8_t* pt;
| 5 |
| 6 | while(1) {
| 7 |
| 8 | for (pt = 0; pt <= (uint8_t*)0xFFFF; pt++) {
| 9 | *pt = 0;
| 10 | }
| 11 |
| 12 | }
| 13 | return 0;
| 14 | }
|
Es wird eindeutig im Code nur der Bereich 0x0000 bis 0xFFFF durchfahren
und nicht überschritten. Aber was macht GCC daraus:
1 | 234: e0 e0 ldi r30, 0x00 ; 0
| 2 | 236: f0 e0 ldi r31, 0x00 ; 0
| 3 | 238: 11 92 st Z+, r1
| 4 | 23a: fe cf rjmp .-4 ; 0x238 <main+0x4>
|
Die Optimierung rechnet damit, dass Z bei einem Überlauf wieder auf
0x0000 steht. Soweit sogut, aber bei einem XMega führt dieser Überlauf
halt zu einem Inkrement des RAMPZ Registers (alles in hardware) und
damit würde die Schleife beim zweiten Durchlauf nicht mehr auf den
Speicherbereich 0x000000 - 0x00FFFF zugreifen sondern auf 0x010000 -
0x01FFFF. Also passiert hier unbeabsichtigt eine Segmentumschaltung. Das
so etwas passiert geht aber aus dem dazugehörigen C Code gar nicht
hervor, daher würde hier kein Programmierer von sich aus manuell nach
der Schleiche RAMPZ wieder auf 0x00 setzen.
Wie gesagt: Das Problem ist nicht die Segmentierung an sich. Solange man
als Programmierer die volle Kontrolle darüber hat ist das ok. Das
Problem ist eben, dass die Segmentumschaltung per Hardware automatisch
geschehen kann.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
|