Programar em RISC-V

Traduzir condições e ciclos, percorrer vetores e texto e operar sobre bits e várias palavras.

TLDR

Perguntar sobre esta página

ChatGPTClaudePerplexityGrokGeminiCopiar e abrirDeepSeekCopiar e abrir

Para guardar o menor de dois números, o programa compara os registos, salta para o ramo escolhido e copia o valor para o destino. Assembly torna explícitas estas operações e os endereços de memória, por isso um mapa dos registos e uma condição sobre o estado de cada ciclo ajudam a verificar a tradução.

Condições e ciclos

Para if (x < y) z=x; else z=y, coloca x em t0, y em t1 e z em t2:

    bge t0, t1, caso_y
    mv t2, t0
    j fim
caso_y:
    mv t2, t1
fim:

O salto testa a condição inversa para ir ao else. Depois do caso x, o salto incondicional evita executar também o caso y. Se x e y forem valores sem sinal, usa bgeu.

Um while testa antes do corpo, podendo executar zero vezes. Um do...while testa depois, executando pelo menos uma vez. Uma condição i<n precisa de um teste final quando i já chegou a n.

Somar um vetor

Queremos somar quatro words 7,-3,10,0. O programa usa t0 como apontador, t1 como número de elementos por ler e t2 como soma. No início de cada iteração, t2 é a soma dos elementos já lidos e t0 aponta para o próximo por ler.

Somar words, incluindo um vetor vazio
.data
.align 2
vetor: .word 7, -3, 10, 0
.text
.globl main
main:
    la t0, vetor
    li t1, 4
    li t2, 0
loop:
    beq t1, zero, done
    lw t3, 0(t0)
    add t2, t2, t3
    addi t0, t0, 4
    addi t1, t1, -1
    j loop
done:
    mv a0, t2
    li a7, 1
    ecall
    li a7, 10
    ecall
Resultado

Executa o código para ver a saída e os gráficos.

Aqui os valores e o resultado cabem em RV32 com sinal. Para outros dados, add continua a guardar apenas os 32 bits inferiores; se precisares da soma matemática exata, tens de detetar overflow ou usar uma representação mais larga. O comprimento é um inteiro não negativo e nunca excede os elementos disponíveis.

Em cada passagem, lw t3,0(t0) lê um elemento, add t2,t2,t3 acumula, o apontador t0 avança 4 bytes e o contador t1 diminui. Quando t1 é zero, o programa salta para done antes de ler memória, por isso o caso vazio imprime 0 sem acessos inválidos; este exemplo corre em main e termina com ecall, não sendo uma rotina com retorno em a0. Para o tornar rotina, recebe base em a0 e comprimento em a1 e devolve a soma em a0, sem ecall.

Traçado com [7,-3,10,0], t0=base, t1=por ler, t2=soma, t3=elemento:

Iteraçãot0t1 antest2 antest3t2 depois
1base+04077
2base+437-34
3base+8241014
4base+12114014

Para procurar o máximo, um vetor vazio precisa de contrato próprio, como devolver um sinal de erro. Num vetor não vazio, inicializa o máximo com o primeiro elemento, não com zero, pois caso contrário [-8,-3] daria incorretamente 0; depois percorre os restantes com comparação com sinal.

Para inverter um vetor, usa apontadores para a primeira e última word. Lê ambos os valores antes de escrever qualquer um, troca-os e aproxima os apontadores por 4 bytes. Termina quando se encontram ou cruzam. O caso ímpar deixa o elemento central no lugar.

Inverter sem perder elementos

No vetor [7,-3,10,0,8], trocamos primeiro 7 com 8, depois -3 com 0. O resultado é [8,0,10,-3,7]; o 10 central não muda. Durante cada troca é necessário ler as duas words antes da primeira escrita, para não apagar o segundo valor que ainda precisamos.

Inverter um vetor no próprio espaço
.data
.align 2
vetor: .word 7, -3, 10, 0, 8
.text
.globl main
main:
    andi sp, sp, -16
    la a0, vetor
    li a1, 5
    jal ra, inverter
    la t0, vetor
    li t1, 5
mostrar:
    beq t1, zero, terminar
    lw a0, 0(t0)
    li a7, 1
    ecall
    li a0, 10
    li a7, 11
    ecall
    addi t0, t0, 4
    addi t1, t1, -1
    j mostrar
terminar:
    li a7, 10
    ecall
inverter:
    li t0, 2
    bltu a1, t0, voltar
    mv t0, a0
    slli t1, a1, 2
    add t1, a0, t1
    addi t1, t1, -4
