henrique miguel basto da costa desenvolvimento de um ... · ao meu orientador doutor pelo tempo...

138
Henrique Miguel Basto da Costa Desenvolvimento de um Processador VLIW Henrique Miguel Basto da Costa outubro de 2013 UMinho | 2013 Desenvolvimento de um Processador VLIW Universidade do Minho Escola de Engenharia

Upload: dophuc

Post on 27-Oct-2018

213 views

Category:

Documents


0 download

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

“I don´t know the key to success, but the key to failure is trying to please everyone”

(Bill Cosby)

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 -