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.

 

Testing, testing

 

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!.

 

Huidige situatie

 

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.

 

Inhoud van het RAM

 

Als we de inhoud van het RAM eens nader bekijken, valt op dat de getalwaarde ABCD “achterstevoren” in het geheugen is terecht­gekomen, 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 flash­geheugen 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.

 

Testsuite John Hayes

 

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.


Hoe nu verder ?

 

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 aan­gepakt, 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 voedings­spanning op de testprint aangesloten en . . . .  het werkt!

 

Snel de John Hayes test uitgevoerd en geheel volgens ver­wachting: 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: