Il y a quand même une toute petite aide qui est de faire l'appel "netlist --help". Et tu peut aussi regarder le script test qui est sur le repository (mais peut-être pas dans le package, je ne suis pas très bon pour faire des .deb)
Globalement, il faut faire par exemple :
netlist -i adder.nl -cycle 16 -v 3
-i donne le fichier d'entrée (sinon c'est l'entrée standard par défaut).
-cycle donne le nombre de cycle de simulation à faire (ça simule des circuit dit "synchrone", ce qui est à la fois une fonctionnalité et une limitation, mais c'est généralement ce par quoi on commence en cours)
-v donne le degré de verbosité, le paramètre est un masque de bit des valeurs suivantes (qui sont toutes activée par défaut) :
1 : erreur de compilation
2 : sortie du programme
4 : warning de la compilation
8 : info de la part du compilo (le nombre de porte du circuit)
16 : Affirme que le programme est valide si c'est le cas
Les valeur au delà ne sont pas activée par défaut et sont plutôt utile pour du débogage.
Il y a d'autre options (par exemple -c qui permet de produire un exécutable natif à partir d'un circuit (mais il y a quelques petites choses qui ne peuvent pas être compilé avec cette option)), mais moins utiles.
Pour ce qui est de la syntaxe des fichiers sources, il faut regarder les exemples, globalement on définit une fonction avec :
(r1,r2,...) = NomFct(e1,e2,...)
{
code
}
r1,r2 etc. sont le nom des variables de sorties (il peut y en avoir un nombre quelconque), NomFct est le nom de la fonction et e1,... les variables d'entrée.
Comme le seul type de variable est le bit, il n'y a pas besoin de déclarer les variables dans le corps des fonction. La fonction renvoie la valeur qu'on ses variables de sortie à la fin de son exécution.
Il y a deux type de fonction, celle dont le nom commence par une lettre, qui sont des fonctions normales, qui peuvent être appelé n'importe où. Et celle dont le nom commence par un '$' qui sont ce que j'appelle des "composant". Quand un programme est exécuté, chaque composant est appelé par le compilateur à tour de rôle (en quelques sorte, ils jouent le rôle de la fonction main).
L'ordre des fonctions n'a pas d'importance (si tu définit deux fonctions avec le même nom, la seconde efface la première et une fonction peut appeler une autre fonction définit après elle).
Par contre, l'ordre des composant à de l'importance car un composant ne peut appeler un autre composant (directement ou au travers de fonction) que si celui-ci a été défini avant et donc exécuté avant. Car l'appel d'une composant, contrairement à une fonction ne provoque pas son exécution, mais récupère juste ça valeur (en terme de circuit, le circuit correspondant à une fonction peut être dupliqué de nombreuses fois, mais pas ceux des composant).
Sauf que pour appeler un composant donné, par exemple $Truc, tu peut faire soit "$Truc()" (un composant ne peut pas avoir de paramètre) ce qui renvoie la valeur à laquel truc a été évalué durant ce cycle, soit "@Truc()" (et ça tu peut le faire depuis n'importe où, même dans un composant déclaré avant Truc) et ça te renvoie la valeur de $Truc au cycle précédant.
J'ai dit qu'il n'y a que des bits comme type de variable, mais il y a aussi des tableaux.
Comme tu peut écrire :
a,b,c = (f,g,h) ou (i,j,k);
Il y a un raccourci qui est :
a[3] = b[3] ou c[3];
Et là, a[3] est remplacé par (a1,a2,a3), de même pour b et c (vraiment remplacé, ça veut dire que plus tard dans la fonction tu peut utiliser la valeur de a2 par exemple). Donc a[5] est le tableau de 5 cases dont les cases s'appellent a1,a2,a3,a4 et a5, mais ce n'est pas la cinquième valeur de a.
Ce qui nous mène au fait qu'en dehors des fonctions tu peut définir des variables par :
a = 0b1; ce qui fait que a va valoir 1 toujours (et donc ce n'est pas très utile).
b[3] = (0b1,0b0,0b0); (ce qui définit un tableau)
c = 0b10011; ce qui est plus intéressant car comme ça, la variable c va prendre à chaque cycle l'une des valeurs (1, puis 0, puis 0, puis 1, puis 1, puis on recommence).
Il y a d'autre possibilité et je m'aperçois que ma grammaire est un peu stricte et qu'il serait facile d'autoriser plus de chose, mais je ne suis pas sûr que je le changerais.
D'autre part, une variable globale ne peut pas être modifié, car une affectation dans une fonction crée automatiquement une variable locale (qui masque la variable globale).
Bon, il y a plein d'autre chose, particulièrement, tu peut écrire au lieu de a[8] juste a[] ce qui crée un tableau de la taille du mot du circuit et tu définit la valeur du mot avec la commande <byte 5> par exemple (donc a[] est exactement égale à a[5]). A noter que cette commande a un effet sur tout le programme où qu'elle soit (le mot est le même partout, même si cette commande est à la fin du fichier), et tu peut faire a[[]] pour avoir un tableau du double de la taille d'un mot.
Et il y a encore d'autre chose, mais pour commencer, ça doit suffire, les exemples de syntaxes sont parlant pour savoir quels opérateurs existent (à noter que &&, || et ?? sont des versions paresseuses des opérateur &, | et ?, donc un programme s'exécute plus vite avec ceux-là, mais il y a des restrictions comme le fait qu'il ne peut pas y avoir de registre dans un sous circuit derrière un opérateur paresseux (sinon, si un registre n'est pas appelé à chaque tour, le circuit n'a plus de sens))
Il faut noter aussi que tout ça est "sûre" : il ne peut pas y avoir d'erreur d'exécution, car le programme est statiquement vérifié pour satisfaire aux contraintes d'un circuit logique (pas de boucle, les registres sont toujours appelé, tout les membres des opérateurs ont la même longueur, etc.) Et, un truc sur lequel j'ai mis pas mal d'attention, c'est que les messages d'erreurs sont censé être assez explicite (sauf pendant le parsing malheureusement, où je ne donne que la liste des derniers symboles lus, ce qui n'est pas terrible).
Bon, j'ai un peu oublié ce que ça peut faire, mais si tu as d'autre questions, je peut regarder.