Forum: Compiler & IDEs winavr und das RAMPZ Register bei XMegas


von A. N. (netbandit)


Lesenswert?

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:
1
st Z+, r16

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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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

von A. N. (netbandit)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von A. N. (netbandit)


Lesenswert?

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 
:)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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

von A. N. (netbandit)


Lesenswert?

@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.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.