Olá Neste artigo, mostrarei quais etapas você precisa seguir para criar um processador e um ambiente simples para ele.
Primeiro você precisa decidir qual será o processador. Os seguintes parâmetros são importantes:
- O tamanho da palavra e dos registros da máquina (bit / "bit" do processador)
- Instruções da máquina (instruções) e seu tamanho
As arquiteturas de processador podem ser divididas em 2 tipos, de acordo com o tamanho das instruções (na verdade, existem mais, mas outras opções são menos populares):
A principal diferença é que os processadores RISC têm o mesmo tamanho de instrução. Suas instruções são simples e executam-se relativamente rapidamente, enquanto os processadores CISC podem ter tamanhos de instruções diferentes, alguns dos quais podem demorar um pouco.
Decidi fazer um processador RISC muito parecido com o MIPS .
Fiz isso por vários motivos:
- É bastante simples criar um protótipo desse processador.
- Toda a complexidade desse tipo de processador é transferida para programas como assembler e / ou compilador.
Aqui estão os principais recursos do meu processador:
- Tamanho da palavra e registro da máquina - 32 bits
- 64 registros (incluindo contador de comandos )
- 2 tipos de instruções
O tipo de registro (a seguir denominado tipo de registro) fica assim:

A peculiaridade de tais instruções é que elas operam com três registros.
Tipo imediato :

Instruções deste tipo operam com dois registros e um número.
OP é o número da instrução a ser executada (ou para indicar que esta instrução do tipo Registro ).
R0 , R1 , R2 são números de registro que servem como operandos para a instrução.
Func é um campo adicional usado para indicar o tipo de instruções do tipo Registro .
Imm é o campo em que o valor é gravado, o qual queremos fornecer explicitamente instruções como um operando.
Uma lista completa de instruções pode ser encontrada no repositório do github .
Aqui estão apenas alguns deles:
nor r0, r1, r2
NOR é uma instrução do tipo Register que executa um OR ORY lógico nos registradores r1 e r2, após o qual grava o resultado no registrador r0.
Para usar esta instrução, é necessário alterar o campo OP para 0000 e o campo Func para 0000000111 no sistema de números binários.
lw r0, n(r1)
LW é uma instrução do tipo Imediato que carrega um valor de memória em r1 + n no registro r0.
Para usar esta instrução, por sua vez, é necessário alterar o campo OP para 0111 e escrever o número n no campo IMM .
Escrevendo código do processador
Após criar o ISA, você pode começar a escrever o processador.
Para isso, precisamos conhecer algum tipo de linguagem de descrição de equipamento. Aqui estão alguns deles:
- Verilog
- VHDL (não confunda com o anterior!)
Eu escolhi a Verilog, porque programar fazia parte do meu curso universitário.
Para escrever um processador, você precisa entender a lógica de sua operação:
- Obter instruções no contador de comandos (PC)
- Instruções de decodificação
- Execução de instruções
- Adicionando ao contador o tamanho do comando da instrução executada
E assim por diante ad infinitum.
Acontece que você precisa criar vários módulos:
Analisaremos cada módulo individualmente.
Registrar arquivo
Um arquivo de registro fornece acesso aos registros. Com isso, você precisa obter os valores de alguns registros ou alterá-los.
No meu caso, tenho 64 registros. Em um dos registros, o resultado da operação nos outros dois é gravado; portanto, preciso oferecer a oportunidade de alterar apenas um e obter os valores dos outros dois.
Decodificador
Um decodificador é a unidade responsável pelas instruções de decodificação. Indica quais operações precisam ser executadas pela ALU e outras unidades.
Por exemplo, a instrução addi deve adicionar o valor do registro $ zero (sempre armazena 0 ) e 20 e colocar o resultado no registro $ t0.
addi $t0, $zero, 20
Neste ponto, o decodificador determina que esta instrução:
- Tipo imediato
- Deve escrever o resultado no registro
E transfere essas informações para os seguintes blocos.
ALU
Depois que o controle passa para a ALU. Geralmente, realiza todas as operações lógicas e matemáticas, bem como operações de comparação de números.
Ou seja, se considerarmos a mesma instrução addi , nesse estágio ocorre a adição de 0 e 20 .
Outros
Além dos blocos acima, o processador deve ser capaz de:
- Obter e alterar valores na memória
- Executar saltos condicionais
Aqui e ali, você pode ver como fica o código.
Montador
Depois de escrever o processador, precisamos de um programa que converta comandos de texto em código de máquina para não fazê-lo manualmente. Portanto, você precisa escrever o assembler.
Eu decidi implementá-lo na linguagem de programação C.
Como meu processador possui uma arquitetura RISC , para simplificar minha vida, decidi projetar o assembler para poder adicionar facilmente minhas próprias pseudo instruções (combinações de várias instruções elementares ou outras pseudo instruções).
Você pode implementar isso usando uma estrutura de dados que armazena o tipo de instrução, seu formato, um ponteiro para uma função que retorna códigos de instruções da máquina e seu nome.
Um programa regular começa com uma declaração de segmento.
Dois segmentos .text são suficientes para nós - nos quais o código fonte de nossos programas será armazenado - e .data - nos quais nossos dados e constantes serão armazenados.
Uma instrução pode ser assim:
.text jie $zero, $zero, $zero # addi $t1, $zero, 2 # $t1 = $zero + 2 lw $t1, 5($t2) # $t1 = *($t2 + 5) syscall 0, $zero, $zero # syscall(0, 0, 0) la $t1, label# $t1 = label
Primeiro, o nome da instrução é indicado, depois os operandos.
Em .data , as declarações de dados são indicadas.
.data .byte 23 # 1 .half 1337 # 2 .word 69000, 25000 # 4 .asciiz "Hello World!" # ( ) .ascii "12312009" # ( ) .space 45 # 45
Um anúncio deve começar com um ponto e um nome de tipo de dados, seguidos por constantes ou argumentos.
É conveniente analisar (varrer) o arquivo assembler da seguinte maneira:
- Primeiro, verifique o segmento
- Se for um segmento .data , analisamos diferentes tipos de dados ou um segmento .text
- Se for um segmento .text , analisamos comandos ou um segmento .data
Para funcionar, o montador precisa passar pelo arquivo de origem 2 vezes. Na primeira vez em que ele considera quais são os desvios dos links (eles servem), eles geralmente ficam assim:
la $s4, loop # loop s4 loop: # ! mul $s2, $s2, $s1 # s2 = s2 * s1 addi $s1, $s1, -1 # s1 = s1 - 1 jil $s3, $s1, $s4 # s3 < s1
E na segunda passagem, você já pode gerar um arquivo.
Sumário
No futuro, você pode executar o arquivo de saída do assembler em nosso processador e avaliar o resultado.
Além disso, o montador pronto pode ser usado no compilador C. Mas isso é mais tarde.
Referências: