Ferramenta de Análise de Código para Detecção de Vulnerabilidades
ChipCflow: ferramenta para conversão de código C em uma ...
Transcript of ChipCflow: ferramenta para conversão de código C em uma ...
ChipCflow: ferramenta para conversão de código C em uma arquitetura a fluxo de dados estática em hardware
reconfigurável
Antonio Carlos Fernandes da Silva
ChipCflow: ferramenta para conversão de código C em uma arquitetura a fluxo
de dados estática em hardware reconfigurável
Antonio Carlos Fernandes da Silva
Orientador: Prof. Dr. Jorge Luiz e Silva
Tese apresentada ao Instituto de Ciências
Matemáticas e de Computação - ICMC-USP,
como parte dos requisitos para obtenção do título
de Doutor em Ciências - Ciências de Computação
e Matemática Computacional. VERSÃO
REVISADA
USP – São Carlos
Março de 2015
SERVIÇO DE PÓS-GRADUAÇÃO DO ICMC-USP
Data de Depósito:
Assinatura:________________________
______
Ficha catalográfica elaborada pela Biblioteca Prof. Achille Bassi e Seção Técnica de Informática, ICMC/USP,
com os dados fornecidos pelo(a) autor(a)
FF354cc
Fernandes da Silva, Antonio Carlos ChipCFlow: ferramenta para conversão de código Cem uma arquitetura a fluxo de dados em hardwarereconfigurável / Antonio Carlos Fernandes da Silva;orientador Jorge Luiz e Silva. -- São Carlos, 2015. 100 p.
Tese (Doutorado - Programa de Pós-Graduação emCiências de Computação e Matemática Computacional) -- Instituto de Ciências Matemáticas e de Computação,Universidade de São Paulo, 2015.
1. Arquitetura a Fluxo de Dados. 2. Conversão decódigo. 3. FPGA. I. Silva, Jorge Luiz e, orient. II.Título.
Dedico este trabalho a meus pais Leonilce e Severino (in memoriam), e a
minha esposa Elizabeth, pelo apoio durante esta jornada e a todos os amigos
que caminharam junto comigo.
v
Agradecimentos
Agradeço aos meus pais e minha esposa, pelo apoio, incentivo e compreen-
são pelos períodos de ausência durante o desenvolvimento deste trabalho.
Ao meu orientador Prof. Dr. Jorge Luiz e Silva, pelo apoio, orientação
acadêmica e amizade.
Aos amigos do projeto ChipCflow, em especial ao Bruno e ao Joelmir, pelas
discussões proveitosa e pelos momentos de descontração.
Aos amigos do LCR, Erinaldo, Jean, Helson, Marcilyanne, Arnaldo, André,
Eva, Cristiano, Lobo, Daniel, pelo momentos de diversão e pelo ótimo ambiente
do Laboratório.
Aos amigos da UTFPR, pela cooperação no período que estive ausente.
ii
Nem tudo que se enfrenta
pode ser modificado mas
nada pode ser modificado
até que seja enfrentado.
Albert Einstein
iii
iv
Resumo
Existe uma crescente busca por softwares e arquiteturas alternativas. Essabusca acontece pois houveram avanços na tecnologia do hardware, e estesavanços devem ser complementados por inovações nas metodologias de proje-tos, testes e verificação para que haja um uso eficaz da tecnologia. Os softwaree arquiteturas alternativas, geralmente são modelos que exploram o parale-lismo das aplicações, ao contrário do modelo de Von Neumann. Dentre asarquiteturas alternativas de alto desempenho, tem-se a arquitetura a fluxode dados. Nesse tipo de arquitetura, o processo de execução de programas édeterminado pela disponibilidade dos dados, logo o paralelismo está embutidona própria natureza do sistema. O modelo a fluxo de dados possui a vantagemde expressar o paralelismo de maneira intrínseca, eliminando a necessidadedo programador explicitar em seu código os trechos onde deve haver parale-lismo. As arquiteturas a fluxo de dados voltaram a ser uma área de pesquisadevido aos avanços do hardware, em particular, os avanços da ComputaçãoReconfigurável e dos Field Programmable Gate Arrays (FPGAs).Nesta tese édescrita uma ferramenta de conversão de código que visa a geração de aplica-ções utilizando uma arquitetura a fluxo de dados estática. Também é descritoo projeto ChipCflow, cuja ferramenta de conversão de código, descrita nestatese, é parte integrante. A especificação do algoritmo a ser convertido é feitaem linguagem C e convertida para uma linguagem de descrição de hardware,respeitando o modelo proposto pelo ChipCflow. Os resultados alcançados vi-sam a prova de conceito da conversão de código de uma linguagem de altonível para uma arquitetura a fluxo de dados a ser configurada em FPGA.
Palavras-chave: Fluxo de Dados, Código C, Compilador, VHDL, FPGA, ChipC-flow.
vi
Abstract
A growing search for alternative architectures and softwares have been no-ted in the last years. This search happens due to the advance of hardwaretechnology and such advances must be complemented by innovations on de-sign methodologies, test and verification techniques in order to use technologyeffectively. Alternative architectures and softwares, in general, explores theparallelism of applications, differently to Von Neumann model. Among highperformance alternative architectures, there is the Dataflow Architecture. Inthis kind of architecture, the process of program execution is determined bydata availability, thus the parallelism is intrinsic in these systems. The da-taflow architectures become again a highlighted search area due to hardwareadvances, in particular, the advances of Reconfigurable Computing and FieldProgrammable Gate Arrays (FPGAs). ChipCflow projet is a tool for executionof algorithms using dynamic dataflow graph in FPGA. In this thesis, the deve-lopment of a code conversion tool to generate aplications in a static dataflowarchitecture, is described. Also the ChipCflow project where the code conver-sion tool is part, is presented. The specification of algorithm to be converted ismade in C language and converted to a hadware description language, respec-ting the proposed by ChipCflow project. The results are the proof of conceptof converting a high-level language code for dataflow architecture to be usedinto a FPGA.
Palavras-chave: DataFlow, C code, Compiler, VHDL, FPGA, ChipCflow.
viii
Sumário
Sumário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi
Lista de Figuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv
Lista de Tabelas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvii
Lista de Códigos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xix
Lista de Abreviaturas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxiii
1 Introdução 11.1 Motivação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Contribuições . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.4 Organização do Texto . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2 Computação Reconfigurável 52.1 Modelos de Computação . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Field Programmable Gate Array (FPGA) . . . . . . . . . . . . . . . . 7
2.3 Arquiteturas Híbridas FPGA/CPU . . . . . . . . . . . . . . . . . . . 9
2.3.1 GARP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3.2 Dynamic Instruction Set Computer (DISC) . . . . . . . . . . 11
2.4 Linguagens de Descrição de Hardware . . . . . . . . . . . . . . . . 13
2.4.1 VHDL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.4.2 Verilog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.5 Considerações Finais . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3 Compilação 153.1 O Que é Um Compilador . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2 O Processo de Compilação . . . . . . . . . . . . . . . . . . . . . . . 16
3.2.1 Fase de Análise . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.2.2 Fase de Síntese . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.3 Compilação para Arquiteturas Reconfiguráveis . . . . . . . . . . . 21
ix
3.3.1 Grafos para Representação Intermediária . . . . . . . . . . 21
3.4 Considerações Finais . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4 Arquitetura a Fluxo de Dados 254.1 Linguagem Básica para o Modelo a Fluxo de Dados . . . . . . . . 26
4.1.1 Estruturas Condicionais . . . . . . . . . . . . . . . . . . . . 27
4.1.2 Estruturas Iterativas e reentrância . . . . . . . . . . . . . . 27
4.2 Modelos de Arquiteturas a Fluxo de Dados . . . . . . . . . . . . . . 29
4.2.1 Arquitetura Estática . . . . . . . . . . . . . . . . . . . . . . . 30
4.2.2 Arquitetura Dinâmica . . . . . . . . . . . . . . . . . . . . . . 30
4.3 Descrição de Arquiteturas Propostas . . . . . . . . . . . . . . . . . 32
4.3.1 Máquina a Fluxo de Dados Estática . . . . . . . . . . . . . . 32
4.3.2 Máquina de Manchester . . . . . . . . . . . . . . . . . . . . . 35
4.3.3 MIT - Tagged-Token Dataflow (TTD) . . . . . . . . . . . . . . 36
4.3.4 Wavescalar . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.3.5 TRIPS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
4.4 Considerações Finais . . . . . . . . . . . . . . . . . . . . . . . . . . 43
5 Compiladores para Hardware 455.1 Trident . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
5.2 Molen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
5.3 HThreads: Modelo de programação multithreads . . . . . . . . . . 48
5.4 ARISE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.5 Nenya / Galadriel . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
5.6 ROCCC (Riverside Optimizing Configurable Computing Compiler) . 53
5.7 Spark . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.8 Considerações Finais e Analise Comparativa. . . . . . . . . . . . . 54
6 O Projeto ChipCflow 576.1 Operadores propostos para a máquina a fluxo de dados estática
do projeto ChipCflow . . . . . . . . . . . . . . . . . . . . . . . . . . 58
6.1.1 Operadores de processamento . . . . . . . . . . . . . . . . . 59
6.1.2 Operador para controle de Loop . . . . . . . . . . . . . . . . 60
6.1.3 Operadores de Memória . . . . . . . . . . . . . . . . . . . . . 63
6.1.4 Protocolo de comunicação entre operadores . . . . . . . . . 63
6.2 Considerações Finais . . . . . . . . . . . . . . . . . . . . . . . . . . 64
7 Conversor de Código C em Aplicações ChipCflow 657.1 Conversão de Código C . . . . . . . . . . . . . . . . . . . . . . . . . 66
7.1.1 Front-End . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
x
7.1.2 Back-End . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
7.2 Considerações Finais . . . . . . . . . . . . . . . . . . . . . . . . . . 76
8 Resultados 778.1 Testes Realizados . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
8.1.1 Algoritmo de Fibonacci . . . . . . . . . . . . . . . . . . . . . 77
8.1.2 Algoritmo de Fatorial . . . . . . . . . . . . . . . . . . . . . . 80
8.1.3 Algoritmo Determinante . . . . . . . . . . . . . . . . . . . . . 82
8.2 Análise Comparativa . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
9 Conclusões e Trabalhos Futuros 899.1 Trabalhos Futuros . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Referências Bibliográficas 99
A Apendice1 101
xi
xii
Lista de Figuras
2.1 Arquitetura reconfigurável típica [Cardoso & Dehon, 2010]. . . . . 7
2.2 Estrutura de um FPGA [Hauck & Dehon, 2007]. . . . . . . . . . . 8
2.3 Exemplo de LUT de duas entradas [Bobda, 2007]. . . . . . . . . . 8
2.4 Arquitetura básica do projeto GARP [Hauser & Wawrzynek, 1997]. 10
2.5 Fluxo de desenvolvimento de aplicações [Hauser & Wawrzynek,
1997]. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.6 Arquitetura do sistema DISC [Wirthlin & Hutchings, 1995]. . . . . 12
2.7 Arquitetura do processador DISC [Wirthlin & Hutchings, 1995]. . 13
3.1 Fluxo básico do processo de compilação [Aho et al., 2007]. . . . . 16
3.2 Estrutura básica do processo de compilação [Muchnick, 1997]. . 16
3.3 Locais para melhorias potenciais por parte de usuário e do com-
pilador [Aho et al., 2007]. . . . . . . . . . . . . . . . . . . . . . . . . 20
3.4 Exemplo de CFG, CDG, DFG e DDG [Duarte, 2006] . . . . . . . . 22
3.5 Exemplo de HTG [Silva, 2009]. . . . . . . . . . . . . . . . . . . . . . 23
3.6 Exemplo de CDFG [Silva et al., 2009]. . . . . . . . . . . . . . . . . 24
4.1 Processo de disparo de um operador em uma arquitetura a fluxo
de dados. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.2 Operadores branch, merge não determinístico e decisão, adap-
tado de [Veen, 1986]. . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.3 Grafo correspondente a estrutura condicional if teste then f(x, y)
else g(x, y) [Veen, 1986]. . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.4 Problemas com ciclos em arquiteturas a fluxo de dados [Veen,
1986]. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.5 Construção de loop com controle de reentrância [Veen, 1986]. . . 29
4.6 Métodos para controle de tags em arquiteturas estáticas [Silva,
2011]. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
xiii
4.7 Métodos para controle de tags em arquiteturas dinâmicas [Silva,
2011]. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.8 Arquitetura básica da máquina a fluxo de dados estática [Dennis,
1980]. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.9 Célula de Instrução [Dennis & Misunas, 1975]. . . . . . . . . . . . 33
4.10Operadores básicos [Dennis & Misunas, 1975]. . . . . . . . . . . . 34
4.11Tipos de enlaces [Dennis & Misunas, 1975]. . . . . . . . . . . . . . 34
4.12Arquitetura da máquina de Manchester [Gurd et al., 1985]. . . . . 36
4.13Operadores definidos para a arquitetura TTDA [Arvind, 2005]. . . 37
4.14Formato da instrução da linguagem ID utilizada pela TTDA [Ar-
vind & Nikhil, 1990]. . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.15Elemento de processamento definido para a TTDA [Arvind & Nikhil,
1990]. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.16Exemplo de código gerado pelo WaveScalar [Swanson et al., 2007]. 40
4.17Estrutura de um cluster do WaveCache [Swanson et al., 2007]. . 41
4.18Estrutura do pipeline de execução do WaveScalar [Swanson et al.,
2007]. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
4.19Exemplo do mecanismo de anotação utilizado pelo WaveScalar[Swanson et al., 2007]. . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.20Arquitetura do TRIPS [Sankaralingam et al., 2003]. . . . . . . . . 43
5.1 Fluxo de Compilação Trident [Tripp et al., 2007]. . . . . . . . . . . 47
5.2 Fluxo de Compilação HThreads [Andrews et al., 2008a]. . . . . . . 48
5.3 Organização Geral de uma Máquina ARISE. [Vassiliadis et al.,
2009]. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.4 Fluxo de desenvolvimento de aplicações no ARISE. [Vassiliadis
et al., 2009]. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
5.5 Fluxo dos compiladores Galadriel e Nenya [Cardoso, 2000]. . . . . 52
5.6 Fluxo de funcionamento do ROCCC [Buyukkurt et al., 2006]. . . 53
5.7 Fluxo de funcionamento do Spark [Gupta et al., 2004]. . . . . . . 55
6.1 Fluxo de execução do ChipCflow, adaptado de [Silva, 2006]. . . . 58
6.2 Tipos de links utilizados no projeto da máquina a fluxo de dados
estática do projeto ChipCflow [Arvind, 2005]. . . . . . . . . . . . . 59
6.3 Operadores do projeto da máquina a fluxo de dados estática do
projeto ChipCflow [Silva, 2006]. . . . . . . . . . . . . . . . . . . . . 60
6.4 Operadores usados em uma máquina a fluxo de dados com da-
dos chegando em seus arcos de entrada, e dados saindo após a
computação [Teifel & Manohar, 2004]. . . . . . . . . . . . . . . . . 60
xiv
6.5 (A) Controle de Loops utilizando os operadores convencionais de
uma máquina a fluxo de dados. (B) Controle de Loop usando o
operador For. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
6.6 Operador For. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
6.7 Exemplo de operadores For aninhados. . . . . . . . . . . . . . . . . 62
6.8 Formato dos operadores Load e Store. . . . . . . . . . . . . . . . . 63
6.9 Protocolo de Handshake entre operadores. . . . . . . . . . . . . . . 64
7.1 Fluxo de funcionamento da ferramenta de conversão de código. . 66
7.2 Fluxo de funcionamento do Flex. . . . . . . . . . . . . . . . . . . . 68
7.3 Exemplo de arquivo .COE. . . . . . . . . . . . . . . . . . . . . . . . 69
7.4 Exemplo de uso dos operadores branch e merge. . . . . . . . . . . 73
8.1 Grafo gerado para o algoritmo de Fibonacci. . . . . . . . . . . . . . 79
8.2 Trecho do código VHDL gerado para o algoritmo de Fibonacci. . . 80
8.3 Grafo gerado para o algoritmo de Fatorial. . . . . . . . . . . . . . . 81
8.4 Grafo gerado para o algoritmo de determinante. . . . . . . . . . . 84
8.5 Resultados das execuções do algoritmo de Fibonacci [Silva &
Silva, 2014] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
8.6 Resultados das execuções do algoritmo de para calculo Fatorial
[Silva & Silva, 2014] . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
8.7 Resultados das execuções do algoritmo de para calculo determi-
nante. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
xv
xvi
Lista de Tabelas
3.1 Exemplos de tokens [Aho et al., 2007]. . . . . . . . . . . . . . . . . 17
5.1 Comparação entre os compiladores estudados. . . . . . . . . . . . 55
7.1 Sub-conjunto da linguagem C . . . . . . . . . . . . . . . . . . . . . 67
7.2 Representação dos operadores no código intermediário . . . . . . 71
7.3 Representação das pseudo-instruções no código intermediário . . 72
8.1 Resultados obtidos após a síntese do algoritmo de Fibonacci no
modelo da máquina a fluxo de dados do projeto ChipCflow. . . . . 79
8.2 Resultados obtidos após a síntese do algoritmo de fatorial no
ChipCflow. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
8.3 Resultados obtidos após a síntese do algoritmo de determinante
no ChipCflow. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
8.4 Resultados das execuções do algoritmo de Fibonacci. . . . . . . . 85
8.5 Resultados das execuções do algoritmo de para calculo Fatorial. . 86
8.6 Resultados das execuções do algoritmo de para calculo de Deter-
minante. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
xvii
xviii
Lista de Códigos
1 Exemplo de instruções de três endereços [Aho et al., 2007]. . . . . 19
2 Exemplo de For aninhado. . . . . . . . . . . . . . . . . . . . . . . . . 62
3 Trecho da tabela de símbolos. . . . . . . . . . . . . . . . . . . . . . . 68
4 Instrução quebrada em instruções de três endereços. . . . . . . . . 70
5 Trecho de instruções de três endereços geradas. . . . . . . . . . . . 70
6 Trecho de instruções de três endereços com operadores específicos
da arquitetura. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
7 Trecho de instruções de três endereços com operadores específicos
da arquitetura. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
8 Trecho de código intermediário com a substituição de nomes das
variáveis. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
9 Exemplo de Fibonacci descrito em Linguagem C. . . . . . . . . . . . 78
10 Código intermediário gerado para o algoritmo de Fibonacci. . . . . 78
11 Exemplo de Fatorial descrito em Linguagem C. . . . . . . . . . . . . 81
12 Código intermediário gerado para o algoritmo de Fatorial. . . . . . 81
13 Trecho do código do algoritmo de determinante implementado. . . 83
xix
xx
Lista de Abreviaturas
ADL Add to Interaction Level
ADR Add Floating-point Values
ARISE Aristotle Reconfigurable Instruction Set Extension
ASICs Aplication Specific Integrated Circuit
BRR Branch
CDFG Control Dataflow Graph
CDG Control Dependence Graph
CFG Control Flow Graph
CGR Compare Floating-point Values
CLAy Configurable Logic Array
CPU Central Processing Unit
DDG Data Dependence Graph
DFG Data Flow Graph
DISC Dynamic Instruction Set Computer
DUP Explicit Duplicate
EABI Embedded Application Binary Interface
EDA Eletronic Design Automation
FIFO First-in-first-out
xxi
FPGA Field Programmable Gate Array
FPGAs Field Programmable Gate Arrays
GCC Gnu C Compiler
GFCD Grafo de fluxo de controle-dados
GPU Graphics Processing Unit
HDL Hardware Definition Language
HTG Hierarchical Task Graph
ICMC Instituto de Ciências Matemáticas e de Computação
ITG Initial Tag Generator
IEEE Institute of Electrical and Electronic Engineers
ISA Industry Standard Architecture
LCR Laboratório de Computação Reconfigurável
LLVM Low Level Virtual Machine
LUT Look-up table
MLR Multiply Floating-point Values
NIG New Iteration Generator
NTD New Tag Destructor
NTM New Tag Manager
OPT Output to Host Processor
OTT Old Tag Table
OVI Open Verilog International
PC Program Counter
RPU Unidade de Processamento Reconfigurável
UCP Unidade Central de Processamento
SIL Set Iteration Level
xxii
SRAM Static Random Access Memory
SSD Solid-State Drive
TR Tag Remover
TTD Tagged-Token Dataflow
TTDA Tagged-Token Dataflow Architecture
ULAs Unidades Lógica e Aritmética
VHDL Very High Speed Integrated Circuit - Hardware Description Language
VLIW Very Long Instruction Word
xxiii
xxiv
CAPÍTULO
1Introdução
1.1 Motivação
Segundo Cardoso [2000] existem áreas de aplicação nas quais o uso da
computação reconfigurável permite alcançar desempenho inalcançáveis por
processadores de uso geral, visto que estas arquiteturas permitem explorar de
forma eficiente o paralelismo. Dentro deste contexto e dos objetivos do projeto
ChipCflow [Silva et al., 2009], busca-se o desenvolvimento de um ferramenta
para gerar uma arquitetura a fluxo de dados a ser executada em um FPGA a
partir de código em linguagem de alto nível.
Além do projeto ChipCflow outras pesquisas estão sendo feitas para o de-
senvolvimento de máquinas baseadas no modelo a fluxo de dados, como as
encontradas em [Swanson et al., 2007] e [Sankaralingam et al., 2003].
A programação para este tipo de arquitetura depende do conhecimento em
projeto de hardware, por isso um grande desafio é criar ferramentas de apoio
que permitam a programadores de linguagem de alto nível gerar circuitos de
hardware com qualidade. Segundo Cardoso [2003] os grandes atrativos destas
ferramentas estão no fato de facilitarem a especificação de sistemas comple-
xos, pois aumentam o nível de abstração do projeto de hardware, e por per-
mitirem a migração de diversos algoritmos já existentes e implementados em
linguagem de alto nível como C/C++.
Além da migração de aplicações existentes, outra área que pode ser ex-
plorada é a criação de aplicações para sistemas embarcados. Os resultados
1
demonstram que o consumo de energia da arquitetura gerado é baixo, isso
aliado ao bom desempenho alcançado pode ser um motivador para maiores
estudos nesta área de desenvolvimento.
1.2 Objetivos
O principal objetivo deste trabalho foi o desenvolvimento de uma ferra-
menta de geração de código que permitisse a conversão de aplicativos descrito
em Linguagem C, para aplicações executáveis utilizando o modelo de arquite-
tura a fluxo de dados estática em FPGA, como parte fundamental do projeto
ChipCflow.
A partir da ferramenta desenvolvida teve-se como objetivo realizar a prova
de conceito, para a geração de arquiteturas a fluxo de dados estáticas, tendo
como código de entrada uma linguagem de alto nível. Esta ferramenta utiliza
técnicas de desenvolvimento de compiladores para software, além de mapea-
mento de grafos voltados a exploração do paralelismo a nível de instruções,
ponto este essencial para a obtenção de um bom desempenho de uma arqui-
tetura a fluxo de dados.
1.3 Contribuições
A principal contribuição encontra-se na prova de conceito proposta para
a conversão de código. Além desta contribuição, pode-se destacar o estudo
para o desenvolvimento da arquitetura a fluxo de dados estática desenvolvida
e descrita neste trabalho. A execução de algoritmos e a avaliação de seus
resultados serviram para demonstrar a viabilidade da conversão de códigos
descritos em linguagem de alto nível para uma arquitetura a fluxo de dados
estática.
Além da prova de conceitos, a comparação do desempenho alcançado na
execução de algoritmos, demonstra que arquiteturas a fluxo de dados estática
são uma boa opção para o desenvolvimento de aceleradores de hardware com
baixo consumo de energia e alto poder de processamento.
A demonstração da viabilidade de conversão de código e o bom desempenho
alcançado pela arquitetura, proporcionam um ambiente favorável ao avanço
das pesquisas nesta área de estudo.
2
1.4 Organização do Texto
Para contextualizar o trabalho, descrever seu desenvolvimento e apresen-
tar os resultados e conclusões, o presente documento está organizado como
segue: No Capítulo 2 é apresentada uma visão geral sobre os modelos de
computação, bem como os FPGAs, arquiteturas híbridas e linguagens de des-
crição de hardware. Em seguida, no Capítulo 3 é apresentada uma descrição
do processo de compilação tradicional e as particularidades da compilação
para arquiteturas reconfiguráveis. No Capítulo 4 é apresentada a arquitetura
a fluxo de dados, seu fluxo de execução, modelos e algumas das máquinas
a fluxo de dados propostas. No Capítulo 5 é apresentada uma revisão de al-
guns trabalhos relacionados a geração de código visando o desenvolvimento
de hardware. No Capítulo 6 é apresentado o projeto ChipCflow, base para o
desenvolvimento deste trabalho. No Capítulo 7 é apresentada a ferramenta
de geração de código do projeto ChipCflow, sua representação intermediária,
operadores disponíveis, grafos gerados, e demais passos necessários para a
geração do código em linguagem de descrição de hardware equivalente a ar-
quitetura proposta. No Capítulo 8 são apresentados os testes da ferramenta
e os resultados obtidos. Os dados obtidos são comparados com outras arqui-
teturas. No Capítulo 9 são apresentadas as conclusões deste trabalho, bem
como sugestões para sua continuidade.
3
4
CAPÍTULO
2Computação Reconfigurável
Neste capítulo será descrito o conceito de computação reconfigurável bem
como tecnologias ligadas a este paradigma de computação. O conceito de
computação reconfigurável foi apresentando por Estrin [Estrin et al., 1963],
e apresenta a possibilidade de unir a flexibilidade de um processador de uso
geral com a velocidade de um processador de uso específico. As características
destes tipos de processadores são apresentadas na próxima seção.
2.1 Modelos de Computação
Tradicionalmente os algoritmos podem ser executados com o uso de Apli-cation Specific Integrated Circuit (ASICs) ou microprocessadores de uso geral
[Hauck & Dehon, 2007].
Os ASICs são desenvolvidos especificamente para uma aplicação, e por este
motivo apresentam grande desempenho na execução, por outro lado não pos-
suem flexibilidade, ou seja, caso a aplicação sofra alterações o ASIC deve ser
totalmente reprojetado, o que gera alto custo. Por outro lado, os processa-
dores de uso geral são dispositivos que permitem sua programação para a
execução de qualquer tipo de operação digital, alterando-se o seu conjunto de
instruções, sem a necessidade de alteração de hardware. Estes processadores
têm um desempenho menor quando comparado com ASICs, mas apresentam
flexibilidade maior [Hauck & Dehon, 2007].
Para tentar unir as melhores características de cada um destes modelos de
5
computação surgiu a computação reconfigurável que, segundo Panainte et al.
[2007], vem demonstrando um grande crescimento devido à sua promessa de
aliar desempenho do hardware e a flexibilidade do software. O conceito básico
deste modelo surgiu nos anos 60, quando Estrin [Estrin et al., 1963] desen-
volveu uma máquina que poderia ser reconfigurada por meio da alteração da
interconexão de seus componentes, que era feita por meio de um painel de
fios e conectores. Este é o mesmo conceito utilizado atualmente, com a uti-
lização de componentes de hardware que podem ter sua lógica alterada por
meio de código gerado em alguma linguagem de descrição de hardware, em
substituição aos fios e conectores da máquina de Estrin.
Embora os sistemas reconfiguráveis apresentem uma alternativa para a
crescente demanda por desempenho e redução de consumo elétrico, a geração
de arquiteturas reconfiguráveis é complexa e exige conhecimentos em hard-
ware e software para otimizar seu funcionamento [Panainte et al., 2007]. Neste
cenário não atrativo para programadores em geral, devido a complexidade do
projeto de hardware, várias ferramentas vêm sendo desenvolvidas com o ob-
jetivo de permitir que um programador, usando linguagem de alto nível, gere
um sistema eficiente e que possa ser comparado ao sistema gerado de forma
manual por um especialista da área.
Segundo Hauck & Dehon [2007] a geração de sistemas reconfiguráveis pode
ser realizada de forma:
• Automática: meio simples e rápido para a descrição que pode ser feita em
linguagem de alto nível ou em linguagens de descrição de hardware, como
por exemplo Very High Speed Integrated Circuit - Hardware DescriptionLanguage (VHDL) e Verilog. Esta descrição é substituída por elementos
de hardware, que são depois mapeados para os componentes específicos
da arquitetura do dispositivo reconfigurável (mapeamento tecnológico). O
compilador pode ser responsável por detectar automaticamente as partes
da descrição que deverão ser mapeadas para hardware e software;
• Automática/Manual: neste tipo de processo o projetista decide quais as
partes do sistema vão ser mapeadas para hardware e software. Depois
de feita a descrição estrutural do sistema, a mesma pode ser mapeada
para o dispositivo reconfigurável por ferramentas de fabricantes;
• Manual: neste tipo de processo o projetista é responsável por toda a
definição do sistema, tornando o processo lento e mais suscetível a erros.
O desenvolvimento deste tipo de arquitetura normalmente baseia-se em
uma Unidade de Processamento Reconfigurável (RPU), usado como um co-
6
processador acoplado a um sistema que utiliza um processador de uso geral
[Cardoso & Dehon, 2010]. Um exemplo deste tipo de arquitetura pode ser
visto na Figura 2.1.
Figura 2.1: Arquitetura reconfigurável típica [Cardoso & Dehon, 2010].
Como RPU, normalmente, são utilizados Field Programmable Gate Arrays(FPGAs) que são dispositivos lógicos que podem ser reprogramados de acordo
com a necessidade de cada aplicação ou arquitetura. Em particular na Figura
2.1,temos um FPGA configurado com uma RPU contendo várias Unidades Ló-
gica e Aritmética (ULAs). Os FPGAs são apresentados a seguir.
2.2 Field Programmable Gate Array (FPGA)
Os FPGAs são constituídos por um circuito integrado com um arranjo de
células lógicas que oferecem suporte para a implementação de inúmeros cir-
cuitos lógicos. A estrutura básica de um FPGA é descrita na Figura 2.2 e é
formada basicamente pelos blocos lógicos, que implementam os circuitos ló-
gicos funcionais, os blocos de interconexão e as chaves de interconexão, res-
ponsáveis por conectar e fazer o roteamento, para ligar de forma adequada os
blocos lógicos, atendendo assim a necessidade de cada projeto [Bobda, 2007].
As funções lógicas dentro de um FPGA podem ser implementadas com o
uso de blocos de memória Look-up table (LUT). Estes blocos de memória,
quando configurados, armazenam todos os resultados de uma dada função
dado um conjunto de dados de entrada. Em FPGAs as LUTs são compostas por
blocos de memória Static Random Access Memory (SRAM) e um decodificador
7
Figura 2.2: Estrutura de um FPGA [Hauck & Dehon, 2007].
responsável por acessar corretamente os endereços de memória de acordo com
o conjunto de entradas. Na Figura 2.3 é descrita uma LUT de duas entradas.
Figura 2.3: Exemplo de LUT de duas entradas [Bobda, 2007].
Em uma única LUT são implementadas funções lógicas simples. Para im-
plementação de funções complexas, a função deve ser dividida em partes e
cada uma das partes implementada em uma LUT. Estas células são voláteis,
ou seja, caso sejam desenergizadas perdem seu conteúdo, necessitando de
reprogramação [Moreno et al., 2005].
As células de um FPGA tem tamanhos variáveis ou seja granulações dis-
tintas e indica sua complexidade. Segundo Hauck & Dehon [2007] arquitetu-
ras internas de um FPGA podem ser classificadas em três sub-conjuntos de
acordo com sua granulação: Granulação fina, as células implementam por-
tas lógicas AND, OR, XOR e NOR que podem ser combinadas para formarem
circuitos mais complexos; Granulação média, as células implementam fun-
8
ções lógico-aritméticas e multiplicadores de poucos bits; Granulação grossa,
as células implementam somadores e multiplicadores de maiores quantidades
de bits e em nível de bytes . Pode-se encontrar arquiteturas como pequenos
processadores e unidades lógica e aritmética.
2.3 Arquiteturas Híbridas FPGA/CPU
Os FPGAs são ineficientes para a realização de algumas operações, como
por exemplo laços de tamanho variável e controle de saltos, além de necessi-
tar de uma fonte de controle para a sua reconfiguração. Por estes motivos e
também para obter maior desempenho e suporte a um número maior de apli-
cações, os FPGAs são normalmente conectados a processadores de uso geral,
criando assim uma arquitetura híbrida FPGA/CPU [Mittal et al., 2007].
Segundo Mittal et al. [2007], este novo modelo de arquitetura tende a criar
uma grande necessidade de migração de softwares projetados para uso em
processador de uso geral, para o modelo híbrido FPGA/CPU, pelo fato deste
modelo alcançar maior desempenho a um baixo custo. Este tipo de migração
é um desafio, pois nem sempre os usuários possuem o código fonte de suas
aplicações e nem sempre estão disponíveis ferramentas para auxilia-los neste
processo.
Na arquitetura híbrida o FPGA pode ser utilizado de várias formas, como
por exemplo:
• Unidade funcional dentro de um processador hospedeiro [Hauck et al.,
2004] [Razdan & Smith, 1994]: esta unidade funcional executa interna-
mente ao processador, o que permite a adição de instruções especiais,
sem alterar o modo de programação;
• Co-processador [Wittig & Chow, 1996] [HPCwire, 2009]: é iniciado pelo
processador do qual recebe os dados para a realização de processamento
de forma independente, podendo acessar memórias internas ou externas
ao sistema;
• Sistema multi-processador [Vuillemin et al., 1996] [Laufer et al., 1999]: o
FPGA é interligado a um processador de uso geral. A comunicação entre
eles ocorre utilizando-se de primitivas especializadas, assim como nos
sistemas multiprocessadores tradicionais;
• Unidade de processamento externa [Quickturn, 1999]: Semelhante ao
uso do FPGA como co-processador, mas neste caso existe pouca comuni-
cação entre o processador e o dispositivo reconfigurável.
9
A seguir são apresentadas duas das primeiras arquiteturas híbridas pro-
postas.
2.3.1 GARP
O projeto GARP [Hauser & Wawrzynek, 1997] foi uma proposta de arqui-
tetura híbrida baseada em um processador MIPS-II e uma unidade reconfi-
gurável (FPGA Xilinx Série 4000). A relação entre o processador e a unidade
reconfigurável era mestre-escravo, ou seja o processador MIPS era o respon-
sável por ativar e configurar o FPGA além de lhe fornecer as informações ne-
cessárias para execução de trechos de código. A arquitetura do GARP pode
ser vista na Figura 2.4.
Figura 2.4: Arquitetura básica do projeto GARP [Hauser & Wawrzynek, 1997].
A proposta de arquitetura do GARP era formada por uma memória com-
partilhada entre o processador e o FPGA e visava melhorar o desempenho de
execução de trechos críticos em aplicações. O processador era responsável
por informar os endereços dos dados a serem acessados ou gravados na me-
mória pelo FPGA tanto na memória principal quanto na cache, uma vez que o
FPGA tinha acesso a toda a hierarquia de memória do processador. Além da
memória principal faziam parte da arquitetura uma memória secundária para
armazenar as instruções e uma memória secundária para armazenamento dos
dados.
O conjunto de instruções disponível para o desenvolvimento de aplicações
era uma extensão do conjunto de instruções do processador MIPS. O pro-
cessador MIPS poderia iniciar ou parar a execução de um trecho de código no
10
FPGA sempre que fosse necessário, porém só era possível reconfigurar o FPGA
caso ele estivesse ocioso.
Para o desenvolvimento de aplicações no GARP foi desenvolvido um con-figurador para gerar código binário a partir de uma descrição textual para a
configuração da unidade reconfigurável, e um simulador para executar o có-
digo gerado como se estivesse executando em uma máquina GARP. O código
gerado era um conjunto de instruções do MIPS e as instruções desenvolvidas
para serem executadas no FPGA. O fluxo de desenvolvimento de uma aplica-
ção GARP pode ser visto na Figura 2.5
Figura 2.5: Fluxo de desenvolvimento de aplicações [Hauser & Wawrzynek,1997].
O código gerado pelo configurador é um arquivo de texto simples que pode
ser utilizado como um array por um código C compilado para a geração do có-
digo executável do MIPS. Todo o código é executado pelo simulador do GARP,
pois não foi realizada a implementação do hardware proposto [Hauser & Wa-
wrzynek, 1997].
2.3.2 Dynamic Instruction Set Computer (DISC)
O DISC [Wirthlin & Hutchings, 1995] foi desenvolvido como uma arquite-
tura híbrida que teve como objetivo utilizar o conceito de reconfiguração par-
cial, visando maior desempenho e também a capacidade de implementação de
11
um grande conjunto de instruções.
Para esta arquitetura foi desenvolvido um hardware específico com um bar-
ramento Industry Standard Architecture (ISA), dois FPGAs Configurable LogicArray (CLAy) e uma memória. Um controlador de configuração foi implemen-
tado no FPGA para realizar o monitoramento da execução de instruções e
manter a comunicação com um computador executando um sistema operaci-
onal baseado em UNIX. Este computador é responsável por validar a presença
de cada uma das instruções no hardware, caso a instrução necessária não
esteja presente, o computador solicita a configuração da instrução no proces-
sador DISC. A localização física da instrução a ser configurada é feita com
base nas áreas livres do FPGA. Caso não exista área livre para a implementa-
ção da instrução a instrução é colocada em uma fila de espera até que exista
espaço suficiente.
Instruções já utilizadas são desalocadas do FPGA pelo computador, ligado
ao barramento ISA, por meio da alteração do bitstream a ser enviado para o
FPGA. A arquitetura do sistema DISC pode ser visto na Figura 2.6.
Figura 2.6: Arquitetura do sistema DISC [Wirthlin & Hutchings, 1995].
Para a execução de uma aplicação em uma arquitetura DISC o passo ini-
cial é a alocação do programa na memória e após isso configurar o FPGA DISC
com o uso do controlador global. Este controlador é implementado na forma
de um processador de 8 bits com um conjunto reduzido de instruções e é o
responsável por monitorar o uso de recursos como memória externa, redes de
comunicação e também o estado global do FPGA. Este controlador provê inter-
face de comunicação com o sistema e um protocolo padrão para as instruções
específicas implementadas. Este controlador é interligado também a um a
um banco de instruções para formar um processador completo. A arquitetura
deste processador pode ser visto na Figura 2.7.
12
Figura 2.7: Arquitetura do processador DISC [Wirthlin & Hutchings, 1995].
2.4 Linguagens de Descrição de Hardware
As linguagens de descrição de hardware são uma alternativa ao desenvol-
vimento de modelos esquemáticos para circuitos digitais. Estas linguagens
são orientadas a descrição das estruturas e do comportamento de um hard-
ware, para isso faz-se necessário duas descrições durante a programação, a
descrição estrutural que informa a interconexão dos componentes que fazem
parte de um circuito e uma descrição comportamental que descreve o funcio-
namento dos componentes deste circuito.
A descrição em linguagem de descrição de hardware, em conjunto com
uma biblioteca de componentes, é utilizada por uma ferramenta de síntese
para a geração automática de um circuito digital, este processo é conhecido
por síntese lógica.
As linguagem de descrição de hardware mais utilizadas são VHDL e Veri-
log. Estas duas linguagens são padronizadas pelo Institute of Electrical andElectronic Engineers (IEEE), e são utilizadas por vários fabricantes, sendo pos-
sível encontrar várias ferramentas. A seguir serão apresentadas as principais
características destas linguagens.
13
2.4.1 VHDL
O VHDL é uma linguagem que foi desenvolvida por empresas contratadas
pelo governo americano, para ser um padrão utilizado no desenvolvimento
dos ASICs projetados para fins militares. Após o inicio do seu uso ele foi
padronizado pelo IEEE.
O desenvolvimento em VHDL requer duas estruturas: a entity que é uma
declaração de entidade e a architecture que defini a arquitetura do sistema a
ser desenvolvido. A entidade é utilizada para definir os detalhes externos das
funções, como por exemplo, nomes de entradas e saídas e nome da função.
A arquitetura define os aspectos internos, como por exemplo, como as entra-
das e saídas influem no comportamento e como se relacionam com os sinais
internos.
2.4.2 Verilog
O Verilog foi criado como uma linguagem proprietária, pertencente a Ga-teway Design Automation. Alguns anos após a venda desta empresas ela
se tornou de domínio público, com a formação da Open Verilog Internatio-nal (OVI), sendo hoje padronizada pelo IEEE.
A linguagem Verilog tem semelhanças com a linguagem C, o que pode fa-
cilitar seu aprendizado por programadores não especialistas em hardware. A
linguagem também é case sensitive, assim como a linguagem C, ou seja dife-
rencia caracteres maiúsculos de minúsculos. O Verilog permite várias formas
para a descrição de um sistema digital como: descrições comportamentais,
que especificam como o sistema deve funcionar; descrições estruturais, que
define a estrutura interna do sistema ou simulação e permite a verificação de
alternativas para a implementação.
2.5 Considerações Finais
Neste capítulo foram apresentados os conceitos de computação reconfi-
gurável e outros modelos para execução de algoritmos, buscando situar este
modelo. Além disso foram apresentadas as tecnologias envolvidas com este pa-
radigma e também formas de utilização de dispositivos reconfiguráveis para
o desenvolvimento de arquiteturas que visam ganho de desempenho. Duas
das arquiteturas que representam o inicio do desenvolvimento de sistemas
reconfiguráveis foram apresentados visando exemplificar formas do uso da
computação reconfigurável.
14
CAPÍTULO
3Compilação
Têm-se como objetivo neste capítulo descrever o que é um compilador bem
como o processo de compilação tradicional, e o processo de compilação para
arquiteturas reconfiguráveis. Embora estes processos sejam muito semelhan-
tes, na compilação para arquiteturas reconfiguráveis algumas novas carac-
terísticas, que podem ser acrescentadas ao código gerado, apresenta novos
desafios para o processo. No final do capítulo são descritos alguns compilado-
res para arquiteturas de hardware.
3.1 O Que é Um Compilador
Segundo Aho et al. [2007] visto de forma bem simples, um compilador
pode ser descrito como uma programa que lê um texto em uma linguagem
de programação específica, conhecida como linguagem fonte, e o traduz em
uma outra linguagem, conhecida como linguagem de máquina ou linguagem
alvo. Uma parte muito importante deste processo de tradução é a informação,
repassada ao usuário, sobre possíveis erros. Na Figura 3.1 pode ser visto o
fluxograma básico de um compilador.
Embora possa parecer simples, este processo de tradução requer um con-
junto de fases que são descritas na próxima seção.
15
Figura 3.1: Fluxo básico do processo de compilação [Aho et al., 2007].
3.2 O Processo de Compilação
O processo de compilação divide-se em duas fases: a análise e a síntese.
A análise faz a divisão do programa fonte e gera uma representação interme-
diária deste programa. A fase de síntese é a responsável por gerar o programa
alvo a partir da representação intermediária. Durante estas fases o compilador
também faz uso de outros dois componentes básicos: uma tabela de símbolos
utilizada para armazenar informações necessárias durante praticamente todo
o processo de compilação, e um interface com o sistema operacional, utili-
zada para realizar a gravação de arquivos e enviar mensagens ao usuário, por
exemplo. A estrutura do processo de compilação é mostrado na Figura 3.2.
Figura 3.2: Estrutura básica do processo de compilação [Muchnick, 1997].
Da mesma forma que o processo de compilação é dividido em duas fases,
16
um compilador pode ser desenvolvido em duas partes, uma chamada de front-end que é responsável pela fase de análise e uma outra chamada de back-end responsável pela fase de síntese. A seguir será explicado de forma mais
detalhada o processo para conclusão de cada uma destas fases.
3.2.1 Fase de Análise
A fase de análise realiza uma série de passos até a geração da representa-
ção intermediária, entre estes passos estão a análise léxica, análise sintática
e análise semântica. Ao término da execução destes passos um código inter-
mediário é gerado. Além disso, pode existir um passo para efetuar otimizações
de código independente de maquina. A seguir cada um destes passos serão
descritos.
Análise Léxica
Esta é o primeiro passo no processo de compilação e tem como objetivo
ler os caracteres de entrada (código fonte) e criar sequências de tokens, que
são unidades ou parte do texto significativas, que serão utilizados durante a
análise sintática, na Tabela 3.1 são apresentados alguns exemplos de tokens.
Tabela 3.1: Exemplos de tokens [Aho et al., 2007].Token Lexemas Exemplo Descrição Informal do Padrãoconst const constif if ifrelação <, <=, =, <>, >, >= < ou <= ou = ou <> ou > ou >=id pi, contador, D2 letra seguida por letras e/ou dígitosnum 3.1416, 0 , 6.02E23 qualquer constante numéricaliteral "conteúdo da memória" quaisquer caracteres entre aspas, exceto
aspa
Como pode ser visto no Tabela 3.1, quando trata-se de análise léxica alguns
termos como token, lexema e padrão são adotados com significados específi-
cos. O termo token como foi explicado anteriormente são partes significativas
do texto e é gerado com o uso de uma regra chamada de padrão associado ao
token, que é utilizada para reconhecer cada cadeia de caracteres. Os lexemas
são reconhecidos pelo padrão do token e podem receber tratamento em con-
junto como instâncias de uma unidade léxica (identificadores, números, etc).
Por fim o padrão é a regra que descreve o conjunto de lexemas válidos para
17
representar um token, por exemplo, o token relação é o conjunto de todos os
operadores relacionais.
O analisador léxico é a parte do compilador que lê todo o texto que será
usado como entrada do compilador e com isso pode realizar algumas tarefas
secundárias, como remover do código os espaços em branco e comentários,
além de relacionar as mensagens de erro do compilador com o texto de entrada
por meio da numeração de linhas [Aho et al., 2007].
Análise Sintática
O passo de análise sintática ocorre após a análise léxica, e para ser efe-
tuado recebe os tokens gerados no passo anterior e verifica se a cadeia de
tokens pode ser gerada pela gramática da linguagem1. Além disso a análise
sintática deve relatar erros encontrados e continuar a verificação mesmo de-
pois de encontrar um erro [Aho et al., 2007].
Os objetivos do tratamento de erros no analisador sintático são: Relatar
de forma clara a presença de erros; Recuperar-se de um erro, para poder
continuar a busca por novos erros; Ser rápido para não atrasar, de forma
significativa, a compilação de programas corretos.
Ao final deste passo é gerada uma estrutura hierárquica que permite iden-
tificar todos os operadores, operandos das expressões e enunciados.
Análise Semântica
Na análise semântica tem-se como objetivo verificar os tipos de dados para
a geração de código e verificar a existência de erros semânticos2. Além disso
tem-se como objetivo verificar o fluxo de controle do programa e a unicidade
da declaração de variáveis.
Para verificar as variáveis o compilador cria uma estrutura conhecida como
tabela de símbolos, com o objetivo de armazenar os nomes declarados no
código fonte. Esta tabela é consultada sempre que é encontrado a declaração
de uma nova variável. Esta consulta e a inserção de novos nomes tem que
ser feita de forma eficiente para não atrasar o processo de compilação. Além
do nome declarado esta tabela armazena informações como, tipo, tamanho e
escopo.
Alguns exemplos de erros semânticos são: Utilização de variáveis não de-
claradas; Problemas de atribuição entre tipos diferentes; Cálculos entre dados
1Gramática da linguagem pode ser definido como as regras que definem todas as combina-ções possíveis desta linguagem.
2Erro semântico pode ser definido como o uso incorreto de um comando ou um identifica-dor de uma variável.
18
de tipos incompatíveis.
Este passo busca identificar os erros e não resolvê-los, por este motivo
faz-se necessário uma forma eficiente de apontar os erros ao usuário.
Geração de Código Intermediário
Uma prática comum em vários compiladores é a geração de um código in-
termediário durante o processo de tradução do programa fonte, realizada após
o programa passar pela fase de análise. Embora seja possível gerar o código-
alvo diretamente, com o uso do código-fonte, a representação intermediária
apresenta benefícios, como por exemplo a facilidade de criação de um com-
pilador para uma máquina alvo diferente, substituindo-se apenas o módulo
de back-end ou também para a utilização de um otimizador dependente de
máquina [Aho et al., 2007].
Esta representação deve possuir duas características básicas: ser fácil de
produzir e ser fácil de ser traduzida para o programa alvo. Uma das formas
de representação deste código é por meio de instruções de três endereços,
que são facilmente geradas e traduzidas, por ser semelhante a linguagens de
montagem. Segundo Aho et al. [2007] esta representação pode ser vista como
um programa para uma máquina abstrata. Um exemplo deste tipo de código
é apresentado na Listagem 1.
Código 1: Exemplo de instruções de três endereços [Aho et al., 2007].temp1 := inttoreal (60)
temp2 := id3 * temp1
temp3 := id2 + temp2
id1 := temp3
A representação em forma de código de três endereços é formado por uma
sequencia de instruções com no máximo três operandos e um operador de
atribuição. Durante a geração deste tipo de código deve-se definir a ordem
de execução das instruções de modo a respeitar a regra de precedência de
operadores. Para cada operação que for dissociada em várias instruções faz-
se necessário gerar nomes temporários para receber valores computados em
algumas instruções.
Além de representar as instruções do código fonte, este código deve ser ca-
paz de representar o fluxo de controle existente. Para este propósito pode ser
utilizado marcações no código, de forma a permitir saltos dentro do conjunto
de instruções, ou uma representação gráfica, normalmente um ou mais gra-
fos, que agrupam instruções de acordo com o fluxo de dados e de controle.
A forma de representação intermediária com o uso de grafos permite uma
19
fácil identificação de pontos de paralelismo no programa, o que torna esta
representação bastante utilizada na geração de código para arquiteturas re-
configuráveis, como pode ser visto nos trabalhos [Tripp et al., 2007] [Cardoso,
2003], que buscam explorar este paralelismo.
3.2.2 Fase de Síntese
A fase de síntese não possui um conjunto de passos definidos, ela pode
ser composta pela otimização de código e da geração do código alvo. Sendo o
ultimo passo o único obrigatório.
Otimização
Segundo Aho et al. [2007] o processo de compilação deveria produzir có-
digo compatível com aquele que poderia ser escrito a mão, mas esta meta só
pode ser alcançada em casos limitados. O processo de compilação busca este
objetivo com o uso de alterações no código, chamadas de otimizações, embora
não se possa garantir que o código gerado seja o melhor possível.
Estas alterações no código buscam fazer com que o programa execute de
forma mais eficiente, tendo uma maior velocidade e ocupando menos memó-
ria. Estas otimizações são obtidas de forma mais eficiente se aplicadas a todos
os níveis, ou seja, desde o código-fonte até o código-alvo, como mostrado na
Figura 3.3.
Figura 3.3: Locais para melhorias potenciais por parte de usuário e do compi-lador [Aho et al., 2007].
Como pode ser visto na Figura 3.3 a otimização de código deve começar
na produção do código-fonte pelo programador, pois o processo de otimização
realizado pelo compilador é altamente dependente do código de entrada.
Geração de Código
A geração de código é o ultimo passo do compilador, e consiste em traduzir
o código intermediário gerado em código da linguagem alvo. Durante este pro-
20
cesso de tradução faz-se necessário definir algumas características do código
a ser gerado, como por exemplo: Como será feita o gerenciamento de memória
e alocação de registradores?
O código intermediário que é entregue ao gerador de código, já passou por
toda a fase de análise, por este motivo parte-se do principio que este código
esta isento de erros e não realiza nenhum tipo de verificação antes de traduzi-
lo para o código de maquina. Para esta tradução faz-se necessário conhecer o
conjunto de instruções da arquitetura alvo e suas particularidades.
Segundo Aho et al. [2007] as exigências de um gerador de código são bas-
tante rígidas, determinando que o código gerado seja correto e de alta quali-
dade, ou seja, utilize efetivamente os recursos da maquina alvo e que o gerador
deve executar de forma eficiente.
3.3 Compilação para Arquiteturas Reconfiguráveis
O processo de compilação para arquiteturas reconfiguráveis segue os mes-
mos passos de um processo de compilação para arquiteturas tradicionais.
Embora os passos sejam os mesmo, este processo de compilação apresenta
preocupações um pouco diferentes, como por exemplo, o particionamento da
aplicação em hardware e software e também a identificação de pontos de pa-
ralelismo em nível de instrução [Panainte et al., 2007].
3.3.1 Grafos para Representação Intermediária
Devido as preocupações da compilação para arquiteturas reconfiguráveis, a
representação intermediária gerada deve ser mais detalhada, visando permitir
explorar, por exemplo, todo o paralelismo presente em uma aplicação. Esta
representação pode ser feita utilizando-se de uma série de grafos que visam
armazenar o fluxo e dependência de dados, fluxo e dependência de controle,
informações de execução de blocos básicos e as estruturas de controle. Alguns
dos grafos que podem ser utilizados são descritos a seguir.
Control Flow Graph (CFG)
É um grafo dirigido, no qual cada nó representa um segmento de código e
cada arco representa um caminho possível a ser seguido durante a execução.
Cada segmento de código representa um conjunto de instruções que devem ser
executadas em ordem, a ultima instrução de um segmento é uma instrução
de salto e a primeira é o destino de um determinado salto. Este grafo é usado
21
como base para a geração de dependência para a exploração do paralelismo
máximo de uma aplicação [Cardoso, 2000].
Control Dependence Graph (CDG)
É um grafo dirigido formado pelo conjunto de nós do CFG, onde as ligações
entre os blocos representam a dependência de controle e restringem a ordem
de execução dos blocos, visando preservar a funcionalidade do programa.
Data Flow Graph (DFG)
Este grafo demonstra o fluxo de dados entre as operações de uma aplica-
ção, seus nós representam as operações e a ligação entre nós a existência de
dependência de fluxo entre eles e as condições segundo as quais determinada
operação é realizada, ou seja, as construções condicionais.
Data Dependence Graph (DDG)
Cada nó do CFG será considerado um nó do DDG, e as ligações entre estes
nós representam a dependência de ordem de execução em termos de depen-
dência de dados. Com a utilização deste grafo é possível explorar o paralelismo
a nível de blocos básicos3. Sempre que dois blocos forem independentes em
termos de fluxo de dados, eles poderão ser executados em paralelo.
Na Figura 3.4 é possível verificar os grafos CFG, CDG, DFG e DDG gerados
para um pequeno trecho de código.
Figura 3.4: Exemplo de CFG, CDG, DFG e DDG [Duarte, 2006]
3Um bloco básico é um trecho de código que se inicia no destino de um desvio e terminaao encontrar um desvio.
22
Hierarchical Task Graph (HTG)
Este grafo proposto por Girkar & Polychronopoulos [1992] é construído
com o uso do DDG e DFG e lida de forma eficiente com o paralelismo funci-
onal, pois permite representar de forma explicita todos os fluxos de controle
e dependência de dados, fluxos de controles múltiplos e os ciclos existentes.
Este grafo é formado por três tipos de nós: Nós simples, que representam
instruções ou blocos básicos do programa; Nós compostos, que representam
blocos de decisão if-then-else; Nós ciclos que representam blocos que podem
ser executados várias vezes consecutivas dentro do HTG.
Figura 3.5: Exemplo de HTG [Silva, 2009].
Control Dataflow Graph (CDFG)
É um grafo em que cada nó pode ser classificado como nó de operação
ou nó de controle. As ligações entre nós deste grafo podem representar a
transferência de valores ou de sinais de controle [Anellal & Kaminska, 1993].
Segundo Namballa et al. [2004] nós deste tipo de grafo podem ser classificados
como: Nó operacional, são nós responsáveis por cálculos e operações lógicas
ou relacionais; Nó de controle, nós responsáveis por construções como loops;
Nós de armazenamento, nós responsáveis por atribuir valores a uma variável;
Nós de chamadas, nós responsáveis por realizar chamadas a sub-programas
ou funções.
Na Figura 3.6 pode ser visto um exemplo de CDFG gerado para um pequeno
trecho de código.
23
Figura 3.6: Exemplo de CDFG [Silva et al., 2009].
3.4 Considerações Finais
Neste capítulo foi apresentado o processo de compilação, suas fases e suas
particularidades. Nesta descrição buscou-se demonstrar como funciona um
compilador e todo o processo necessário para a tradução de uma linguagem
para um programa executável. Durante o capítulo foi dada uma atenção es-
pecial a representação intermediária formada por grafos, para auxiliar na tra-
dução de código, principalmente visando paralelismo a nível de instruções.
24
CAPÍTULO
4Arquitetura a Fluxo de Dados
Segundo Veen [1986] o conceito de arquitetura a fluxo de dados é tão antigo
quanto a computação eletrônica, porém, as pesquisas explorando este con-
ceito ganharam destaque acadêmico na década de 70, principalmente para o
seu uso em computação paralela [Dennis & Misunas, 1975]. Este conceito
surgiu como uma proposta alternativa para a computação de alto desempe-
nho, motivada pelo trabalho publicado por Dennis [1974], que apresentava
o conceito de implementação de um sistema altamente paralelo, cujo proces-
samento paralelo não exigisse grande esforço para ser obtido. Durante parte
dos anos 80 e 90 acreditou-se que este tipo de arquitetura seria o substituto
para os modelos tradicionais, mas várias dificuldades foram encontradas para
o seu desenvolvimento.
Após as expectativas e esforços iniciais, a pesquisa com este tipo de ar-
quitetura foi descontinuada, devido principalmente a restrição de hardware
[Johnston et al., 2004]. Com a evolução dos elementos de hardware, princi-
palmente os FPGAs, estas pesquisas foram retomadas [Cappelli et al., 2004]
[Swanson et al., 2007].
Este modelo é muito atrativo para a computação paralela devido a duas de
suas características principais, que são, ser assíncrono e explicitar o parale-
lismo inerente das aplicações [Lee & Hurson, 1994]. A escolha das instruções
a serem executadas é não determinística, ou seja é feita em tempo de execu-
ção, e são limitadas apenas pela dependência de dados do algoritmo. Além
de permitir a exploração do paralelismo implícito, este modelo falicita a pro-
gramação da máquina, sem a necessidade de conhecimentos em programação
25
concorrente ou paralela [Marques, 1993].
O modelo a fluxo de dados permite explorar o paralelismo à nível de instru-
ções em várias aplicações adaptando o hardware para a execução baseada na
presença de dados. Esta característica acrescenta um problema referente a
ordenação de memória, para disponibilizar os dados necessários no momento
necessário. Para tentar solucionar este problema, cada projeto de maquina a
fluxo de dados apresenta uma proposta para solucionar este problema.
4.1 Linguagem Básica para o Modelo a Fluxo de Da-
dos
Em uma arquitetura a fluxo de dado, cada instrução é vista como um ope-
rador. Os operadores deste modelo possuem somente uma referência para
a instrução que irá consumir o resultado gerado por sua computação, desta
forma um programa fluxo de dados pode ser visto como um grafo [Veen, 1986].
Devido a esta característica a descrição de programas, para este tipo de ar-
quitetura, pode ser representada na forma de um grafo direcional G = (V,A),
onde V é o conjunto de operadores e A o conjunto de arcos que interligam os
operadores. Estes arcos representam a dependência entre os dados. A repre-
sentação dos programas nesta forma de grafo é muito atrativa para máquinas
paralelas, desde que os nós que não possuam dependência de dados possam
disparar concorrentemente [Veen, 1986].
A computação na arquitetura a fluxo de dados ocorre quando um operador
(nó) é ativado, ou seja, quando os dados (tokens) necessários estão presentes
em seus arcos de entrada, após ativado o operador dispara, ou seja, realiza
a computação dos dados presentes em seus arcos e envia o resultado para o
seu arco de saída [Dennis & Misunas, 1975]. Na Figura 4.1 é ilustrado este
processo.
Figura 4.1: Processo de disparo de um operador em uma arquitetura a fluxode dados.
26
4.1.1 Estruturas Condicionais
Para a execução de instruções condicionais em arquiteturas a fluxo de da-
dos, faze-se necessário o uso de operadores que decidam de acordo com um
sinal true ou false qual o destino que o dado recebido em seu arco de entrada
deve seguir (ex.: Operador Branch, Figura 4.2). Para definir em qual arco o
operador deve entregar o dado disponível em sua entrada, é necessário que ele
receba o valor de controle, normalmente gerado por um operador de decisão
(ex.: Operador decider, Figura 4.2). Um operador de decisão recebe dois dados
em suas entradas, testa uma condição e gera em sua saída um valor true ou
false. Este operadores são apresentados na Figura 4.2.
Figura 4.2: Operadores branch, merge não determinístico e decisão, adaptadode [Veen, 1986].
O operador Non Deterministic Merge, descrito na Figura 4.2, é utilizado no
contexto condicional, entretanto não depende de um sinal de controle para
selecionar entre dois dados que recebe em suas entradas, ele simplesmente
transfere o primeiro dado a chegar para sua saída.
A Figura 4.3 mostra uma estrutura condicional que implementa a seguinte
expressão: if teste then f(x, y) else g(x, y). O grafo gerado, recebe como entrada
os dados x, y, e de acordo com o valor recebido como teste, decide se enviará
o fluxo ao sub-grafo f ou sub-grafo g, fazendo com que somente um dos sub-
grafos seja executado. Ao final da execução de um dos sub-grafos, o resultado
de sua execução é enviado para um operador merge não determinístico, que
repassa para a sua saída o primeiro sinal recebido em uma de suas entradas,
garantindo assim que a execução siga corretamente após a computação da
estrutura condicional [Veen, 1986].
4.1.2 Estruturas Iterativas e reentrância
A existência de ciclos em uma arquitetura a fluxo de dados, pode gerar
alguns problemas [Veen, 1986]. A Figura 4.4 apresenta exemplos deste pro-
blemas. No grafo a esquerda (A), ocorrerá um estado chamado deadlock, ou
27
Figura 4.3: Grafo correspondente a estrutura condicional if teste then f(x, y)else g(x, y) [Veen, 1986].
seja, o processamento ficará impedido de continuar, pois o loop não atenderá
a condição de disparo. No grafo a direita (b) o operador nunca deixará de
disparar, pois sua entrada receberá dados infinitamente.
Figura 4.4: Problemas com ciclos em arquiteturas a fluxo de dados [Veen,1986].
Embora os exemplos da Figura 4.4 não representem a realidade, eles ilus-
tram problemas que podem surgir em qualquer grafo cíclico, caso ações não
sejam tomadas. A forma correta de implementação de um ciclo em um grafo é
apresentada na Figura 4.5 [Veen, 1986]. Neste exemplo, os operadores lidam
com um conjunto de dados, x e y. Este grafo implementa um comando whilef(x) do begin (x,y):=g(x,y), e usa um método conhecido como lock method para
proteger os sub-grafos da reentrância (f e g). São utilizados operadores mergee branch, para o controle da execução, além de uma verificação da condição
de parada do loop.
Devido a forma como o grafo da Figura 4.5, foi implementada, com uma
regra de habilitação bem definida, garante-se que cada um dos nós só seja
disparado após o sub-grafo g ter liberado os seus dois tokens. Esta resolução
28
Figura 4.5: Construção de loop com controle de reentrância [Veen, 1986].
da reentrância gera uma arquitetura a fluxo de dados chamada de estática[Veen, 1986].
Para obtenção de um maior nível de paralelismo cada iteração de um grafo
deveria ser executada em uma instância de grafo separada, esta forma de
execução, com cópia de código, requer uma maquina capaz de criar novas
instâncias do sub-grafo e direcionar seus tokens para a instância apropriada.
Para a identificação destes tokens, pertencentes a instâncias diferentes, eles
devem receber tags de identificação, indicando a qual sub-grafo pertencem.
Este tipo de arquitetura a fluxo de dados é chamada de tagged-token e sua
regra de habilitação visa garantir que o nó só será habilitado, se em cada arco
de entrada existam tokens com tags idênticas. Esta resolução da reentrância
gera uma arquitetura a fluxo de dados chamada de dinâmica Veen [1986].
4.2 Modelos de Arquiteturas a Fluxo de Dados
Um ponto relevante a ser destacado é a impossibilidade de implementação
pura do modelo teórico proposto, pois não é possível aceitar infinitos tokens ao
mesmo tempo e nem processa-los em paralelo, pois a memória e o elementos
de processamento são finitos [Johnston et al., 2004].
Como visto anteriormente, existem duas implementações possíveis para
arquiteturas as fluxo de dados: Estática e Dinâmica. Estas implementações
são apresentadas a seguir.
29
4.2.1 Arquitetura Estática
A arquitetura a fluxo de dados estática [Dennis & Misunas, 1975] [Davis,
1978], é um modelo mais simples de ser implementado, mas apresenta uma
capacidade menor para explorar o paralelismo presente em aplicações, pois
limita a sequencia de dados que podem ser colocados nos arcos de vários
operadores, podendo bloquear um operador devido ao atraso na chegada de
seu dado parceiro. Isso se deve ao fato deste modelo de arquitetura permitir
que apenas um dado esteja presente em cada arco de um operador.
Sempre que um operador recebe um dado em algum de seus arcos, este
arco não poderá receber outro dado, até que o seu parceiro de processamento
chegue no outro arco de entrada. Este controle pode ser implementado com o
uso de um protocolo de comunicação, como por exemplo, lock e acknowledge.
A Figura 4.6 ilustra o funcionamento dos métodos lock e acknowledge.
Na Figura 4.6(a), nós para controle de chegada de dados e nós para controle
condicional são utilizados para controlar quantos dados estarão presentes nas
entradas de cada operador. No inicio da iteração um operador é incluído para
selecionar os dados de entrada que serão utilizados em uma iteração e devido
as regras de ativação do operador de controle condicional, ele só irá disparar
depois que subgrafo anterior tenha sido executado e tenha enviado o resultado
de sua computação para suas saídas.
No grafo ilustrado na Figura 4.6(b) os ciclos de execução podem ter tem-
pos diferentes, o que pode causar um comportamento inadequado, ou seja,
fazer com que os valores de x cheguem mais rápido que os valores de y, o que
causaria a computação de dados não parceiros, tornando o grafo não confiá-
vel. Para tornar este grafo confiável, é utilizado o método de acknowledge que
consiste em adicionar arcos extras que indicariam a presença de um dado no
arco e não permitiriam o recebimento de novos dados.
4.2.2 Arquitetura Dinâmica
A arquitetura a fluxo de dados dinâmica [Gurd et al., 1985] [Grafe et al.,
1989] [Papadopoulos & Culler, 1998], é uma evolução da arquitetura estática
que permite que vários dados estejam em cada arco de um operador, sendo
necessário então a identificação destes dados para permitir que os parceiros
corretos sejam computados. Para esta identificação podem ser utilizadas téc-
nicas como o tagged-token ou o code-copying o que torna sua implementação
mais complexa.
Na Figura 4.7 é ilustrada uma estrutura de repetição que utiliza-se do mé-
todo tagged-token. Este método consiste em inserir nós no grafo que tem como
30
Figura 4.6: Métodos para controle de tags em arquiteturas estáticas [Silva,2011].
função manipular identificadores para cada dado que está dentro do grafo, ou
seja, identificá-los e com isso conseguir realizar o processamento dos parcei-
ros corretos.
Figura 4.7: Métodos para controle de tags em arquiteturas dinâmicas [Silva,2011].
O método de code-copying consiste na realização de várias cópias de um
mesmo grafo, permitindo assim o processamento de várias iterações em pa-
ralelo, gerando um alto nível de paralelismo, quando não há dependência de
dados. Este método é semelhante a técnica de loop-unrolling e necessita que
arquitetura permita a reconfiguração parcial dinâmica.
Estes métodos podem ser utilizados para resolver problemas decorrentes
de chamada de funções, visto que os problemas gerados são os mesmo [Veen,
31
1986].
4.3 Descrição de Arquiteturas Propostas
A seguir serão apresentadas as três arquiteturas a fluxo de dados mais in-
fluentes no inicio das pesquisas destes tipo de arquitetura. Em seguida serão
apresentados os dois principais projetos atuais que visam o desenvolvimento
destas arquiteturas.
4.3.1 Máquina a Fluxo de Dados Estática
A maquina proposta por Dennis & Misunas [1975] tinha como objetivo ser
um computador altamente paralelo e este paralelismo deveria ser obtido com
o menor sacrifico possível. Para alcançar este objetivo buscou-se uma arqui-
tetura que representasse o paralelismo das aplicações de forma simples. Para
isso projetou-se um processador para utilizar linguagem a fluxo de dados,
como linguagem base.
A arquitetura básica da máquina proposta pode ser vista na Figura 4.8.
Nesta arquitetura as instruções, que estão disponíveis na Memória do Pro-
grama, são armazenadas em forma de uma fila na Pilha de Instruções. Quando
estão prontas para serem executadas, o módulo Busca tem como função pe-
gar esta instrução, empacota-la e envia-la para a Unidade de Operação, onde
a instrução é processada e seu resultado é empacotado e enviado para o mó-
dulo Atualização. Após receber o pacote de resultados o módulo Atualização
armazena os operando e o resultado na Memória do Programa e verifica se
existe alguma nova instrução a ser executada, caso exista informa ao módulo
Busca para reiniciar o processo. Para a verificação de novas operações a se-
rem executadas é feita uma verificação de presença dos operandos em uma
operação, obedecendo-se assim a regra de disparo de uma arquitetura a fluxo
de dados.
A Memória do Programa é organizada em células de instruções, e cada uma
destas células corresponde a um nó do grafo a fluxo de dados a ser executado.
Uma célula de instruções pode ser vista na Figura 4.9. Cada uma das células é
formada por três registradores, o primeiro deles especifica o valor da operação
a ser executada e os endereços de registradores onde serão armazenados os
resultados. Os outros registradores armazenam os operandos necessários e
também possuem bits para indicar se o operando esta ou não disponível.
Os operadores básicos definidos, para a representação a fluxo de dados, fo-
ram: (a) Operador, responsável por realizar operações como adição, subtração,
32
Figura 4.8: Arquitetura básica da máquina a fluxo de dados estática [Dennis,1980].
Figura 4.9: Célula de Instrução [Dennis & Misunas, 1975].
multiplicação e divisão; (b) Decisão, responsável por receber dois dados, apli-
car um predicado e produzir um token de controle verdadeiro ou falso no seu
arco de saída; (c) T-gate, responsável por repassar o dado do arco de entrada
para a saída quando recebe em seu arco de controle um sinal verdadeiro; (d) F-gate, responsável por repassar o dado do arco de entrada para a saída quando
recebe em seu arco de controle um sinal falso; (e) Merge recebe duas entra-
33
das de dados, uma associada a um valor verdadeiro e outra relacionada a um
valor falso, além de uma entrada de controle, que definirá quais das entradas
será direcionada para o arco de saída, (f) Operador Booleano, responsável por
receber dois dados em seus arcos, realizar uma operação booleana e enviar o
resultado da operação para seu arco de saída. Estes operadores podem ser
vistos na Figura 4.10.
Figura 4.10: Operadores básicos [Dennis & Misunas, 1975].
Além dos operadores foram definidos dois tipos de enlace: (a) Enlace de
dados, responsável por transmitir os dados entre os operadores e (b) Enlace
de controle, responsável por transmitir os valores, verdadeiro ou falso, res-
ponsáveis por definir o funcionamento de alguns operadores ou receber o valor
gerado por um operador booleano. Estes enlaces podem ser vistos na Figura
4.11.
Figura 4.11: Tipos de enlaces [Dennis & Misunas, 1975].
34
4.3.2 Máquina de Manchester
A máquina de Manchester [Gurd et al., 1985] foi desenvolvida como uma
arquitetura a fluxo de dados dinâmica, e para permitir a presença de vários
dados em um mesmo arco, utilizava-se do tag de dados ou tagged token. O
tag consiste em marcar cada um dos pares de dados com um conjunto de
informações de forma a permitir a identificação dos parceiros antes do pro-
cessamento.
As informações contidas em cada um dos dados são: (a) iteration level, para
controlar os operandos de diferentes iterações, que estejam em um mesmo
subgrafo; (b) activation name, para procedimentos, funções recursivas, vi-
sando controlar a qual iteração pertence o dado, caso várias iterações este-
jam ocorrendo simultaneamente; (c) index, serve para indicar de qual parte de
uma estrutura de dados o dado faz parte, como por exemplo, qual elemento
do vetor ele representa. Cada dado que transita pelos arcos dos operadores é
empacotado com o tag contendo estas informações.
Para o gerenciamento dos tags, devem ser criados operadores, que tem
como função criar e alterar estes tags. Os operadores definidos por Gurd
et al. [1985], para este gerenciamento foram: Add to Interaction Level (ADL),
responsável por informar qual o nível da iteração dentro de um grafo e SetIteration Level (SIL), responsável por alterar o nível de uma iteração.
Além destes operadores, foram definidos os operadores de processamento,
como parte da definição da linguagem e fluxo de dados. Este operadores são
[Gurd et al., 1985]: (a) Add Floating-point Values (ADR), responsável por reali-
zar adição entre valores de ponto-flutuante; (b) Branch (BRR), responsável por
controlar os saltos dentro do grafo; (c) Explicit Duplicate (DUP), responsável por
duplicar os dados, ou seja enviar os dados recebidos em seu arco de entrada
e enviar para dois arcos de saída; (d) Compare Floating-point Values (CGR),
responsável pela comparação booleana entre valores de ponto-flutuante; (e)
Multiply Floating-point Values (MLR), responsável pela multiplicação de valores
de ponto-flutuante e Output to Host Processor (OPT), responsável por enviar a
saída do grafo para o processador hospedeiro.
A arquitetura da máquina de Manchester é apresentada na Figura 4.12.
Segundo Gurd et al. [1985] arquitetura é baseada em um anel com quatro
módulos independentes que funcionam em forma de pipeline, sendo o anel
conectado a um processador hospedeiro por meio do módulo de chaveamento
que permite que programas e dados sejam carregados ou enviados ao proces-
sador hospedeiro.
Os dados circulam pelo anel encapsulados e na unidade de matching são
35
formados os pares necessários para o processamento. Como esta unidade
tem capacidade limitada, existe uma unidade de overflow para armazenar os
dados que não tenham espaço na unidade de matching. Após formar os pares,
ou em caso de instruções com apenas uma entrada, é realizada a busca da
instrução a ser executada na memória de instruções. A memória de instruções
armazena o código de máquina do operador a fluxo de dados. Os dados a
serem executados seguem o protocolo First-in-first-out (FIFO) e os dados são
consumidos de acordo com o pipeline do anel.
Figura 4.12: Arquitetura da máquina de Manchester [Gurd et al., 1985].
4.3.3 MIT - Tagged-Token Dataflow (TTD)
O projeto MIT - TTD teve como seu resultado o desenvolvimento de uma
arquitetura a fluxo de dados chamada Tagged-Token Dataflow Architecture(TTDA) [Arvind & Nikhil, 1990] visando o desenvolvimento de pesquisas na
área de computação paralela e de propósito geral com alto desempenho. A
TDDA utiliza o conceito de linguagem a fluxo de dado proposto por Dennis &
Misunas [1975] adaptando-o para construir uma arquitetura a fluxo de dados
dinâmica, ou seja vários dados podem estar presentes em um arco em um
dado momento.
Para a implementação da arquitetura dinâmica fez-se necessário a utiliza-
ção do mesmo princípio da máquina de Manchester, o tagged-token. Os tags
36
possuem cinco partes [Arvind & Nikhil, 1990]: (a)Invocation ID, responsável
por controlar os dados em chamadas a funções e procedimentos; (b)IterationID, responsável por controlar a qual iteração de um loop o dado pertence;
(c)Code block, identifica as funções definidas pelo programador, cada função
possui um identificador; (d)Instruction Address, responsável por identificar
qual a instrução de destino e (e)Destination port, indica qual a porta de destino
o dado deve se entregue, já que um operador de destino pode possuir mais de
uma porta de entrada.
Para gerenciar as tags foi proposto um conjunto de operadores. Para a
manipulação do campo iteration ID foram criados os operadores D para incre-
mentar o campo e D−1 para decrementa-lo. Foi definido também um operador
para atribuir valores para o invocation ID, sempre que for necessário o uso de
loops aninhados e funções. O operador chamado de apply serve para receber
os dados de um code block e depois criar uma nova invocation ID e alterar o
valor da iteration ID, indicando que o token entrou em um novo sub-grafo e
foi definido um operador para recuperar as tags originais para que os dados
possam ser devolvidos a níveis mais externos de um sub-grafo.
Além deste operadores de controle, foram definidos também operadores de
processamento [Arvind, 2005]. Este operadores são apresentados na Figura
4.13.
Figura 4.13: Operadores definidos para a arquitetura TTDA [Arvind, 2005].
O operador Fork recebe um dado em sua entrada e o duplica para suas
saídas, o operador Primitive Ops é o responsável por realizar as operações
aritméticas básicas como soma, subtração, multiplicação e divisão, o operador
Switch recebe um dado em sua entrada e de acordo com um sinal de controle
define para qual saída envia-lo e o operador Merge recebe dois valores de
entrada e de acordo com um valor de controle define qual valor será enviado
para a sua saída.
37
O TTDA utiliza uma linguagem chamada de ID. Na definição desta lingua-
gem as instruções foram definidas com duas entradas, um campo opcode para
descrever o tipo de operação a ser realizada, um campo chamado literal/cons-
tante que pode ser um valor literal ou um deslocamento de memória offsete campos para designar o destino da saída de cada uma das instruções. O
formato de uma instrução da linguagem ID pode ser visto na Figura 4.14.
Figura 4.14: Formato da instrução da linguagem ID utilizada pela TTDA [Ar-vind & Nikhil, 1990].
A arquitetura da máquina TTDA é formada por uma quantidade pré-definida
de elementos de processamento e por unidades de armazenamento conecta-
das em rede. As unidades de armazenamento contem os dados e códigos
que serão utilizados pelos elementos de processamento. A arquitetura de um
elemento de processamento pode ser vista na Figura 4.15. Os elementos de
processamento tem uma estrutura em formato de pipeline, podendo assim,
conter diversos dados ao mesmo tempo, em estágios diferentes.
O elemento de processamento é formado por um conjunto de módulos, cada
um deles sendo um estágio do pipeline. O módulo Wait-Match é uma memória
onde os dados ficam armazenados até a chegada de seus parceiros, caso uma
instrução necessite de mais de um dado para ser executada. Caso a instrução
necessite de apenas um dado, este é repassada para a Instruction-Fetch que
é responsável por fazer a busca da instrução a ser executada, além de todas
as outras informações necessárias para a execução da instrução. Os módu-
los ALU e Compute Tag funcionam em conjunto, sendo a ULA uma unidade
de lógica e aritmética simples e o Compute Tag atribui as tags corretas a um
dado que esta saindo do elemento de processamento. O módulo Form-Tokensconstrói a mensagem a ser enviada para a saída do elemento de processa-
mento, com a junção do resultado gerado pela ULA e as tags informadas pelo
Compute Tag. O módulo Control é responsável pela manipulação dos estados
do elemento de processamento e também permite a conexão do elemento de
processamento com o mundo externo.
38
Figura 4.15: Elemento de processamento definido para a TTDA [Arvind &Nikhil, 1990].
4.3.4 Wavescalar
WaveScalar [Swanson et al., 2007] é um conjunto de instruções a fluxo de
dados e um modelo de execução projetado para utilizar processadores com
baixa complexidade e alto desempenho. No WaveScalar, é possível executar
aplicações convencionais de única thread, e também multi-thread, no estilo
pthread.
No WaveScalar, o programa é representado para o processador como grafos
a fluxo de dados [Swanson et al., 2007]. Cada nó no grafo é uma instrução,
os arcos entre os nós codificam estaticamente e são dirigidos, representando
para onde irá a saída da operação. O WaveScalar utiliza um código assemblemuito próximo do tradicional apresentando diferença em dois aspectos: Pri-
meiro, apesar das arestas do grafo serem sintaticamente iguais a nomes de
registradores, elas não correspondem a uma entidade arquitetural específica;
Segundo, a ordem das instruções no código não afeta a sua execução, uma
vez que a definição dessa ordem é controlada pelo fluxo de dados. Cada ins-
trução possui um endereço único, que é usado em chamadas de funções. Na
Figura 4.16 pode ser visto um trecho de programa, o grafo correspondente e o
assemble gerado.
O WaveScalar é uma arquitetura a fluxo de dados dinâmica, que utiliza
Tags para identificar os dados parceiros em um operador, estes Tags são cha-
mado de wave numbers [Swanson et al., 2007]. A utilização deste tipo de
39
Figura 4.16: Exemplo de código gerado pelo WaveScalar [Swanson et al.,2007].
recurso é conhecido como tagged-token.
A arquitetura do WaveScalar é conhecida como WaveCache. O WaveCache é
formado por todos os elementos necessários para a execução de um programa
WaveScalar exceto a memória principal. O WaveCache é formado por um
conjunto de elementos de processamento idênticos organizados de forma hie-
rárquica, buscando reduzir os custo de comunicação [Swanson et al., 2007].
O WaveCache é formado por um conjunto de clusters, cada um deles con-
tendo quatro domínios formados por oito elementos de processamento idên-
ticos. Cada um dos clusters contém quatro bancos de memória cache, uma
interface de comunicação com os clusters vizinhos além de uma interface de
comunicação com a memória. Na Figura 4.17 é apresentada a estrutura de
um clusters.
No momento da execução cada instrução do programa WaveCache é alo-
cada em um elemento de processamento, buscando deixar instruções que
apresentem dependências no mesmo cluster, para reduzir o custo de comuni-
cação. A execução das instruções é realizado utilizando um modelo de pipelinede cinco estágios (Figura 4.18). Segundo Swanson et al. [2007], estes estágios
são:
1. Input: Estágio de recebimento de uma instrução pelo elemento de pro-
cessamento, esta instrução pode ser enviada por outro elemento de pro-
cessamento ou ser uma reentrada do próprio elemento;
2. Match: Verifica se a instrução que chegou esta pronta para ser executada,
ou seja, se seus operandos já estão disponíveis;
3. Dispatch: Responsável por selecionar uma instrução que esteja disponí-
vel para ser executada, buscar seus operandos na tabela de matching e
40
Figura 4.17: Estrutura de um cluster do WaveCache [Swanson et al., 2007].
enviar os dados para a execução;
4. Execute: Estágio de execução da instrução pelo elemento de processa-
mento. O resultado da execução é enviado para uma fila de saída, ou
para a rede de interconexão dos clusters;
5. Output: A instrução é enviada para algum consumidor pela rede que
interliga os domínios de um WaveCache.
Um dos maiores problemas em arquiteturas á fluxo de dados é a organi-
zação da memória para permitir que os dados estejam disponíveis de forma
não sequencial no momento que um dado operador necessite [Swanson et al.,
2003]. Para resolver este problema o WaveScalar utiliza um mecanismo de
anotação que busca garantir que operações de memória aconteçam na ordem
correta, utilizando-se da representação de dependência de dados. Um exemplo
do mecanismo de anotação pode ser visto na Figura 4.19.
Na Figura 4.19 pode-se perceber que a anotação utilizada é formada por
três partes, a primeira parte serve para identificar qual instrução de load ou
store deve ser executada anteriormente, a segunda parte representa a sequen-
cia da própria instrução e a terceira parte qual a próxima instrução a ser exe-
cutada. O "."encontrado na primeira linha serve para indicar que esta é a
primeira instrução de memória a ser executada. O "."encontrado na ultima
linha serve para indicar que ela é a ultima instrução de memória a ser execu-
tada. Em instruções que necessitam executar um salto, não é possível prever
41
Figura 4.18: Estrutura do pipeline de execução do WaveScalar [Swanson et al.,2007].
qual a próxima instrução a ser executada, assim a terceira parte da anotação
é representada pelo símbolo "?".
A geração do programa WaveScalar seria feita com o uso de um compi-
lador específico para a arquitetura, sendo o compilador o responsável pela
organização de memória principal e escalonamento do conjunto de instruções
buscando a execução de forma mais eficiente, mas com a descontinuidade do
projeto, este compilador não foi desenvolvido.
4.3.5 TRIPS
TRIPS é uma arquitetura polimórfica, hibrida von-Neumann/fluxo de da-
dos, a qual pode ser configurada para diferentes granularidades e tipos de
paralelismo [Sankaralingam et al., 2003].
Segundo Swanson et al. [2003] o TRIPS utiliza uma arquitetura híbrida
Very Long Instruction Word (VLIW)/fluxo de dados, e esta investe no mesmo
desafio tecnológico que o projeto Wavescalar.
TRIPS contém mecanismos que habilitam os núcleos de processamento e
sistema em memória "on-chip"que pode ser configurado e combinado em dife-
rentes nós para paralelismo ao nível de instruções, dados ou threads. Para
42
Figura 4.19: Exemplo do mecanismo de anotação utilizado pelo WaveScalar[Swanson et al., 2007].
explorar o paralelismo da aplicação e prover um grande uso dos recursos dis-
poníveis, TRIPS usa três nós de execuções diferentes: D-morph, que procura o
paralelismo em nível de instruções; T-morph que trabalha ao nível de thread,
mapeando múltiplos threads em um único núcleo do TRIPS e S-morph que
tem como objetivo aplicações como fluxo de mídia (streaming media), com alto
nível de paralelismo ao nível de dados [Rutzig et al., 2007].
Na Figura 4.20 é apresentada a arquitetura do TRIPS. Na Figura 4.20(a) é
demonstrado o diagrama da arquitetura TRIPS que irá ser implementada em
um protótipo em chip. O protótipo consiste de quatro núcleos polimórficos,
com dezesseis nós de execução em um arranjo de 32Kb de memória conec-
tados por uma rede de roteamento e um grupo de controladores de memória
distribuídos com canais para memória externa. Na Figura 4.20(b) é descrito
uma visão expandida de um núcleo do sistema TRIPS e o sistema primário de
memória. Na Figura 4.20(c) é descrito um nó de execução do sistema.
Figura 4.20: Arquitetura do TRIPS [Sankaralingam et al., 2003].
4.4 Considerações Finais
Neste capítulo foi apresentado o conceito de arquitetura a fluxo de dados
estática e dinâmica. Este modelo de arquitetura foi proposto por Dennis &
43
Misunas [1975]. No inicio de seu desenvolvimento vários trabalhos foram
propostos, como os descritos no capítulo, após estas pesquisas este tipo de
arquitetura passou por um período de estagnação, não tendo trabalhos rele-
vantes desenvolvidos. Por volta dos anos 2000 algumas pesquisas voltaram a
ser desenvolvidas nesta área e foram descritas neste capítulo.
44
CAPÍTULO
5Compiladores para Hardware
Estes compiladores representam uma grande evolução para a síntese de
alto nível de circuitos digitais, por aproximar programadores de linguagens de
alto nível desta área tão restrita, a projetistas de hardware.
Segundo Hall et al. [2009] as linguagens de alto nível e o desenvolvimento
de compiladores eficientes podem ser vistos como as bases centrais para o
desenvolvimento destas arquiteturas.
5.1 Trident
O Trident [Tripp et al., 2007] é um framework aberto para compilação, que
visa a geração de VHDL tendo como entrada algoritmos descritos em lingua-
gem C. O Trident utiliza técnicas de otimização de código tradicional e técnicas
voltadas à computação de alto desempenho, como por exemplo verificação de
operações que podem ser executadas concorrentemente, geração de circuitos
para controle de fluxo de dados entre a memória, registradores e unidades de
operação.
Para uso do Trident, o programador deve particionar manualmente o pro-
grama e escrever o código C responsável pela comunicação entre o software e o
hardware. O trecho de código a ser mapeado para hardware contém restrições
quanto ao código de entrada, não sendo permitidos comandos de impressão,
código recursivo, uso de ponteiros, funções com argumentos ou passagem de
parâmetros ou vetores sem tamanho declarado. Durante sua execução, os ve-
45
tores e variáveis são alocadas de forma estática e todas as operações de ponto
flutuante são mapeadas para unidades de hardware.
Os passos utilizados na compilação são apresentados na Figura 5.1 e des-
critos a seguir:
• Criação da representação intermediária: Esta representação é gerada
pelo Low Level Virtual Machine (LLVM) [Lattner & Adve, 2004], que é um
framework que utiliza o gcc como front-end, para a geração de um código-
objeto independente de plataforma, conhecido como LLVM Bytecode;
• Transformações na representação intermediária: Para cada instrução if
é criado um hyperbloco1, para explorar o paralelismo a nível de instru-
ções. Nesta fase também é gerado o Control Flow Graph (CFG), que é
utilizado para as otimizações de código e para mapeamento de todas as
operações de hardware em módulos selecionados pelo usuário;
• Sincronização: Neste passo é feita a alocação de vetores. Esta alocação é
feita em tempo de compilação, de forma a se obter um tempo de acesso
determinístico à memória e baixa latência no acesso aos dados. Após
isso é realizada a sincronização, ou determinação de ordem de execução
dos hyperblocos. Para isso, o Trident seleciona a melhor opção entre
os seguintes algoritmos: as soon as possible,as late as possible, forcedirected ou iterative modulo.
• Síntese: Neste passo é feita a tradução para VHDL-RTL utilizando-se o
GFC.
O Trident compartilha seu código e serve de extensão para o SeaCucumber[Tripp et al., 2002], que é um compilador desenvolvido pela Brigham YoungUniversity, com objetivo de gerar VHDL com uma entrada em linguagem Java.
O Trident fornece a este compilador a capacidade de aceitar código de entrada
em C, suportar operações de ponto-flutuante, além de ser o responsável pela
geração do código VHDL.
5.2 Molen
O Molen [Panainte et al., 2007] é um compilador que tem como objetivo ge-
rar código para uma arquitetura específica conhecida como máquina de Molen
[Vassiliadis et al., 2004], constituída por um processador de uso geral e um
FPGA. Este compilador foi desenvolvido para gerar código para uma máquina
1Denominação dada aos blocos de código.
46
Figura 5.1: Fluxo de Compilação Trident [Tripp et al., 2007].
de Molen formado por um processador IBM PowerPC 405 e um FPGA Virtex II
Pro.
O Molen foi construído a partir de dois frameworks, o SUIF [Hall et al.,
1998], utilizado como front-end, e o Machine SUIF [Smith & Holloway, 2002],
utilizado como back-end e ferramenta para aplicar as otimizações para a ge-
ração do código para o FPGA Virtex II. Este código é gerado por meio de uma
representação intermediária própria do Machine SUIF.
Para a geração de código para o processador PowerPC, foi desenvolvido um
back-end específico de acordo com o PowerPC Embedded Application BinaryInterface (EABI) [Sobek & Burke, 1995], no qual estão definidos os padrões
de instruções, alocação de registradores, emulação de operações de ponto-
flutuante e outras informações relevantes para o desenvolvimento do código
alvo.
O Molen propõe uma nova abordagem para sincronização da configurações
de hardware necessárias para executar trechos de uma aplicação. Devido à
complexidade de algumas aplicações, os circuitos em hardware necessários
podem ocupar uma área maior que a disponível no FPGA, o que pode gerar
conflito ou áreas de hardware sobrescritas.
Para evitar este problema foi proposto, com base na eliminação parcial de
expressões redundantes [Cai & Xue, 2003], um algoritmo que busca redu-
zir a área final necessária no FPGA. No caso de não haver espaço suficiente
47
para a implementação dos circuitos de hardware, parte da aplicação, que ini-
cialmente seria implementada em hardware, pode ser executada em software,
paralelamente com a execução de outros trechos em hardware. Desta forma
pode-se assim ganhar desempenho, devido aos altos tempos para a reconfigu-
ração de um FPGA [Sima et al., 2002].
5.3 HThreads: Modelo de programação multithreads
O HTreads [Andrews et al., 2008a] é um compilador para a linguagem C,
que utiliza como front-end o Gnu C Compiler (GCC), que é responsável pela
produção de uma forma intermediária de hardware.
Utilizando-se desta representação intermediária é gerado o código VHDL
para as threads que serão executadas em hardware, o código para o compi-
lador alvo e as bibliotecas necessárias para a sincronização e comunicação
entre as threads.
Para representação intermediária o HTreads utiliza o GIMPLE [Merrill, 2003],
que é uma forma de árvore para representação intermediária que visa facilitar
a otimização de código.
O fluxo de compilação do HTreads é apresentado na Figura 5.2.
Figura 5.2: Fluxo de Compilação HThreads [Andrews et al., 2008a].
O HThreads utiliza a mesma sintaxe e é totalmente compatível com a bi-
blioteca pthreads, permitindo inclusive o teste do código em linguagem de alto
nível em um computador executando qualquer distribuição do sistema opera-
cional Linux.
48
Para o funcionamento correto das threads, as funções que seriam desem-
penhadas pelo sistema operacional foram implementadas via hardware, como
por exemplo o gerenciamento de threads, gerenciamento de semáforos e inter-
rupções da Unidade Central de Processamento (UCP).
5.4 ARISE
O Aristotle Reconfigurable Instruction Set Extension (ARISE) [Vassiliadis et al.,
2009] é um framework que visa permitir a um processador de uso geral sua
utilização com um co-processador para execução de partes de código que ne-
cessite de computação intensa ou com um unidade funcional para execução
de instruções customizadas de granulação fina. O ARISE permite um desen-
volvimento modular de aplicações permitindo a separação entre o hardware e
o software.
O ARISE utiliza a linguagem C/C++ para geração de aplicações e utiliza
como front-end de compilação o MachineSUIF [Smith & Holloway, 2002]. O
código gerado tem como alvo uma arquitetura especifica, formada por um
processador MIPS-I e FPGAs. Nesta arquitetura o número de FPGAs é variável,
pois ela apresenta uma interface para controle dos FPGAs. O código objeto
gerado é constituído por instruções específicas para a arquitetura e instruções
para o processador MIPS-I.
Figura 5.3: Organização Geral de uma Máquina ARISE. [Vassiliadis et al.,2009].
A organização de uma maquina ARISE é apresentada na Figura 5.3 e con-
49
siste em: Um processador principal, um decodificador de instruções, que tem
como objetivo verificar se a instrução deve ser executada pelo FPGA ou pelo
processador de uso geral, a interface de comunicação com os FPGAs e os con-
troladores de execução das instruções (CCU wrappers).
A geração de código no ARISE não é um processo muito simples, sendo
composto por uma série de etapas. A etapa inicia consiste no profiling do
código, que é sub-dividido em duas partes, primeiro é feito o profiling que irá
servir para guiar o restante do processo de desenvolvimento e a segunda parte
serve para estimar o custo de comunicação entre procedimentos e a carga do
sistema. Após esta etapa é feita a otimização do código visando diminuir o
tempo de execução da aplicação, após isso é realizada a separação do código,
dividindo-se o que será executado em hardware e o que será executado em
software , como saída destas etapas temos o profiling results e o pruned code.
Estes arquivos de saída são utilizados para definir qual o tipo de instrução será
gerada, instruções para serem executadas em uma unidade funcional ou uma
instrução de granulação fina. O terceiro passo consiste em fazer as anotações
no código original, com diretivas pragma, de forma a marcar as partes de
instruções ARISE e as instruções a serem executadas pelo processador de uso
geral. Após as execuções destes passos pelo front-end o código é repassado
para o back-end que deve executar três tarefas: desenvolvimento do software,
desenvolvimento do hardware e integração e avaliação da aplicação. Caso a
avaliação da aplicação não seja satisfatória o processo pode ser reiniciado com
a intervenção do programador na definição do que ser executa em software ou
em hardware. O fluxo de desenvolvimento é apresentado na Figura 5.4.
5.5 Nenya / Galadriel
Este projeto foi desenvolvido como uma proposta inovadora para a com-
pilação de algoritmos descritos em Java, por meio de seus bytecodes, para
hardware reconfigurável [Cardoso, 2000].
No processo de compilação existem dois compiladores que atuam em série
para a geração de hardware especializado de forma eficiente. A compilação
é realizada a partir dos bytecodes da linguagem Java, objetivando gerar uma
representação intermediária que explore altos graus de paralelismo.
A forma de atuação dos compiladores e algumas técnicas utilizadas pelos
mesmos podem ser observadas na Figura 5.5 e são descritas a seguir.
O Galadriel é o compilador de front-end responsável por retirar as infor-
mações necessárias do classfile fonte e com estas informações gerar a repre-
sentação intermediária, que consiste em: Grafo de fluxo de controle, grafo de
50
Figura 5.4: Fluxo de desenvolvimento de aplicações no ARISE. [Vassiliadiset al., 2009].
dependência de controle, grafo de dependência de dados,Grafo de dependên-
cias de fusão, grafo hierárquico de dependências de programa e grafo de fluxo
de dados.
O Galadriel não suporta todo o conjunto Java, não permitindo, por exem-
plo, que o código a ser analisado possua invocação de métodos.
Após a geração da representação intermediária, o Nenya é responsável por
executar uma série de transformações nos grafos gerados para otimização do
circuito a ser gerado. Estas transformações envolvem: Re-associação das ope-
rações, redução do custo de operações, aferição do número de bits de repre-
sentação, propagação de padrões de constantes ao nível de bits e aferição do
número de bits em regiões cíclicas.
O código gerado pelo Nenya em VHDL é otimizado para uma arquitetura
constituída por um FPGA e uma ou mais memórias RAM (Random Access
51
Figura 5.5: Fluxo dos compiladores Galadriel e Nenya [Cardoso, 2000].
Memory) [Cardoso & Neto, 2001]. O hardware reconfigurável gerado é formado
por duas unidades: Unidade de dados: responsável por enviar os dados sobre
seu estado à unidade de controle e unidade de controle: responsável pelo
controle dos acessos às memórias, pelo controle de escritas em registradores
e pela execução correta dos ciclos.
Para a geração da unidade de dados o compilador utiliza uma biblioteca
de macrocélulas parametrizáveis. Cada uma destas macrocélulas é respon-
sável pela geração de circuito especializado para a realização de determinada
operação [Cardoso, 2000].
A descrição da unidade de controle é gerada pelo compilador em VHDL-RTL
(Very High Speed Integrated Circuit Hardware Description Language - RegisterTransfer Level) comportamental e por isso é necessário utilizar uma ferra-
menta de síntese lógica para se obter o circuito final.
O Nenya implementa partição temporal com base no algoritmo simulatedannealing [Cardoso, 2000]. A partição temporal consiste em permitir que um
programa que exceda a capacidade do FPGA seja executado. Para esta execu-
ção o circuito gerado é divido em partes chamadas de frações temporais, que
são executadas individualmente.
52
5.6 ROCCC (Riverside Optimizing Configurable Com-
puting Compiler)
O ROCCC [Buyukkurt et al., 2006] [Guo et al., 2005] é um compilador para
sistemas reconfiguráveis, construído a partir do SUIF [Stanford SUIF Compi-
ler Group, 1994], que é um framework de compilação desenvolvida na univer-
sidade de Stanford para a pesquisa colaborativa em técnicas de compilação
[Hall et al., 1998] e do Machine SUIF [Smith & Holloway, 2002], que é um
framework para construção de back-ends de compiladores, desenvolvido pela
Universidade de Harvard, com suporte a várias linguagens de alto nível como:
C/C++, Fortran, Java.
O fluxo de funcionamento do ROCCC pode ser visto na Figura 5.6. O
ROCCC apresenta algumas restrições no código de entrada, associadas à res-
trições ao mapeamento do código para o FPGA, como por exemplo, o uso de
funções recursivas e uso de ponteiros.
Figura 5.6: Fluxo de funcionamento do ROCCC [Buyukkurt et al., 2006].
Este compilador foi desenvolvido com o objetivo de otimizar aplicações de
fluxo intenso de dados, portanto opera melhor com aplicações sem muitos
desvios no fluxo de controle. O ROCCC está dividido em dois componentes
principais: o front-end, responsável por transformações de laços (como por
exemplo movimentação de código ciclo-invariante, abertura total ou parcial de
laços e fusão de laços), e o back-end responsável por otimizações em nível de
procedimentos (como por exemplo propagação de constantes, propagação de
cópias e eliminação de código morto).
Estas otimizações são aplicadas à representação intermediária gerada, que
é denominada CIRRF (Compiler Intermediate Representation for ReconfigurableFabrics), que tem como objetivos explorar ao máximo o paralelismo existente
nos laços, minimizar o acesso à memória, com a reutilização de dados e ge-
rar um pipeline eficiente para minimizar a necessidade de ciclos de processa-
mento.
53
De posse da CIRRF o compilador gera o componente VHDL para cada nó do
CFG que corresponda a partes da aplicação que serão executadas com maior
frequência. Para as partes de código executadas com menor frequência ou
para as quais o FPGA não é eficiente, são geradas as funções em linguagem C.
5.7 Spark
O Spark [Gupta et al., 2004] é o projeto de um compilador de síntese de
alto nível, que tem com objetivo alcançar resultados semelhantes aos obtidos
em projeto desenvolvidos manualmente, por especialistas da área. O Spark
utiliza o ANSI-C como linguagem de entrada e apresenta algumas restrições,
como por exemplo: não permite o uso de ponteiros e funções recursivas.
O Spark foi desenvolvido para facilitar o desenvolvimento de aplicações de
granulação grossa e fina, aplicando técnicas de otimização que podem ser
avaliadas no código VHDL gerado, permitindo otimização do código.
Uma das partes principais do compilador Spark, é chamada de caixa de
ferramentas de transformações, que permite ao programador definir os parâ-
metros das transformações e otimizações que serão aplicadas no código VHDL
a ser gerado. A caixa de ferramenta permite: extrair dependência de dados,
aplicar técnicas de paralelismo de código, aplicar técnicas para renomear va-
riáveis e aplicar como, eliminação de código morto, propagação de constantes
e propagação de cópias.
Para aplicar as transformações e otimizações no código e armazenar toda a
descrição comportamental, o Spark utiliza como representação intermediária
um GHT e um Grafo de fluxo de controle-dados (GFCD), que são acessados
durante todo o processo de compilação, como pode ser visto na Figura 5.7.
5.8 Considerações Finais e Analise Comparativa.
Neste capítulo foram apresentados alguns compiladores para geração de
hardware estudados durante o desenvolvimento deste trabalho. Na tabela 5.1
é apresentada uma comparação entre recursos encontrados nestes compila-
dores.
Pode-se verificar na Tabela 5.1 grande parte dos compiladores estudados
tem como código de entrada a linguagem C, por ser uma linguagem de pro-
pósito geral, com um ótimo conjunto de operadores e estruturas de dados
[Kernighan & Ritchie, 1988]. Pode-se também perceber que os recursos bási-
cos das linguagens, como comando de decisão IF e laços de repetição, estão
presentes em todos os compiladores.
54
Figura 5.7: Fluxo de funcionamento do Spark [Gupta et al., 2004].
Tabela 5.1: Comparação entre os compiladores estudados.Compilador Nenya/
GaladrielSpark ROCCC Trident Molen HThreads
CódigoFonte
Java C C/C++,Fortran,Java
C C C
If Sim Sim Sim Sim Sim SimLaços Sim Sim Sim Sim Sim SimEstruturas Não Não Sim Sim Sim SimPonteiros Não Não Não Não Sim SimFunções Não Não Não Não Sim SimRepres.Interme-diária
GHDP GHT+GFCD
CIRRF LLVM MachineSUIF IR
Gimple
Código Ge-rado
VHDL VHDL VHDL+C VHDL VirtexII Pro +PowerPc
VHDL
55
56
CAPÍTULO
6O Projeto ChipCflow
Neste capítulo é descrito o projeto ChipCflow [Silva, 2006], que é a base
para o desenvolvimento deste trabalho. Durante o desenvolvimento do traba-
lho descrito nesta tese, foi realizado em paralelo o aperfeiçoamento da arqui-
tetura do projeto.
No projeto ChipCflow tem-se como objetivo o desenvolvimento de uma fer-
ramenta para execução de algoritmos descritos em linguagem C, utilizando o
modelo a fluxo de dados em hardware reconfigurável, e vem sendo desenvol-
vido no Laboratório de Computação Reconfigurável (LCR), vinculado ao De-
partamento de Sistemas de Computação do Instituto de Ciências Matemáticas
e de Computação (ICMC) da Universidade de São Paulo. O principal obje-
tivo do projeto aqui apresentado é utilizar o modelo a fluxo de dados estático,
buscando melhorar o desempenho de aplicações descritas em linguagem C. A
ferramenta aproveita o potencial de paralelismo existente implicitamente na
aplicação. A principio toda a aplicação é convertida em hardware a fluxo de
dados estático, visando-se em um segundo estágio, acelerar somente as par-
tes de maior processamento, como loops e cálculos complexos, aproveitando
de forma mais eficiente partes passíveis de grande paralelismo, considerado
natural no modelo a fluxo de dados [Silva et al., 2009].
A pesquisa para desenvolvimento da ferramenta foi iniciada pelo Prof. Dr.
Jorge Luiz e Silva em sua tese de doutorado [Silva, 1992], que tinha como
objetivo propor uma máquina a fluxo de dados dinâmica tolerante a falhas.
No projeto inicial foi proposto o conceito básico utilizado nos operadores da
máquina a fluxo de dados estática do projeto ChipCflow.
57
O fluxo de funcionamento do projeto ChipCflow pode ser visto na Figura
6.1. Para a geração desta arquitetura um programa descrito em linguagem
C é pré-compilado visando a geração de um Control Dataflow Graph (CDFG).
Este CDFG após passar por otimizações necessárias é então convertido em
VHDL utilizando uma base de operadores pré-definidos, gerando-se assim
uma arquitetura a fluxo de dados estática. Após gerado o VHDL o próximo
passo consiste em sintetizar e carregar o programa em um FPGA, utilizando
ferramentas disponibilizadas pelo fabricante do FPGA.
Figura 6.1: Fluxo de execução do ChipCflow, adaptado de [Silva, 2006].
6.1 Operadores propostos para a máquina a fluxo de
dados estática do projeto ChipCflow
Um programa para ser executado em uma máquina a fluxo de dados deve
ser organizado como uma máquina a fluxo de dados [Lopes, 2012]. Neste tipo
de máquina cada nó existente especifica uma instrução a ser executada e os
arcos entre os nós especifica a dependência entre as instruções e o caminho
dos dados entre os nós.
Como definido em [Arvind, 2005] [Dennis & Misunas, 1975] e utilizado em
[Silva, 1992], exitem dois tipos de arcos na descrição da máquina a fluxo de
dados. Como mostra a Figura 6.2, um arco é representado por uma linha
continua e representa os valores a serem enviados entre os nós. O arco re-
presentado por uma linha pontilhada representa os valores booleanos, que
servem como sinais de controle.
Para possibilitar a transformação do código escrito em linguagem de alto
nível em um CDFG, foram definidos operadores específicos para o projeto.
Estes operadores tem a função de realizar todo o processamento e também
gerenciamento do funcionamento da máquina a fluxo de dados. A principio
58
Figura 6.2: Tipos de links utilizados no projeto da máquina a fluxo de dadosestática do projeto ChipCflow [Arvind, 2005].
estes operadores estão divididos em duas categorias: (a) Operadores de pro-
cessamento e (b) operadores de gerenciamento de memória.
A seguir serão descritos todos os operadores e suas funcionalidades.
6.1.1 Operadores de processamento
Para o processamento das instruções definidas em linguagem de alto nível
serão utilizados seis operadores, que são:
• decider: operador que recebe dois valores como entrada e de acordo com
uma função de teste, tem como saída o valor true ou false,
• Non-deterministic merge: operador que recebe dois valores de entrada e o
primeiro a ser recebido é enviado para a saída,
• Deterministic merge: operador que recebe dois valores e de acordo com
um valor de controle define qual das entradas será copiada para a saída,
• Branch: operador que recebe um valor como entrada e de acordo com um
valor de controle recebido, decide para qual saída irá enviar uma cópia
do valor,
• Copy: operador que copia um valor de entrada para duas saídas e
• Operator: operador que implementa operações aritméticas básicas (soma,
subtração, divisão, multiplicação e operações lógicas).
Na Figura 6.3 são descritos os seis operadores e seus respectivos símbolos.
Após o processamento, ou seja, o consumo dos dados, os mesmos são envi-
ados para as saídas dos operadores. O processo de entrada e saída dos dados
dos operadores pode ser visto na Figura 6.4. Na primeira parte da Figura
pode-se ver os dados (círculos preenchidos) de entrada em cada operador, da-
dos para computação ou dados de controle. Após a computação, os dados
(círculos preenchidos) são enviados para as saídas de acordo com o processa-
mento realizado.
59
Figura 6.3: Operadores do projeto da máquina a fluxo de dados estática doprojeto ChipCflow [Silva, 2006].
Figura 6.4: Operadores usados em uma máquina a fluxo de dados com dadoschegando em seus arcos de entrada, e dados saindo após a computação [Teifel& Manohar, 2004].
6.1.2 Operador para controle de Loop
Além dos operadores clássicos de uma máquina a fluxo de dados apre-
sentados anteriormente, para a máquina a fluxo de dados estática do projeto
ChipCflow, foi desenvolvido também um operador para controle de Loops, de
forma a simplificar e diminuir o hardware necessário para a implementação
do comando For. Utilizando os operadores convencionais o controle de um
loop se mostrava bastante complexo, além de consumir uma grande área de
hardware. A diferença entre as implementações do controle de Loops podem
ser vistas na Figura 6.5. Como demonstrado na Figura 6.5, o controle com
60
os operadores convencionais implica na implementação do controle do incre-
mento e também da comparação para término do Loop, além de cópias de
valores simplesmente para que o Loop seja executado um número N de vezes.
No caso do operador For especifico da máquina a fluxo de dados estática do
projeto ChipCflow, o processo de incremento assim como a comparação para
indicar o final do laço são realizadas internamente no operador.
Figura 6.5: (A) Controle de Loops utilizando os operadores convencionais deuma máquina a fluxo de dados. (B) Controle de Loop usando o operador For.
O operador For, como pode ser visto na Figura 6.6, recebe dois valores
principais para iniciar o seu funcionamento, o valor I que indica o contador
inicial do laço e o valor N que indica o numero total de iterações. De posse
destes dois valores o operador pode iniciar seu funcionamento, gerando dois
outros dados, OC que é um valor lógico, que sinaliza True ou False e serve
como sinal de controle para a execução ou não de determinado trecho da
máquina a fluxo de dados, e o valor Si que é o valor atualizado do contador,
caso seja necessário o seu uso na computação dos dados.
Figura 6.6: Operador For.
61
Além das entradas e saídas, citadas anteriormente para o operador for, fez-
se necessário a implementação de mais duas entradas e duas saídas, de 1 bit
cada, visando o aninhamento de dois ou mais operadores.
Para ilustra o problema aqui descrito pode-se analisar o trecho de código
para calculo de tabuada, apresentado na Listagem 2. Neste trecho de código,
após a geração do primeiro valor de i, o for j começa a ser executado, não sendo
possível a geração de um novo valor para i, antes que o for j seja concluído.
Assim que o for j terminar sua execução o controle volta para o for i para que
este gere um novo valor de i e a execução continue.
Código 2: Exemplo de For aninhado.for (i=1; i<=10; i++)
for (j=1; j<=10; j++)
tab=i*j;
Conforme descrito na Figura 6.7, assim que o operador for i gera o primeiro
valor de i, ele entra em modo de espera, e avisa por meio de sua saída FCoutao operador for j que precisa de uma confirmação do consumo do dado. O for j
recebe esta informação por meio de sua entrada FCin e após o termino de sua
execução ele utiliza sua saída FCinAck para informar ao operador for i que
terminou sua execução, liberando o operador e permitindo assim a geração do
proximo valor para i.
Figura 6.7: Exemplo de operadores For aninhados.
Este processo garante que os dados sejam gerados em ordem correta e
mantenham a correta execução, característica fundamental para uma má-
quina a fluxo de dados estática.
Caso estes conjuntos de entradas e saídas (FCInAck, FCIn, FCoutAck e
FCout) não sejam utilizados, as saídas são ligadas a operadores sem função,
para que o valor gerado seja consumido e suas entradas sejam alimentadas
62
com valores padronizados de forma a permitir o correto funcionamento dos
operadores.
6.1.3 Operadores de Memória
Para controle de acesso a memória foram implementados dois operadores
baseados em instruções Load e Store, que são ligados a um operador de con-
trole e acesso a memória. O operador Load tem como função buscar os dados
necessário ao grafo na memória. Para esta função é informado ao operador
qual o endereço de memória no qual o dado pode ser encontrado, e em caso
de vetores ou matrizes, o deslocamento necessário para percorrê-los. Após o
acesso, o operador Load entrega o dado ao operador ligado a ele. O opera-
dor Store tem funcionamento semelhante ao Load, entretanto sua função é
armazenar o dado vindo do grafo na memória. Faz-se necessário informar o
endereço na memória e caso trate-se de um vetor ou matriz, o deslocamento
necessário para o armazenamento do dado enviado pelo operador ligado a ele.
Figura 6.8: Formato dos operadores Load e Store.
6.1.4 Protocolo de comunicação entre operadores
Os operadores controlam o recebimento e envio de tokens por meio de um
protocolo handshake, proposto em [Silva & Marques, 2006]. Neste protocolo
foi definido que para cada entrada ou saída de um operador, como pode ser
visto na Figura 6.9, deve haver um par de sinais (ack e strobe) que são res-
ponsáveis pelo controle de comunicação entre os operadores, realizando a sin-
cronização dos tokens nas entradas e saídas. Assim que o token é enviado
para um operador consumidor, juntamente com ele é enviado, pelo operador
produtor, um sinal de strobe, sinalizando que existe um novo dado no arco.
Após a leitura do dado o sinal de ack é então enviado do operador consumi-
dor para o operador produtor, sinalizando que o dado já foi consumido. Até o
recebimento do sinal de ack, o operador produtor fica impedido de enviar um
63
novo dado para o operador consumidor, permitindo desta forma apenas um
dado por vez em cada arco.
Figura 6.9: Protocolo de Handshake entre operadores.
6.2 Considerações Finais
Neste capítulo foi apresentado o projeto ChipCflow, projeto este no qual se
insere esta proposta de trabalho de doutorado. Foi apresentado o conceito
básico da máquina a fluxo de dados estática e suas características bem como
o desenvolvimento dos operadores necessários para o seu funcionamento. No
próximo capitulo é descrito o conversor de código C para aplicações para a
máquina a fluxo de dados estática do projeto ChipCflow, tratando-se de um
compilador responsável por gerar uma CDFG com os operadores apresentados
neste capítulo.
64
CAPÍTULO
7Conversor de Código C em
Aplicações ChipCflow
A presente tese tem como objetivo o desenvolvimento de uma ferramenta
de geração de código que permita a conversão de aplicativos descritos em lin-
guagem C, para aplicações executáveis, utilizando o modelo de arquitetura a
fluxo de dados estática em FPGA, como parte fundamental do projeto ChipC-
flow. Projeto este que como citado no Capitulo 6, consiste de uma ferramenta
para o desenvolvimento de arquiteturas a fluxo de dados executadas em FPGA,
visando o ganho de desempenho em aplicações, por meio da exploração do
paralelismo implícito, encontrado em código descritos em linguagem C. Além
disso, nesta tese tem-se como objetivo a PROVA DE CONCEITO, sobre a via-
bilidade de conversão de um código em linguagem C para o código VHDL cor-
respondente a uma arquitetura a fluxo de dados estática, a apresentação de
resultados que comprovem a corretude do código gerado, o correto funciona-
mento da arquitetura, e verificar o desempenho apresentado pela arquitetura
desenvolvida.
Na Figura 7.1 pode ser visto o fluxo de funcionamento da ferramenta de-
senvolvida durante o trabalho. Como pode ser visto na Figura 7.1. A partir
do código fonte descrito em linguagem C, é realizada uma análise do código
visando a geração de um código intermediário, a partir deste código inter-
mediário, tem-se a geração de um arquivo com as definições e dados iniciais
da memória da arquitetura. Em paralelo com a geração do arquivo inicial de
memória, tem-se a geração do grafo a fluxo de dados. Este grafo, é então
65
convertido na representação da arquitetura a fluxo de dados estática, em lin-
guagem de descrição de hardware. Este código em linguagem de descrição
de hardware faz referência aos operadores da arquitetura, que necessitam ser
armazenados juntamente com o código gerado.
A seguir será descrita de forma detalhada o processo para conversão de
código C em linguagem VHDL correspondente à arquitetura a fluxo de dados
estática.
Figura 7.1: Fluxo de funcionamento da ferramenta de conversão de código.
7.1 Conversão de Código C
A ferramenta para conversão de código foi divido em um front-end e um
back-end, gerados em linguagem C. O fluxo de funcionamento da ferramenta
esta dividido nas seguintes etapas: leitura das instruções, e quebra destas
instruções em unidades formadas por dois operandos e um operador; gera-
ção de arquivo para carregamento dos dados iniciais na memória; geração de
instruções de três endereços; geração de código intermediário, baseado nas
instruções de três endereços, de acordo com as particularidades dos opera-
dores do projeto ChipCflow e por fim a conversão do código intermediário em
código VHDL compatível com a máquina a fluxo de dados estática.
66
7.1.1 Front-End
O código de entrada é descrito em linguagem C, com restrições, ou seja, um
sub-conjunto da linguagem, descrito na Tabela 7.1. A restrição dos coman-
dos de entrada se deve em parte às características da arquitetura, como por
exemplo, não possibilitar o uso de ponteiros visto que a memória será alocada
sequencialmente, em bancos de memória individuais, para cada vetor ou ma-
triz. Devido a definição de todos os dados de entrada já estarem presentes no
código C, e não sendo possível novas entradas, toda a alocação e leitura de me-
mória é feita pelos operadores load e store, como foi descrito no Capitulo 6. No
caso de laços de repetição e também comandos de decisão, a implementação
foi definida de acordo com os operadores disponíveis no projeto.
Tabela 7.1: Sub-conjunto da linguagem COperações matemáticas +,−, ∗, /, sqrt()Operações booleanas &, |Operação de comparação ==Comandos de decisão if, then, elseLaços de repetição ForComando de atribuição =Tipos de variáveis int, char, int[], char[]
Uma vez definido o sub-conjunto da linguagem a ser utilizado, foi definido
que a código de entrada deveria respeitar a sintaxe exata da linguagem C, de
forma a permitir a migração de aplicações com o mínimo possível de alterações
no código.
O processo de conversão tem inicio com a leitura das instruções e frag-
mentação das mesma, de modo a facilitar o processo de conversão para um
código intermediário. Nesta fase foi utilizada a ferramenta de análise léxica
Flex [Project., 2008], que permite a leitura do código de entrada, separando-
o em tokens, e permitindo uma correta interpretação e tratamento de cada
um dos operadores e operandos existentes. Na Figura 7.2 é apresentado o
diagrama de funcionamento do analisador léxico Flex.
O analisador léxico é responsável pela retirada de espaços em branco e
também comentários existentes no código, além de verificar a corretude do
código, permitindo a criação de rotinas para tratamento de erros.
Para o correto funcionamento do analisador léxico, faz-se necessário a ge-
ração de uma tabela de símbolos, que contém os tokens e lexemas permitidos
na linguagem.
Na Listagem 3 é descrito um trecho da tabela de símbolos utilizada no de-
senvolvimento do gerador de código. Como pode ser visto na Listagem 3, para
67
Figura 7.2: Fluxo de funcionamento do Flex.
cada caractere ou conjunto de caracteres encontrados, que forme um token,
é associado um valor de retorno. Por exemplo, caso o token encontrado pelo
analisador léxico seja o if, esta tabela retornará para a ferramenta de conver-
são o valor seis (6). Com este valor a ferramenta identifica qual a rotina de
tratamento deve ser acionada. Este processo é repetido para todos os tokensencontrados durante o processo de conversão.
A especificação desta tabela de símbolos foi desenvolvida com base no sub-
conjunto da linguagem permitido para uso, ao invés de utilizar a definição
completa da linguagem. Sempre que a compilação de um trecho de código for
realizada, todos os tokens gerados pelo Flex, são validados de acordo com a
tabela de símbolos disponível e um erro será gerado caso algum destes tokensapresente alguma não conformidade.
Código 3: Trecho da tabela de símbolos./*** sessão de regras ***/
main return(888);
[a− zA− Z][a− zA− Z0− 9]* return(2);
if return(6);
else return(7);
for return(9);
pow return(5);
sqrt return(5);
int return(1);
Parte do tratamento dos tokens é verificar o tamanho da expressão e a
declaração de variáveis. Caso seja identificada a declaração de uma variável,
os dados desta declaração, como label da variável e seu valor, além de um
endereço gerado em tempo de compilação, são armazenados em um arquivo
.COE, que é utilizado para o carregamento dos dados iniciais na memória,
68
além de conter configurações de memória. Na Figura 7.3 é apresentado trecho
de arquivo .COE.
Figura 7.3: Exemplo de arquivo .COE.
No trecho do arquivo .COE demonstrado, podemos ver dois parâmetros de
configuração da memória, o parâmetro memory_initialization_radix informa a
forma de armazenamento dos dados, por exemplo em binários ou hexadeci-
mais. O segundo parâmetro, memory_initialization_vector, pode ser utilizado
para informar um vetor de inicialização para a memória, ou seja, criar a me-
mória já carregada com dados.
Este arquivo para geração de memória, é uma das saídas da ferramenta de
geração de código, necessário para a correta implementação da memória da
máquina a fluxo de dados estática.
Em paralelo com a geração das entradas iniciais da memória, as instruções
são verificadas e quebradas em instruções de dois ou três endereços que são
compostas por operações binárias ou unárias e atribuição.
Para este processo, após o recebimento de cada token, a expressão é ana-
lisada e, caso a instrução possua somente três endereços, como por exemplo
a instrução a = b + c, ela á automaticamente convertida para uma instrução
de três endereços (add b,c,a). Caso a expressão possua mais elementos, ela
será montada, como representada na linguagem C. A formatação da expres-
são como esta na linguagem C, é necessária para a avaliação da precedência
de operadores, visando a correta computação da expressão. Após remontada
a expressão é lida e dividida em expressões menores, levando-se em conta a
precedência de operadores.
Para a quebra das instruções, faz-se necessário o uso de operandos tem-
porários, responsáveis por armazenar os valores intermediários da execução
das partes da instrução. Como pode ser visto na Listagem 4, que apresenta
69
um exemplo desta quebra de uma instrução longa em instruções de três en-
dereços, após a computação da multiplicação a ∗ c, o resultado é armazenado
em um operando chamado Temp1. Isso é necessário, para que se possa ar-
mazenar o valor da multiplicação e utilizá-lo em outro trecho de código, neste
exemplo, na linha subsequente (Temp2 = 4 x Temp1).
Código 4: Instrução quebrada em instruções de três endereços.Instrução originald = b2 - 4 * a * c
Instrução quebrada em três endereçosTemp1 = a * c
Temp2 = 4 * Temp1
Temp3 = b * b
d = Temp3 - Temp2
Após a quebra de instruções, como visto na Listagem 4, é gerado o código
intermediário de três endereços. Este formato de representação foi escolhido
por ser um formato conhecido e por facilitar a geração da representação inter-
mediária e sua conversão em operadores da arquitetura a fluxo de dados. O
código intermediário gerado pode ser visto como uma representação linear de
um grafo. Na Listagem 5 é apresentado um trecho do código de três endereços
gerado a partir da quebra da instrução apresentada na Listagem 4.
Código 5: Trecho de instruções de três endereços geradas.mult a, c Temp1
mult 4, Temp1, Temp2
mult b, b, Temp3
sub Temp3, Temp2, d
A representação intermediária em três endereços foi adaptada de forma
a permitir a representação dos operadores da arquitetura a fluxo de dados
estática do projeto ChipCflow, isto porque alguns operadores possuem um
conjunto maior ou menor de entradas e saídas consequentemente mais ou
menos que três endereços. Um exemplo da representação destes operadores
pode ser visto na Listagem 6.
Código 6: Trecho de instruções de três endereços com operadores espe-
cíficos da arquitetura.for i,n;
add first, second, tmp;
atr second, first;
atr tmp, second;
%END FOR;
70
Na Listagem 6 pode-se ver um operador for representado, e suas respec-
tivas entradas, i e n, ou seja o valor inicial do contador e seu valor final,
sendo uma instrução com apenas dois endereços. No caso do operador de atrsomente dois endereços são necessários, por se tratar de uma simples atribui-
ção de valor entre endereços, como por exemplo second = first. Um detalhe na
geração desta representação é a ordem das entradas e saídas dos operadores,
como por exemplo, o operador add possui três endereços representados, os
dois primeiros, first e second, são suas entradas, e tmp sua saída.
Embora na fase inicial os comentários do código sejam retirados, nesta
fase, como pode ser visto na Listagem 6, alguns comentários específicos são
incluídos de forma a apontarem o final de loops e trechos específicos que
podem ou não ser executados, como os presentes em comandos de decisão.
Esta anotação é necessária para a posterior alteração de código, e consequente
geração da máquina a fluxo de dados estática. O ponto-e-vírgula tem como
função indicar o final de cada uma das linhas de instruções, evitando assim a
necessidade de continuar lendo uma linha para a verificação da existência de
novos dados.
Esta representação intermediária gerada, é o ultimo passo do processo de
tradução atribuído ao front-end, e por ser definida em um modelo genérico,
permite o seu reaproveitamento para a geração de código para outras plata-
formas.
7.1.2 Back-End
Para a conversão do código intermediário gerado, foi necessária a definição
de um conjunto de mnemônicos para representar cada um dos operadores.
Na Tabela 7.2 é apresentado o conjunto de mnemônicos e o formato de
cada instrução especifica do ChipCflow.
Tabela 7.2: Representação dos operadores no código intermediárioOperador Mnemônicos FormatoOperator add, sub, mul, div add a,b,cBranch branch branch a,ctrl,b,cMerge merge merge a,b,ctrl,cNon Deterministic Merge Nmerge Nmerge a,b,cCopy copy copy a, a1, a2For for for i,n,ctrl, fi 1
Atr Nmerge atr a,b,cInc add add a,1,bDec sub sub a,1,bNon Operator nop nop a
71
Além das instruções que representam os operadores descritos para a ar-
quitetura a fluxo de dados estática, foram criados mnemônicos para pseudo-
instruções, pois alguns comandos da linguagem C não são representados di-
retamente por operadores, mas podem ser implementados, e tiveram suas
identificações mantidas de forma a permanecerem compativeis com o código
original. Estes comandos são apresentados na Tabela 7.3
Tabela 7.3: Representação das pseudo-instruções no código intermediárioOperador Descrição Mnemônicos FormatoAtr Atribuição de valores (a:=c) Nmerge atr a,nop,cInc Incremento (a++) add add a,1,bDec Decremento (a−−) sub sub a,1,b
Devido a característica de alguns operadores como por exemplo o for, foi
implementado um operador adicional chamado NOP, ou not operator, que tem
como função receber um valor que não será utilizado. Isso é necessário, pois
sempre que um operador dispara, ou seja, envia um valor para sua saída, ele
precisa receber um sinal confirmando o consumo daquele sinal pelo operador
subsequente. No caso do operador for uma de suas saídas é o valor da variável
incrementada. Esta variável, pode ou não ser usada no grafo, dependendo de
sua característica. Caso o dado não seja utilizado ele é ligado a um operador
NOP, de forma que o operador for, receba uma confirmação do recebimento do
dado gerado.
Com a definição do mnemônicos, é realizada a geração do código interme-
diário especifico para a máquina a fluxo de dados estática. Na Listagem 7
pode ser visto um exemplo de código intermediário gerado para a arquitetura.
Código 7: Trecho de instruções de três endereços com operadores espe-
cíficos da arquitetura.for i, n, _L1, if
nop if
copy _L1, _L1, _L1
Nmerge second, fibo, second
Nmerge first, second, first
branch second, _L1, second, second
branch first, _L1, first, first
copy second, second, second
add first, second, tmp
nop first
Nesta fase da representação intermediária, são incluídos todos os operado-
res necessários para que a arquitetura a fluxo de dados estática seja repre-
72
sentada. Como pode ser visto na Listagem 7 foram acrescentadas, ao código
da Listagem 6, as saídas dos operadores, como por exemplo os valores _L1 e
if no operador for, além de operadores para executar o controle do fluxo dos
dados no grafo, como por exemplo as cópias do valores de controle (copy) e
operadores branch, responsáveis por verificar se os dados continuam no loop,
ou se o loop chegou ao fim. Os comandos de atribuição (atr) foram substituí-
dos por operadores Nmerge, que repassam para suas saídas o o primeiro valor
a chegar em suas entradas.
Os operadores de acesso a memória load ou store, são incluídos pelo com-
pilador, mas sua ligação com o operador de gerenciamento de memória é feita
direto no código VHDL.
A inclusão do operador non deterministic merge é necessária sempre que
temos a reentrada de um dado, ou seja, sua reutilização em um grafo. Como
exemplo podemos citar o uso de uma variável para acumular um valor durante
a execução de um laço de repetição. Esta variável inicia o laço com o valor zero
e a cada iteração do laço seu valor é alterado, sendo somada a outro valor, o
que significa que esta variável é reentrante no grafo, e necessita ser mantida
durante toda a execução do grafo. Um exemplo da utilização destes operadores
pode ser visto na Figura 7.4
Figura 7.4: Exemplo de uso dos operadores branch e merge.
O operador branch, controla a execução das instruções contidas no laço,
ou então a saída do laço, e é utilizado como um jump dentro do grafo. Sempre
que o operador for incrementar o valor de sua variável de controle, o operador
branch recebe um sinal em sua entrada de controle, informando se o loop será
executado novamente ou não, como base nesta informação, o operador define
para qual de suas saídas enviará o dado que chegar a sua entrada.
73
Estes operadores são incluídos de acordo com as anotações feitas no có-
digo, quando a representação intermediária é gerada pelo front-end, removendo-
se assim o comentário incluído. Esta anotação permite por exemplo verificar
o final de um laço ou comando de decisão e também definir a existência de
comandos aninhados.
Com a inclusão destes comandos, o código intermediário especifico já re-
presenta uma máquina a fluxo de dados estática do projeto ChipCflow, mas
antes da conversão faz-se necessário renomear variáveis que aparecem várias
vezes dentro do grafo.
Isso se deve ao fato de que na arquitetura a fluxo de dados, os nomes das
variáveis não representam endereços de memória, como em uma arquitetura
convencional, este nomes representam ligações entre operadores que devem
ter nomes únicos dentro do circuito gerado.
Para este processo foi gerado um algoritmo que lê o código existente e com
base no nome de cada variável armazenada na memória, realiza a substituição
nos nomes quando encontrados no grafo. Na primeira ocorrência do nome, o
mesmo será mantido, mas assim que for encontrado novamente, o nome será
alterado. Um modelo desta alteração pode ser visto na Listagem 8.
Código 8: Trecho de código intermediário com a substituição de nomes
das variáveis.add i,1,_i1
add first, second, tmp
atr _second1, NOP, _first1
atr _tmp1, NOP, _second2
O algoritmo após ler a variável pela primeira vez, sai percorrendo o código
e a cada vez que encontra o mesmo valor, altera a variável incluindo um un-derline (_) no inicio de seu nome e um contador sequencial no final, de forma
a distingui-los. A inclusão do caractere underline (_) no inicio, visa evitar que
o processo de troca dos nomes gere algum nome já existente no código. Além
de renomear, este algoritmo também verifica todas as arestas soltas no grafo.
Cada aresta solta deve ser ligada a um operador de store para que tenha o
seu valor armazenado na memória. Este processo tem que ser realizado, pois
como citado anteriormente, um sinal sem resposta faz com que um operador
trave e toda a execução pare por falta de dados. Este tipo de ligação só é re-
alizado caso o valor em questão faça parte das variáveis declaradas no código
entrada, caso isso não ocorra, este sinal é conectado a um operador NOP.
Para renomear as variáveis o algoritmo utiliza uma lista encadeada que
armazena os dados necessários para encontrar e gerar os novos nomes, como
o nome da variável original e um contador para verificar quantas vezes ela já
74
ocorreu no código. Este processo gera o novo código também armazenado em
um lista, que posteriormente é passada para um arquivo de texto que constitui
a segunda saída da ferramenta de geração de código. Este arquivo foi gerado
visando verificar a corretude das instruções geradas.
O próximo passo do processo é a geração de um CDFG, que é uma repre-
sentação do grafo a fluxo de dados. Esta representação já pode ser vista como
o arcos, que representam as trocas de dados entre estes nós.
Com o CDFG gerado de forma correta, o processo de tradução do grafo para
o código VHDL é iniciado. A primeira etapa neste processo é a geração de um
arquivo VHDL com o cabeçalho indicando o nome do arquivo e carregando
as bibliotecas necessárias que correspondem aos operadores já implementa-
dos em VHDL e que serão utilizados no CDFG. Em uma segunda etapa do
processo, uma entidade (Entity) é gerada, bem como seus sinais com seus res-
pectivos tamanhos em bits. Estes sinais podem ser vistos como as variáveis
existentes no código, além dos sinais de ACK e Strobe gerados para controle
dos operadores. Esta é a parte principal do arquivo VHDL, por definir as in-
terfaces com as entradas e saídas da máquina a fluxo de dados estática que
esta sendo gerada.
A terceira etapa do processo consiste em gerar o corpo da máquina a fluxo
de dados estática, ou seja, sua arquitetura (Architecture), onde serão defini-
dos todos os operadores que serão utilizados, bem como a interconexão entre
eles. A descrição de todos os operadores já está definida na ferramenta de
conversão de código e são carregadas após a leitura do mnemônico referente
a cada operador. Este modelo permite a utilização de um banco de dados de
operadores, facilitando o uso e a geração do código.
Ainda nesta terceira etapa do processo, é necessário gerar todos os sinais
que interligaram os operadores. Além dos sinais de entrada e saída de da-
dos, são definidos os sinais de controle (ACK e Strobe) e os sinais de (Clock e
Reset), que são considerados entradas na definição da entidade, citada ante-
riormente, e são utilizados por todos os operadores.
Nas definições dos sinais não pode haver nomes repetidos, por este motivo,
é necessário submeter o código ao processo de renomear variáveis.
Nesta fase cada um dos nomes de operadores definidos durante o processo
de geração do código, é identificado e tem o seu operador correspondente atri-
buído a ele. A definição de todas as suas interfaces é o que define a quem
cada um dos operadores esta conectado, por este motivo é muito importante
que a fase de verificação de todas as entradas e saídas geradas no código in-
termediário seja feita de forma correta. Um exemplo de código VHDL gerado
pode ser visto no Apêndice A.
75
7.2 Considerações Finais
Neste Capítulo foi apresentada a ferramenta de conversão de código de-
senvolvida, suas particularidades e suas limitações iniciais. Esta ferramenta,
parte fundamental do projeto ChipCflow, é o resultado deste trabalho. Neste
Capítulo foi descrito todo o processo necessário para que o código de entrada,
em linguagem de alto nível, seja convertido em uma linguagem de descrição
de hardware, representando uma arquitetura a fluxo de dados estática.
No próximo Capítulo serão apresentados os resultados da prova de conceito
para a geração da arquitetura a fluxo de dados estática do projeto ChipCflow.
76
CAPÍTULO
8Resultados
Neste capítulo são apresentados teste realizados para a conversão de código
em linguagem C diretamente para VHDL. Este testes foram utilizados como
prova de conceito na geração do código equivalente a arquitetura a fluxo de
dados proposta.
Além disso, foi realizada uma análise de desempenho dos resultados obti-
dos com outras plataformas.
8.1 Testes Realizados
Para efeito de teste, foi realizada a conversão de três algoritmos, para a
arquitetura a fluxo de dados proposta, são eles: Fibonacci, Fatorial e Deter-
minante de uma matriz quadrada.
Os algoritmos convertidos para uma máquina a fluxo de dados estática, fo-
ram sintetizados e simulados em um FPGA da família Virtex 7 (7V285TFFG11
57-3), usando a ferramenta ISE 13.2 (Xilinx [2014]), comprovando o correto
funcionamento dos algoritmos, segundo a simulação em hardware.
8.1.1 Algoritmo de Fibonacci
O primeiro teste realizado foi a implementação do algoritmo de Fibonacci,
que apresenta uma dependência de dois níveis entre as iterações. O código
77
fonte do algoritmo utilizado pode ser visto na Listagem 9.
Código 9: Exemplo de Fibonacci descrito em Linguagem C.#define N 10000
int fibonacci(){
int i;
int second=1;
int first=0;
int fibo;
for (i=0; i<N; i++){
fibo=first+second;
second=first;
first=fibo;}
return fibo;
}
Após a execução do código em linguagem C, descrito na Listagem 9, que
comprova os resultados esperados para o algoritmo de Fibonacci, utilizou-se
a ferramenta de conversão de código, gerando assim o código intermediário, já
com as alterações necessárias para a implementação da máquina a fluxo de
dados estática do projeto ChipCflow. Este código pode ser visto na Listagem
10.
Código 10: Código intermediário gerado para o algoritmo de Fibonacci.for i, n, _L1, ifnop ifcopy _L1, _L11, _L12Nmerge second, fibo, _second1Nmerge first, _second4, _first1branch _second1, _L11, _second2, _second3branch _first1, _L12, _first2, _first3copy _second2, _second4, _second5add _first3, _second5, fibonop _first2
Antes da geração do código, uma análise de todas as operações a serem
executadas dentro do loop é realizada. Com base nesta análise os operadores
necessários para a correta execução são criados. Os operadores são ordenados
dentro do código intermediário buscando facilitar a interpretação do código
gerado.
Esta representação intermediária, está pronta para ser convertida no có-
digo VHDL correspondente ao grafo a fluxo de dados. Este grafo pode ser visto
na Figura 8.1.
78
Figura 8.1: Grafo gerado para o algoritmo de Fibonacci.
O ultimo passo é a geração do código VHDL. Neste código cada um dos
operadores é instanciado, com o seu nome definido no VHDL component e
também diferenciado, caso o mesmo operador apareça mais de uma vez no
código, com um numerador sequencial, como acontece no caso de renomear as
variáveis. O código VHDL completo correspondente ao algoritmo de Fibonacci,
implementado no modelo a fluxo de dados estático do projeto ChipCflow, pode
ser visto no Anexo A.
Na Figura 8.2 é descrito uma parte do código do algoritmo de Fibonacci
convertido para VHDL.
Como pode ser visto na Figura 8.2, a entidade (Entity) Fibo possui todas
as entradas e saídas necessárias para a implementação da máquina a fluxo
de dados estática, que corresponde ao algoritmo de Fibonacci, inicialmente
descrita em linguagem C. Como também pode ser visto no final da Figura 8.2,
o componente fori, foi instanciado como fori0. Caso outro componente forifosse utilizado ele seria nomeado como fori1 e assim sucessivamente.
O código gerado foi sintetizado no ambiente ISE obtendo-se os dados apre-
sentados na Tabela 8.1.
Tabela 8.1: Resultados obtidos após a síntese do algoritmo de Fibonacci nomodelo da máquina a fluxo de dados do projeto ChipCflow.
FibonacciFF 143LUT 416Slices 197Frequência 556,514 MHz
79
Figura 8.2: Trecho do código VHDL gerado para o algoritmo de Fibonacci.
8.1.2 Algoritmo de Fatorial
Para efeito de teste o mesmo processo foi realizado com o algoritmo para
calculo de Fatorial. O código correspondente ao algoritmo implementado pode
80
ser visto na Listagem 11.
Código 11: Exemplo de Fatorial descrito em Linguagem C.#define N 5
int fat(){
int fatorial=N;
int i;
int tmp;
for(i = 1 ; i < N; i++){
tmp = fatorial * i;
fatorial = tmp;
}
Da mesma forma que o algoritmo de Fibonacci, o código em linguagem C do
algoritmo de Fatorial, descrito na Listagem 11, foi executado para comprovar
seu funcionamento. Em seguida utilizou-se a ferramenta de conversão de
código, gerando o código intermediário para a implementação da máquina a
fluxo de dados estática do projeto ChipCflow. Este código pode ser visto na
Listagem 12.
Código 12: Código intermediário gerado para o algoritmo de Fatorial.for i, n, _L1, ifNmerge fatorial, tmp, _fatorial1branch _fatorial1, _fatorial2, _fatorial3add if, _fatorial3, tmp
Após a representação intermediária, foi gerado o grafo referente ao algo-
ritmo de Fatorial, como pode ser visto na Figura 8.3.
Figura 8.3: Grafo gerado para o algoritmo de Fatorial.
O código gerado foi sintetizado no ambiente ISE obtendo-se os dados apre-
sentados na Tabela 8.2.
81
Tabela 8.2: Resultados obtidos após a síntese do algoritmo de fatorial noChipCflow.
FatorialFF 88LUT 425Slices 363Frequência 247,537 MHz
Cabe observar que a frequencia máxima da execução do algoritmo de Fa-
torial é praticamente a metade da encontrada na execução do algoritmo de
Fibonacci. Isso se deve ao fato que o algoritmo de Fatorial utiliza um operador
de multiplicação, que atrasa a execução do algoritmo.
8.1.3 Algoritmo Determinante
O ultimo algoritmo implementado para teste foi o algoritmo de Determi-
nante, que permitiu verificar o funcionamento do operador for, após as altera-
ções implementadas neste operador de forma a permitir a implementação de
for aninhado.
Para a execução do algoritmo de determinante, fez-se necessária a criação
de uma memória, direto na ambiente ISE, que armazenasse uma matriz, de
forma a permitir o acesso aos seus dados, assim tanto a memória como os
operadores de acesso a memória foram incluídos manualmente no arquivo
VHDL. Um trecho do algoritmo implementado em linguagem C para calculo
do Determinante pode ser visto na Listagem 8.3.
O grafo gerado para este algoritmo, já com o operador de load para a matriz,
bem como os operadores for aninhados, podem ser vistos na Figura 8.4.
Os resultados da síntese desta implementação no ambiente ISE, podem ser
vistos na Tabela 8.3.
De uma maneira geral, pode-se concluir que, em termos de ocupação do
FPGA, os três algoritmos ocuparam uma porcentagem inferior a 1% de slices,
LUTs e flip-flops do FPGA utilizado. Esta informação demonstra a viabilidade
de implementação de algoritmos complexos, no que diz respeito a área de
FPGA ocupada.
8.2 Análise Comparativa
O objetivo geral deste trabalho é realizar a prova de conceito sobre a viabili-
dade da geração de uma arquitetura a fluxo de dados estática, por meio de um
82
Código 13: Trecho do código do algoritmo de determinante implemen-tado.
for (i=0; i<n; i++) {y=i;for (j=0; j<n; j++){
if(y==n)y=0;
mult=mult * mat [j] [y];y++;
}soma1=soma1+mult;mult=1;}for (i=0; i<n; i++){
y=i;for (j=n-1; j>=0; j–) {
if (y==n)y=0;
mult=mult * [j] [y];y=y+1;
}soma2=soma2+mult;mult=1; }
det=soma1-soma2;
Tabela 8.3: Resultados obtidos após a síntese do algoritmo de determinanteno ChipCflow.
DeterminanteFF 223LUT 272Slices 597Frequência 313,7 MHz
83
Figura 8.4: Grafo gerado para o algoritmo de determinante.
código de entrada em linguagem C, o que foi demonstrado com os resultados
anteriores.
Além da prova de conceito, foi realizada uma análise do desempenho da
arquitetura a fluxo de dados estática do projeto do ChipCflow quando compa-
rado a uma Central Processing Unit (CPU) Core I7 e uma Graphics ProcessingUnit (GPU) NVidia GForce GTX-660.
Para esta comparação os 3 algoritmos foram implementados em linguagem
C, para a máquina a fluxo de dados estática do projeto ChipCflow e para a
CPU Core I7, e em CUDA no caso da GPU.
A CPU utilizada, para a execução dos algoritmos, foi um I7-3820 de 3,60
GHz com 4 núcleos e 8 threads, e 10 MB de memória cache. O sistema opera-
cional utilizado foi o Ubuntu Linux versão 13.4, rodando somente em terminal
sem interface gráfica, memória RAM de 8GB DDR3, e armazenamento externo
em um Solid-State Drive (SSD) de 128GB
A GPU Nvidia GForce GTX-660 utilizada, conta com 960 núcleos de pro-
cessamento com velocidade de 1020 MHz, 2 GB de memória DDR5, com 6008
MHz e 192 Bits. Para a execução dos teste foi utilizado um computador Core
I5, rodando o sistema operacional Ubuntu Linux. A analise dos dados foi
realizada utilizando a ferramenta Visual Profiler da NVidia.
Em todos os testes, os algoritmos foram executados 30 vezes e seus tempos
armazenados para posterior calculo da média. Para avaliar o tempo de execu-
ção na CPU foi utilizado o GNUProf. Na GPU o Visual Profiler é o responsável
pela execução e calculo da média de tempo, e na arquitetura a fluxo de dados
84
os tempos foram obtidos a ferramenta ISE.
A implementação dos algoritmos para a arquitetura a fluxo de dados está-
tica do projeto ChipCflow e para a CPU foi exatamente a mesma. No caso do
algoritmo de determinante no código executado na CPU, a matriz foi iniciali-
zada com os valores dentro do código C, de forma a evitar a necessidade de
leitura de dados externos.
O código C foi alterado de forma a conter as diretivas do CUDA, para a
correta execução na GPU.
Os algoritmos de Fibonacci e Fatorial foram executados na CPU e na GPU
com iterações 100 mil, 300 mil e 500 mil.
Os resultados da execução do algoritmo de Fibonacci pode ser visto na
Tabela 8.4 [Silva & Silva, 2014].
Tabela 8.4: Resultados das execuções do algoritmo de Fibonacci.Fibonacci I7 GPU ChipCflow100000 2 ms 0,27 ms 1,28 ms300000 4 ms 1,06 ms 3,85 ms500000 6,1 ms 1,82 ms 5,43 ms
Na Figura 8.5 é apresentado o gráfico comparativo dos resultados obtidos
na execução do algoritmo de Fibonacci.
Figura 8.5: Resultados das execuções do algoritmo de Fibonacci [Silva & Silva,2014] .
Como pode ser visto na Figura 8.5, para a implementação do algoritmo
de Fibonacci, a execução na máquina a fluxo de dados estática do projeto
85
ChipCflow foi em média aproximadamente 16% mais rápida do que a execução
na CPU, porém foi em média aproximadamente 71% mais lenta do que a GPU.
O segundo algoritmo analisado foi o fatorial, o resultado das execuções
deste algoritmos podem ser vistos na Tabela 8.5 [Silva & Silva, 2014].
Tabela 8.5: Resultados das execuções do algoritmo de para calculo Fatorial.Fatorial I7 GPU ChipCflow100000 2,1 ms 0,42 ms 1,02 ms300000 5 ms 1,29 ms 3,07 ms500000 6,92 ms 2,16 ms 5,12 ms
Na Figura 8.6 é apresentado o gráfico comparativo dos resultados obtidos
na execução do algoritmo para calculo de fatorial.
Figura 8.6: Resultados das execuções do algoritmo de para calculo Fatorial[Silva & Silva, 2014] .
Como pode ser visto na Figura 8.6, para a implementação do algoritmo de
Fatorial, a execução na máquina a fluxo de dados estática do projeto ChipC-
flow foi em média aproximadamente 37% mais rápida do que a execução na
CPU, porém foi em média aproximadamente 57% mais lenta do que a GPU.
Este fato se deve a dificuldade e o crescimento do hardware necessário
para manipular as atribuições de valores, existentes no cálculo de Fibonacci.
O ultimo algoritmo analisado foi o determinante, o resultado das execuções
deste algoritmos podem ser vistos na Tabela 8.6.
Na Figura 8.7 é apresentado o gráfico comparativo dos resultados obtidos
na execução do algoritmo para calculo de determinante.
86
Tabela 8.6: Resultados das execuções do algoritmo de para calculo de Deter-minante.
Determinante I7 GPU ChipCflow20x20 5,3 ms 0,82 ms 2,7 ms100x100 24,5 ms 5,9 ms 13,4 ms200x200 59,6 ms 12,2 ms 28,7 ms
Figura 8.7: Resultados das execuções do algoritmo de para calculo determi-nante.
O resultado da execução do algoritmo de determinante, assim como os ou-
tros algoritmos, teve a melhor execução na GPU. Esta eficiência de execução
da GPU já era esperada nos resultados devido ao alto poder de processamento
apresentando. Neste algoritmo a execução na máquina a fluxo de dados está-
tica do projeto ChipCflow foi em média aproximadamente 50% mais rápida do
que a CPU, e em média aproximadamente 67% mais lenta do que a GPU.
87
88
CAPÍTULO
9Conclusões e Trabalhos Futuros
O principal objetivo nesta tese foi implementar uma ferramenta para co-
dificar códigos escritos em linguagem C para uma linguagem de descrição
de hardware (em particular o VHDL) que utilizasse o modelo a fluxo de da-
dos estáticos como parte fundamental do projeto ChipCflow. Com o projeto
ChipCflow pretende-se gerar um ambiente de desenvolvimento para acelerar
aplicações em um modelo híbrido de funcionamento caracterizado pelo uso de
CPUs interconectados a FPGAs. O Ambiente ChipCflow integra um editor, o
compilador, foco desta tese em questão, a síntese lógica do hardware gerado
em ambiente comercial como ferramentas Eletronic Design Automation (EDA)
Xilinx e Altera, simulação e geração final do arquivo de configuração do FPGA
e partição Hardware/Software da aplicação. Nesta tese portanto foram apre-
sentadas inicialmente os aspectos relacionados ao conceito de Computação
Reconfigurável, particularmente envolvidos com o uso de dispositivos FPGAs,
suas características e potencialidades. Em seguida, ainda em se tratando de
Computação Reconfigurável, uma das formas que vem sendo utilizadas para
acelerar processamento é o uso das tradicionais CPUs associadas aos FPGAs
em um modelo híbrido de funcionamento, ficando para os FPGAs o trabalho
mais intenso de processamento como loops. Com essa característica híbrida
de trabalho, foram apresentadas as primeiras máquinas desenvolvidas com
o conceito de Computação Reconfigurável, em particular as máquinas GARP
[Hauser & Wawrzynek, 1997] e DISC [Wirthlin & Hutchings, 1995], e finali-
zando este item, foram apresentadas as linguagens necessárias para o desen-
volvimento desses sistemas, destacando-se a linguagem VHDL e linguagem
89
Verilog. Em seguida foram apresentados os conceitos envolvidos para a ge-
ração de um Compilador, inicialmente de forma geral, suas fases, a geração
de código intermediário, a fase de síntese, finalizando com o código gerado. A
partir dessas características, foram apresentados os aspectos ligados a Com-
pilador para geração de código em linguagem de hardware, com um objetivo
principal de, a partir de uma linguagem de alto nível como a Linguagem C,
poder gerar código para descrição de hardware como a Linguagem VHDL. Ar-
quiteturas a Fluxo de Dados teve seu início nos anos 70 e descontinuado
no final dos anos 90, mas tornou-se atrativo novamente em função da evo-
lução do hardware, tanto no que diz respeito aos FPGAs como multicores e
as GPUs. Em particular no capítulo 4 desta tese, foi apresentado o modelo
a fluxo de dados, a linguagem gráfica utilizada neste modelo, as primeiras
máquinas implementadas utilizando este modelo, em particular o modelo a
fluxo de dados estático proposto por Dennis & Misunas [1975], a máquina
também estática de Manchester [Gurd et al., 1985], a máquina dinâmica do
MIT [Arvind & Nikhil, 1990] e máquinas contemporâneas como a máquina
dinâmica Wavescalar [Swanson et al., 2007] e a máquina TRIPS [Sankaralin-
gam et al., 2003]. Com o foco em compiladores voltados para aplicações em
hardware, no capítulo 5 desta tese foram apresentados os principais proje-
tos relacionados com a proposta para o projeto ChipCflow. Assim foi descrito
o compilador Trident [Tripp et al., 2007] que gera código VHDL a partir da
linguagem C, o compilador Molen [Panainte et al., 2007] utilizado em uma ar-
quitetura específica que faz uso do modelo híbrido CPU e FPGA, o compilador
HThreads [Andrews et al., 2008b] que gera uma representação intermediária
para o código VHDL a partir da linguagem C, o compilador ARISE [Vassilia-
dis et al., 2009] que também faz uma divisão hardware e software no modelo
híbrido CPU e FPGA, o compilador Nenya/Galadriel [Cardoso, 2000] que gera
código VHDL a partir de bytecodes da linguagem Java e finalmente o compi-
lador ROCCC [Buyukkurt et al., 2006] que também gera código VHDL a partir
de linguagens como C/C++, Fortran e Java. Detalhes do modelo a fluxo de
dados do projeto ChipCflow foi apresentado no capítulo 6 desta tese. Inicial-
mente foi apresentado uma visão geral do projeto ChipCflow para em seguida
ser descrito em detalhes os operadores utilizados no projeto ChipCflow, no
caso, operadores aritméticos e lógicos, operadores condicionais, operadores
de acesso à memória e operadores controladores de loops. Como parte inte-
grante do modelo a fluxo de dados estático descrito neste item foi descrito o
protocolo de comunicação entre os operadores, sem o qual não seria possível
configurar a máquina a fluxo de dados como uma máquina estática. Uma vez
descrito toda a configuração do hardware, passou-se à descrição do conversor
90
de código C para a linguagem VHDL que geraria a máquina a fluxo de da-
dos estática do projeto Chipcflow, sendo este o principal capítulo desta tese,
no caso o capítulo 7. Inicialmente foi descrito a primeira parte do compila-
dor, o Front-End baseado na ferramenta de análise léxica Flex [Project., 2008]
gerando uma representação intermediária no modelo de instruções com três
endereços, representação essa que pode ser utilizada inclusive para outras
plataformas que não a proposta no projeto ChipCflow. Em seguia, passou-se
a implementação do Back-End, tendo como base a representação intermediá-
ria gerada pelo Front-End e os operadores de referência já implementados no
projeto ChipCflow. Uma vez especificado o Back-End, alguns algoritmos fo-
ram implementados e foram descritos em detalhes no capítulo 8. No capítulo
8 portanto, são apresentados os resultados de conversão de código e imple-
mentação dos algoritmos: Fatorial, Fibonacci e cálculo do Determinante de
Matriz Quadrada, sendo em seguida sintetizados em ferramentas EDA Xilinx,
para o FPGA Virtex 7 modelo (7V285TFFG1157-3). Para cada algoritmo são
descrito: o código C original, o código intermediário gerado para aquele algo-
ritmo, uma representação gráfica no modelo a fluxo de dados estático para
cada algoritmo e apenas para o algoritmo Fibonacci o código completo gerado
em VHDL no anexo A da tese. Também são apresentados neste capítulo os
resultados de síntese dos algoritmos no referido FPGA, com tabelas contendo
ocupação para cada algoritmo e a frequência máxima utilizada para executar
aquele algoritmo naquele FPGA. A partir desses dados, e utilizando ferramen-
tas apropriadas, foram feitas análises de desempenho dos resultados obtidos
na execução dos algoritmos em FPGA com os mesmos algoritmos executados
em uma CPU Core I7 e uma GPU NVIDIA GForce GTX-660. Os resultados
mostram um ganho em desempenho do modelo a fluxo de dados estático se
comparado à CPU, que varia de 16% a 50%, dependendo das características
de cada algoritmo.
9.1 Trabalhos Futuros
A ferramenta desenvolvida durante este trabalho permite o seu aprimora-
mento e o desenvolvimento e implementação de novos recursos na arquitetura
proposta. A seguir são relacionadas algumas sugestões para trabalhos futuros
que podem aprimorar a ferramenta de conversão e também a arquitetura.
• Inclusão na ferramenta de compilação de outras estruturas de repetição
e decisões, visando permitir a conversão de uma variedade maior de al-
goritmos sem a necessidade de alteração em seus códigos. Esta inclusão
91
permitiria atingir uma gama maior de aplicações e facilitaria o desenvol-
vimento de novas aplicações.
• Inclusão de operadores de load e store visando o acesso a matrizes e ve-
tores, de acordo com a arquitetura de memória que esta sendo desenvol-
vida e permitindo o desenvolvimento de aplicações distribuídas em mais
de um FPGA. O uso de uma arquitetura desenvolvida e sendo executada
em vários FPGAs permitiria o desenvolvimento de aplicações complexas,
além de permitir testar técnicas como loop Unrolling.
• Realizar pesquisa sobre a organização de memória, que pode ser realizada
no processo de compilação da aplicação. A organização de memória, caso
viável, permite diminuir a quantidade de hardware e com isso o tempo
de execução de aplicações. Esta é uma proposta de trabalho que permite
um amplo campo de pesquisa, pois além de verificar a viabilidade desta
organização, faz-se necessário uma ferramenta de conversão que altere
o código gerado, de forma a eliminar loops que servem para acesso de
dados de forma não sequencial em vetores ou matrizes.
• Desenvolvimento de uma ferramenta visual que permita ao programador
visualizar, ou gerar um arquitetura a fluxo de dados utilizando uma fer-
ramenta visual. Esta ferramenta permitiria ao programador visualizar e
alterar o grafo gerado pelo conversor de código visando a correção de er-
ros ou otimizações. Além de permitir a visualização de um grafo existente
também permitiria a geração de uma arquitetura por meio de um grafo
desenhado pelo programador. Esta ferramenta encontra-se em estagio
inicial de pesquisa para seu desenvolvimento, prevendo-se um protótipo
funcional para o inicio de 2015.
• Pesquisar a paralelização de grafos de forma a buscar desempenho se-
melhante ao obtido com o uso de GPUs.
92
Referências Bibliográficas
Aho, A. V., Lam, M. S., Sethi, R., & Ullman, J. D. (2007). Compilers: Princi-ples, Techniques, and Tools. Reading, Massachusetts: Addison-Wesley, 2st
edition. xiii, xvii, xix, 15, 16, 17, 18, 19, 20, 21
Andrews, D. L., Sass, R., Anderson, E., Agron, J., Peck, W., Stevens, J., Bai-
jot, F., & Komp, E. (2008a). Achieving programming model abstractions for
reconfigurable computing. IEEE Trans. VLSI Syst, 16(1), 34–44. xiv, 48
Andrews, D. L., Sass, R., Anderson, E., Agron, J., Peck, W., Stevens, J., Baijot,
F., & Komp, E. (2008b). Achieving programming model abstractions for
reconfigurable computing. IEEE Trans. VLSI Syst, 16(1). 90
Anellal, S. & Kaminska, B. (1993). Scheduling of a control and data flow graph.
IEEE International Symposium on Circuits and Systems, 3, 1666–1669. 23
Arvind, K. (2005). Dataflow: Passing the token. xiv, 37, 58, 59
Arvind, K. & Nikhil, R. S. (1990). Executing a program on the mit tagged-token
dataflow architecture. IEEE Trans. Comput., 39, 300–318. xiv, 36, 37, 38,
39, 90
Bobda, C. (2007). Introduction to Reconfigurable Computing: Architectures, Al-gorithms, and Applications. Springer Publishing Company, Incorporated.
xiii, 7, 8
Buyukkurt, B., Guo, Z., & Najjar, W. A. (2006). Impact of loop unrolling on
area, throughput and clock frequency in ROCCC: C to VHDL compiler for
FPGAs. In K. Bertels, J. M. P. Cardoso, & S. Vassiliadis (Eds.), ARC, volume
3985 of Lecture Notes in Computer Science (pp. 401–412).: Springer. xiv, 53,
90
93
Cai, Q. & Xue, J. (2003). Optimal and efficient speculation-based partial re-
dundancy elimination. In CGO (pp. 91–104).: IEEE Computer Society. 47
Cappelli, A., Lodi, A., Mucci, C., Toma, M., & Campi, F. (2004). A dataflow con-
trol unit for c-to-configurable pipelines compilation flow. In Proceedings ofthe 12th Annual IEEE Symposium on Field-Programmable Custom ComputingMachines (pp. 332–333). Washington, DC, USA: IEEE Computer Society. 25
Cardoso, J. M. P. e Neto, H. (2003). Compilation for FPGA-based reconfigura-
ble hardware. IEEE Design & Test of Computers, 20(2), 65–75. 1, 20
Cardoso, J. a. M. P. & Dehon, A. (2010). Compiling for Reconfigurable Com-
puting: A Survey . ACM Computing Surveys, 42(4). xiii, 7
Cardoso, J. M. P. (2000). Compilação de algoritmos em Java para sistemascomputacionais reconfiguráveis com exploração do paralelismo ao nível deoperações. PhD thesis, Universidade Técnica de Lisboa. xiv, 1, 22, 50, 52,
90
Cardoso, J. M. P. & Neto, H. C. (2001). Compilation increasing the scheduling
scope for multi-memory-FPGA-based custom computing machines. LectureNotes in Computer Science, 2147. 52
Davis, A. L. (1978). The architecture and system method of ddm1: A recur-
sively structured data driven machine. In Proceedings of the 5th annualsymposium on Computer architecture, ISCA ’78 (pp. 210–215). New York, NY,
USA: ACM. 30
Dennis, J. B. (1974). First version of a data flow procedure language. In
Programming Symposium, Proceedings Colloque Sur La Programmation (pp.
362–376). London, UK, UK: Springer-Verlag. 25
Dennis, J. B. (1980). Data flow supercomputers. Computer, 13, 48–56. xiv, 33
Dennis, J. B. & Misunas, D. P. (1975). A preliminary architecture for a ba-
sic data-flow processor. In In Proceedings of the 2nd Annual Symposium onComputer Architecture (pp. 126–132). xiv, 25, 26, 30, 32, 33, 34, 36, 43, 58,
90
Duarte, F. L. (2006). Phoenix – um framework para trabalhos em síntese de
alto nível de circuitos digitais. xiii, 22
Estrin, G., Bussel, B., Turn, R., & Bibb, J. (1963). Parallel processing on a
restructurable computer system. IEEE Trans. Electronic Computers, EC-12,
747–754. 5, 6
94
Girkar, M. & Polychronopoulos, C. D. (1992). Automatic extraction of functio-
nal parallelism from ordinary programs. IEEE Trans. Parallel Distrib. Syst.,3(2), 166–178. 23
Grafe, V. G., Davidson, G. S., Hoch, J. E., & Holmes, V. P. (1989). The epsilon
dataflow processor. In Proceedings of the 16th annual international sympo-sium on Computer architecture, ISCA ’89 (pp. 36–45). New York, NY, USA:
ACM. 30
Guo, Z., Buyukkurt, B., Najjar, W., & Vissers, K. (2005). Optimized generation
of data-path from c codes for fpgas. 53
Gupta, S., Gupta, R., Dutt, N., & Nicolau, A. (2004). SPARK: A ParallelizingApproach to the High-Level Synthesis of Digital Circuits. Norwell, Massachu-
setts, USA: Kluwer Academic Publishers. xiv, 54, 55
Gurd, J. R., Kirkham, C. C., & Watson, I. (1985). The manchester prototype
dataflow computer. Commun. ACM, 28, 34–52. xiv, 30, 35, 36, 90
Hall, M., Padua, D., & Pingali, K. (2009). Compiler Research: The Next 50
Years. Comunications of the ACM, 52(2), 60–67. 45
Hall, M. W., Anderson, J. M., Amarasinghe, S. P., Murphy, B. R., Liao, S.-W.,
Bugnion, E., & Lam, M. S. (1998). Maximizing multiprocessor performance
with the SUIF compiler. Digital Technical Journal of Digital Equipment Cor-poration, 10(1). 47, 53
Hauck, S. & Dehon, A. (2007). Reconfigurable Computing: The Theory andPractice of FPGA-Based Computation (Systems on Silicon). Morgan Kauf-
mann. xiii, 5, 6, 8
Hauck, S., Fry, T. W., Hosler, M. M., & Kao, J. P. (2004). The chimaera recon-
figurable functional unit. IEEE Trans. VLSI Syst, 12(2), 206–217. 9
Hauser, J. & Wawrzynek, J. (1997). Garp: a MIPS processor with a reconfi-
gurable coprocessor. Proceedings. The 5th Annual IEEE Symposium on Field-Programmable Custom Computing Machines Cat. No.97TB100186), (pp. 24—
-33). xiii, 10, 11, 89
HPCwire (2009). Cray selects drc fpga coprocessors for supercomputers. 9
Johnston, W. M., Hanna, J. R. P., & Millar, R. J. (2004). Advances in dataflow
programming languages. ACM Comput. Surv., 36, 1–34. 25, 29
95
Kernighan, B. W. & Ritchie, D. M. (1988). The C Programming Language. New
Jersey, USA: Prentice Hall, second edition. 54
Lattner, C. & Adve, V. (2004). LLVM: A Compilation Framework for Lifelong
Program Analysis & Transformation. In Proceedings of the 2004 InternationalSymposium on Code Generation and Optimization (CGO’04) (pp. 75– 86). Palo
Alto, California. 46
Laufer, R., Taylor, R. R., & Schmit, H. (1999). PCI-PipeRench and the Swor-
dAPI: A system for stream-based reconfigurable computing. In K. L. Pocek &
J. Arnold (Eds.), IEEE Symposium on FPGAs for Custom Computing Machines(pp. 200–208). Los Alamitos, CA: IEEE Computer Society Press. 9
Lee, B. & Hurson, A. R. (1994). Dataflow architectures and multithreading.
Computer, 27, 27–39. 25
Lopes, J. J. (2012). ChipCflow - Uma ferramenta para execução de algortimosutilizando o modelo a fluxo de dados dinâmico em hardware reconfigurável.PhD thesis, Universidade de São Paulo. 58
Marques, E. (1993). Projeto de um elemento de processamento de um compu-
tador maçivamente paralelo para execução dirigida a fluxo de dados. 26
Merrill, J. (2003). GENERIC and GIMPLE: A new tree representation for entire
functions. In A. J. Hutton, S. Donovan, & C. C. Ross (Eds.), Proceedings ofthe GCC Developers Summit May 25–27, 2003, Ottawa,Ontario Canada (pp.
171–193). 48
Mittal, G., Zaretsky, D., Tang, X., & Banerjee, P. (2007). An overview of a
compiler for mapping software binaries to hardware. IEEE Trans. VLSI Syst,15(11), 1177–1190. 9
Moreno, E. D., Penteado, C. G., & Silva, A. C. R. d. (2005). Microcontroladorese FPGAs. Novatec Editora. 8
Muchnick, S. S. (1997). Advanced compiler design and implementation. San
Francisco, CA, USA: Morgan Kaufmann Publishers Inc. xiii, 16
Namballa, R., Ranganathan, N., & Ejnioui, A. (2004). Control and data flow
graph extraction for high-level synthesis. IEEE Computer Society AnnualSymposium on VLSI, (pp. 187–192). 23
Panainte, E. M., Bertels, K., & Vassiliadis, S. (2007). The molen compiler for
reconfigurable processors. ACM Trans. Embedded Comput. Systems, 6(1). 6,
21, 46, 90
96
Papadopoulos, G. M. & Culler, D. E. (1998). Monsoon: an explicit token-
store architecture. In 25 years of the international symposia on Computerarchitecture (selected papers), ISCA ’98 (pp. 398–407). New York, NY, USA:
ACM. 30
Project., T. F. (2008). Flex: The fast lexical analyzer. Acessado em janeiro de
2011. 67, 91
Quickturn, A. A. C. (1999). Mercurytm design verification system technology
backgrounder. 9
Razdan, R. & Smith, M. D. (1994). A high-performance microarchitecture with
hardware-programmable functional units. In Proceedings of the 27th AnnualInternational Symposium on Microarchitecture (pp. 172–80).: IEEE/ACM. 9
Rutzig, M. B., Beck, A. C. S., & Carro, L. (2007). Transparent dataflow execu-
tion for embedded applications. VLSI, IEEE Computer Society Annual Sym-posium on, 0, 47–54. 43
Sankaralingam, K., Nagarajan, R., Liu, H., Kim, C., Huh, J., Burger, D., Kec-
kler, S. W., & Moore, C. (2003). Exploiting ilp, tlp, and dlp with the poly-
morphous trips architecture. In In Proceedings of the 30th annual internati-onal symposium on Computer architecture (pp. 422–433).: ACM Press. xiv, 1,
42, 43, 90
Silva, A. C. F. & Silva, J. L. e. (2014). The chipcflow: a tool to generate hard-
ware accelerators using a static dataflow machine designed for a fpga. In
MPP2014 (pp. 1–6).: SBAC-PADW. xv, 85, 86
Silva, A. C. F. d. (2009). Compilação para arquitetura reconfigurável. Master’s
thesis, Universidade Estadual Paulista. xiii, 23
Silva, B. D. A. (2011). Gerenciamento de tags na arquitetura ChipCflow – umamáquina a fluxo de dados dinâmica. PhD thesis, USP - Universidade de São
Paulo. xiii, xiv, 31
Silva, J. L. e. (1992). Processamento a Fluxo de Dados Toelrante a Falhas emum Computador Paralelo. PhD thesis, Universidade Estadual de Campinas.
57, 58
Silva, J. L. e. (2006). Execution of algorithms using a Dynamic Dataflow Model
for Reconfigurable Hardware - A purpose for Matching Data. xiv, 57, 58, 60
97
Silva, J. L. e., Da Costa, K. A. P., & Roda, V. O. (2009). The c compiler gene-
rating a source file in vhdl for a dynamic dataflow machine being executed
direct into a hardware. W. Trans. on Comp., 8(12), 1908–1916. xiii, 1, 24,
57
Silva, J. L. e. & Marques, E. (2006). Executing algorithms for dynamic dataflow
reconfigurable hardware - the operators protocol. In R. Cumplido-Parra, C.
Torres-Huitzil, & A. D. García (Eds.), ReConFig (pp. 64–70).: IEEE Computer
Society. 63
Sima, M., Vassiliadis, S., Cotofana, S., van Eijndhoven, J. T. J., & Vissers, K.
(2002). Field-programmable custom computing machines — A taxonomy.
Lecture Notes in Computer Science, 2438, 79–88. 48
Smith, M. D. & Holloway, G. (2002). An Introduction to Machine SUIF and itsPortable Libraries for Analysis and Optimization. Technical report, Harvard
University. 47, 49, 53
Sobek, S. & Burke, K. (1995). Powerpc embedded application binary interface.
47
Stanford SUIF Compiler Group (1994). SUIF: A Parallelizing & Optimizing Re-search Compiler. Technical Report CSL-TR-94-620, Computer Systems La-
boratory, Stanford University. 53
Swanson, S., Michelson, K., Schwerin, A., Oskin, M., & Science, C. (2003).
Dataflow: The Road Less Complex. Workshop on Complexity-effective Design.
41, 42
Swanson, S., Schwerin, A., Mercaldi, M., Petersen, A., Putnam, A., Michelson,
K., Oskin, M., & Eggers, S. J. (2007). The wavescalar architecture. ACMTransactions on Computer Systems (TOCS), 25(2). xiv, 1, 25, 39, 40, 41, 42,
43, 90
Teifel, J. & Manohar, R. (2004). An asynchronous dataflow fpga architecture.
IEEE Transactions on Computers, 53(11), 1376–1392. xiv, 60
Tripp, J. L., Gokhale, M. B., & Peterson, K. D. (2007). Trident: From high-level
language to hardware circuitry. xiv, 20, 45, 47, 90
Tripp, J. L., Jackson, P. A., & Hutchings, B. (2002). Sea cucumber: A
synthesizing compiler for fpgas. In FPL ’02: Proceedings of the Reconfigura-ble Computing Is Going Mainstream, 12th International Conference on Field-
98
Programmable Logic and Applications (pp. 875–885). London, UK: Springer-
Verlag. 46
Vassiliadis, N., Theodoridis, G., & Nikolaidis, S. (2009). An Application De-
velopment Framework for ARISE Reconfigurable Processors. ACM Transac-tions on Reconfigurable Technology and Systems, 2(4), 1–30. xiv, 49, 51,
90
Vassiliadis, S., Gaydadjiev, G., Bertels, K., & Panainte, E. M. (2004). The
molen programming paradigm. In SAMOS, volume 3133 of Lecture Notes inComputer Science: Springer. 46
Veen, A. H. (1986). Dataflow machine architecture. ACM Computing Surveys,
18(4), 365–396. xiii, 25, 26, 27, 28, 29, 31
Vuillemin, J., Bertin, P., Roncin, D., Shand, M., Touati, H., & Boucard, P.
(1996). Programmable active memories: Reconfigurable systems come of
age. IEEE Transactions on VLSI Systems, 4(1), 56–69. 9
Wirthlin, M. J. & Hutchings, B. L. (1995). DISC: The dynamic instruction set
computer. Field Programmable Gate Arrays for Fast Board Development andReconfigurable Computing, (pp. 99—-103). xiii, 11, 12, 13, 89
Wittig, R. & Chow, P. (1996). OneChip: An FPGA processor with reconfigurable
logic. In K. L. Pocek & J. Arnold (Eds.), IEEE Symposium on FPGAs for Cus-tom Computing Machines (pp. 126–135). Los Alamitos, CA: IEEE Computer
Society Press. 9
Xilinx (2014). Ise design suite. Acessado em janeiro de 2014. 77
99
100
APÊNDICE
AApendice1
LIBRARY ieee ;
USE ieee . std_logic_1164 . a l l ;
ENTITY fa to r i a l IS
PORT( clk : IN STD_LOGIC;
reset : IN STD_LOGIC;
i : IN STD_LOGIC_VECTOR(15 DOWNTO 0) ;
istr2in : IN STD_LOGIC;
iack2ack : OUT STD_LOGIC;
n: IN STD_LOGIC_VECTOR(15 DOWNTO 0) ;
nstr2in : IN STD_LOGIC;
nack2ack : OUT STD_LOGIC;
fat : IN STD_LOGIC_VECTOR(15 DOWNTO 0) ;
fatstr2in : IN STD_LOGIC;
fatack2ack : OUT STD_LOGIC −−esta linha não contem ;
) ;
END fa to r i a l ;
ARCHITECTURE structural OF fa to r i a l IS
COMPONENT fo r i IS
PORT( clkin : IN STD_LOGIC;
reset : IN STD_LOGIC;
n: IN STD_LOGIC_VECTOR(15 DOWNTO 0) ;
nin : IN STD_LOGIC;
101
nack : OUT STD_LOGIC;
um: IN STD_LOGIC_VECTOR(15 DOWNTO 0) ;
umin: IN STD_LOGIC;
umack: OUT STD_LOGIC;
oc : OUT STD_LOGIC_VECTOR(15 DOWNTO 0) ;
ocstr : OUT STD_LOGIC;
ocack : IN STD_LOGIC;
oi : OUT STD_LOGIC_VECTOR(15 DOWNTO 0) ;
o is t r : OUT STD_LOGIC;
oiack : IN STD_LOGIC ) ;
END COMPONENT;
−−−−−−−−−−−−−−−−−−−−−−COMPONENT ndmerge IS
PORT( clkin : IN STD_LOGIC;
reset : IN STD_LOGIC;
a : IN STD_LOGIC_VECTOR(15 DOWNTO 0) ;
ain : IN STD_LOGIC;
aack : OUT STD_LOGIC;
b: IN STD_LOGIC_VECTOR(15 DOWNTO 0) ;
bin : IN STD_LOGIC;
back : OUT STD_LOGIC;
v : OUT STD_LOGIC_VECTOR(15 DOWNTO 0) ;
vstr : OUT STD_LOGIC;
vack : IN STD_LOGIC ) ;
END COMPONENT;
−−−−−−−−−−−−−−−−−−−−−−COMPONENT branch IS
PORT( clkin : IN STD_LOGIC;
reset : IN STD_LOGIC;
v : IN STD_LOGIC_VECTOR(15 DOWNTO 0) ;
vin : IN STD_LOGIC;
vack : OUT STD_LOGIC;
c : IN STD_LOGIC_VECTOR(15 DOWNTO 0) ;
cin : IN STD_LOGIC;
cack : OUT STD_LOGIC;
f : OUT STD_LOGIC_VECTOR(15 DOWNTO 0) ;
f s t r : OUT STD_LOGIC;
fack : IN STD_LOGIC;
t : OUT STD_LOGIC_VECTOR(15 DOWNTO 0) ;
ts t r : OUT STD_LOGIC;
tack : IN STD_LOGIC ) ;
102
END COMPONENT;
−−−−−−−−−−−−−−−−−−−−−−COMPONENT mul IS
PORT( clkin : IN STD_LOGIC;
reset : IN STD_LOGIC;
a : IN STD_LOGIC_VECTOR(15 DOWNTO 0) ;
ain : IN STD_LOGIC;
aack : OUT STD_LOGIC;
b: IN STD_LOGIC_VECTOR(15 DOWNTO 0) ;
bin : IN STD_LOGIC;
back : OUT STD_LOGIC;
z : OUT STD_LOGIC_VECTOR(15 DOWNTO 0) ;
zstr : OUT STD_LOGIC;
zack : IN STD_LOGIC ) ;
END COMPONENT;
−−−−−−−−−−−−−−−−−−−−−−COMPONENT nopop IS
PORT( clkin : IN STD_LOGIC;
reset : IN std_logic ;
a : IN STD_LOGIC_VECTOR(15 DOWNTO 0) ;
ain : IN STD_LOGIC;
aack : OUT STD_LOGIC ) ;
END COMPONENT;
−−−−−−−−−−−−−−−−−−−−−−SIGNAL s4 : STD_LOGIC_VECTOR(15 DOWNTO 0) ;
SIGNAL s4str2in : STD_LOGIC;
SIGNAL s4ack2ack : STD_LOGIC;
SIGNAL s5 : STD_LOGIC_VECTOR(15 DOWNTO 0) ;
SIGNAL s5str2in : STD_LOGIC;
SIGNAL s5ack2ack : STD_LOGIC;
SIGNAL s6 : STD_LOGIC_VECTOR(15 DOWNTO 0) ;
SIGNAL s6str2in : STD_LOGIC;
SIGNAL s6ack2ack : STD_LOGIC;
SIGNAL s7 : STD_LOGIC_VECTOR(15 DOWNTO 0) ;
SIGNAL s7str2in : STD_LOGIC;
SIGNAL s7ack2ack : STD_LOGIC;
SIGNAL s8 : STD_LOGIC_VECTOR(15 DOWNTO 0) ;
103
SIGNAL s8str2in : STD_LOGIC;
SIGNAL s8ack2ack : STD_LOGIC;
SIGNAL s9 : STD_LOGIC_VECTOR(15 DOWNTO 0) ;
SIGNAL s9str2in : STD_LOGIC;
SIGNAL s9ack2ack : STD_LOGIC;
SIGNAL for i0c lk : STD_LOGIC;
SIGNAL ndmerge0clk : STD_LOGIC;
SIGNAL branch0clk : STD_LOGIC;
SIGNAL mul0clk : STD_LOGIC;
SIGNAL nopop0clk : STD_LOGIC;
BEGIN
for i0c lk <= clk ;
branch0clk <= clk ;
ndmerge0clk <= clk ;
mul0clk <= clk ;
nopop0clk <= clk ;
for i0 : f o r i PORT MAP( fori0clk , reset , n, nstr2in , nack2ack ,
i , istr2in , iack2ack , s9 , s9str2in , s9ack2ack , s5 , s5str2in , s5ack2ack ) ;
ndmerge0: ndmerge PORT MAP( ndmerge0clk , reset , fat , fatstr2in ,
fatack2ack , s7 , s7str2in , s7ack2ack , s4 , s4str2in , s4ack2ack ) ;
branch0 : branch PORT MAP( brach0clk , reset , s4 , s4str2in , s4ack2ack ,
s9 , s9str2in , s9ack2ack , s8 , s8str2in , s8ack2ack , s6 , s6str2in , s6ack2ack ) ;
nopop0: nopop PORT MAP( nopop0clk , reset , s8 , s8str2in , s8ack2ack ) ;
mul0: mul PORT MAP( mul0clk , reset , s5 , s5str2in , s5ack2ack ,
s6 , s6str2in , s6ack2ack , s7 , s7str2in , s7ack2ack ) ;
END structural ;
104