#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdint.h>

// TP 1 de IN330 -- exercices préparatoires
// LG et SM pour IN330, 2024
// BEGIN CUT
// 1. Type S, opcode 0100011, source = x3, offset = -8, dest = x12
//    La nomenclature de "source" et "destination" est un peu floue, si rs1/rs2
//    est src/dst ou l'inverse c'est pas grave tant que les décalages de bits
//    sont bons. Le encode_S_type impose src = rs2 et dest = rs1, mais pour les
//    autres formats ils peuvent faire un peu différemment
// 2. Arguments entiers parce que les instructions sont des entiers/champs de
//    bits. Indice : on a encodé en entier dans parse_arg.c. Valeur de retour :
//    u32 parce que les instructions font 32 bits (unsigned pour éviter un peu
//    de dynamique de signes qui gênerait les décalages vers la droite quand on
//    décode)
// 3. Ça construit progressivement le champs de bits
// 4. %x : hexadécimal, 8 : 8 caractères, 0 : remplir avec des zéros
//    e.g. 42 -> 0000002a
// END CUT

/* Code fourni : types S, B, J */
uint32_t encode_S_type(int opcode, int funct3, int rsrc, int imm, int rdst)
{
    printf(":: S type (opcode=%d funct3=%d args=[%d,%d,%d])\n",
           opcode, funct3, rsrc, rdst, imm);
    int imm_4_0  = (imm & 0b000000011111);
    int imm_11_5 = (imm & 0b111111100000) >> 5;
    return opcode | (imm_4_0 << 7) | (funct3 << 12) | (rdst << 15)
           | (rsrc << 20) | (imm_11_5 << 25);
}

uint32_t encode_B_type(int opcode, int funct3, int rsrc1, int rsrc2, int imm)
{
    printf(":: B type (opcode=%d funct3=%d args=[%d,%d,%d])\n",
           opcode, funct3, rsrc1, rsrc2, imm);
    int imm_4_1  = (imm & 0b0000000011110) >> 1;
    int imm_10_5 = (imm & 0b0011111100000) >> 5;
    int imm_11   = (imm & 0b0100000000000) >> 11;
    int imm_12   = (imm & 0b1000000000000) >> 12;
    return opcode | (imm_11 << 7) | (imm_4_1 << 8) | (funct3 << 12)
           | (rsrc1 << 15) | (rsrc2 << 20) | (imm_10_5 << 25)
           | (imm_12 << 31);
}

uint32_t encode_J_type(int opcode, int rdest, int imm)
{
    printf(":: J type (opcode=%d args=[%d,%d])\n",
        opcode, rdest, imm);
    int imm_10_1  = (imm & 0b000000000011111111110) >> 1;
    int imm_11    = (imm & 0b000000000100000000000) >> 11;
    int imm_19_12 = (imm & 0b011111111000000000000) >> 12;
    int imm_20    = (imm & 0b100000000000000000000) >> 20;
    return opcode | (rdest << 7) | (imm_19_12 << 12) | (imm_11 << 20)
           | (imm_10_1 << 21) | (imm_20 << 31);
}


//TODO : réaliser les fonctions pour les types I, et R
//BEGIN CUT
uint32_t encode_I_type(int opcode, int funct3,  int rsrc, int rdst, int imm)
{
    printf(":: I type (opcode=%d funct3=%d args=[%d,%d,%d])\n",
        opcode, funct3, rsrc, rdst, imm);
    return opcode | (rdst << 7) | (funct3 << 12)
           | (rsrc << 15) | ((imm & 0xfff) << 20);
}


uint32_t encode_R_type(int opcode, int funct3, int funct7, int rsrc1, int rsrc2, int rdst)
{
    printf(":: R type (opcode=%d funct3=%d funct7=%d args=[%d,%d,%d])\n",
        opcode, funct3, funct7, rdst, rsrc1, rsrc2);
    return opcode | (rdst << 7) | (funct3 << 12)
           | (rsrc1 << 15) | (rsrc2 << 20) | (funct7 << 25);
}
//END CUT



