Tag: 6502

  • L’assembleur de l’Oric1 et Atmos – Programmation en Langage Machine

    Sommaire :

    CHAPITRE I : INTRODUCTION ET RAPPELS
    Les systèmes de numération
    La présentation des nombres
    Les opérations
    Le système Oric
    Le microprocesseur 6502A
    Les codes machine
    CHAPITRE II : OPERATIONS DE BASE
    Exploitation des programmes en C.M.
    Les codes machine de chargement
    Les codes machine qui réalisent les opérations arithmétiques
    CHAPITRE III : BOUCLES, COMPARAISONS, TESTS
    L’écran basse résolution
    Les branchements conditionnels relatifs
    Les comparaisons
    Les opérations logiques
    Les instructions BIT
    CHAPITRE IV : HAUTE DEFINITION, ATTRIBUTS
    Quelques codes machine
    Les glissements et les rotations
    Les attributs série
    L’écran haute résolution
    CHAPITRE V INTRODUCTION AUX TECHNIQUES AVANCEES
    Quelques programmes
    Délai programme
    Génération de nombres pseudo-aléatoires
    L’animation
    CHAPITRE VI : LA MEMOIRE MORTE
    Quelques routines
    Points d’entrée de quelques routines
    Différents moyens d’appeler des codes machine
    ANNEXES
    Annexe I
    Annexe II Liste alphabétique des mnémoniques
    Annexe III : Influence des mnémoniques sur les
    indicateurs
    Annexe IV Table des nombres avec signe pour les valeurs négatives
    Annexe V Conversion Hexadécimal – Décimal

    [wpdm_package id=’2863′]

    Téléchargement ouvert à tous

  • Micro UK101 + Mod

    Micro UK 101, un ordinateur à réaliser soi-même avec quelques composants “old fashion”.


    Micro UK 101 + Mod, avec ajout de modifications.

    UK 101 ?
    L’ordinateur Compukit UK101, paru en 1979 en Angleterre, pouvait s’acheter en kit ou déjà tout monté. Il était destiné au marché anglais. Pourvu d’un 6502 à 1 MHz, il utlisait 8 kb de ROM , 8 Kb de RAM, augmentée d’un Kb pour la sortie vidéo UHF. Le clavier et l’alimentation faisaient partie de la carte mère, complétée par un port extension à 40 broches. La lecture et la sauvegarde de données pouvait se faire sur cassette mono à 300 bauds. L’affichage sur écran (TV) était de 16 rangées sur 48 colonnes, sans autre option vidéo.

    Micro UK 101 !
    Grant Searle, un passionne doué, fait revivre des ancêtres comme le 6502, le Z80 ou le 6809, en les allégeant beaucoup. Entre autres, il a créé le Micro UK 101, clone de l’UK 101, doté toujours du sempiternel 6502, mais avec maintenant 32 Kb de ROM, 32 Kb de RAM, un ACIA 6850 et quelques circuits logiques. Sans entrée clavier, ni sortie vidéo, le Micro UK 101 est à relier sur un ordinateur récent par un port COM. Sans carte son, ce petit bijou peut s’assembler sur une plaque d’essais type “Breadbord” ou sur une plaque pastillée avec fils point à point.
    Son site fourmille de documents et de conseils.
    Libre à vous de construire un micro des années 80 ou de vous lancer dans un clone à FPGA.


    Sur le Bay, on peut parfois trouver un kit. Celui-ci était déjà monté.

    On peut relier un adaptateur USB RS232 sur la prise DB9 ou un module USB TTL type FT232 sur les broches en bas à droite de la carte.
    Exemple de module FT232 facile à trouver sur le Bay :

    Les ordinateurs récents n’ont plus de port série à DB9, ni de port imprimante. D’où la première Mod faite à cette carte : retirer la DB9, le circuit ICL232 et ses condensateurs associés pour les remplacer par un connecteurs à 4 broches servant au module FT232, en y intercalant des résistances de protection. Et remplacer cette Led rouge par une bleue, avec une résistance de 2k2 pour être plus agréable à voir sans être ébloui ! Enfin, il suffit de dessouder le condensateur C17 et de le repositionner couché, pour gagner quelques millimètres en hauteur.
    Ainsi la carte sera placée entre un support en plastique et une plaque transparente, avec des entretoises de bonne hauteur et quelques boulons.

    Voici le résultat de la Mod 1 :

    Notez les bandes rouge et noire autour du connecteur à 4 broches…

    Deuxième Mod à faire : une petite carte pour relier le module FT232 directement sur la carte sans utiliser de câblage. Un interrupteur petit modèle permettra d’alimenter la carte à partir du PC, opération visualisée par la Led verte..

    Les bandes rouge et noire servent de détrompeur…
    (Rouge pour VCC, noire pour GND)

    L’utilisation du Micro UK 101 est facilitée par ces deux Mods.

    Il ne reste plus qu’à le tester sur PC, en utilisant par exemple un logiciel comme PuTTY et en réglant le port COM relié au FT232 à 19200 bauds.

    Et voici l’écran d’accueil :

    Un petit programme en Basic pour tester l’ensemble :

    Et ça fonctionne !

    Déjà cette carte permet de se faire le main sur le 6502 sans passer par l’Oric et ses périphériques (alimentation, adaptateur RGBS-HDMI, écran LCD ou autre). Le Basic a beau être limité (8 Kb seulement au lieu des 16 Kb de notre Oric), il permet néanmoins de progresser et de découvrir le programmation en langage machine…
    C’est déjà un bon point pour cette petite carte !

    Mais il lui manque de quoi communiquer avec l’extérieur, autre que le PC auquel elle est reliée. Interagir avec quelques Leds et des interrupteurs ou des boutons poussoirs serait un sacré plus.

    Qu’à cela ne tienne ! Il suffit de lui rajouter un 6522 (ou un 65C22) !
    Comme celui qui se trouve dans l’Oric…
    C’est l’objet de la troisième Mod.



    Une petite plaque PCB pastillée sera installée à côté de la carte et supportera le 6522 avec moult connecteurs (pour les ports A et B, les sorties CA et CB). Des fils fins relieront le 6522 au 6502 et il reste à choisir une sortie du 74LS138 pour l’adresse à utiliser (arbitrairement ici, la 5, soit à partir de #D000).

    Il faudra redimensionner les plaques en-dessous et au-dessus du nouvel ensemble, et cela donne :

    Format 10 x 15 cm !

    Afin d’avoir un aperçu visuel des ports du 65(C)22, cette petite carte facile à réaliser s’avère très utile :

    2 circuits ULN2803 et 16 Leds



    Exemple avec un chenillard de type “K 2000” sur 16 Leds :

    Et vu du dos de la carte à 16 Leds :


    Ce petit ordinateur, même limité en périphériques, est maintenant capable de communiquer avec l’extérieur après ces quelques modifications. Facile à transporter, une fois relié à un PC, il s’avère agréable d’emploi et facile à programmer. Le stockage des fichiers se fait directement sur le disque dur du PC.

    Un grand merci à Grant pour ce cousin de notre Oric !


  • Fonctionnement de la pile matérielle du 6502 (4/8)

    PARAMÈTRES ET SOUS-ROUTINES

    L’entrée et la sortie de données nécessaires au fonctionnement d’une sous-routine, peut se dérouler de quatre façons :

    1. En passant par les registres (A, X et / ou Y). C’est la meilleure méthode et la plus performante lorsqu’il s’agit de données constituées d’un ou deux octets
    2. En passant par des variables en RAM. Cette méthode est parfaite pour des données qui doivent rester en RAM et qui doivent servir à d’autres routines. Elle est souvent utilisée de manière conjointe à la première méthode.
    3. Les besoins en entrées de la sous-routine peuvent être intégrés au programme lui-même, juste après l’instruction JSR (comme par exemple une chaîne alphanumérique à afficher). Dans ce cas, la sous-routine utilise l’adresse de retour stockée sur la pile en guise d’adresse de début des données. Elle peut aussi utiliser un octet qui détermine le nombre de données ou un délimiteur afin de savoir de combien d’octets avancer l’adresse de retour avant d’exécuter l’instruction RTS (et d’éviter que le processeur ne traite les données comme une instruction).
    4. En passant par la pile matériel.
      • Pile en page 1 : le programme appelant empile la (ou les) données grâce à PHA, puis récupère le (ou les) résultats grâce à PLA, au retour de la sous-routine.
      • Pile virtuelle en page 0 : Idem précédemment, avec une pile de données différentes de la pile matérielle, ce qui peut faciliter certaines opérations sur la pile et l’adressage indirect. Par exemple, la pile de données peut être utilisée dans un processus en plusieurs étapes pour calculer une adresse, adresse qui à son tour peut être utilisée pour un accès indirect à un octet dans un tableau pour faire un calcul ou une opération logique.
      • Pile en RAM : Idem pile virtuelle en page 0, mais pouvant typiquement servir pour des nombres en virgule flottante autres que des adresses nécessitant les modes d’adressage disponibles en page 0.

    Examinons un exemple dans lequel il s’agit d’utiliser la pile matériel pour une donnée d’entrée. Dans cet exemple, notre sous-routine transforme un demi-octet hexadécimal en octet ASCII, avec un octet en entrée et un octet en sortie, le tout stocké sur la pile matériel.

    <do_stuff>      ; Get the nybble into A.  Allowable value is 0-F.
    PHA             ; Other code can be put between the PHA and the PLA, as long
    JSR  NYB2ASCII ; as it doesn't care if NYB2ASCII overwrites A and X. The
    PLA             ; subroutine's input and output are protected on the stack though.
    <do_stuff>      ; Do something with the ASCII output, like add it to a string, display it, etc..

    La sous-routine NYB2ASCII pourrait ressembler à ceci.

    NYB2ASCII:

    TSX

    LDA  $103,X     ; 103 reaches past the return address, to the input parameter.

    CMP  #$0A       ; Anything below $0A will end up in the $30’s.

    CLC             ; CLC before the BMI so we only have to do it once.

    BMI  n2a1       ; For 9 or less, skip the next instruction.

    ADC  #7         ; $0A becomes $11, $0B becomes $12, etc..  C is still clear.

     n2a1:  ADC  #$30       ; Whether the 7 got added above or not, this gives the ASCII.

    STA  $103,X     ; Put it on the stack, overwriting the input value.  Note

    RTS             ; that we read and overwrote the byte just behind the

    ; return address, leaving the return address undisturbed.

    Voici un autre exemple dans lequel deux nombres de 16 bits non signés en entrée sur la pile, sont multipliés pour donner un nombre de 32 bits non signé, en sortie sur la pile.

    On empile tout d’abord les deux nombres à transformer, chacun avec le poids fort en premier, de sorte que le poids fort prenne l’adresse la plus haute comme dans le fonctionnement habituel du 6502.

    <do_stuff>     ; Get high byte of first input,

    PHA            ; and push it.

    <do_stuff>     ; Get low  byte of first input,

    PHA            ; and push it.

    <do_stuff>     ; Get high byte of second input,

    PHA            ; and push it.

    <do_stuff>     ; Get low byte of second input,

    PHA            ; and push it.

    JSR  UM_STAR   ; Now you can call the subroutine below that does the multiplying.

                   ; If you pull the product off the stack now, the byte order will be:

    PLA            ; 2nd-highest byte

    <do_stuff>

    PLA            ; high byte

    <do_stuff>

    PLA            ; low byte

    <do_stuff>

    PLA            ; 2nd lowest byte

    <do_stuff>

    Selon les opérations que vous souhaitez réaliser ensuite, vous pouvez très bien laisser le premier résultat sur la pile.

    Si l’opération après la multiplication des deux nombres est de calculer la racine carrée pour en obtenir la raison géométrique, alors il convient de ne pas toucher à la pile tant que cette seconde opération n’est pas effectuée (la raison géométrique équivaut à une moyenne logarithmique. La raison géométrique de 1 et 100 est de 10, et non de 50,5).

    Pour accéder aux données stockées sur la pile sans les dépiler et dans l’ordre de votre préférence, vous pouvez continuer d’utiliser X comme index de pile (ainsi que décrit dans l’exemple UM_STAR ci-dessous). En cas de TSX, vous devrez ajuster l’index en tenant compte du fait que l’adresse de retour n’est plus sur la pile, et comme décrit ci-après, qu’un PLA a déplacé les résultats sur la pile de $101,X à $104,X. Par contre, sans TSX l’index est toujours valide et pointe bien sur les bonnes données.

    UM_STAR: LDA #0                 ; Unsigned, mixed-precision (16-bit by 16-bit input, 32-bit output)

             PHA                    ; multiply.  Add a variable byte to the stack, initializing it as 0.

             TSX                    ; Now 101,X holds that new variable, $102,X and $103,X hold the return address

             LSR $107,X             ;and $104,X to $107,X holds the inputs and later the outputs.

             ROR $106,X

             FOR_Y  16, DOWN_TO, 0  ; Loop 16x.  The DEY, BNE in NEXT_Y below will drop through on 0.

                 IF_CARRY_SET

                     CLC

                     PHA            ; Note that the PHA (and PLA below) doesn’t affect the indexing.

                        LDA $101,X

                        ADC $104,X

                        STA $101,X

                     PLA

                     ADC $105,X

                 END_IF

                 ROR

                 ROR $101,X

                 ROR $107,X

                 ROR $106,X

             NEXT_Y

             STA $105,X

             PLA                    ; Retrieve the variable byte we added at the top, cleaning up the stack.

             STA $104,X             ; Again note that the PLA changed S but not X, so the $104 is still $104.

             RTS

    Note : Dans de nombreux cas, il serait plus judicieux de nommer les données présentes sur la pile, en utilisant la macro EQU, plutôt que de se contenter de $101,X ou équivalent.

    Supposons maintenant que vous ayez besoin d’une sous-routine avec quatre octets en entrée et six octets en sortie.

    Afin de servir de la pile matérielle pour deux données supplémentaires en sortie, il faut donc résoudre le problème de l’adresse de retour qui ne sera pas en haut de la pile lors du RTS.

    La meilleure méthode consiste à faire de la place sur la pile avant d’appeler la sous-routine :

    PHA              ; Push two dummy bytes onto the stack to hold the

    PHA              ; positions open for outputs of the subroutine called below.

    <do_stuff>       ; Prepare the subroutine input bytes to be passed.

    JSR  subroutine  ; Without changing the stack pointer, the subroutine

    ; can now give you two more bytes of output than input.

    Autre cas pratique, celui où vous auriez besoin de plus de données en entrées qu’en sortie.

    Dans ce cas, il suffit de dépiler les données inutiles après être sorti de la sous-routine.

    Ainsi, pour restaurer la hauteur de pile, faites :

    <do_stuff>        ; Set up inputs, reserve output byte places in stack, etc..

    TSX               ; Mark the current stack position.

    <do_more_stuff>

    JSR  <subroutine>

    <do_stuff>        ; Handle outputs, etc..

    TXS               ; Restore stack to the marker set earlier, possibly also

    ; to put certain outputs at the top.

    Évidemment le registre X ne devra pas être modifié par la sous-routine, ou alors il faudra le sauvegarder à son tour sur la pile.

    Comme décrit précédemment, la pile matériel n’est pas la seule solution en tant que pile de stockage. Une pile en page zéro (ZP) présente par exemple l’avantage de disposer de modes d’adressages supplémentaires.

    Utiliser une autre pile que la pile matériel permet aussi de résoudre le problème suivant : admettons que la pile matériel en page 1 serve pour passer des paramètres à une sous-routine qui elle-même doit fournir des paramètres à autre sous-routine. Lors de l’entrée dans la seconde sous-routine, l’adresse de retour se retrouve donc désormais au sommet de la pile, tandis que les données d’entrée sont décalées vers le bas, ce qui donnera à coup sûr des résultats erronés.

    Mettons en œuvre cet exemple avec la sous-routine GEOMEAN.

    GEOMEAN: JSR  UM_STAR  ; Multiply two 16-bit unsigned inputs on the stack.  Get 32-bit product.

             JSR  SQRT     ; Take a 32-bit input on the stack and get a 16-bit output, leaving two

             RTS           ; dummy bytes on stack (problem: GEOMEAN’s return addr is still on top!)

    Supposons que vous commenciez par empiler $1234 sur la pile matériel (poids fort en premier, sur le dessus de la pile), puis que vous empiliez $5678 avant d’appeler la sous-routine GEOMEAN.

    Lorsque le pointeur de programme attend la première instruction après PHA et TSX dans le programme UM_STAR, UM_STAR part à la recherche du premier nombre à multiplier ($1234 dans notre exemple), censé se trouver aux adresses $106,X et $107,X, puis du second nombre ($5678 dans l’exemple) qui se trouve aux adresses $104,X et $105,X. L’adresse de retour se trouve quant à elle indexée par $102,X et $103,X.

    Il y a cependant maintenant deux adresses de retour au sommet de la pile : celle qui a fait appel à UM_STAR et celle qui a fait appel à GEOMEAN. UM_STAR va donc ignorer $1234 et multiplier $5678 avec l’adresse de retour de GEOMEAN, entraînant ainsi un résultat erroné et l’écrasement de l’adresse de retour de GEOMEAN. C’est le plantage assuré !

    Vous vous apercevez également que le fait d’utiliser JMP au lieu de la paire d’instruction JSR / RTS peut entraîner des problèmes d’incompatibilité liés à une mauvaise gestion des données empilées sur la pile matériel.

    La pile en page zéro permet de contourner ce genre de problème, tout simplement parce que l’adresse de retour n’est pas empilée sur cette dernière.

    En plus de l’adressage de la page zéro qui est plus performant que celui de la pile matériel, signalons aussi les bénéfices suivants :

    • Inutile de replacer les paramètres d’entrée sur la pile sans arrêt, avec pour résultat immédiat une exécution plus rapide des étages de la pile ;
    • Réduction des effets liés à la récursivité (ce sujet sera abordé dans un prochain article) ;
    • Possibilité de placer plusieurs type (et quantité) de valeurs ;
    • Code plus compact qui permet un meilleur fonctionnement car plus besoin de devoir empiler/dépiler des paramètres lors de l’appel de sous-routines.

    Revenons maintenant à notre sous-routine qui transforme un demi-octet hexadécimal en octet ASCII et voyons comment réaliser cette transformation en utilisant une pile ZP.

    X servira de pointeur de pile, initialisé en début de programme pour indexer de la manière suivante $00,X.

    Pour empiler en page zéro, il faut donc faire un DEX puis stocker en $0,X.

    Voici ce que donne la routine modifiée pour la pile ZP :

    <do_stuff>      ; Get the nybble into A.  Allowable value is 0-F.

    DEX

    STA  0,X        ; Other code can be put between the STA and the LDA, and

    JSR  NYB2ASCII  ; NYB2ASCII’s input and output are protected on the stack.

    LDA  0,X        ; X must be preserved as the data stack pointer of course.

    INX

    <do_stuff>      ; Do something with the ASCII output, like add it to a string, display it, etc..

    Note : Si vous avez besoin du registre X dans le reste de votre programme, il vous faudra le sauver sur la pile matériel.

    La sous-routine NYB2ASCII devient :

    NYB2ASCII:              ; (The initial TSX is no longer necessary.)

            LDA  0,X        ; Get the input parameter from the data stack.

            CMP  #$0A       ; Anything below $0A will end up in the $30’s.

            CLC             ; CLC before the BMI so we only have to do it once.

            BMI  n2a1       ; For 9 or less, skip the next instruction.

            ADC  #7         ; $0A becomes $11, $0B becomes $12, etc..  C is still clear.

     n2a1:  ADC  #$30       ; Whether the 7 got added above or not, this gives the ASCII.

            STA  0,X        ; Put it on the data stack, overwriting the input value.  Note

            RTS             ; that the return address is not on this stack to worry about.

    Procédons selon la même logique pour la sous-routine UM_STAR.

    UM_STAR: DEX                    ; Add a stack byte to use as a temporary variable.

             LDA  #0

             STA  0,X               ; 0,X addresses the temporary variable.  These are in ZP.

             LSR  4,X

             ROR  3,X

             FOR_Y  16, DOWN_TO, 0

                 IF_CARRY_SET       ; The 1st time thru the loop, A needs 0; so don’t use STZ above.

                     CLC

                     PHA

                        LDA  0,X

                        ADC  1,X

                        STA  0,X

                     PLA

                     ADC  2,X

                 END_IF

                 ROR

                 ROR  0,X

                 ROR  4,X

                 ROR  3,X

             NEXT_Y

             STA  2,X

             LDA  0,X

             STA  1,X

             INX                    ; Take back the stack byte we used as a temporary variable.

             RTS

     ;——————

    Ici, un octet est empilé sur la pile ZP grâce à l’instruction DEX (puis dépilé grâce à INX), de la même manière que pour PHA (puis PLA). La pile ZP permet donc d’éviter d’avoir à sauver/restaurer l’index X.

    Il est en outre possible de se passer des instructions DEX/INX en adressant le nouvel octet grâce à $FF,X qui permet de rester en page zéro et qui économise au passage 2 octets et 4 cycles d’horloge.

    Assurez-vous cependant de n’avoir aucune sous-routine ou interruption susceptible d’écraser cet octet, car X semble indiquer que cet emplacement est libre.

    La sous-routine GEOMEAN quant à elle, peut désormais s’écrire :

    GEOMEAN: JSR  UM_STAR  ; Multiply two 16-bit unsigned inputs on the stack and get a 32-bit product.

             JMP  SQRT     ; (JSR, RTS)  Take a 32-bit input on the stack and get a 16-bit output.

     ;——————

    Voici dans le détail les avantages à travailler avec des piles séparées :

    • Le nombre d’octets entrés est indépendant du nombre d’octets sortis. En d’autres termes, nul besoin de nettoyer les octets de sortie factices ni de placer des octets factices sur la pile ;
    • En disposant de données séparées, les adresses de retour ne sont pas traitées comme des données. Le paramétrage devient implicite ;
    • Une sous-routine peut faire appel à une autre sous-routine sans avoir à faire le ménage sur la pile matériel (qui devient désormais la pile de retour) ;
    • La pile matériel est maintenant disponible pour d’autres usages tels que la gestion des limites et des index de boucles (y compris des boucles imbriquées) ;
    • L’ajout d’une nouvelle pile permet d’augmenter l’espace de stockage et d’éviter un crash sur la pile matériel ;
    • Grâce aux modes d’adressages du 6502 en page zéro (ZP,X), nous pouvons adresser la pile ZP très facilement.

    Voyons justement comment mettre à profit les modes d’adressage ZP,X dans l’exemple de routine de déplacement suivante.

    Nous appellerons cette routine CMOVE, pour “character move”.

    Avant de faire tourner cette routine, nous plaçons six octets sur la pile ZP, sous la forme de 3 nombres de 16 bits (poids fort sur l’adresse la plus haute dans chaque cas) :

    • Adresse de départ
    • Adresse de destination
    • Taille de mémoire à déplacer (pouvant aller jusqu’à quelques dizaines de milliers d’octets)

    CMOVE:  LDA  0,X      ; “See-move” Character (memory) move  ( from to len — )

            ORA  1,X

            BEQ  POP3     ; If remaining length is 0, branch to POP3 which is just 6 INX’s, then RTS.

            LDA  (4,X)    ; Get a byte and

            STA  (2,X)    ; transfer it.

            INC  4,X

            BNE  cm1$

            INC  5,X      ; Increment the source addr

     cm1$:  INC  2,X

            BNE  cm2$

            INC  3,X      ; and the destination addr,

     cm2$:  DEC  0,X      ; decrement the count left,

            LDA  0,X

            CMP  #$FF

            BNE  CMOVE

            DEC  1,X

                          ; and go back up for another loop.  If we’re done,

            BRA  CMOVE    ; that fact will be caught in the first three lines (BRA is for 65c02 only and stands for Branch Always).

    Quand faut-il éviter l’emploi d’une pile pour l’usage de paramètres d’entrées / sorties dans des sous-routines ?

    Déplacer des données dans une pile requiert du temps processeur.

    Aussi, si vous devez manipuler une grande quantité de données, comme une chaîne alphanumérique, un tableau ou même une matrice, placez là ailleurs en mémoire et indiquez son adresse de stockage sur la pile.

    RÉCURSIVITÉ

    Une sous-routine peut présenter la particularité d’être récursive, à savoir qu’elle peut s’appeler elle-même à de nombreuses reprises.

    Afin d’éviter que ce genre de sous-routine ne reboucle indéfiniment, entraînant une surcharge de la pile matériel, il faut prévoir une condition qui permette de quitter la sous-routine au bout de x boucles récursives.

    Lorsque cette (ou ces) condition(s) est respectée, on quitte alors la sous-routine par une série de RTS.

    Chaque sous-routine nidifiée (imbriquée) peut disposer de ses propres variables ayant  pourtant des noms semblables.

    Prenons l’exemple de la fonction de Fibonacci qui est utilisée en calculs financiers, lors de la génération de nombres pseudo aléatoires, dans des algorithmes de tri et de recherche ou encore pour la compression de fichiers audio.

    fiboIO:    SETL $103    ; For clarity, give names to the locals.

    fiboTemp:  SETL $102    ; (Record of X will be at $101,X.)

    FIBO:  CMP  #2          ; Test the input.

           BCC  end         ; If it’s 0 or 1, so is the output, so just end.

                            ; This prevents endless nesting, too.  Otherwise,

           PHA              ; create local variable fiboIO, and store A there.

           PHA              ; Create another local variable byte, fiboTemp.

                            ; (Its initial value doesn’t matter.)

           PHX              ; Push X too, to protect the calling routines.

           TSX              ; Prepare X to use for stack-relative addressing.

           DEA              ; Get the n-1 term

           JSR  FIBO        ; and calculate the number for it (recursing here)

           STA  fiboTemp,X  ; and store its result in local variable fiboTemp.

           LDA  fiboIO,X

           DEA

           DEA              ; Now get the n-2 term

           JSR  FIBO        ; and calculate the number for it (recursing here).

           CLC

           ADC  fiboTemp,X  ; Add those two together, and

           STA  fiboIO,X    ; store the answer.  Don’t forget the “,X”!

                            ; Note that there is no looping.

           PLX              ; Restore X for the calling routines.

           PLA              ; Pull fiboTemp off the stack and discard it.

           PLA              ; Pull the answer (fiboIO) off the stack into A.

    end:   RTS

    Cette sous-routine tient sur 34 octets et n’utilise pas la RAM pour le stockage, hormis bien sûr la pile matériel.

    La donnée d’entrée doit rester inférieure à 14 car la donnée de sortie est sur 8 bits et le nombre de Fibonacci pour 13 est 233.

    Avec une réponse sur deux octets ou plus, nous pourrions avoir une donnée d’entrée plus grande sans manquer de place sur la pile, même si ce genre de sous-routine en consomme beaucoup.

    La place disponible sur la pile est largement suffisante pour des sous-routines simples, mais rappelons que les routines récursives ne sont pas des routines normales.

    À suivre.

  • Fonctionnement de la pile matérielle du 6502 (1/8)

    EN GUISE D’INTRODUCTION

    Fasciné par certaines utilisations détournées de la pile de matériel du 6502, telles que les programmes de protections, j’ai voulu en apprendre davantage sur son fonctionnement.
    Je vous propose donc ici la traduction partielle d’un sujet dont la version originale se trouve à l’adresse suivante : http://wilsonminesco.com/stacks/

    QUELQUES DEFINITIONS

    Le type de pile dont il est question ici est une partie de la mémoire vive utilisée pour le stockage temporaire de données, qu’il s’agisse de variables ou d’adresses, avec comme mode d’accès, dernier entré, premier sorti (Last In First Out).
    On compare généralement ce type de pile à une pile d’assiettes dans un meuble à ressorts que l’on peut trouver à côté d’un buffet restaurant.

    La quantité de compression ou d’étirement des ressorts est proportionnelle au poids des assiettes que le meuble contient, de sorte que tant que le meuble n’est pas rempli complètement, un employé peut ajouter n’importe quel nombre d’assiettes récemment lavées et les assiettes seront toujours dans la même position au sommet, prêtes pour le prochain client.

    D’habitude, ni l’employé ni le client n’ont besoin de savoir combien d’assiettes chaudes sont stockées en dessous.

    On peut aussi prendre l’exemple d’une tige métallique avec un tas de papiers embrochés.  Vous pouvez voir les documents qui sont sous le sommet, et vous pourriez même lire ou écrire sur eux dans une certaine mesure sans retirer ceux qui sont au-dessus, mais le retrait ou l’ajout réel de documents a toujours lieu par le sommet.

    Le contraire d’une pile (ou mémoire LIFO) serait une mémoire de type premier entré, premier sorti (FIFO) ou file d’attente, qui peut dans ce cas être comparée au convoyeur d’une machine à rayons X dans un aéroport. Sur cette machine, les affaires passent d’un côté à un autre des rayons X, dans le même ordre qu’elles sont entrées.

    Contrairement à la pile d’assiettes, une pile de nombres dans la mémoire de l’ordinateur ne se déplace pas de haut en bas quand quelque chose est ajouté ou enlevé de la pile. Il serait en effet contre-productif de déplacer toutes ces données vers le haut ou vers le bas comme pour les assiettes, alors qu’il est plus simple d’utiliser un pointeur réglable pour savoir où se trouve le haut ou, en fait, dans le cas de la pile matériel du 6502, quelle est l’adresse RAM qui est la prochaine disponible pour mettre quelque chose sur la pile.

    Ce pointeur est le registre S (Stack pointer) du 6502.

    Dès lors, si l’analogie du distributeur d’assiettes ne tient plus, pourquoi l’évoquer ?

    En fait, ce qu’il convient surtout de retenir ici, c’est qu’habituellement, on ne s’occupe que de ce qui se trouve au-dessus de la pile, sans se soucier de la profondeur de celle-ci, comme un navire en pleine mer qui flotte de la même manière que la profondeur de l’eau soit de 100 mètres ou de 1000 mètres. Tant qu’une action ou une routine dispose des éléments de la pile dont elle a besoin, peu importe combien d’autres éléments sont plus bas dans la pile, en attendant d’être utilisés par d’autres routines ou parties d’une routine.

    La pile du processeur 6502 permet de stocker des adresses pour des sous-routines et des interruptions (moins fréquentes que pour des sous-routines) ainsi que des variables.

    C’est dans la première page du 6502 ($0100-$01FF) que réside la pile de matériel. Les tests montrent d’ailleurs que contrairement à une légende urbaine, cette zone mémoire est beaucoup plus grande que ce dont le programmeur a réellement besoin.

    Le pointeur de la pile de matériel est le registre S qui n’a que 8 bits de largeur; le 6502 ajustant automatiquement le numéro de page à $01 pour tous les accès à la pile. La pile de matériel en page 1 se développe vers le bas, et non vers le haut. Toute routine de réinitialisation doit donc initialiser le pointeur de pile au sommet de l’espace disponible. Il s’agit habituellement de l’adresse $01FF. Cette routine s’écrira simplement :

    LDX  #$FF ; Initialise X au sommet de la pile
    TXS ; Transfère dans le pointeur de pile

    Puisque la pile se développe vers le bas, le pointeur de pile se décrémente après chaque octet poussé sur la pile, et s’incrémente avant que chaque octet ne soit tiré hors de la pile.

    ADRESSES DE SOUS-ROUTINE ET NIDIFICATION

    Une sous-routine est une partie de votre programme dont vous avez besoin à de multiples reprises et à différents endroits. Au lieu d’avoir une copie de cette routine à chaque endroit où vous en avez besoin, vous l’écrivez une fois pour toutes et vous l’appelez depuis la portion de programme requise. Le compteur de programme se rend à cette sous-routine puis doit pointer de nouveau sur la routine d’appel pour reprendre l’exécution. Pour savoir où revenir, le compteur de programme (donc l’adresse de retour) a au préalable été sauvée sur la pile.

    C’est ce que réalise l’instruction JSR : « Jump, Saving Return address » (JSR est habituellement traduit par « Jump to SubRoutine », ce qui fonctionne tout aussi bien mais n’est pas tout à fait exact.) Supposons maintenant que cette routine doive en appeler d’autres. Comme chaque JSR met l’adresse de retour sur la pile, les adresses de retour nécessaires en premier lieu seront les dernières stockées sur la pile.

    La nidification est ce qui convient de mieux pour ce genre de situation. On appelle nidification (ou imbrication), le fait qu’une sous-routine en appelle une autre, qui à son tour en appelle une autre, et ainsi de suite. Si vous n’aviez pas besoin de la nidification, vous pourriez inscrire l’adresse de retour dans une variable ou un registre; mais vous vous apercevez que dès la première nidification, la variable serait écrasée et la première adresse de retour serait perdue.

    C’est là que la pile entre en jeu.

    Considérons à présent la nidification des sous-routines ci-dessous.

    Dans ce scénario, la pile contiendra les nombres hexadécimaux suivants aux points  (a), (b) et (c) ; (sans compter ce qui aurait déjà pu être sur la pile avant (a).

      (a)(b)(c)
    Sommet pile (*) $1915 $2632 $2F43
        $1915 $2632
          $1915

    (*) Top Of Stack (TOS)

    Ce tableau semble comporter des erreurs. En effet, au point (a)  l’adresse de la prochaine instruction est censée être $1916, et non $1915.

    En fait, l’instruction RTS (Return From subroutine) incrémentera l’adresse de retour et l’exécution du programme reprendra bien à $1916 (ce qui n’est pas le cas pour RTI, ReTurn-from-Interrupt, et fera l’objet d’un autre article).

    Puisque le bus de données et la mémoire du 6502 sont adressés sur 8 bits, chaque adresse de retour évoquée ci-dessus nécessite deux emplacements dans la pile. Lorsque JSR pousse l’adresse de retour (moins 1) sur la pile, il pousse d’abord l’octet haut, puis l’octet bas. N’oubliez pas que la pile grandit, et que le pointeur de la pile se décrémente lorsque les octets sont poussés sur la pile, de sorte que l’ordre d’octet résultant est l’octet de poids faible dans l’adresse mémoire la plus basse, comme le sont les autres adresses dans le 6502.

    Supposons par exemple que la valeur du pointeur de pile (registre S) était $EF avant que le code n’entre dans le diagramme de pile ci-dessus. Cela donnerait une occupation de l’espace mémoire de la pile selon le tableau suivant (ne sachant pas ce qui figurait de $EF à $FF, ces octets ne sont pas cartographiés).

    Lorsque vous quittez les sous routines et que vous revenez à la routine principale, voici à quoi ressemble la mémoire de la pile.

    Les nombres entre parenthèses sont toujours là, mais cette partie de la mémoire de la pile est libérée. Retirer quelque chose de la pile signifie simplement que le processeur en lit le contenu et incrémente ensuite le pointeur de la pile. Pouvez-vous utiliser ces chiffres, puisqu’ils sont toujours là ? Si vous avez des interruptions, la réponse est non, car une interruption pourrait survenir à tout moment et les écraser. Cela violerait le protocole LIFO et le processus des interruptions nécessite que ce protocole soit respecté.

    À suivre.