= L'assembleur = Style mips, c'est le plus simple : plein de registres + un registre zero qui contient toujours 0 2 types d'instructions - [alu rdest r1 r2] pour les operations arith/logiques où alu parcours {add, sub, or, and, ... } prends aussi en compte move car : move $rdest $r ~ or rdest $r $zero prends aussi en compte les jump avec move $pc $x - [load dest addr/store orig addr] qui prends ou mets des trucs dans la RAM à l'addresse $addr De plus, chaque instruction prends un registre supplementaire $c en argument et ne s'execute que si celui ci est nul (sinon elle devient move $zero $zero ~ nop). Cela permet le branching et on met $zero pour faire le mips usuel. Encodage : chaque instruction sur 4 mots [op][arg1][arg2][arg3] (quand l'operation a moins d'arguments on pad avec des 0) où : op = | 00[alu_type] => operation sur l'alu { arg1 = rdest, arg2 = r2, arg3 = r3}, on transmet alu_type | 10(0*) => load { dest = arg1, addr = arg2 } | 11(0*) => store { orig = arg3, addr = arg2 } (le 0* c'est qu'on complète par des zeros, on est indépendant de la taille du mot à la compilation) = La ROM = Elle va lire les mots 4 par 4 parceque c'est la taille de nos instructions donc c'est plus simple : - une entrée read_addr[wordsize] - une sortie instr[4*wordsize] = La RAM = - deux entrée read_addr[wordsize], write_addr[wordsize] - une entrée write_data[wordsize] et une entrée write[1] - une sortie read_data[wordsize] si write est à 1 la RAM écrit write_data à l'addresse write_addr etc. = Architecture = Un registre spécial $pc est toujours branché sur le read_addr de la ROM. Il s'incrémente de plus tout seul a chaque tick d'horloge. Tous les sorties registres sont branchés sur 2 gros selecteurs qui prennent en argument arg2 et arg3 resp. En gros on a 3 mots qui valent a tout moment $arg2 et $arg3 resp. De même les entrées sont branchées sur un selecteur qui vaut $arg1$. Donc a chaque instruction, on écrits TOUJOURS le résultat de quelquechose dans $arg1 et on lit TOUJOURS $arg2 et $arg3 r1_read == | | r2_read == | SELECT (instr[wordsize:2*wordsize]) | ... | | == arg2 rn_read == | | pareil pour arg3 Pour alu & load: arg2 =(read_addr)= [RAM] || (read_data) _____ || arg2 =(input1)= | ALU | =(output)= select(op[0:2]) == arg3 arg3 =(input2)= |_____| Pour brifzero : zero | arg2 == is_zero -- select(op[0:2]) -- pc_overwrite || pc_write = Organisation = - jop/ : Module camlp4 et runtime pour la compilation ml => netlist. - circuits/ : Fichiers ml décrivant des netlists. - netlists/ : Netlists générées et fichiers d'input. - simulator/ : Simulateur de netlist en java. L'utilisation est expliquée en executant JOP_Simulator.jar sans argument (java -jar JOP_Simulator.jar) = Comment lancer une demo ? = Les demos dispos sont (pour l'instant) : ./rundemo.sh full_adder Teste tout les combinaisons d'entrées sur un full adder décrit dans circuits/full_adder.ml Sortie espérée : 0 1 1 1 2 2 2 3 ./rundemo.sh minus Fais le moins unaire de l'entrée (ici 100000000...) Sortie espérée : 11111111111.... ./rundemo.sh count <n> Incrémente un compteur modulo 2^n pendant 32 ticks d'horloge. Sortie espérée : - 0 1 2 3 ... ((2^n)-1) 0 1 ... (valeur du compteur) - 0 0 0 0 ... 1 0 0 ... (bit d'overflow) ./rundemo.sh modn <n> Même chose que pour count, mais on a ici un compteur modulo n, avec n quelconque. ./rundemo.sh clk2 Effectue une division d'horloge. Sortie attendue : 110011001100... (ou la même chose avec un déphasage de 1 pour l'autre sortie) Le code des circuits n'est pour l'instant pas très beau, ceci étant dût à quelques soucis avec camlp4, work in progress. = Est-il possible de faire ces démos avec nos propres inputs d'entrée ? = Bien sûr, il suffit pour cela de modifier le fichier d'extension ".nl.input" correspondant. Les fichiers input sont concus de telle manière : Tout d'abord le nombre de cycle d'horloge à simuler, Puis pour chaque input, le nom de la variable suivie (séparés par des espaces) les valeurs au cours du temps. Un exemple de fichier pourrait être : 8 a 0 1 0 0 0 1 1 1 b 0 0 1 0 1 0 1 1 ci 0 0 0 1 1 1 0 1