trocar:
    bgeu t0, t1, voltar
    lw t2, 0(t0)
    lw t3, 0(t1)
    sw t3, 0(t0)
    sw t2, 0(t1)
    addi t0, t0, 4
    addi t1, t1, -4
    j trocar
voltar:
    ret
Resultado

Executa o código para ver a saída e os gráficos.

A rotina recebe a base em a0 e o comprimento em a1. Se houver menos de dois elementos, volta antes de calcular a última posição. Caso contrário, slli t1,a1,2 converte elementos em bytes e o último elemento fica em base+4×comprimento−4. A comparação sem sinal entre os apontadores termina o ciclo quando se encontram ou cruzam. Supomos um vetor válido, alinhado e que não atravessa o fim do espaço de endereços. A rotina é leaf e só altera registos caller-saved.

Texto ASCII

Uma string .asciz termina num byte zero. lbu lê um byte e o apontador avança 1; o terminador não conta no comprimento. Para reconhecer um dígito, testa '0'<=c<='9'; se for verdadeiro, o valor é c−48.

Para uma letra, testa separadamente os intervalos A a Z e a a z. Não basta o intervalo de A a z, porque inclui pontuação entre as maiúsculas e minúsculas.

Contar caracteres ASCII alfanuméricos
.data
texto: .asciz "A-7_z!"
.text
.globl main
main:
    la t0, texto
    li t1, 0
loop:
    lbu t2, 0(t0)
    beq t2, zero, done
    li t3, 48
    bltu t2, t3, next
    li t3, 57
    bgeu t3, t2, count
    li t3, 65
    bltu t2, t3, next
    li t3, 90
    bgeu t3, t2, count
    li t3, 97
    bltu t2, t3, next
    li t3, 122
    bltu t3, t2, next
count:
    addi t1, t1, 1
next:
    addi t0, t0, 1
    j loop
done:
    mv a0, t1
    li a7, 1
    ecall
    li a7, 10
    ecall
Resultado

Executa o código para ver a saída e os gráficos.

A classificação conta só letras e dígitos ASCII: em A-7_z!, conta A, 7 e z, dando 3. O traço, o underscore e a exclamação ficam excluídos. Não é uma rotina Unicode.

Converter decimal sem overflow

Para converter uma string ASCII de dígitos num inteiro, começa com acumulador v=0v=0. Cada novo dígito d=c−48d=c-48 ocupa a casa das unidades e desloca os anteriores:

v+=10v+d.v^+=10v+d.

Em 204, os estados do acumulador são 2, 20 e 204. Testa primeiro se o byte é zero ou se está entre 48 e 57; o terminador fecha a conversão e um byte fora do intervalo torna a entrada inválida. O contrato deve decidir se uma string vazia representa zero ou é rejeitada.

Para um resultado sem sinal de 32 bits, o máximo é M=232−1=4294967295M=2^{32}-1=4294967295. Antes da multiplicação, divide esse limite em quociente e resto por 10:

M=10×429496729+5.M=10\times429496729+5.

Rejeita se v>429496729v>429496729, ou se v=429496729v=429496729 e d>5d>5. Nos restantes casos, 10v+d10v+d cabe. Usa comparações sem sinal; a constante grande precisa de li e de um registo, não cabe no imediato de addi.

A entrada 4294967295 chega ao último passo com v=429496729 e d=5, por isso é aceite. 4294967296 falha com o mesmo v e d=6 antes de alterar o acumulador. Testar só depois de mul e add não basta: essas instruções conservam 32 bits e podem produzir um valor pequeno depois de perder os bits altos.

Máscaras e deslocamentos

Para testar o bit k, calcula x & (1 << k). Para o limpar, faz AND com a máscara complementar. Para o colocar a 1, faz OR. Para o trocar, faz XOR. Um AND com 1 isola o bit menos significativo.

Contar bits a 1 por deslocamento usa srli até a palavra ficar zero. Com srai, uma palavra com bit de sinal 1 pode nunca chegar a zero. Uma rotação à esquerda por k combina (x≪k)(x\ll k) com (x≫(32−k))(x\gg(32-k)) através de OR; trata k=0 em separado para não assumir um deslocamento efetivo de 32.

Soma de várias palavras

Um inteiro de 64 bits pode ser guardado em duas words, baixa e alta. Soma primeiro as baixas e obtém carry se a soma sem sinal for menor que uma das entradas; depois soma as altas e o carry, com

L=(AL+BL) mod 232,c=[L<AL],H=(AH+BH+c) mod 232.L=(A_L+B_L)\bmod2^{32},\quad c=[L<A_L],\quad H=(A_H+B_H+c)\bmod2^{32}.

A igualdade não indica carry: se BL=0B_L=0, L pode ser igual a ALA_L sem ter havido transbordo.

