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 nombresi_type
. Cada función de extractor se declarainline
y toma lauint_t
más estrecha tipo que contiene el ancho del formato y muestra elint_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 deuint_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
Agrega instrucciones de rama y de salto y enlace
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.