henrique miguel basto da costa desenvolvimento de um ... · ao meu orientador doutor pelo tempo...
TRANSCRIPT
Henrique Miguel Basto da Costa
Desenvolvimento de um Processador VLIW
Henr
ique
Migu
el Ba
sto
da C
osta
outubro de 2013UMin
ho |
201
3De
senv
olvi
men
to d
e um
Pro
cess
ador
VLI
W
Universidade do MinhoEscola de Engenharia
outubro de 2013
Tese de MestradoCiclo de Estudos Integrados Conducentes ao Grau deMestre em Engenharia Electrónica Industrial e Computadores
Trabalho efetuado sob a orientação doProfessor Doutor Paulo Francisco Silva Cardoso
Henrique Miguel Basto da Costa
Desenvolvimento de um Processador VLIW
Universidade do MinhoEscola de Engenharia
V
Agradecimentos
Em primeiro lugar quero agradecer aos meus pais por todos sacrifícios que
fizeram neste meu percurso e que sempre me proporcionaram todas as condições e
oportunidades necessárias para que eu alcançasse e finalizasse esta importante etapa da
minha formação académica.
Ao meu orientador Doutor Paulo Francisco Silva Cardoso pelo tempo
disponibilizado, incentivo e orientação essencial e sempre importante durante a
realização desta dissertação.
À minha querida amiga Liliana Sofia Cerqueira Rego, por ter acreditado, pela
força, apoio e coragem que me deu, em suma, pelas adversidades que superei sempre
graças ao seu apoio.
Aos meus colegas Tiago Gomes, Paulo Garcia, José Silva e Hélder Barbosa pela
ajuda e apoio despendido e pelo companheirismo constante.
À minha família e amigos, pessoas sem as quais não teria sido possível alcançar
esta meta.
À minha irmã, que adoro, e que esta meta por mim alcançada lhe sirva de
motivação para um dia ser e fazer melhor.
Em especial ao meu amigo Filipe Salgado que teve sempre uma atitude de louvar
e de elevada importância durante a realização deste projeto, onde nem o meio mundo
que nos separava o impossibilitou de estar presente.
Por ultimo, a uma das pessoas que mais admiro no seio da universidade, ao
professor Adriano Tavares por sempre me ter tratado como membro do grupo ESRG.
A todos, o meu Muito Obrigado!
VII
Resumo
A arquitetura very long instruction word (VLIW) consiste numa implementação
da técnica de aumento de performance instruction-level parallelism (ILP) e destaca-se
das demais por efetuar esse paralelismo recorrendo à utilização de múltiplas unidades
funcionais em paralelo. No VLIW, tanto a deteção de existência de paralelismo como a
resolução de conflitos nas instruções é efetuada em compiling time, reduzindo
significativamente a complexidade do hardware, o que resulta num menor custo de
implementação e consumo inferior. Existem no entanto alguns obstáculos à afirmação
desta arquitetura, como por exemplo a compatibilidade binária com o software legacy.
Nesta dissertação pretende-se desenvolver um processador VLIW, pois, devido ao
seu alto throughput, e baixo consumo, os processadores VLIW enquadram-se nos
requisitos dos sistemas embebidos. O processador implementado deve servir-se da
cache como meio de acesso à memória principal. Será também desenvolvido um
Assembler dedicado ao processador implementado por forma a gerar código máquina
compatível e com o intuito de permitir que futuras alterações na microarquitetura
possam ser acompanhadas de alteração na geração de código máquina.
Foi feito o estudo de alguns Instruction Set Arquitectures (ISAs) e de
microarquitecturas VLIW existentes, de forma a implementar um processador VLIW
softcore de acordo com o state-of-the-art numa plataforma Xilinx FPGA
Palavras-chave: Very Long Instruction Word (VLIW); Instruction Level Parallelism
(ILP); Multi-Cluster Processor; Low-power computing; ISA Implementation; Vex
Assembler;
IX
Abstract
A very long instruction word architecture (VLIW) is an implementation of the
technique to increase performance instruction- level parallelism (ILP), and stands out
from the others for making this parallelism through the use of multiple functional units
in parallel. In VLIW, the detection of parallelism and conflict resolution in the
instructions is done on compiling time, significantly reducing the complexity of the
hardware, resulting in a lower cost of implementation and less consumption. However,
there are some barriers to the affirmation of this architecture, such as the binary
compatibility with legacy software.
This thesis aims to develop a VLIW processor, because, thanks to its high
throughput and low-power, VLIW processors fit the requirements of embedded
systems. The implemented processor should use a cache memory for access to main
memory. An assembler dedicated to the processor implemented will also be in order to
generate machine code compatible and in order to allow future changes in the
microarchitecture may be accompanied by changes in the generation of machine code.
Study was conducted on some existing VLIW Instruction Set Architectures (ISAs)
and microarchitectures in order to implement a soft-core VLIW processor according to
the state-of-the-art in a Xilinx FPGA platform.
KeyWords: Very Long Instruction Word (VLIW); Instruction Level Parallelism (ILP);
Multi-Cluster Processor; Low-power computing; ISA Implementation; Vex Assembler;
XI
Índice
Agradecimentos....................................................................................................... V
Resumo ................................................................................................................. VII
Abstract ................................................................................................................. IX
Índice de figuras ................................................................................................... XV
Índice de tabelas ................................................................................................. XIX
Lista de Acrónimos ............................................................................................ XXI
Introdução ........................................................................................ 1 CAPÍTULO 1
Enquadramento ....................................................................................... 1 1.1.
Motivação ............................................................................................... 2 1.2.
Objetivos ................................................................................................. 2 1.3.
Análise ............................................................................................. 5 CAPÍTULO 2
Introdução ............................................................................................... 5 2.1.
Evolução arquitetural .............................................................................. 5 2.2.
Arquitetura VLIW ................................................................................... 6 2.3.
Implementações softcore relevantes ....................................................... 7 2.4.
Técnicas de aumento de desempenho ..................................................... 8 2.5.
Pipelining de instruções ..................................................................... 9 2.5.1.
Paralelismo de unidades de execução................................................. 9 2.5.2.
Multi-cluster e Multiprocessador ..................................................... 10 2.5.3.
Instruction Set Architecture VEX ......................................................... 11 2.6.
Caches ................................................................................................... 12 2.7.
XII
Mapping Function ............................................................................ 13 2.7.1.
Algoritmo de substituição ................................................................ 16 2.7.2.
Política de escrita .............................................................................. 17 2.7.3.
Restrições da implementação ................................................................ 18 2.8.
Requisitos funcionais ............................................................................ 18 2.9.
Especificações de Hardware e Software ............................................... 19 2.10.
Metodologia de design e implementação .............................................. 20 2.11.
Design do Hc-Vex ......................................................................... 23 CAPÍTULO 3
Introdução ............................................................................................. 23 3.1.
Formato das instruções ..................................................................... 24 3.1.1.
Definição da instrução VLIW .......................................................... 25 3.1.2.
Codificação binária das sílabas ........................................................ 26 3.1.3.
Memória interna .................................................................................... 30 3.2.
Register File ..................................................................................... 31 3.2.1.
Cache ................................................................................................ 32 3.2.2.
Acesso à memória cache ....................................................................... 33 3.3.
Arquitetura do processador Hc-Vex ..................................................... 34 3.4.
Implementação do Hc-Vex ............................................................ 37 CAPÍTULO 4
Introdução ............................................................................................. 37 4.1.
Estágios de execução ............................................................................ 38 4.2.
Estágio de Fetch ............................................................................... 38 4.2.1.
Estágio de Decode ............................................................................ 43 4.2.2.
Estágio de Execute ............................................................................ 48 4.2.3.
Estágio WriteBack ............................................................................ 54 4.2.4.
XIII
Fluxo de instruções ............................................................................... 56 4.3.
Fluxo dos sinais de controlo ................................................................. 58 4.4.
Unidade Fetch ................................................................................... 59 4.4.1.
Unidade Decode ............................................................................... 59 4.4.2.
Unidade Execute, Memory Control e Control .................................. 59 4.4.3.
Unidade WriteBack .......................................................................... 59 4.4.4.
Unidade State Control ...................................................................... 60 4.4.5.
Fluxo de dados ...................................................................................... 61 4.5.
Unidade Fetch ................................................................................... 61 4.5.1.
Unidade Decode ............................................................................... 61 4.5.2.
Unidade Execute ............................................................................... 62 4.5.3.
Unidade Control ............................................................................... 62 4.5.4.
Unidade Memory Control ................................................................ 63 4.5.5.
Unidade WriteBack .......................................................................... 63 4.5.6.
Caches ................................................................................................... 64 4.6.
Cache de código ............................................................................... 65 4.6.1.
Cache de dados ................................................................................. 67 4.6.2.
Assembler ...................................................................................... 73 CAPÍTULO 5
Introdução ............................................................................................. 73 5.1.
Análise .................................................................................................. 73 5.2.
Design ................................................................................................... 75 5.3.
Implementação ...................................................................................... 76 5.4.
Analisador Léxico ............................................................................ 77 5.4.1.
Analisador sintático .......................................................................... 80 5.4.2.
Geração de código ............................................................................ 83 5.4.3.
XIV
Resultados experimentais .............................................................. 86 CAPÍTULO 6
Introdução ............................................................................................. 86 6.1.
Código C ............................................................................................... 86 6.2.
Compilação ........................................................................................... 87 6.3.
Assembling ........................................................................................... 89 6.4.
Execução das instruções ....................................................................... 90 6.5.
Conclusão .................................................................................... 101 CAPÍTULO 7
Conclusões .......................................................................................... 101 7.1.
Trabalho futuro ................................................................................... 102 7.2.
Bibliografia ......................................................................................................... 103
Anexo A .............................................................................................................. 107
Anexo B .............................................................................................................. 112
XV
Índice de figuras
Figura 2.1 - Organização VEX ............................................................................. 12
Figura 2.2 - Single cache [14] ............................................................................... 13
Figura 2.3 – Organização de cache de 3 níveis [14] ............................................. 13
Figura 2.4- Direct Mapped Cache [14] ................................................................. 14
Figura 2.5 - Fully associative cache ..................................................................... 15
Figura 2.6 - n-way set associative cache [14] ....................................................... 16
Figura 2.7 – Organização da cache n-way set associative [14] ............................ 16
Figura 2.8 - Xilinx Virtex-5 FPGA ...................................................................... 19
Figura 2.9 - Xilinx ISE Design Suite 13.2 ............................................................ 20
Figura 2.10- Diagrama da metodologia de design e implementação .................... 20
Figura 3.1- Layout genérico das sílabas ................................................................ 25
Figura 3.2 - Constituição da instrução VLIW ....................................................... 25
Figura 3.3 - Distribuição das silabas no estágio de execução ............................... 26
Figura 3.4 - Codificação binária das operações ADDCG, DIVS, SLCT e SLCTF
........................................................................................................................................ 27
Figura 3.5 - Codificação binária das operações CALL e GOTO .......................... 28
Figura 3.6 - Codificação binária das operações ICALL e IGOTO ....................... 28
Figura 3.7 - Codificação binária das operações BR e BRF................................... 28
Figura 3.8 - Codificação binária das operações RETURN e RFI ......................... 28
Figura 3.9 - Codificação binária das operações LOAD ........................................ 29
Figura 3.10 - Codificação binária das operações STORE..................................... 29
Figura 3.11 - Codificação binária da operação MOV ........................................... 29
Figura 3.12 - Codificação binária da operação NOP ............................................ 30
Figura 3.13 - Codificação binária da operação STOP .......................................... 30
Figura 3.14 - Hierarquia da memória [14] ............................................................ 31
Figura 3.15 – Organização Hc-Vex ...................................................................... 34
Figura 4.1 - Unidades que compõem os estágios do Hc-Vex ............................... 37
Figura 4.2 -Comportamento da unidade Fetch ...................................................... 40
Figura 4.3 - Máquina de estados da unidade State Control................................... 41
Figura 4.4 – Comportamento da unidade Decode ................................................. 46
Figura 4.5 - Comportamento da unidade Control ................................................. 49
Figura 4.6 - Comportamento da unidade Memory Control .................................. 51
XVI
Figura 4.7 - Comportamento da unidade Execute ................................................. 52
Figura 4.8 - Comportamento da unidade WriteBack ............................................ 54
Figura 4.9 – Fluxo de instruções ........................................................................... 56
Figura 4.10 - Paralelismo de hardware no interior da unidade Execute ............... 57
Figura 4.11 - Fluxo dos sinais de controlo ............................................................ 58
Figura 4.12 - Fluxo de dados ................................................................................ 61
Figura 4.13 - Interface das caches ......................................................................... 65
Figura 4.14 - Máquina de estados da cache de código .......................................... 65
Figura 4.15 – Decode de uma instrução da cache de código ................................ 67
Figura 4.16 – Árvore binária e comportamento do método de substituição Pseudo-
LRU ................................................................................................................................ 68
Figura 4.17 - Máquina de estados da cache de dados ........................................... 69
Figura 4.18 - Decode do endereço da cache de dados .......................................... 72
Figura 5.1 - Etapas de compilação do código ....................................................... 74
Figura 5.2 - Estrutura do Cel-Asm ........................................................................ 75
Figura 5.3 - Fluxograma do primeiro passo .......................................................... 76
Figura 5.4 - Fluxograma do segundo passo .......................................................... 76
Figura 5.5 - Definições do analisador léxico ........................................................ 77
Figura 5.6 – Análise léxica das operações de acesso à memória .......................... 77
Figura 5.7 - Análise léxica dos registos ................................................................ 78
Figura 5.8 - Análise léxica de dígitos.................................................................... 79
Figura 5.9 - Análise léxica de labels ..................................................................... 79
Figura 5.10 - Caracteres a ignorar ......................................................................... 79
Figura 5.11 - Tokens iguais aos caracteres ............................................................ 79
Figura 5.12 - Identificador de separação de instruções VLIW ............................. 80
Figura 5.13 – Função “yywrap()” ......................................................................... 80
Figura 5.14 - Tokens e variáveis temporárias ....................................................... 80
Figura 5.15 - Função “add_stmt()” ....................................................................... 81
Figura 5.16 - Estrutura do código intermédio ....................................................... 81
Figura 5.17 - Regras gramaticais .......................................................................... 82
Figura 5.18 - Geração de código ........................................................................... 83
Figura 5.19 - Fim de instrução VLIW ................................................................... 84
Figura 5.20 - Função "print_code()" ..................................................................... 85
Figura 6.1 - Código C usado como teste ............................................................... 87
XVII
Figura 6.2 - Assembly resultante da compilação do código C............................... 89
Figura 6.3 - Código nativo gerado pelo Assembler .............................................. 90
Figura 6.4 Execução da instrução "add $r0.1 = $r0.1, (-0x40)" ........................... 91
Figura 6.5 - Execução da instrução "call $l0.0 = _add" ........................................ 92
Figura 6.6 - Execução da instruçao "return $r0.1 = $r0.1, (0x0), $l0.0" .............. 93
Figura 6.7 - Execução da instrução "stw 0x20[$r0.1] = $l0.0" ............................. 94
Figura 6.8 - Execução da instrução "ldw $r0.4 = 4[$r0.5]" .................................. 95
Figura 6.9 - Comportamento da cache de código ................................................. 96
Figura 6.10 - Comportamento da cache de dados numa escrita ............................ 97
Figura 6.11 - Comportamento da cache de dados numa leitura ............................ 98
Figura 6.12 - Linhas de dados da cache set 0 ....................................................... 98
Figura 6.13 - Fim de execução do benchmark e duração da operação divisão ..... 99
Figura 6.14 - Número de ciclos necessários nos microcontroladores da família
MSP430 .......................................................................................................................... 99
XIX
Índice de tabelas
Tabela 3.1 - Immediate Switch .............................................................................. 24
Tabela 3.2 - utilização dos registos no Hc-Vex .................................................... 32
Tabela 3.3 - Características da cache de dados ..................................................... 33
Tabela 4.2 - Sinais e barramentos da unidade Fetch ............................................. 38
Tabela 4.1 - Sinais da unidade State Control ........................................................ 42
Tabela 4.3 - Sinais e barramentos da unidade Decode .......................................... 43
Tabela 4.4 - Sinais e barramentos do General Registers ....................................... 46
Tabela 4.5 - Sinais e barramentos do Branch Registers ........................................ 47
Tabela 4.6 - Sinais e barramentos da unidade Control.......................................... 50
Tabela 4.7 - Sinais e Barramentos da unidade Memory Control .......................... 51
Tabela 4.8 - Sinais e Barramentos da unidade Execute ........................................ 53
Tabela 4.9 - Sinais e Barramentos da ALU........................................................... 53
Tabela 4.10 - Sinais e Barramentos da unidade MUL .......................................... 53
Tabela 4.11- Sinais e barramentos da unidade WriteBack ................................... 54
Tabela 4.12 - Destinos da escrita da unidade WriteBack...................................... 63
Tabela 4.13- Registos internos da cache de código .............................................. 66
Tabela 4.14 - Sinais e barramentos da Cache de Código ...................................... 66
Tabela 4.15 - Registos internos da cache de dados ............................................... 70
Tabela 4.16 - Sinais e barramentos da cache de dados ......................................... 71
Tabela 0.1 - Semântica do Assembly Vex ............................................................ 110
Tabela 0.2 - Simbologia da tabela 5.1 ................................................................. 111
Tabela 0.1 - Operações aritméticas VEX ............................................................ 112
Tabela 0.2 - Operações de multiplicação VEX ................................................... 113
Tabela 0.3 - Operações lógicas e de seleção VEX .............................................. 113
Tabela 0.4 - Operações de Acesso à memória VEX ........................................... 114
Tabela 0.5 - Operações de controlo VEX ........................................................... 114
XXI
Lista de Acrónimos
BR Branch Registers
CISC Complex Instruction Set Computing
CPU Central Processing Unit
EPIC Explicitly Parallel Instruction Computing
FIFO First-In-First-Out
FPGA Field-programmable gate array
FU Functional Unit
GR General Registers
HDL Hardware Decription Language
ILP Intruction-Level Parallelism
ISA Instruction Set Architecture
LR Link Register
LRU Least Recently Used
LFU Least Frequently Used
PC Program Counter
RAM Random Access Memory
RISC Reduced Instruction Set Computing
SP Stack Pointer
VLIW Very Long Instruction Word
VEX Vliw EXample
1
CAPÍTULO 1
Introdução
Enquadramento 1.1.
Os avanços na tecnologia microeletrónica contribuíram significativamente para
que os computadores sejam uma parte integral da sociedade [1]. Tanto em sistemas
usados na indústria (sistemas utilizados nos pontos de venda, sistemas de controlo,
sistemas financeiros, sistemas de base de dados etc.) como nos sistemas utilizados pelo
consumidor comum (segurança de casa, cartões de crédito com chip, carro, telemóvel,
forno micro-ondas, frigorifico etc.) são frequentemente utilizados microprocessadores
ou microcontroladores como núcleo do sistema. Isto leva-nos à conclusão de que cada
passo no nosso dia-a-dia seja, provavelmente, influenciado por esta tecnologia.
Exemplos de classes de sistemas computacionais tais como desktop computers
(desenhado para utilização individual), servers (usado para correr pesados programas
para múltiplos utilizadores), supercomputers (computadores com a maior performance e
custo) e embebed computers (computadores embebido noutro dispositivo utilizados para
executar aplicações ou software pré-determinado) levam a um aumento de exigência na
performance dos processadores, na necessidade de baixo consumo, no baixo custo de
desenvolvimento e produção.
Para responder a estas exigências surgiram diferentes abordagens na organização
dos processadores. Os sistemas de computação com single general-purpose Central
Processing Unit (CPU), como é o caso dos processadores Intel Pentium III [2] e
Pentium 4 [3], deram lugar aos multiple general-purpose CPU, o caso dos
processadores Intel Core 2 duo [4] ,Core i7-860 [5] e Core i7-970 [6] com dois e quatro
e seis CPUs respetivamente. Também o instruction-level parallelism (ILP) foi abordado
como solução para responder a estas exigências devido ao elevado throughput que
permite obter [7]. Neste âmbito surgiram as arquiteturas very long instruction word
(VLIW), Superscalar e explicitly parallel instruction computing (EPIC) que, de forma
diferente, implementam o ILP.
2
Como exemplo de implementações VLIW existem os processadores TriMedia da
NXP [8], desenvolvidos para funcionar como processadores multimédia embebidos em
dispositivos eletrónicos de consumo geral, e o DAISY [9], que tem como
particularidade a compatibilidade com arquiteturas anteriores. Como exemplo de
implementação Superscalar existe o PowerPC 970 [10] e como exemplo de processador
EPIC existe o Intel Itanium [11].
Motivação 1.2.
As arquiteturas convencionais do tipo RISC e CISC tentam alcançar um
desempenho mais eficiente abordando o paralelismo mas de forma temporal, para isso
recorrem ao pipeline de instruções. Uma implementação pipelined implica um aumento
na complexidade, que se traduz num aumento do consumo energético e do custo de
implementação. A arquitetura VLIW por sua vez tenta alcançar maior performance
servindo-se do paralelismo físico na execução das instruções conseguida devido à
existência de múltiplas unidades de execução em paralelo, a ausência de pipeline
possibilita uma implementação mais simples. A deteção de existência de paralelismo e
de dependências nas instruções é realizada em compiling time, o que permite remover
do hardware a complexa implementação de deteção de hazards passando-a para o
compilador, reduzindo por isso a complexidade e o custo de implementação.
A arquitetura VLIW é interessante para sistemas embebidos pois é caracterizada
pelo seu baixo consumo energético e elevado throughput. O baixo consumo deve-se à
ausência de um escalonador dinâmico de tarefas e recursos, bastante dispendioso em
termos energéticos e de área, uma vez que todo o processo de escalonamento é feito
estaticamente pelo compilador. O elevado throughput é conseguido pela emissão
constante de operações em paralelo, obtendo assim uma carga de trabalho por instrução
muito elevada.
Objetivos 1.3.
Nesta dissertação pretende-se implementar, numa plataforma Xilinx Field-
programmable gate array (FPGA), um processador VLIW, de nome Hc-VEX. Para
isso, serão analisados processadores VLIW open-source, comparados os seus
3
Instruction Set Architecture’s (ISA’s) e as ferramentas de suporte existentes para cada
um deles. O ISA VLIW escolhido e a sua arquitetura serão implementados recorrendo à
linguagem HDL (Hardware Decription Language) Verilog, pois tal é requisito por
forma a facilitar a integração em projetos futuros desenvolvidos no grupo de sistemas
embebidos da Universidade do Minho.
O processador será implementado de forma paramétrica com recurso a
ferramentas Verilog para o efeito de modo a permitir que futuras alterações aos
parâmetros sejam facilmente realizáveis. Esta característica leva a que seja necessário
desenvolver também um Assembler dedicado ao processador implementado por forma a
permitir que o código máquina gerado se adapte às alterações efetuadas na
microarquitetura.
5
CAPÍTULO 2
Análise
Introdução 2.1.
Este capítulo começa por analisar a evolução arquitetural dos processadores,
segue-se uma abordagem aos temas relevantes para a realização da implementação. São
analisadas técnicas utilizadas para aumento de desempenho nos processadores e de que
forma as arquiteturas implementam essas técnicas, são também abordadas algumas
arquiteturas relevantes e implementações softcore existentes. Por fim é feita uma análise
ao funcionamento das memórias cache visto ser um requisito deste processador a
utilização de memória cache como meio de acesso à memória. Este capítulo tem por
função reunir o know-how necessário à implementação em FPGA de um processador
VLIW softcore. O processador implementado é apelidado de Hc-Vex.
Evolução arquitetural 2.2.
As microarquitecturas evoluíram por forma a aumentar o desempenho de
execução. As arquiteturas single-core evoluíram das baseadas em complex instruction
set computing (CISC) para as baseadas em reduced instruction set computing (RISC). A
arquitetura RISC explora o aumento de performance recorrendo à implementação da
técnica ILP pipelining de instruções. As arquiteturas superscalar e very long instruction
word (VLIW) surgiram como implementações ILP que recorrem à utilização de
múltiplas unidades de execução em paralelo [1].
A microarquitectura CISC foi motivada pelo alto custo da memória, o facto de
acondicionar uma maior complexidade por instrução permitiu que o código a executar
fosse menor, necessitando por isso de menos espaço para armazenamento em memória.
Os instruction set architecture’s (ISA’s) CISC são extensos e o tamanho das instruções
variável, isto permite codificar de forma longa as instruções complexas mantendo
pequenas as instruções básicas [7].
6
A microarquitectura RISC destaca-se da CISC pois tem como principal
característica o uso de um instruction set tão simples quanto possível, por forma a
possibilitar a programação do processador apenas com recurso a instruções simples.
Esta ideia partiu do cientista de computadores John Cocke da IBM Research após se
aperceber que muitas das instruções complexas CISC raramente eram utilizadas e que as
simples tinham uso frequente.
Tanto a microarquitectura VLIW como a superscalar implementam a técnica
instruction level parallelism (ILP) que consiste na execução de múltiplas instruções em
paralelo, mas com diferenças na forma como a identificação de paralelismo é
processada.
Arquitetura VLIW 2.3.
O conceito VLIW foi inventado por Joseph A. Fisher na década de 80 quando
integrava um grupo de investigação da Yale University, tendo integrado o grupo
Hewlett-Packard labs em 1990 onde deu continuidade ao seu trabalho com o conceito
instructin-level parallelism [12].
A arquitetura VLIW é caracterizada por utilizar microcodificação horizontal de
instruções e por consequência, utilizar instruções compostas por centenas de bits. A
microcodificação horizontal consiste na utilização de diferentes campos da instrução
para carregar os opcodes das diferentes unidades funcionais [13]. Por exemplo, no caso
de se pretender executar um programa escrito nas convencionais instruções de 32 bits,
estas teriam de ser unidas e compactadas de modo a formar instruções VLIW. Esta
compactação do código é efetuada em compiling time, levando a que o sucesso do
VLIW dependa fortemente da eficiente compactação do código.
O facto de ser no tempo de compilação que o paralelismo de instruções e a
manipulação de dados são especificados faz com que as tarefas de escalonamento e
sincronização dos recursos em run time sejam eliminadas. Isto reduz a complexidade do
design o que resulta numa melhor performance por parte do processador, um menor
consumo de energia, torna o design menos suscetível a erros de desenvolvimento,
passando a complexidade desta tarefa para o compilador.
7
Nos Processadores VLIW, várias instruções independentes são compactadas numa
instrução longa, que informarão a unidade de execução o que fazer. Uma desvantagem é
que o compilador não tem uma visão run time do código, o que o força a ser
conservador no que diz respeito ao scheduling das instruções. O compilador VLIW cria
as very long instructions e arbitra também todas as dependências. As instruções VLIW
são constituídas tipicamente por 4 a 8 instruções.
Num processador VLIW múltiplas unidades funcionais são utilizadas em
simultâneo, partilhando entre si o mesmo Register File, isto é possível porque a
instrução VLIW faz a sincronização da execução destas unidades. Esta característica
pode-se revelar um ponto contra, caso as unidades necessitem de diferentes latências
para concluir a execução. Dentro da mesma arquitetura VLIW diferentes
implementações podem não ter o formato binário das instruções compatível. A grande
vantagem do VLIW é a sua simplicidade no que diz respeito à estrutura do hardware e
Instruction Set [1].
Implementações softcore relevantes 2.4.
A arquitetura reduced instruction set computing (RISC) tem como principais
características a utilização de um instruction set limitado e com formato fixo, a
utilização de uma grande quantidade de registos e a utilização de um otimizado
instruction pipelining devido à simplicidade do instruction set, onde existem menos e
mais previsíveis operações realizadas por instrução [14].
Como exemplos de processadores Softcore RISC existem entre outros o
MicroBlaze [15] da Xilinx e o Nios II [16] da Altera, que são os disponibilizados pelos
fabricantes líder de mercado, e como tal, tidos como mais fiáveis, estes processadores
estão otimizados para uma utilização em dispositivos reconfiguráveis. Embora estes
processadores forneçam uma arquitetura sequencial eficiente, apenas expõem um
pequeno nível de extensibilidade, e sendo eles processadores RISC, não permitem
alterações ao seu issue-width (quantidade de instruções executadas simultaneamente).
Além disso, não são Open-Source, o que implicaria a compra de uma licença para a sua
utilização.
8
Como Open-Source, dentro dos processadores RISC existem, por exemplo, o
OpenRISC [17]e o LatticeMico32 [18], que, tal como os anteriores, têm um baixo nível
de configurabilidade, e não permitem alterar o issue-width. Temos também o MicroCore
[19] que, embora permita uma considerável configurabilidade da sua arquitetura, está
arquitecturalmente limitado a executar uma instrução de cada vez, pois é RISC.
Relativamente à arquitetura VLIW, a primeira implementação softcore de que há
referência é o Spyder [20], que consiste num processador com múltiplas unidades de
execução reconfiguráveis, mas com limitações na extensibilidade do tamanho das
instruções e do número de unidades funcionais.
Após o Spyder, surgiram outros projetos de softcores VLIW [21], mas a ausência
de extensibilidade manteve-se, ou seja, a configuração do issue-width e a alteração do
número de unidades funcionais continuava a ser um obstáculo.
Recentemente foi desenvolvido o ρ-VEX [22], um softcore de arquitetura
Harvard, baseado no ISA VEX (Vliw EXample, proposto por Fisher [12]) que permite
uma implementação multi-cluster, com possibilidade de uma implementação VEX
diferente em cada um dos Clusters. O ρ-VEX permite também configurar dentro de
cada cluster o número de unidades funcionais ALU e MUL, o número de registos de
propósito geral GR, e o número de branch registers BR.
Técnicas de aumento de desempenho 2.5.
Uma das principais técnicas usadas para aumento do desempenho de um
processador é o instruction-level parallelism (ILP). Diz-se que um sistema
computacional inclui ILP quando os programas executados por ele alcançam melhor
performance com recurso a múltiplas operações executadas simultaneamente [12].
Os programas comuns funcionam geralmente de forma sequencial, ou seja,
executam uma instrução após outra de acordo com a ordem definida no código. O ILP
permite que sejam executadas várias operações em simultâneo, ou até, que se troque a
ordem de execução. Existem diferentes implementações ILP, o pipelining de instruções
e a utilização de múltiplas unidades de execução em paralelo são exemplos de como
isso pode ser feito.
9
Pipelining de instruções 2.5.1.
No pipelining de instruções consegue-se aumentar o throughput de instruções com
recurso a um paralelismo “temporal” de operações, ou seja, embora seja efetuado o
fetch a uma instrução de cada vez, quando esta passa para o estágio seguinte de
execução, é efetuado o fetch de uma nova instrução e assim sucessivamente. Numa
organização de 4 estágios, fetch, decode, execute e writeback, consegue-se ter 4
instruções a ser processadas em simultâneo, uma em cada estágio. A ocorrência de
branches ou de dependências entre as instruções complica o design e uso do pipeline.
Este é o método utilizado nos processadores de arquitetura reduced instruction set
computing (RISC) com o objetivo de aumentar o desempenho.
Paralelismo de unidades de execução 2.5.2.
A execução paralela de instruções com recurso a múltiplas unidades de execução
em paralelo permite que várias operações sejam executadas em paralelo dentro de cada
um dos estágios. Na arquitetura superscalar a deteção da existência de paralelismo no
código executado é efetuada pelo hardware em run time, o que torna necessário a
existência de um escalonador dinâmico no hardware, este processo consiste num fetch
de múltiplas instruções em simultâneo e os problemas com dependências são resolvidos
em run time através de hardware específico. Outra característica comum no superscalar
é a utilização de pipelining. Estas características tornam esta arquitetura de design
complexo e de baixo rendimento energético.
Na arquitetura VLIW a identificação da existência de paralelismo nas instruções
do código a executar é efetuada pelo compilador, permitindo que as instruções a
executar em paralelo sejam enviadas para o processador embebidas em instruções de
grande dimensão geradas pelo compilador. Isto reduz consideravelmente a
complexidade do design pois não é necessário existir um escalonador dinâmico no
hardware, aumentando a eficiência energética.
O explicitly parallel instruction computing (EPIC) não é tanto uma arquitetura, é
mais uma filosofia, consiste numa evolução do VLIW que absorveu os melhores
conceitos do superscalar, quer isto dizer que embora seja o compilador a identificar a
existência de ILP no código, o hardware é mais complexo que o VLIW puro porque
10
possui também um escalonador dinâmico por forma a otimizar o desempenho. Com isto
consegue-se melhor desempenho que no VLIW puro, e com menos complexidade no
design comparativamente ao Superscalar [23].
Multi-cluster e Multiprocessador 2.5.3.
As áreas mais promissoras no projeto de sistemas de computação são as
implementações multi-cluster e multiprocessador, ou seja, agregados ou aglomerados de
processadores.
Podemos definir um multi-cluster como sendo um grupo de computadores
completos interconectados, trabalhando juntos como um recurso de computação
unificado, que cria a ilusão de constituir uma única máquina [14]. Uma das
desvantagens do multi-cluster é o facto de o custo de fabrico de um sistema composto
por N máquinas ser equivalente ao do fabrico de N máquinas independentes, ao passo
que no caso de uma máquina multiprocessador, com espaço de memória compartilhado
com N processadores o custo de fabrico equivale ao custo de uma única máquina
independente.
Outra desvantagem da configuração multi-cluster quando comparada com a
configuração multiprocessador é o facto da comunicação entre clusters ser feita
geralmente com recurso a um barramento de entrada e saída, (sendo a comunicação
intercluster responsável por reduzir a performance do processador) e a divisão de
memória (um cluster de N maquinas tem N memórias independentes e N cópias do
sistema operacional). Já a configuração multiprocessador não tem esta limitação pois os
vários cores estão ligados ao barramento de memória compartilhada, o que permite que
um único programa use quase toda a memória do computador.
Uma abordagem multi-cluster tem como característica o facto de permitir uma
implementação do ISA diferente em cada cluster, o que se revela uma vantagem quando
o pretendido é uma implementação extensível.
11
Instruction Set Architecture VEX 2.6.
O Vex é uma arquitetura load/ store o que significa que apenas as operações do
tipo Load e Store podem aceder à memória e que as operações de acesso à memória
apenas podem ter como destino ou fonte registos de propósito geral (GR), ou seja, não
existem operações do tipo memory-to-memory. Outra característica importante do VEX
é o modo de endereçamento das operações de acesso à memória, o modo como é
realizado o endereçamento é base-plus-offset, sendo a base um registo GR e o offset um
imediato do tipo short [12].
Neste subcapítulo são definidas as especificações técnicas de acordo com o ISA
escolhido, estas especificações vão influenciar a arquitetura e microarquitectura do
processador.
• O microprocessador vai operar com 32 bits (tamanho das sílabas que
constituem a instrução VLIW);
o O bus de dados e os registos internos têm de ser definidos para uma
arquitetura de 32 bits;
• A área de implementação deve ser mínima;
• Implementação single cluster com possibilidade de expansão;
• Issue width de 4 operações por cluster;
• 4 Unidades lógicas e aritméticas (ALU) por cluster;
• 2 Unidades de multiplicação (MUL) por cluster;
• 64 Registos internos com 32 bits cada;
o Isto constituirá o general-porpose register file (GR) do
microprocessador. Cada registo é endereçado usando 6 bits (de
6’b000000 para o registo R0 até 6’b111111 para o registo R63);
• 8 Registos internos com 1 bit cada;
o Isto constituirá o branch register (BR) do microprocessador. Cada
registo é endereçado usando 3 bits (de 6’b000 para o registo B0 até
6’b111 para o registo B7);
• 1 Link register de 32 bits;
o Utilizado para guardar o endereço para indirect branches;
• 1 Unidade memory load por cluster;
12
• 1 Unidade memory store por cluster;
• O instruction set inclui operações aritméticas, operações de load e store da
memória, operações de branch e operações de comparação;
A organização VEX é baseada na arquitetura Harvard, o que implica que
memória de dados e memória de código estejam separadas fisicamente, permitindo
assim diferente tamanho de dados e instruções.
Figura 2.1 - Organização VEX
Caches 2.7.
A memória de um computador é organizada de forma hierárquica. As memórias
de registos do processador (General Registers e Branch Registers) constituem as
memórias de mais alto nível (mais próximas do processador) e o disco rígido ocupa o
lugar de memória de mais baixo nivel. A cache segue-se às memórias de registos na
hierarquia, podendo esta preencher mais que um nível, normalmente denominados de
L1, L2 etc. O nível que se segue é preenchido pela memória principal. Todas estas
13
memórias são consideradas internas ao sistema. A hierarquia continua com as memórias
externas sendo normalmente a memória RAM a ocupar o nível seguinte [14].
Com o baixar da posição hierárquica da memória, diminui o custo por bit de
memória, aumenta a capacidade e diminui a velocidade de acesso. O ideal em termos de
performance seria usar apenas a memória de mais alto nível, mas devido elevado custo
por bit, é necessário usar também memórias de nível inferior. Uma solução para
equilibrar a relação performance/custo é organizar os dados e o código para que os
dados de trabalho estejam preferencialmente guardados na memória mais rápida.
O uso de Cache tem por função, não só permitir um acesso à memória a uma
velocidade próxima das memórias mais rápidas, como também disponibilizar mais
espaço de armazenamento a preços próximos das memórias mais baratas. A escolha das
características da cache a implementar tem uma influência direta nessa função. A
interação da Cache com o processador e a memória principal está ilustrada nas Figura
2.1 - Single cache [14] e Figura 2.2.
CPU Cache
Memória principal
Transferência de Word’s
Transferência de blocos
Rápido Lento
Figura 2.2 - Single cache [14]
CPUMais
rápido
Nivel 1(L1) cache
Rápido
Nivel 2(L2) cache
Menos rápido
Nivel 3(L3) cache
Lento
Memória principal
Figura 2.3 – Organização de cache de 3 níveis [14]
Mapping Function 2.7.1.
O facto de existirem menos linhas de cache do que blocos de memória leva a que
seja necessário o uso de um algoritmo para mapear os blocos da memória principal nas
14
linhas de cache existentes. Quando o processador pretende aceder a uma determinada
linha de memória podem ocorrer os seguintes eventos:
• Miss – Significa que a linha de memória a que o processador pretende
aceder não se encontra armazenada em cache, o que origina uma leitura da
memória principal por parte da cache;
• Hit – Significa que a linha de memória a que o processador pretende
aceder está armazenada em cache, sendo os dados disponibilizados de
imediato ao processador;
Na Figura 2.3 vemos como é feito o Mapeamento de uma cache do tipo Direct
Mapped. Neste caso a técnica de mapeamento é simples e tem custo de implementação
baixo, mas devido ao facto de existir apenas uma linha de cache para alocar cada bloco
da memória principal, pode ocorrer uma elevada taxa de misses. Caso o processador
faça consecutivamente o pedido de words que estejam alocadas em blocos diferentes da
memória principal, mas que sejam mapeadas na mesma linha de cache, irão ocorrer
misses consecutivos, o que torna o acesso à cache lento, pois será necessário realizar
leituras consecutivas de blocos da memória principal.
Memória cacheBlocos da memória de dados(tamanho igual ao da cache)
m li
nhas
btb
L0
L m-1
B0
B m-1
b – Comprimento do bloco em bitst – Comprimento do tag em bits
Figura 2.4- Direct Mapped Cache [14]
No outro extremo temos as caches do tipo Fully Associative (Figura 2.4) que
embora solucionem a desvantagem da cache Direct Mapped, pois permitem que um
bloco da memória principal seja alocado em qualquer linha da cache, tem como
15
desvantagem a complexidade do circuito necessário para examinar os tags de todas as
linhas da cache.
Memória cache
Um bloco da memória principal
m li
nhas
bt
L0
L m-1
b – Comprimento do bloco em bitst – Comprimento do tag em bits
b
Figura 2.5 - Fully associative cache
Por fim temos as caches do tipo n-way set associative (Figura 2.5) que,
conjugando pontos fortes de cada uma das caches anteriores, consegue diminuir as suas
desvantagens. Neste tipo de cache existe um número fixo de linhas (uma por set, dois
sets no mínimo) onde cada bloco da memória principal pode ser alocado, e a
complexidade necessária para verificar as tags da linha pretendida pelo processador é
bastante reduzida. A sua organização está explicada na Figura 2.6.
16
m li
nhas
bt
L0
L m-1
m li
nhas
bt
L0
L m-1
Memória cache – Set v-1
Memória cache – Set 0
B v-1
B 0
Figura 2.6 - n-way set associative cache [14]
Figura 2.7 – Organização da cache n-way set associative [14]
Algoritmo de substituição 2.7.2.
O algoritmo de substituição estabelece as regras com que novos dados são escritos
na cache em caso da mesma estar cheia, ou a posição pretendida ocupada. No caso de
17
uma cache do tipo direct mapped, não é usado qualquer algoritmo, pois apenas existe
uma linha de cache para alocar qualquer bloco em particular. Nos restantes casos já
existe necessidade de um algoritmo que determine a linha onde o bloco será escrito.
O algoritmo de substituição least recently used (LRU) consiste na substituição do
bloco que está em cache há mais tempo sem ser referenciado, a implementação é feita
com recurso a um contador em cada bloco de cache por forma a armazenar o histórico
de utilizações, sempre que um bloco de um determinado set é referenciada o contador
correspondente toma valor “0” e o contador do bloco correspondente dos restantes sets é
incrementado. O algoritmo pseuso least recently used (Pseudo-LRU) substitui, tal como
o LRU, o bloco de cache que está há mais tempo sem ser referenciada. A
implementação deste algoritmo implica a utilização de apenas um bit em cada bloco de
cache, e identifica o bloco a substituir recorrendo a uma árvore binária.
Comparativamente ao algoritmo Pseudo-LRU o LRU tem como desvantagem o facto de
ser de implementação mais complicada, necessita de mais bits e a lógica de atualização
é mais complexa [24].
Existem mais exemplos de algoritmos de substituição possíveis, tal como o first-
in-first-out (FIFO), que consiste na substituição do bloco que esta há mais tempo em
cache, ou o least frequently used (LFU) que consiste na substituição do bloco de cache
que menos frequentemente foi referenciado.
Política de escrita 2.7.3.
O que a cache contém é a cópia de partes da memória principal. Existem políticas
de escrita para salvaguardar a coerência entre o conteúdo da cache, e o conteúdo da
memória principal, ou seja, de modo a garantir que as alterações que se façam ao
conteúdo de uma posição de memória em cache, sejam salvaguardadas na memória
principal.
2.7.3.1. Write-through
Uma cache que use a política de escrita write-through garante a coerência entre os
dados dela mesma e da memória no próximo nível hierárquico recorrendo à escrita
simultânea em cache e na memória do próximo nível. Sempre que um bloco de cache é
18
alterado, é enviada uma ordem de escrita desse bloco para que a memória principal
correspondente seja também alterada. Como o acesso à memória principal é lento
comparativamente com o acesso à cache, estes acessos constantes à memória atrasam o
funcionamento da cache, o que se pode traduzir num atraso da performance do
processador [24].
2.7.3.2. Write-back
A política de escrita write-back usa um método distinto que reduz o número de
acessos à memória. A escrita em memória acontece apenas quando se pretende
substituir um bloco da cache que tenha sido previamente alterado, ou seja, que já não
esteja coerente com o bloco de memória do qual é cópia. O bloco da cache alterado é
então escrito na memória para posteriormente ser substituído pelo bloco da memória
pretendido [24].
Restrições da implementação 2.8.
A implementação será feita na linguagem HDL Verilog, de modo a que o código
fonte seja independente da família FPGA utilizada e para que facilmente seja integrado
em projetos futuros desenvolvidos no grupo de sistemas embebidos da Universidade do
Minho. Após análise de vários ISAs de arquitetura VLIW, o ISA escolhido para ser
utilizado na implementação do microprocessador foi o ISA do VEX (Vliw EXample)
devido à sua extensibilidade e existência de uma fiável toolchain que incorpora um
compilador.
Requisitos funcionais 2.9.
É requisito desta implementação que o acesso à memória principal seja efetuado
por meio de cache por forma a reduzir tempos de acesso otimizando a performance do
processador. O desenvolvimento de um assembler dedicado ao processador
implementado é também requisito resultante da não existência de acesso ao source code
do assembler que incorpora a toolchain existente para a arquitetura VEX desenvolvida
pela HP.
19
Especificações de Hardware e Software 2.10.
As FPGA tornaram-se numa ferramenta de uso frequente na criação de protótipos
de processadores porque, além de emularem a performance do hardware, permitem que
seja o utilizador a definir as suas funcionalidades. A configuração das FPGA é
geralmente feita recorrendo a uma hardware description language (HDL) [1]. A FPGA
utilizada será a Board Xilinx virtex5 (figura 2.8).
Figura 2.8 - Xilinx Virtex-5 FPGA
O software de apoio utilizado para desenvolvimento do microprocessador será o
Xilinx ISE Design Suite (figura 2.9) e a linguagem HDL utilizada será Verilog.
20
Figura 2.9 - Xilinx ISE Design Suite 13.2
Metodologia de design e implementação 2.11.
O diagrama da Figura 2.9 mostra a metodologia que será seguida na realização da
dissertação.
Figura 2.10- Diagrama da metodologia de design e implementação
Inicialmente é analisado o ISA e definido o formato das instruções, com base no
que se definiu são elaboradas as especificações da microarquitetura e recorrendo à HDL
21
Verilog, o processador é implementado em ambiente de simulação no software ISE. Em
paralelo com estas tarefas é desenvolvido um assembler por forma a gerar código
máquina a partir do código assembler gerado pelo compilador existente na toolchain
adotada. Desta forma tornar-se-á possível efetuar simulações de execução do
processador e proceder ao aperfeiçoamento da implementação realizada corrigindo
eventuais erros de implementação. A implementação aprimorada em ambiente de
simulação tem como finalidade a sua implementação em FPGA.
23
CAPÍTULO 3
Design do Hc-Vex
Introdução 3.1.
Este capítulo apresenta o design do Hc-Vex. O design é baseado na arquitetura
Harvard, ou seja, memória de dados e de código estão separadas fisicamente.
Inicialmente o design incide no formato das instruções, tendo sido opção usar o
template utilizado no ρ-Vex por respeitar o ISA VEX. Serão especificadas as
características das memórias internas, tanto os bancos de registos como as caches de
dados e código. Por fim será definida a organização do Hc-Vex.
O fator mais importante no design de um processador very long instruction word
(VLIW) é o compilador, isto porque é o compilador que resolve as dependências e faz o
escalonamento das instruções. Optou-se por ter como base da implementação do Hc-
Vex o instruction set architecture (ISA) VEX pelo facto de existir uma toolchain livre e
fiável desenvolvida pela Hewlett-Packard (HP) que inclui um compilador C. O
processador desenvolvido irá ser constituído por um cluster apenas, estando depois em
aberto uma implementação multi-cluster ou multiprocessador. A implementação do Hc-
Vex tem como relevantes os seguintes pontos:
• A linguagem HDL utilizada é verilog;
• A existência de cache de dados e de código;
• Utilização do Compilador existente na toolchain VEX desenvolvida pela
HP para gerar código assembly;
• Desenvolvimento de um assembler dedicado ao Hc-Vex visto não ter
existido acesso ao source code do assembler existente na toolchain;
• A implementação do Hc-Vex será efetuada de forma paramétrica com
recurso às técnicas disponíveis na linguagem Verilog para o efeito. Isto
associado ao facto de ter um assembler dedicado e open-source permite
que futuramente se efetuem alterações tanto na arquitetura como na
microarquitetura do processador.
24
Formato das instruções 3.1.1.
O ISA escolhido é constituído por 73 operações, o que torna necessário utilizar 7
bits para codificar todas as operações (27 = 128) previstas. Das 73 operações fazem
parte operações aritméticas, leitura e escrita na memória, comparação e operações
branch. A semântica destas operações está detalhada para consulta no Anexo A.
As instruções usadas para processar as operações do ISA são constituídas por 32
bits, os 7 bits de opcode combinados com os bits necessários para endereçar os registos
internos e, em certas operações, com imediatos embebidos na sílabacompletam os 32
bits da instrução. Esta instrução de 32 bits tem o nome de sílaba, por forma a diferenciar
a instrução VLIW das instruções de 32 bits.
A sílaba reserva os dois bits menos significativos para Meta-Data. O propósito
destes bits é identificar a posição da sílaba no interior da instrução VLIW, o bit F
informa se a sílaba é a primeira da instrução e o bit L se a sílaba é a última, embora não
vá ser utilizada esta funcionalidade, eles ficam reservados por forma a respeitar o layout
binário adotado. Está também reservado um campo de dois bits para o immediate
switch, que tem como propósito identificar o tipo de imediato.
Tabela 3.1 - Immediate Switch
Tipo de imediato Tamanho Immediate switch
Sem imediato - 0 0
Short immediate 9 bits 0 1
Branch offset immediate 12 bits 1 0
Long immediate 32 bits 1 1
O VEX standard define o uso de 3 tipos de imediatos, o short immediate com 9
bits, o branch offset immediate com 24 bits, e o long immediate com 32 bits. O layout
binário das sílabas utilizado altera o tamanho do branch offset immediate de 24 para 12
bits por forma a explorar o layout de forma mais eficiente. Para identificar o tipo de
imediato presente nas instruções, o campo immediate switch pode ter as combinações
referidas na Tabela 3.1
25
Para organização binaria das sílabas, será utilizado o layout utilisado no ρ-vex
(Figura 3.1) uma vez que se adequa ao pretendido, pois respeitam o ISA VEX, e porque
não é requisito desta dissertação a criação de um layout binário.
7 bits Opcode 0 0 6 bits destino GR 6 bits origem GR 1 6 bits origem GR 2 3 bits destino BR L F
7 bits Opcode 0 1 6 bits destino GR 6 bits origem GR 1 9 bits short immediate L F
7 bits Opcode 1 0 6 bits link register 12 bits branch offset immediate L F3 bits destino BR
7 bits Opcode 1 1 6 bits destino GR 6 bits origem GR 1 10 bits long immediate [0-9] F
Identificador Long_Imm L F-22 bits long immediate [10-31]
Figura 3.1- Layout genérico das sílabas
Definição da instrução VLIW 3.1.2.
Estando definido o template para o layout binário sílabas, fica então a faltar
definir como é constituída a instrução VLIW. Esta instrução é constituída por 128 bits e
resulta da concatenação de 4 sílabas. O modo como as sílabas são distribuídas na
instrução VLIW segue algumas regras (Figura 3.2). As 4 sílabas que compõem a
instrução são identificadas com o índice 0,1,2 e 3, a sua posição no interior na instrução
é fixa e o tipo de operações que podem executar difere consoante a sílaba.
Silaba 3
ALU / MEM
Silaba 2
ALU / MUL
Silaba 1
ALU / MUL
Silaba 0
ALU / CTRL
0316395127
Figura 3.2 - Constituição da instrução VLIW
A sílaba_0 está alocada entre os bits 0 e 31, a sílaba_1 entre os bits 32 e 63, a
sílaba_2 entre os bits 64 e 95 e finalmente a sílaba_3 entre os bits 96 e 127.As
operações do tipo logico e aritmético (ALU) podem ser executadas em todas as sílabas
porque existem 4 unidades ALU paralelas, as operações de multiplicação (MUL) apenas
podem ser processadas pelas sílabas 1 e 2 pelo facto de só existirem 2 unidades MUL,
as operações de acesso à memória (MEM) são executadas apenas pela sílaba 3 e as
instruções que geram saltos na execução do código (CTRL) são executadas apenas pela
26
sílaba 0. Esta ordem das operações no interior da instrução está definida pela arquitetura
VEX (figura 3.3).
Figura 3.3 - Distribuição das silabas no estágio de execução
Codificação binária das sílabas 3.1.3.
Embora a maior parte das sílabas respeitem o layout genérico das sílabas, existem
algumas operações que exigem uma codificação binária diferente do modelo standard,
isto acontece porque algumas operações endereçam registos que não estão no template,
binário, como é o caso do endereço fonte de registos BR. Segue-se a análise da
codificação binária das operações que constituem o ISA
3.1.3.1. Instruções do tipo ALU e MUL
Existem 42 tipos de operações destinadas à ALU e 11 destinadas à unidade MUL.
Como foi dito anteriormente, todas as 4 sílabas podem conter operações ALU e apenas
as sílabas 1 e 2 podem conter operações MUL. O template binário das sílabas adotado
não prevê todas as operações da ALU, é o caso das operações “ADDCG”, “DIVS”,
“SLCT” e “SLCTF”. Estas operações utilizam com 3 operandos fonte, 2 provenientes
do GR e um do BR. Dado não existir espaço reservado nos 32 bits da sílaba para
endereçar o registo proveniente do BR, a solução adotada foi a definição de um opcode
de 4 bits único para cada uma destas operações, permitindo assim utilizar os 3 bits
menos significativos restantes para endereçar o registo BR fonte (Figura 3.3).
27
4 bits Opcode 0 0 6 bits destino GR 6 bits origem GR 1 6 bits origem GR 2 3 bits destino BR L F3 bits
origem BR Figura 3.4 - Codificação binária das operações ADDCG, DIVS, SLCT e SLCTF
Segundo a semântica definida no VEX (Anexo A) as operações “SLCT” e
“SLCTF” admitem como operandos registos e a possibilidade de utilizar um imediato.
Nesta implementação VEX apenas será admitida a utilização de registos. Não está
contemplada a possibilidade de utilizar operandos do tipo imediato porque o campo do
imediato sobrepõe-se ao campo dos bits que endereçam o destino para o registo BR
resultante da operação. A resolução desta limitação encontra-se em aberto, e devido ao
facto de se tratar de uma implementação paramétrica, uma alteração ao código é
perfeitamente realizável sem comprometer o resto do código.
As instruções que utilizam a semântica tipo II (Anexo A) têm a particularidade de
tanto terem como destino do resultado da operação um registo GR como um registo BR.
A identificação do destino de escrita é feita recorrendo a uma análise dos bits reservados
para o endereçar o registo de destino GR, no caso do endereço de destino ser zero o
destino do resultado das operações é um registo BR.
3.1.3.2. Instruções do tipo CTRL
As instruções do tipo CTRL apenas são possíveis executar na sílaba 0, pois, e tal
como define o standard VEX, apenas existe uma unidade Control. Em todas as
instruções deste tipo é respeitado o template binário das sílabas.
As operações” GOTO” e “IGOTO” provocam ambas um salto indireto absoluto,
com a particularidade de, caso se trate da operação “GOTO”, o valor do program
Counter é atualizado com o valor do Branch Offset Immediate, caso se trate da operação
“IGOTO”, o program counter é atualizado com o conteúdo do Link Register.
As operações “CALL” e “ICALL” provocam ambas também um salto indireto
absoluto e, no que se refere à atualização do valor do program counter, seguem a
mesma regra que as operações “GOTO” e “IGOTO” respetivamente. A diferença entre
estas e as anteriores é a de que o valor do Link Register é atualizado com o valor “PC +
1” por forma a que, quando processada a instrução “RETURN”, esteja garantido o
regresso do código à instrução imediatamente seguinte à que originou o salto.
28
7 bits Opcode 1 0 12 bits branch offset immediate L F
Figura 3.5 - Codificação binária das operações CALL e GOTO
7 bits Opcode 0 0 L F6 bits endereço link register
Figura 3.6 - Codificação binária das operações ICALL e IGOTO
No caso das operações “BR” e “BRF”, quando a condição de salto é avaliada
como verdadeira, no caso da operação BR, ou como falsa, no caso da operação BRF, o
valor do program counter é atualizado com o valor do Branch Offset Immediate.
3 bits destino BR7 bits Opcode 1 0 12 bits branch offset immediate L F
Figura 3.7 - Codificação binária das operações BR e BRF
As operações “RETURN” e “RFI” efetuam o pop da stack frame e o program
counter toma o valor presente no registo Link Register. O stack pointer reside na
posição $R0.1.
12 bits branch offset immediate7 bits Opcode 1 0 L F6 bits endereço link register
Figura 3.8 - Codificação binária das operações RETURN e RFI
3.1.3.3. Instruções do tipo MEM
As instruções do tipo MEM apenas são possíveis executar na sílaba 3, pois, e tal
como define o standard VEX, apenas existe uma unidade Memory Control.
Relativamente ao layout da sílaba, existem diferenças relativamente ao modelo
genérico.
Tal como prevê o modelo, as instruções do tipo Load e Store contêm também um
campo para um registo imediato. Este imediato consiste num offset que será somado ao
conteúdo do registo de destino. Mas, embora se trate de um offset e o campo de
imediatos lhe atribua o tipo “sem imediato”, trata-se de um imediato curto, por forma a
garantir que a instrução não provoque um overflow da sílaba.
O que diferencia os dois tipos de instruções é a localização do espaço reservado
ao registo fonte e de destino, estando as instruções Load de acordo com o template, mas
já as Store diferem, tal está definido na semântica das operações, estando os 6 bits de
29
endereçamento do registo fonte no espaço onde segundo o template seria de
endereçamento do registo de destino, e o mesmo acontece com campo de
endereçamento do destino que ocupa o lugar destinado ao endereçamento do registo
fonte.
7 bits Opcode 0 0 6 bits destino GR 6 bits origem GR 9 bits short immediate L F
Figura 3.9 - Codificação binária das operações LOAD
7 bits Opcode 0 0 6 bits origem GR 6 bits destino BR 9 bits short immediate L F
Figura 3.10 - Codificação binária das operações STORE
3.1.3.4. Operação MOV
Para esta implementação VEX, e por forma a criar uma maior compatibilidade
com o assembly gerado pelo compilador VEX utilizado, foi alterada a instrução MOV.
Esta instrução surge no assembly gerado pelo compilador HP como sendo do tipo
registo – imediato e não apenas registo – registo como prevê o ISA VEX. A solução
adotada para que o assembly gerado fosse possível utilizar, foi, além de preparar o
assembler desenvolvido para esta nova funcionalidade da instrução, preparar o
processador por forma a interpretar corretamente o novo binário devolvido pelo
assembler.
Assim sendo, a operação MOV atual, para além de permitir a movimentação de
dados entre registos, permite também mover para os registos GR um imediato de 9 bits.
Especial atenção para o facto de este imediato ser do tipo signed, ou seja, o bit mais
significativo é bit de sinal, o que leva a uma gama de valores entre “-255” e “255”. O
imediato é do tipo curto por forma a respeitar o modelo genérico da codificação das
sílabas.
7 bits Opcode 0 0 6 bits destino GR 6 bits origem GR L F
7 bits Opcode 0 1 6 bits destino GR 9 bits short immediate L F
Figura 3.11 - Codificação binária da operação MOV
30
3.1.3.5. Outras instruções
A operação NOP faz com que a sílaba não altere o valor de nenhum registo
durante o seu percurso pelas unidades do processador. É utilizada para preencher as
sílabas da instrução VLIW que não esteja a ser utilizada para executar nenhuma outra
operação.
A operação STOP provoca uma paragem imediata no funcionamento do
processador. Isto acontece em qualquer que seja a sílaba que a operação seja
processada. As unidades funcionais permanecem inativas até que aconteça um reset do
processador.
0 0 0 0 0 0 0 L F
Figura 3.12 - Codificação binária da operação NOP
0 0 1 1 1 1 1 L F
Figura 3.13 - Codificação binária da operação STOP
Memória interna 3.2.
A memória de um computador é organizada de forma hierárquica, sendo as
memórias de registos do processador (General Registers e Branch Registers) as de mais
alto nível (mais próximas do processador). A cache constitui o nível seguinte na
hierarquia, podendo esta preencher mais que um nível, normalmente denominados de
L1, L2 etc. (no Hc-Vex apenas será implementado um nível de cache). O nível que se
segue é preenchido pela memória principal. Todas estas memórias são consideradas
internas ao sistema. A hierarquia continua com as memórias externas sendo
normalmente um disco rígido a ocupar o nível seguinte.
31
Figura 3.14 - Hierarquia da memória [14]
Com o baixar da posição hierárquica da memória, diminui o custo por bit de
memória, aumenta a capacidade e diminui a velocidade de acesso. O ideal em termos de
performance seria usar apenas a memória de mais alto nível, mas devido elevado custo
por bit, somos obrigados a usar também memórias de nível inferior. Uma solução para
equilibrar a relação performance/custo é organizar os dados e o código para que a
memória necessária esteja normalmente guardada na memória de acesso mais rápido
[24].
Existem dois tipos de memória internos no Hc-Vex, o register file e a cache
Register File 3.2.1.
Do tipo register file existem duas memórias diferentes, o general-porpose
Register (GR) e o branch register (BR). Os registos GR são constituídos por 64 registos
de 32 bits. Destes 64 registos fazem parte os registos StackPointer, LinkRegister, entre
outros de caracter especial. A outra memória do tipo register file é a BR, esta por sua
vez é constituída por 8 registos de 1bit cada. A tabela seguinte ilustra a organização
destas duas memórias.
32
Tabela 3.2 - utilização dos registos no Hc-Vex
Registo Classe Utilização
General registers
$r0.0 Constante Sempre zero
$r0.1 Especial Stack pointer
$r0.2 Rascunho Struct return pointer
$r0.3 - $r0.10 Rascunho Valores argumentos /
devolvidos
$r0.11 - $r0.56 Rascunho Temporário
$r0.57 - $r0.62 Preservado Temporário
$r0.63 Especial Link register
Branch registers
$b0.0 – $b0.7 Rascunho Temporário
Cache 3.2.2.
Uma máquina Von Newmann é caracterizada pelo facto de guardar numa única
memória de dados e instruções, enquanto numa máquina Harvard estas duas memórias
estão separadas fisicamente, sendo a memória de instruções do tipo read only, e a
memória de dados do tipo read/write.
O design do Hc-Vex segue a organização VEX, esta é baseado na arquitetura
Harvard implicando que memória de instruções e memória de dados estejam separadas
fisicamente. Existem por isso duas caches diferentes, a cache de dados e a cache de
instruções. A cache de dados implementada tem 64 linhas de dimensão, a escolha na
dimensão de ambas as caches deveu-se a questões relacionadas com a velocidade de
sintetização no software, como a implementação é paramétrica a dimensão da cache é
configurável. Cada linha de cache aloca 8 words. O tamanho da word é 32 bits, ou seja,
cada linha de cache possui 256 bits, correspondentes às 8 words que a constituem. A
cache de instruções tem, tal como a cache de dados, 64 linhas de dimensão, cada linha
tem 128 bits de tamanho e aloca apenas uma instrução VLIW. Caso se pretenda, a
dimensão das caches pode ser alterada, isto porque a implementação do processador é
paramétrica.
33
Tabela 3.3 - Características da cache de dados
Parâmetros Características
Word Tipicamente o tamanho da word é igual ao
tamanho da instrução, no caso do Hc-Vex , a
instrução tem tamanho de 128 bits, mas esta é
desmembrada em 4 sílabas de 32 bits cada, que é
também o tamanho da word.
Unidade endereçável A unidade endereçável é a word, mas
internamente o processador faz o decode da
word, e servindo-se de concatenação, permite a
alteração de um byte apenas.
Unidade de transferência A unidade de transferência é a linha inteira da
cache, ou seja, 256 bits no caso da cache de
dados, e 128 no caso da cache de instruções.
Método de acesso à cache 4-way set associative
Política de escrita write-back policy
Número de níveis de cache Um
Política de substituição Pseudo Least recently used (LRU)
Acesso à memória cache 3.3.
A maior unidade da memória de dados endereçável a partir do processador tem de
tamanho 32 bits, o nome dado a esta secção da memória é word. De modo a ser possível
o acesso independente a cada word, existe um endereço distinto para cada uma. Este
endereço permite localizar na memória a posição em que se pretende efetuar uma escrita
ou leitura. O número de bits (b) necessários para distintamente endereçar todas as
posições de memória do micro, depende do número de words da memória (N).
No processo de escrita em memória por parte do processador, existem 3 passos
importantes a seguir:
• Passar do processador para o registo Memory Data Register (MDR) a
word que se pretende guardar em memória.
• Passar do processador para o registo Memory Address Register (MAR) o
endereço da memória onde se pretende guardar a word.
34
• Enviar um sinal de escrita para a memória, deste modo a memória é
informada que a word presente no registo MDR deve ser guardada no
endereço presente no registo MAR.
No processo de leitura os passos a seguir são os seguintes:
• Passar do processador para o registo MAR o endereço da memória do qual
se pretende ler a word.
• Enviar um sinal de leitura para a memória, de modo a informar a memória
da intenção do processador em ler a word correspondente ao endereço
presente no registo MAR.
• Passar da memória para o registo MDR a word pretendida pelo
processador, de modo a que este a possa aceder.
Arquitetura do processador Hc-Vex 3.4.
Figura 3.15 – Organização Hc-Vex
A configuração do Hc-Vex consiste na implementação de apenas 1 cluster VEX,
existe a possibilidade de expandir o número de clusters mas nesta implementação não
35
ficam para já garantidas as condições necessárias, que seria a existência de um
barramento input/output que possibilitasse a comunicação entre clusters.
A organização single-cluster do Hc-Vex (Figura 4.4) consiste em 4 estágios,
Fetch, Decode, Execute e WriteBack. O Hc-Vex é composto por 4 unidades ALU, 2
unidades MUL, 1 unidade CTRL, 1 unidade MEMCTRL, 1 memória GR com 64
registos de 32 bits e 1 memória BR com 8 registos de 1 bit. O acesso à memória
principal é feito com recurso a caches.
O Hc-Vex respeita a arquitetura Harvard, esta organização é ideal para
arquiteturas VLIW pois permite que instruções e dados tenham tamanhos diferentes.
Uma Organização Von Neumann implicaria que código e dados partilhassem a mesma
memória, o que traria problemas, pois na arquitetura VLIW o tamanho das instruções é
diferente do tamanho dos dados.
37
CAPÍTULO 4
Implementação do Hc-Vex
Introdução 4.1.
Neste capítulo é relatada a implementação do Hc-Vex. São descritas com detalhe
todas as unidades funcionais que constituem o processador, assim como o seu
funcionamento e os interfaces que cada uma possui com outras unidades. Segue-se uma
análise do fluxo de sinais de controlo, instruções e dados entre todas as unidades que
constituem o Hc-Vex. Por fim é descrita a implementação das caches de dados e
instruções, são detalhadas as máquinas de estado que controlam o seu funcionamento,
os sinais e barramentos de interface com o processador e memória principal.
Figura 4.1 - Unidades que compõem os estágios do Hc-Vex
O Hc-Vex possui uma memória dedicada à memória de código, a leitura das
instruções armazenadas na memória é feita pela unidade Fetch. As instruções
armazenadas na memória são instruções VLIW com 128 bits, a unidade Fetch, além de
38
fazer a leitura da instrução VLIW, decompõe-na em 4 instruções de 32 bits, as quais são
apelidadas de sílabas. As 4 sílabas são disponibilizadas à unidade Decode, que procede
à descodificação da informação presente nas sílabas. Valores de Opcode, endereços
fonte e de destino dos registos GR e BR e também de imediatos que possam vir
embebidos na sílaba são descodificados nesta unidade por forma a permitir que as
unidades seguintes interpretem a instrução e obtenham os recursos necessários à
execução da operação que lhes foi designada.
Estágios de execução 4.2.
Neste tópico são identificados e detalhados os sinais e barramentos que interligam
as unidades, são também descritos alguns registos internos relevantes para a
interpretação do funcionamento de cada uma das unidades. O comportamento das
unidades mediante o seu estado de funcionamento é também abordado neste tópico
Estágio de Fetch 4.2.1.
Composto pela unidade funcional Fetch, e pelas unidades Program Counter e
State Control, este estágio tem por função ler da memória de dados a instrução VLIW
apontada pelo program counter. Como a instrução recebida tem de tamanho 128 bits, a
unidade Fetch procede ao desmembramento da instrução VLIW em 4 instruções de 32
bits cada. As sílabas são colocadas nos barramentos de saída da unidade de forma a
permitir a sua leitura por parte do estágio de Decode. O sinal FetchOk toma o valor ‘1’
de modo a informar a unidade State Control que o processo de fetch terminou.
A Tabela 4.2 detalha os sinais e barramentos da unidade Fetch e seu interface com
as restantes unidades do microprocessador.
Tabela 4.1 - Sinais e barramentos da unidade Fetch
Nome do sinal Entrada /
saída
Unidade de
interface Descrição
InstrReady Entrada Instruction
Cache
Informação de que a instrução pretendida pelo Fetch,
foi disponibilizada pela Cache de instruções
FetchOk Saída Decode Conclusão do Fetch da Instrução
39
Nome do sinal Entrada /
saída
Unidade de
interface Descrição
InstrRequest Saída Cache de
instruções Pedido de leitura à Cache de instruções
Instr[127:0] Entrada Cache de
instruçoes Instrução VLIW de 128 bits recebida pelo Fetch
Pc[9:0] Entrada Program
Counter Valor do Program Counter
Address[31:0] Saída Cache de
instruções Endereço da instrução a ler da Cache de Instruções
Syllable_0[31:0
] Saída Decode
Instrução de 32 bits correspondente aos bits 0 a 31 da
instrução VLIW
Syllable_1[31:0
] Saída Decode
Instrução de 32 bits correspondente aos bits 32 a 63
da instrução VLIW
Syllable_2[31:0
] Saída Decode
Instrução de 32 bits correspondente aos bits 64 a 95
da instrução VLIW
Syllable_3[31:0
] Saída Decode
Instrução de 32 bits correspondente aos bits 96 a 127
da instrução VLIW
State[1:0] Entrada State Control Estado de funcionamento da unidade Fetch
No estado de funcionamento “RESET” o Fetch inicializa todos os registos a zero.
No estado “WAITING” o barramento Address[31:0] (endereço da instrução a ler da
memória de código) é atualizado com o valor do registo program counter. No estado
“EXECUTE” o sinal InstrRequest toma o valor “1” por forma a fazer um pedido de
leitura à memória de código. Quando sinal de entrada InstrReady for “1” significa que a
instrução VLIW requerida está disponível no barramento Instr[127:0], de imediato a
unidade Fetch decompõe a instrução recebida em quatro sílabas e coloca o sinal fetchOk
com valor “1” por forma a informar a unidade State Control da conclusão do processo
de fetch.
40
Figura 4.2 -Comportamento da unidade Fetch
4.2.1.1. Unidade Program Counter
Esta unidade é responsável por armazenar o registo program counter bem como a
lógica de actualização do mesmo. Este registo contém o endereço da posição da
memória de código de onde se pretende ler a próxima instrução. Este valor é facultado à
unidade Fetch através do barramento Pc[31:0] pois é na unidade Fetch que o acesso à
memória de código é efetuado.
O calculo do valor que o registo program counter irá assumir no final da execução
da instrução atual é feito na unidade Control e é através do barramento Pc_GoTo[31:0]
que o novo valor transita da unidade Control para a unidade Program Counter. A
atualização para o novo valor acontece quando autorizada pela unidade State Control
por via do sinal UpdatePc.
Barramentos Entrada /
Saída
Unidade de
interface Descrição
Pc_GoTo Entrada Control Novo valor do program counter
UpdadePc Entrada State Control Autorização para atualização do program
counter
Pc Saída Fetch
Control Valor atual do program counter
41
4.2.1.1. Unidade State Control
A unidade State Control tem por função controlar o estado de funcionamento
dos 4 estágios do cluster.
Figura 4.3 - Máquina de estados da unidade State Control
A transição para o estado de funcionamento “RESET” acontece,
independentemente do estado de funcionamento atual, caso se verifique um sinal de
reset ou stop, não se verificando estes sinais o estado inicial é o “FETCH”. Neste estado
a unidade Fetch é colocada no estado “EXECUTE” e todas as outras no estado
“WAITING”. Este tipo de controlo do estado de funcionamento das unidades do
processador é transversal a todos os estados da unidade State Control, ou seja, as
unidades que assumem o estado de funcionamento “EXECUTE” serão, tal como
referido, a unidade Fetch quando o estado for “FETCH”, a unidade Decode no estado
“Decode”, as unidades que constituem o estágio Execute quando o estado for
“EXECUTE” e a unidade WriteBack quando o estado for “WRITEBACK”, todas as
outras unidades terão estado de funcionamento “WAITING”.
As condições de transição entre estados são FetchFinish igual a “1” para
transição do estado “FETCH” para “DECODE”, OperandsReady igual a “1” para
transição do estado “DECODE” para “EXECUTE”, ExecuteDone igual a “1” para
transição do estado “EXECUTE” para “WRITEBACK” e finalmente WbFinish igual a
“1” para transição do estado “WRITEBACK” para o estado “FETCH”. Mais nenhuma
transição entre estados está contemplada.
42
Para garantir uma correta execução do código, o valor do program counter é
atualizado pela unidade State control no estado “WRITEBACK”. Desta forma, assim
que a unidade Fetch entre em modo “EXECUTE”, o valor do program counter está
atualizado com o endereço da próxima instrução alvo de fetch. A máquina de estados da
Figura 4.2 ilustra o funcionamento anteriormente descrito. A Tabela 4.1 descreve com
pormenor os sinais e registos da unidade State Control.
Tabela 4.2 - Sinais da unidade State Control
Nome do sinal Entrada /
Saída
Unidade de
interface Descrição
WbFinish Entrada WriteBack Informação de conclusão da escrita por parte da
unidade WriteBack.
InstrReady_m Entrada Cache de
instruções
Informação de que a nova instrução VLIW pretendida
está disponível para fetch.
State_f Saída Fetch Sinal que controla o estado de funcionamento da
unidade Fetch.
FetchFinish Entrada Fetch Informação de que o fetch da instrução VLIW está
concluído.
State_d Saída Decode Sinal que controla o estado de funcionamento da
unidade Decode.
OperandsReady Entrada Decode Conclusão do decode das sílabas.
State_ex Saída Execute Sinal que controla o estado de funcionamento da
unidade Execute.
ExecuteDone Entrada Execute
Informação de que as unidades Execute, Control e
Memory Control concluíram a execução das operações
correspondentes e que os resultados estão disponíveis
nos barramentos de saída.
State_wb Saída WriteBack Sinal que controla o estado de funcionamento da
unidade WriteBack.
State Registo Estado de funcionamento da unidade State Control.
43
Estágio de Decode 4.2.2.
Constituído pela unidade Decode e pelos bancos de registos GR e BR, este estágio
tem como função decifrar as sílabas que recebe do estágio de Fetch, ou seja, identificar
a informação presente nas sílabas, tais como opcode das instruções, tipo de imediato e
seu valor, endereço dos operandos a escrever e a ler do banco de registos GR e BR. A
unidade Decode faz também a leitura dos registos GR e BR necessários à execução da
instrução, usando os endereços descodificados a partir da sílaba recebida. Estes valores
(endereços de destino e operandos) são enviados para o estágio de Execute.
4.2.2.1. Unidade Decode
A Tabela 4.3 contém os sinais e barramentos desta unidade faz uma descrição das
suas especificidades.
Tabela 4.3 - Sinais e barramentos da unidade Decode
Sinal / Barramento Entrada /
saída
Unidade de
interface Descrição
State[1:0] Entrada State
Control Estado de funcionamento da unidade Decode
Syllable_0[31:0] Entrada Fetch Instrução de 32 bits correspondente aos bits 0 a
31 da instrução VLIW
Syllable_1[31:0] Entrada Fetch Instrução de 32 bits correspondente aos bits 32 a
63 da instrução VLIW
Syllable_2[31:0] Entrada Fetch Instrução de 32 bits correspondente aos bits 64 a
95 da instrução VLIW
Syllable_3[31:0] Entrada Fetch Instrução de 32 bits correspondente aos bits 96 a
127 da instrução VLIW
Data_S#R1[31:0] Entrada General
Registers
Operando referente ao registo 1 da sílaba
correspondente
Data_S#R2[31:0] Entrada General
Registers
Operando referente ao registo 2 da sílaba
correspondente
Data_S#Br Entrada Branch
Registers Branch bit referente à sílaba correspondente
44
Sinal / Barramento Entrada /
saída
Unidade de
interface Descrição
OperandsReady Saída State
Control Conclusão do processo de decode
Opcode_0[6:0] Saída Execute e
Control Opcode correspondente à operação da sílaba 0
Opcode_1[6:0] Saída Execute Opcode correspondente à operação da sílaba 1
Opcode_2[6:0] Saída Execute Opcode correspondente à operação da sílaba 2
Opcode_3[6:0] Saída
Execute e
Memory
Control
Opcode correspondente à operação da sílaba 3
Address_S#R1[5:0] Saída General
Registers Endereço do registo 1 da sílaba correspondente
Address_S#R2[5:0] Saída General
Registers Endereço do registo 2 da sílaba correspondente
Address_S#Br[2:0] Saída Branch
Registers Endereço do branch bit da sílaba correspondente
AddressDest_S#Gr[5:
0] Saída
General
Registers
Endereço de destino do registo resultante da
operação da sílaba correspondente
AddressDest_S#Br[2:0
] Saída
Branch
Registers
Endereço de destino do branch bit resultante da
operação da sílaba correspondente
PosOff[1:0] Saída Memory
Control
No caso de uma escrita ao byte este registo
identifica, dentro da word, qual o byte alvo de
escrita
Operand1_S0[31:0] Saída Execute Operando 1 da sílaba 0
Operand1_S1[31:0] Saída Execute Operando 1 da sílaba 1
Operand1_S2[31:0] Saída Execute Operando 1 da sílaba 2
Operand1_S3[31:0] Saída Execute Operando 1 da sílaba 3
Operand2_S0[31:0] Saída Execute Operando 2 da sílaba 0
Operand2_S1[31:0] Saída Execute Operando 2 da sílaba 1
Operand2_S2[31:0] Saída Execute Operando 2 da sílaba 2
Operand2_S3[31:0] Saída Execute e Operando 2 da sílaba 3
45
Sinal / Barramento Entrada /
saída
Unidade de
interface Descrição
Memory
Control
Operandb_S# Saída Execute Branch operando da sílaba correspondente
TargetIsBr_S# Saída Execute Informação de que o resultado da operação da
sílaba correspondente é do tipo branch register
WriteEn_S#Br Saída Branch
Registers
Autorização da escrita do branch bit da sílaba
correspondente no Branch Register
Target_#[2:0] Saída WriteBack
Indica qual o alvo da escrita, por parte do módulo
WriteBack, dos valores resultantes da execução da
sílaba correspondente, podendo ser PC, GR e ou
BR
OffSet[11:0] Saída Control Valor do imediato do tipo branch utilizado para os
saltos na execução do código.
DataMemAddress_S3
[31:0] Saída
Cache de
Dados
Endereço da posição de memória que se pretende
ler da cache de dados
ReadMemRequest Saída Memory
Control
Informação de que a operação da sílaba 3 implica
uma leitura da cache de dados
No estado de funcionamento “RESET” todos os registos internos da unidade
Decode são inicializados com o valor zero. Quando o estado de funcionamento da
unidade for “WAITING” significa que ela está à espera de novas sílabas para efetuar o
decode e enquanto isso todos os registos permanecem com o valor inalterado. Quando o
estado de funcionamento for “EXECUTE” é efetuado o decode das 4 sílabas presentes
nos barramentos syllable_(0,1,2 e 3) e os barramentos de saída são preenchidos com os
dados resultantes do decode efetuado.
46
Figura 4.4 – Comportamento da unidade Decode
4.2.2.2. General-purpose Register (GR)
Consiste num conjunto de registos que podem ser lidos ou escritos de forma
imediata durante a execução da instrução. A unidade responsável pela leitura destes
registos é o Decode e a unidade WriteBack é responsável pela escrita. O banco de
registos implementado é constituído por 64 registos de 32 bits, não sendo a totalidade de
propósito geral. Existem registos de carácter especial como é o caso do registo $r0.0 que
tem sempre valor zero, o registo $r0.1 contém o valor do stack pointer e o registo $r0.63
contém o valor do link register. A tabela 3.2 descreve o uso desses registos pré definido
pela arquitetura VEX (especial atenção para o facto de se ter definido nesta
implementação VEX o registo $r0.63 para armazenar o valor do link register,
contrariamente ao que prevê o standard VEX)
Tabela 4.4 - Sinais e barramentos do General Registers
Sinal / Barramento Entrada /
Saída
Unidade de
Interface Descrição
AdressDest_S#Gr[5:0] Entrada Decode Endereço do registo onde será escrito DataIn_S#
Adress_S#R1[5:0] Entrada Decode Endereço do operando 1 que a unidade decode
pretende ler
Address_S#R2[5:0] Entrada Decode Endereço do operando 2 que a unidade decode
pretende ler
47
Sinal / Barramento Entrada /
Saída
Unidade de
Interface Descrição
DataIn_S#[31:0] Entrada WriteBack Dados a guardar no registo endereçado por
AdressDest_S#Gr
Lr_In[31:0] Entrada Control Novo valor do link register
Sp_In[31:0] Entrada Control Novo valor do stack pointer
WriteLr_Enable Entrada WriteBack Autorização de escrita no link register
WriteSp_Enable Entrada WriteBack Autorização de escrita no registo stack pointer
Write_S#_Enable Entrada WriteBack Autorização de escrita nos registos de propósito
geral
DataOut_S#R1[31:0] Saída Decode Operando 1 proveniente do registo com
endereço Adress_S#R1
DataOut_S#R2[31:0] Saída Decode Operando 2 proveniente do registo com
endereço Adress_S#R2
Lr_Out[31:0] Saída Control Valor atual do link register
Sp_Out[31:0] Saída Control Valor atual do registo stack pointer
4.2.2.3. Branch Register (BR)
Este banco de registos é também de leitura e escrita imediata e as unidades
responsáveis por isso são as mesmas que no banco General-purpose Register. É
constituído por 8 registos de 1 bit. A tabela 3.2 descreve também o uso destes registos
pré definido pela arquitetura VEX.
Tabela 4.5 - Sinais e barramentos do Branch Registers
Sinal / Barramento Entrada /
Saída
Unidade de
Interface Descrição
AddressToRead_S#[2:0] Entrada Decode Endereço dos registos que a unidade Decode
pretende ler
AddressToWrite_S#[2:0
] Entrada Decode
Endereço dos registos onde a unidade
WriteBack pretende escrever
DataIn_S# Entrada WriteBack Dados a escrever no registo com o endereço
AddressToWrite_S#
48
Sinal / Barramento Entrada /
Saída
Unidade de
Interface Descrição
WriteEn_S# Entrada WriteBack Autorização de escrita nos registos
DataOut_S# Saída Decode Operando proveniente do registo com o
endereço AddressToRead_S#
Estágio de Execute 4.2.3.
Esta unidade é responsável pela execução das operações previstas pelo ISA
(Anexo A). No Anexo B está a descrição daquilo que efetua cada uma das operações
previstas no ISA, e de que forma o faz. Esta descrição foi usada na implementação de
todas as operações realizadas no estágio de Execute
Este estágio é constituído pelas unidades Execute, Control e Memory Control. As
instruções do tipo MEM são processadas na unidade Memory Control, as instruções do
tipo CTRL na unidade Control e tanto as instruções do tipo ALU como do tipo MUL
são processadas na unidade Execute.
4.2.3.1. Unidade Control
A unidade de controlo tem por função processar as instruções do tipo CTRL. A
execução deste tipo de instruções pode gerar um salto na execução do código, a unidade
Control garante uma correta gestão dos valores do program counter, link register e
stack pointer de modo a garantir um correto funcionamento na execução do código.
Segue-se uma explicação do que sucede com estes registos em cada uma das instruções
quando processadas:
Operação Valores dos registos especiais
GOTO O registo program counter fica com o valor do
imediato branch offset.
IGOTO O registo program counter fica com valor igual ao
valor do link register.
CALL O registo program counter fica com o valor do
49
Operação Valores dos registos especiais
imediato branch offset e o link register é
atualizado com o valor “PC+1”
ICALL O registo program counter fica com valor igual ao
valor do link register e este por sua vez é
atualizado com o valor “PC+1”
BR Caso a condição testada seja igual a “1”, o
program counter toma o valor do imediato branch
offset
BRF Caso a condição testada seja igual a “0”, o
program counter toma o valor do imediato branch
offset
RETURN O registo stack pointer é atualizado com o valor
“Sp += Offset”. O program counter toma o valor
do link register
Figura 4.5 - Comportamento da unidade Control
A Figura 4.5 ilustra o comportamento da unidade dependendo do estado de
funcionamento e a Tabela 4.6 detalha os sinais e barramentos que interagem com esta
unidade.
50
Tabela 4.6 - Sinais e barramentos da unidade Control
Barramentos Entrada /
Saída
Unidade de
interface Descrição
State[1:0] Entrada State Control Estado de funcionamento da unidade Control
Lr[31:0] Entrada General
Registers Valor do link register guardado no registo r0.63
OffSet[11:0] Entrada Decode Valor do imediato branch offset
Opcode[6:0] Entrada Decode Opcode da instrução a processar
Pc[31:0] Entrada ProgramCounter Valor atual do program counter
Sp[31:0] Entrada General
Registers Valor do stack pointer guardado no registo r0.1
Br Entrada Decode Operando BR que será utilizado como condição
de teste nas instruções BR e BRF
LinkReg[31:0] Saída General
Registers
Valor atualizado do link register depois de
processada a instrução.
OldLr[31:0] Saída Memory Control Valor do link register anterior à execução da
instrução.
ProgramCntr[31:0] Saída Program
Counter
Valor atualizado do program counter depois de
processada a instrução.
StackPt[31:0] Saída General
Registes
Valor atualizado do stack pointer depois de
processada a instrução.
OutValid Saída Conclusão da execução da instrução
4.2.3.2. Unidade Memory Control
A unidade Memory Control é responsável pela gestão das leituras da memória de
dados. Quando o tipo de acesso da operação executada for uma escrita na memória, esta
unidade envia os dados que se pretende escrever para a unidade WriteBack, pois é esta a
unidade responsável pela escrita na memória. Desta forma garante-se que todas as
escritas são feitas em simultâneo, independentemente do destino ser um registo interno,
ou uma posição da memória de dados.
51
Figura 4.6 - Comportamento da unidade Memory Control
A Figura 4.6 ilustra o comportamento da unidade dependendo do estado de
funcionamento e a Tabela 4.7 detalha os sinais e barramentos que interagem com esta
unidade.
Tabela 4.7 - Sinais e Barramentos da unidade Memory Control
Barramento Entrada
/ Saída
Unidade de
interface Descrição
Address_S3R2[5:0] Entrada Decode Endereço do registo GR que será escrito na
memória de dados
DataFromReg[31:0] Entrada Decode Operando que será escrito na memória de dados
DataMemLoaded
[31:0] Entrada
Cache de
dados Operando lido da memória de dados
Lr_In[31:0] Entrada Control Unit Valor atual do link register
MemAddrToWr
[31:0] Entrada Decode
Endereço da posição da memória de dados onde
será efetuada a escrita
Opcode[6:0] Entrada Decode Opcode da operação a processar
PosOff[1:0] Entrada Decode
No caso de uma escrita ao byte este registo
identifica, dentro da word, qual o byte alvo de
escrita
State[1:0] Entrada State
Control
Estado de funcionamento da unidade Memory
Control
52
Barramento Entrada
/ Saída
Unidade de
interface Descrição
DataMemReady Entrada Cache de
dados
Informação de que a cache processou o pedido de
leitura e que o operando está disponível
ReadMemRequest_
d Entrada Decode
Informação de que a próxima instrução a processar
implicará uma leitura da cache de dados
DataToMem[31:0] Saída WriteBack Dados a guardar na memória de dados
DataToReg[31:0] Saída WriteBack Dados a guardar no banco de registos GR
ReadMemRequest Saída Cache de
dados
Sinal que efetua o pedido de leitura na cache de
dados
OutValid Saída State
Control Conclusão da execução da instrução
4.2.3.3. Unidade Execute
Figura 4.7 - Comportamento da unidade Execute
Esta unidade é responsável pela execução das operações do tipo ALU e MUL
definidas pelo ISA no Anexo A. O tipo
Por forma a executar as operações, possui no seu interior 4 unidades ALU e 2
unidades MUL. O resultado dessas operações e os endereços dos registos onde será feita
a escrita desses resultados são enviados para o estágio de WriteBack
53
Tabela 4.8 - Sinais e Barramentos da unidade Execute
Sinal / Barramento Entrada
/ Saída
Unidade de
interface Descrição
Opcode_#[6:0] Entrada Decode Opcode da operação a processar
Operand1_#[31:0] Entrada Decode Operando 1 da sílaba correspondente
Operand2_#[31:0] Entrada Decode Operando 2 da sílaba correspondente
OperandBr_# Entrada Decode Operando Br da sílaba correspondente
State[1:0] Entrada State Control Estado de funcionamento da unidade
BrDest_# Entrada Decode
Informação de que o resultado da operação é
do tipo BR, e tem como destino de escrita o
banco de registos BR.
Result_#[31:0] Saída WriteBack
Resultado da operação da sílaba a que
corresponde que tem como destino de escrita o
banco de registos GR
BrResult_# Saída WriteBack
Resultado da operação da sílaba a que
corresponde que tem como destino de escrita o
banco de registos BR
ValidOut Saída WriteBack Conclusão da execução das instruções
Tabela 4.9 - Sinais e Barramentos da ALU
Sinal / Barramento Entrada
/ Saída
Descrição
AluOp[6:0] Entrada Opcode da operação a executar
ExecuteState[1:0] Entrada Informação do estado de funcionamento da unidade Execute
Reg1[31:0] Entrada Operando 1 que será parte da operação da ALU
Reg2[31:0] Entrada Operando 2 que será parte da operação da ALU
Cin Entrada Carry in
Result[31:0] Saída Resultado da operação executada na ALU
Cout Saída Carry out
ValidResult Saída Conclusão da execução das operações
Tabela 4.10 - Sinais e Barramentos da unidade MUL
54
Sinal / Barramento Entrada /
Saída Descrição
AluOp[6:0] Entrada Opcode da operação a executar
ExecuteState[1:0] Entrada Informação do estado de funcionamento da unidade Execute
Reg1[31:0] Entrada Operando 1 que será parte da operação da unidade MUL
Reg2[31:0] Entrada Operando 2 que será parte da operação da unidade MUL
Cin Entrada Carry in
Result[31:0] Saída Resultado da operação executada na unidade MUL
Overflow Saída Informação de que a operação efetuada provocou um
overflow do resultado
ValidResult Saída Conclusão da execução das operações
Estágio WriteBack 4.2.4.
Figura 4.8 - Comportamento da unidade WriteBack
Concluída a Execução das 4 sílabas pelas unidades que compõem o estágio
Execute, esta unidade recebe os resultados dessas operações por forma a guarda-los no
destino pretendido. Independentemente do destino de escrita, a unidade WriteBack faz a
escrita desses dados em simultâneo.
Tabela 4.11- Sinais e barramentos da unidade WriteBack
55
Sinal / Barramento Entrada
/ Saída
Unidade de
interface Descrição
DataInGr_#[31:0] Entrada Execute Registo GR resultante da execução na unidade
Execute
DataInBr_# Entrada Execute Registo BR resultante da execução na unidade
Execute
Target_# Entrada Decode Informação do destino das escritas pretendidas
pela instrução correspondente
DataInMemToGr_3
[31:0] Entrada
Memory
Control Registo lido da memória de dados
DataToMemIn_3
[31:0] Entrada
Memory
Control
Registo que se pretende guardar na memória de
dados
DataCacheReady Entrada Cache de
dados Cache de dados disponível para acesso
State[1:0] Entrada State Control Estado de funcionamento da unidade
DataOutGr_#[31:0] Saída General
Registers Registo que será escrito no banco de registos GR
DataOutBr_# Saída Branch
Registers Registo que será escrito no banco de registos BR
DataOutMem[31:0] Saída Data Cache Registo que será escrito na memória de dados
WriteEnBr_# Saída Branch
Registers
Autorização da escrita do registo DataOutBr_# no
banco de registos BR
WriteEnGr_# Saída General
Registers
Autorização da escrita do registo DataOutGr_# no
banco de registos GR
WriteLr_Enable Saída General
registers Autorização de escrita no link register
WriteSp_Enable Saída General
registers Autorização de escrita no registo stack pointer
WriteMemRequest Saída Data Cache Pedido de escrita na cache de dados
56
Fluxo de instruções 4.3.
Figura 4.9 – Fluxo de instruções
Pode-se verificar na Figura 4.9 que o opcode_0 e o opcode_3 podem ter duas
unidades como destino. Tal deve-se ao Layout da instrução VLIW que, lembrando o
descrito anteriormente, permite instruções do tipo ALU nas 4 sílabas, instruções MUL
nas sílabas 1 e 2, instruções CTRL apenas na sílaba 0 e instruções MEM apenas na
sílaba 3. Por isto facilmente se deduz que as sílabas 0 e 3 podem ter como alvo unidades
diferentes e, por forma a garantir que a sílaba opere na unidade pretendida, o seu opcode
é facultado às duas unidades funcionais, Execute e Control no caso da sílaba 0, Execute
e Memory Control no caso da sílaba 3. Dependendo do valor do Opcode, as unidades
funcionais identificam quais as operações que são da sua competência e executam-nas.
Na figura 4.10 pode-se verificar como é efetuado o paralelismo no interior da unidade
funcional Execute, mediante o valor do opcode recebido, cada uma das unidades
identifica se a operação que se pretende executar é da sua responsabilidade, e procede à
execução da mesmo caso a condição se verifique.
57
ALU_1
ALU_2
ALU_3
ALU_0
MUL_1
MUL_2
Operand1_S0
Operand2_S0
Operand1_S1
Operand2_S1
Operand1_S2
Operand2_S2
Operand1_S3
Operand2_S3
Result_S0
Result_S1
Result_S2
Result_S3
Opcode_S0
Opcode_S1 Opcode_S1
Opcode_S2 Opcode_S2
Opcode_S3
Figura 4.10 - Paralelismo de hardware no interior da unidade Execute
58
Fluxo dos sinais de controlo 4.4.
Figura 4.11 - Fluxo dos sinais de controlo
De modo a garantir que todas as unidades que constituem o processador
funcionem de forma síncrona, inicialmente a implementação seguia um modelo de
unidade de controlo distribuído. Nesta implementação inicial os sinais de controlo
permitiam a interação entre unidades, mas verificou-se que o funcionamento não era o
pretendido. Um dos problemas identificados era que só quando a instrução chegava à
última unidade, o WriteBack, é que a unidade Fetch, a primeira unidade do processo,
era informada da conclusão do processamento da instrução. Por este motivo optou-se
então por se migrar para um modelo com a unidade de controlo centralizada. Criou-se a
unidade StateControl, onde ligam todos os sinais de controlo, com a finalidade de gerir
o estado de funcionamento de cada uma das unidades do processador. Com esta
implementação, relativamente ao problema descrito anteriormente, consegue-se
informar o Fetch da conclusão do decode sem que para isso seja necessário a instrução
atingir a unidade WriteBack. Apenas os sinais de interação com a cache não passam
59
pelo StateControl, isto deve-se ao facto das caches possuírem máquina de estados
interna.
Unidade Fetch 4.4.1.
Como saída temos os sinais de controlo FetchOk e o InstrRequest que sinalizam a
conclusão do fetch da instrução e um pedido de leitura da cache de Código
respetivamente. Os sinais de controlo que dão entrada na unidade Fetch são os sinais
InstrReady e state. O InstrReady informa a unidade que a instrução requerida à memória
de código está disponível para leitura no barramento Instr[127:0]. O sinal state[1:0]
provém da unidade State Control e controla o estado de funcionamento da unidade
Fetch. Todas as unidades possuem este sinal de controlo de estado, pelo que apenas aqui
será abordado de modo a evitar redundância de informação.
Unidade Decode 4.4.2.
Esta unidade possui dois sinais de saída, o sinal ReadMemRequest, que é usado
para fazer o pedido de leitura à memória de dados, e o sinal OperandsReady que
sinaliza a conclusão do decode da instrução.
Unidade Execute, Memory Control e Control 4.4.3.
As unidades Execute, Memory Control e Control processam a instrução em
paralelo, isso implica que apenas quando todas elas tiverem concluído o seu processo é
que a execução da instrução pode passar para a unidade seguinte. Para isso, cada uma
das unidades possui um sinal de conclusão de execução, o sinal OutValid, e recorrendo
a um AND lógico dos 3 sinais de conclusão é garantido o funcionamento pretendido,
pois apenas quando os 3 sinais tiverem valor “1” (disponíveis para executar nova
instrução) é que o AND lógico dos sinais terá valor “1”, informando a unidade State
Control da conclusão dos processos em execução de todas as unidades do estagio
Execute.
Unidade WriteBack 4.4.4.
Esta unidade utiliza dois sinais de controlo, o sinal WriteMemRequest faz um
pedido de escrita à memória de dados, o endereço onde se pretende efetuar a escrita é
60
passado pela unidade Decode através do barramento DataMemAddress_S3[31:0]. O
sinal de entrada DataCacheReady informa que a memória de dados está disponível para
escrita. O sinal WriteFinish sinaliza a conclusão da escrita por parte da unidade
WriteBack, tanto nos registos internos, como na memória de dados.
Unidade State Control 4.4.5.
Com exceção dos sinais que interagem com as caches, todos os outros sinais de
controlo estão ligados a esta unidade. É no State Control que, combinando os vários
sinais de controlo e seus valores, é feita a gestão do estado de funcionamento de cada
uma das unidades que constituem o processador. Essa informação é passada para as
unidades com recurso aos sinais state_f, state_d, state_ex e state_wb que controlam o
estado de funcionamento das unidades Fetch, Decode, Execute e WriteBack
respetivamente. Note-se que devido ao facto do estado das unidades Control e Memory
Control operarem em paralelo com a unidade Execute, são controladas também com o
sinal state_ex.
61
Fluxo de dados 4.5.
Figura 4.12 - Fluxo de dados
O fluxo de dados consiste no trânsito de dados entre as unidades funcionais que
constituem o processador. Os dados podem ser operandos dos bancos de registo GR e
BR, registos especiais tais como o valor do program counter, link register e stack
pointer, podem ser imediatos que estejam embebidos nas instruções, ou então dados da
memória de dados. A Figura 4.11 ilustra o fluxo desses dados entre as unidades que
compõem o processador.
Unidade Fetch 4.5.1.
A função desta unidade é efetuar o fetch da instrução VLIW da memória de
código e sua decomposição, para isso necessita apenas do valor do program counter que
lhe é passado pela unidade Program Counter através do barramento Pc[31:0].
Unidade Decode 4.5.2.
Os operandos GR são recebidos através dos barramentos Data_R1_S#[31:0] e
Data_R1_S#[31:0], o sinal Data_Br_S# transporta o operando BR pretendido. Estes
62
operandos são depois facultados às unidades que se seguem através dos barramentos
Operand1_S#[31:0], Operand2_S#[31:0] e Operandb_S# (operando 1 e 2 do GR e
operando BR respetivamente). Em alguns tipos de instruções é usado um operando do
tipo imediato, quando isto acontece é ignorado o operando 2 proveniente do banco GR e
o barramento Operand2_#[31:0] assume o valor do imediato que se encontra embebido
na sílaba.
O operando Branch Offset immediate é disponibilizado à unidade Control através
do barramento Offset[11:0]. O sinal Target_#[2:0] transporta para a unidade WriteBack
a informação do destino de escrita da sílaba a que corresponde. O sinal PosOff[1:0] tem
como funcionalidade informar a unidade Memory Control, no caso de uma escrita ao
byte, de qual o byte a ser escrito dentro dos 32 bits do registo alvo. Por fim, o sinal
TargetIsBr_S# informa a unidade Execute que o alvo de escrita, do resultado da
operação da sílaba a que corresponde, é um registo do banco BR.
Unidade Execute 4.5.3.
Os operandos necessários para execução das instruções chegam por meio dos
barramentos Operand_1_#[31:0], Operand_2_#[31:0] e OperandBr_# (operandos 1 e 2
do GR e operando BR respetivamente). O resultado das operações é colocado nos
barramentos Result_#[31:0] e BrResult_#. O sinal BrDest_# tem como função informar
a unidade Execute que o resultado da operação da sílaba correspondente é do tipo BR,
isto permite saber se o resultado da operação realizada nos módulos ALU deve ser
colocado no barramento Result_#[31:0] ou no BrResult_#.
Unidade Control 4.5.4.
Apenas a sílaba_0 está habilitada a utilizar a unidade Control, pois apenas as
instruções do tipo CTRL são executadas nesta unidade. Este tipo de instruções, ao
gerarem saltos na execução do código, originam alterações aos valores do program
counter, link register e stack pointer. Os barramentos Pc[31:0], Lr[31:0] e Sp[31:0]
contêm os valores atuais destes registos, que, após execução da instrução, seguem
atualizados para os barramentos ProgramCntr[31:0], LinkReg[31:0] e StackPt[31:0].
A atualização apenas será efetuada mediante autorização da unidade WriteBack. O valor
do branch immediate necessário neste tipo de instruções encontra-se no barramento
63
Offset[11:0]. O sinal Br transporta o operando Br que será testado como condição nas
instruções “BR” e “BRF”.
Unidade Memory Control 4.5.5.
A unidade Memory Control é responsável pela gestão do acesso à memória de
dados. No caso de uma leitura da memória, os dados chegam através do barramento
DataMemLoaded[31:0] e são enviados para a unidade WriteBack pelo barramento
DataToReg[31:0] por forma a serem guardados no banco de registos interno. No caso
de operações de escrita na memória, o barramento DataFromReg[31:0] faz chegar os
operandos provenientes do GR e estes seguem para a unidade WriteBack pelo
barramento DataToMem[31:0].
Unidade WriteBack 4.5.6.
O barramento Target_#[2:0] contém a informação do destino de escrita do
resultado de cada uma das sílabas, a Tabela 4.12 mostra os destinos possíveis para
escrita e que valores este barramento assume dependendo do destino.
Tabela 4.12 - Destinos da escrita da unidade WriteBack
Target_# Destino da escrita Mnemónica
000 Não escrever WRITE_NOP
001 Banco de registos GR WRITE_GR
010 Banco de registos BR WRITE_BR
011 Banco de registos GR e
Banco de registos BR
WRITE_GR_BR
100 Program counter WRITE_PC
101 Program counter e
Banco de registos GR
WRITE_PC_GR
110 Memória de dados WRITE_DATAMEM
111 Banco de registos GR
(leitura da memória)
WRITE_GR_MEMLOAD
64
Os barramentos DataInGr_#[31:0] e DataInBr_# fazem chegar os resultados das
instruções executadas na unidade “Execute” cujo destino seja o banco GR ou BR. Estes
dados seguem para escrita pelos barramentos DataOutGr_#[31:0] e DataOutBr_#.
Caso o barramento target_3[2:0] seja “WRITE_GR_MEMLOAD” significa que o
barramento DataInMemToGr_3[31:0] contém também dados a escrever no banco GR.
No barramento DataOutMem[31:0] estão os dados provenientes da unidade Memory
Control que se pretende guardar na memória de dados, o barramento
DataToMemIn_3[31:0] envia estes dados para a memória. A escrita dos registos
program counter, link register e stack pointer é feita de modo diferente, em vês da
unidade Control enviar os dados a escrever para a unidade WriteBack, estes são
enviados para as unidades que os armazenam, o que a unidade WriteBack faz é
autorizar a escrita através dos sinais WritePc_Enable, WriteLr_Enable e
WriteSp_Enable. Garante-se assim que todas as escritas ocorrem em simultâneo.
Caches 4.6.
Como foi referido, as caches são memórias que têm por função permitir um
acesso à memória a uma velocidade próxima das memórias mais rápidas. A figura 4.12
mostra o esquemático de referente ao interface das caches com o processador e com as
memórias de dados e código principais.
65
Figura 4.13 - Interface das caches
Cache de código 4.6.1.
Inicialmente foi criada uma máquina de estados que descrevesse o comportamento
da cache de código, em que foram previstas as condições de transição de estado e os
registos afetados em cada estado.
Figura 4.14 - Máquina de estados da cache de código
66
A máquina de estados da Figura 4.12 descreve o funcionamento da cache de
código. São necessários apenas dois estados para que a cache obtenha o funcionamento
que é pretendido. Após Reset, o estado de funcionamento por defeito é “IDLE”. No
estado “IDLE” os registos permanecem inalterados e, no caso de existir um pedido de
leitura de uma instrução que esteja guardada em cache, ela é disponibilizada para leitura
sem para isso necessitar de sair do estado “IDLE”. O estado de funcionamento
permanece inalterado até que a cache receba da unidade Fetch um pedido de leitura de
uma instrução que não esteja em armazenada na cache. Verificando-se esta condição de
transição, o estado de funcionamento da cache passa a ser “READ_MEM” e neste
estado a cache faz um pedido de leitura à memória principal, requisitando a instrução
pretendida pelo Fetch, aguarda pela mesma, e assim que esteja disponível, armazena-a
numa linha da cache e simultaneamente disponibiliza a instrução ao Fetch, no fim disto,
a cache de código regressa ao estado de funcionamento IDLE.
As Tabela 4.13 e Tabela 4.14 descrevem com detalhe os registos internos,
barramentos e sinais da cache.
Tabela 4.13- Registos internos da cache de código
Registos Descrição
[127:0]Cache_set[63:0[ Matriz de memória para armazenar as instruções lidas da memória de
código.
[25:0]Cache_tag[63:0]
Espaço de memória para armazenar a informação correspondente ao
endereço da memória principal a que correspondem as instruções guardadas
no registo Cache_set. Deste modo é possível identificar o bloco da memória
principal a que corresponde cada instrução armazenada na cache.
ValidCache[63:0] Registo que guarda a informação da validade das linhas de código guardadas
no registo Cache_set, tendo o valor “1” caso a esteja válida.
Tabela 4.14 - Sinais e barramentos da Cache de Código
Sinais / Barramentos Entrada /
Saída
Unidade de
interface Descrição
AddressInstr_p[31:0] Entrada Fetch Endereço da instrução que o processador
pretende ler da memória
AddressInstr_m[31:0] Saída Memória de Endereço da instrução que a cache pretende
67
Código ler da memória de código
InstrOut_p[127:0] Saída Fetch Instrução VLIW enviada para o processador
InstrIn_m[127:0] Entrada Memória de
Código
Instrução VLIW recebida da memória de
código
ICacheReady Saída Informação de que a cache esta disponível
para acesso.
InstrReady_p Saída Fetch
State Control
Informação de que a instrução pedida pelo
processador está disponível para leitura.
InstrRequest_p Entrada Fetch Pedido de leitura de instrução por parte do
processador.
InstrRequest_m Saída Memória de
código
Pedido à memória de código para leitura de
instrução
InstrReady_m Entrada Memória de
código
Informação de que a memória de código tem
a instrução disponível para acesso por parte
da cache
A cache implementada é do tipo Direct Mapped, o que significa que cada posição
da memória principal é mapeada diretamente numa linha de cache predefinida.
Constituída por 64 linhas de cache o mapeamento da memória de dados nas linhas de
cache processa-se da seguinte forma: os 6 bits menos significativos do endereço da
instrução são utilizados para endereçar as 64 linhas de cache, os restantes bits do
endereço constituem o Tag e têm como função identificar o bloco da memória principal
a que pertence a linha endereçada (Figura 4.13).
Endereço da linhaEndereço Tag
0531
Figura 4.15 – Decode de uma instrução da cache de código
Cache de dados 4.6.2.
A máquina de estados da Figura 4.15 apesenta o comportamento da cache de
dados. Para a implementação desta cache houve a necessidade de recorrer a 3 estados de
funcionamento, o facto de se tratar de uma cache do tipo Read/Write faz com que a
68
implementação seja mais complexa comparativamente à cache de instruções, porque
obriga a que se implementem políticas de escrita e de substituição de dados. O método
de acesso à cache de dados é do tipo 4-way set associative, a política de escrita é Write-
Back policy e o algoritmo de substituição utilizado foi Pseudo Least recently used
(Pseudo-LRU).
Bit_a
Bit_b1 Bit_b2
Cache Set_ 0
Cache Set_1
Cache Set_2
Cache Set_3
0 – Segue pela esquerda1 – Segue pela direita
0 1
0 1 0 1
Bit_a Bit_b1 Bit_b2 Bit_a Bit_b1 Bit_b2Cache Set_0 comuta bit_b1 0 0 0 0 1 0Cache Set_1 comuta bit_a e bit_b1 0 1 0 1 0 0Cache Set_2 comuta bit_b2 1 0 0 1 0 1Cache Set_3 comuta bit_a e bit_b2 1 0 1 0 0 0
Combinação que leva ao uso do Cache set
Valor dos bits após acesso ao cache SetAção
Figura 4.16 – Árvore binária e comportamento do método de substituição Pseudo-LRU
O estado após Reset é o estado “IDLE”, neste estado a cache está disponível para
acesso por parte do processador, quer para leitura quer para escrita. Os sinais
ReadRequest_p e WriteRequest_p são sinais de interface entre o processador e a cache,
servindo o primeiro para receber pedidos de leitura e o segundo pedidos de escrita. O
endereço da posição de memória a que o processador pretende aceder é passado pelo
barramento AddressData_p[31:0]. Através do sinal CacheReady a cache informa o
processador de que está disponível para acesso, o sinal DataReady_p informa o
processador que os dados pedidos estão disponíveis para leitura. Um novo pedido de
leitura ou de escrita só é aceite quando a cache está no estado de funcionamento
“IDLE”. Em cada novo pedido de leitura ou escrita, por forma a respeitar a política de
69
escrita Write-back, é verificado se os dados a que se pretende aceder estão em cache
(sinal Hit). É também verificado se a linha de cache tem valor diferente da linha de
dados do bloco da memória a que corresponde (o sinal Dirty tem valor “1” quando os
valores forem diferentes).
Figura 4.17 - Máquina de estados da cache de dados
70
O comportamento da cache no estado de funcionamento “IDLE” depende do tipo
de condição satisfeita, quando (ReadRequest_p & Hit) igual a “1” os dados pretendidos
pelo processador são disponibilizados para leitura, quando (WriteRequest_p & Hit)
igual a “1” os dados enviados pelo processador são guardados em cache, quando (!Hit
& Dirty) for “1”, ou seja, quando os dados do endereço pretendido não estiverem em
cache e a linha de cache que eles forem ocupar estiver marcada como dirty (diferente do
seu homogéneo na memória principal), o estado de funcionamento passa a ser
“WRITE_MEM”, e quando (!Hit & !Dirty) for “1” significa que o endereço do qual se
pretendem ler os dados não está em cache e que a posição que será ocupada no interior
da cache não necessita de ser salvaguardada, neste caso o estado de funcionamento
passa a ser “READ_MEM”. No estado “WRITE_MEM” os dados de uma linha de
cache que esteja marcada como dirty é restituída à memória, antes que seja substituída,
de forma a não se perder a coerência entre cache e memória. Após concluída a escrita
na memória principal, o estado transita para “READ_MEM” onde é feita a leitura de
uma nova linha de dados da memória principal e guardada em cache. No caso de se
tratar de um pedido de leitura, os dados são disponibilizados ao processador em
simultâneo. Assim que a leitura da nova linha de dados estiver concluída, o estado de
funcionamento volta a ser “IDLE” e assim permanece até que se verifiquem as
condições de transição descritas anteriormente.
As Tabela 4.15 e Tabela 4.16 descrevem as características dos registos, sinais e
barramentos da cache de dados.
Tabela 4.15 - Registos internos da cache de dados
Registos Descrição
[255:0]Cache_set_#[63:0] Matriz de registos onde estão armazenados os dados. Por se tratar de uma
cache do tipo 4-way set associative existem 4 matrizes deste tipo.
[22:0][Cache_tag_#[63:0]
Espaço de memória para armazenar a informação correspondente ao
endereço da memória principal a que correspondem os dados guardados
no registo Cache_set correspondente. Deste modo é possível identificar o
bloco da memória principal a que corresponde cada linha de dados
armazenada na cache.
ValidCache_#[63:0] Registo que possui informação de que a linha de cache correspondente
possui dados validos.
71
Registos Descrição
DirtyCache_#[63:0]
Registo que possui informação de que a linha de cache correspondente
possui dados que não estão em conformidade com o seu homólogo na
memória principal.
State[1:0] Estado de funcionamento da cache de dados.
Tabela 4.16 - Sinais e barramentos da cache de dados
Sinal / Barramento Entrada
/ Saída
Unidade de
interface Descrição
ReadRequest_p Entrada Memory
Control Pedido de leitura por parte do processador.
WriteRequest_p Entrada WriteBack Pedido de escrita por parte do processador.
CacheReady Saída WriteBack Cache disponível para acesso.
DataReady_p Saída Memory
Control
Dados requeridos pelo processador estão
disponíveis no barramento.
ReadRequest_m Saída Memória de
dados Pedido de leitura à memória de dados principal.
WriteRequest_m Saída Memória de
dados
Pedido para escrita na memória de dados
principal.
ReadReady_m Entrada Memória de
dados
Informação de que os dados que a cache pretende
ler da memória principal estão disponíveis no
barramento.
WriteReady_m Entrada Memória de
dados
Informação de que a escrita na memória de dados
principal está concluída.
DataIn_p[31:0] Entrada WriteBack Dados que o processador pretende guardar na
cache.
AddressData_p[31:0] Entrada Decode Endereço da posição de memória de dados que o
processador pretende aceder.
DataOut_p[31:0] Saída Memory
Control Dados que o processador requereu à cache.
AddressData_m[31:0] Saída Memória de
dados
Endereço a que a cache pretende aceder na
memória de dados principal.
DataOut_m[255:0] Saida Memória de Dados que a cache pretende escrever na memória
72
Sinal / Barramento Entrada
/ Saída
Unidade de
interface Descrição
dados principal
DataIn_m[255:0] Entrada Memória de
dados Dados que a cache requereu à memória principal.
A cache foi implementada sendo do tipo 4-way set associative, o que significa que
cada bloco da memória principal pode ser mapeada em 4 localizações diferentes. De
modo a possibilitar o endereçamento à word (32 bits), os 3 bits menos significativos do
endereço identificam qual das 8 words é a pretendida. Para endereçar as 64 linhas da
cache são necessários o 6 bits, serão portanto os bits AddressData_p[8:3] , os restantes
bits do endereço constituem o Tag e têm como função identificar o bloco da memória
principal a que pertence a linha endereçada ( Figura 4.16).
Figura 4.18 - Decode do endereço da cache de dados
73
CAPÍTULO 5
Assembler
Introdução 5.1.
Este capítulo é dedicado ao Assembler desenvolvido para o Hc-Vex. O assembler
foi desenvolvido com o objetivo de acompanhar a configurabilidade do processador, ou
seja, o facto de se ter um assembler open-source dedicado ao processador permite que
se altere facilmente o código máquina gerado de acordo com as alterações efetuadas no
processador. O design do assembler teve por base o assembler X-asm desenvolvido na
Universidade do Minho e foi alterado de acordo com o ISA VEX para que o código
máquina pudesse ser utilizado no Hc-Vex. É descrita a implementação do analisador
léxico, sintático e a geração de código.
Análise 5.2.
O compilador é responsável por identificar o paralelismo no código a executar, o
código assembly gerado pelo compilador é por isso isento de dependências entre
operações. Sendo que a arquitetura do Hc-Vex admite a execução de 4 operações em
paralelo (pelos motivos descritos anteriormente) o assembler gerado é composto por até
4 operações em cada instrução VLIW, a separação de instruções é realizada pelo
identificador “;;”. A organização das operações no interior da instrução é realizada pelo
compilador. Em alguns casos isso não se verifica, foi portanto necessário atribuir essa
funcionalidade ao assembler por forma a garantir que o layout da instrução VLIW é
respeitado.
De modo a gerar código nativo para executar no processador implementado,
revelou-se necessário desenvolver um assembler que, com base na semântica das
operações existentes no instruction set VEX (Anexo A) e ajustando-o às especificações
microarquiteturais da implementação, gere código objeto compatível com o
74
processador. A Figura 5.1 demonstra os passos de compilação de um código escrito em
linguagem C até à sua execução no processador.
Figura 5.1 - Etapas de compilação do código
O código que se pretende executar no processador começa por ser escrito em
linguagem C e, com recurso ao compilador VEX-3.43, é feita a compilação do código C
e gerado código Assembly, fica então a faltar efetuar a assemblagem do código por
forma a obter código nativo. Foi a necessidade de gerar código máquina dedicado ao
processador implementado e o desconhecimento do source code do assembler existente
na toolchain da qual o compilador faz parte que levou a que fosse desenvolvido um
assembler dedicado ao processador Hc-Vex.
Embora o Vex-3.43 efetue assemblagem, o código gerado não é binariamente
compatível com o processador Hc-Vex, para além disso, o facto de se tratar de uma
implementação paramétrica permite que se possam efetuar alterações ao processador,
ter um assembler open-source dedicado ao processador permite que se altere facilmente
o código máquina gerado de acordo com as alterações efetuadas no processador.
75
Design 5.3.
Neste tópico será abordado o design do assembler que se pretende desenvolver,
será apresentada a sua estrutura e funcionamento com recurso a diagramas de blocos e
fluxogramas.
O assembler desenvolvido tem por base o assembler X-Asm desenvolvido na
Universidade do Minho, embora este assembler seja dedicado a um processador de
arquitetura RISC, o X-soc, com a alteração da tabela de símbolos de acordo com o ISA
VEX (Anexo A) e da geração de código tentar-se-á fazer dele um assembler dedicado
ao Hc-Vex.
A estratégia de desenvolvimento do assembler é a utilizada no X-Asm sendo a
assemblagem dividida em duas etapas, a primeira consiste em ler o código fonte linha a
linha analisando-o lexicalmente e sintaticamente e gerando um código intermédio. Na
segunda etapa, este código intermédio é relido, processado com a ajuda da tabela de
símbolos e convertido em código objeto.
Código Fonte
Tabela de símbolos
Etapa 1
Código intermédio
Etapa 2
Geração de código
Tabela de símbolos
Código objecto
Figura 5.2 - Estrutura do Cel-Asm
A Figura 5.2 apresenta um diagrama que descreve a estrutura de desenvolvimento
do assembler, os fluxogramas das Figura 5.3 e Figura 5.4 descrevem o funcionamento
do primeiro e segundo passo respetivamente.
76
Código Fonte
Abre o ficheiro e inicia a leitura linha a linha
Decompõe cada linha em tokens
Verifica a validade da frase de acordo com a gramática e caso
seja valido devolve o pacote
O opcode é uma diretiva (.org) ?
Inicia o LC e o endereço inicial com a constante especificada na
diretiva
Atualiza a representação intermédia com a estrutura da
frase atual processa a próxima linha
Atualiza o LC e o endereço inicial com o valor zero
Processa as restantes linhas
Atualiza a representação intermedia com a estrutura da
ultima linha
Calcula e guarda o cumprimento do módulo
Fim
Não
Sim
Figura 5.3 - Fluxograma do primeiro passo
Representação intermédia
Lê a primeira entrada
Obtêm o opcode da entrada em processamento
O opcode é uma diretiva (.org) ?
Escreve o cabeçalho do código objeto
Inicializa o primeiro registo de codificação da instrução
Escreve a linha de listagem e lê a proxima entrada da
representação intermédia
Processa as restantes representações do código
intermédio
Escreve o último registo de codificação da instrução do
código objeto
Escreve o registo final (a diretiva END) do código objeto
Fim
Não
Sim
Escreve a ultima linha da listagem assembly
Figura 5.4 - Fluxograma do segundo passo
Implementação 5.4.
Neste tópico serão ilustradas algumas partes do código utilizado na
implementação do assembler.
77
Analisador Léxico 5.4.1.
O analisador léxico divide-se em 3 partes, na primeira parte, Figura 5.5, efetuam-
se as definições do analisador.
%{
/* LEX source for vasm.c
*/
/* start of definitions
*/
#include "xasm.tab.h"#include "gvars.h"%}
letter [a-zA-Z]digit [0-9]hex [a-fA-F]
Figura 5.5 - Definições do analisador léxico
Na segunda parte definem-se as regras do analisador. A Figura 5.6 demonstra
parte do conjunto de regras léxicas usadas para devolver um token sempre que seja
reconhecido um símbolo da linguagem assembly. Neste exemplo vemos as regras para o
reconhecimento dos símbolos assembly para operações de acesso à memória.
ldw|LDW {yylval = NONE; return(LDW); }ldh|LDH {yylval = NONE; return(LDH); }ldhu|LDHU {yylval = NONE; return(LDHU);}ldb|LDB {yylval = NONE; return(SUB); }ldbu|LDBU {yylval = NONE; return(LDBU);}stw|STW {yylval = NONE; return(STW); }sth|STH {yylval = NONE; return(STH); }stb|STB {yylval = NONE; return(STB); }pft|PFT {yylval = NONE; return(PFT); }
Figura 5.6 – Análise léxica das operações de acesso à memória
Na Figura 5.7 podem-se ver as regras léxicas utilizadas nos registos GR e BR.
Neste caso, para além de ser devolvido o token “REG_NUM”, os registos são
adicionados a uma tabela de símbolos, e o analisador sintático recebe um apontador para
a sua posição por meio do “yylval”. É usada a função “sscanf” de modo a extrair da
string o número do registo e guarda-lo no campo “value” da estrutura apontada por
“yyval”.
78
$r0.{digit}+ { /* register, e.g. r0.15 */yylval = add_symbol(yytext);/* extract register number from string */
sscanf(sym_table[yylval].name,"$r0.%d",&sym_table[yylval].value);return(REG_NUM);}
$R0.{digit}+ { /* register, e.g. R0.15 */yylval = add_symbol(yytext);/* extract register number from string */
sscanf(sym_table[yylval].name,"$R0.%d",&sym_table[yylval].value);return(REG_NUM);}
$b0.{digit} { /* register, e.g. b0.4 */yylval = add_symbol(yytext);/* extract register number from string */
sscanf(sym_table[yylval].name,"$b0.%d",&sym_table[yylval].value);return(BR_REG_NUM);}
$B0.{digit} { /* register, e.g. B0.4 */yylval = add_symbol(yytext);/* extract register number from string */
sscanf(sym_table[yylval].name,"$B0.%d",&sym_table[yylval].value);return(BR_REG_NUM);}
$l0.{digit} { /* Link register */yylval = add_symbol(yytext);/* extract register number from string */sym_table[yylval].value = 63;return(LINK_REG);}
$L0.{digit} { /* Link register */yylval = add_symbol(yytext);/* extract register number from string */sym_table[yylval].value = 63;return(LINK_REG);}
Figura 5.7 - Análise léxica dos registos
No caso da análise de dígitos (Figura 5.8), os valores são identificados pela
função “sscanf” e guardados no campo “value” do símbolo correspondente. O seu valor
é rodado 4 bits à esquerda e guardado no campo “value_rot” por forma a disponibilizar
ao parser o digito no seu estado puro e o seu valor rodado.
Quando o analisador léxico encontra uma label no código assembly (Figura 5.9) é
adicionado um novo valor na tabela de símbolos e feito o retorno do token
“IDENTIFIER”. Também os caracteres a ignorar tais como mudanças de linha, espaços
em branco e comentários estão previstos pelo analisador léxico (Figura 5.10). Em
alguns caracteres, o analisador léxico retorna o próprio caractere (Figura 5.11).
79
0x({digit}|{hex})+ { /* hex constants, e.g. 0x1A */yylval = add_symbol(yytext);/* convert string to actual value */sscanf(yytext,"0x%x",&sym_table[yylval].value);sym_table[yylval].value_rot = 0xfff0 & (sym_table[yylval].value << 4);return(NUMBER);}
{digit}+ {yylval = add_symbol(yytext);/* convert string to actual value */sscanf(yytext,"%d",&sym_table[yylval].value);sym_table[yylval].value_rot = 0xfff0 & (sym_table[yylval].value << 4);return(NUMBER);}
Figura 5.8 - Análise léxica de dígitos
(_|{letter})({letter}|{digit}|_|"."|"$")*{yylval = add_symbol(yytext);return(IDENTIFIER);}
Figura 5.9 - Análise léxica de labels
[ \t]* ; /* ignore any white space */
"c0" {yylval = NONE; return( ); } /* ignore c0*/
("."{letter}+|"##")(.)* ; /* . or ## start comments*/
"\n" {curr_line++;}
Figura 5.10 - Caracteres a ignorar
"=" {yylval = NONE; return('='); }
"," {yylval = NONE; return(','); }
"(" {yylval = NONE; return('('); }
")" {yylval = NONE; return(')'); }
"+" {yylval = NONE; return('+'); }
"-" {yylval = NONE; return('-'); }
":" {yylval = NONE; return(':'); }
";" {yylval = NONE; return(';'); }
Figura 5.11 - Tokens iguais aos caracteres
Não podemos esquecer a particularidade de que cada instrução VLIW ser
constituída por 4 operações, existe portanto necessidade de um identificador especial
para sinalizar a separação de instruções VLIW (Figura 5.12)
80
;; {return(NEW_LINE);
}
Figura 5.12 - Identificador de separação de instruções VLIW
Por fim, na terceira parte do analisador léxico é definida a função “yywrap”. Esta
função é chamada sempre que a análise de um ficheiro termine por forma a decidir o
que fazer de seguida. Como neste caso não existe mais nenhum código para analisar é
devolvido o valor “1”, mas caso houvesse intensão de analisar mais algum ficheiro, era
devolvido o valor “0” e a função “yywrap” definida por defeito pela biblioteca do
analisador apontaria “yyin” para o ficheiro seguinte.
%% /* start of programs section
*/
int yywrap(void){
return 1;}
Figura 5.13 – Função “yywrap()”
Analisador sintático 5.4.2.
O analisador sintático é também composto por 3 partes distintas, na primeira é
feita a declaração das variáveis (Figura 5.14), na segunda parte são definidas as regras
%{#include <stdio.h>#include "gvars.h" /* declaration of global variables */
int temp1; /* temp used to add some implicit ops */int temp2; /* temp used to add some implicit ops */char temp_str[80]; /* temp string used for conversions */int j; /* loop counter */int lc = 0; /* location counter--# bytes for this instr */
#define YYDEBUG 1%}
/* start of definitions */
%start stmt_list
/* tokens and values returned by lexical analyzer */
%token NEW_LINE%token ADD%token AND%token ANDC%token MAX(...)
Figura 5.14 - Tokens e variáveis temporárias
81
gramaticais e na terceira parte é definida a função “add_stmt()” (Figura 5.15),
responsável por adicionar dados às estruturas que constituem o código intermédio. Este
código resulta da análise do código fonte, e a partir dele será gerado o código máquina
final e é constituído por um array de estruturas que contém a informação relativa às
instruções (Figura 5.16).
/* start of programs section */%%
/* add opcode, operands, etc. to current statement */add_stmt(code,o1,o2,o3,o4,o5,o6,n)int code; /* the opcode that will appear in the object file */int o1; /* pointer into sym_table[] for operand 1 */int o2; /* pointer into sym_table[] for operand 2 */int o3; /* pointer into sym_table[] for operand 3 */int o4; /* pointer into sym_table[] for operand 4 */int o5; /* pointer into sym_table[] for operand 5 */int o6; /* pointer into sym_table[] for operand 6 */
int n; /* number of bytes for this instr */{
stmt[curr_stmt].op_code = code;stmt[curr_stmt].op1 = o1;stmt[curr_stmt].op2 = o2;stmt[curr_stmt].op3 = o3;stmt[curr_stmt].op4 = o4;stmt[curr_stmt].op5 = o5;stmt[curr_stmt].op6 = o6;stmt[curr_stmt].line_num = curr_line;
curr_stmt++;
if (curr_stmt >= MAX_ISTMT)err_exit("Too many input statements",STMT_OFLW);
}
Figura 5.15 - Função “add_stmt()”
/* symbol table structures */int next_free_ptr; /* points to next available slot in sym_table[] */int hash_table[MAX_HASH_TABLE]; /* points into sym_table[] */
struct {char name[MAX_NAME_LEN]; /* the symbol itself */int value; /* mem addr, reg #, constant value, etc */int value_rot; /* mem addr, reg #, constant value, etc */int link; /* points to next symbol in this chain */} sym_table[MAX_SYM_TABLE];
/* structure for holding statement quadruples */struct {
int op_code; /* op-code that will appear in object file */int op1; /* pointer to sym_table[] for operand 1 */int op2; /* pointer to sym_table[] for operand 2 */int op3; /* pointer to sym_table[] for operand 3 */int op4; /* pointer to sym_table[] for operand 4 */int op5; /* pointer to sym_table[] for operand 5 */int op6; /* pointer to sym_table[] for operand 6 */int line_num; /* input source line number for this stmt
the type of branch condition */} stmt[MAX_ISTMT];
Figura 5.16 - Estrutura do código intermédio
82
Na Figura 5.17 está um exemplo de algumas regras gramaticais do analisador
sintático. Nestes casos é chamada a função “add_stmt()” que recebe 8 parâmetros, de
forma ordenada do primeiro para o ultimo eles são:
(...)| CMPNE reg '=' reg ',' reg
//RR{
add_stmt(CMPNE_CODE,$2,$4,$6,0,0,0,WORD_SIZE);}
| CMPNE reg '=' reg ',' expr //RI
{add_stmt(CMPNE_CODE,$2,$4,$6,1,0,0,WORD_SIZE);
}| CMPNE breg '=' reg ',' reg
//RR{
add_stmt(CMPNE_CODE,$2,$4,$6,0,0,1,WORD_SIZE);}
| CMPNE breg '=' reg ',' expr //RI
{add_stmt(CMPNE_CODE,$2,$4,$6,1,0,1,WORD_SIZE);
}
| ADDCG reg ',' breg '=' breg ',' reg ',' reg {
add_stmt(ADDCG_CODE,$2,$8,$10,0,$6,$4,WORD_SIZE);}
(...)
Figura 5.17 - Regras gramaticais
• Opcode da operação a que corresponde o token;
• Endereço do registo de destino (podendo ser general register ou branch
register);
• Endereço do registo do primeiro operando;
• Endereço do registo do segundo operando ou o valor do imediato existente
na instrução;
• Informação de qual o tipo do quarto parâmetro, podendo ser o endereço de
um registo ou o valor de um imediato;
• Endereço do operando branch register;
• Endereço do branch register de destino ou indicação de que a escrita é
feita num branch register (quando o valor deste parâmetro for “1”, o
segundo parâmetro consiste no endereço do branch register de destino);
• Tamanho em número de bytes que a instrução ocupa;
83
Geração de código 5.4.3.
A segunda etapa do funcionamento do assembler consiste na geração de código, é
feita a leitura da representação intermédia e, com auxílio da tabela de símbolos, é
gerado o código objeto. Este código é impresso em ficheiro pela função “print_code()” à
medida que a análise é realizada sempre que encontrar o token “NEW_LINE”
(indicação de nova instrução VLIW).
Na Figura 5.18 está um exemplo de como é feita a manipulação ao bit de modo a
inserir os vários parâmetros na instrução respeitando o seu formato. Segue-se uma breve
explicação do caso demonstrado na figura:
case MPYHS_CODE :
code = 0;code |= (0x7f & stmt[i].op_code) << 25;
if(stmt[i].op4==0){code |= (0x00) << 23;code |= (0x3f & sym_table[stmt[i].op1].value) << 17;code |= (0x3f & sym_table[stmt[i].op2].value) << 11;code |= (0x3f & sym_table[stmt[i].op3].value) << 5;}
else{code |= (0x01) << 23;code |= (0x3f & sym_table[stmt[i].op1].value) << 17;code |= (0x3f & sym_table[stmt[i].op2].value) << 11;code |= (0x1ff & sym_table[stmt[i].op3].value) << 2;}
print_code(code,4,&lc,00,0,0);break;
Figura 5.18 - Geração de código
• Inicializa os 32 bits que constituem código com valor “0”;
• Insere o valor do opcode nos bits mais significativos com início na posição
25;
• Se todos os operandos forem registos:
o Insere o valor “00” no campo de identificação de imediatos (bits 23
e 24);
o Insere o endereço do registo de destino a partir do bit 17;
o Insere o endereço do primeiro operando a partir do bit 11;
o Insere o endereço do segundo operando a partir do bit 5;
• Caso exista um operando do tipo imediato:
84
o Insere o endereço do registo de destino a partir do bit 17;
o Insere o endereço do primeiro operando a partir do bit 11;
o Insere o operando do tipo imediato a partir do bit 2;
• Invoca a função “print_code()”;
• Termina a escrita;
Recordando que se trata de instruções VLIW, o facto de terminar a escrita de uma
operação não significa que terminou a escrita da instrução, pois como já foi referido,
uma instrução VLIW pode conter até 4 operações. A organização das silabas no interior
da instrução é feita respeitando o layout da instrução VLIW (figura 3.2) e com base no
token que identifica a operação. A escrita de uma instrução apenas termina quando for
encontrado o identificador que sinaliza o fim da instrução VLIW (Figura 5.19).
switch (stmt[i].op_code){case NEW_LINE_CODE :
print_code(0,0,0,1,0,0);break;
Figura 5.19 - Fim de instrução VLIW
A função “print_code()” (Figura 5.20) recebe 6 parâmetros por forma e efetuar
corretamente a escrita da instrução VLIW são eles:
• Código a ser escrito correspondente a cada operação que constitui a
instrução VLIW;
• Número de bytes a escrever por cada operação;
• Valor atual do location counter;
• Informação de que a instrução VLIW está completa;
• Informação de que se trata de uma operação de acesso à memória (esta
informação é fundamental no correto posicionamento das operações no
interior da instrução VLIW);
• Número de operações do tipo “NOP” a executar (apenas na operação
“XNOP”);
Relativamente ao funcionamento da função, enquanto não for encontrado o
identificador que sinaliza o fim da instrução VLIW é preenchido um array com o valor
85
do código recebido de cada operação, quando for encontrado esse identificador, o array
é impresso e posteriormente inicializado.
print_code(code,count,lc,nl,MemOp,Xnops)unsigned code; /* the object code value
*/int count; /* number of bytes to print
*/int *lc; /* current program counter value */int nl; /* new VLIW Instruction */int MemOp; /*instruçao mem access*/int Xnops; /*numero de xnops*/{ int i;
if(nl!=1)
{ if (MemOp == 1){
vliw_instr[3] = code;*lc += count;
}else{
vliw_instr[posicao] = code;
posicao++; /* increment lc by count bytes */ *lc += count;}
}if(nl==1){
//fprintf(fp,"%04x :\t",addr_mem);
for (posicao = 3; posicao >= 0; posicao--){
fprintf(fp,"%08x",(vliw_instr[posicao]));}
for (i=0; i <= 3; i++){
vliw_instr[i] = 0;}
/* End with a newline character. */fprintf(fp,"\n");
posicao=0;addr_mem++;
}}
Figura 5.20 - Função "print_code()"
86
CAPÍTULO 6
Resultados experimentais
Introdução 6.1.
Neste capítulo são apresentados os resultados experimentais. será utilizado um
Benchmark utilizado pela Texas Instruments para testar a performance de
microcontroladores. É apresentado e analisado o código assembly gerado pelo
compilador VEX da HP e o código máquina correspondente gerado pelo assembler
desenvolvido nesta dissertação. A execução do código é analisada e verificado a correta
execução das instruções pelo processador, verificando que os resultados finais são os
pretendidos. Por forma a perceber os passos de execução, inicialmente é explicado o
código e os seus passos de compilação até à obtenção do código nativo. Os testes de
execução do código pelo processador foram realizados em ambiente de simulação no
software Xilinx ISE Design Suite 13.2.
Código C 6.2.
O código C utilizado consiste num Benchmark utilizado pela Texas Instruments
para testar a performance de microcontroladores na execução de operações matemáticas
com operadores de 32 bits.
87
/*********************************************************************************Name: 32-bit Math*Purpose : Benchmark 32-bit math functions.********************************************************************************/#include <math.h>
typedef unsigned long UInt32;
UInt32 add(UInt32 a, UInt32 b){return (a + b);}UInt32 mul(UInt32 a, UInt32 b){return (a * b);}UInt32 div(UInt32 a, UInt32 b){return (a / b);}void main(void){volatile UInt32 result[4];
result[0]=255;result[1]=127;result[2]=add(result[0], result[1]);result[1]=mul(result[0], result[2]);result[3]=div(result[1], result[2]);return;
}
Figura 6.1 - Código C usado como teste
Testar este Benchmark permite avaliar e comparar a performance do Hc-Vex com
outros microcontroladores existentes. Esta comparação com dispositivos embebidos
existentes possibilita uma análise adaptada ao mundo real da performance do Hc-Vex.
Compilação 6.3.
O código acima referido é compilado pelo compilador Vex-3.43. A Figura 6.2
mostra o código Assembly gerado na compilação do código C. Da compilação do
código C resultou um código assembly com 28 instruções VLIW (o caracter “;;”
identifica o fim de uma instrução VLIW). O índice de cada instrução é dado pelo valor
que se segue ao identificador de comentário “##”.
Relativamente à qualidade do código gerado, pode-se verificar que existem
bastantes instruções VLIW que não exploram a potencialidade do VLIW ao não
possuírem 4 operações por instrução, havendo casos em que uma instrução VLIW
apenas executa uma operação. Isto deve-se ao desconhecimento do compilador do
comportamento em run-time do processador ao identificar o paralelismo, o que o força a
88
ser conservador. Este inconveniente pode ser resolvido acrescentando no estágio de
fetch a funcionalidade de pre-fetching, ou seja, aumentar a complexidade da
implementação por forma a detetar possíveis paralelismos entre instruções VLIW. O
facto de se executarem sucessivas operações de acesso à memória também é
responsável pelo desaproveitamento de silabas disponíveis pois apenas existe uma
unidade de acesso à memória.
Outro inconveniente do código gerado é o não cumprimento do layout binário da
instrução VLIW relativamente ao posicionamento das silabas, por exemplo, operações
de acesso à memória apenas podem ser executadas pela silaba 3, mas no código gerado
estas não surgem na devida posição, a que a implementação do assembler levou isso em
conta e essa funcionalidade foi adicionada ao assembler.
89
## Begin mainmain::
c0 add $r0.1 = $r0.1, (-0x40)c0 mov $r0.2 = 255 ## 255(SI32)
;;##0c0 add $r0.5 = $r0.1, 0x10 ## bblock 0, line 28, t0, t11, offset(result?1.8)=0x10(P32)c0 mov $r0.6 = 127 ## 127(SI32)c0 stw 0x20[$r0.1] = $l0.0 ## spill ## t10
;;##1c0 stw 0x24[$r0.1] = $r0.5 ## spill ## t0
;;##2c0 stw 0[$r0.5] = $r0.2 ## bblock 0, line 30, t0, 43125(SI32)
;;##3c0 stw 4[$r0.5] = $r0.6 ## bblock 0, line 31, t0, 14567(SI32)
;;##4c0 ldw $r0.3 = 0[$r0.5] ## bblock 0, line 32, t2, t0
;;##5c0 ldw $r0.4 = 4[$r0.5] ## bblock 0, line 32, t3, t0
;;##6c0 call $l0.0 = _add ## bblock 0, line 32, raddr(add)(P32), t2, t3
;;##7c0 ldw $r0.5 = 0x24[$r0.1] ## restore ## t0
;;##8c0 stw 8[$r0.5] = $r0.3 ## bblock 1, line 32, t0, t1
;;##9c0 ldw $r0.3 = 0[$r0.5] ## bblock 1, line 33, t5, t0
;;##10c0 ldw $r0.4 = 8[$r0.5] ## bblock 1, line 33, t6, t0
;;##11c0 call $l0.0 = _mul ## bblock 1, line 33, raddr(mul)(P32), t5, t6
;;##12c0 ldw $r0.5 = 0x24[$r0.1] ## restore ## t0
;;##13c0 stw 4[$r0.5] = $r0.3 ## bblock 2, line 33, t0, t4
;;##14c0 ldw $r0.3 = 4[$r0.5] ## bblock 2, line 34, t8, t0
;;##15c0 ldw $r0.4 = 8[$r0.5] ## bblock 2, line 34, t9, t0
;;##16c0 call $l0.0 = _div ## bblock 2, line 34, raddr(div)(P32), t8, t9
;;##17c0 ldw $r0.5 = 0x24[$r0.1] ## restore ## t0
;;##18c0 ldw $l0.0 = 0x20[$r0.1] ## restore ## t10
;;##19c0 stw 12[$r0.5] = $r0.3 ## bblock 3, line 34, t0, t7
;;##20c0 return $r0.1 = $r0.1, (0x40), $l0.0 ## bblock 3, line 35, t11, ?2.4?2auto_size(I32), t10
;;##21 ## End main ## Begin add_add::
c0 add $r0.3 = $r0.3, $r0.4 ## bblock 0, line 16, t2, t1, t0;;##22
c0 return $r0.1 = $r0.1, (0x0), $l0.0 ## bblock 0, line 16, t4, ?2.1?2auto_size(I32), t3;;##23 ## End add ## Begin mul_mul::.trace 1
c0 nop## auto_size == 0c0 mpylu $r0.2 = $r0.3, $r0.4 ## bblock 0, line 20, t18, t1, t0c0 mpyhs $r0.3 = $r0.3, $r0.4 ## bblock 0, line 20, t19, t1, t0
;;##24c0 add $r0.3 = $r0.2, $r0.3 ## bblock 0, line 20, t2, t18, t19
;;##25c0 return $r0.1 = $r0.1, (0x0), $l0.0 ## bblock 0, line 20, t4, ?2.2?2auto_size(I32), t3
;;##26
## End mul ## Begin div_div::
c0 divs $r0.3, $b0.1 = $b0.2, $r0.3 , $r0.4 ;;##27
c0 return $r0.1 = $r0.1, (0x20), $l0.0;;##28
Figura 6.2 - Assembly resultante da compilação do código C
Assembling 6.4.
O código assembly gerado pelo compilador é agora assemblado pelo assembler
desenvolvido para este processador. O código nativo gerado é o da Figura 6.3.
90
0000000000000000b08403fc82820f002c7e088000000000b08c01fc828a08402c0a08900000000000000000000000002c0428000000000000000000000000002c0c28100000000000000000000000002286280000000000000000000000000022882810000000000000000052000000000000000000000000000000470002c0228a08900000000000000000000000002c062820000000000000000000000000228628000000000000000000000000002288282000000000000000005200000000000000000000000000000047000300228a08900000000000000000000000002c062810000000000000000000000000228628100000000000000000000000002288282000000000000000000000000000000000000000000000000047000360228a089000000000000000000000000022fe08800000000000000000000000002c0628300000000000000000000000000000000000000000000000004f000800000000000000000000000000820618800000000000000000000000004f00000000000000160618801004188000000000000000000000000000000000820610600000000000000000000000004f000000000000000000000000000000e20618880000000000000000000000004f000400
Figura 6.3 - Código nativo gerado pelo Assembler
Tal como o código assembly, o código nativo tem também 29 instruções VLIW. A
fonte utilizada é hexadecimal, sendo cada linha composta por 32 caracteres verifica-se
os 128 bits por instrução VLIW.
Relativamente à qualidade do código gerado, verifica-se que a organização das
silabas no interior da instrução VLIW foi realizada com êxito, o elevado numero de
silabas com o valor zero é resultado do desaproveitamento provocado pelo código
gerado pelo compilador. O código gerado não apresenta falhas binárias e a execução do
código máquina no processador foi realizada com sucesso.
Segue-se a análise dos resultados gerados pelas unidades funcionais do
processador durante a execução do código gerado pelo assembler.
Execução das instruções 6.5.
A figura 6.4 mostra o percurso de execução da instrução “add $r0.1 = $r0.1, (-
0x40)".
91
Figura 6.4 Execução da instrução "add $r0.1 = $r0.1, (-0x40)"
No instante 55ns a unidade Fetch inicia o processo de fetch da instrução guardada
no endereço da memória de código dada pelo program counter. O fetch é concluído
passados 3 ciclos de clock, no instante 85ns, estando a sílaba disponível para decode. O
processo de decode termina passado um ciclo de clock, estando no barramento
Operand1_S0 disponível o valor do Sp (registo r0.1), no Operand2_S0 esta o resultado
da soma do valor do Sp com o offset “-0x40”. A execução da operação inicia no instante
95ns e termina passado um ciclo de clock, neste momento o barramento Result_0 tem o
resultado da operação, ou seja, o valor da soma do Sp com o offset. O processo de
escrita dura também um ciclo de clock, terminando no instante 115ns. O destino de
escrita do resultado é o banco de registos GR e por isso o valor do barramento Target_0
é “1”. Pode-se verificar que no fim da execução da operação o valor do registo Sp (r0.1)
é atualizado com o resultado da operação.
A Figura 6.5 demonstra o funcionamento da operação CALL, esta operação é
executada pela sílaba 0 da instrução VLIW nº 7. A execução desta operação vai
provocar um salto na execução do código, alterando o valor dos registos especiais link
register e program counter, ficando o program counter com o endereço da linha de
código para onde é efetuado o salto e o link register com o endereço da instrução a
executar quando o programa regressar do salto.
92
Figura 6.5 - Execução da instrução "call $l0.0 = _add"
No instante 475ns a unidade Fetch inicia o processo de fetch da instrução
guardada no endereço da memória de código dada pelo program counter. O fetch é
concluído passados 3 ciclos de clock, no instante 505ns, estando a sílaba disponível para
decode. O processo de decode termina passado um ciclo de clock, estando disponível no
barramento Operand2_S0 o endereço da label “_add”, que será o próximo valor do Pc.
A execução da operação inicia no instante 515ns e termina passado um ciclo de clock,
neste momento o novo valor dos registos LinkReg (valor “0x8”) e ProgramCntr (valor
“0x16”) é dado como válido. O processo de escrita dura também um ciclo de clock,
terminando no instante 535ns. O destino de escrita do resultado é o program counter e o
banco de registos GR e por isso o valor do barramento Target_0 é “5”. Pode-se verificar
que no fim da execução da operação o valor do registo Lr (r0.63) é atualizado com o
valor “0x8” e o barramento Pc com o valor “0x16”.
A Figura 6.6 demonstra o funcionamento da operação RETURN, esta operação é
executada pela sílaba 0 da instrução VLIW nº 23. A execução desta operação vai
provocar um salto na execução do código da função “_add” para o endereço
imediatamente a seguir ao da instrução que gerou o salto para função “_add”. Tal como
com a operação “CALL”, a execução da operação “RETURN” altera o valor dos
registos especiais link register e program counter e neste caso também do stack pointer,
ficando o program counter com o endereço dado pelo conteúdo do link register, que
93
corresponde à linha de código para onde é efetuado o salto, o valor do stack pointer é
atualizado com o resultado da soma dele mesmo com o valor do offset.
Figura 6.6 - Execução da instruçao "return $r0.1 = $r0.1, (0x0), $l0.0"
No instante 595ns a unidade Fetch inicia o processo de fetch da instrução
guardada no endereço da memória de código dada pelo program counter. O fetch é
concluído passados 3 ciclos de clock, no instante 625ns, estando a sílaba disponível para
decode. O processo de decode termina passado um ciclo de clock, estando disponível no
barramento Operand1_S0 o valor atual do stack pointer e no barramento offset o valor
que será somado ao Sp (“0x0”). A execução da operação inicia no instante 635ns e
termina passado um ciclo de clock, neste momento o novo valor dos registos
ProgramCntr (valor “0x8”) e stack pointer (valor 0xffc0) é dado como válido. O
processo de escrita dura também um ciclo de clock, terminando no instante 655ns. O
destino de escrita do resultado é o program counter e o banco de registos GR e por isso
o valor do barramento Target_0 é “5”. Pode-se verificar que no fim da execução da
operação o valor do registo Sp (r0.1) é atualizado com o valor “0xffc0” e o barramento
Pc com o valor “0x8”.
A Figura 6.7 demonstra o funcionamento da operação STW, esta operação é
executada pela sílaba 3 da instrução VLIW nº 1. A execução desta operação vai gerar
uma escrita na memória de dados do conteúdo do link register.
94
Figura 6.7 - Execução da instrução "stw 0x20[$r0.1] = $l0.0"
No instante 115ns a unidade Fetch inicia o processo de fetch da instrução
guardada no endereço da memória de código dada pelo program counter. O fetch é
concluído passados 3 ciclos de clock, no instante 145ns, estando a sílaba disponível para
decode. O processo de decode termina passado um ciclo de clock, estando disponível no
barramento Operand1_S3 o valor atual do stack pointer e no barramento DataToMem
os dados que se pretendem guardar em memória. Como se trata de uma escrita na pilha,
o endereço da memória de dados onde se pretende efetuar a escrita é dado pelo registo
Sp, no caso de se pretender escrever ou ler de uma qualquer outra posição da cache, o
endereço pode ser dado por um qualquer registo desde que não seja um registo especial
(tanto num caso como noutro é somado um offset ao valor do registo). A execução da
operação inicia no instante 155ns e termina passado um ciclo de clock, neste momento o
novo valor dos barramentos MemAddrToWr (0xffc0) e DataToMem (0x0) é dado como
válido. O processo de escrita dura também um ciclo de clock, terminando no instante
175ns. O destino de escrita do resultado é a memória de dados e por isso o valor do
barramento Target_3 é “6”. O pedido de escrita é feito durante o processo de escrita,
mas como estamos a usar uma cache entre a memória principal e o processador, um
ciclo de clock após a conclusão do processo de escrita a linha de memória onde se
pretende escrever os dados é lida para a cache, e apenas no ciclo de clock seguinte, no
instante 195ns, é que a escrita dos dados é feita na linha de cache na posição pretendida.
95
A Figura 6.8Figura 6.7 demonstra o funcionamento da operação LDW, esta
operação é executada pela sílaba 3 da instrução VLIW nº 6. A execução desta operação
vai gerar uma leitura da memória de dados.
Figura 6.8 - Execução da instrução "ldw $r0.4 = 4[$r0.5]"
No instante 415ns a unidade Fetch inicia o processo de fetch da instrução
guardada no endereço da memória de código dada pelo program counter. O fetch é
concluído passados 3 ciclos de clock, no instante 445ns, estando a sílaba disponível para
decode. O processo de decode termina passado um ciclo de clock, estando disponível no
barramento Operand1_S3 o valor do registo ”r0.5”, no barramento Operand2_S3 o
valor do offset que será somado ao conteúdo de “r0.5” por forma a calcular o endereço
de onde será efetuada a leitura e no barramento AddressDest_S3Gr o endereço do
registo Gr onde serão guardados os dados lidos da memória. A execução da operação
inicia no instante 455ns e termina passado um ciclo de clock, neste momento o valor do
barramento DataToReg (0x7f) é dado como válido. O processo de escrita dura também
um ciclo de clock, terminando no instante 475ns. O destino de escrita dos dados lidos da
memória é um registo GR, por isso o valor do barramento Target_3 é “7”. O pedido de
leitura à memória é feito durante o processo de execute por forma a ter disponível os
dados para escrita a quando da execução do processo de escrita.
96
A Figura 6.9 ilustra o comportamento da cache de código a quando de um pedido
de leitura por parte do processador.
Figura 6.9 - Comportamento da cache de código
Quando o sinal InstrRequest comuta para “1” (instante 65ns) o sinal state passa a
ser “1”, ou seja, a cache passa a estar no estado de funcionamento “WAITING”. Do
endereço presente no barramento AddressInstr_p a cache retira o tag address e o line
address, com isto verifica se a instrução esta armazenada em cache (sinal Hit), como se
pode ver na figura o valor de HitInstr_p é “0”, há portanto a necessidade de ler a
instrução da memória de código. Neste mesmo instante a cache comuta o sinal
InstrRequest_m para “1” por forma a efetuar o pedido de leitura. No instante 75ns o
sinal InstrReady_m comuta para “1” informando a cache de que a instrução pretendida
está disponível no barramento InstrIn_m. A cache coloca a instrução recebida no
barramento InstrOut_p e coloca o sinal InstrReady_p com o valor “1” por forma a
informar o processador que a instrução pedida está disponível. Como a instrução passou
a estar armazenada em cache, o sinal HitInstr_p passa a ter o valor “1” para este
endereço da memória.
A Figura 6.10 permite observar o comportamento da cache de dados a quando de
um pedido de escrita por parte do processador.
97
Figura 6.10 - Comportamento da cache de dados numa escrita
Pode-se observar na figura que no instante 165ns o sinal WriteRequest_p comuta
para o valor “1” o que significa que existe um pedido de escrita, o barramento DataIn_p
contém os dados que se pretende guardar na cache, neste caso 0x00. Como a linha de
memória em que o processador pretende efetuar a escrita não esta armazenada na cache
,pois o sinal HitData é “0”, no ciclo de clock seguinte a cache faz um pedido de leitura à
memória de dados da linha de memória com o endereço dado pelo barramento Address.
A cache transita para o estado de funcionamento “READ_MEM” no instante 175ns e
mantem-se neste estado um ciclo de clock, durante este período a cache coloca o sinal
ReadRequest_m com valor “1”. No instante 185ns o sinal ReadReady comuta para “1”
indicando que a linha de dados requerida está disponível no barramento DataIn_m. A
linha de dados é guardada em cache e no ciclo de clock seguinte os dados são guardados
na posição pretendida dentro da linha de cache.
A Figura 6.11 por sua vez permite observar o comportamento da cache de dados a
quando de um pedido de leitura por parte do processador.
98
Figura 6.11 - Comportamento da cache de dados numa leitura
Pode-se observar na figura que no instante 395ns o sinal ReadRequest_p comuta
para o valor “1” o que significa que existe um pedido de leitura, o barramento Address
contém o endereço dos dados que se pretende ler da cache. Como o sinal HitData é “1”
significa que a linha da memória de dados correspondente ao endereço de onde se
pretende ler os dados, então a cache coloca de imediato os dados pedidos no barramento
DataOut_p e comuta o sinal DataReady_p para o valor “1” por forma a informar o
processador que os dados estão disponíveis.
Figura 6.12 - Linhas de dados da cache set 0
A Figura 6.12 apresenta o conteúdo das linhas da cache de dados no final da
execução do código, pode-se observar que toda a escrita foi feita no cache set 0, isto
deveu-se à utilização do algoritmo de substituição usado, o Pseudo-LRU. Na quinta
99
word do endereço 0x3A ficou guardado o resultado da multiplicação, na primeira word
do endereço 0x3B ficou guardado o resultado da soma e na quinta word da mesma linha
de cache ficou guardado o resultado da divisão.
A execução do benchmark 32-bit match foi concluída ao fim de 2105ns, tal como
mostra a figura figura 6.13, em termos de ciclos de relógio foram necessários 206 ciclos
(2105−4510
= 206, em que 2105 é o instante em que foi concluída a execução, 45ns é o
instante em que o sinal de reset vai a “0” iniciando assim o funcionamento e 10ns é o
tempo que demora cada ciclo de relógio). É neste instante que o valor do program
counter volta a ter valor “0”, o que significa que todas as linhas de código forma
executadas e que a ultima instrução executada provocou um return para o endereço “0”
da memória de código. Outro aspeto importante que a imagem ilustra é o número de
ciclos necessários para efetuar uma divisão, a divisão é efetuada na instrução “27” do
código e a sua execução dura 36 ciclos de relógio.
Figura 6.13 - Fim de execução do benchmark e duração da operação divisão
O desempenho de execução do Hc-Vex foi comparado com os microcontroladores
da familia MSP430 porque foi este o benchmark a que se teve acesso, de notar que a
comparação com micros de 16 bits não é a ideal para uma conclusão convincente, mas
serve para ter uma noção do desempenho. Podemos verificar que o número de ciclos
necessários no Hc-Vex foi inferior ao necessário em qualquer microcontrolador da
família MSP430.
Figura 6.14 - Número de ciclos necessários nos microcontroladores da família MSP430
101
CAPÍTULO 7
Conclusão
Neste capítulo são apresentadas as ilações com base no trabalho realizado. São
também enunciadas algumas propostas para trabalho futuro.
Conclusões 7.1.
Nesta tese foi apresentado o processador Hc-Vex, um processador VLIW open-
source reconfigurável e extensível baseado no ISA VEX. O objetivo desta dissertação
foi desenvolver e implementar numa plataforma FPGA em linguagem HDL Verilog um
processador reconfigurável de arquitetura VLIW.
O ISA escolhido para esta implementação foi o VEX, os fatores que motivaram a
sua escolha foram a sua configurabilidade, extensibilidade e a existência de um
compilador VEX fiável e livre desenvolvido pela HP.
Nesta implementação foi respeitada a organização do VEX, Os bancos de registos
implementados foram o General Register e o Branch Register com 64 registos de 32 bits
e 8 registos de 1 bit respetivamante. Foram implementadas duas caches, uma de acesso
à memória de dados e outra de acesso à memória de código. A cache de dados utiliza o
método de acesso do tipo 4-way set associative, a política de escrita é Write-Back policy
e o algoritmo de substituição utilizado foi Pseudo Least recently used (Pseudo-LRU). A
cache de código é do tipo Direct Mapped. O template das silabas adotado foi o utilizado
na implementação ρ-Vex pois respeita o ISA VEX.
O Assembler desenvolvido efetuou a assemblagem do código com sucesso,
organizando as silabas respeitando o template da instrução VLIW o que solucionou uma
lacuna do código gerado pelo compilador. Em termos binários as instruções foram
corretamente traduzidas para código máquina.
Relativamente aos resultados experimentais, foram executados testes com
diversos códigos em linguagem C, por forma a verificar e comprovar o correto
102
funcionamento do trabalho realizado. Os códigos máquina gerados pelo assembler
foram executados sempre com sucesso no processador implementado. Em termos de
latência de execução, ficou garantida a latência de execução definida pelo VEX.
Embora não tenham sido efetuados testes em FPGA, as simulações efetuadas
permitem concluir que o objetivo desta dissertação foi alcançado. Foi implementado um
processador extensível e reconfigurável de arquitetura VLIW, a linguagem HDL
utilizada foi verilog tal como era requerido, o funcionamento do processador foi testado
e comprovado o correto funcionamento devido aos resultados corretos que se obtiveram,
o funcionamento das caches implementadas teve o comportamento pretendido. O
assembler desenvolvido também teve os resultados esperados, o código máquina foi
gerado sempre de forma correta não havendo falhas binárias nas instruções do código a
executar.
Trabalho futuro 7.2.
Apesar de cumpridos os objetivos propostos, como trabalho futuro existe a
possibilidade de desenvolver um Hc-Vex multi-cluster ou multiprocessador por forma a
aumentar a desempenho do processador. Uma abordagem próxima da arquitetura EPIC
também pode ser uma mais-valia visto que o compilador desconhece o comportamento
em run-time o que o força a ser conservador no que diz respeito ao scheduling das
instruções.
103
Bibliografia
1]
W. F. Lee, VLIW Microprocessor Hardware Design for ASIC and FPGA,
McGraw-Hill, 2008.
2]
http://www.intel.com/support/pt/processors/pentiumiii/sb/CS-023730.htm.
3]
http://ark.intel.com/products/family/78132/Legacy-Intel-Pentium-Processor.
4]
http://www.intel.com/products/processor/core2duo/
5]
Intel, “http://ark.intel.com/products/41316,” [Online].
6]
Intel, “http://ark.intel.com/products/47933,” [Online].
7]
J. L. Linda Null, The essentials of computer organization and architecture,
Jones and Bartlett Publishers, Inc., 2003.
8]
http://www.nxp.com/products/automotive/multimedia/
9]
J. F. S. K. M. G. A. K. K. T. B. Kemal Ebcioglu, “An Eight-Issue Tree-
VLIW Processor for Dynamic Binary Translation”.
10]
https://www-01.ibm.com/chips/techlib/techlib.nsf/products/PowerPC
11]
“processadores intel itanium,” [Online]. Available:
http://www.intel.com.br/content/www/br/pt/processors/itanium/itanium-processor-
104
9000-sequence.html.
12]
P. F. C. Y. Joseph A. Fischer, Embedded Computing - A VLIW approach to
architecture, Compilers and Tools.
13]
M. A.-E.-B. Hesham El-Rewini, Advanced computer architecture -
Parallelism Scabability programmability, JOHN WILEY & SONS, INC, 2005.
14]
W. Stallings, Computer organization and architecture - Designing for
performance, Prentice Hall, 2003.
15]
Xilinx, “http://www.xilinx.com/tools/microblaze.htm,” [Online].
16]
Altera, “http://www.altera.com/devices/processor/arm/cortex-a9/m-arm-
cortex-a9.html,” [Online].
17]
Opencores, “http://opencores.org/or1k/OR1K:Community_Portal,” [Online].
18]
l. semiconductor,
“http://www.latticesemi.com/Products/DesignSoftwareAndIP.aspx,” [Online].
19]
microcore, “http://www.microcore.org/,” [Online].
20]
E. S. C. Iseli, Spyder: a Reconfigurable VLIW Processor using FPGAs.
21]
F. Y. M. P. V. Brost, A modular VLIW Processor.
22]
T. V. A. G. B. S. Wong, ρ-VEX: A reconfigurable and extensible softcore
VLIW processor, 2008.
B. R. R. Michael S. Schlansker, “EPIC: An architecture for instruction-level
105
23] parallel processors,” 2000.
24]
S. W. N. D. T. W. Bruce Jacob, Memory Systems - Cache, DRAM, Disk,
Elsevier, 2008.
25]
J. L. H. David A. Patterson, Computer organizations and design: The
hardware/ software interface, Morgan Kaufmann, 2009.
26]
H. E.-R. Mostafa Abd-El-Barr, Fundamentals of computer organization and
architecture, John Wiley & Sons, Inc., 2005.
27]
G. B. J. F. G. D. e. F. H. P. Faraboschi, “Lx:A Technology Platform for
Customizable VLIW Embedded Processing,” Proceedings of the 27th Annual
International Symposium of Computer, pp. 203 - 213, 2000.
28]
T. P. Series, “http://www.nxp.com/,” [Online].
29]
S. W. a. F. N. Fakhar Anjam, “A Shared Reconfigurable VLIW
Multiprocessor,” 2010.
30]
T. A. a. G. B. S. Wong, “ρ-VEX: A Reconfigurable and Extensible Softcore
VLIW Processor,” pp. 369 - 372, 2008.
31]
T. J. J. L. M. Joan Puiggalí, “Out-of-Order execution in Master/Slave
Speculative Parallelization Architecture for Computer Clusters,” 2011.
32]
H. Y. Z. Q. Y. Y. B. L. Haibing Guan, “The Optimizations in Dynamic
Binary Translation,” 2010.
107
Anexo A
Instruction Set Architecture VEX
Operações da ALU
Operação Tipo de semântica Opcode Descrição
ADD I 1000001 Adição
AND I 1000011 AND bit a bit
ANDC I 1000100 Complemento bit a bit e ADD
MAX I 1000101 Máximo com sinal
MAXU I 1000110 Máximo sem sinal
MIN I 1000111 Mínimo com sinal
MINU I 1001000 Mínimo sem sinal
OR I 1001001 OR bit a bit
ORC I 1001010 Complemento bit a bit e OR
SH1ADD I 1001011 1 Shift à esquerda e soma
SH2ADD 1001100 2 Shifts à esquerda e soma
SH3ADD 1001101 3 Shifts à esquerda e soma
SH4ADD 1001110 4 Shifts à esquerda e soma
SHL I 1001111 Shift à esquerda
SHR I 1010000 Shift à direita com sinal
SHRU I 1010001 Shift à direita sem sinal
SUB VI 1010010 Subração
SXTB VII 1010011 Extensão do sinal de um byte
SXTH VII 1010100 Extensão do sinal de meia word (16 bits)
ZXTB VII 1010101 Extensão dos zeros de um byte
ZXTH VII 1010110 Extensão do sinal de meia word (16 bits)
XOR I 1010111 XOR bit a bit
MOV VII 1011000 Cópia de s1 para outra localização
CMPEQ II 1011001 Comparação: igual
CMPGE II 1011010 Comparação: maior ou igual com sinal
108
Operação Tipo de semântica Opcode Descrição
CMPGEU II 1011011 Comparação: maior ou igual sem sinal
CMPGT II 1011100 Comparação: maior com sinal
CMPGTU II 1011101 Comparação: maior sem sinal
CMPLE II 1011110 Comparação: menor ou igual com sinal
CMPLEU II 1011111 Comparação: menor ou igual sem sinal
CMPLT II 1100000 Comparação: menor com sinal
CMPLTU II 1100001 Comparação: menor sem sinal
CMPNE II 1100010 Comparação: diferente
NANDL II 1100011 NAND lógico
NORL II 1100100 NOR lógico
ORL II 1100110 OR lógico
MTB V 1100111 Mover GR para BR
ANDL II 1101000 AND lógico
ADDCG IV 1111--- ADD com carry e geração de carry
DIVS IV 1110--- Passo de divisão com carry e geração de
carry
SLCT III 0111--- Seleciona S1 se condição verdadeira
SLCTF III 0110--- Seleciona S1 se condição falsa
Operações MUL
Operação Tipo de semântica Opcode Descrição
MPYLL I 0000001 Multiplicação com sinal low 16 * low 16 bits
MPYLLU I 0000010 Multiplicação sem sinal low 16 * low 16 bits
MPYLH I 0000011 Multiplicação com sinal low 16 (s1)* high 16
(s2) bits
MPYLHU I 0000100 Multiplicação sem sinal low 16 (s1)* high 16
(s2) bits
MPYHH I 0000101 Multiplicação com sinal high 16 * high 16 bits
MPYHHU I 0000110 Multiplicação sem sinal high 16 * high 16 bits
109
Operação Tipo de semântica Opcode Descrição
MPYL I 0000111 Multiplicação com sinal low 16(s2) * 32 (s1)
bits
MPYLU I 0001000 Multiplicação sem sinal low 16(s2) * 32 (s1)
bits
MPYH I 0001001 Multiplicação com sinal high 16(s2) * 32 (s1)
bits
MPYHU I 0001010 Multiplicação sem sinal high 16(s2) * 32 (s1)
bits
MPYHS I 0001011 Multiplicação com sinal high 16(s2) * 32 (s1)
bits e 16 shifts à esquerda
Operações CTRL
Operação Tipo de semântica Opcode Descrição
GOTO XIII 0100001 Salto incondicional relativo
IGOTO XIX 0100010 Salto incondicional indireto absoluto para
o link register
CALL XVI 0100011 Call incondicional relativo
ICALL XX 0100100 Call incondicional indireto absoluto para o
link register
BR VIII 0100101 Branch relativo condicional se condição
verdadeira
BRF VIII 0100110 Branch relativo condicional se condição
verdadeira
RETURN XVII 0100111 Pop stack frame e vai para link register
Operações MEM
Operação Tipo de semântica Opcode Descrição
LDW X 0010001 Leitura de word
LDH X 0010010 Leitura de meia word com bit de sinal
110
Operação Tipo de semântica Opcode Descrição
LDHU X 0010011 Leitura de meia word sem bit de sinal
LDB X 0010100 Leitura de byte com bit de sinal
LDBU X 0010101 Leitura de byte sem bit de sinal
STW XI 0010110 Escrita de word
STH XI 0010111 Escrita de meia word
STB XI 0011000 Escrita de byte
Operações mistas
Operação Tipo de semântica Opcode Descrição
STOP XIV 0011111 Operação Stop
NOP XIV 0000000 Sem operação
Semântica das instruções
Tabela 0.1 - Semântica do Assembly Vex
Tipo Semântica
I operação $r0.t = $r0.s1, $r0.s2
operação $r0.t = $r0.s1, s2
II
operação $r0.t = $r0.s1, $r0.s2
operação $r0.t = $r0.s1, s2
operação $b0.b = $r0.s1, $r0.s2
operação $b0.b = $r0.s1, s2
III operação $r0.t = $b0.b1, $r0.s1, $r0.s2
operação $r0.t = $b0.b1, $r0.s1, s2
IV operação $r0.t, $b0.b = $b0.b1, $r0.s1, $r0.s2
V operação $b0.b = $r0.s1
VI operação $r0.t = $r0.s2, $r0.s1
operação $r0.t = s2, $r0.s1
VII operação $r0.t = $r0.s1
111
Tipo Semântica
VIII operação $b0.b, label
IX operação $r0.s1, path
X operação $r0.t = offset[$r0.s1]
XI operação offset[$r0.s1] = $r0.s2
XII operação offset[$r0.s1]
XIII operação label
operação $r0.lr
XIV operação
XV operação n
XVI operação $l0.t = label
operação $l0.t = $l0.t
XVII operação $r0.t = $r0.t, label, $r0.lr
XVIII operação $r0.t = path
XIX operação $r0.lr
XX operação $l0.t = $l0.t
Tabela 0.2 - Simbologia da tabela 5.1
Simbologia
Operação Sigla correspondente à operação a executar
$r0.* Registo GR, podendo ser t (destino), s1 (fonte 1), s2 (fonte 2) ou lr(link
register).
$b0.* Registo BR, podendo ser b (destino) ou b1 (fonte 1)
S2 Operando imediato
Label Label para onde saltar nas operações de branch
Offset Offset utilizado nas operações de leitura e escrita da memória
n Número de ciclos (XNOP)
Patch Utilizado nas operações entre clusters (não suportado)
112
Anexo B
Descrição das operações VEX
Tabela 0.1 - Operações aritméticas VEX
Operação Tipo de semântica Descrição
ADD t = s1, {s2|im} I ADD(t,s1,s2) t = (s1) + (s2)
ADDCG t,b = b, s1, s2 IV
ADDCG(t,cout,s1,s2,cin) t = (s1) + (s2) + ((cin) & 0x1); cout = ((cin) & 0x1) ?
(UINT32(t) <= UINT32(s1)): (UINT32(t) < UINT32(s1));
AND t = s1, {s2|im} I AND(t,s1,s2) t = (s1) & (s2)
ANDC t = s1, {s2|im} I ANDC(t,s1,s2) t = ∼(s1) & (s2)
DIVS t ,b = b, s1, s2 IV
DIVS(t,cout,s1,s2,cin) unsigned tmp = ((s1) « 1) | (cin); cout = UINT32(s1) » 31; t = cout ? tmp + (s2) : tmp - (s2);
MAX t=s1,{s2|im} I
MAXU t=s1,{s2|im} I
MIN t=s1,{s2|im} I
MINU t=s1,{s2|im} I
OR t=s1,{s2|im} I OR(t,s1,s2) t = (s1) | (s2)
ORC t=s1,{s2|im} I ORC(t,s1,s2) t = (∼(s1)) | (s2)
SH1ADD t=s1,{s2|im} I SH1ADD(t,s1,s2) t = ((s1) « 1) + (s2)
SH2ADD t=s1,{s2|im} I SH2ADD(t,s1,s2) t = ((s1) « 2) + (s2)
SH3ADD t=s1,{s2|im} I SH3ADD(t,s1,s2) t = ((s1) « 3) + (s2)
SH4ADD t=s1,{s2|im} I SH4ADD(t,s1,s2) t = ((s1) « 4) + (s2)
SHL t=s1,{s2|im} I SHL(t,s1,s2) t = (INT32(s1)) « (s2)
SHR t=s1,{s2|im} I SHR(t,s1,s2) t = (INT32(s1)) » (s2)
SHRU t=s1,{s2|im} I SHRU(t,s1,s2) t = (UINT32(s1)) » (s2)
SUB t={s2|im},s1 VI SUB(t,s1,s2) t = (s1) - (s2)
SXTB t=s1 VII SXTB(t,s1) t = UINT32((INT32((s1) « 24)) » 24)
SXTH t=s1 VII SXTH(t,s1) t = UINT32((INT32((s1) « 16)) » 16)
ZXTB t=s1 VII ZXTB(t,s1) t = ((s1) & 0xff)
ZXTH t=s1 VII ZXTH(t,s1) t = ((s1) & 0xffff)
XOR t=s1,{s2|im} I XOR(t,s1,s2) t = (s1) ˆ (s2)
113
Tabela 0.2 - Operações de multiplicação VEX
Operação Tipo de semântica Descrição
MPYLL t=s1,{s2|im} I MPYLL(t,s1,s2) t = INT16(s1) * INT16(s2)
MPYLLU t=s1,{s2|im} I MPYLLU(t,s1,s2) t = UINT16(s1) * UINT16(s2)
MPYLH t=s1,{s2|im} I MPYLH(t,s1,s2) t = INT16(s1) * INT16((s2) » 16)
MPYLHU t=s1,{s2|im} I MPYLHU(t,s1,s2) t = UINT16(s1) * UINT16((s2) » 16)
MPYHH t=s1,{s2|im} I MPYHH(t,s1,s2) t = INT16((s1) » 16) * INT16((s2) » 16)
MPYHHU t=s1,{s2|im} I MPYHHU(t,s1,s2) t = UINT16((s1) » 16) * UINT16((s2) » 16)
MPYL t=s1,{s2|im} I MPYL(t,s1,s2) t = (s1) * INT16(s2)
MPYLU t=s1,{s2|im} I MPYLU(t,s1,s2) t = (s1) * UINT16(s2)
MPYH t=s1,{s2|im} I MPYH(t,s1,s2) t = (s1) * INT16((s2) » 16)
MPYHU t=s1,{s2|im} I MPYHU(t,s1,s2) t = (s1) * UINT16((s2) » 16)
MPYHS t=s1,{s2|im} I MPYHS(t,s1,s2) t = ((s1) * INT16((s2) » 16)) « 16
Tabela 0.3 - Operações lógicas e de seleção VEX
Operação Tipo de semântica Descrição
CMPEQ {t|b}=s1,{s2|im} II CMPEQ(t,s1,s2) t = ((s1) == (s2))
CMPGE {t|b}=s1,{s2|im} II CMPGE(t,s1,s2) t = (INT32(s1) >= INT32(s2))
CMPGEU {t|b}=s1,{s2|im} II CMPGEU(t,s1,s2) t = (UINT32(s1) >= UINT32(s2))
CMPGT {t|b}=s1,{s2|im} II CMPGT(t,s1,s2) t = (INT32(s1) > INT32(s2))
CMPGTU {t|b}=s1,{s2|im} II CMPGTU(t,s1,s2) t = (UINT32(s1) > UINT32(s2))
CMPLE {t|b}=s1,{s2|im} II CMPLE(t,s1,s2) t = (INT32(s1) <= INT32(s2))
CMPLEU {t|b}=s1,{s2|im} II CMPLEU(t,s1,s2) t = (UINT32(s1) <= UINT32(s2))
CMPLT {t|b}=s1,{s2|im} II CMPLT(t,s1,s2) t = (INT32(s1) < INT32(s2))
CMPLTU {t|b}=s1,{s2|im} II CMPLTU(t,s1,s2) t = (UINT32(s1) < UINT32(s2))
CMPNE {t|b}=s1,{s2|im} II CMPNE(t,s1,s2) t = ((s1) != (s2))
NANDL {t|b}=s1,s2 II NANDL(t,s1,s2) t = (((s1) == 0) | ((s2) == 0)) ? 1 : 0
NORL {t|b}=s1,s2 II NORL(t,s1,s2) t = (((s1) == 0) & ((s2) == 0)) ? 1 : 0
ORL {t|b}=s1,s2 II ORL(t,s1,s2) t = (((s1) == 0) & ((s2) == 0)) ? 0 : 1
114
Operação Tipo de semântica Descrição
SLCT t=b,s1,{s2|im} III SLCT(t,s1,s2,s3) t = UINT32(((s1) == 1) ? (s2) : (s3))
SLCTF t=b,s1,{s2|im} III SLCTF(t,s1,s2,s3) t = UINT32(((s1) == 0) ? (s2) : (s3))
Tabela 0.4 - Operações de Acesso à memória VEX
Operação Tipo de semântica Descrição
LDW t = im[s] X LDW(t,s1) t = INT32(MEM32(s1))
LDH t = im[s1] X LDH(t,s1) t = INT16(MEM16(s1))
LDHU t = im[s1] X LDHU(t,s1) t = UINT16(MEM16(s1))
LDB t = im[s1] X LDB(t,s1) t = INT8(MEM8(s1))
LDBU t = im[s1] X LDBU(t,s1) t = UINT8(MEM8(s1))
STW im[s1] = s2 XI STW(t,s1) MEM32(t) = UINT32(s1)
STH im[s1] = s2 XI STH(t,s1) MEM16(t) = UINT16(s1)
STB im[s1] = s2 XI STB(t,s1) MEM8(t) = UINT8(s1)
Tabela 0.5 - Operações de controlo VEX
Operação Tipo de semântica Descrição
GOTO off XIII GOTO(off) goto_instr(off)
IGOTO lr XIX IGOTO(lr) goto_instr(lr)
CALL lr = im XVI CALL(off,lr) { lr = next_instr(); goto_instr(off); }
ICALL lr = lr XX ICALL(off) { lr = next_instr(); goto_instr(s1); }
BR b, off VIII BR(b1,off) if (b1 == 1) goto_instr(off)
BRF b, off VIII BRF(b1,off) if (b1 == 0) goto_instr(off)
RETURN t = t, off, lr XVII RETURN(sp,off,lr) { sp += off; goto_instr(lr); }
XNOP n XV -