Mudancas Recentes - Buscar:

Principal

 Objetivos  
 Ementa  
 Livros 

OAC

LOAC

Professores:

  JOSEANA
  ELMAR

CONTATO

CRÉDITOS

editar



Nesta página, veremos como programar em C, em Assembler e por fim em código de máquina mesmo.

Primeiro iremos programar o processador Intel do seu PC, depois o processador RISC-V do Labarc.

Para simular em casa siga estas instruções.

Programar em C

Aqui usamos a linguagem C para desenvolver software. Mostramos várias formas como se pode testar o software e como compilar ele para linguagem assembly para finalmente carregá-lo na forma de código de máquina no nosso processador RISC-V que está implementado na placa de FPGA.

Dica para quem não sabe usar Linux: selecione arrastando o botão esquerdo do mouse, cole com botão do meio (sim, mouse tem botão no meio, é o da rodinha!).

Exemplo de código C (usa dois arquivos)
arquivo lupi.c:

int lupi(register int x, register int y) {
   register int s=0;
   do {
      s = s+x+y;
      x = x-y;
   } while (x>0);
   return s;
}

arquivo lupi.h:

int lupi(register int x, register int y);

Este código não tem o objetivo de implementar algum algorítmo específico, trata-se somente de um exemplo mesmo.

Executar em PC com processador Intel e Sistema Operacional Linux

Queremos primeiro testar o nosso código C em um PC.

Compilador para Arquitetura Intel

Compilação para assembly do processador Intel para gerar o arquivo lupi.s em assembly x86_64:
gcc -O1 -S lupi.c

A opção -O1 solicita otimizações básicas no código assembly.

Visualize o arquivo gerado:
cat lupi.s

Para poder receber valores de entrada de uma linha de comando em Linux e poder devolver o resultado para a linha de comando é preciso uma casca ou envelope (wrapper) - arquivo lupi-main-pc.c :

#include "lupi.h"
int main(int argc,char *argv[]) {
   register int x,y;
   x = argv[1][0]-'0';       // primeira letra do primeiro argumento convertida para valor entre 0 e 9
   y = argv[2][0]-'0';       // primeira letra do segundo argumento
   return lupi(x,y);
}

Para deixar o código mais simples, só são possíveis valores de entrada de 0 a 9.

O comando para compilar a casca é
gcc -O1 -S lupi-main-pc.c

Assembler Intel

Chamamos o assembler para processador Intel, fornecendo os códigos assembly da casca e da subrotina lupi, para que seja gerado código de máquina:
gcc lupi-main-pc.s lupi.s -o lupi-pc

O arquivo executável lupi-pc contém agora todo o código de máquina para processador Intel, do main e do lupi.

Execução

Testando no PC:
./lupi-pc 7 2; echo $?

Simular no simulador ISA RISC-V

Agora queremos usar um simulador de ISA (Instruction Set Arquitecture) do RISC-V. Este simulador roda em PC e se chama spike. No diretório do simulador de Ícaro encontra-se um Makfile e uma GUI que facilitam este exercício.

Antes removemos os arquivos em assembly para Intel:
 rm *.s 
Os argumentos de linha de comando precisam ser passados dentra da variável ARGS:
 make isa ARGS="7 2"

Dê Enter para avançar a simulação passo-a-passo.

Observe os argumentos nos registradores a0 e a1 no momento da chamada de lupi.

Observe o resultado sendo formado no registrador a0.

Simular em simulador de placa FPGA

Agora usamos software bare-metal, ou seja, sem sistema operacional e sem argumentos de linha de comando, para rodar-lo num processador RISC-V construído aqui no Labarc, em Systemverilog, ou seja, usando always_comb e always_ff.

Para isso, a casca precisa ser diferente para usar a unidade de Entrada/Saída da nossa CPU - arquivo lupi-main-fpga.c :

#include "lupi.h"
void __attribute__ ((naked)) main() { // naked significa desconsiderar a pilha, nao precisa salvar nada na pilha
   volatile int * const io = (int *)(0x3F*4); // apontador para entrada/saida
   // o apontador aponta para algo que é volátil, ou seja, algo que pode mudar fora do controle do software
                  // o valor do apontador é constante, ou seja, o endereço não pode mudar
   int x,y;
   x = *io;                       // primeiro pega x de SWI[4:0]
   y = *io;                       // no clock seguinte y
   *io = lupi(x,y);               // resultado vai para saida - LED[4:0]
}

apague o arquivo lupi-main-pc.c e o arquivo a.out e dê o comando:
make inst.objdump

Verifique que você esteja com o arquivo executável RISCV_sim conforme instruções aqui e dê o comando
./RISCV_sim

Este último comando carrega automaticamente o arquivo inst.objdump.

Use a SWI[7] para travar o clock e a SWI[6] para resetar o processador.

Com clock ativado e ainda com reset ativado, você precisa fornecer o primeiro argumento (nos exemplos acima usamos o valor 7 como primeiro argumento) em SWI[3:0].

Agora você pode desativar o reset, até que o primeiro argumento esteja carregado no registrador a0 e o processador estiver na instrução seguinte (pc=8), aí você deve travar o clock para colocar o segundo argumento (nos exemplos acima usamos o valor 2 como segundo argumento) em SWI[3:0].

Ative novamente o clock e verifique que a0 contem 7 e a1 contém 2 para poder seguir em frente.

Rodar em processador RISC-V físico

Crie um .zip com os arquivos lupi-main-fpga.c, lupi.c, e lupi.h e carregue no simulador remoto.

Programar um processador RISC-V em Assembly

Aqui iniciamos escrevendo o software diretamente em linguagem de Assembly.

Primeiro dê o comando
 make isa-clean 

para apagar os arquivos C.

Deve-se criar um arquivo contendo as instruções em assembly para serem geradas as instruções binárias, deve ser uma instrução por linha. O arquivo deve ter a extensão .s. Ex.: inst.s
O arquivo deve estar no mesmo diretório onde está o projeto e a descrição Verilog da memória de instruções.

- Um par de /* e */ é usado para iniciar um comentário na linha, como em C ou Java

- Diretivas de assembly:

.section .text /* indica que agora vem instruções RISC-V */
.globl main /* declara o label "main" como label global
.skip <n> /* deixa um espaço de n bytes */
/* uma instrução RISC-V ocupa 4 bytes */
exemplo completo (pode colocar num arquivo chamado inst.s):
.section .text
.globl main
main:
        addi    a2,zero,55
	add	a3,a2,a1
/* Iremos usar o registrador sp para fazer acessos à memória.
   Spike supõe RAM a partir do endereço 0x80000000.
   A instrução lui coloca este valor em sp.
   Não precisa usar esta instrução para a implementação FPGA. */
        lui     sp,0x80000
	addi	sp,sp,0x40
pula:
	lw	a1,8(sp)
        sw      a3,16(sp)
        bne     a2,a3,pula
fim:
.skip  0x20 - (fim -main)  /* cria espaço ate o endereço 0x20 */
        lw   t4, 48(s0)    /* esta instrução ficará no endereço 0x20 */

Usar o simulador ISA para RISC-V

No diretório do simulador de Ícaro, dê o comando:
make isa 

Agora é só dar Enter para executar uma instrução por vez.

Observe que as instruções lw e sw só podem ser usados no spike com endereços múltiplos de 4 e maior do que 0x80000000. No caso da nossa implementação física do RISC-V, endereços de memória tem seus 24 bits mais significados cortados, assim o endereço 0x80000000 se torna 0x00.

Usar o simulador de placa FPGA de Ícaro

Dê os comandos:
make inst.objdump
./RISCV_sim

Rodar em processador RISC-V físico

Carregue o arquivo assembly no simulador remoto.

Programar um processador RISC-V em código de máquina

Aqui usamos código de máquina em notação binária mesmo.

Primeiro dê o comando
 make isa-clean 

para apagar os arquivos assembly.

Use este exemplo completo (pode colocar num arquivo chamado inst.101):
; para um comentario inicie a linha com ;

; campos da instrução tipo I
;     imm        rs1  funct3  rd    opcode
0000 0100 0000  00000  000  00010  001 0011 ; addi    sp,zero,0x40
0000 0001 0001  00000  000  01011  001 0011 ; addi    a1,zero,0x11
0000 0010 0010  00000  000  01100  001 0011 ; addi    a2,zero,0x22

; campos da instrução tipo R
; funct7   rs2    rs1  funct3  rd    opcode
0000 000  01011  01100  000  01101  011 0011 ; add     a3,a2,a1

; campos das instruções tipo S e SB
;  imm     rs2    rs1  funct3 imm    opcode
0000 000  01101  00010  010  10000  010 0011 ; sw      a3,16(sp)

Usar o simulador ISA para RISC-V

No diretório do simulador de Ícaro, dê os comando:
make isa 

Usar o simulador de FPGA de Ícaro

./RiSCV_sim

Este comando carrega automaticamente o arquivo inst.101.

Rodar em processador RISC-V físico

Carregue o arquivo inst.101 no simulador remoto.

Centavos

resultadocentavos
executar o código C na placa FPGA (com argumentos)20
executar seu próprio código C na placa FPGA (com argumentos)20..200
executar seu próprio código C++ (.cpp) na placa FPGA (com argumentos)100..200
executar o código Assembly na placa FPGA10
executar seu próprio código Assembly na placa FPGA10..100
executar o código de máquina na placa FPGA10
executar seu próprio código de máquina na placa FPGA10..50

Só vale uma única execução para cada linha. Se fizer várias, vale a última. A linha correspondente da coluna resultado deve ser copiada sem tirar nada nem acrescentar nada para a linha de descrição (a segunda linha do arquivo). No caso de vários arquivos, nome e descrição devem constar no arquivo que contém o main.

O código C, assembly ou binário pode ser qualquer coisa, não precisa ter uma função específica. Porém, tem que compilar/assemblar/disassemblar sem erro e entrar na memória de instruções da implementação RISC-V dentro da FPGA e ser realmente executado.

Mais informações

Material de Joseana

© 2008 Profs. Elmar Melcher e Joseana Fechine. Monitores: Sergio Espinola e Fabricio Lelis - DSC/UFCG
Modificada em March 15, 2022, at 01:27 PM