Contagem de bits e soma de 64 bits
.text
.globl main
main:
    li t0, 0x80000005
    li t1, 0
count_loop:
    beq t0, zero, count_done
    andi t2, t0, 1
    add t1, t1, t2
    srli t0, t0, 1
    j count_loop
count_done:
    mv a0, t1
    li a7, 1
    ecall
    li a0, 10
    li a7, 11
    ecall
    li t0, -1
    li t1, 1
    li t2, 1
    li t3, 2
    add t4, t0, t2
    sltu t5, t4, t0
    add t6, t1, t3
    add t6, t6, t5
    mv a0, t4
    li a7, 1
    ecall
    li a0, 10
    li a7, 11
    ecall
    mv a0, t6
    li a7, 1
    ecall
    li a7, 10
    ecall
Resultado

Executa o código para ver a saída e os gráficos.

As palavras do resultado são impressas separadamente para mostrar o carry entre a word baixa e a alta.

Para analisar um binary32 guardado como bits, extrai expoente com deslocamento e máscara e verifica a fração. NaN exige expoente 0xFF e fração diferente de zero; infinito tem fração zero. A máscara de magnitude 0x7FFFFFFF permite reconhecer ambos os zeros sem confundir -0 com um número negativo não nulo.

Exercícios

Máximo de negativos

Uma rotina inicia max=0 e percorre o vetor [-12,-4,-9]. Explica o erro e corrige a inicialização e o caso vazio.

Pista

O zero pode não ser um elemento do vetor.

Mais uma pista

Para um vetor não vazio, começa pelo primeiro valor.

Solução

A rotina devolve 0, mas o máximo é -4. Deve testar primeiro se o comprimento é zero e aplicar um contrato de erro ou ausência de resultado. Se não for vazio, carrega -12 como máximo, depois compara -4 e -9 com sinal. O resultado é -4.

Confere a tua resposta:

  • Máximo correto -4.
  • Inicializa com o primeiro elemento e define o caso vazio.

Erros frequentes

Não resolve o erro trocar para comparação unsigned; isso muda a ordem matemática.

Voltar à explicação
Converter dígitos sem dar a volta

Uma rotina converte uma string ASCII terminada em zero num inteiro sem sinal de 32 bits. O resultado deve ser exato; vazio, bytes que não sejam dígitos e overflow são erros. A memória da string é válida até ao terminador.

Dá a atualização do acumulador e segue 204. Deriva um teste que rejeite overflow antes de multiplicar por 10, usando o máximo 232−12^{32}-1. Aplica-o ao último dígito de 4294967295 e 4294967296. Explica por que testar só depois de guardar 32 bits não basta.

Pista

Escreve o máximo como 10 vezes um quociente mais um resto. Compara o acumulador com o quociente antes de o alterar.

Solução

Depois de confirmar 48≤c≤5748\le c\le57, calcula d=c−48d=c-48 e atualiza v+=10v+dv^+=10v+d. Com v inicial zero, 204 dá 2, 20 e 204. Se o primeiro byte for zero, rejeita vazio; se algum byte não for dígito, rejeita antes de o usar.

O máximo é 4294967295=10×429496729+54294967295=10\times429496729+5. Se v>429496729, qualquer próximo dígito transborda. Se v=429496729, só d≤5 cabe. Se v for menor, todos os dígitos 0…9 cabem. As comparações são sem sinal.

No último passo das duas entradas, v=429496729. Com d=5, o resultado é exatamente o máximo; com d=6, rejeita antes da conta. Se calculasse primeiro em 32 bits, 4294967296=2324294967296=2^{32} ficaria 0, ocultando o overflow. É preciso também um estado ou teste inicial para distinguir a string vazia da string 0, que é válida.

Confere a tua resposta:

  • Usei v seguinte=10v+(c−48), depois de validar o byte.
  • Rejeitei antes da conta se v>429496729, ou se v=429496729 e d>5.
  • Aceitei 4294967295 e rejeitei 4294967296; vazio e byte inválido são erros.

Erros frequentes

Não uses comparações com sinal para o acumulador unsigned nem assumas que um resultado pequeno depois de mul prova que a operação coube.

Voltar à explicação
Carry sem sinal

Na soma das words baixas A=0xFFFFFFFF e B=0, a soma L é 0xFFFFFFFF. Uma rotina considera carry se L≤A. Está correta? Dá a condição correta e um caso com carry.

Pista

Igualdade pode ocorrer sem ultrapassar 32 bits.

Mais uma pista

Usa comparação estrita unsigned, porque a igualdade também acontece sem carry.

Solução

Não: L=A e a soma não teve carry. A condição é L<AL<A, sem sinal, implementável por sltu. Com A=0xFFFFFFFF e B=1, L=0 e L<AL<A é verdadeiro, carry=1. Esse carry acrescenta-se à soma das words altas.

Confere a tua resposta:

  • A igualdade não gera carry neste caso.
  • Usa comparação unsigned estrita e confirma o caso FFFFFFFF+1.

Erros frequentes

Não uses comparação com sinal nem menor ou igual para esta condição.

Voltar à explicação
Classificar sem operações de float

Uma word binary32 é lida em t0. Descreve testes com máscaras que distinguem zero, infinito e NaN sem calcular em vírgula flutuante.

Pista

Separa magnitude, expoente e fração.

Mais uma pista

Máscaras: 0x7FFFFFFF, 0x7F800000, 0x007FFFFF.

Solução

Zero: (t0 & 0x7FFFFFFF)==0, aceitando +0 e -0. Expoente máximo: (t0 & 0x7F800000)==0x7F800000. Se isso for verdadeiro, fração (t0 & 0x007FFFFF)==0 indica infinito; fração diferente de zero indica NaN. Para normais/subnormais, examina se o expoente é zero e se a fração é não zero.

Confere a tua resposta:

  • Zero por magnitude, expoente máximo e fração separados.
  • Fração zero distingue infinito de NaN.

Erros frequentes

Não ignores a fração nem testes apenas um bit do expoente. As máscaras grandes exigem li e AND por registo, não um imediato andi de 32 bits.

Voltar à explicação
Dois níveis de endereçamento

Um descritor em endereço D guarda o apontador do vetor no offset 0 e o comprimento no offset 4. O vetor contém words. Escreve o caminho para ler o elemento i=2 usando t0=D.

Pista

Primeiro lê o apontador guardado, depois o elemento.

Mais uma pista

O offset do terceiro elemento é 8 bytes.

Solução

O excerto é lw t1,0(t0) para obter a base e lw t2,8(t1) para ler o elemento. lw t2,8(t0) leria dados do descritor ou depois dele, não o elemento apontado. Antes do acesso, verifica que o comprimento lw t3,4(t0) é pelo menos 3.

Confere a tua resposta:

  • Primeiro lê a base em 0(t0), depois 8(base).
  • Verifica comprimento pelo menos três.

Erros frequentes

O endereço do campo não é o apontador que o campo armazena.

Voltar à explicação
Trocar sem apagar o outro valor

Uma rotina recebe um vetor válido de words em a0 e comprimento não negativo em a1. Deve inverter o vetor no próprio espaço, incluindo comprimentos 0,1, pares e ímpares. Escreve o núcleo RV32 usando apenas temporários, explica a condição de saída e segue [4,-2,9,6].

Pista

Usa dois apontadores, um em cada extremo.

Mais uma pista

O último elemento tem endereço base+4×comprimento−4.

Solução

Uma solução leaf é:

    li t0, 2
    bltu a1, t0, fim
    mv t0, a0
    slli t1, a1, 2
    add t1, a0, t1
    addi t1, t1, -4
ciclo:
    bgeu t0, t1, fim
    lw t2, 0(t0)
    lw t3, 0(t1)
    sw t3, 0(t0)
    sw t2, 0(t1)
    addi t0, t0, 4
    addi t1, t1, -4
    j ciclo
fim:
    ret

Na primeira troca, 4 e 6 mudam de lugar; na segunda, -2 e 9. Ficamos com [6,9,-2,4] e os apontadores cruzam-se. Em comprimento ímpar encontram-se no elemento central, que fica igual. Comprimentos 0 e 1 voltam sem loads nem stores. A comparação de endereços é sem sinal e supomos que o vetor não atravessa o fim do espaço de endereços.

Confere a tua resposta:

  • Testa comprimento menor que dois antes de calcular o último apontador.
  • Lê as duas words antes das escritas; avança e recua quatro bytes.
  • Termina ao encontrar/cruzar apontadores; resultado [6,9,-2,4].

Erros frequentes

Escrever o primeiro valor sobre o segundo antes de ler ambos perde informação. Só verificar igualdade não cobre o cruzamento em comprimento par.

Voltar à explicação

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

Navegação

Cursor de leitura, sem edição. h, j, k, l movem; / pesquisa nesta página. v seleciona caracteres; V seleciona linhas; y copia. Tab permite destacar ou comentar. Espaço ou Enter abre blocos recolhíveis. Nos cartões, h, j, k, l mudam o foco.

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Copiar fórmula

A cópia automática não está disponível. Copia o LaTeX abaixo.

Pesquisar nesta página

Enter procura · n / N repetem · Esc cancela. Ctrl / Cmd K pesquisa todo o site.

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.