Forth vanaf de grond 12
Ron Minke
Hier is het twaalfde deel van het verhaal om een AVR Forth systeem te maken met als uitgangspunt “from scratch”. Dit deel gaat over de indeling van het RAM geheugen en de overwegingen daarbij.
In de vorige aflevering van ‘Forth vanaf de grond’ heb ik het gehad over de indeling van de bytes bij de stackvolgorde. Na enig heen- en weergeschuif van beslissingen is er nu een stabiele situatie ontstaan die ook aan de ANSI standaard voldoet (zie ANSI document punt 3.1.4). De indeling van diezelfde bytes in de dictionary in het RAM geheugen (weet u nog: deze Forth versie kopieert zichzelf naar RAM) is een geheel andere zaak. Tijdens het testen van de Forth kernel met een aangepaste versie van de John Hayes testsuite bleek er iets fout te gaan met de uitvoering van de woorden 2@ en 2!.
De inzichten uit deel 11 van deze serie zijn toegepast. In de Forth-kernel (in het flashgeheugen) is het woord LIT, die het ophalen van een getal uit de dictionary verzorgt, gedefinieerd als:
0630:
LIT:
83 lengte van de tekst 'LIT' met msb=1
4C 'L'
49 'I'
D4 'T' met msb=1
0620 link naar vorige woord
0200 pointer naar Code_Lit: machinecode voor LIT
De bijbehorende machinecode:
0200:
Code_Lit:
Ld R16,X+ ; get lo value from RAM dictionary
Ld R17,X+ ; get hi value, auto update IP to next word
St -Y,R16 ; store lo value on data stack
St -Y,R17 ; store hi value
Next
Ergens in de dictionary op plaats 730 (in de RAM) staat een stukje Forth welke een getal met de waarde ABCD op de datastack zet:
0730:
.dw xxxx
.dw yyyy
.dw LIT
.dw 0xABCD
.dw zzzz
Een hexdump van dit stukje RAM laat zien:
0730: xx xx yy yy 30 06 CD AB zz zz .......
De machinecode van LIT haalt de waarde ABCD op uit het RAM en plaatst die op de stack.
Code_Lit ( --- n ) tmp. data
AVR stack
input output reg. adres
SP - > 0 0 --- 0x100
1 1 n lage byte = CD R16 0x0FF
2 SP - > 2 n hoge byte = AB R17 0x0FE
3 3 0x0FD
Tot op dit punt is er niets bijzonders aan de hand. Forth werkt zoals hij het moet doen.
Als we de inhoud van het RAM eens nader bekijken, valt op dat de getalwaarde ABCD “achterstevoren” in het geheugen is terechtgekomen, evenals de locatie van het woord LIT (=0630). Voor de werking van de Forth maakt dat natuurlijk helemaal niets uit; de bijbehorende stukjes machinecode verwerken de informatie correct. Toch blijft hierover een ontevreden gevoel achter. Een test met een “double” in de assembly sourcecode laat zien:
.org 0x410
.dd 0x12345678
Dit komt na vertaling door de Atmel assembler in het flashgeheugen terecht als:
0410:
5678
1234
Deze waarde komt na het kopiëren naar de RAM bij een hexdump eruit te zien als:
0640: 78 56 34 12 .......
Ook hier staat dit getal voor het gevoel “achterstevoren”. Bovendien valt op dat het “high word” van dit getal achteraan staat. En dat stemt niet overeen met wat in de ANSI-standaard staat.
Het vermoeden dat er toch iets niet correct is in de Forth kernel wordt bevestigd door een stukje uit de testsuite van John Hayes. Bij de test van de woorden 2@ en 2! komt er een foutmelding dat de resultaten op de datastack niet kloppen.
De oorspronkelijke code in
de Forth kernel voor het woord 2@ is
(met toevoeging van de getalwaarden uit de RAM):
; 2@ Replace the 16-bit address on top of
; the data stack with the 32-bit
; nucleus contents d of that memory address.
;
; addr --- d
;
; input output
;
; 0 0
; 1 lo byte addr = 40 1 lo byte lo word d = 78
; Dsp --> 2 hi byte addr = 06 2 hi byte lo word d = 56
; 3 3 lo byte hi word d = 34
; 4 Dsp --> 4 hi byte hi word d = 12
; 5 5
Code_TwoAt:
Ld ZH,Y+ ; get hi address = 06
Ld ZL,Y+ ; get lo address = 40
Ld R18,Z+ ; get lo value lo word = 78
Ld R19,Z+ ; get hi value lo word = 56
Ld R16,Z+ ; get lo value hi word = 34
Ld R17,Z+ ; get hi value hi word = 12
St -Y,R18 ; put on data stack
St -Y,R19
St -Y,R16
St -Y,R17
Next
Als we met deze machinecode de getalwaarde op adres 640 ophalen komt dat in de correcte volgorde op de datastack te staan.
Maar . . . . in de ANSI standaard staat bij de bespreking van het woord 2@ (punt 6.1.0350) het volgende: “. . . . . . . Het woord 2@ is gelijk aan het uitvoeren van de volgende serie woorden” :
DUP CELL+ @ SWAP @
Als we dit uitvoeren met
diezelfde getalwaarde 12345678 op
adres 640 in het RAM dan komt er iets geheel anders tevoorschijn. Even
naspelen:
Start stack
--- 0640
DUP 0640 0640
CELL+ 0640 0642
@ 0640 1234
SWAP 1234 0640
@ 1234 5678
Als we even in herinnering nemen dat (volgens ANSI) de waarde op de bovenste stackpositie de MSD van een getal moet zijn dan zie we dat dit hier niet het geval is. Het resultaat van de John Hayes test is dus correct: de huidige implementatie van het woord 2@ levert niet het juiste resultaat.
Om een en ander te laten voldoen aan de ANSI standaard moeten er blijkbaar woorden en bytes worden verwisseld. Voor de machinecode van LIT is dat simpel: de waarde uit het RAM “andersom” ophalen. Maar daarmee hebben we de oorsprong van het probleem nog niet aangepakt. Het werkelijke probleem is de volgorde van de bytes in het flashgeheugen bij de .dw statements.
Een standaard aanroep om een getalwaarde in het flash te zetten
.org 0x540
.dw 0xABCD
komt, na assembleren en kopiëren naar het RAM, terecht als
0540: CD AB .......
Maar eigenlijk willen we dat deze waarde hier verschijnt als
0540: AB CD .......
De oplossing hiervoor is gelukkig eenvoudig: het definiëren en daarna toepassen van een macro die de bytevolgorde omdraait bij het assembleren. Deze macro geven we de naam defwrd.
.macro defwrd
.db high(@0), low(@0)
.endmacro
De aanroep van het testgetal ABCD wordt dan
.org 0x540
defwrd 0xABCD
en na assembleren en kopiëren naar de RAM komt er te staan
0540: AB CD .......
Deze wijziging heeft heel veel impact op de assembly sourcecode! Gelukkig heeft de editor een “global replace” functie die dit klusje doet: alle .dw naar defwrd veranderen.
Rest ons nog het aanpassen van de machinecode voor het woord 2@. U ziet: het volgorde van registergebruik is anders.
Code_TwoAt:
Ld ZH,Y+ ; get hi address
Ld ZL,Y+ ; get lo address
Ld R17,Z+ ; get hi value hi word
Ld R16,Z+ ; get lo value hi word
Ld R19,Z+ ; get hi value lo word
Ld R18,Z+ ; get lo value lo word
St -Y,R18 ; put on data stack
St -Y,R19
St -Y,R16
St -Y,R17
Next
Niet alleen de machinecode voor het woord 2@ moet worden aangepakt, alle woorden die iets met RAM geheugentoegang doen moeten worden gewijzigd. Het gaat om de woorden
LIT EXECUTE BRANCH ! @ +! 2@ 2! HERE (FIND) RP! SP! DoConstant DoUser ((EMIT)) NEXT
Na deze veelomvattende actie heb ik de complete sourcecode door de assembler heengehaald en in de flash gezet, de voedingsspanning op de testprint aangesloten en . . . . het werkt!
Snel de John Hayes test uitgevoerd en geheel volgens verwachting: geen fouten!
Oef . . . heel veel werk
deze keer en dat allemaal door zo’n “simpele fout”. Het is echter wel een
volgende stap naar het
“Forth vanaf de grond” systeem !
- - - - - - -
De John Hayes
testsuite: