mardi 28 mars 2017

ForthEx (partie 2)

Dans cette partie je décris la structure générale du système logiciel.

structure logicielle

Il s'agissait donc de construire un système à partir de zéro. Pour utiliser le MCU il faut d'abord effectuer une configuration matérielle de celui-ci en fonction de l'usage des différents périphériques. J'ai conçu le système pour entrer le plus rapidement possible dans le système forth mais cependant il y a plusieurs fonctions logicielles qui ne font pas parti du système forth à proprement parler puisque ces fonctions ne sont pas intégrées au fonctionnement de la machine virtuelle forth tel qu'expliqué plus bas dans cet article. On pourrait appeler ces fonctions de base BIOS pour Basic Input Output Services ou encore HAL Hardware Abstraction Layer puisqu'elles sont spécifiques à cette plateforme matérielle. Si on dessine un schéma de la structure logicielle ça ressemble à ceci, l'information circulant entre les couches adjacentes seulement.

Abstraction matérielle

Les routines de service d'interruptions ainsi certaines routines qui peuvent-être appelées par des mots forth mais qui ne sont pas intégrés à la machine virtuelle font partis de cette couche logicielle. Les procédures qui ne font pas partie du système forth sont appelées par l'instruction assembleur call et se termine par l'instruction assembleur return. Les routines en codes qui font parti de la machine virtuelle sont liées les unes aux autres par un système d’enfilage (threading) assuré par une macro appelée NEXT qui est définie dans le fichier macros.inc.

La machine virtuelle

Il s'agit d'une machine virtuelle 16 bits, donc les adresses sont dans l'intervalle {0..65535} et les entiers dans l'intervalle {-32768..32767}. Dans le jargon forth une unité élémentaire de donnée s'appelle une cellule (CELL). ForthEx utilise des cellules de 16 bits. La machine virtuelle est constituée de 3 piles et d'un interpréteur interne qui a pour fonction de lire une liste d'adresses qui correspond au code à exécuter. Les données stockées sur les piles sont en unité cellule. Les entiers doubles (32 bits) occupe donc 2 cellules sur la pile. Il existe des mots spécialisés pour faire de l'arithmétique sur les cellules et s'assurer qu'une adresse est alignée sur le début d'une cellule.

La première pile est la pile des arguments appelée pstack dans le fichier core.s. Cette pile sert à passer les arguments entre les fonctions (appelés mots en forth).

La deuxième pile sert principalement à conserver les adresses de retour pour les appels de mots de haut-niveaux. Cette pile s'appelle rstack dans le fichier core.s

La 3ième pile n'est utilisée que par le compilateur et s'appelle cstack dans le fichier core.s. Le compilateur utilise cette pile pour résoudre des adresses de sauts. En dehors de la compilation cette pile n'est pas utilisée.

En plus de ces 3 piles la machine virtuelle a un certain nombre de registres. La machine virtuelle a donc la structure suivante:

  • IP Instruction Pointer. Joue le même rôle que le compteur ordinal (Program counter) d'un CPU. IP pointe la prochaine adresse de code à exécuter.
  • DSP Data Stack Pointer. Pointeur pour la pile pstack. Pointe à la position de l'avant dernier élément empilé, le dernier élément étant conservé dans le registre T.
  • RSP Return Stack Pointer. Pointeur pour la pile rstack. Pointe l'adresse après le dernier élément empilé. Notez la différence entre le pstack et le rstack. DSP est incrémenté avant d'empiler et décrémenté après le dépilement. RSP est incrémenté après l'empilement et décrémenté avant le dépilement.
  • VP. Variable Pointer. Le système utilise un certain nombre de variables. Ces variables sont rangées en un seul bloc en mémoire RAM. VP pointe sur l'adresse du début du bloc. Les variables système sont référencées par leur position relative à VP.
  • I et LIMIT. Ces 2 registres sont utilisés ensemble par les boucles DO ... LOOP et DO ... +LOOP. I est le compteur de boucle incrémenté à chaque itération de la boucle et LIMIT est la valeur limite du compteur. Lorsque I atteint LIMIT le bouclage se termine.
  • WP Working Pointer. Après l'appel d'un mot ce registre pointe vers le champ des paramètres du mot.
  • T est un registre qui contient la valeur au sommet du pstack.

