Instruction Set Architecture, a linguagem das máquinas
Tags: programming, architecture, hardware
Published: 2024-05-29 22:09:36

Para se comunicar com um computador precisamos falar a sua língua, ao vocabulário desta “linguagem” é conhecido como *Instruction Set.* Para **os programadores de plantão, podemos dizer que a ISA de um hardware é a interface deste para com o software, ou, simplificando, sua API.

Já abordei um pouco sobre como computadores funcionam e como ocorre a comunicação entre a nossa linguagem de programação e o hardware em um [post](https://dev.to/erick_tmr/como-um-computador-funciona-4me9) anterior, mas vou recapitular brevemente.

## Como *ifs*, *fors* e *vars* viram binários que o computador entende?

Como dito anteriormente, o computador entende somente *instructions,* ou seja, precisamos utilizar a ISA para operar um computador. As *instructions* são constituídas por sequencias de números binários que possuem significado para o computador.

Utilizar números binários direto para se comunicar com o computador é uma tarefa complexa e tediosa, ou seja, um prato cheio para que um programador torne esta tarefa mais simples, então, foi criado um programa que ajuda a programar, os chamados `assemblers`, que traduziam linguagem escrita e entendida por seres humanos em linguagem de máquina (instructions).

Apesar do `assembly` já ser uma simplificação absurda em comparação com as *instructions* cruas, ainda era muito semelhante a forma de pensar e agir da máquina e não do ser humano, sendo assim foram criadas as linguagens de programação de mais alto nível, como C, C++, Java e afins.

As primeiras linguagens funcionavam como uma abstração acima da camada do `assembly`, onde você tinha um programa que transformava os comandos da linguagem em comandos `assembly` , esse programa é chamado de compilador.

O compilador torna acessível a comunicação com o computador, fazendo com que consigamos modelar de maneira mais próxima ao nosso pensamento o que gostaríamos que a máquina executasse. 

```
// o seguinte programa em C só é possível de ser interpretado por conta dos compiladores

swap(size_t v[], size_t k) {
	size_t temp;
	temp = v[k];
	v[k] = v[k+1];
	v[k+1] = temp;
}

// traduzido para assembly pelo compilador
swap:
	slli x6, x11, 3
	add  x6, x10, x6
	lw   x5, 0(x6)
	lw   x7, 4(x6)
	sw   x7, 0(x6)
	sw   x5, 4(x6)
	jalr x0, 0(x1)
	
// traduzido para binários
00000000001101011001001100010011
00000000011001010000001100110011
00000000000000110011001010000011
00000000100000110011001110000011
00000000011100110011000000100011
00000000010100110011010000100011
00000000000000001000000001100111
```

## Operações aritméticas

Qualquer computador precisa saber trabalhar com operações aritméticas, sendo assim, as instruções aritméticas formam a base de qualquer ISA.

Analisando a arquitetura RISC-V e sua linguagem assembly, temos 3 instruções aritméticas:

- Add (Soma, utiliza 3 registradores)
- Subtract (Subtração, utiliza 3 registradores)
- Add immediate (Soma com constantes, utiliza 2 registradores)

Operações aritméticas em hardware acontecem somente em lugares específicos, esses lugares são chamados de registradores.

Continuando com a arquitetura RISC-V, ela possui 32 registradores de 32 bits de tamanho, esse agrupamento de 32 bits acontece com tanta frequência que chamamos de *word*.

## Operações de memória

Como vimos, a arquitetura RISC-V possui 32 registradores de 32 bits cada, porém sabemos das linguagens de programação que podemos ter tipos de tamanho variável, ou até tipos compostos como arrays. Dada essa necessidade precisamos de instruções que consigam mover dados dos registradores para a memória do computador, e vice versa.

As instruções que movem dados entre registradores e memória damos o nome de *data transfer instructions*.

Em RISC-V temos duas instruções de transferência de dados:

- Load (Copia um word de um certo endereço da memória para um registrador)
- Store (Copia um word de um registrador para um certo endereço da memória)

A memória do computador nada mais é do que um grande array, onde os índices são os seus endereços. Em RISC-V, como trabalhamos primariamente com words (agrupamentos de 32 bits ou 4 bytes), o endereço de um word coincide com o endereço de um de seus bytes, words que são vizinhos estão separados por 4 endereços, consequentemente.

Computadores são divididos entre aqueles que usam o endereço do byte mais à esquerda ("big-endian") e aqueles que usam o endereço do byte mais à direita ("little-endian"), a esse conceito damos o nome de *endiannes*. RISC-V utiliza o “liitle-endian”, ou seja, o endereço de um word equivale ao endereço do seu byte mais a direita.

## Representando instruções no computador

Instruções da ISA são mantidas no hardware através de uma série de sinais eletrônicos de baixa e alta voltagem, esses sinais podem ser representados em números, como 0 e 1, ou seja, bits.

Cada componente de uma instrução pode ser considerada como um número individual, agrupando esses números lado a lado temos a representação da instrução. A essa forma de agrupamento de bits damos o nome de “instruction format”.

Dando como exemplo uma instrução de soma do RISC-V:

```
add x9, x20, x21

Representação decimal:
0  21  20  0  9  51

Cada segmento é chamado de "field"
Os segmentos 1, 4 e 6 (contendo os números 0, 0 e 51) coletivamente dizem para 
o computador RISC-V que essa é uma instrução de soma.
O segmento 2 diz qual é o ultimo registrador da operação de soma (um dos "somandos")
O segmento 3 diz o outro registrador envolvido na soma
O segmento 5 contém o registrador que vai receber o resultado da operação

Representação binária:
|  0000000  |  10101  |  10100  |  000  |  01001  |  0110011  |
   7 bits     5 bits    5 bits   3 bits   5 bits     7 bits 
```

Para distinguir a representação numérica da linguagem assembly, chamamos ela de *machine language*, uma sequência de instruções é chamada de *machine code*.

## Finalizando

Ao explorar como instruções simples, como operações aritméticas e transferências de dados, são representadas em binário e executadas pelo hardware, podemos apreciar a complexidade e a elegância dos sistemas computacionais. Essa compreensão não só melhora nossas habilidades como programadores, mas também nos permite otimizar e depurar nosso código de maneira mais eficiente.

A evolução das linguagens de programação, desde o assembly até as linguagens de alto nível, reflete o constante esforço da comunidade de tecnologia em tornar a interação com computadores mais acessível e intuitiva. No entanto, a base de toda essa evolução permanece na ISA, que continua a ser o alicerce sobre o qual construímos nossas aplicações.

Apesar de tudo isso, não se sinta pressionado a dominar esses conceitos profundamente. Nós, programadores, trabalhamos diretamente com as linguagens de programação, que foram criadas para abstrair essa complexidade e nos ajudar a ser mais produtivos. Conheço vários ótimos programadores que não dominam esses conceitos e estão se saindo muito bem em suas carreiras. Aprofundar-se em ISA é uma escolha que pode enriquecer sua compreensão e habilidade técnica, mas não é um requisito para ser um excelente programador.