Commit 4202c8ae authored by Daru13's avatar Daru13
Browse files

Merge branch 'master' of https://git.eleves.ens.fr/mpepin/sysdig

parents d1a4ea4f 7572219c
*.pdf
*_build
test/*_sch.net
*.native
*.byte
*.swp
*.pdf
*.aux
*.bbl
*.blg
*.fdb_latexmk
*.fls
*.log
# La pollution de Mac
.DS_Store
RAPPORT=RAPPORT
BUILD_DIR=_CPU_build
TEST_DIR=_test_build
SRC=$(wildcard cpu/src/*.mj)
CPU=$(BUILD_DIR)/cpu
.PHONY: simulator all minijazz fullclean test clean pre_build
.PHONY: simulator all minijazz fullclean test clean pre_build pdf
all: $(RAPPORT).pdf $(CPU).net
all: $(CPU).net pdf
simulator:
@echo "==> Building simulator"
......@@ -35,15 +34,14 @@ test: minijazz simulator pre_build
@echo "==> Building asm tests"
cd simulator && make asm
@echo "==> Running asm tests"
./test_asm.sh
test/test_asm.sh
@echo "==> Running minijazz tests"
./test_mj.sh
%.pdf: %.md
@echo "==> Trying to build $(RAPPORT).pdf using pandoc"
@(pandoc -sS -t latex -o $@ $< 2>/dev/null && echo "Success") \
|| echo "Failed, read $(RAPPORT).md instead"
test/test_mj.sh
pdf:
@echo "==> Building RAPPORT.pdf"
cd rapport && make
cp rapport/RAPPORT.pdf .
clean:
rm -rf $(BUILD_DIR)
......@@ -52,7 +50,9 @@ clean:
rm -f test/mj/*.net
fullclean: clean
rm -f $(RAPPORT).pdf
rm -f *.pdf
rm -f RAPPORT.pdf
cd simulator && make clean
cd minijazz && make clean
cd cpu && make clean
cd rapport && make clean
% Simulateur de circuits
% Martin Pépin
% 7 novembre 2016
# Mode d'emploi
Le simulateur doit être compilé avec Ocamlbuild et Ocaml $4.03$. Ceci peut être
fait à l'aide de la commande `make` et le binaire généré s'appelle `simul.byte`.
Pour lancer lacer le simulateur sur un fichier `foo.net`, entrer la comamnde
./simul.byte foo.net
- L'option `-n <int>` permet de préciser le nombre de tours de boucle. Par
défaut le programme boucle à l'infini et on peut le quitter non-violemment
en envoyant le caractère fin de fichier : `Ctrl+D`.
- L'option `-print` permet de calculer seulement la netlist après ordonnancement
sans l'interpréter
# Choix techniques
## Structure du programme
1. On parse d'abord les entrées de l'utilisateur à l'aide de `Scanf`, on
vérifie au passage que les entrées sont bien formées.
2. On fait une première passe sur la liste des équations pour déterminer si la
RAM et la ROM sont utilisées et préparer la mémoire (cf plus bas).
3. On évalue les instructions.
4. On met à jour les registres.
5. On oublie les variables intermédiaires (cf plus bas)
## La mémoire
Tout ce qui est relatif à la mémoire est encapsulé dans le module
`Interpreter.Env`. Cela m'a permis de faire plusieurs changements
d'implémentation sans trop d'efforts au cours de l'écriture de l'interpréteur.
L'implémentation finale est la suivante :
### RAM et ROM
La RAM et la ROM sont des matrices dont les lignes sont les mots stockés en
mémoire. Lors de chaque accès, la taille de l'adresses mémoire fournie est
vérifiée puis transformées en entier (conversion binaire - décimal)
Le simulateur commence par lire une première fois toutes les instructions pour
savoir si la RAM et la ROM sont utilisées et instancier une matrice de la bonne
taille le cas échéant. À cette occasion on vérifie que les tailles des adresses
et des mots spécifiées par le programmes sont cohérentes entre elles pour ne
plus avoir à le faire plus tard.
### Variables
Les variables sont stockées dans une table de hachage, facile à gérer tout le
long du programme car mutable. À la fin de chaque tour de boucle, cette table
est vidée.
### Registres
Les registres sont aussi représentés par une table de hachage, ils sont mis à
jour à la fin de chaque tour de boucle avant la remise à zéro des variables.
Au premier tour de boucle, cette table est vide, on considère que toutes les
registres sont à `false` et on profite de ce premier passage pour remplir la
table des registres
# Projet du cours de système digital 2016-2017
## HOWTO
La commande pour compiler le processeur est `make` il faut ensuite le lancer à
l'aide de `make run` (tourne dans le vide) ou
`./run.sh _CPU_build/cpu.net <asm file>``<asm file>` désigne le code
assembleur à charger dans la ROM avant l'exécution.
### Compiler séparément les différentes parties
Voici la liste des commandes makefile disponibles :
- `make`, `make run` : cf ci dessus.
- `make simulator` : compile uniquement le simulateur.
- `make minijazz` : compile uniquement le compilateur minijazz.
- `make pre_build` assemble les sources minijazz en un seul fichier.
- `make test` : lance une batterie de tests, cf plus bas.
- `make pdf` : génère un pdf à partir de `RAPPORT.md` et `README.md` à l'aide de
pandoc si pandoc est disponible. Ces fichiers sont lisibles tels quels à
défaut.
- `make clean` : nettoie le dépôt des fichiers générés par `make` et
`make test` uniquement.
- `make fullclean` : Nettoie tout le dépôt, y compris les fichiers générés par
ocamlbuild et le pdfs.
---
## Structure du dépôt
- `simulator/` : les sources du simulateur.
- Un parseur de netlist
- Un parseur d'assembleur MIPS
- L'ordonnancement et l'exécution du circuit
- `minijazz/` : le compilateur minijazz vers netlist.
- `cpu/` : le code minijazz du processeur lui même.
- Les différentes parties du processeur sont sous `cpu/src/`. Le point
d'entrée est dans le fichier `main.mj`.
- Un script Ocaml qui assemble les différents morceaux du CPU en un seul
fichier. Cf la section suivante.
- `test/` : des tests unitaires plus ou moins exhaustifs.
Les dossiers `_CPU_build/` et `_test_build/` sont générés automatiquement pour y
stocker les fichiers générés lors de la compilation.
Enfin, le script `run.sh` sert à lancer le simulateur sur un programme
assembleur donné en entrée. Usage : `run.sh <file.net> [<asm file>]`
## Système de build custom pour minijazz
Le script `cpu/pre_build.ml` sert à séparer le code du CPU en plusieurs fichiers
(dans `cpu/src/`). Ces fichiers doivent contenir sur leurs premières lignes la
liste des fichiers dont ils dépendent au format `require foo` (pour signifier
"dépend de `foo.mj`"). Le graphe de dépendance des différents morceaux de
circuit et ensuite construit et les fichiers sont concaténés dans le bon ordre.
**Attention** : ce n'est **pas** un vrai système de module au sens où il n'y a
pas de namespace : si deux fichiers génèrent des blocs avec le même nom il y
aura conflit.
---
## Tests
Le dossier `test/` contient trois types de tests :
- Des fichiers `.net` pour tester le simulateur (utilisés lors de l'écriture du
simulateur, plus utiles désormais).
- Des fichiers `.mj` avec pour chacun, des fichiers `.in` et `.out` qui
correspondent respectivement à des entrées données au circuit à chaque cycle
et les sorties attendues correspondantes. Le script `test_mj.sh` teste ces
circuits un à un sur les entrées spécifiées et vérifie la sortie.
- Des fichiers assembleur. Actuellement le script `test_asm.sh` vérifie
uniquement que les programmes sont chargés sans incident dans la ROM. À terme
il faudrait que le CPU soit lancé sur ces programmes et qu'on vérifie que
l'exécution se déroule correctement.
La commande `make test` permet de lancer tous les tests et d'afficher le
résultat.
require registers
require alu/utils
(* The ALU implementation
TODO
shamt et constantes sont envoyés par le multiplexeur
*)
ALU<n>(input1:[n], input2:[n], alu_control:[4]) = (alu_zero, result:[n]) where
alu_zero = false; (* TODO *)
result = input1; (* TODO *)
ALU(input1:[32], input2:[32], alu_control:[4]) = (alu_zero, result:[32]) where
result =
mux_n<word>(alu_control[0], (* if 1 then shift or HI/Lo, else logical or artih*)
mux_n<word>(alu_control[2], (* if 1 then HI/LO, else logical shift *)
mux_n<word>(alu_control[3], (* Case HI/LO *)
read_HI(), (*TODO*)
read_LO()
), (*TODO*)
mux_n<word>(alu_control[1], (* Case logical shift *)
shift_by_reg_ALU(input1, input2, 1, alu_control[3]),
shift_by_reg_ALU(input1, input2, 0, 0)
)
),
mux_n<word>(alu_control[1], (*if 1 then logical else arihtmetical *)
mux_n<word>(alu_control[2],
mux_n<word>(alu_control[3],
xor_ALU(input1, input2),
nor_ALU(input1, input2)
),
mux_n<word>(alu_control[3],
or_ALU(input1, input2),
and_ALU(input1, input2)
)
),
mux_n<word>(alu_control[2],
mux_n<word>(alu_control[3],
(*TODO : multiplication et division*)
zero_n<32>(),
zero_n<32>()
),
add_ALU(input1, input2, alu_control[3])
)
)
);
alu_zero = equal_zero_n<32>(result);
end where
......@@ -32,7 +32,40 @@ extand_const_n<n, offset>(c:[n]) = (o:[n + offset]) where
end where
(* logical shifts *)
shift_by_const_n<n, m, shift_val>(input:[n], cst:[m], is_right, keep_sign) =
(out:[n]) where
if (n + 1) <= (2 * shift_val) then (* remarque : seul <= est implémenté... *)
aux = mux_n<n>(cst[m-1],
mux_n<n>(is_right,
extand_left_n<n - shift_val, shift_val>
(input[..(n-1) - shift_val], mux(keep_sign, input[0], 0)),
extand_right_n<n - shift_val, shift_val>
(input[shift_val..], 0)),
input);
out = shift_by_const_n<n, m-1, n>(aux, cst[..m-2], is_right, keep_sign);
else
aux = mux_n<n>(cst[m-1],
mux_n<n>(is_right,
extand_left_n<n - shift_val, shift_val>
(input[..(n-1) - shift_val], mux(keep_sign, input[0], 0)),
extand_right_n<n - shift_val, shift_val>
(input[shift_val..], 0)),
input);
out = shift_by_const_n<n, m-1, 2*shift_val>
(aux, cst[..m-2], is_right, keep_sign);
end if
end where
(* shift by shamt, either to the right or to the left. Arithmetic or not.*)
shift_by_shamt_ALU(input:[32], shamt:[5], is_right, keep_sign) = (out:[32]) where
out = shift_by_const_n<32, 5, 1>(input, shamt, is_right, keep_sign)
end where
(* shift by value stored in reg, to the right or left, arithmetic or not *)
shift_by_reg_ALU(input:[32], cst:[32], is_right, keep_sign) = (out:[32]) where
out = shift_by_const_n<32, 32, 1>(input, cst, is_right, keep_sign)
end where
(* ------------------------------------------------------------------------- *)
(* Opérations booléennes *)
......@@ -96,7 +129,9 @@ not_n<n>(a:[n]) = (o:[n]) where
end if
end where
nor_ALU(a:[32], b:[32]) = (o:[32]) where
o = not_n<32>(or_ALU(a, b));
end where
(* ------------------------------------------------------------------------- *)
(*Opérations arithmétiques *)
......@@ -119,21 +154,15 @@ end where
(* Une fonction pour changer le signe d'une nappe *)
change_sign_n<n>(input:[n]) = (out:[n]) where
value = not_n<n>(input);
(out, flag) = adder_n<n>(value, extand_left_n<1,n-1>(1, 0), 0);
value = not_n<n>(input);
(out, flag) = adder_n<n>(value, extand_left_n<1,n-1>(1, 0), 0);
end where
(* Fonction d'addition, qui fait la soustraction si le drapeau vaut 1 *)
(* flag est le drapeau d'overflow, sub le drapeau de soustraction *)
add_ALU(a:[32], b:[32], sub) = (o:[32], flag) where
(o, flag) = adder_n<32>(a, mux(sub, change_sign_n<32>(b), b), 0);
zero = equal_zero_n<32>(o);
end where
addi_ALU(a:[32], c:[16], sub) = (o:[32], flag) where
(o, flag) = add_ALU(a, extand_const_n<16,16>(c), sub);
zero = equal_zero_n<32>(o);
add_ALU(a:[32], b:[32], sub) = (o:[32]) where
(o, flag) = adder_n<32>(a, mux_n<32>(sub, change_sign_n<32>(b), b), 0);
end where
(* Une fonction multiplication absolument peu pratique *)
......@@ -157,10 +186,10 @@ end where
(*TODO : changer le calcul pour utiliser la soustraction ?*)
slt_n<n>(a:[n], b:[n]) = (out, fin) where
if n = 1 then
if n = 1 then
fin = a[0]^b[0];
out = b[0]
else
else
(res, e) = slt_n<n-1>(a[..(n-2)], b[..(n-2)]);
fin = mux(e, 1, a[n-1]^b[n-1]);
out = mux(e, res, b[n-1])
......
require utils
(* Bloc de control du micro-processeur
Entrée : les 6 premiers bits d'une instruction
Sortie : les drapeaux de contrôle utilisés pour contrôler différents
blocs en fonction de l'instruction courante
......@@ -10,7 +10,7 @@ require utils
control(instruction:[6]) = (reg_dst, jump, branch, mem_read, mem_to_reg,
alu_op:[3], mem_write, alu_src, reg_write,
write_pc) where
(* Variable interne égale à 1 s'il s'agit d'une instruction de branchement,
beq ou bne *)
is_branch = equal_n<5>(instruction[0..4], 0.0.0.1.0);
......@@ -35,20 +35,25 @@ control(instruction:[6]) = (reg_dst, jump, branch, mem_read, mem_to_reg,
Uniquement utilisé par les trois instructions de lecture mémoire *)
mem_to_reg = mem_read;
(* alu_op à 00 => lecture/écriture mémoire nécéssitant une addition
(pour calculer l'adresse)
alu_op à 01 => branchement en cas d'(in)égalité nécéssitant
une soustraction
alu_op à 10 => choix d'instruction laissé au bloc de contrôle de l'ALU
(selon funct) *)
alu_op = not instruction[0] . is_branch . 1; (* TODO *)
(* Cas 11 supposé ignoré par alu_control *)
(* alu_op => cf google doc pour savoir quelles valeurs sont associées avec
quelles instructions
Remarque : la gestion actuelle fait que 000 est attribué aux R-format*)
alu_op =
mux(instruction[0], 1,
mux(instruction[2] & (not instruction[3]), 1, 0)) .
mux((instruction[2] & instruction[3]) & (instruction[4] xor instruction[5]),
1, 0) .
mux((instruction[2] xor instruction[5]) &
not(instruction[0]) &
instruction[3] or instruction[4],
1, 0);
(* mem_write à 1 => mémoire RAM accédée en écriture
Uniquement utilisé par les trois instructions d'écriture mémoire *)
mem_write = instruction[0] and instruction[2];
(* alu_src à 1 => seconde entré de l'ALU = valeur immédiate
(* alu_src à 1 => seconde entré de l'ALU = valeur immédiate
Ce cas est vrai pour tout format I, sauf pour les branchements
Ces instructions sont caractérisées par un des trois premiers bits à 1 *)
alu_src = instruction[0] or instruction[1] or instruction[2];
......
......@@ -31,8 +31,13 @@ main (b) = (instruction:[word], syscall, ctrl:[2]) where
(* Processing the calculi *)
imm = extand_const_n<16,16>(instruction[16..31]);
alu_input_2 = mux_n<word>(
use_shamt,
extand_left_n<5,word-5>(instruction[21..25]),
mux_n<word>(alu_src, imm, read_data2)
);
(alu_zero, alu_result) = ALU<word>(read_data1,
mux_n<word>(alu_src, imm, read_data2),
alu_input_2,
alu_ctrl);
(* Reading/writing RAM *)
......
all:
latexmk -pdf > /dev/null 2>&1
clean:
latexmk -c
rm -f *.bbl
This diff is collapsed.
@misc{ wiki-mips,
author = "Wikipedia",
title = "MIPS instruction set",
year = "2016",
howpublished = "\url{https://en.wikipedia.org/wiki/MIPS_instruction_set\#MIPS_assembly_language}",
note = "[accédée le 4 décembre 2016]"
}
@misc{ mars-syscall,
author = "Sanderson, Pete and Vollmar, Ken",
title = "MIPS syscall functions available in MARS",
howpublished = "\url{http://courses.missouristate.edu/KenVollmar/mars/Help/SyscallHelp.html}",
note = "[accédée le 9 décembre 2016]"
}
@book{patterson2013computer,
title={Computer organization and design: the hardware/software interface},
author={Patterson, David A and Hennessy, John L},
year={2013},
publisher={Newnes}
}
\ No newline at end of file
......@@ -60,6 +60,7 @@ module Env = struct
| n -> wrong_size n
let ram env = env.ram
let rom env = env.rom
end
......@@ -184,6 +185,7 @@ let interp asm_file n script_mode input_c output_c p =
)
p.p_outputs;
(* Handling system calls *)
let return_code = Ram.bits_of_int 32 @@ Syscall.syscall (Env.ram env) in
Env.write_on_ram 32 env [|true;true;false;false|] return_code
let return_code = Syscall.syscall (Env.ram env) (Env.rom env) in
let return_code_bin = Ram.bits_of_int 32 return_code in
Env.write_on_ram 32 env [|true;true;false;false|] return_code_bin
done
(** Prints an integer on stdout and returns 0 *)
let sys_print_int a0 =
Format.printf "\n\n ===> %d <== \n\n@?" a0;
Format.printf "%d@?" a0;
0
let sys_print_str _ = assert false
(** Reads an integer from stdin and returns it *)
let sys_input_int () = Scanf.scanf "%d " (fun x -> x)
let sys_input_str _ _ = assert false
(** Prints the string starting at start_addr in RAM on stdout.
The string must end with \000. *)
let sys_print_str rom start_addr =
let addr = ref start_addr in
let current_char = ref 0 in
let read_char () =
let c = Ram.int_of_bits @@ Ram.get_byte rom !addr in
current_char := c;
incr addr in
while (read_char () ; !current_char <> 0) do
print_char (char_of_int !current_char)
done;
0
(** Stops the simulator execution, maybe a little bit violent but… It will do
the trick. *)
let sys_exit () = exit 0
let syscall ram =
(** The switch for syscalls:
- The values of $a0, $a1, $v0 are read from the RAM
- The syscall is executed
- The return value is written at the right place in RAM
*)
let syscall ram rom =
let a0 = Ram.int_of_bits @@ Ram.get_word ram 4 in
let a1 = Ram.int_of_bits @@ Ram.get_word ram 8 in
let v0 = Ram.int_of_bits @@ Ram.get_byte ram 1 in
Printf.printf "SYSCALL: $v0=%d $a0=%d $a1=%d\n" v0 a0 a1;
match v0 with
| 0 -> 0
| 0 -> 0 (* a dummy syscall *)
| 1 -> sys_print_int a0
| 4 -> sys_print_str a0
| 4 -> sys_print_str rom a0
| 5 -> sys_input_int ()
| 8 -> sys_input_str a0 a1
| 10 -> sys_exit ()
| _ -> assert false
| n -> failwith @@
Printf.sprintf "Invalid syscall code: %d" n
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment