Instructivo sobre decodificador de instrucciones binarias

Los objetivos de este instructivo son los siguientes:

  • Conoce la estructura y sintaxis del archivo de descripción en formato binario.
  • Obtén más información sobre cómo la descripción en formato binario coincide con la descripción de ISA.
  • Escribe las descripciones binarias para el subconjunto de instrucciones RiscV RV32I.

Descripción general

Codificación de instrucciones binarias de RiscV

La codificación de instrucciones binarias es la forma estándar de codificar instrucciones para la ejecución en un microprocesador. Por lo general, se almacenan en un archivo ejecutable, generalmente en formato ELF. Las instrucciones pueden ser de ancho fijo o variables ancho.

Normalmente, las instrucciones usan un pequeño conjunto de formatos de codificación, con cada formato para el tipo de instrucciones codificadas. Por ejemplo, registro-registro las instrucciones pueden usar un formato que maximice la cantidad de códigos de operación disponibles, mientras que las instrucciones inmediatas de registro usan otra que intercambia la cantidad de los códigos de operación disponibles para aumentar el tamaño del inmediato que se puede codificar. Las instrucciones Branch y jump casi siempre usan formatos que maximizan el tamaño de lo inmediato para admitir ramas con desplazamientos más grandes.

Los formatos de instrucción que usan las instrucciones que queremos decodificar en nuestra RiscV del simulador son los siguientes:

Formato de tipo R, que se usa para instrucciones de registro y registro:

31/25 24/20 19/15 14-12 11 a 7 6...
7 5 5 3 5 7
func7 rs2 rs1 func3 .o código de operación

formato I-Type, se usa para registrar instrucciones inmediatas, cargar instrucciones y la instrucción jalr, 12 bits inmediatos.

31/20 19/15 14-12 11 a 7 6...
12 5 3 5 7
imm12 rs1 func3 .o código de operación

Formato especializado tipo I, que se usa para cambios con instrucciones inmediatas, 5 bits inmediato:

31/25 24/20 19/15 14-12 11 a 7 6...
7 5 5 3 5 7
func7 uimm5 rs1 func3 .o código de operación

Formato U-Type, se usa para instrucciones inmediatas largas (lui, auipc), 20 bits inmediato:

31-12 11 a 7 6...
20 5 7
uimm20 .o código de operación

Formato tipo B, que se usa para ramas condicionales, inmediato de 12 bits.

31 30-25 24/20 19/15 14-12 11 a 8 7 6...
1 6 5 5 3 4 1 7
MM MM rs2 rs1 func3 MM MM código de operación

Formato de tipo J, que se usa para la instrucción jal, 20 bits inmediatos.

31 30-21 20 19/12 11 a 7 6...
1 10 1 8 5 7
MM MM MM MM .o código de operación

Formato tipo S, que se usa para las instrucciones de almacenamiento; 12 bits inmediatos.

31/25 24/20 19/15 14-12 11 a 7 6...
7 5 5 3 5 7
MM rs2 rs1 func3 MM código de operación

Como puedes ver en estos formatos, todas estas instrucciones son de 32 bits y los 7 bits bajos de cada formato es el campo del código de operación. También observa que, si bien varios formatos tienen el mismo tamaño en inmediatos; sus bits se toman de diferentes partes de la instrucción. Como veremos, el decodificador binario de especificación del modelo de AA pueda expresar esto.

Descripción de codificación binaria

La codificación binaria de la instrucción se expresa en formato binario (.bin_fmt) archivo de descripción. Describe la codificación binaria de instrucciones en un ISA para que un decodificador de instrucciones de formato binario pueda de red. El decodificador generado determina el código de operación, extrae el valor de operando e inmediato, para proporcionar la información que necesita la ISA independiente de codificación descrito en el instructivo anterior.

En este instructivo, escribiremos un archivo de descripción de codificación binaria para un subconjunto de las instrucciones RiscV32I necesarias para simular las instrucciones usadas en una pequeño "Hello World" . Para obtener más detalles sobre el ISA de RiscV, consulta Especificaciones de Risc-V{.external}.

Para comenzar, abre el archivo: riscv_bin_decoder/riscv32i.bin_fmt

El contenido del archivo se divide en varias secciones.

En primer lugar, está la definición de decoder.

decoder RiscV32I {
  // The namespace in which code will be generated.
  namespace mpact::sim::codelab;
  // The name (including any namespace qualifiers) of the opcode enum type.
  opcode_enum = "OpcodeEnum";
  // Include files specific to this decoder.
  includes {
    #include "riscv_isa_decoder/solution/riscv32i_decoder.h"
  }
  // Instruction groups for which to generate decode functions.
  RiscVInst32;
};

La definición del decodificador especifica el nombre de nuestro decodificador RiscV32I, así como cuatro datos adicionales. El primero es namespace, que define el espacio de nombres en el que se ubicará el código generado. En segundo lugar, el opcode_enum, que indica el tipo de enumeración de código de operación que se genera se debe hacer referencia a través del decodificador ISA en el código generado. Tercero, includes {} especifica los archivos de inclusión necesarios para el código generado para este decodificador. En nuestro caso, este es el archivo que produce el decodificador ISA a partir de la instructivo anterior. Los archivos de inclusión adicionales se pueden especificar en un includes {} con alcance global. definición. Esto es útil si se definen varios decodificadores y todos necesitan incluir algunos de los mismos archivos. El cuarto es una lista de nombres de instrucciones grupos que componen las instrucciones para las que se genera el decodificador. En nuestra solo hay una: RiscVInst32.

A continuación, hay tres definiciones de formato. Estos representan instrucciones diferentes formatos para una palabra de instrucción de 32 bits que usan las instrucciones ya definidas en el archivo.

// The generic RiscV 32 bit instruction format.
format Inst32Format[32] {
  fields:
    unsigned bits[25];
    unsigned opcode[7];
};

// RiscV 32 bit instruction format used by a number of instructions
// needing a 12 bit immediate, including CSR instructions.
format IType[32] : Inst32Format {
  fields:
    signed imm12[12];
    unsigned rs1[5];
    unsigned func3[3];
    unsigned rd[5];
    unsigned opcode[7];
};

// RiscV instruction format used by fence instructions.
format Fence[32] : Inst32Format {
  fields:
    unsigned fm[4];
    unsigned pred[4];
    unsigned succ[4];
    unsigned rs1[5];
    unsigned func3[3];
    unsigned rd[5];
    unsigned opcode[7];
};

El primero define un formato de instrucción de 32 bits de ancho llamado Inst32Format que tiene dos campos: bits (25 bits de ancho) y opcode (7 bits de ancho). Cada campo unsigned, lo que significa que el valor se extenderá en cero cuando se extraiga. y se coloca en un tipo de número entero C++. La suma de los anchos de los campos de bits debe igual al ancho del formato. La herramienta generará un error si un desacuerdo. Este formato no deriva de ningún otro, por lo que es se considera un formato de nivel superior.

La segunda define un formato de instrucción de 32 bits de ancho llamado IType que deriva de Inst32Format, lo que hace que estos dos formatos estén relacionados. El formato contiene 5 campos: imm12, rs1, func3, rd y opcode. El campo imm12 tiene las siguientes características: signed, lo que significa que el valor se extenderá por signo cuando el valor sea se extraen y se colocan en un tipo de número entero C++. Ten en cuenta que IType.opcode tiene el mismo atributo con o sin firma y se refiere a los mismos bits de la palabra de instrucción como Inst32Format.opcode.

El tercer formato es un formato personalizado que solo usa fence. instrucción, que es una instrucción que ya está especificada y no tenemos en las que debes preocuparte en este instructivo.

Punto clave: reutilizar los nombres de los campos en distintos formatos relacionados, siempre y cuando representar los mismos bits y tener el mismo atributo firmado/sin firma.

Después de las definiciones de formato en riscv32i.bin_fmt, aparece un grupo de instrucciones. definición. Todas las instrucciones de un grupo de instrucciones deben tener el mismo longitud de bits y usar un formato que derive (quizás indirectamente) de la misma formato de instrucción de primer nivel. Cuando un ISA puede tener instrucciones con diferentes se utiliza un grupo de instrucciones diferente para cada longitud. Además: si la decodificación de ISA de destino depende de un modo de ejecución, como ARM frente a Thumb. se requiere un grupo de instrucciones separado para cada modo. El El analizador bin_fmt genera un decodificador binario para cada grupo de instrucciones.

instruction group RiscV32I[32] "OpcodeEnum" : Inst32Format {
  fence   : Fence  : func3 == 0b000, opcode == 0b000'1111;
  csrs    : IType  : func3 == 0b010, rs1 != 0, opcode == 0b111'0011;
  csrw_nr : IType  : func3 == 0b001, rd == 0,  opcode == 0b111'0011;
  csrs_nw : IType  : func3 == 0b010, rs1 == 0, opcode == 0b111'0011;
};

El grupo de instrucciones define un nombre RiscV32I, un [32] de ancho y el nombre de el tipo de enumeración de código de operación para usar "OpcodeEnum" y una instrucción base de un conjunto de datos tengan un formato común. El tipo de enumeración del código de operación debe ser el mismo que produce el independiente de formato del decodificador de instrucciones que se aborda en el instructivo sobre la ISA decodificador.

Cada descripción de codificación de instrucciones consta de 3 partes:

  • El nombre del código de operación, que debe ser el mismo que se usó en la instrucción del decodificador para que funcionen juntos.
  • El formato de la instrucción que se usará para el código de operación. Este es el formato que se usa para satisfacer referencias a campos de bits en la parte final.
  • Una lista separada por comas de restricciones de campos de bits, ==, !=, <, <=, >, y >= deben ser verdaderos para que el código de operación coincida correctamente con el palabra de instrucción.

El analizador .bin_fmt usa toda esta información para compilar un decodificador que hace lo siguiente:

  • Proporciona funciones de extracción (con/sin firma) según corresponda para cada bit en todos los formatos. Las funciones de extractor se colocan en espacios de nombres nombrado por la versión en snake case del nombre del formato. Por ejemplo, el Las funciones de extractor para el formato IType se colocan en el espacio de nombres i_type. Cada función de extractor se declara inline y toma la uint_t más estrecha tipo que contiene el ancho del formato y muestra el int_t más estrecho (para firma), uint_t (para sin firma), que contiene el campo extraído ancho. P. ej.:
inline uint8_t ExtractOpcode(uint32_t value) {
  return value & 0x7f;
}
  • Una función de decodificación para cada grupo de instrucciones. Muestra un valor de tipo OpcodeEnum y toma el tipo de uint_t más angosto que tiene el ancho de el formato del grupo de instrucciones.

Realiza la compilación inicial

Cambia el directorio a riscv_bin_decoder y compila el proyecto con el comando siguiente comando:

$ cd riscv_bin_decoder
$ bazel build :all

Ahora, cambia tu directorio nuevamente a la raíz del repositorio. Luego, veamos en las fuentes que se generaron. Para eso, cambia el directorio bazel-out/k8-fastbuild/bin/riscv_bin_decoder (suponiendo que usas un x86 host: para otros hosts, k8-fastbuild será otra cadena).

$ cd ..
$ cd bazel-out/k8-fastbuild/bin/riscv_bin_decoder
  • riscv32i_bin_decoder.h
  • riscv32i_bin_decoder.cc

El archivo de encabezado generado (.h)

Abre riscv32i_bin_decoder.h. La primera parte del archivo contiene imágenes protecciones estándar, archivos include, declaraciones de espacio de nombres Luego, hay una función auxiliar con plantilla en el espacio de nombres internal. Esta función se usa para extraer campos de bits de formatos demasiado largos para ajustarse a un código C++ de 64 bits entero.

#ifndef RISCV32I_BIN_DECODER_H
#define RISCV32I_BIN_DECODER_H

#include <iostream>
#include <cstdint>

#include "third_party/absl/functional/any_invocable.h"


#include "learning/brain/research/mpact/sim/codelab/riscv_isa_decoder/solution/riscv32i_decoder.h"

namespace mpact {
namespace sim {
namespace codelab {


namespace internal {

template <typename T>
static inline T ExtractBits(const uint8_t *data, int data_size,
                            int bit_index, int width) {
  if (width == 0) return 0;

  int byte_pos = bit_index >> 3;
  int end_byte = (bit_index + width - 1) >> 3;
  int start_bit = bit_index & 0x7;

  // If it is only from one byte, extract and return.
  if (byte_pos == end_byte) {
    uint8_t mask = 0xff >> start_bit;
    return (mask & data[byte_pos]) >> (8 - start_bit - width);
  }

  // Extract from the first byte.
  T val = 0;
  val = data[byte_pos++] & 0xff >> start_bit;
  int remainder = width - (8 - start_bit);
  while (remainder >= 8) {
    val = (val << 8) | data[byte_pos++];
    remainder -= 8;
  }

  // Extract any remaining bits.
  if (remainder > 0) {
    val <<= remainder;
    int shift = 8 - remainder;
    uint8_t mask = 0b1111'1111 << shift;
    val |= (data[byte_pos] & mask) >> shift;
  }
  return val;
}

}  // namespace internal

Luego de la sección inicial, hay un conjunto de tres espacios de nombres, uno para cada uno de las declaraciones format en el archivo riscv32i.bin_fmt:


namespace fence {

...

}  // namespace fence

namespace i_type {

...

}  // namespace i_type

namespace inst32_format {

...

}  // namespace inst32_format

En cada uno de estos espacios de nombres, la función de extracción de campos de bits inline para cada campo de bits en ese formato. Además, el formato base duplica las funciones de extracción de los formatos subordinados para los que 1) los nombres de los campos solo aparecen en un único nombre de campo o 2) para el que los nombres de campo se refieren al mismo campo de tipo (posiciones con signo/sin firmar y bits) en cada formato en el que aparecen. Esto habilita campos de bits que describen la misma bits que se extraerán con funciones en el espacio de nombres de formato de nivel superior.

A continuación, se muestran las funciones del espacio de nombres i_type:

namespace i_type {

inline uint8_t ExtractFunc3(uint32_t value) {
  return  (value >> 12) & 0x7;
}

inline int16_t ExtractImm12(uint32_t value) {
  int16_t result = ( (value >> 20) & 0xfff) << 4;
  result = result >> 4;
  return result;
}

inline uint8_t ExtractOpcode(uint32_t value) {
  return value & 0x7f;
}

inline uint8_t ExtractRd(uint32_t value) {
  return  (value >> 7) & 0x1f;
}

inline uint8_t ExtractRs1(uint32_t value) {
  return  (value >> 15) & 0x1f;
}

}  // namespace i_type

Por último, la declaración de función de la función de decodificador para la instrucción Se declaró el grupo RiscVInst32. Toma 32 bits sin signo como valor de la palabra de la instrucción y muestra el miembro de la clase de enumeración OpcodeEnum que coincida, o OpcodeEnum::kNone si no hay ninguna coincidencia.

OpcodeEnum DecodeRiscVInst32(uint32_t inst_word);

El archivo fuente generado (.cc)

Ahora abre riscv32i_bin_decoder.cc. La primera parte del archivo contiene las declaraciones #include y de espacio de nombres, seguidas de la función de decodificador declaraciones:

#include "riscv32i_bin_decoder.h"

namespace mpact {
namespace sim {
namespace codelab {

OpcodeEnum DecodeRiscVInst32None(uint32_t);
OpcodeEnum DecodeRiscVInst32_0(uint32_t inst_word);
OpcodeEnum DecodeRiscVInst32_0_3(uint32_t inst_word);
OpcodeEnum DecodeRiscVInst32_0_3c(uint32_t inst_word);
OpcodeEnum DecodeRiscVInst32_0_5c(uint32_t inst_word);

DecodeRiscVInst32None se usa para acciones de decodificación vacías, es decir, las que que devuelven OpcodeEnum::kNone. Las otras tres funciones conforman la decodificador generado. El decodificador general funciona de manera jerárquica. Un conjunto de bits en la palabra de instrucción se calcula para diferenciar entre instrucciones o grupos de instrucciones en el nivel superior. Los bits no necesitan ser contiguas. El número de bits determina el tamaño de una tabla de consulta que se completa con funciones de decodificador de segundo nivel. Esto se ve en el próximo del archivo:

absl::AnyInvocable<OpcodeEnum(uint32_t)> parse_group_RiscVInst32_0[kParseGroupRiscVInst32_0_Size] = {
    &DecodeRiscVInst32None, &DecodeRiscVInst32None,
    &DecodeRiscVInst32None, &DecodeRiscVInst32_0_3,
    &DecodeRiscVInst32None, &DecodeRiscVInst32None,

    ...

    &DecodeRiscVInst32None, &DecodeRiscVInst32None,
    &DecodeRiscVInst32None, &DecodeRiscVInst32None,
    &DecodeRiscVInst32_0_3c, &DecodeRiscVInst32None,

    ...
};

Por último, se definen las funciones del decodificador:

OpcodeEnum DecodeRiscVInst32None(uint32_t) {
  return OpcodeEnum::kNone;
}

OpcodeEnum DecodeRiscVInst32_0(uint32_t inst_word) {
  if ((inst_word & 0x4003) != 0x3) return OpcodeEnum::kNone;
  uint32_t index;
  index = (inst_word >> 2) & 0x1f;
  index |= (inst_word >> 7) & 0x60;
  return parse_group_RiscVInst32_0[index](inst_word);
}

OpcodeEnum DecodeRiscVInst32_0_3(uint32_t inst_word) {
  return OpcodeEnum::kFence;
}

OpcodeEnum DecodeRiscVInst32_0_3c(uint32_t inst_word) {
  if ((inst_word & 0xf80) != 0x0) return OpcodeEnum::kNone;
  return OpcodeEnum::kCsrwNr;
}

OpcodeEnum DecodeRiscVInst32_0_5c(uint32_t inst_word) {
  uint32_t rs1_value = (inst_word >> 15) & 0x1f;
  if (rs1_value != 0x0)
    return OpcodeEnum::kCsrs;
  if (rs1_value == 0x0)
    return OpcodeEnum::kCsrsNw;
  return OpcodeEnum::kNone;
}

OpcodeEnum DecodeRiscVInst32(uint32_t inst_word) {
  OpcodeEnum opcode;
  opcode = DecodeRiscVInst32_0(inst_word);
  return opcode;
}

En este caso, donde solo hay 4 instrucciones definidas, solo hay un con un solo nivel de decodificación y una tabla de consulta muy dispersa. Como las instrucciones se agrega, la estructura del decodificador cambiará y la cantidad de niveles en la la jerarquía de la tabla del decodificador puede aumentar.


Agrega instrucciones de registro y registro para ALU

Ahora es momento de agregar algunas instrucciones nuevas al archivo riscv32i.bin_fmt. El primer grupo de instrucciones son instrucciones de registro-registro de ALU, como add, and, etc. En RiscV32, todos usan la instrucción binaria de tipo R. formato:

31/25 24/20 19/15 14-12 11 a 7 6...
7 5 5 3 5 7
func7 rs2 rs1 func3 .o código de operación

Lo primero que debemos hacer es agregar el formato. Adelante riscv32i.bin_fmt en tu editor favorito. Justo después de Inst32Format, Agrega un formato llamado RType, que deriva de Inst32Format. Todos los campos de bits en RType son unsigned. Usa los nombres, el ancho de bits y el orden (de izquierda a derecha) de la tabla anterior para definir el formato. Si necesitas una pista o quieres ver la solución completa, haz clic aquí.

A continuación, debemos agregar las instrucciones. Las instrucciones son las siguientes:

  • add: Suma de número entero.
  • and: a nivel de bits y.
  • or: O a nivel de bits.
  • sll: Desplazamiento lógico hacia la izquierda.
  • sltu: establecido menor que, sin firma.
  • sub: resta de números enteros.
  • xor: Xor a nivel de bits.

Sus codificaciones son las siguientes:

31/25 24/20 19/15 14-12 11 a 7 6... Nombre del código de operación
000 0000 rs2 rs1 000 .o 011 0011 add
000 0000 rs2 rs1 111 .o 011 0011 y
000 0000 rs2 rs1 110 .o 011 0011 o
000 0000 rs2 rs1 001 .o 011 0011 SLL
000 0000 rs2 rs1 011 .o 011 0011 SLTU
010 0000 rs2 rs1 000 .o 011 0011 sub
000 0000 rs2 rs1 100 .o 011 0011 xor
func7 func3 código de operación

Agrega estas definiciones de instrucciones antes de las otras instrucciones en la RiscVInst32 grupo de instrucciones. Las cadenas binarias se especifican el prefijo 0b (similar a 0x para los números hexadecimales). Para que sea más fácil lee cadenas largas de dígitos binarios, también puedes insertar la comilla simple ' como un separador de dígitos cuando lo creas conveniente.

Cada una de estas definiciones de instrucciones tendrá tres restricciones: func7, func3 y opcode. Para todos los elementos, excepto sub, la restricción func7 ser:

func7 == 0b000'0000

La restricción func3 varía en la mayoría de las instrucciones. Para add y sub es:

func3 == 0b000

La restricción opcode es la misma para cada una de estas instrucciones:

opcode == 0b011'0011

Recuerda terminar cada línea con un punto y coma ;.

La solución terminada es aquí.

Compila tu proyecto como antes y abre la biblioteca riscv32i_bin_decoder.cc. Verás que las funciones adicionales del decodificador para manejar las nuevas instrucciones. En su mayoría, son similares a las que se generaron antes, pero observa DecodeRiscVInst32_0_c, que se usa para la decodificación add/sub:

OpcodeEnum DecodeRiscVInst32_0_c(uint32_t inst_word) {
  static constexpr OpcodeEnum opcodes[2] = {
    OpcodeEnum::kAdd,
    OpcodeEnum::kSub,
  };
  if ((inst_word & 0xbe000000) != 0x0) return OpcodeEnum::kNone;
  uint32_t index;
  index = (inst_word >> 30) & 0x1;
  return opcodes[index];
}

En esta función, se genera una tabla de decodificación estática y se extraída de la palabra de instrucción para seleccionar el índice apropiado. Esto agrega un la segunda capa en la jerarquía del decodificador de instrucciones, pero como el código de operación puede directamente en una tabla sin más comparaciones, se intercala en este en lugar de requerir otra llamada a función.


Agrega instrucciones de ALU con inmediatos

El siguiente conjunto de instrucciones que agregaremos son instrucciones de ALU que usan un un valor inmediato en lugar de uno de los registros. Existen tres grupos de estos instrucciones (basadas en el campo inmediato): las instrucciones inmediatas de I-Type con una firma de 12 bits inmediata, las instrucciones especializadas inmediatas de tipo I para los cambios, y el tipo U inmediato, con un valor inmediato de 20 bits sin firma. Los formatos se muestran a continuación:

El formato inmediato I-Type:

31/20 19/15 14-12 11 a 7 6...
12 5 3 5 7
imm12 rs1 func3 .o código de operación

El formato inmediato especializado I-Type:

31/25 24/20 19/15 14-12 11 a 7 6...
7 5 5 3 5 7
func7 uimm5 rs1 func3 .o código de operación

El formato inmediato tipo U:

31-12 11 a 7 6...
20 5 7
uimm20 .o código de operación

El formato I-Type ya existe en riscv32i.bin_fmt, por lo que no es necesario agregar ese formato.

Si comparamos el formato especializado tipo I con el formato tipo R que definimos en En el ejercicio anterior, vemos que la única diferencia es que los campos rs2 se cambia el nombre por uimm5. En lugar de agregar un formato completamente nuevo, podemos aumentar R-Type. No podemos agregar otro campo, ya que eso aumentaría el ancho de el formato, pero podemos agregar una superposición. Una superposición es un alias para un conjunto de bits en el formato y se pueden usar para combinar múltiples subsecuencias de la en una entidad con nombre independiente. El efecto secundario es que el código generado ahora también incluirá una función de extracción para la superposición, los de los campos. En este caso, cuando rs2 y uimm5 no tienen firma. no hace mucha diferencia, excepto aclarar que el campo se usa como inmediato. Para agregar una superposición llamada uimm5 al formato R-Type, agrega la lo siguiente después del último campo:

  overlays:
    unsigned uimm5[5] = rs2;

El único formato nuevo que debemos agregar es el formato U-Type. Antes de agregar el consideremos las dos instrucciones que usan ese formato: auipc y lui Ambos desplazan el valor inmediato de 20 bits a la izquierda 12 antes de usarlos. para agregarle la PC (auipc) o escribirla directamente en un registro (lui) Al usar una superposición, podemos proporcionar una versión previamente desplazada de los objetos hacer un cambio un poco de procesamiento de la ejecución de instrucciones a la instrucción la decodificación y la decodificación. Primero, agrega el formato según los campos especificados en la tabla. arriba. Luego, podemos agregar la siguiente superposición:

  overlays:
    unsigned uimm32[32] = uimm20, 0b0000'0000'0000;

La sintaxis de superposición nos permite concatenar no solo campos, sino también literales como en la nube. En este caso, la concatenamos con 12 ceros y, de hecho, la desplazamos hacia la izquierda. 12.

Las instrucciones de I-Type que debemos agregar son:

  • addi: Se agrega inmediatamente.
  • andi: bit a bit y con inmediato.
  • ori: bit a bit o con inmediato.
  • xori: Es un xor bit a bit con inmediato.

Sus codificaciones son las siguientes:

31/20 19/15 14-12 11 a 7 6... opcode_name
imm12 rs1 000 .o 001 0011 Addi
imm12 rs1 111 .o 001 0011 andi
imm12 rs1 110 .o 001 0011 Orí
imm12 rs1 100 .o 001 0011 Xori
func3 código de operación

Las instrucciones de R-Type (tipo I especializada) que debemos agregar son las siguientes:

  • slli: Desplaza a la izquierda un lógico de inmediato.
  • srai: Desplaza la aritmética hacia la derecha de manera inmediata.
  • srli: Desplaza hacia la derecha de manera lógica.

Sus codificaciones son las siguientes:

31/25 24/20 19/15 14-12 11 a 7 6... Nombre del código de operación
000 0000 uimm5 rs1 001 .o 001 0011 SLLI
010 0000 uimm5 rs1 101 .o 001 0011 serraí
000 0000 uimm5 rs1 101 .o 001 0011 Srli
func7 func3 código de operación

Las instrucciones sobre el tipo de U que debemos agregar son las siguientes:

  • auipc: Agrega el valor inmediato superior al CPC.
  • lui: Carga la parte superior inmediatamente.

Sus codificaciones son las siguientes:

31-12 11 a 7 6... Nombre del código de operación
uimm20 .o 001 0111 auipc
uimm20 .o 011 0111 lui
código de operación

Realiza los cambios y, luego, compila. Verifica el resultado generado. Justo como antes, puedes comparar tu trabajo con riscv32i.bin_fmt


El siguiente conjunto de instrucciones que deben definirse es la rama condicional la instrucción de salto y enlace, y el registro de salto y enlace instrucciones.

Todas las ramas condicionales que agregamos usan la codificación tipo B.

31/25 24/20 19/15 14-12 11 a 7 6...
7 5 5 3 5 7
imm7 rs2 rs1 func3 imm5 código de operación

Si bien el diseño de la codificación del tipo B es idéntico a la codificación del tipo R, elegir un nuevo tipo de formato para alinearlo con la documentación de RiscV. Pero también podrías haber agregado una superposición para obtener la rama adecuada Desplazamiento inmediato hacia afuera, usando los campos func7 y rd del tipo R o la codificación.

Es necesario agregar un formato BType con los campos especificados anteriormente, pero no es necesario suficientes. Como puedes ver, el inmediato se divide en dos campos de instrucción. Además, las instrucciones de la rama no tratan esto como una simple concatenación de ambos campos. En cambio, cada campo se particiona, y estas particiones se concatenan en un orden diferente. Finalmente, ese valor se desplaza hacia la izquierda uno para obtener un desplazamiento alineado de 16 bits.

La secuencia de bits en la palabra de instrucción utilizada para formar el inmediato es: 31, 7, 30-25, 11-8. Esto corresponde a las siguientes referencias de subcampos, en las que el índice o el rango especifican los bits en el campo, numerados de derecha a izquierda, es decir, imm7[6] se refiere al msb de imm7 y imm5[0] se refiere al lsb de imm5

imm7[6], imm5[0], imm7[5..0], imm5[4..1]

Hacer que esta manipulación de bits sea parte de las instrucciones de la rama tienen dos grandes desventajas. Primero, vincula la implementación de la función semántica a en la representación de instrucciones binarias. Segundo, agrega más tiempo de ejecución la sobrecarga. La respuesta es agregar una superposición al formato BType, incluida una "0" al final para dar cuenta del desplazamiento a la izquierda.

  overlays:
    signed b_imm[13] = imm7[6], imm5[0], imm7[5..0], imm5[4..1], 0b0;

Observa que la superposición está firmada, por lo que se firmará automáticamente cuando se extrae de la palabra de instrucción.

La instrucción de salto y vínculo (inmediato) usa la codificación tipo J:

31-12 11 a 7 6...
20 5 7
imm20 .o código de operación

Este también es un formato fácil de agregar, pero nuevamente, la inmediata la instrucción no es tan sencilla como parece. Las secuencias de bits que se usan para del inmediato completo son: 31, 19..12, 20, 30..21, y el inmediato final es desplazada hacia la izquierda en uno para la alineación de media palabra. La solución es agregar otro superponer (21 bits para dar cuenta del desplazamiento a la izquierda) al formato:

