Architecture Seq
Architecture générale:
Attention, pour que la figure reste relativement lisible, tous les fils ne sont pas montrés. Notamment, icode, ifun, etc. sortant de Instruction memory sont branchés à diverses boîtes grises. Les boîtes grises dstE, dstM, srcA, srcB sont par ailleurs branchées sur Register file.
Les données vont de bas en haut, il faut donc commencer par regarder le bas.
Les boîtes bleues ne seront pas détaillées: elles se comportent tel que l'on a vu en Cours et en TD:
- Instruction memory est le circuit qui lit en mémoire les instructions, il produit l'instruction toute décodée (icode, ifun, rA, rB, valC).
- PC increment calcule l'adresse de l'instruction située juste après l'instruction courante en mémoire, appelée valP.
- Register file prend en entrée des numéros de registre srcA et srcB, et sort les valeurs de ces registres, valA et valB. Il prend également en entrée des numéros de registre dstE et dstM, ainsi que des valeurs valE et valM, et écrit ces valeurs dans les registres correspondant (à la fin du cycle).
- ALU prend deux valeurs aluA et aluB et un numéro de fonction alufun, effectue le calcul, produit le résultat valE et les codes de condition CC.
- CC est un simple registre.
- bcond n'est utilisé que pour les jump: il produit en sortie Bch si un branchement doit être pris ou non.
- Data memory permet de lire / écrire en mémoire. mem_read et mem_write indiquent si l'on veut effectuer une lecture, une écriture, ou ne rien faire. mem_addr indique l'adresse à laquelle lire ou écrire, et mem_data indique quelle valeur écrire dans le cas d'une écriture. La valeur éventuellement lue est produite en sortie valM.
Ce qui nous intéresse plus, ce sont les boîtes grises, qui sont codées en
HCL, décrit ci-dessous. Ce sont en fait juste des multiplexeurs un peu évolués:
ils prennent des informations en entrée et produisent une valeur en sortie, qui
est en général l'une des entrées.
Syntaxe HCL
type nom_sortie = [
condition1 : valeur_de_sortie_1;
condition2 : valeur_de_sortie_2;
];
Le comportement est très similaire à un switch C: si la
condition1 est vraie, la sortie nom_sortie prend la valeur
valeur_de_sortie_1, et sinon, si la condition2 est vraie, elle
prend la valeur valeur_de_sortie_2, etc. Bien souvent, une valeur par
défaut est ajoutée à la fin en utilisant simplement la condition 1
(toujours vraie), qui "attrape" donc tous les cas qui n'ont pas été traité plus
haut.
Dans les conditions, on peut écrire des tests comme en C: valeur == VAL1 || valeur == VAL2. HCL permet cependant une syntaxe additionnelle bien plus agréable: on peut utiliser la syntaxe valeur in { VAL1, VAL2 } qui en est équivalent.
Nota: JXX représente l'ensemble des JMP, JE, JNE, JL, JLE, JG, JGE. C'est
Bch qui indique si le branchement doit être pris ou non (il est donc bien sûr
toujours égal à 1 dans le cas de JMP).
Nota2: OPL représente l'ensemble des
ADDL, SUBL, ANDL, XORL, SALL, SARL, et IOPL représente l'ensemble des
IADDL, ISUBL, IANDL, IXORL, ISALL, ISARL. C'est ensuite ifun qui indique quelle
opération est demandée dans l'instruction.
Nota3: RESP désigne le registre esp.
Détail des boîtes combinatoires:
int new_pc = [
# Call. Use instruction constant
icode == CALL : valC;
# Taken branch. Use instruction constant
icode == JXX && Bch : valC;
# Completion of RET instruction. Use value from stack
icode == RET : valM;
# Default: Use incremented PC
1 : valP;
];
## Set read control signal
bool mem_read = icode in { MRMOVL, POPL, RET };
## Set write control signal
bool mem_write = icode in { RMMOVL, PUSHL, CALL };
## Select memory address
int mem_addr = [
icode in { RMMOVL, PUSHL, CALL, MRMOVL } : valE;
icode in { POPL, RET } : valA;
# Other instructions don't need address
];
## Select memory input data
int mem_data = [
# Value from register
icode in { RMMOVL, PUSHL } : valA;
# Return PC
icode == CALL : valP;
# Default: Don't write anything
];
## Select input A to ALU
int aluA = [
icode in { RRMOVL, OPL } : valA;
icode in { IRMOVL, RMMOVL, MRMOVL, IOPL } : valC;
icode in { CALL, PUSHL } : -4;
icode in { RET, POPL } : 4;
# Other instructions don't need ALU
];
## Select input B to ALU
int aluB = [
icode in { RMMOVL, MRMOVL, OPL, IOPL, CALL,
PUSHL, RET, POPL } : valB;
icode in { RRMOVL, IRMOVL } : 0;
# Other instructions don't need ALU
];
## Set the ALU function
int alufun = [
icode in { OPL, IOPL } : ifun;
1 : ALUADD;
];
## Should the condition codes be updated?
bool set_cc = icode in { OPL, IOPL };
## What register should be used as the A source?
int srcA = [
icode in { RRMOVL, RMMOVL, OPL, PUSHL } : rA;
icode in { POPL, RET } : RESP;
1 : RNONE; # Don't need register
];
## What register should be used as the B source?
int srcB = [
icode in { OPL, IOPL, RMMOVL, MRMOVL } : rB;
icode in { PUSHL, POPL, CALL, RET } : RESP;
1 : RNONE; # Don't need register
];
## What register should be used as the E destination?
int dstE = [
icode in { RRMOVL, IRMOVL, OPL, IOPL} : rB;
icode in { PUSHL, POPL, CALL, RET } : RESP;
1 : RNONE; # Don't need register
];
## What register should be used as the M destination?
int dstM = [
icode in { MRMOVL, POPL } : rA;
1 : RNONE; # Don't need register
];
En résumé, pour le côté A:
- rA est le numéro de registre encodé dans l'instruction
- srcA est le numéro de registre envoyé au Register file, en général c'est rA, mais pas toujours (e.g. POP/RET).
- valA est la valeur du registre srcA lu par le Register file
- aluA est la valeur envoyée à l'ALU côté A, en général c'est valA, mais pas toujours (e.g. IOPL)