IMPLÉMENTATION D'UN FORTH EN ROM POUR ORIC ATMOS Le projet qui va être développé pendant plusieurs numéros est de remplacer le basic en rom par le langage FORTH. C'est un exercice purement stylistique qui permettre de comprendre le fonctionnement interne de l'ORIC ATMOS. Cet article présente ce que nous devons prendre en compte pour le projet. Je m'autorise à entrer parfois en détail sur certaines notions. Pourquoi avoir choisi le FORTH ? Bien qu'il existe un FORTH (compatible F83) pour l'ORIC ATMOS, je voulais que les articles soient didactiques et qu'il existe une abondante littérature. Mon choix s'est porté sur le figFORTH qui est une version 79 du FORTH. Il est donc plus jeune que le F83. Le figFORTH a l'avantage d'avoir été porté sur beaucoup de processeur 8 bits de l'époque et particulièrement le 6502. Avant de continuer la discussion, revenons sur la philosophie du FORTH. Ce langage écrit pour les machines de quatrième génération a été inventé par M. Charles H. Moore dans les années 1960. Le principe est en assez simple. Prenez une pile de données (qui stocke donc ... les données), une modélisation d'un processeur utilisant la pile de données et inventez le concept du bytecode. En effet, ce langage s'appuie sur l'utilisation de primitives qui sont soit implémentées directement par un processeur spécialisé soit émulées par un processeur. Nous pouvons inclure aussi dans la liste des spécificités du FORTH la notation polonaise inversée qui semble être déroutant de prime abord mais avec un peu d'habitude le cerveau peut facilement s'y adapter. (Petit rappel : en langage normal une simple addition s'écrit de la façon suivante : 1 + 1 = 2 alors qu'en notation polonaise inversée (RPN en anglais) cela s'écrit 1 1 + : les opérandes en premier puis l'opérateur). En langage FORTH, tout est affaire de mot. En effet, le mot est l'élément primitif non atomique du langage. De plus, il permet de programmer finement le système (niveau hardware) mais aussi de se rapprocher d'un langage de haut niveau (utilisation de if ... then ... else). I - L'implémentation du figFORTH sur un processeur 6502 Le source de figFORTH est disponible sur le net. Nous allons expliquer comment est implémenté le figFORTH. I - 1 - MOTEUR 16 BITS SUR PROCESSEUR 8 BITS Comme nous l'avons dit précédemment, le FORTH utilise la notion de bytecode. Mais pour interpréter ce bytecode, il nous faut une machine virtuelle (comme le JAVA ...). Pour faire court, une machine virtuelle est la simulation d'un processeur qui interpréterait le langage FORTH nativement. Heureusement pour nous, l'implémentation des instructions comprises par la machine virtuelle est fort simple. Cette machine virtuelle est constituée de 5 registres et d'une unité mathématique (arithmétique et logique) travaillant sur la pile de données et d'instructions manipulant les registres et l'unité mathématique. Ces 5 registres sont : + IP est le pointeur d'instructions, + W est le registre de décodage, + RP est le pointeur de pile d'adresses de retours, + SP est le pointeur de pile données, + UP est le pointeur de zone utilisateur. L'implémentation sur un 6502 de la machine virtuelle FORTH est effectuée de la manière suivante : Comme le 6502 ne propose pas beaucoup de registre, il a fallu jongler avec la mémoire vive en page zéro. Nous aurons comme pénalité par rapport à un registre processeur (typiquement l'accumulateur A du processeur 6502), un cycle de plus. Le registre IP est implémenté en page zéro. Il indique la position de la prochaine instruction FORTH à exécuter. Le registre IP est incrémenté de 1 à chaque fois. Le registre W est implémenté en page zéro. Il contient l'instruction FORTH à exécuter. Le registre UP est la position de la première case mémoire libre pour stocker les mots FORTH de l'utilisateur. Il est stocké en mémoire vive. Le registre RP est la position de la première case mémoire libre pour stocker les adresses de retours . Il est simulé par le registre X du 6502. Le registre SP est la position de la première case mémoire libre pour stocker les données sur la pile de données. Il est simulé par le registre S du 6502 I - 2 - QUELQUES CONCEPTS EN FORTH Les mots FORTH sont regroupés en dictionnaire. Ce dictionnaire peut être générique tel que le dictionnaire FORTH ou bien spécifique tel que le dictionnaire EDITOR (permet d'éditer le source FORTH ...). Cette notion de dictionnaire est importante. a - LE MOT EN FORTH Il n'est pas la plus petite entité du langage FORTH. En effet, un mot FORTH est constitué de m micro-instructions comprises par la machine virtuelle. Le mot FORTH est constitué : d'une zone NFA (Name field Adress) : zone stockant le nom du mot FORTH (31 caractères au maximum) défini ainsi que le type de mot, d'une zone LFA (Link field Adress) : Zone stockant l'adresse du prochain mot FORTH défini. Si cette zone est égale à 0 alors nous sommes à la fin du dictionnaire d'une zone CFA (Code Field Adress) : Astuce : Si (PFA)=PFA+2 alors code assembleur d'une zone PFA (Parametric field Adress) : Zone stockant les micros instructions. Le mot FORTH est constitué d'une zone nommée cfa (Code Field Adress) qui indique l'adresse des micro-instructions à exécuter. Une zone suivant celle nommée cfa est appelé le pfa (Parameter Field Adress) qui stocke les paramètres de l'instruction ou mot. Le but des articles n'étant pas d'expliquer le fonctionnement du FORTH mais plutôt d'implémenter le FORTH en rom, nous n'allons pas nous étendre. Vous trouvez ci-dessous deux schémas qui montrent l'implémentation en mémoire de manière standard et sur un 6502. Le schéma générique du figFORTH est le suivant : + Un noyau FORTH contenant la séquence de démarrage et le dictionnaire, + Un tampon pour la manipulation de chaîne de caractère, + La pile de données, + La pile de retour, + Le tampon stockant les saisies clavier de l'utilisateur, + La zone utilisateur qui stocke les nouveaux mots créés par l'utilisateur, + Le tampon pour les accès disquettes. Ce schéma est utilisé par les processeurs implémentant le langage FORTH sur une puce. Le deuxième schéma représente l'implémentation réelle sur un processeur 6502. Il faut émuler les UP,N,IP,W. STANDARD fig-FORTH MEMORY MAP LIMIT ---> +----------------------------------+ <--- USE | | | DISC BUFFERS | | | FIRST ---> +----------------------------------+ <--- PREV +----------------------------------+ | | | USER AREA | | | UP ---> +----------------------------------+ R0 ---> +----------------------------------+ | | \ | RETURN STACK | \ | | ^ | \ IN | v | | / | TERMINAL BUFFER | / | | / RP ---> +----------------------------------+ <--- TIB S0 ---> +----------------------------------+ | | | | | STACK | | v | SP ---> +----------------------------------+ +----------------------------------+ | | | TEXT BUFFER | | | +----------------------------------+ <--- PAD "WORD" BUFFER DP ---> +----------------------------------+ | | | DICTIONARY | | | +----------------------------------+ +----------------------------------+ | | | BOOT-UP LITERALS | | | +----------------------------------+ <--- 0 +ORIGIN 6502 fig-FORTH MEMORY MAP LIMIT ---> +----------------------------------+ <--- USE | | | DISC BUFFERS | | | FIRST ---> +----------------------------------+ <--- PREV +----------------------------------+ | | | USER AREA | | | UP ---> +----------------------------------+ +----------------------------------+ | | | TEXT BUFFER | | | +----------------------------------+ <--- PAD "WORD" BUFFER DP ---> +----------------------------------+ | | | DICTIONARY | | | | -------------------------------- | | | $200 | BOOT-UP LITERALS | | | +----------------------------------+ <--- 0 +ORIGIN $01FF ---> +----------------------------------+ R0 | | \ | RETURN STACK | \ | | ^ | \ IN | V | | / RP ---> | TERMINAL BUFFER | / | | / $0100 ---> +----------------------------------+ <--- TIB +----------------------------------+ | | Z-PAGE | UP N IP W | | | +----------------------------------+ S0 ---> +----------------------------------+ SP IS X REGISTER | | | RP IS STACK POINTER OF CPU | | STACK | | | $009F - $0010 | | v | SP ---> +----------------------------------+ b - SPÉCIFITÉ DU FIGFORTH + Le figFORTH permet d'accéder au lecteur de disquette. Nous n'utiliserons pas cette fonctionnalité dans un premier temps et nous implémenterons à la place un disque virtuel en RAM. + Les variables utilisateurs : C'est une zone mémoire où les mots FORTH peuvent stocker par exemple l'adresse du tampon pour le disque virtuel, l'état de la machine virtuelle (mode compilation/interprétation), etc. Cette notion est importante pour l'implémentation du figFORTH en mémoire morte. II - LE HARDWARE DE L'ORIC ATMOS Avant d'aller plus loin, il faut connaître les caractéristiques du hardware pour mener à bien notre projet. L'ORIC ATMOS est constitué de : - un processeur 6502, - un processeur d'entrée/sortie VIA 6522, - un processeur sonore PSG AY-3-8192, - une glue, l'ULA, gère l'affichage, les accès mémoire et les accès au périphérique. Le processeur d'entrée/sortie gère le clavier (avec l'aide du PSG), le lecteur de K7 et le port imprimante. Ce qu'il faut savoir : La notion d'interruption : Une interruption permet de dérouter le processeur sur une routine spécifique. Les interruptions sont : - le reset généré par le branchement électrique, - le NMI (no mask interruption) généré par le bouton reset, - l'IRQ (Interrupt ReQuest) qui est généré soit par le via soir par le bus externe. Sur l'ORIC ATMOS, l'interruption permet de scanner la matrice du clavier, d'incrémenter les timers softwares du basic, et le traitement des interruptions secondaires (appel d'une routine écrite par l'utilisateur). II - 1 - L'AFFICHAGE L'ULA lit la mémoire à partir de la zone 0xBB80 pour trouver les caractères à afficher. La matrice de caractères qui représentent le caractère affiché n'est pas incluse dans l'ULA. Elle est stockée en mémoire morte et recopié en mémoire vive à une adresse précise. Au début, nous n'allons pas gérer les attributs de caractère et nous afficherons les caractères en blanc sur fond noir dans un écran constitué de 28 lignes de 40 caractères. Au fur à mesure de l'avancement, nous ajouterons la couleur puis le graphisme haute résolution. Pour initialiser le mode texte de l'ORIC, il faut stocker à l'adresse 0xBFDF un code qui sera stocké dans l'ULA. II - LE FONCTIONNEMENT DE L'AFFICHAGE Nous allons nous intéresser au fonctionnement de l'affichage en mode texte de l'ORIC ATMOS. Je ne traiterai donc pas le mode semi-graphique et graphique. Le mode texte de l'ORIC ATMOS est particulier. En effet, les attributs de couleurs sont mélangés avec le texte à afficher. Ce qui conduit de ne pas pouvoir gérer finement les attributs du caractère. Les contraintes de couleur étant secondaires dans le présent article, je vais m'attacher à décrire comment créer un affichage minimaliste sur l'ORIC ATMOS. L'affichage de l'ORIC ATMOS est constitué de 28 lignes de 40 colonnes. La première ligne est appelée ligne d'état qui permet d'afficher habituellement les messages CAPS et ceux générés par les routines gérant le lecteur K7 (searching, ...). Cette affichage est monochrome (fond noir et encre blanche). Qui affiche l'écran ? C'est ULA. C'est un composant rustique qui fait son boulot mais il aurait pu être "customisé" un peu plus ... A côté du 6845, Cathode Ray Tube Controller (CRTC), utilisé par le CPC 464, l'ULA fait office d'épouvantail. L'ULA est fort "bête". Il lit séquentiellement les données situées en 0XBB80. A partir de la donnée, il lit une table de caractère qui permet de lui dire comment dessiner ce caractère. A l'ére des chipsets NVIDIA et ATI, ce n'est pas terrible. Car pas de registre de programmation, pas d'effet 3D !! Tout juste la couleur, la surbrillance, la double taille et le clignotement. Mais il n'est pas aussi bête que cela !! Il sait que l'écran commence en 0xBB80, qu'il fait 28X40 caractères et que la table d'affichage se trouve en 0xB400. Il sait générer les signaux nécessaires pour "piloter" une télévision. J'ai tout dit : L'ULA cherche les caractères en 0xBB80, va chercher en 0xB400 la table de caractère et l'affiche !! Mais point de registre à programmer ? Que nenni !! Il existe des registres dans l'ULA mais ils sont initialisés indirectement. Car l'ULA sait qu'il peut rencontrer des caractères qui sont spéciaux. Cela il ne faut pas les afficher mais les utiliser pour qu'il puisse se programmer !! En effet, comment l'ULA sait qu'il doit s'initialiser en mode texte et non pas en mode graphique ? Mais par ces caractères spéciaux, pardi ! Pour tout dire, il suffit de mettre la valeur 0x1A en 0xBFDF pour que l'ULA s'initialise en mode TEXTE ... Magique non ? Et bien non, le concepteur de l'ULA a tout prévu !! En effet, il existe 4 registres d'une capacité de 3 bits (un peu chiche le concepteur ;-) ) : Le codage se fait du poids faible au poids fort. 1 - r0 : Ink : color R,G,B 2 - r1 : Style : Alt(Std), Dbl(Std), Flash(Std) 3 - r2 : Paper : color R,G,B 4 - r3 : Mode : xxx 50Hz(60Hz) Hires(Text/Lores) Très bien mais comment dire à l'ULA qu'il faut programmer ces registres ? Si la valeur de l'octet à afficher est inférieure strictement à 32 (0x20) alors c'est un caractère ... d'attribut. Ce caractère d'attribut est décomposé de la façon suivante : Notation : bx xième bit b0,b1,b2 : data b3,b4 : numéro de registre b5,b6,b7 : 000 Le registre r3 permet d'indiquer à l'ULA de passer en mode Hires ou en mode Texte et en 50 Hz ou 60 Hz pour la fréquence de balayage. Nous sommes en France donc 50 Hz. Nous voulons le mode Text donc r3 doit valoir 010b : don't care, mode 50 Hz, mode text. Il semblerait que le bit 2 du registre r3 ne sert à rien. Dommage ! Finalement : b0,b1,b2=010b b3,b4 =11b b5,b6,b7=000b D'où le $1A en 0xBFDF. Pourquoi changer la résolution de l'écran en 0xBFDF ? Euh ! Je ne sais pas ... Trêve de plaisanterie ! Pour que cela soit propre nous changeons le mode graphique de l'écran quand on a terminé l'affichage. Il existe une routine en rom qui s'occupe du passage en mode text et en mode : - Passage en mode hires : routine INIT_HIRES, étiquette INIT_HIRES_HGR. Pour initialiser le mode graphique, nous mettons 0x1E dans 0xBFDF - Passage en mode texte : routine INIT_TEXT, étiquette INIT_TEXT_TEXT, appel de INIT_TEXTSCREEN qui appelle à INITMODETEXT avec paramètre A=$1A Mais c'est bien beau d'avoir une puce pour visualiser des données mais comment l'ULA sait qu'il doit dessiner un 'A' lorsqu'il rencontre le code ASCII 0x41 (ou 65 en decimal ) ? En fait, il se sert du code ASCII comme index dans une table qui décrit les caractères affichables dans une matrice 8x8. L'ULA est fort bête mais obstiné et consciencieux dans son travail ! Il a été programmé à lire dans la ram la table contenant les matrices d'affichage de caractère. Cette table se trouve en 0xB500 est fait 768 octets de long. Elle décrit 96 caractères ASCII qui vont de 0x20 (Espace) à 0x7F (pavé du curseur). Chaque caractère est codé sur 8 octets. Chaque octet est la représentation d'une ligne de 8 colonnes. La table de description nommé aTabCar est stockée en 0xFC78 Note : Pour initialiser le mode texte, nous introduisons un temps d'attente (voir routine INITMODETEXT). ADRESSE EN ROM (ORIC ATMOS): INIT_HIRES : 0xF920 INIT_HIRES_HGR : 0xF933 INIT_TEXT : 0xF967 INIT_TEXT_TEXT : 0xF975 INIT_TEXTSCREEN : 0xF9C9 INITMODETEXT : 0xFA07 WAIT : 0xEEC9 SET_TIMER : 0xEEAB GET_TIMER : 0xEE9D Par défaut, au début de chaque ligne, les attributs sont : fond noir, encre blanche, pas de clignotement, pas de double taille et jeu de caractère standard. II - 3 - L'INITIALISATION DES COMPOSANTS A chaque démarrage de l'ORIC ATMOS, les composants électroniques se trouvent dans un état chaotique. Pour remédier à cela, les concepteurs de microprocesseur ont prévu que lors d'un reset du 6502, celui-ci se déroute à l'adresse 0xFFFE pour lire la prochaine adresse qui contiendra la prochaine instruction à exécuter. Ce déroutement est totalement prévisible et donc va permettre aux concepteurs d'ordinateurs de mettre un peu d'ordre dans ce chaos et surtout d'initialiser les composants pour qu'ils aient un comportement prévisible et déterministe. Pour l'ORIC ATMOS, ce déroutement se fait à l'adresse 0xF88F. La routine que nous nommerons COLD_START initialise le pointeur de pile, initialise les vecteurs, fixe la paramétrage pour le clavier, interdit les interruptions, initialise la RAM (détermination de la taille de la ram), initialise les composants (VIA,PSG,TIMERS,CURSEUR) et l'écran (mode texte) et recopie de la matrice d'affichage des caractères, affichage du mot CAPS sur la ligne d'état) puis initialise une partie de la mémoire vive pour le paramétrage du basic avant de rendre la main à celui-ci. II - 4 - LE CLAVIER Le clavier de l'ORIC ATMOS est constitué 64 touches organisées en une matrice de taille 8x8. L'implémentation de la recherche de la touche activée fort simple. Nous positionnons la nième ligne à l'état bas et nous mettons successivement les 8 colonnes à l'état haut. A chaque colonne testée, nous lisons l'état de la broche PB3 du via 6522 pour détecter si la touche recherchée a été actionnée. a - Du point de vue électronique La réalisation est fort laborieuse. Nous utilisons le port IOA du PSG pour les colonnes, nous utilisons les broches PB0 à PB2 du via 6522 et un demultiplexer 3 -> 8 pour les lignes et enfin l'ensemble 2 résistances et 1 transistor relié à la PB3 du via 6522. Le rôle du demultiplexer 3 -> 8 est de suppléer le manque de broches. En effet, nous avons 8 lignes à tester et nous avons 3 broches à notre disposition. L'idée est de constater que 2^3 donne 8 états. C'est à dire que chaque broche peut prendre 2 états ('0' ou '1') et cela nous donne 2^3 possibilités de configuration. Le rôle du demultiplexer est d'activer une sortie parmi 8 suivant le code présent sur les broches d'entrées. Mais pourquoi diable ne pas avoir fait de même pour les colonnes ? b - Du point de vue software Nous constatons que les broches PB0 à PB2 doivent être initialisées comme sortie et la broche PB3 en entrée. Cette initialisation est effectuée par les instructions suivantes : 0xF9AA : LDA $FF STA v3_DDRA ; all pins of PORT A are output LDA $B7 STA v3_IORB ; Init orb first with PB4 set to 1 LDA $F7 STA v3_DDRB ; Then turn PB4 from high Z to high output and PB3 is an input LDA $DD STA v3_PCR ; Peripheral Control Center LDA $7F STA v3_IER LDA $00 STA v3_ACR Combinaison pour PCR $DD= 110 : CB2 has low output, used to drive PSG (BDIR) 1 : CB1 has positive active edge 110 : CA2 has low output, used to drive PSG (BC1) 1 : CA1 has positive active edge Combinaison pour IER : $7F : Comme le bit 7 est à 0 et les bits 6 à 0 sont à 1 alors aucune interruption généré par le via 6522 n'est prise en compte. Combinaison pour ACR : $00 : 00 : T1 timer control : Timed interrupt each time T1 is loaded 0 : T2 timer control : Timed interrupt 000 : Shift register control : disabled 0 : Latching enable/disable for PB : disable latching 0 : Latching enable/disable for PA : disable latching Avec initialisation, nous avons : - Le port A en sortie - Le port B en sortie sauf PB3 qui sera une entrée - Si un des constituants du via 6522 (Timer1, Timer2, niveau ou front sur une broche) génére une interruption alors aucune interruption sera générée. - Pas de latch sur les ports A et B Maintenant, il faut s'intéresser au soft. Tout d'abord, il faut avoir diverses informations en notre possession pour comprendre le code source. Tout d'abord : + le hardware : - Les 8 broches du port I/O du PSG permet de commander les colonnes, - Les 3 broches PB0 à PB2 du port A du via 6522 permet de commander les lignes, - La broche PB3 permet de détecter ou pas si la touche à la colonne J, ligne I est actionnée. + les variables en page 0 - 0x208: v2KBD : code de la touche actionnée - 0x209: v2KBD_CTRL : code de la touche contrôle actionnée - 0x20A: v2KBD_ColPattern : Variable de travail contenant le n° de colonne de la dernière touche actionnée. Elle est utilisée dans le monde répétition - 0x20B: v2KBD_CTRLColPattern : Variable de travail contenant le n° de colonne de la dernière touche de contrôle actionnée - 0x20C: v2KBD_UpcaseFlag : Par défaut, initialiser à $7F pour lower case - 0x20D: v2KBD_ColCount : Contient le numéro de colonne codé sur 3 bits et décalé de 3 bits à gauche - 0x20E: v2KBD_RepeatCount : Variable de travail contenant les constantes de repetition et automatique - 0x210: v2KBD_Work1 : Variable de travail contenant le numéro de ligne de la touche actionnée codé sur 3 bits - 0x211: v2KBD_Work2 : Variable de travail - 0x24E: v2KBD_TempoRep : Constante de répétition, initialisé à 9 - 0x24F: v2KBD_TempoAuto : Constante d'auto répétition, initialisé à 1 - 0x2DF: v2IChar + Les adresse en ROM : 0xF495 : SUB_SCRUTKEYBOARD 0xF4BD : RESETSEQREAD 0xF4EF : CONV_MATRIX_2_ASCII 0xF523 : READ_MATRIX_KEYB 0xF561 : LIT_KEYBOARD 0xF590 : W8192 0xFF78 : aTabMask (tableau de conversion Matrix => ASCII) + La table de conversion : elle contient le code ASCII de la touche actionnée suivant les coordonnées X,Y 8x8 éléments : minuscule : Le bit 7 est à '1' 7 | j | m | k | spc | u | y | 8 ; Ligne 0 de la colonne 7 a la colonne 0 n | t | 6 | 9 | , | i | h | l ; Ligne 1 de la colonne 7 a la colonne 0 5 | r | b | ; | . | o | g | 0 ; Ligne 2 de la colonne 7 a la colonne 0 v | f | 4 | _ | Up | p | e | / ; Ligne 3 de la colonne 7 a la colonne 0 | | | | | | | ; Ligne 4 de la colonne 7 a la colonne 0 1 | esc | z | | <- | del | a | ret ; Ligne 5 de la colonne 7 a la colonne 0 x | q | 2 | \ | down | ] | s | ; Ligne 6 de la colonne 7 a la colonne 0 3 | d | c | ' | right | [ | W | = ; Ligne 7 de la colonne 7 a la colonne 0 8x8 éléments : majuscule & | J | M | K | SPC | U | Y | * N | T | ^ | ( | < | I | H | L % | R | B | : | > | O | G | ) V | F | $ | - | Up | P | E | ? | | | | | | | ! | ESC | Z | | <- | DEL | A | RET X | Q | @ | | | DOWN | } | S | # | D | C | " | RIGHT | { | W | + Position dans la rom : 0xFF78 aTabMask: .BYTE $37, $EA, $ED, $EB, $20, $F5, $F9, $38 .BYTE $EE, $F4, $36, $39, $2C, $E9, $E8, $EC .BYTE $35, $F2, $E2, $3B, $2E, $EF, $E7, $30 .BYTE $F6, $E6, $34, $2D, $B, $F0, $E5, $2F .BYTE 0, 0, 0, 0, 0, 0, 0, 0 .BYTE $31, $1B, $FA, 0, 8, $7F, $E1, $D .BYTE $F8, $F1, $32, $5C, $A, $5D, $F3, 0 .BYTE $33, $E4, $E3, $27, 9, $5B, $F7, $3D .BYTE $26, $4A, $4D, $4B, $20, $55, $59, $2A .BYTE $4E, $54, $5E, $28, $3C, $49, $48, $4C .BYTE $25, $52, $42, $3A, $3E, $4F, $47, $29 .BYTE $56, $46, $24, $5F, $B, $50, $45, $3F .BYTE 0, 0, 0, 0, 0, 0, 0, 0 .BYTE $21, $1B, $5A, 0, 8, $7F, $41, $D .BYTE $58, $51, $40, $7C, $A, $7D, $53, 0 .BYTE $23, $44, $43, $22, 9, $7B, $57, $2B + Codage matriciel du clavier colonne, notée I et ligne notée J ------------------------------------------------------------------ |PB210| PA7 | PA6 | PA5 | PA4 | PA3 | PA2 | PA1 | PA0 | |----------------------------------------------------------------- | 000 | 3 | X | 1 | | V | 5 | N | 7 | | 001 | D | Q | ESC | | F | R | T | J | | 010 | C | 2 | Z | CTRL | 4 | B | 6 | M | | 011 | ' | \ | | | - | ; | 9 | K | | 100 |Right | Down | Left | Left Sh | Up | . | , | SPC | | 101 | [ | ] | DEL | FCT | P | O | I | U | | 110 | W | S | A | | E | G | H | Y | | 111 | = | | RET | Right Sh | / | 0 | L | 8 | ------------------------------------------------------------------ + Une table de masque (ou conversion) qui permet de correspondre le couple (I,J) au caractère ASCII correspondant. Elle se situe à l'adresse 0xFF78 et fait 128 octets . En effet, il y a 64 octets pour le mode minuscule et 64 octets pour le mode majuscule. Comment ça marche ? Rien de plus simple. A chaque interruption, la routine gérant celle-ci s'occupe des timers puis appelle la routine SUB_SCRUTKEYBOARD pour obtenir le code de la touche actionnée. Avant de se plonger dans le code, il faut être au courant de quelques subtilités ou facilités de programmation. - Pour pouvoir accéder à la position (I,J) du tableau de conversion touche => ASCII, les colonnes I sont codées sur 3 bits de la position bit 3 à bit 5. Les lignes seront codées sur 3 bits de la position bit 0 à bit 2. Ce qui nous donne bien 2^6 = 64 possibilités. Le bit 6 permet d'accéder aux 64 dernières positions du tableau de masque. Au début de la routine READ_MATRIX_KEYB, nous avons l'initialisation des variables v2KBD_ColCount, v2KBD et v2KBD_CTRL à 0x38. En binaire 0x38 est égal à 00111000. Nous codons la première colonne à tester à 111. Ce qui revient à tester la huitième colonne. - Nous utilisons la pile pour stocker le numéro de colonne codé sur 8 bits et un seul bit actif à la fois. La simple instruction ror permettra de tester la colonne suivante (le sens de déplacement du test est de la huitième colonne vers la colonne zéro. (Question subsidiaire : Pourquoi utiliser la pile pour stocker cette valeur ?) - Si nous étudions la matrice du clavier, nous constatons que la 5ème colonne ne contient que des touches de contrôle ou des touches inutilisées. Ainsi dans le code de la routine READ_MATRIX_KEYB, il y a le texte suivant : LDX #0 LDY #$20 CPY v2KBD_ColCount BNE SaveKeybRd INX SaveKeybRd: STA v2KBD,X Nous savons que v2KBD_ColCount contient le numéro de colonne testé codé sur 3 bits et se situé du bit 3 au bit 5 avec bit 5 de poid fort. Colonne 7 : 0x38 Colonne 6 : 0x30 Colonne 5 : 0x28 Colonne 4 : 0x20 : Colonne des touches CTRL,SHIFT ET FCT Colonne 3 : 0x18 Colonne 2 : 0x10 Colonne 1 : 0x08 Colonne 0 : 0x00 Pour tester rapidement le cas des touches de contrôle, nous testons si v2KBD_ColCount est égale à 0x20. Si c'est le cas alors incrémentons X pour sauver en v2KBD_CTRL au lieu de v2KBD !! - La programmation le port I/O du PSG AY-3-8192 : Cela fait par l'intermédiaire de la routine W8192, adresse rom 0xF590. La programmation du port I/O se fait de manière particulière. Cela se fait en 2 étapes : - Indiquer le numéro de registre qui va recevoir la donnée, - Écrire la donnée à mettre dans le numéro de registre. A chaque fin des 2 étapes, le PSG doit être dans l'état inactif (Étiquette INACTIVE_PSG et INACTIVE_1_PSG). La routine SUB_SCRUTKEYBOARD Elle permet de détecter la dernière touche activée ainsi que les touches de contrôle. L'algorithme de lecture des touches de clavier est fort simple (j'ai ajouté les noms d'étiquettes) : TEST_REPETITION: Touche actionnée ? SI OUI alors même touche actionnée ? SI OUI ALORS TRAITKEYB FIN SI FIN SI RESETSEQREAD : FOR Colonne = 7 to 0 do FOR Ligne =7 to 0 do Est-ce que la touche à la position (I,J) est pressée ? Si oui sauvegarde son code soit à v2KBD soit à v2KBD_CTRL NEXT Ligne NEXT Colonne TRAITKEYB: CONVERSION_MATRIX_2_ASCII EMETTRE_BEEP ;------------------------------------------------------------------------------ ; S u b r o u t i n e ; FCT: Scrutation clavier ; IN : / ; OUT: X = 00 si aucune touche actionnee (ou meme touche actionnee) sinon code SUB_SCRUTKEYBOARD: PHA ; Sauvegarde A,P,Y PHP TYA PHA CLD ; Mode decimal TEST_REPETITION: LDA v2KBD BPL RESETSEQREAD ; Si < $80 alors va à RST_SEQ_KEYBOARD ; Test si c'est la même touche qui est actionnee AND #$87 STA v2KBD_Work1 LDX v2KBD_ColPattern JSR LIT_KEYBOARD ; IN : X pos of column ; A pos of line ; OUT: A code Si > 80 alors touche actionnée CMP v2KBD_Work1 BNE RESETSEQREAD ; Si ce n'est pas la même touche ; alors on recherche la nouvelle touche actionnée DEC v2KBD_RepeatCount BNE F_NOKB_SUB_SCRUTKEYBOARD LDA v2KBD_TempoAuto STA v2KBD_RepeatCount JMP TRAITKEYB RESETSEQREAD: LDA v2KBD_TempoRep ; RESET DE LA VALEUR TEMPO CLAVIER STA v2KBD_RepeatCount JSR READ_MATRIX_KEYB TRAITKEYB: JSR CONV_MATRIX_2_ASCII ; FCT: Touche actionnee est CTRL ou SHIFT ? ; IN : / ; OUT: A : Valeur de la touche actionnée TAX BPL F_SUB_SCRUTKEYBOARD ; Si < 0 alors pas de touche PHA EMITBEEP: LDA v2Mode0 ; B0:Curseur affiche B1:Affiche autorise ; B3:Clavier muet B4:last keyb is ESC ; B5:Affichage 40 col B6: Affichage sur 2 lignes AND #8 BNE SuiteScrut ; Faut-il emettre un son ? PLA PHA CMP #$A0 ; Si > 0xA0 alors CTRL ou shift BCC EmetCtrlBeep EmitKbBeep: JSR KB_BEEP JMP SuiteScrut EmitCtrlBeep: JSR CTRL_BEEP SuiteScrut: PLA JMP F_SUB_SCRUTKEYBOARD F_NOKB_SUB_SCRUTKEYBOARD: LDA #0 F_SUB_SCRUTKEYBOARD: TAX PLA TAY PLP PLA RTS ;------------------------------------------------------------------------------ ; S u b r o u t i n e ; FCT: Conversion de la touche actionnée en ASCII ; IN : / ; OUT: A : Valeur de la touche actionnée CONV_MATRIX_2_ASCII: LDA v2KBD_CTRL TAY LDA #0 CPY #$A4 ; = Shift gauche BEQ SetCaps CPY #$A7 ; = Shift Droit BNE SuiteIsCtrl SetCaps: CLC ADC #$40 ; Acces a la 2eme partie du tableau ; de conversion SuiteConv: CLC ADC v2KBD BPL F_CONV_MATRIX_2_ASCII ; Si < $80 alors pas de conversion AND #$7F ; Le tableau a 128 positions ... TAX LDA aTabMask,X ; Contient le code ASCII de la touche ; actionnée suivant les coordonnees X,Y ; 8x8 elements : minuscule ; 8x8 elements : majuscule AND v2KBD_UpcaseFlag ; Traitement du drapeau LOWER/UPPER BPL IsCtrl SEC SBC #$20 ; Passage en mode MAJUSCULE IsCtrl: AND #$7F CPY #$A2 ; = Ctrl BNE IsNotCtrl ; Si pas egal alors va a IsNotCtrl CMP #$40 BMI IsNotCtrl ; Si A>= $40 ('@') alors pas de gestion ; CTRL+Touche AND #$1F ; Gestion de CTRL+Touche IsNotCtrl: ORA #$80 ; Mise à '1' du bit 7 de A F_CONV_MATRIX_2_ASCII: RTS ;------------------------------------------------------------------------------ ; S u b r o u t i n e ; FCT: Recherche de la dernière touche actionnée (touche normal et controle) ; IN : / ; OUT: / READ_MATRIX_KEYB: LDA #$38 STA v2KBD_ColCount STA v2KBD STA v2KBD_CTRL LDA #$7F ; Pattern de depart de la colonne ; utilisé pour programmer les sorties ; du port I/O du PSG PHA ; On stocke dans la pile le numero de ligne ; codé en binaire BclReadMatrix: PLA ; SimULAtion de POP X ET PUSH X !! PHA TAX LDA #7 JSR LIT_KEYBOARD ; IN : X pos of column ; A pos of line ; OUT: A $00 : pas de touche actionnée sinon ; =10000lll avec lll numero de ligne ORA v2KBD_ColCount ; Ajout du numéro de colonne codé sur 3 bits BPL PrepColLinMatr ; Si < 0 alors tester la colonne suivante LDX #0 ; Index pour sauvegarde de touche ; (=0 touche normal, =1 touche de fonction) LDY #$20 ; Est-ce la 4eme colonne ? (colonne CTRL, SHIFT et FCT ?) CPY v2KBD_ColCount BNE SaveKeybRd ; Si non alors touche normal INX ; C'est une touche de controle SaveKeybRd: STA v2KBD,X ; Sauvegarde dans soit v2KBD soit v2KBD_CTRL PLA ; Recuperation du motif binaire associé au numéro ; de colonne testé PHA STA v2KBD_ColPattern,X ; Sauvegarde le pattern de la colonne ; où la touche a été actionnée PrepColLinMatr: SEC ; La retenue à '1' sera utilisée par le ROR PLA ROR A ; Colonne de la ligne suivante PHA ; On garde sur la pile le numéro de colonne SEC LDA v2KBD_ColCount ; Le codage du numéro de colonne ; se fait par rapport à $38=7,$30=6... ; $00=0 SBC #8 ; Passage à la colonne suivante STA v2KBD_ColCount BPL BclReadMatrix ; Si < 0 alors on continue la scrutation par colonne PLA ; Suppression du parametre colonne de la pile RTS ;------------------------------------------------------------------------------ ; S u b r o u t i n e ; FCT: Cherche le numéro de la ligne de la touche actionnée ; pour une colonne donnée ; IN : X pos of column ; A pos of line ; OUT: A Si $00 pas de touche actionnée sinon 10000lll avec lll numero de ligne LIT_KEYBOARD: PHA ; Sauvegarde du numero de ligne SET_COLUMN: LDA #$E ; On veut ecrire sur le I/O Port data du PSG JSR W8912 ; PROGRAMMATION DU PSG ; IN : X = data to write to register of PSG ; A = register of PSG SET_LINE: PLA ; Récupération du numéro de ligne AND #7 ; Limitation à 8 lignes (0-7) TAX ; X=X mod 7 STA v2KBD_Work2 ; Sauvegarde du numéro de ligne ; pour arrêter le scan BCL_LIT_KEYBOARD: ORA #$B8 ; Le OU permet fixer l'état de certaines ; broches du port B STA v3_IORB ; On applique le numéro de ligne JMP IsPressed ; Introduction de temps d'attente NOP NOP IsPressed: LDA v3_IORB ; On lit le port B AND #8 ; On récupère l'état de la broche 3 du ; port B BNE FIN_LIT_KEYBOARD; La touche a été frappée (=1) ? DEX ; Non alors ligne suivante TXA AND #7 TAX ; X=X MOD 7 CMP v2KBD_Work2 ; A-t-on terminé ? BNE BCL_LIT_KEYBOARD; Non alors à la ligne suivante LDA #0 ; Touche non actionnée RTS FIN_LIT_KEYBOARD: TXA ORA #$80 ; Le bit 7 de A est force à '1' ; Touche actionnée RTS ;------------------------------------------------------------------------------ ; S u b r o u t i n e ; PROGRAMMATION DU PSG ; IN : X = data to write to register of PSG ; A = register of PSG W8912: PHP SEI SET_REGISTER: STA v3_IOA ; Numéro de colonne sur le port A TAY ; Y=A TXA ; A=X CPY #7 ; Numéro de registre est = 7 ? BNE LATCH_REGISTER ; non alors en suite_W8192 ORA #$40 ; Si le n° registre=7 alors on ; on met d'office le bit 6 à '1' ; pour que le I/O port DATA soit ; programmé en sortie LATCH_REGISTER: PHA LDA v3_PCR ; Charge le Peripheral Control Register ORA #$EE ; BC2 = '1' et CA2 = '1' ; BC2=BDIR et CA2=BC1 du PSG STA v3_PCR ; LATCH ADRESS FROM BUS OF PSG INACTIVE_PSG: AND #$11 ; BC2='0' et CA2='0' ORA #$CC ; BDIR=CB2 et BC1=CA2 du PSG STA v3_PCR ; PSG is inactive SET_DATA: TAX ; X=A PLA ; Récupération du numéro de colonne STA v3_IOA ; Sur le port A TXA ; A=X ORA #$EC ; CB2=BDIR='1' CA2=BC1='0' STA v3_PCR ; DATA WRITE TO PSG INACTIVE_1_PSG: AND #$11 ORA #$CC ; CB2=BDIR='0' et CA2=BC1='0' STA v3_PCR ; PSG is inactive PLP RTS III - LE SYSTÈME MINIMAL III - 1 - INTRODUCTION C'est un système qui permet d'initialiser le hardware de l'ORIC, de gérer le mode texte, d'attendre une frappe de touche et d'afficher le caractère. Il faut donc : - Installer un gestionnaire d'interruption, - Initialiser le VIA 6522 (Timer, programmation des ports I/O, etc.) , le PSG, - Recopier en mémoire vive la table des caractères, - Initialiser le mode texte (Init. de l'ULA, chargement de la table des caractères, initialisation des variables associées à la gestion du mode texte) - Créer une boucle d'attente infini qui lorsqu'une touche est actionnée affiche le caractère ASCII correspondant à l'écran. Caractéristiques de l'affichage : 1 ligne d'état de 40 caractères où s'affichera le message CAPS (mode majuscule) 27 lignes de 40 caractères Affichage fond noir et encre blanc Scrolling lorsque le curseur atteint la dernière ligne Gestionnaire d'interruption : - RESET : - NMI : - INT : Toutes les 10 ms, une interruption est déclenchée par le VIA. Le processeur se déroute vers la routine d'interruption qui : - Gestion du clavier - Gestion des timers softwares Nous allons nous inspirer de ce qui est fait par la rom de l'ORIC ATMOS tout en corrigeant quelques défauts. Les étapes effectuées par l'ORIC ATMOS lors d'un reset III - 2 - RESET Ce que fait l'oric ATMOS : - Le 6502 lit l'adresse contenu en 0xFFFC (0xF88F) et fait un saut vers celle-ci, - 0xF88F : Initialisation de la pile : le pointeur de pile est égal à 0xFF, - 0xF893 : Recopie en mémoire (page 2 <=> 0x2XX) des adresses de saut, - 0xF89E : Initialisation de la durée d'attente avant répétition (90 ms) et de la vitesse de répétition (10 ms), - 0xF8A8 : Autorisation des interruptions, - 0xFA14 : Routine de Test de la mémoire et détermine la taille de celle-ci. Renseigne des variables en page 2, - 0xF8B8 : Routine d'initialisation du VIA, du PSG, des timers software 1 et 2, le mode texte, efface la ligne d'état, recopie la table des caractères de la rom (0xFC78) en ram (0xB500), des caractères semi-graphiques et affichage du mot CAPS en ligne d'état, - 0xECCC : Routine d'initialisation des variables, de vecteur de saut, puis rentre en boucle d'attente en 0xC4B7 Ce que va faire notre système minimal : - Le 6502 lit l'adresse contenu en 0xFFFC et effectue un saut vers ORIC_START, - Initialisation de la pile : le pointeur de pile est égal à 0xFF, - Recopie en mémoire (page 2 <=> 0x2XX) des adresses de saut, - Initialisation de la durée d'attente avant répétition (90 ms) et de la vitesse de répétition (10 ms), - Autorisation des interruptions, - Routine d'initialisation du VIA et du PSG - Routine d'initialisation des timers - Routine d'initialisation du mode texte, effacement de la ligne d'état, affichage du mot CAPS en ligne d'état - Entre en boucle infini : attente d'une frappe de caractère et affichage Le système minimal a été segmenté pour montrer les différentes actions menées lors d'un reset. La routine d'initialisation de la ram a été supprimée. Il faut ne pas perdre de vue ce que nous cherchons à effectuer. Le but de ces articles est de proposer un système minimal fonctionnel et réutilisable pour implémenter par exemple : - le logo, - une machine virtuelle pour le smalltalk minimaliste, - un interpréteur PASCAL, - un LISP ... Petite parenthèse : le smalltalk est une machine basé sur une machine virtuelle comme le FORTH et le JAVA ... III - 3 - SCHÉMA MÉMOIRE DE L'IMPLÉMENTATION DU FIG-FORTH SUR ORIC ATMOS J'anticipe un peu sur le reste des articles pour décrire le schéma mémoire du fig-FORTH implémenté sur l'ORIC ATMOS 6502 fig-FORTH MEMORY MAP for ORIC ATMOS $FFFF +----------------------------------+ | | | DICTIONARY | | | | -------------------------------- | | | | BOOT-UP LITERALS | | | $C000 +----------------------------------+ <--- +ORIGIN | FREE | $BFDF +----------------------------------+ | | | ECRAN | | | $BB80 +----------------------------------+ | TABLE DE CARACTERE | $B500 +----------------------------------+ | | | FREE | | | $9800 ---> +----------------------------------+ HI | | | PSEUDO DISC | | | $7000 ---> +----------------------------------+ LO | | | USER AREA | | | $6F80 ---> +----------------------------------+ <--- UP, LIMIT | | | <--- USE | DISC BUFFERS | | | v | <--- PREV $6B70 ---> +----------------------------------+ <--- FIRST | | | FREE | | | +----------------------------------+ <--- PAD = HERE + 68 octets | TEXT BUFFER | +----------------------------------+ | ^ | | | | <--- DP = HERE | | USER DICTIONARY | $0500 ---> +----------------------------------+ | | \ | ^ | \ | | | \ IN | | | / | TERMINAL BUFFER | / | | / $0400 +----------------------------------+ <--- TIB | | | I/O | | | | | $0300 +----------------------------------+ | | | Variable systeme | | | | | $0200 +----------------------------------+ R0, $01FF +----------------------------------+ | | | RETURN STACK | | | | | | | | v | | | <--- RP IS STACK POINTER OF CPU $0100 +----------------------------------+ | | | FREE | | | $00B9 +----------------------------------+ | N IP W UP SW NP | Z-PAGE | | S0, $009E +----------------------------------+ | | | | | STACK | | | $009E - $0010 | | v | SP, $0010 ---> +----------------------------------+ SP IS X REGISTER | FREE | $0000 +----------------------------------+ Avant d'aller plus loin, il faut expliciter les choix. Notre but est de fournir une rom contenant un "bios" minimal et le langage FORTH. En ce qui le langage FORTH, nous partons du source du fig FORTH prévu pour fonctionner en ram. Il faut donc adapter le source pour qu'il fonctionne correctement. Dans l'implémentation prévue du FORTH, nous prévoyons la gestion d'un lecteur de disquette virtuelle de taille limité mais cela nous permettra de vérifier le fonctionnement des mots du FORTH associés à la gestion du lecteur de disquette. Mais avant de s'y plonger, nous allons nous occuper du bios. Il permet d'initialiser correctement l'ORIC ATMOS et met à disposition des services (clavier et affichage). Il ne faut pas perdre de vu que l'ensemble est minimal et au fil des articles nous allons enrichir par des fonctions évoluées (gestion de la haute résolution, du son et du lecteur de K7) III - 4 - DESCRIPTION D'UN BIOS Le BIOS (Basic Input Output Services) est constitué de - une partie d'initialisation, - services. a - L'initialisation Elle permet préparer l'ORIC ATMOS à son futur fonctionnement. Il faut donc s'occuper du VIA 6522, du PSG, de l'affichage et de la routine s'occupant de l'IRQ. b - Gestion de l'interruption par l'ORIC ATMOS Nous allons nous intéresser à la routine de gestion de l'IRQ. Lorsque l'IRQ survient, le 6502 se déroute et lit l'adresse contenue en 0xFFFE-0xFFFF. Sous l'oric ATMOS, il se déroute à l'adresse 0x244. Cette adresse contient un saut en 0xEE22 (INT_IRQ). Cette routine vérifie que c'est une interruption en provenance du 6522 (plus précisément du timer T1). Si non nous sortons de la routine sinon nous remettons le drapeau de l'interruption du timer T1 puis nous nous déroutons vers l'adresse 0xEE34 (MANAGE_IRQ) qui : - Décrémente de 1 les 3 timers soft, - Interroge le timer 1 : (Gestion clavier) si la valeur lue est égale à 0 alors on réinitialise le timer soft T1 à 30 ms puis on lit le clavier - Interroge le timer 2 : (Gestion clignotement du curseur) si la valeur lue est égale à 0 alors on réinitialise le timer soft T2 à 190 ms puis on inverse le bit de clignotement (ou exclusive avec la variable v2AuthClig) puis affiche/cache le curseur pour simuler le clignotement. - effectue un saut vers l'adresse 0x24A (v2JmpRti) qui contient un RTI La routine MANAGE_IRQ utilise deux routines nommées GET_TIMER (0xEE9D) et SET_TIMER (0xEEAB) qui me semble fort compliquée. Nous allons remanier le code pour être plus rapide et plus compact (avoir le beurre et l'argent du beurre). GET_TIMER Le code actuel : PHA 1,4 ASL 1,2 TAY 1,2 SEI 1,2 LDA v2TIMER1,Y 3,4 LDX v2TIMER1+1,Y 3,4 CLI 1,2 TAY 1,2 PLA 1,4 RTS 1,6 ---------------- 14 octets,32 cycles Cela est bien tortueux ... Si X est le l'index du timer voulu (Timer 1 => X=$00, Timer 2 => X=$02, Timer 3 => X=$04) Il nous reste : SEI 1,2 LDA v2TIMER1,X 3,4 LDY v2TIMER1+1,X 3,4 CLI 1,2 RTS 1,6 ---------------- 9 octets,18 cycles ==> Pas d'emplacement de pile utilisé !! Plus rapide et plus compact !!! AVANT | 14 | 32 | ------------------------ APRÈS page 2 | 9 | 18 | APRÈS page 0 | 7 | 18 | SET_TIMER Le code actuel : PHA 1,3 TXA 1,2 PHA 1,3 TYA 1,2 PHA 1,3 TSX 1,2 LDA PILE+3,Y 3,4 ASL 1,2 TAY 1,2 PLA 1,4 PHA 1,3 SEI 1,2 STA v2TIMER1,Y 3,5 LDA PILE+2,X 3,4 STA v2TIMER1+1,Y 3,5 CLI 1,2 PLA 1,4 TAY 1,2 PLA 1,4 TAX 1,2 PLA 1,4 RTS 1,6 --------- 33 octets, 68 cycles Plus que tortueux !!! En posant (Timer 1 => X=$00, Timer 2 => X=$02, Timer 3 => X=$04), le nouveau code est : SEI STA v2TIMER1,X TYA STA V2TIMER1+1,X CLI RTS Nombre d'octets : 1 + 3 + 3 + 1 + 1 = 9 octets Nombre de cycles : 2 + 5 + 5 + 2 + 6 = 20 cycles De plus, nous pouvons rendre plus compact si on utilise une adresse en zéro page !! SEI STA v0TIMER1,X STY v0TIMER1,X CLI RTS Nombre d'octets : 1 + 2 + 2 + 1 + 1 = 7 octets Nombre de cycles : 2 + 4 + 4 + 2 + 6 = 18 cycles AVANT | 33 | 68 | ------------------------ APRÈS page 2 | 9 | 20 | APRÈS page 0 | 7 | 18 | Sans commentaire !! Ces 2 routines ont le même temps d'exécution !! OPTIMISATIONS MANAGE_IRQ: PHA TXA PHA TYA PHA LDY #0 DT_BCL: LDA v2Timer1,Y SEC SBC #1 STA v2Timer1,Y INY LDA v2Timer1,Y SBC #0 STA v2Timer1,Y INY CPY #6 BNE DT_BCL GetValueTimerT1: LDA #0 JSR GET_TIMER CPY #0 BNE DT_ManageTimer2 LDX #0 LDY #3 JSR SET_TIMER JSR SCRUTKEYBOARD STR_GET_KEYB: TXA BPL DT_ManageTimer2 STX v2IChar DT_ManageTimer2: LDA #1 JSR GET_TIMER CPY #0 BNE F_DEC_TIMERS LDX #0 LDY #$19 JSR SET_TIMER LDA v2AuthClig ; 1=allume, 0=eteint EOR #1 STA v2AuthClig ; 1=allume, 0=eteint JSR ManageBlinkingCursor F_DEC_TIMERS: PLA TAY PLA TAX PLA RTS Optimisation du code : LDY #$00 DT_BCL: LDA v2Timer1,Y 3,4 SEC 1,2 SBC #1 2,2 STA v2Timer1,Y 3,5 INY 1,2 LDA v2Timer1,Y 3,4 SBC #0 2,2 STA v2Timer1,Y 3,4 INY 1,2 CPY #6 2,2 BNE DT_BCL 1,3 ou 1,2 si pas saut -------- 22 octets, 32 cycles (ou 31 si pas saut) Après optimisation : LDX #$04 DT_BCL: LDA v2Timer1,X 3,4 SEC 1,2 SBC #$01 2,2 STA v2Timer1,X 3,5 LDA v2Timer1+1,X 3,4 SBC #$00 2,2 STA v2Timer1+1,X 3,5 DEX 1,2 DEX 1,2 BPL DT_BCL 1,3 ou 1,2 si pas saut -------- 20 octets, 31 cycles (ou 30 si pas saut) ===> Ce n'est pas énorme mais bon ... Si on passe les timers en page 0, nous pouvons gagner encore 4 octets et 4 cycles !! FINALEMENT INT_IRQ: PHA LDA v3_IFR AND #$40 BEQ F_INT_IRQ STA v3_IFR TXA PHA TYA PHA LDX #$04 DT_BCL: LDA v2Timer1,X SEC SBC #$01 STA v2Timer1,X LDA v2Timer1+1,X SBC #$00 STA v2Timer1+1,X DEX DEX BPL DT_BCL GetValueTimerT1: LDX #$00 JSR GET_TIMER CPY #$00 BNE GetValueTimerT2 LDA #$00 LDY #$03 JSR SET_TIMER JSR SCRUTKEYBOARD STORE_KEY: TXA BPL DT_ManageTimer2 STX v2IChar GetValueTimerT2: LDX #$02 JSR GET_TIMER CPY #$00 BNE F_MANAGE_IRQ INIT_TimerT2: LDA #$00 LDY #$19 JSR SET_TIMER GestionClignotement: LDA v2AuthClig EOR #1 STA v2AuthClig JSR ManageBlinkingCursor F_MANAGE_IRQ: PLA TAY PLA TAX F_INT_IRQ: PLA JMP v2JmpRti Temps d'exécution (Sans tenir compte de la scrutation de clavier). Si aucun timer (T1 ou T2) n'arrive en expiration : 200 cycles (200 µs) Si Timer T1 arrive en expiration : 236 cycles Si Timer T1 et T2 arrive en expiration : 322 cycles c - LE CLAVIER Voir ci-dessus pour les explications. d - LES SERVICES WRITECAR A : Affiche 1 caractère et gère le curseur WRITESTRING X,Y,pile : Affiche 1 chaîne de caractère terminée par un '0' à la position X,Y CLRSCR : Efface l'écran INITTEXTSCREEN : Initialise l'écran en mode texte GOTOXY X,Y : Positionne le curseur en X,Y CLEARSTATUSLINE : Efface la ligne de status SMC_UPPERCAPS : Écrit le mot CAPS sur la ligne de status SMC_LOWERCAPS : Écrit le mot caps sur la ligne de status SCROLLTEXT : Effectue le scrolling d'un écran e - LES SOURCES Vous trouverez le code assembleur et la version rom à l'adresse suivante SOURCE ASM : http://passionoric.free.fr/download/INIT_NEWVERSION.ASM ROM Euphoric : http://passionoric.free.fr/download/INIT_NEWVERSION.ROM IV - BIBLIOGRAPHE FORTH WIKIPEDIA FORTH : http://en.wikipedia.org/wiki/Forth_programming_language site FIG FORTH : http://www.forth.org/ site sur le forth : http://forth.free.fr/ Initiation au FORTH : http://archive-edutice.ccsd.cnrs.fr/docs/00/03/06/74/PDF/b38p140.pdf Livre : http://forth.free.fr/livres/guide/guide.htm Documentation pour bien écrire le FORTH : http://www.taygeta.com/forth_coding.html THINKING FORTH (Leo BRODIE) : http://thinking-forth.sourceforge.net/ STARTING FORTH (Leo BRODIE) : http://www.amresearch.com/starting_forth/index.html FORTH INTERPRETER CONCEPT : http://www.amresearch.com/forth_dimensions/FD-V6N1.pdf MOVING FORTH Part 1: Design Decisions in the FORTH Kernelby Brad Rodriguez : http://www.zetetics.com/bj/papers/moving1.htm 79 FORTH ROM for APPLE II : http://www.forth.org/fd/FD-V4N4.pdf Documentation technique OPCODE 6502 : http://www.6502.org/tutorials/6502opcodes.html PSG : http://www.wacci.org.uk/magazine/137/137_04.html ULA : http://ORIC.ifrance.com/ORIC/hardware/ULA.html SCHÉMA CARTE MÉRE ORIC ATMOS : http://ORIC.ifrance.com/ORIC/IMAGES/ORIC1-1s.gif ORIC 1 & ATMOS Graphics and machine code techniques by GEOFF Phillips : www.geffers.demon.co.uk/files/chap1.htm Tableau ASCII : http://www.lookuptables.com/