Guten Morgen
Linker-Skripts für den GCC zu schreiben kommt mir ein wenig wie
Russisch-Roulett vor, egal wie gut man im Umgang mit dem Werkzeug geübt
ist, man hat stets eine 1/6 Chance sich in den Kopf zu schießen. Wenn
man ungeübt ist (wie ich), dann schafft man nicht einmal das und schießt
sich ins Knie... oder so ähnlich.
Heute kümmert mich folgendes Problem. Ich hab 4x Linker-Skripts für
Cortex-M4 Prozessoren angehängt.
STM32F407VGTx_FLASH.ld -> Original von ST
STM32F407VGTx_FLASH_modified.ld -> Alignment geändert
STM32L476VGTx_FLASH_modified.ld -> SRAM2 hinzugefügt
sections.ld -> Template ARM Plugins Eclipse
Soweit sogut. Auf den ersten Blick unterscheiden sich die Skripts nicht
wirklich grob voneinander. Alle enthalten ihre Speicher Definitionen,
ihre Sections, usw.
Was aber recht schnell auffällt ist, dass die Alignments der
verschiedenen Sections von Skript zu Skript abweicht. Wieso etwa ist die
._user_heap_stack Section von STs Original 8-Byte aligned, während der
Rest 4-Byte aligned ist?
1
/* User_heap_stack section, used to check that there is enough RAM left */
2
._user_heap_stack:
3
{
4
.=ALIGN(8);
5
PROVIDE(end=.);
6
PROVIDE(_end=.);
7
.=.+_Min_Heap_Size;
8
.=.+_Min_Stack_Size;
9
.=ALIGN(8);
10
}>RAM
Noch interessanter wirds, wenn man versucht mit dem originalen Skript
Variablen im CCMRAM des F407 abzulegen und aus dem Flash zu
initialisieren. Dazu hab ich folgendes probiert:
1
.section.ccmram
2
myvar0_ccmram:.word3
3
myvar1_ccmram:.word4
4
myvar2_ccmram:.word5
Leider finden sich die Initialisierungswerte nirgends im Flash wieder
und ein Blick ins Listening verrät wieso:
Woher kommt dieser Wandel? Das Ergebnis lässt sich beliebig
reproduzieren. Zum Beispiel auf einem STM32L476. Was ich in Bezug auf
dessen Linker Skript dann nicht verstehe ist, wieso ST hier plötzlich so
gut wie alle Sections 8-Byte aligned hat?
Ansonsten tritt auch hier exakt das gleiche Problem mit der
Initialisierung auf. Zweite RAM-Section 8-Byte aligned und alles ist
gut, 4-Byte aligned und VMA und LMA sind ident, sprich keine
Initialisierungswerte im Flash!
Das Template, dass seitens des GNU ARM Eclipse Plugins erstellt wird,
aligned übrigens ALLE Sections 4-Byte...
Blickt da noch wer durch?
Wie kann das 4/8-Byte Alignment dafür verantwortlich sein ob
Initialisierungswerte im Flash landen oder nicht?
lg
Also erstmal zum CCM, das kann man natürlich fast normal nutzen.
Allerdings erstens nicht für DMA und zweitens nicht für Code (data
only). Zumindest das zweite/dritte Linkerscript ist da falsch, weil es
fürs CCM auch "execute" erlaubt. Das geht von der Hardware her aber
nicht.
Dittens enthält der Startupcode wohl keine Initschleife fürs CCM, weder
Nullinitialisierung noch Kopierschleife für initialisierte (non-zero)
Variablen. Das mußt Du ggf. ergänzen.
Funktionen im RAM sind bei STM32 dann schlecht, wenn man damit
Performance haben will. Wegen des ART einerseits und Buskollision
andererseits wird das dann eher langsamer. Miß es mal nach. Wirklich
angebracht sind sie nur dann, wenn Du damit im Betrieb das Flash
beschreiben willst.
Readonly-Phänomen: Wenn man beim GCC Variablen deklariert, die man aber
nur lesend verwendet, dann schnallt GCC das und setzt die automatisch
als read-only, auch ohne const. Das ist lästig, wenn man sie ganz bewußt
nicht als const markiert hat, um sie in die data-Sektion zu kriegen,
damit sie zur Laufzeit aus dem schnellen RAM und nicht aus dem
langsameren (scattered access) ROM gelesen werden. Abhilfe schafft
__attribute mit der gewünschten section.
Alignment: Guck Dir im Startupcode mal die Kopier/Ausnullungsschleifen
an. Womöglich sind die doppelt ausgerollt, um Bootzeit zu sparen. Dann
wäre klar, wieso die 8-Byte-alignment haben müssen. Nur so ne Idee.
Danke für die rasche Antwort!
Ja dass man den CCM nicht für Code nutzen kann, darauf führte mich der
eintretende HardFault... :)
Den als "SRAM2" bezeichneten Speicher des STM32L476 sollte man aber
schon für Code nutzen können und auch das Datenblatt weist extra darauf
hin:
"Execution can be performed from SRAM2 with maximum performance without
any remap thanks to access through ICode bus."
Buskollisionen dürfte es dann ja nur geben, wenn man aus dem SRAM2
gleichzeitig Code ausführt und Speicherzugriffe tätigt?
/edit
Die Initialisierung hab ich natürlich händisch eingefügt. Aber bei
"ALIGN(4)" landen die Werte tatsächlich nicht im Flash... Und ich
versteh nicht wirklich wieso...
Ev. kann das Problem mal wer reproduzieren?
_sdata2 = .; /* create a global symbol at ccmram start */
5
*(.ccmram) /* .data sections */
6
*(.ccmram*) /* .data* sections */
7
8
. = ALIGN(4);
9
_edata2 = .; /* create a global symbol at ccmram end */
10
} >CCMRAM AT> FLASH
seit Jahr und Tag wunderbar.
Vielleicht probierst du mal aus, die Output Section anders als die Input
Section zu benennen (also nicht beide ".ccmram") wobei das rein logisch
nichts bewirken sollte...
Was passiert, wenn du die Variablen nicht per Assembler sondern einfach
in C(++) anlegst?
Sehr interessant.
ALIGN(4) ohne angelegte Variable in C++ funktioniert nicht.
ALIGN(4) inkl. angelegter Variable in C++ funktioniert.
ALIGN(8) ohne angelegte Variable in C++ funktioniert.
/edit
Kanns sein, dass sich da irgendeine Optimierung einmischt?
Sowas wie -ffunction-sections oder -fdata-sections?
Vincent H. schrieb:> Buskollisionen dürfte es dann ja nur geben, wenn man aus dem SRAM2> gleichzeitig Code ausführt und Speicherzugriffe tätigt?
Ja, wobei "Speicherzugriffe" natürlich auch Stack sind, weil der auf dem
Datenbus daherkommt. Eine Steigerung ist also nur realistisch, wenn Du
ausschließlich Registerbefehle nutzt, und das geht nur in Assembler.
"Maximum performance" ist da etwas beschönigend: zwar hast Du, anders
als beim Flash, keine waitstates, insofern ist die Aussage korrekt. Aber
der ART maskiert die Waitstates beim Flash schon sehr gut. Von den
Buskollisionen sagt ST da natürlich nichts. Hilft nur messen mit dem
Oszi und gucken, ob es für Deine Routinen was bringt. Nur daß Du Dich
halt nicht so stark wunderst, wenn es sogar langsamer ist.
Ich würde übrigens SRAM und SRAM2, also die 128kB, im Linkerfile nicht
als zwei Bereiche markieren, sondern als einen kontinuierlichen.
Zusammenhängender Speicher ist immer besser.
Zum Verschwinden der Variablen: Welche Compiler- und Linkeroptionen
nutzt Du? Optimierungslevel, LTO, Entsorgung ungenutzter Teile?
(gc-sections oder irgendwie sowas)
Tip: Manchmal hat man Variablen oder Funktionen, die man nicht nutzt
(oder nicht sichtbar nutzt), die aber nicht wegoptimiert werden sollen.
Da hilft beim gcc dann das __attribute "used".
-O0, -g3
Auf die Variablen greif ich extra in einer Assembler Funktion zu.
Generell würd ich den Speicherbereich gerne für Assembler Sachen nutzen,
daher kommt mir die Trennung eigentlich recht gelegen.
gc-sections nutz ich in Verbindung mit
-ffunction-sections und
-fdata-sections
Ich bin immmer noch planlos. Sofern man eine C-Variable inkl.
section("..") anlegt findet man die entsprechenden Initialisierungswerte
immer im Flash. Auch die, die direkt in Assembler angelegt wurden.
Wirklich weh tut das Wegrationalisieren erst bei RAM-Funktionen. Wenn
sich die im Flash nicht wiederfinden, dann gute Nacht...
OK, dann nimm die ganzen GCC-Compilerparameter mit den section-Sachen
mal raus, sowohl beim Compiler wie auch beim Linker. Also die
function-sections, gc-sections, alles mal raus.
Wenn Du die Funktion im RAM willst, dann wirst Du sie wohl explizit in
die Data-Sektion legen müssen, weil GCC einem gerne auch mal Variablen,
die nicht beschrieben werden, einfach wie const behandelt (auch ohne
const!) und sie dann in RO-Data legt. Spart ja RAM.
Attribut "used", dann wird es nicht wegoptimiert, align(4) damit
32bit-Thumb2-Befehle auch korrekt aligned werden und dann noch eines,
damit es eben in der Data-Sektion landet anstatt in der Text-Sektion.
Bedenke auch, wenn Deine Funktion absolute Werte lädt, daß der ARM da
etwas eingeschränkt ist und deswegen unerwartet doch aufs Flash
zugreift. Direkt im Opcode gehen IIRC nur Werte, die man als geshiftete
8-bit-Werte darstellen kann, also etwas wie 0xff00, aber nicht 0xf0f0.
Lösung ist, solche Werte in eine lokale static-Variable zu laden und
dann die Variable statt des absoluten Wertes zu nehmen.
Und aus
https://www.embeddedrelated.com/showthread/lpc2000/5031-2.php#tabs1-chronological
:
"So to summarize, to get and use a function in RAM you only need to do
three things:
(1) Have an init in the startup code for the .data section.
(2) Use the __attribute__((section(".data"))) on the functions.
(3) Define function pointer variables for the functions and make
indirect calls using those."
-O0 oder -O3, -gc-sections oder nicht, alles irrelevant. Außer dem
Aligment scheint nichts die Initialisierungswerte fürs Flash zu
beeinflussen...
Ich habs mittlerweile ehrlich gesagt auch aufgegeben.
Vincent H. schrieb:> Was ich in Bezug auf> dessen Linker Skript dann nicht verstehe ist, wieso ST hier plötzlich so> gut wie alle Sections 8-Byte aligned hat?
Wenigstens der Stack sollte 8-Byte aligned sein. Braucht man für
FPU-fähige ARM Cores, auch bei Cortex-M, und ist daher AFAIK eine
Vorgabe des ABI.
Antwort von ST:
Dear customer,
the 8byte alignment is required due to few reasons (but not mandatory):
The ST ART Flash accelerator reads the flash using a 64bit wide word.
Therefore it is performance-wise recommended to use such alignment on
code and data sections in flash.
The other reason is that the STM32L4 bootloader cannot deal with
4byte-only alignment properly, when crossing memory pages/banks, as per
the application note AN2606 section 45.3 "Known limitations" in the
table below.
Best regards,
ST MCU Support Team