
As guerras entre RISC e CISC no final dos anos 1990 morreram há muito tempo, e hoje acredita-se que a diferença entre RISC e CISC é completamente irrelevante. Muitas pessoas afirmam que os conjuntos de comandos são irrelevantes.
No entanto, os conjuntos de comandos são realmente importantes. Eles colocam restrições nos tipos de otimizações que podem ser facilmente adicionadas ao microprocessador.
Recentemente, dei uma olhada mais de perto nas informações sobre a arquitetura de conjunto de instruções RISC-V (ISA), e aqui estão alguns dos aspectos que realmente me impressionaram sobre o ISA RISC-V:
- Este é um conjunto de comandos RISC pequeno e fácil de aprender. Muito preferível para quem está interessado em aprender sobre microprocessadores.
- , , .
- CPU ISA RISC-V.
- , , RISC-V.
RISC
Quando comecei a entender melhor o RISC-V, percebi que o RISC-V acabou sendo um retorno radical ao que muitos acreditavam ser uma era da computação no passado. Do ponto de vista do design, RISC-V em uma máquina semelhante ao tempo de movimento para um clássico R educed I nstruction S et C omputer (RISC, «computador com um conjunto de comandos curtos") início dos anos 80 e 90.
Nos últimos anos, muitos argumentaram que a divisão em RISC e CISC não faz mais sentido, uma vez que tantas instruções foram adicionadas aos processadores RISC como ARM e, ao mesmo tempo, muitos deles são bastante complexos, que no estágio atual é mais um híbrido do que um processador RISC puro. Considerações semelhantes foram aplicadas a outros processadores RISC, como o PowerPC.
O RISC-V, por outro lado, é um representante verdadeiramente "hardcore" dos processadores RISC. Se você ler sobre as discussões do RISC-V na Internet, encontrará pessoas afirmando que o RISC-V foi desenvolvido por alguns radicais RISC da velha escola que se recusam a acompanhar os tempos.
O ex-engenheiro da ARM Erin Shepherd escreveu uma crítica interessante do RISC-V alguns anos atrás :
ISA RISC-V . , .. (, , ) , .
Darei um pequeno contexto brevemente. O pequeno tamanho do código tem uma vantagem de desempenho porque torna mais fácil armazenar o código executável dentro do cache de alta velocidade do processador.
A crítica aqui é que os projetistas do RISC-V se concentraram demais em fornecer um pequeno conjunto de instruções. Afinal, esse é um dos objetivos originais do RISC.
Segundo Erin, a consequência disso era que um programa real precisaria de muito mais instruções para completar as tarefas, ou seja, ocuparia mais espaço na memória.
Tradicionalmente, por muitos anos, acreditava-se que mais instruções deveriam ser adicionadas ao processador RISC para torná-lo mais semelhante ao CISC. A ideia é que comandos mais especializados podem substituir o uso de vários comandos comuns.
Compressão de comando e fusão macro-operacional
No entanto, existem duas inovações na arquitetura do processador que tornam redundante essa estratégia de adicionar instruções mais complexas de várias maneiras:
- Instruções compactadas - as instruções são compactadas na memória e descompactadas no primeiro estágio do processador.
- Macro-operation Fusion - Duas ou mais instruções simples são lidas pelo processador e fundidas em uma instrução mais complexa.
Na verdade, o ARM já usa essas duas estratégias e os processadores x86 usam a segunda, então o RISC-V não faz nenhum truque novo aqui.
No entanto, há uma sutileza aqui: o RISC-V se beneficia muito mais dessas duas estratégias por duas razões importantes:
- Comandos compactados foram adicionados inicialmente. Outras arquiteturas, como a ARM, pensaram nisso depois e as parafusaram de uma forma bastante apressada.
- É aqui que a obsessão da RISC por um pequeno número de equipes exclusivas se justifica. Simplesmente resta mais espaço para adicionar comandos compactados.
O segundo ponto requer alguns esclarecimentos. Em arquiteturas RISC, os comandos geralmente têm 32 bits de largura. Esses bits precisam ser usados para codificar várias informações. Digamos que temos um comando como este (há comentários após o ponto e vírgula):
ADD x1, x4, x8 ; x1 ← x4 + x8
Ele adiciona o conteúdo dos registradores
x4
e
x8
armazena o resultado em
x1
. O número de bits necessários para codificar esta instrução depende do número de registros disponíveis. RISC-V e ARM têm 32 registradores. O número 32 pode ser expresso em 5 bits:
2⁵ = 32
Como três registros diferentes devem ser especificados no comando, um total de 15 bits (3 × 5) é necessário para codificar os operandos (dados de entrada para a operação de adição).
Portanto, quanto mais recursos queremos oferecer suporte no conjunto de instruções, mais bits tiraremos dos 32 bits disponíveis para nós. Claro, podemos passar para os comandos de 64 bits, mas isso consumirá muita memória, o que significa que o desempenho será prejudicado.
Em um esforço agressivo para manter o número de instruções pequeno, o RISC-V deixa mais espaço para adicionar bits para indicar que estamos usando instruções compactadas. Se o processador vê que certos bits estão definidos no comando, ele entende que precisa ser interpretado como compactado.
Isso significa que, em vez de inserir 32 bits de uma instrução, podemos encaixar duas instruções de 16 bits cada. Naturalmente, nem todos os comandos RISC-V podem ser expressos no formato de 16 bits. Portanto, um subconjunto de instruções de 32 bits é selecionado com base em sua utilidade e frequência de uso. Enquanto as instruções não compactadas podem receber 3 operandos (dados de entrada), as instruções compactadas podem receber apenas 2 operandos. Ou seja, o comando compactado
ADD
terá a seguinte aparência:
C.ADD x4, x8 ; x4 ← x4 + x8
O código de montagem RISC-V usa um prefixo
C.
para indicar que o comando deve ser montado em um comando compactado.
Essencialmente, as instruções compactadas reduzem o número de operandos. Três registradores de operando ocupariam 15 bits, deixando apenas 1 bit para indicar a operação! Assim, ao utilizar dois operandos para indicar o opcode (a operação a ser realizada), temos 6 bits restantes.
Na verdade, isso se aproxima de como o x86 assembler funciona quando não há bits suficientes reservados para usar os três registradores de operando. O processador x86 desperdiça bits para permitir, por exemplo, que o comando
ADD
leia os dados de entrada da memória e dos registros.
No entanto, o verdadeironós nos beneficiamos da combinação de compressão de comando com fusão de macro-operação. Quando o processador recebe uma palavra de 32 bits contendo duas instruções compactadas de 16 bits, ele pode mesclá-las em uma instrução mais complexa.
Parece um absurdo - estamos de volta ao ponto de partida?
Não, já que estamos contornando a necessidade de preencher as especificações ISA com um monte de instruções complexas (ou seja, a estratégia que o ARM está seguindo). Em vez disso, estamos essencialmente expressando toda uma série de comandos complexos indiretamente , por meio de várias combinações de comandos simples.
Em circunstâncias normais, a macro-fusão causaria um problema: embora duas instruções sejam substituídas por uma, elas ainda ocupam o dobro da memória. No entanto, ao compactar comandos, não ocupamos nenhum espaço extra. Aproveitamos ambas as arquiteturas.
Vejamos um dos exemplos dados por Erin Shepherd. Em seu artigo crítico sobre ISA RISC-V, ela mostra uma função simples em C. Para deixar mais claro, tomei a liberdade de reescrevê-la:
int get_index(int *array, int i) {
return array[i];
}
No x86, isso será compilado para o seguinte código de montagem:
mov eax, [rdi+rsi*4]
ret
Quando uma função é chamada em uma linguagem de programação, os argumentos geralmente são passados para a função em um registro de acordo com uma ordem estabelecida que depende do conjunto de instruções usado. Em x86, o primeiro argumento é colocado em um registro
rdi
, o segundo em
rsi
. Por padrão, os valores de retorno devem ser colocados em um registro
eax
.
O primeiro comando multiplica o conteúdo
rsi
por 4. Ele contém uma variável
i
. Por que isso se multiplica? Porque
array
consiste em elementos inteiros separados por 4 bytes. Portanto, o terceiro elemento da matriz está no deslocamento 3 × 4 = 12 bytes.
Em seguida, adicionamos isto ao
rdi
qual contém o endereço de base
array
... Isso nos dá o endereço final do
i
ésimo elemento
array
. Lemos o conteúdo da célula de memória neste endereço e o armazenamos em
eax
: tarefa concluída.
No ARM, tudo acontece de maneira semelhante:
LDR r0, [r0, r1, lsl #2]
BX lr ;return
Aqui, não estamos multiplicando por 4, mas deslocando o registro
r1
2 bits para a esquerda, o que equivale a multiplicar por 4. Esta é provavelmente uma descrição mais precisa do que acontece no x86. Duvido que seja possível multiplicar por qualquer coisa que não seja um múltiplo de 2, uma vez que a multiplicação é uma operação um tanto complicada e deslocar é barato e fácil.
Pela minha descrição do x86, o resto é uma incógnita. Agora vamos ao RISC-V, onde a verdadeira diversão começa! (comentários começam com ponto e vírgula)
SLLI a1, a1, 2 ; a1 ← a1 << 2
ADD a0, a0, a1 ; a0 ← a0 + a1
LW a0, a0, 0 ; a0 ← [a0 + 0]
RET
No RISC-V, registra
a0
e
a1
são simplesmente aliases para
x10
e
x11
. É aqui que o primeiro e o segundo argumentos da chamada de função são colocados.
RET
É um pseudo-comando (abreviação):
JALR x0, 0(ra) ; sp ← 0 + ra
; x0 ← sp + 4 ignoring result
JALR
navega para o endereço armazenado em
ra
que se refere ao endereço de retorno.
ra
É um pseudônimo
x1
.
E tudo parece absolutamente terrível, certo? O dobro de comandos para uma operação simples e comumente usada, como realizar uma pesquisa de índice em uma tabela e retornar um resultado.
Realmente parece ruim. É por isso que Erin Shepherd foi extremamente crítica em relação às decisões de design feitas pelos desenvolvedores do RISC-V. Ela escreve:
As simplificações RISC-V tornam o decodificador (ou seja, o processador front-end) mais simples, mas isso vem com o custo de mais instruções. No entanto, dimensionar a largura do pipeline é uma tarefa complicada, enquanto a decodificação de algumas (ou altamente) instruções incomuns é bem estudada (as principais dificuldades surgem quando a determinação do comprimento do comando não é trivial - devido aos seus prefixos infinitos, x86 é um caso particularmente negligenciado).
No entanto, graças à compressão de comando e fusão macro-op, a situação pode ser mudada para melhor.
C.SLLI a1, 2 ; a1 ← a1 << 2
C.ADD a0, a1 ; a0 ← a0 + a1
C.LW a0, a0, 0 ; a0 ← [a0 + 0]
C.JR ra
Agora, as instruções ocupam exatamente a mesma quantidade de espaço de memória que o exemplo do ARM.
Ok, agora vamos fazer a fusão macro-op !
Uma das condições para o RISC-V permitir a fusão de operações em uma é que o registro de destino corresponda . Esta condição é atendida para comandos
ADD
e
LW
(carregar palavra, "carregar palavra"). Portanto, o processador os transformará em uma instrução.
Se essa condição fosse atendida para SLLI, poderíamos mesclar todos os três comandos em um . Ou seja, o processador veria algo que se assemelha a uma instrução ARM mais complexa:
LDR r0, [r0, r1, lsl #2]
Mas por que não poderíamos escrever essa operação de macro complexa diretamente no código?
Porque o ISA não oferece suporte para essa operação de macro! Lembre-se de que temos um número limitado de bits. Então vamos deixar os comandos mais longos! Não, vai ocupar muita memória e estourar o precioso cache do processador mais rápido.
No entanto, se em vez disso emitirmos essas instruções longas e semicomplexas dentro do processador, não haverá problemas. Um processador nunca tem mais do que algumas centenas de instruções ao mesmo tempo. Portanto, se gastarmos em cada comando, digamos, 128 bits, isso não criará dificuldades. Ainda haverá silício suficiente para tudo.
Quando um decodificador recebe um comando comum, ele geralmente o transforma em uma ou mais microoperações. Essas microoperações são as instruções com as quais o processador realmente trabalha. Eles podem ser muito amplos e conter muitas informações adicionais úteis. O prefixo "micro" soa irônico, porque são mais amplos. No entanto, na realidade, "micro" significa que eles têm um número limitado de tarefas.
A fusão da macrooperação vira o decodificador de cabeça para baixo: em vez de transformar um comando em várias microoperações, pegamos muitas operações e as transformamos em uma microoperação.
Ou seja, o que está acontecendo em um processador moderno pode parecer um tanto estranho:
- Primeiro, ele combina as duas equipes em uma usando compressão .
- Ele então os divide em dois usando a descompactação .
- Em seguida, ele os combina de volta em uma operação usando fusão macro-op .
Outros comandos podem ser divididos em várias microoperações, em vez de mesclados. Por que algumas equipes se fundem enquanto outras se dividem? Existe um sistema nesta loucura?
Um aspecto fundamental da transição para microoperações é o nível de complexidade desejado:
- Não é muito complicado, porque caso contrário eles não serão capazes de completar em um número fixo de ciclos de clock alocados para cada comando.
- Não é tão simples, porque senão vamos apenas desperdiçar os recursos do processador. Fazer duas microoperações levará o dobro do tempo que fazer apenas uma.
Tudo começou com processadores CISC. A Intel começou a dividir suas instruções CISC complexas em microoperações para torná-las mais fáceis de encaixar em pipelines de processador como as instruções RISC. No entanto, em construções subsequentes, os desenvolvedores perceberam que muitas equipes CISC poderiam ser fundidas em uma equipe moderadamente complexa. Se houver menos comandos para executar, o trabalho será concluído mais rápido.
Benefícios obtidos
Discutimos muitos detalhes, então agora deve ser difícil para você entender qual é o significado de todo esse trabalho. Para que serve toda essa compactação e mesclagem? Eles parecem estar fazendo um monte de trabalho desnecessário.
Em primeiro lugar, compactar comandos é completamente diferente de compactar zip. A palavra "compressão" é um pouco enganosa porque a compressão ou descompressão instantânea de um comando é completamente fácil. Nenhum tempo é perdido com isso.
O mesmo se aplica à fusão de macro-operação. Embora o processo possa parecer complicado, sistemas semelhantes já são usados em microprocessadores modernos. Portanto, os custos que toda essa complexidade acrescenta já foram pagos.
No entanto, ao contrário dos designers de ARM, MIPS e x86, quando eles começaram a projetar seu ISA, os criadores do RISC-V sabiam sobre compressão de comando e fusão de macro-ops. Por meio de vários testes com o primeiro conjunto mínimo de instruções, eles fizeram duas descobertas importantes:
- Os programas RISC-V normalmente ocupam o mesmo ou menos espaço de memória do que qualquer outra arquitetura de processador. Incluindo o x86, que deve usar a memória de forma eficiente, visto que é ISA CISC.
- Ele precisa realizar menos microoperações do que outras ISAs.
Na verdade, ao projetar o conjunto de instruções básicas com a fusão em mente, eles foram capazes de fundir instruções suficientes para que o processador de qualquer programa tivesse que realizar menos microoperações do que os processadores concorrentes.
Isso levou a equipe de desenvolvimento do RISC-V a redobrar os esforços para implementar a fusão macro-operacional como uma estratégia RISC-V fundamental. O manual RISC-V tem muitas notas sobre quais operações você pode fazer fusão. Ele também inclui algumas correções para facilitar a mesclagem de comandos encontrados em padrões comuns.
ISA pequeno torna mais fácil para os alunos aprenderem. Isso significa que é mais fácil para um estudante de arquitetura de processador projetar seu próprio processador rodando em instruções RISC-V. É importante lembrar que tanto a compressão de comando quanto a fusão macro-op são opcionais.
RISC-V tem um pequeno conjunto de comandos fundamental que deve ser implementado. No entanto, todos os outros comandos são implementados como parte das extensões. Os comandos compactados são apenas uma extensão opcional.
A fusão macro-op é apenas otimização. Ele não altera o comportamento em geral e, portanto, não precisa ser implementado em seu próprio processador RISC-V.
Estratégia de design RISC-V
O RISC-V pegou tudo o que sabemos sobre os processadores modernos hoje e usou esse conhecimento para projetar os processadores ISA. Por exemplo, sabemos que:
- Os núcleos do processador de hoje têm um sistema sofisticado de previsão de ramificação.
- Os núcleos do processador são superescalares, ou seja, executam muitas instruções em paralelo.
- Para garantir a superescala, a execução fora de ordem é usada.
- Eles têm transportadores.
Isso significa que recursos como a execução condicional com suporte de ARM não são mais necessários. O suporte do ARM para esta função consome pedaços do formato da instrução. O RISC-V pode salvar esses bits.
A execução condicional foi originalmente projetada para evitar bifurcações, porque são ruins para pipelines. Para agilizar o trabalho do processador, ele costuma receber antecipadamente os seguintes comandos, de forma que imediatamente após a execução do anterior, na primeira etapa do processador, o próximo possa ser retirado.
Com a ramificação condicional, não podemos saber com antecedência onde estará o próximo comando quando começarmos a preencher o pipeline. No entanto, um processador superescalar pode simplesmente executar ambas as ramificações em paralelo.
É por isso que o RISV-C também não possui registradores de status, pois eles criam dependências entre os comandos. Quanto mais independente for cada comando, mais fácil será executá-lo em paralelo com outro comando.
Basicamente, a estratégia RISC-V é que podemos tornar o ISA o mais simples possível e a implementação mínima do processador RISC-V o mais simples possível, sem a necessidade de decisões de design que tornariam impossível criar um processador de alto desempenho.
Publicidade
Nossa empresa oferece servidores não apenas com CPUs Intel, mas também servidores com processadores AMD EPYC. Tal como acontece com outros tipos de servidores, existe uma grande seleção de sistemas operacionais para instalação automática, é possível instalar qualquer sistema operacional a partir de sua própria imagem. Tente agora!
