emulsiV é um simulador virtual para um processador RISC simples, chamado Virgule.
Virgule é um processador de núcleo RISC de 32-bits que implementa um conjunto mínimo de instruções RISC-V. Aqui, “mínimo” significa que Virgule aceita somente as instruções que o compilador C deve gerar a partir de um programa "C stand-alone".
Virgule e emulsiV são usados para ensinar arquitetura e projeto de circuitos digitais para iniciante em ESEO. Antes de escolher uma arquitetura de processador, nos tivemos os seguintes requisitos em mente:
Dentre as arquiteturas candidatas, o RISC-V preencheu todos os requisitos:
Estas propriedades permite diversos cenários de ensino.
Em um curso de arquitetura de computadores, estudantes descobrem como um processador funciona, que tipo de linguagem e ferramentas podem ser usadas para criar um programa em baixo nível (Assembly, C). Um simulador é a melhor forma de visualizar como cada instrução afeta o caminho dos dados.
Em um curso de projeto de circuitos digitais, estudantes podem implementar um core de processador em VHDL ou Verilog, ou instânciar um para fazer seu próprio system-on-chip.
Virgule implementa todas as instruções computacionais, de transferência de controle, e acesso a memória do subconjunto de inteiros do RV32I da especificação RISC-V. Ele também fornece uma instrução de retorno de exceção que pode ser usada em funções de tratamento de interrupção.
As seguintes intruções não estão disponíveis:
A arquitetura RISC-V define três níveis de privilégios: user, supervisor, e machine. Virgule somente suporta o nível machine.
Virgule contem os seguintes registradores 32-bit:
x0
até x32
como especificado no conjunto base de instruções RV32E da especificação RISC-V.pc
. Este registrador conteim o endereço da instrução corrente.
Ao ser resetado este valor retorna para zero e sempre será multiplo de 4.mepc
. Este registrador
recebe o endereço de retorno quando uma exceção a nível de máquina ocorre.Formato do dado para instruções de carga e gravação seguem as seguintes convenções:
Nome | Tamanho do dados em (bits) | O enderço é múltiplo de |
---|---|---|
Byte (B ) | 8 | 1 |
Half word (H ) | 16 | 2 |
Word (W ) | 32 | 4 |
Em memória, 16-bit e 32-bit os dados seguem a ordenção little-endian.
Os dois endereços a seguir tem uma regra específica:
O Virgule implementa um hardware simples para o esquema de interrupção no modo previlegiado de máquina. Não controle de interrupção ou registrador de estatus internamente no core de processador.
Quando ele recebe uma requisição de interrupção, Virgule executa as seguintes operações:
mepc
para o endereço da próxima instrução.pc
para 4, que transferirá o controle para a função de tratamento de interrupção.Retornando de uma função de tratamento de interrupção é feito com a instrução mret
.
Esta intrução tem o seguinte efeito:
mepc
para pc
.Na seguinte tabela:
rd
é o registrador de proposito geral de destino.
rs1
e rs2
são os registradores de proposito geral para de dados.
imm
é um valor inteiro literal (immediate).
Instrução | Sintáxe | Operação |
---|---|---|
Load Upper Immediate | LUI rd, imm | rd ← imm |
Add Upper Immediate to PC | AUIPC rd, imm | rd ← pc + imm |
Jump And Link | JAL rd, imm | rd ← pc + 4; pc ← pc + imm |
Jump And Link Register | JALR rd, rs1, imm | rd ← pc + 4; pc ← rs1 + imm |
Branch if Equal | BEQ rs1, rs2, imm |
if rs1 = rs2: pc ← pc + imm else: pc ← pc + 4 |
Branch if Not Equal | BNE rs1, rs2, imm |
if rs1 ≠ rs2: pc ← pc + imm else: pc ← pc + 4 |
Branch if Less Than | BLT rs1, rs2, imm |
if signed(rs1) < signed(rs2): pc ← pc + imm else: pc ← pc + 4 |
Branch if Greater or Equal | BGE rs1, rs2, imm |
if signed(rs1) ≥ signed(rs2): pc ← pc + imm else: pc ← pc + 4 |
Branch if Less Than Unsigned | BLTU rs1, rs2, imm |
if unsigned(rs1) < unsigned(rs2): pc ← pc + imm else: pc ← pc + 4 |
Branch if Greater or Equal Unsigned | BGEU rs1, rs2, imm |
if unsigned(rs1) ≥ unsigned(rs2): pc ← pc + imm else: pc ← pc + 4 |
Load Byte | LB rd, imm(rs1) | rd ← signed(mem[rs1+imm]) |
Load Half word | LH rd, imm(rs1) | rd ← signed(mem[rs1+imm:rs1+imm+1]) |
Load Word | LW rd, imm(rs1) | rd ← signed(mem[rs1+imm:rs1+imm+3]) |
Load Byte Unsigned | LBU rd, imm(rs1) | rd ← unsigned(mem[rs1+imm]) |
Load Half word Unsigned | LHU rd, imm(rs1) | rd ← unsigned(mem[rs1+imm:rs1+imm+1]) |
Store Byte | SB rs2, imm(rs1) | mem[rs1+imm] ← rs2[7:0] |
Store Half word | SH rs2, imm(rs1) | mem[rs1+imm:rs1+imm+1] ← rs2[15:0] |
Store Word | SW rs2, imm(rs1) | mem[rs1+imm:rs1+imm+3] ← rs2 |
Add Immediate | ADDI rd, rs1, imm | rd ← rs1 + imm |
Shift Left Logical Immediate | SLLI rd, rs1, imm | rd ← rs1 sll imm |
Set on Less Than Immediate | SLTI rd, rs1, imm |
if signed(rs1) < signed(imm): rd ← 1 else: rd ← 0 |
Set on Less Than Immediate Unsigned | SLTIU rd, rs1, imm |
if unsigned(rs1) < unsigned(imm): rd ← 1 else: rd ← 0 |
Exclusive Or Immediate | XORI rd, rs1, imm | rd ← rs1 xor imm |
Shift Right Logical Immediate | SRLI rd, rs1, imm | rd ← rs1 srl imm |
Shift Right Arithmetic Immediate | SRAI rd, rs1, imm | rd ← rs1 sra imm |
Or Immediate | ORI rd, rs1, imm | rd ← rs1 or imm |
And Immediate | ANDI rd, rs1, imm | rd ← rs1 and imm |
Add | ADD rd, rs1, rs2 | rd ← rs1 + rs2 |
Subtract | SUB rd, rs1, rs2 | rd ← rs1 - rs2 |
Shift Left Logical | SLL rd, rs1, rs2 | rd ← rs1 sll rs2 |
Set on Less Than | SLT rd, rs1, rs2 |
if signed(rs1) < signed(rs2): rd ← 1 else: rd ← 0 |
Set on Less Than Unsigned | SLTU rd, rs1, rs2 |
if unsigned(rs1) < unsigned(rs2): rd ← 1 else: rd ← 0 |
Exclusive Or | XOR rd, rs1, rs2 | rd ← rs1 xor rs2 |
Shift Right Logical | SRL rd, rs1, rs2 | rd ← rs1 srl rs2[4:0] |
Shift Right Arithmetic | SRA rd, rs1, rs2 | rd ← rs1 sra rs2[4:0] |
Or | OR rd, rs1, rs2 | rd ← rs1 or rs2 |
And | AND rd, rs1, rs2 | rd ← rs1 and rs2 |
Machine Return | MRET | pc ← mepc |
Na tabela acima, operações lógicas e deslocamento tem o seguinte significado:
Operador | Efeito |
---|---|
and | Bitwise and |
or | Bitwise or |
xor | Bitwise exclusive or |
sll | Logical shift left |
srl | Logical shift right |
sra | Arithmetic shift right (com extenção de sinal) |
Uma palavra (word - 4 bytes) de instrução pode ser composta pelos seguintes campos:
funct7
, funct3
e opcode
definem a operação a ser executada;
rd
é o indice do registrador de propósito geral de destino (valores permitidos na faixa de 0 a 15).
rs1
e rs2
são indices dos registradores de propósito geral como fontes de dados(valores permitidos na faixa de 0 a 15);
imm
representa um valor inteiro literal (immediate);
O conjunto de instruções RISC-V define seis formatos de instrução:
Formato / Bits | 31:25 | 24:20 | 19:15 | 14:12 | 11:7 | 6:0 |
---|---|---|---|---|---|---|
R | funct7 | rs2 | rs1 | funct3 | rd | opcode |
I | imm[11:5]`/`funct7 | imm[4:0] | rs1 | funct3 | rd | opcode |
S | imm[11:5] | rs2 | rs1 | funct3 | imm[4:0] | opcode |
B | imm[12,10:5] | rs2 | rs1 | funct3 | imm[4:1,11] | opcode |
U | imm[31:25] | imm[24:20] | imm[19:15] | imm[14:12] | rd | opcode |
J | imm[20,10:5] | imm[4:1,11] | imm[19:15] | imm[14:12] | rd | opcode |
Valores Immediate são extendidos no sinal (sign-extended) para 32 bits.
quando eles não são explicitamente codificados no campo imm
, o bit menos significante é 0.
Na especificação, formatos B
e J
são descritos como variantes dos formatos S
e U
.
Nos formatos B
e J
, valores immediate representam offsets de instruções
ramificações do código relativas.
Elas são codificadas para que eles compartilhem o máximo de bits com outros formatos,
enqaunto preservam seus bits mais significantes na localização 31 da palavra (word - 4 bytes) de instrução.
A Seguinte tabela mostra o mapeamento entre os bits de uma palavra (word - 4 bytes) de instrução e os bits de um valor imediato:
Formato | imm[31:25] | imm[24:21] | imm[20] | imm[19:15] | imm[14:12] | imm[11] | imm[10:5] | imm[4:1] | imm[0] |
---|---|---|---|---|---|---|---|---|---|
I | inst[31] | inst[31] | inst[31] | inst[31] | inst[31] | inst[31] | inst[30:25] | inst[24:21] | inst[20] |
S | inst[31] | inst[31] | inst[31] | inst[31] | inst[31] | inst[31] | inst[30:25] | inst[11:8] | inst[7] |
B | inst[31] | inst[31] | inst[31] | inst[31] | inst[31] | inst[7] | inst[30:25] | inst[11:8] | 0 |
U | inst[31:25] | inst[24:21] | inst[20] | inst[19:15] | inst[14:12] | 0 | 0 | 0 | 0 |
J | inst[31] | inst[31] | inst[31] | inst[19:15] | inst[14:12] | inst[20] | inst[30:25] | inst[24:21] | 0 |
No Virgule, nos tempos retido o seguinte opcotes de base da especificação RISC-V. cara opcode corresponde a um formato de instrução específico:
Nome | opcode | Formato |
---|---|---|
LOAD | 0000011 | I |
OP-IMM | 0010011 | I |
AUIPC | 0010111 | U |
STORE | 0100011 | S |
OP | 0110011 | R |
LUI | 0110111 | U |
BRANCH | 1100011 | B |
JALR | 1100111 | I |
JAL | 1101111 | J |
SYSTEM | 1110011 | I |
Quando decodificando uma palavra de instruão, Virgule usa os seguintes campos para identificar a instrução atual.
Nesta tabela, a coluna opcode
refere-se aos nomes de base do opcode na tabela acima.
Instrução | opcode | funct3 | funct7 | rs2 |
---|---|---|---|---|
LUI | LUI | — | — | — |
AUIPC | AUIPC | — | — | — |
JAL | JAL | — | — | — |
JALR | JALR | 000 | — | — |
BEQ | BRANCH | 000 | — | — |
BNE | BRANCH | 001 | — | — |
BLT | BRANCH | 100 | — | — |
BGE | BRANCH | 101 | — | — |
BLTU | BRANCH | 110 | — | — |
BGEU | BRANCH | 111 | — | — |
LB | LOAD | 000 | — | — |
LH | LOAD | 001 | — | — |
LW | LOAD | 010 | — | — |
LBU | LOAD | 100 | — | — |
LHU | LOAD | 101 | — | — |
SB | STORE | 000 | — | — |
SH | STORE | 001 | — | — |
SW | STORE | 010 | — | — |
ADDI | OP-IMM | 000 | — | — |
SLLI | OP-IMM | 001 | 0000000 | — |
SLTI | OP-IMM | 010 | — | — |
SLTIU | OP-IMM | 011 | — | — |
XORI | OP-IMM | 100 | — | — |
SRLI | OP-IMM | 101 | 0000000 | — |
SRAI | OP-IMM | 101 | 0100000 | — |
ORI | OP-IMM | 110 | — | — |
ANDI | OP-IMM | 111 | — | — |
ADD | OP | 000 | 0000000 | — |
SUB | OP | 000 | 0100000 | — |
SLL | OP | 001 | 0000000 | — |
SLT | OP | 010 | 0000000 | — |
SLTU | OP | 011 | 0000000 | — |
XOR | OP | 100 | 0000000 | — |
SRL | OP | 101 | 0000000 | — |
SRA | OP | 101 | 0100000 | — |
OR | OP | 110 | 0000000 | — |
AND | OP | 111 | 0000000 | — |
MRET | SYSTEM | 000 | 0011000 | 00010 |
O espaço de endereçamento é organizado como segue.
Endereço (hex) | Dispositivos |
---|---|
00000000 ⋮ 00000bff | RAM (3072 bytes) |
00000C00 ⋮ 00000fff | Bitmap RAM (1024 bytes) |
B0000000 B0000001 | Entrada de Texto |
C0000000 | Saída de Texto |
D0000000 | Entrada e Saída de propósito geral |
O dispositivo de entrada de texto é representado por um campo de texto na interface de usuário do simulador. Ele tem dois registradores de 8-bit:
Endereço (hex) | Regra | Valor |
---|---|---|
B0000000 | Controle/Estatus | Bit 7: Hablita Interrução Bit 6: Character recebido |
B0000001 | Data | O código ASCII do ultimo caracter inserido. |
O registrador de controle/Estatus trabalha da seguinte forma:
O dispositivo de saída de texto é representado por uma área de texto na interface de usuário do simulador. Ele tem somente um registrador somente escrita:
Endereço (hex) | Papel | Valor |
---|---|---|
C0000000 | Data | O código ASCII do caracter para exibir. |
O GPIO (Entrada e Saída de propósito geral) de periféricos permite conectar pelo menos 32 dispostivos simples de entrada e saída:
As entradas são organizadas em um grid de 8×4 na parte inferior da seção E/S de propósito geral do simulador. Clique com o botão direito do mouse na célula para trocar o seu tipo. Clique com o botão esquerdo do mouse na célula alterar o estado do botão ou da chave.
Ele tem os seguintes registradores de 32-bits:
Endereço (hex) | Papel | Valor |
---|---|---|
D0000000 | Direção (dir) | A configuração de cada pino (0 para saída, 1 para entrada). |
D0000004 | Interrupção habilitada (ien) | Habilita a interrupção dos eventos de entrada. |
D0000008 | Eventos subida de borda (rev) | Cada bit é definido como 1 se a entrada correspondente alterar o pino de 0 para 1. |
D000000C | Eventos decida de borda (fev) | Cada bit é definido como 1 se a entrada correspondente alterar o pino de 1 para 0. |
D0000010 | Valor (val) | O valor atual de cada entrada ou saída. |
Durante o reset, todos os pinos são configurados como entradas e interrupções são desabilitadas.
Eventos de subida de borda ou descida de borda devem ser limpos por software usando instruções de store quando o evento foi processados.
O simulador fornece uma área de "display" gráfico com 32 linhas com 32 pixels cada. Cada pixel é mapeado para um bate da RAM e sua cor é codificada da seguinte forma:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
Vermelho (Red) | Verde (Green) | Azul (Blue) |
O bitmap na RAM segue uma ordem "raster-scan". Para cada endereço, esta tabela mostra coordenadas (x, y) do pixel correspondente:
+0 | +1 | +2 | … | +1F | |
---|---|---|---|---|---|
00000C00 | (0, 0) | (1, 0) | (2, 0) | … | (31, 0) |
00000C20 | (0, 1) | (1, 1) | (2, 1) | … | (31, 1) |
00000C40 | (0, 2) | (1, 2) | (2, 2) | … | (31, 2) |
⋮ | ⋮ | ⋮ | ⋮ | … | ⋮ |
00000FE0 | (0, 31) | (1, 31) | (2, 31) | … | (31, 31) |
O simulador permite criar e editar programas pela entrada de instruções na columna "Assembly" do visualizador de memória. Outra opçlão é digitar seu programa no editor de texto e gerar um executável para o emulsiV usando as ferramentas GNU.
Você pode usar as seguintes intruções para instalar as ferramentas GNU para RISC-V no ambiente Linux Debian ou no Ubuntu. O projeto riscv-gnu-toolchain fornece informações adicionais se você precisa instalar o toolchain em outra distribuição, ou se você precisa recursos que não estão tratados aqui.
Para o processo de "build" serão necessários os seguintes pacotes:
sudo apt install autoconf automake autotools-dev curl libmpc-dev zlib1g-dev \ libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf \ libtool patchutils bc zlib1g-dev libexpat-dev git-core
Clone o repositório fonte da ferramenta RISC-V GNU:
git clone --recursive https://github.com/riscv/riscv-gnu-toolchain
Configure e faça o "build" das ferramentas com suporte para o target RV32E.
O comando make
irá tentar instalar a ferramenta em /opt/riscv
.
Você pode alterar a opção --prefix
se você deseja instalar em outro lugar.
Execute o comando sudo make
se você não tem permissão para criar o folder destino.
cd riscv-gnu-toolchain ./configure --prefix=/opt/riscv --with-arch=rv32e --with-abi=ilp32 make
Antes de usar as ferramentas, atualize a variável PATH
da seguinte forma:
export PATH=$PATH:/opt/riscv/bin
Este comando compila o arquivo fonte example.s
gerando um arquivo de objetos example.o
:
riscv32-unknown-elf-gcc -march=rv32e -c -o example.o example.s
Aqui está um módulo típico de inicialização (startup.s
) que você pode usar para seu programa.
Outros arquivos fontes "assembly" ou "C" devem definir o subprograma main
,
e opcionalmente sobreescrever o subprograma irq_handler
.
.section vectors, "x" .global __reset __reset: j start __irq: j irq_handler .text .align 4 .weak irq_handler irq_handler: mret start: la gp, __global_pointer la sp, __stack_pointer la t0, __bss_start la t1, __bss_end bgeu t0, t1, memclr_done memclr: sw zero, (t0) addi t0, t0, 4 bltu t0, t1, memclr memclr_done: call main j .
Este comando compila o arquivo fonte example.c
em um arquivo objeto example.o
:
riscv32-unknown-elf-gcc -march=rv32e -ffreestanding -c -o example.o example.c
Se você deseja escrever uma função para tratar uma interrupção em C, você pode sobrescrever o subprograma irq_handler
,
adicionando o atributo interrupt
como a seguir:
__attribute__((interrupt("machine"))) void irq_handler(void) { // Insert your code here. }
Este comando liga startup.o
e example.o
em um arquivo executável example.elf
:
riscv32-unknown-elf-gcc -nostdlib -T emulsiv.ld -o example.elf startup.o example.o
O mapa de memória do do simulador é configurado no "linker script" emulsiv.ld
apresentado abaixo:
ENTRY(__reset) MEM_SIZE = 4K; STACK_SIZE = 512; BITMAP_SIZE = 1K; SECTIONS { . = 0x0; .text : { *(vectors) *(.text) __text_end = .; } .data : { *(.data) } .rodata : { *(.rodata) } __global_pointer = ALIGN(4); .bss ALIGN(4) : { __bss_start = .; *(.bss COMMON) __bss_end = ALIGN(4); } . = MEM_SIZE - STACK_SIZE - BITMAP_SIZE; .stack ALIGN(4) : { __stack_start = .; . += STACK_SIZE; __stack_pointer = .; } .bitmap ALIGN(4) : { __bitmap_start = .; *(bitmap) } __bitmap_end = __bitmap_start + BITMAP_SIZE; }
Este comando converte um arquivo binário do tipo ELF example.elf
em um arquivo texto example.hex
com o formato Intel Hex:
riscv32-unknown-elf-objcopy -O ihex example.elf example.hex
No simulador, use o botão "Open an hex file from your computer" e escolha arquivo hex para ser carregado.
emulsiV é um software livre e é distribuido sobre os termos da licença Mozilla Public 2.0.
Este documento foi criado por Guillaume Savaton, ESEO. E é licenciado sobre os termos da licença Creative Commons Attribution-ShareAlike 4.0 International License.