uint32_t encode_instruction(char* name, int arg1, int arg2, int arg3)
{
  // Cette fonction dispatche les instructions en regardant leur nom.

  // Exemple :
  if(!strcmp(name, "sd"))
    return encode_S_type(0b0100011, 0b011, arg1, arg2, arg3);

  //TODO : ajouter les autres instructions.
  // Attention à l'ordre des arguments.
  // Evidemment, on teste au fur et à mesure !
  
  //BEGIN CUT
  // LG attention je n'ai vraiment pas testé le dispatch!!
  if(!strcmp(name, "addi"))
    return encode_I_type(0b0010011, 0b000, arg2, arg1, arg3);
  if(!strcmp(name, "add"))
    return encode_R_type(0b0110011, 0b000, 0b0000000, arg2, arg3, arg1);
  if(!strcmp(name, "beq"))
    return encode_B_type(0b1100011, 0b000, arg1, arg2, arg3);
  if(!strcmp(name, "bne"))
    return encode_B_type(0b1100011, 0b001, arg1, arg2, arg3);
  if(!strcmp(name, "blt"))
    return encode_B_type(0b1100011, 0b100, arg1, arg2, arg3);
  if(!strcmp(name, "bge"))
    return encode_B_type(0b1100011, 0b101, arg1, arg2, arg3);
  if(!strcmp(name, "jal"))
    return encode_J_type(0b1101111, arg1, arg2);
  if(!strcmp(name, "sub"))
    return encode_R_type(0b0110011, 0b000, 0b0100000, arg1, arg2, arg3);

  /* Pour ld on inverse les arguments 2 et 3 pour coller au format */
  if(!strcmp(name, "ld")) {
    return encode_I_type(0b0000011, 0b011, arg1, arg3, arg2);
  }
  
  /* Alias */
  if(!strcmp(name, "j")) {
    return encode_J_type(0b1101111, 0, arg1);
  }
  if(!strcmp(name, "li")) {//comme un add avec x0
    return encode_I_type(0b0010011, 0b000, 0, arg1, arg2);
  }
  if(!strcmp(name, "mv")) {
    return encode_I_type(0b0010011, 0b000, arg1, arg2, 0);
  }
  //END CUT
  
  printf(":: UNKNOWN INSTRUCTION\n");
  return 0;
}
  
int main(void)
{
  // Encodons l'instruction : sd x12, -8(x3)
  // source = 12, imm = -8, dest = 3
  // On doit trouver fec1bc23
  
  // D'abord avec la fonction spécifique aux instructions de type S.
  uint32_t enc1 = encode_S_type(0b0100011, 0b011, 12, -8, 3);
  printf("encodage = %08x\n", enc1);

  //BEGIN CUT
  
  //jal ra 4 -- 004000ef -- ra = 1
  enc1 = encode_J_type(0b1101111, 1, 4);
  printf("encodage = %08x\n", enc1);

  //addi x6, x2, 42 -- 02a10313
  enc1 = encode_I_type(0b0010011, 0b000, 2, 6, 42);
  printf("encodage = %08x\n", enc1);

  //sub x2, x1, x3 -- 40308133
  enc1 = encode_R_type(0b0110011, 0b000, 0x20, 1, 3, 2);
  printf("encodage = %08x\n", enc1);
  
  // bne x5, x17, -4 ff129ee3
  enc1 = encode_B_type(0b1100011, 0b001, 5, 17, -4);
  printf("encodage = %08x\n", enc1);

  enc1 = encode_instruction("bne", 5, 17, -4);
  printf("encodage = %08x\n", enc1);
  
  
  //END CUT
  
  // Ensuite avec une fonction qui sélectionne le bon type et fournit opcode,
  // funct3 selon le nom de l'instruction.
  enc1 = encode_instruction("sd", 12, -8, 3);
  printf("encodage = %08x\n", enc1);

  // Autres petits exemples:
  //   jal ra, 4          -> 004000ef
  //   addi x6, x2, 42    -> 02a10313
  //   sub x2, x1, x3     -> 40308133
  //   bne x5, x17, -4    -> ff129ee3

  return 0;
}