plan de la mémoire

Ce plan de la mémoire est appelé à varier avec chaque version du système.

Mémoire RAM

  • 0x0000-0x1000 Bloc d'adresse réservés pour les SFR Special Function Register. Les périphériques sont accédés via ce bloc d'adresses. C'est spécifique à ce MCU.
  • 0x1000-0x107F rstack, Pile des retours.
  • 0x1080-0x10BF pstack, Pile des arguments.
  • 0x10C0-0x10FF cstack, Pile de contrôle.
  • 0x1100-0x104F TIB, Terminal Input Buffer. Tampon pour le clavier ou autre source d'entrée.
  • 0x1050-0x109F PAD, Tampon pour le formatage des textes en sortie.
  • 0x10A0-0x10C7 Tampon d'entrée pour le port série.
  • 0x10C8-0x11ED Variables système.
  • 0x11EE-0x1211 Variables et tampon utilisé par le USART du clavier.
  • 0x1212-0x1233 Bloc de variables utilisées par les fonctions HAL.
  • 0x1234-0x12F7 Bloc libre.
  • 0x12F8-0x12FF Variables utilisées par le mécanisme de sauvegarde/restauration d'image boot.
  • 0x1300-0x7FFF Espace données utilisateur, dictionnaire utilisateur. Ce bloc mémoire contient les fonctions, les variables et les constantes définis par l'utilisateur.
  • 0x8000-0xC9FF Mémoire EDS, Extended Data Space, réservée pour l'allocation dynamique.
  • 0xCA00-0xCFFF Mémoire vidéo, contient les caractères affichés à l'écran.

Mémoire FLASH

  • 0x000000-0x000003 vecteur de réinitialisation.
  • 0x000004-0x0001FF Table des vecteurs d'interruptions.
  • 0x000200-0x000229 Code machine _reset.
  • 0x00022A-0x0003A9 Code machine des routines ISR.
  • 0x0003AA-0x00235D Dictionnaire système. Contient les mots définis lors de la création du système.
  • 0x00235E-0x00275D Police de caractères, table ASCII.
  • 0x00275E-0x0027C3 Chaînes de caractères constantes, messages système.
  • 0x0027C4-0x00396D Code machine des mots forth définis avec les macros DEFCODE et HEADLESS.
  • 0x00396E-0x003C82 Code machine fonctions HAL.
  • 0x007800-0x007823 Constantes pour l'initialisation des variables système.
  • 0x008000-0x0557EC Mémoire libre. l'image boot est enregistrée au début de cette plage et peu occupée jusqu'à ~28000 octets.

lexique de la machine virtuelle

D'abord un lexique pour comprendre la machine virtuelle.

  • dictionnaire Le dictionnaire est une base de donnée qui fait le lien entre les noms des routines et l'adresse du code. La structure d'une entrée dans le dictionnaire est comme suis:
    +-----+-----+-----+-----+
    | LFA | NFA | CFA | PFA |
    +-----+-----+-----+-----+
    
    Le dictionnaire est utilisé par l'interpréteur/compilateur. Une partie du dictionnaire est dans la mémoire FLASH et l'autre correspondant aux mots définis par l'utilisateur est en mémoire RAM.
    • LFA Link Field Address, est le champ dans une entrée dictionnaire qui contient l'adresse du prochain mot. Le dictionnaire étant une liste chaînée. Occupe 2 octets.
    • NFA Name Field Address, contient la chaîne comptée du nom. Le premier octet contient la longueur du nom et les suivants le nom lui-même. Si le nombre d'octets occupés par ce champ est impaire, 1 octet est ajouté pour aligner le CFA sur une adresse paire. Occupe un nombre pair d'octets.
    • CFA Code Field Address, ce champ contient l'adresse du point d'entrée de la routine à exécuter pour ce mot. Occupe 2 octets.
    • PFA Parameter Field Address, ce champ contient les paramètres utilisés par la routine. Occupe 0 ou un nombre quelconque d'octets. Le champ est complété pour assurer une adresse paire sur l'entrée suivante.
  • Interpréteur interne, Une fois qu'une définition est compilée, le code à exécuter est une liste d'adresses lues par l'interpréteur interne ou machine virtuelle. Chacune de ces adresses est appelée XT pour eXecution Token.
  • XT, eXecution Token, est un pointeur qui contient l'adresse du code à exécuter. Le modèle d'exécution de cette machine virtuelle est appellée ITC.
  • ITC, Indirect Threaded Code, est un modèle d'exécution dans lequel le programme est une liste pointeur ou chaque pointeur contient l'adresse de la routine en code machine à exécuter. C'est le modèle d'exécution utilisé par ForthEx.
  • Interpréteur/compilateur, C'est l'interface utilisateur qui analyse la ligne d'entrée fournie par l'utilisateur ou provenant d'un fichier source et qui interprète ou compile le texte lu selon l'état de la variable système STATE.
  • STATE, est la variable système qui indique à l'interpréteur/compilateur si les mots lus dans le flux d'entré doivent-être exécuter immédiatement ou compilés dans une nouvelle entrée du dictionnaire. Lorsque cette variable est à zéro les mots sont interprétés sinon ils sont compilés.
  • Colon Definition, Est un mot définit par :, ce caractère étant appelé colon en anglais. Les Colon definition sont des listes d'adresses qui débutent par l'exécution du code ENTER et se terminent par l'exécution du code EXIT. Pour la machine virtuelle ces 2 mots sont l'équivalent des instructions machine CALL et RETURN respectivement. C'est à dire que ENTER empile la valeur de IP sur rstack avant d'initialiser IP avec la valeur qui est dans le CFA du mot. La dernière adresse est celle de EXIT qui a pour effet de prendre la valeur qui est au sommet de rstack pour la remettre dans IP ce qui correspond à une sortie de sous-routine. donc les colon definition sont comme des sous-routines, tandis que les mots codes sont comme des instructions machine pour la machine virtuelle forth.


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;    
; interpréteur interne
; exécute l'instruction suivante
; de la machine virtuelle 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;    
.macro NEXT
    mov [IP++], WP  ; WP=CFA, IP pointe vers item suivant
    mov [WP++], W0  ; W0= adresse code, WP=PFA
    goto W0         ; saut vers code routine
.endm    

Comme on le voit la machine virtuelle est très simple puisqu'elle ne comprend que 3 instructions machine. Chaque mot en code machine doit se terminer par cette macro pour assurer le fonctionnement de la machine virtuelle.

démarrage de la machine virtuelle

Voici le code qui s'exécute lors de la réinitialisation de l'ordinateur. La réinitialisation se produit lors de la mise sous tension de l'ordinateur mais aussi s'il y a une exception logicielle, une commande reboot ou une des combinaisons de touches suivante: CTRL-C pour un warm reset ou CTRL-ALT-DEL pour un cold reset.


; vecteur de réinitialisation du processeur    
.section .start.text code address(0x200)
.global __reset    
__reset: 
    clr ANSELA    ; désactivation entrées analogiques
    ; priorité 6 pour _INT1Interrupt
    mov #6, W0
    ior IPC5
    mov #rstack, RSP
    mov #pstack, DSP
    mov DSP, W0
    sub #RSTK_GUARD, W0
    mov W0, SPLIM
    movpag #1,DSWPAG
    btsc RCON,#SWR
    bra 1f
    ; power on reset
    movpag #psvpage(_cold),DSRPAG
    mov #psvoffset(_cold),IP
    NEXT
    ; réinitialisation logicielle
1:  movpag #psvpage(_reboot),DSRPAG
    mov #psvoffset(_reboot),IP
    NEXT
    
.text

   
_reboot:
    .word QCOLD,TBRANCH,_cold-$
_warm:
    .word LIT,fwarm,FETCH,LIT,_DP,FETCH,LIT,_LATEST,FETCH
    .word CLS,CLR_LOW_RAM
    .word HARDWARE_INIT,VARS_INIT
    .word LATEST,STORE,DP,STORE
    .word DUP,LIT,USER_ABORT,EQUAL,ZBRANCH,1f-$
    .word DROP,LIT,_user_aborted,BRANCH,8f-$
1:  .word DUP,LIT,MATH_EXCEPTION,EQUAL,ZBRANCH,2f-$
    .word DROP,LIT,_math_error,BRANCH,8f-$
2:  .word DUP,LIT,STACK_EXCEPTION,EQUAL,ZBRANCH,3f-$
    .word DROP,LIT,_stack_reset,BRANCH,8f-$
3:  .word DROP,LIT,_unknown_reset    
8:  .word COUNT,TYPE,CR,QUIT
  
_cold:
    .word CLR_RAM,HARDWARE_INIT,VARS_INIT
    .word VERSION,COUNT,TYPE,CR 
    ; autochargement système en RAM à partir
    ; d'une image en FLASH MCU ou EEPROM
    .word BOOTDEV,FETCH,BOOT
    .word QUIT ; boucle de l'interpréteur

Le code assembleur exécute un minimum de code qui consiste à désactiver les entrées analogiques et à initialiser les pointeurs de piles DSP et RSP ainsi que le compteur ordinal de la machine virtuelle IP. A partir de la macro NEXT c'est la machine virtuelle forth qui contrôle l'exécution.

La machine virtuelle forth a 2 points d'entrés, _cold et _reboot. Le point d'entrée est sélectionné en fonction de l'état dut bit SWR dans le registre RCON du MCU. Si ce bit est à 1 il s'agit d'une réinitialisation logicielle donc IP est initialisé pour exécuter le code _reboot sinon IP est réinitialiser pour exécuter le code _cold.

A partir du moment où la machine virtuelle est en action il n'agit plus d'exécuter directement du code machine mais du code forth. Donc _reboot et _cold sont des mots forth mais sans entête de dictionnaire car l'interpréteur/compilateur n'a pas besoin de les voir. Donc chacun des noms qui suis une directive assembleur .word correspond au CFA d'un mot forth. QCOLD est le CFA du mot défini plus bas:

; est-ce un cold reboot    
HEADLESS QCOLD,HWORD
    .word LIT,fwarm,FETCH,ZEROEQ,EXIT
HEADLESS est une macro pour définir les mots forth qui ne sont pas dans le dictionnaire. Dans le fichier macros.inc il y a plusieurs macros pour faciliter la définition des mots forth lors de la construction du système. Tous les mots forth qui sont dans la mémoire FLASH du MCU sont définis à l'aide de ces macros. Donc chaque fois qu'il y a dans les fichiers sources une des macros suivantes: HEADLESS, DEFCODE, DEFWORD, DEFUSER, DEFCONST il s'agit d'une macro qui simplifie la création de définitions forth. La lecture des fichiers source révèle donc que la plus grosse part du système est en forth. Bien sur écrire des définitions forth de cette façon est différent que d'écrire des définitions forth pour l'interpréteur/ compilateur par l'utilisateur de l'ordinateur. Pour quelqu'un qui n'est pas habitué cette différence peu prêter à confusion. J'ai passé tellement de temps à définir des mots de cette façon que les premières fois que j'utilisais l'ordinateur il m'arrivais de taper par exemple sur la ligne de commande le mot MINUS au lieu du caractère -.

Utilisation de l'ordinateur

Cet ordinateur fonctionne seulement en mode texte, affichant 25 lignes de 64 caractères en monochrome. La table des caractères utilise le code ASCII qui est un code à 7 bits. Le 8ème bit, le plus significatif, est utilisé pour inverser le caractère à l'écran. C'est à dire que si ce bit est à 1 le caractère est affiché noir sur fond blanc au lieu de blanc sur fond noir. Dans le fichier TVout.S se trouve les mots forth pour l'affichage. On y trouve entre autre le mot TGLCHAR qui inverse le caractère à la position du curseur. Comme les mots SETX et SETY permettent de positionner le curseur il est possible de manipuler l'affichage pour souligner certains mots en les inversant.

Le système n'a pas d'assembleur donc les seuls mots qui peuvent-être définis par l'utilisateur sont des colon definition. Ces définitions sont sauvegardées en mémoire RAM et perdues lorsque l'utilisateur éteint l'ordinateur, à moins qu'il n'utilise une sauvegarde >BOOT.

Sauvegarde >BOOT et BOOT

Le MCU possède 512Ko de mémoire FLASH mais seulement 64Ko sont réservés pour le système forth. Le reste peu donc être utilisé pour la sauvegarde de données utilisateur. Les fichiers flash.s et store.s contiennent des définitions qui permettent de sauvegarder des données dans la mémoire flash. Deux mots sont d'intérêts pour sauvegarder les définitions de l'utilisateur pour qu'elles soient rechargées automatiquement au démarrage. Le mot >BOOT sert à sauvegarder une image des définitions utilisateur et le mot BOOT est utilisé pour recharger cette image en RAM. Le mot BOOT s'exécute automatiquement lors d'un démarrage à froid et récupère l'image s'il y en a une mais il peut-être lancé manuellement. Voici un exemple d'utilisation.


: p2 ( n -- n^^2 ) \ puissance de 2 
    dup * ;
: p3 ( n -- n^^3 ) \ puissance de 3
    dup p2 * ;
\ sauvegarde de l'image RAM
MFLASH >BOOT
Dans cette exemple 2 définitions ont été créées par l'utilisateur. Pour que ces définitions soit sauvegardées en permanence l'utilisateur a décidé de créer une image boot dans la mémoire flash du MCU, de sorte qu'à chaque démarrage cette image sera rechargée en mémoire RAM. MFLASH est le nom du périphérique de sauvegarde. l'image aurait pu être sauvegardée dans l'EEPROM mais dans ce cas il aurait fallu la recharger manuellement avec la commande:

EEPROM BOOT

On peut donc étendre les fonctionnalités du système en créant de nouvelles définitions en RAM et en sauvegardant avec la commande:


MFLASH >BOOT
L'image modifiée sera disponible à chaque démarrage.

démo d'utilisation

Conclusion

Je n'ai pas l'intention de donner un cours complet sur le forth sur ce blog ce serait redondant, de nombreuses ressources sont disponibles dans l'internet ( en anglais du moins) . Ce qu'il me reste à faire c'est d'écrire un lexique complet des mots définis dans forthex. Ce document sera disponible dans le dossier documentation du github.

Le système de base sera sans doute élargie, par exemple par l'ajout d'un gestionnaire de mémoire dynamique ainsi qu'un système de fichiers pour la carte SD. Il faudrait aussi écrire un éditeur de texte.


Liens

jeudi 23 mars 2017

ForthEx (partie 1)

Dans cet article je présente un projet que j'ai débuté en septembre 2015 puis mis sur pause pour le reprendre en octobre 2016. Depuis j'y travaille régulièrement de sorte que le projet est maintenant suffisant avancé pour considérer ForthEx, un petit ordinateur basé sur système forth, comme fonctionnel.

Origine du projet

En septembre 2015 j'ai trouvé un document pdf dans l'internet écris par Charles Moore créateur du langage forth. Dans ce document Charles Moore explique la construction d'un système forth à partir de rien (bare metal programming). C'est à dire en programmant la machine d'abord en assembleur puis lorsque le minimum requis en assembleur est fonctionnel continuer le développement en utilisant forth lui-même.

Après avoir lu ce document je me suis dis que ce serais intéressant de tenter cette expérience. Il me fallait donc une base matériel c'est à dire un ordinateur pour faire le travail. J'aurais pu prendre un vieux PC mais dans un vieux PC il y a déjà le BIOS d'installé. Je voulais vraiment partir de zéro au point de vu logiciel. J'ai donc conçu un petit ordinateur simple et peu coûteux en utilisant un MCU 16 bits de Microchip. Nommément le PIC24EP512GP202-I/SP. Il s'agit d'un microcontrôleur 16 bits, le même que j'ai utilisé pour le projet PV16SOG.

À partir de cette plateforme matérielle j'ai commencé à construire le système en assembleur et en forth jusqu'à ce que j'arrive à un système utilisable en autonome.

ForthEx

C'est le nom que j'ai donné à ce petit système forth. Ce nom englobe la plateforme matérielle et le système logiciel embarqué. Les caractéristiques de cet ordinateur sont les suivantes:

  • périphériques:
    • clavier d'ordinateur avec connecteur USB.
    • Sortie vidéo composite NTSC ou PAL monochrome
      mode texte 25 lignes de 64 caractères.
    • port de communication RS-232.
    • sortie audio simple tonalité.
  • stockage persistant:
    • Mémoire FLASH du MCU environ 475Ko disponible pour l'utilisateur.
    • Mémoire EEPROM externe avec interface SPI de 128Ko.
    • Carte SD et SDHC.
  • Extension RAM externe avec interface SPI de 128Ko.
  • système forth incluant presque la totalité du vocabulaire core et core extension du standard ANSI 2012, plus le vocabulaire spécifique à ce système. Bien que le système ne soit pas encore complété le mot WORDS affiche présentement 367 mots. Il y en a plus que ça dans le dictionnaire car certains mots utilisés par le compilateur sont caché à l'utilisateur.

Plateforme matérielle

On a donc le processeur principal PIC24EP512GP202-I/SP. Comme le clavier fonctionne à 5 volt il faut 2 régulateurs de tension 5 volt pour le clavier et le PIC12F1572 et 3,3 volt pour le reste du circuit. Pour le détail de l'interface clavier il faut consulter le projet interface pour clavier ps2. Notez que même si un clavier USB est utilisé il fonctionne en mode PS2. En effet de par la façon dont les lignes D- et D+ sont connectés à des pullup le MCU du clavier reconnaît qu'il s'agit d'une configuration PS2 et non USB.

Ce petit ordinateur est donc très peu coûteux à fabriqué car il utilise un minimum de composants. On peut même laisser tomber certains composants comme l'extension RAM externe et/ou la l'EEPROM externe. Une autre option serait de laisser tomber l'interface pour la carte SD et à la place utilisé seulement des EEPROM SPI enfichable pour le stockage persistant.

Le prototype est assemblé sur une carte à pastille avec trous passants (pastilles sur les 2 faces). Il s'agit d'une carte Vector electonics modèle 8015 de 4" x 6" ( 10cm x 15cm). Ce n'est pas très long à assembler. Le gros du travail c'est l'écriture du système logiciel.

Liste matérielle

Reference

 Value

R11

4k7

R8

120R

R10

680R

P1

ICSP

R7

470R

R6

10K

C7

100nF

SW2

reset

C8

100nF

C11

100nF

C12

47µF

X1

8Mhz

C6

18pF

C4

18pF

C13

100nF

R15

10k

U4

25LC1024

SW1

Power

R17

10k

R13

10K

R5

150R

D2

LED

CON2

BARREL_JACK 2,1mm

C1

47µF

C3

100nF

U1

LD1117v33

J2

RCA_JACK

J1

RCA_JACK

R12

1K

C14

100nF

U5

23LC1024

R16

10k

R9

4k7

C9

10nF

C10

22nF

D3

1N6263

U3

PIC24EP512GP202

Q2

2N3904

R2

47K

D1

1N6263

C2

33µF

Q1

2N3906

R4

10k

R1

3K3

R3

47K

U2

LM7805CT

C5

220nF

CON1

SD_Card

F1

500mA

D4

1N4148

J3

RJ12

R14

10K

R23

10K

R24

10K

R25

10K

D7

1N4148

D6

1N4148

R18

10K

P2

keyboard

C15

100nF

C16

100nF

U6

PIC12F1572

D5

LED

R20

470

P3

ICSP

R19

470

R21

10k

R22

10k

C17

10µF/50v

D8

1N4148

R26

470

Dans la partie 2 je vais débuter la description du système logiciel.


liens