6.5.
A figura 6.4 mostra o percurso de execução da instrução “add $r0.1 = $r0.1, (- 0x40)".
91
Figura 6.4 Execução da instrução "add $r0.1 = $r0.1, (-0x40)"
No instante 55ns a unidade Fetch inicia o processo de fetch da instrução guardada no endereço da memória de código dada pelo program counter. O fetch é concluído passados 3 ciclos de clock, no instante 85ns, estando a sílaba disponível para decode. O processo de decode termina passado um ciclo de clock, estando no barramento
Operand1_S0 disponível o valor do Sp (registo r0.1), no Operand2_S0 esta o resultado
da soma do valor do Sp com o offset “-0x40”. A execução da operação inicia no instante 95ns e termina passado um ciclo de clock, neste momento o barramento Result_0 tem o resultado da operação, ou seja, o valor da soma do Sp com o offset. O processo de escrita dura também um ciclo de clock, terminando no instante 115ns. O destino de escrita do resultado é o banco de registos GR e por isso o valor do barramento Target_0 é “1”. Pode-se verificar que no fim da execução da operação o valor do registo Sp (r0.1) é atualizado com o resultado da operação.
A Figura 6.5 demonstra o funcionamento da operação CALL, esta operação é executada pela sílaba 0 da instrução VLIW nº 7. A execução desta operação vai provocar um salto na execução do código, alterando o valor dos registos especiais link
register e program counter, ficando o program counter com o endereço da linha de
código para onde é efetuado o salto e o link register com o endereço da instrução a executar quando o programa regressar do salto.
92
Figura 6.5 - Execução da instrução "call $l0.0 = _add"
No instante 475ns a unidade Fetch inicia o processo de fetch da instrução guardada no endereço da memória de código dada pelo program counter. O fetch é concluído passados 3 ciclos de clock, no instante 505ns, estando a sílaba disponível para
decode. O processo de decode termina passado um ciclo de clock, estando disponível no
barramento Operand2_S0 o endereço da label “_add”, que será o próximo valor do Pc. A execução da operação inicia no instante 515ns e termina passado um ciclo de clock, neste momento o novo valor dos registos LinkReg (valor “0x8”) e ProgramCntr (valor “0x16”) é dado como válido. O processo de escrita dura também um ciclo de clock, terminando no instante 535ns. O destino de escrita do resultado é o program counter e o banco de registos GR e por isso o valor do barramento Target_0 é “5”. Pode-se verificar que no fim da execução da operação o valor do registo Lr (r0.63) é atualizado com o valor “0x8” e o barramento Pc com o valor “0x16”.
A Figura 6.6 demonstra o funcionamento da operação RETURN, esta operação é executada pela sílaba 0 da instrução VLIW nº 23. A execução desta operação vai provocar um salto na execução do código da função “_add” para o endereço imediatamente a seguir ao da instrução que gerou o salto para função “_add”. Tal como com a operação “CALL”, a execução da operação “RETURN” altera o valor dos registos especiais link register e program counter e neste caso também do stack pointer, ficando o program counter com o endereço dado pelo conteúdo do link register, que
93 corresponde à linha de código para onde é efetuado o salto, o valor do stack pointer é atualizado com o resultado da soma dele mesmo com o valor do offset.
Figura 6.6 - Execução da instruçao "return $r0.1 = $r0.1, (0x0), $l0.0"
No instante 595ns a unidade Fetch inicia o processo de fetch da instrução guardada no endereço da memória de código dada pelo program counter. O fetch é concluído passados 3 ciclos de clock, no instante 625ns, estando a sílaba disponível para
decode. O processo de decode termina passado um ciclo de clock, estando disponível no
barramento Operand1_S0 o valor atual do stack pointer e no barramento offset o valor que será somado ao Sp (“0x0”). A execução da operação inicia no instante 635ns e termina passado um ciclo de clock, neste momento o novo valor dos registos
ProgramCntr (valor “0x8”) e stack pointer (valor 0xffc0) é dado como válido. O
processo de escrita dura também um ciclo de clock, terminando no instante 655ns. O destino de escrita do resultado é o program counter e o banco de registos GR e por isso o valor do barramento Target_0 é “5”. Pode-se verificar que no fim da execução da operação o valor do registo Sp (r0.1) é atualizado com o valor “0xffc0” e o barramento
Pc com o valor “0x8”.
A Figura 6.7 demonstra o funcionamento da operação STW, esta operação é executada pela sílaba 3 da instrução VLIW nº 1. A execução desta operação vai gerar uma escrita na memória de dados do conteúdo do link register.
94
Figura 6.7 - Execução da instrução "stw 0x20[$r0.1] = $l0.0"
No instante 115ns a unidade Fetch inicia o processo de fetch da instrução guardada no endereço da memória de código dada pelo program counter. O fetch é concluído passados 3 ciclos de clock, no instante 145ns, estando a sílaba disponível para
decode. O processo de decode termina passado um ciclo de clock, estando disponível no
barramento Operand1_S3 o valor atual do stack pointer e no barramento DataToMem os dados que se pretendem guardar em memória. Como se trata de uma escrita na pilha, o endereço da memória de dados onde se pretende efetuar a escrita é dado pelo registo
Sp, no caso de se pretender escrever ou ler de uma qualquer outra posição da cache, o
endereço pode ser dado por um qualquer registo desde que não seja um registo especial (tanto num caso como noutro é somado um offset ao valor do registo). A execução da operação inicia no instante 155ns e termina passado um ciclo de clock, neste momento o novo valor dos barramentos MemAddrToWr (0xffc0) e DataToMem (0x0) é dado como válido. O processo de escrita dura também um ciclo de clock, terminando no instante 175ns. O destino de escrita do resultado é a memória de dados e por isso o valor do barramento Target_3 é “6”. O pedido de escrita é feito durante o processo de escrita, mas como estamos a usar uma cache entre a memória principal e o processador, um ciclo de clock após a conclusão do processo de escrita a linha de memória onde se pretende escrever os dados é lida para a cache, e apenas no ciclo de clock seguinte, no instante 195ns, é que a escrita dos dados é feita na linha de cache na posição pretendida.
95 A Figura 6.8Figura 6.7 demonstra o funcionamento da operação LDW, esta operação é executada pela sílaba 3 da instrução VLIW nº 6. A execução desta operação vai gerar uma leitura da memória de dados.
Figura 6.8 - Execução da instrução "ldw $r0.4 = 4[$r0.5]"
No instante 415ns a unidade Fetch inicia o processo de fetch da instrução guardada no endereço da memória de código dada pelo program counter. O fetch é concluído passados 3 ciclos de clock, no instante 445ns, estando a sílaba disponível para
decode. O processo de decode termina passado um ciclo de clock, estando disponível no
barramento Operand1_S3 o valor do registo ”r0.5”, no barramento Operand2_S3 o valor do offset que será somado ao conteúdo de “r0.5” por forma a calcular o endereço de onde será efetuada a leitura e no barramento AddressDest_S3Gr o endereço do registo Gr onde serão guardados os dados lidos da memória. A execução da operação inicia no instante 455ns e termina passado um ciclo de clock, neste momento o valor do barramento DataToReg (0x7f) é dado como válido. O processo de escrita dura também um ciclo de clock, terminando no instante 475ns. O destino de escrita dos dados lidos da memória é um registo GR, por isso o valor do barramento Target_3 é “7”. O pedido de leitura à memória é feito durante o processo de execute por forma a ter disponível os dados para escrita a quando da execução do processo de escrita.
96
A Figura 6.9 ilustra o comportamento da cache de código a quando de um pedido de leitura por parte do processador.
Figura 6.9 - Comportamento da cache de código
Quando o sinal InstrRequest comuta para “1” (instante 65ns) o sinal state passa a ser “1”, ou seja, a cache passa a estar no estado de funcionamento “WAITING”. Do endereço presente no barramento AddressInstr_p a cache retira o tag address e o line
address, com isto verifica se a instrução esta armazenada em cache (sinal Hit), como se
pode ver na figura o valor de HitInstr_p é “0”, há portanto a necessidade de ler a instrução da memória de código. Neste mesmo instante a cache comuta o sinal
InstrRequest_m para “1” por forma a efetuar o pedido de leitura. No instante 75ns o
sinal InstrReady_m comuta para “1” informando a cache de que a instrução pretendida está disponível no barramento InstrIn_m. A cache coloca a instrução recebida no barramento InstrOut_p e coloca o sinal InstrReady_p com o valor “1” por forma a informar o processador que a instrução pedida está disponível. Como a instrução passou a estar armazenada em cache, o sinal HitInstr_p passa a ter o valor “1” para este endereço da memória.
A Figura 6.10 permite observar o comportamento da cache de dados a quando de um pedido de escrita por parte do processador.
97
Figura 6.10 - Comportamento da cache de dados numa escrita
Pode-se observar na figura que no instante 165ns o sinal WriteRequest_p comuta para o valor “1” o que significa que existe um pedido de escrita, o barramento DataIn_p contém os dados que se pretende guardar na cache, neste caso 0x00. Como a linha de memória em que o processador pretende efetuar a escrita não esta armazenada na cache ,pois o sinal HitData é “0”, no ciclo de clock seguinte a cache faz um pedido de leitura à memória de dados da linha de memória com o endereço dado pelo barramento Address. A cache transita para o estado de funcionamento “READ_MEM” no instante 175ns e mantem-se neste estado um ciclo de clock, durante este período a cache coloca o sinal
ReadRequest_m com valor “1”. No instante 185ns o sinal ReadReady comuta para “1”
indicando que a linha de dados requerida está disponível no barramento DataIn_m. A linha de dados é guardada em cache e no ciclo de clock seguinte os dados são guardados na posição pretendida dentro da linha de cache.
A Figura 6.11 por sua vez permite observar o comportamento da cache de dados a quando de um pedido de leitura por parte do processador.
98
Figura 6.11 - Comportamento da cache de dados numa leitura
Pode-se observar na figura que no instante 395ns o sinal ReadRequest_p comuta para o valor “1” o que significa que existe um pedido de leitura, o barramento Address contém o endereço dos dados que se pretende ler da cache. Como o sinal HitData é “1” significa que a linha da memória de dados correspondente ao endereço de onde se pretende ler os dados, então a cache coloca de imediato os dados pedidos no barramento
DataOut_p e comuta o sinal DataReady_p para o valor “1” por forma a informar o
processador que os dados estão disponíveis.
Figura 6.12 - Linhas de dados da cache set 0
A Figura 6.12 apresenta o conteúdo das linhas da cache de dados no final da execução do código, pode-se observar que toda a escrita foi feita no cache set 0, isto deveu-se à utilização do algoritmo de substituição usado, o Pseudo-LRU. Na quinta
99
word do endereço 0x3A ficou guardado o resultado da multiplicação, na primeira word
do endereço 0x3B ficou guardado o resultado da soma e na quinta word da mesma linha de cache ficou guardado o resultado da divisão.
A execução do benchmark 32-bit match foi concluída ao fim de 2105ns, tal como mostra a figura figura 6.13, em termos de ciclos de relógio foram necessários 206 ciclos
(2105−45
10 = 206, em que 2105 é o instante em que foi concluída a execução, 45ns é o
instante em que o sinal de reset vai a “0” iniciando assim o funcionamento e 10ns é o tempo que demora cada ciclo de relógio). É neste instante que o valor do program
counter volta a ter valor “0”, o que significa que todas as linhas de código forma
executadas e que a ultima instrução executada provocou um return para o endereço “0” da memória de código. Outro aspeto importante que a imagem ilustra é o número de ciclos necessários para efetuar uma divisão, a divisão é efetuada na instrução “27” do código e a sua execução dura 36 ciclos de relógio.
Figura 6.13 - Fim de execução do benchmark e duração da operação divisão
O desempenho de execução do Hc-Vex foi comparado com os microcontroladores da familia MSP430 porque foi este o benchmark a que se teve acesso, de notar que a comparação com micros de 16 bits não é a ideal para uma conclusão convincente, mas serve para ter uma noção do desempenho. Podemos verificar que o número de ciclos necessários no Hc-Vex foi inferior ao necessário em qualquer microcontrolador da família MSP430.
101
CAPÍTULO 7
Conclusão
Neste capítulo são apresentadas as ilações com base no trabalho realizado. São também enunciadas algumas propostas para trabalho futuro.
Conclusões
7.1.
Nesta tese foi apresentado o processador Hc-Vex, um processador VLIW open-
source reconfigurável e extensível baseado no ISA VEX. O objetivo desta dissertação
foi desenvolver e implementar numa plataforma FPGA em linguagem HDL Verilog um processador reconfigurável de arquitetura VLIW.
O ISA escolhido para esta implementação foi o VEX, os fatores que motivaram a sua escolha foram a sua configurabilidade, extensibilidade e a existência de um compilador VEX fiável e livre desenvolvido pela HP.
Nesta implementação foi respeitada a organização do VEX, Os bancos de registos implementados foram o General Register e o Branch Register com 64 registos de 32 bits e 8 registos de 1 bit respetivamante. Foram implementadas duas caches, uma de acesso à memória de dados e outra de acesso à memória de código. A cache de dados utiliza o método de acesso do tipo 4-way set associative, a política de escrita é Write-Back policy e o algoritmo de substituição utilizado foi Pseudo Least recently used (Pseudo-LRU). A
cache de código é do tipo Direct Mapped. O template das silabas adotado foi o utilizado
na implementação ρ-Vex pois respeita o ISA VEX.
O Assembler desenvolvido efetuou a assemblagem do código com sucesso, organizando as silabas respeitando o template da instrução VLIW o que solucionou uma lacuna do código gerado pelo compilador. Em termos binários as instruções foram corretamente traduzidas para código máquina.
Relativamente aos resultados experimentais, foram executados testes com diversos códigos em linguagem C, por forma a verificar e comprovar o correto
102
funcionamento do trabalho realizado. Os códigos máquina gerados pelo assembler foram executados sempre com sucesso no processador implementado. Em termos de latência de execução, ficou garantida a latência de execução definida pelo VEX.
Embora não tenham sido efetuados testes em FPGA, as simulações efetuadas permitem concluir que o objetivo desta dissertação foi alcançado. Foi implementado um processador extensível e reconfigurável de arquitetura VLIW, a linguagem HDL utilizada foi verilog tal como era requerido, o funcionamento do processador foi testado e comprovado o correto funcionamento devido aos resultados corretos que se obtiveram, o funcionamento das caches implementadas teve o comportamento pretendido. O
assembler desenvolvido também teve os resultados esperados, o código máquina foi
gerado sempre de forma correta não havendo falhas binárias nas instruções do código a executar.