  overlays:
    signed j_imm[21] = imm20[19, 7..0, 8, 18..9], 0b0;

Como puedes ver, la sintaxis para superposiciones admite la especificación de varios rangos en una en un formato abreviado. Además, si no se usa ningún nombre de campo, el bit números se refieren a la palabra de la instrucción en sí, por lo que lo anterior también podría escrito como:

    signed j_imm[21] = [31, 19..12, 20, 30..21], 0b0;

Por último, el salto y enlace (registro) utiliza el formato tipo I, tal como se usa anteriormente.

El formato inmediato I-Type:

31/20 19/15 14-12 11 a 7 6...
12 5 3 5 7
imm12 rs1 func3 .o código de operación

Esta vez, no es necesario realizar ningún cambio en el formato.

Las instrucciones de la rama que debemos agregar son las siguientes:

  • beq: Rama si es igual.
  • bge: Rama si es mayor o igual que.
  • bgeu: Rama si es mayor o igual sin signo.
  • blt: Rama si es menor que.
  • bltu: Rama si es menor que sin firmar.
  • bne: Rama si no es igual.

Se codifican de la siguiente manera:

31/25 24/20 19/15 14-12 11 a 7 6... Nombre del código de operación
imm7 rs2 rs1 000 imm5 110 0011 solicitar
imm7 rs2 rs1 101 imm5 110 0011 aterrizaje
imm7 rs2 rs1 111 imm5 110 0011 bgeu
imm7 rs2 rs1 100 imm5 110 0011 blt
imm7 rs2 rs1 110 imm5 110 0011 bltu
imm7 rs2 rs1 001 imm5 110 0011 BN
func3 código de operación

La instrucción jal se codifica de la siguiente manera:

31-12 11 a 7 6... Nombre del código de operación
imm20 .o 110 1111 Jal
código de operación

La instrucción jalr se codifica de la siguiente manera:

31/20 19/15 14-12 11 a 7 6... opcode_name
imm12 rs1 000 .o 110 0111 jalr
func3 código de operación

Realiza los cambios y, luego, compila. Verifica el resultado generado. Justo como antes, puedes comparar tu trabajo con riscv32i.bin_fmt


Agregar instrucciones de la tienda

Las instrucciones de la tienda utilizan la codificación tipo S, que es idéntica al tipo B. que usan las instrucciones de la rama, excepto por la composición de la inmediatos. Optamos por agregar el formato SType para mantener la alineación con la RiscV. en la documentación de Google Cloud.

31/25 24/20 19/15 14-12 11 a 7 6...
7 5 5 3 5 7
imm7 rs2 rs1 func3 imm5 código de operación

En el caso del formato SType, afortunadamente el inmediato es un recto directa la concatenación de los dos campos inmediatos, de modo que la especificación de superposición es simplemente:

  overlays:
    signed s_imm[12] = imm7, imm5;

Ten en cuenta que no se requieren especificadores de rango de bits cuando se concatenan campos enteros.

Las instrucciones de la tienda se codifican de la siguiente manera:

31/25 24/20 19/15 14-12 11 a 7 6... Nombre del código de operación
imm7 rs2 rs1 000 imm5 010 0011 sb
imm7 rs2 rs1 001 imm5 010 0011 sh
imm7 rs2 rs1 010 imm5 010 0011 sw
func3 código de operación

Realiza los cambios y, luego, compila. Verifica el resultado generado. Justo como antes, puedes comparar tu trabajo con riscv32i.bin_fmt


Agregar instrucciones de carga

Las instrucciones de carga usan el formato I-Type. No es necesario realizar ningún cambio.

La codificación es la siguiente:

31/20 19/15 14-12 11 a 7 6... opcode_name
imm12 rs1 000 .o 000 0011 lb
imm12 rs1 100 .o 000 0011 LBU
imm12 rs1 001 .o 000 0011 talla
imm12 rs1 101 .o 000 0011 Lhu
imm12 rs1 010 .o 000 0011 lw
func3 código de operación

Realiza los cambios y, luego, compila. Verifica el resultado generado. Justo como antes, puedes comparar tu trabajo con riscv32i.bin_fmt

Con esto concluye este instructivo, esperamos que te haya resultado útil.