4.4 Comparação entre FPGA, ASIC e CPLD
4.4.2 FPGA e CPLD
Um CPLD é a combinação de um array AND/OR completamente programável com um bloco de macro-células. O array é re-programável e desempenha uma grande variedade de funções lógicas, enquanto que as macro-células são blocos funcionais que desempenham lógica sequencial e combinatorial. É, tal como a FPGA, um dispositivo que permite alterar o design de um projeto instantanea- mente e sem custos [42]. Enquanto que os CPLD oferecem recursos lógicos com um maior número de inputs, as FPGA uma maior proporção de flip-flops para os recursos lógicos do que os CPLD. Portanto, as FPGA possuem uma capacidade lógica maior, sendo usadas em projetos maiores e complexos. São também mais apropriadas ao desenvolvimento de circuitos lógicos porque têm mais registos, enquanto que os CPLD são mais apropriados para aplicações de controlo. Estes dois tipos de dispositivos pertencem à classe de dispositivos FPD, que mudaram a forma como se desenvolve hardware, oferecendo baixos custos de implementa-
ção, baixo risco financeiro e possibilidade de realizar mudanças numa implemen- tação sempre que o programador desejar4[43].
4Deste conjunto de dispositivos também fazem parte os PLA, que contêm dois níveis de lógica,
um nível AND e um nível OR, ambos programáveis. Surgiram nos anos 70 do século XX, sendo os predecessores dos CPLD e FPGA.
Conceção, Implementação e Testes
A conceção deste projeto envolveu várias fases, estando este capítulo organizado de acordo com essas fases. No início procedeu-se ao estudo da máquina vir- tual Java, implementando um protótipo em linguagem C para se ter a noção de problemas ou das características inerentes à implementação desta esta máquina virtual. No decorrer dessa implementação desenvolveu-se um módulo de pré- -processamento do projeto que visa simplificar a execução da máquina virtual Java dentro da FPGA, nomeadamente no processo de comparação de strings. Posteriormente, foram implementados vários programas na FPGA Spartan-3E, para testar a melhor forma de transferir dados do computador para a FPGA, ar- mazenar esses dados dentro do dispositivo, conceber máquinas de estados efici- entes, em suma, testar os recursos da própria FPGA.
A primeira fase de implementação da máquina virtual Java em linguagem Veri- log traduziu-se em simulações, adaptando o código de acordo com os resultados simulados obtidos até obter uma máquina virtual funcional. No entanto, para se poder compilar o projeto dessa máquina virtual, foram feitas modificações ao código que possibilitaram a obtenção de um código sintetizável e a conse- quente programação da FPGA, e a implementação final da máquina virtual Java na FPGA.
O Spartan-3A Starter Kit foi introduzido durante a fase de implementação da máquina virtual Java na FPGA pois esta placa contém mais recursos que a da Spartan-3E (a placa Nexys2), nomeadamente um ecrã LCD que permite fazer de- bug ao código em execução, ajudando assim a melhorar o projeto. O ecrã LCD
também é importante para a execução de algumas instruções, nomeadamente para imprimir valores, pois fornece o meio necessário para essas instruções se- rem executadas com sucesso.
Note-se que o ponto de partida para a implementação desta máquina virtual Java é executar ficheiros class que pertencem a programas Java que contêm essen- cialmente operações sobre inteiros e operações de imprimir valores, resultados e strings.
5.1
Estudo inicial e módulo de pré-processamento
O primeiro passo deste projeto foi estudar a The Java Virtual Machine Specification [19] e perceber em que se transforma o código de um ficheiro Java depois de ser compilado. Criou-se um ficheiro de teste simples Teste.java, representado na figura 5.1, onde existem duas variáveis do tipo int que são somadas e é feito um print do resultado.
Figura 5.1: Ficheiro de teste Teste.java.
De acordo com a The Java Virtual Machine Specification, durante a compilação foi criado o ficheiro Teste.class, que contém os bytecodes associados a esse ficheiro Java. Através de um leitor de ficheiros binários, é possível visualizar o conteúdo do ficheiro class, parte deste ficheiro está ilustrado na figura 5.2.
Para se visualizar apenas os bytecodes e sem ser em formato binário é possível usar o comando “javap -c Teste.class” na linha de comandos, obtendo-se o output da figura 5.3.
Constata-se que existem dois métodos, o método de inicialização <init> e o método void main (String[]). Os opcodes presentes neste ficheiro class têm as seguintes funções:
Figura 5.2: Parte do ficheiro class resultante.
• <init>
aload_0(0x2a) - carrega uma referência para a stack a partir da vari- ável local 0;
invokespecial #1(0xb7) - invoca a instância do método no objeto objectref, onde o método é identificado por um índice de referência ao método na constant_pool (indexbyte1 < < 8 + indexbyte2), neste caso é 0001;
return(0xb1) - volta para a linha de execução do chamador sem de- volver um valor.
• void main (String[])
iconst_2(0x05) - guarda o valor inteiro (int) 2 na stack;
istore_1(0x3c) - guarda o valor do tipo int na variável local 1; iconst_4(0x07) - guarda o valor inteiro (int) 4 na stack;
istore_1(0x3d) - guarda o valor do tipo int na variável local 2; iload_1 (0x1b) - carrega um valor do tipo int a partir da variável local 1;
iload_2 (0x1c) - carrega um valor do tipo int a partir da variável local 2;
iadd(0x60) - adiciona dois inteiros;
istore_3(0x3e) - guarda o valor do tipo inteiro na variável local 3; getstatic #2(0xb2) - obtém o valor de um campo static de uma classe, onde o campo é identificado por uma referência do campo no índice (index1 < < 8 + index2) da constant_pool, neste caso é 0002;
iload_3 (0x1d) - carrega um valor do tipo int a partir da variável local 3;
invokevirtual #3 (0xb6) - este bytecode invoca o método virtual no objeto objectref, onde o método é identificado por um índice de referência ao método na constant_pool (indexbyte1 < < 8 + indexbyte2), neste caso é 0003;
return(0xb1) - volta para a linha de execução do chamador sem de- volver um valor.
Junto da designação de cada opcode está a sua representação numérica em formato hexadecimal, estando também os opcodes destacados na figura 5.4, para ilustrar a sua localização no ficheiro Teste.class.
Figura 5.4: Localização dos opcodes no ficheiro class.
De acordo com [19] e como já foi referido no Capítulo 3, a implementação correta da máquina virtual Java baseia-se em ser capaz de ler o ficheiro class e executar corretamente as operações nele contidas. A partir deste ponto desenvolveu-se um programa em linguagem C para ler corretamente o ficheiro class, imprimindo o resultado no ecrã. Os primeiros quatro bytes lidos são os do magic number CA- FEBABE, os quatro a seguir a minor_version e major_version, seguindo-se os dois bytes que indicam a constant_pool_count. Este número indica que existem 36 − 1 entradas na tabela constant_pool. A seguir, lê-se a informação contida nesta tabela. Um porção do output resultante está representado na figura
5.5 (note-se que o print “numero” não faz parte da constant_pool, só serve para fazer debug quanto à posição da tabela em que se está a ler).
Figura 5.5: Output da leitura do ficheiro class.
Então, o processo para descodificar o ficheiro class consiste em ler a quantidade de bytes definida na [19] para cada parâmetro presente nesse ficheiro. Para saber quais são os métodos a ser invocados ou quais os atributos presentes nas tabelas fields, methods ou attributes, é necessário comparar strings.