Les instructions immédiates
Nouveaux fichiers : immediate.h immediate.c
Fichiers modifiés : instr.h
Les instructions immédiates sont des instructions qui réalisent leur tache en traitant des données codées dans l'instruction elle-même. Ces données sont donc immédiatement disponibles. Typiquement, ce sont des instructions sur 2 ou 3 octets : le premier octet décrit l'opération à effectuer et l'octet ou les octets suivants sont les données à traiter.
Parmi ces instructions, on trouve :
- LXI (load register pair immediate) sur 3 octets : permet de charger une valeur 16-bit dans une paire de registre. Le premier octet indique que l'opération est LXI et donne la paire de registres. Les deux autres octets sont la valeur à charger.
- MVI (move immediate data ) sur 2 octets : permet de charger une valeur dans un registre ou dans la mémoire. Le premier octet indique que l'opération est MVI et précise le registre. Le second octet est la valeur à charger.
- Les opérations arithmétiques et logiques (ADI, ACI, SUI, SBI, ANI, ORI, XRI, CPI), sur deux octets : le premier octet décrit l'opération à effectuer, le second octet décrit la valeur à utiliser. Par exemple ADI ajoute la valeur donnée à l'accumulateur (c'est l'équivalent d'un
+=en C).
Bien sûr, pour implémenter les opérations arithmétiques et logiques, nous nous servirons de nos fonctions op_XXX codées dans l'unité alu. Cela permettra également de mettre à jour correctement les flags.
On ajoute le fichier d'en-tête suivant :
#ifndef IMMEDIATE_H
#define IMMEDIATE_H
#include "computer.h"
#include <stdint.h>
void instr_lxi(Computer *comp, uint8_t instr, uint8_t low_data, uint8_t high_data);
void instr_mvi(Computer *comp, uint8_t instr, uint8_t data);
void instr_adi(Computer *comp, uint8_t data);
void instr_aci(Computer *comp, uint8_t data);
void instr_sui(Computer *comp, uint8_t data);
void instr_sbi(Computer *comp, uint8_t data);
void instr_ani(Computer *comp, uint8_t data);
void instr_ori(Computer *comp, uint8_t data);
void instr_xri(Computer *comp, uint8_t data);
void instr_cpi(Computer *comp, uint8_t data);
#endif
1. Instruction LXI
L'instruction LXI est codée sur trois octets, sert à enregistrer une valeur donnée dans une paire de registres. Elle a le format suivant :
où les deux bitsRR servent à préciser la paire de registre avec les conventions suivantes :
RR |
Paire de registres (ou registre) |
|---|---|
00 |
BC |
01 |
DE |
10 |
HL |
11 |
SP |
Remarquer que dans le dernier cas, il ne s'agit pas d'une paire de registres mais simplement du registre SP (Stack pointer). En effet, rappelez-vous que c'est un des deux seuls registres 16-bits du processeur et qu'il faut donc lui fournir une donnée sur 16 bits.
Pour éviter d'écrire à la main dans le code ces constantes arbitraire, nous allons proprement ajouter les constantes adéquates dans le fichier instr.h existant :
// Constantes pour les paires de registres
#define PAIR_BC 0b00
#define PAIR_DE 0b01
#define PAIR_HL 0b10
#define PAIR_SP Ob11
Enfin concernant la donnée 16 bits à enregistrer dans la paire de registres (ou SP), elle est codée sur les 2 octets low_data et high_data. low_data représente les 8 bits de poids faible de la valeur et high_data les 8 bits de poids fort.
Exercice
Dans un nouveau fichier immediate.c, implémenter la fonction instr_lxi. Pour cela il faudra :
- Décoder l'argument
instrpour déterminer la paire de registre (ou SP) à modifier - Enregistrer la valeur donnée par
low_dataethigh_datadans la paire de registres désignée (ou SP)
Remarquer que dans le cas de SP, il sera nécessaire de reformer un entier 16 bits à partir des deux entiers 8 bits donnés.
2. Instruction MVI
L'instruction MVI est comparable à l'instruction LXI sauf qu'elle enregistre une valeur sur 8 bits au lieu d'une valeur sur 16 bits. Elle a pour forme :
Dans le premier octet,RRR désigne le registre à modifier avec exactement les mêmes conventions que pour l'instruction MOV déjà traitée. On rappelle tout de même les conventions :
| Registre (ou mémoire) | Code |
|---|---|
| Registre B | 000 |
| Registre C | 001 |
| Registre D | 010 |
| Registre E | 011 |
| Registre H | 100 |
| Registre L | 101 |
| Mémoire | 110 |
| Registre A | 111 |
Dans le cas de la valeur 110, la donnée ne sera pas enregistrée dans un registre mais dans la mémoire de l'ordinateur. L'adresse mémoire à utiliser sera alors celle précisée par la paire de registres HL.
Nous rappelons également que toutes ces constantes sont déjà définies dans instr.h (REGA, ..., REGL, MEM).
La donnée à enregistrer est tout simplement le second octet (data) de l'instruction.
Exercice
Dans le fichier immediate.c, implémenter la fonction instr_mvi. Pour cela il faudra :
- Décoder l'argument
instrpour déterminer le registre à modifier - Dans le cas de
MEM: récupérer l'adresse stockée dans HL. - Enregistrer la valeur
dataau bon endroit.
3. Les instructions arithmétiques et logiques immédiates
Ce sont des instructions sur 2 octets ayant pour format :
Le code d'instruction ne contient aucune donnée donc nous n'auront pas besoin de le manipuler ici. Il sert simplement à décrire l'opération à effectuer. Toutes ces opérations s'accumulent dans le registre A (d'où son nom). Par exemple, ADI ajoute la valeur data dans le registre A. Ce sera donc le seul registre modifié par les opérations ci-dessous.Décrivons maintenant chacune des opérations. Dans le tableau ci-dessous je note \(d\) la valeur sur 8 bits à manipuler et \(A\) le registre A.
| Instruction | Effet | Description |
|---|---|---|
| ADI | \(A \gets A + d\) | Ajoute \(d\) dans l'accumulateur |
| ACI | \(A \gets A + d + 1\) | Ajoute \(d\) dans l'accumulateur avec une retenue d'entrée |
| SUI | \(A \gets A - d\) | Soustrait \(d\) de l'accumulateur |
| SBI | \(A \gets A - d - 1\) | Soustrait \(d\) de l'accumulateur avec une retenue d'entrée (emprunt) |
| ANI | \(A \gets A \text{ AND } d\) | Réalise le ET bit à bit de \(A\) et \(d\) |
| ORI | \(A \gets A \text{ OR } d\) | Réalise le OU bit à bit de \(A\) et \(d\) |
| XRI | \(A \gets A \text{ XOR } d\) | Réalise le OU exclusif bit à bit de \(A\) et \(d\) |
| CPI | \(\varnothing \gets A - d\) | Réalise en interne la soustraction de \(A\) par \(d\) sans changer \(A\) (permet de comparer) |
La dernière opération mérite une explication. Lorsqu'on exécute CPI d aucun registre n'est modifié, cependant l'opération \(A - d\) est réalisée
en interne et les drapeaux sont modifiés par cette soustraction. On peut alors regarder ensuite :
- Le flag Z (Zero) : s'il est set les deux valeurs sont égales, s'il est reset elles sont différentes
- Le flag CY (Carry) : s'il est set c'est que \(d > A\), s'il est reset c'est que \(d \leq A\).
On comprend maintenant le but de cette opération qui est de comparer deux valeurs.
Attention
Ce n'est plus à vous de modifier les flags ici, ils se modifieront automatiquement par l'appel à la fonction op_sub
Exercice
Implémenter dans immediate.c les instructions instr_adi, instr_aci, ..., instr_cpi. Pour cela :
- Chaque instruction fera un seul appel à l'une des fonctions
op_XXXde l'unitéalu. Il faudra utiliser correctement les paramètres carry et borrow deop_addetop_sub - Enregistrer le résultat dans le registre A (sauf pour
CPIbien sûr)
Remarque
Les appels de fonction op_XXX mettront correctement les drapeaux à jour, on n'a pas à s'en préoccuper ici.