Os módulos HDL do SAPHO por dentro¶
Este capítulo abre a caixa: o que exatamente é o processador que o YANC gera, e por que ele merece o rótulo de soft-core otimizado. É leitura para quem vai estudar ou estender a arquitetura; o uso normal da plataforma não exige nada daqui.
A biblioteca de módulos¶
O processador é montado a partir de uma biblioteca fixa de módulos Verilog, instalada com a AURORA em components/HDL:
Arquivo |
Contém |
|---|---|
|
as memórias de instrução e de dados, e o módulo |
|
o contador de programa, o prefetch, as duas pilhas, o controle de memória e de I/O, e o núcleo |
|
o decodificador de instruções |
|
a unidade lógica e aritmética, um submódulo por operação |
|
o decodificador one-hot das portas de I/O |
O arquivo gerado por projeto, Hardware/<proc>.v, é um invólucro fino: instancia processor com os parâmetros do seu programa e liga as portas externas.
A arquitetura¶
Um processador de acumulador único, Harvard, com três estágios de pipeline efetivos e uma instrução por ciclo:
Memórias separadas de programa e de dados, com larguras independentes. As duas são inicializadas pelos
.mifvia$readmemb.Não há banco de registradores: toda operação passa pelo acumulador. Expressões aninhadas usam a pilha de dados (profundidade
#NDSTAC); chamadas de função usam a pilha de retorno (profundidade#SDEPTH).Os desvios resolvem no estágio de busca, sem penalidade. Não há forwarding nem stall porque a arquitetura não cria hazards: o caminho de dados e o de controle são casados por construção.
Reset síncrono em tudo, uma escolha amigável a FPGA.
O conjunto completo de instruções, com os 108 opcodes, está em Conjunto de instruções do processador.
A otimização que dá nome à plataforma¶
Aqui está o mecanismo central. Abra um <proc>.v gerado e olhe a instância do processor: além dos parâmetros numéricos, há uma lista de parâmetros com nomes de instruções, cada um ligado em 1:
processor #(
.NUBITS(16), .NBMANT(10), .NBEXPO(5),
// ...
.ADD(1), .S_ADD(1), .SHR(1), .INN(1), .OUT(1)
// as demais instrucoes ficam em 0
) u_proc ( /* ... */ );
Cada instrução que o seu programa usa vira um parâmetro em 1. Dentro dos módulos da biblioteca, cada bloco está embrulhado em um generate if condicionado a esse parâmetro: instrução não usada, bloco não sintetizado. O somador de ponto flutuante, o divisor, o deslocador, cada um só existe no circuito se alguma linha do seu C± o exigir.
É por isso que o TASM imprime o relatório de recursos instanciados e a estimativa de uso do conjunto de instruções: aquilo é a lista dos generate que ligaram. E é por isso que trocar / 4 por >> 2 no tutorial muda o diagrama do PRISM: o parâmetro DIV foi de 1 a 0, e o divisor evaporou.
O resultado prático: dois programas diferentes geram dois processadores de tamanhos diferentes sobre a mesma biblioteca. O processador é do tamanho do algoritmo, e o custo de área em FPGA acompanha o que o código pede, não o pior caso da arquitetura.
A visibilidade de simulação¶
Os módulos carregam um arnês de simulação, ativado apenas nos simuladores, que expõe as suas variáveis pelo nome, as trilhas de assembly e de linha C±, e os sinais delta_float e delta_int de erro de arredondamento. Nada disso existe na síntese física: o <proc>.v sintetizado é só o circuito.
Parâmetros derivados¶
Dois parâmetros são calculados pelo compilador, não escolhidos por você: a largura do campo de operando (que cresce com o tamanho do programa e dos dados) e os tamanhos das memórias (o número exato de instruções e de células de dados do programa). O processador não carrega memória sobrando.
Quem quiser ir além, o código-fonte da biblioteca e dos compiladores é aberto, no repositório do YANC na organização nipscernlab.