• Aucun résultat trouvé

4.5.

Figura 4.12 - Fluxo de dados

O fluxo de dados consiste no trânsito de dados entre as unidades funcionais que constituem o processador. Os dados podem ser operandos dos bancos de registo GR e BR, registos especiais tais como o valor do program counter, link register e stack

pointer, podem ser imediatos que estejam embebidos nas instruções, ou então dados da

memória de dados. A Figura 4.11 ilustra o fluxo desses dados entre as unidades que compõem o processador.

Unidade Fetch 4.5.1.

A função desta unidade é efetuar o fetch da instrução VLIW da memória de código e sua decomposição, para isso necessita apenas do valor do program counter que lhe é passado pela unidade Program Counter através do barramento Pc[31:0].

Unidade Decode 4.5.2.

Os operandos GR são recebidos através dos barramentos Data_R1_S#[31:0] e

62

operandos são depois facultados às unidades que se seguem através dos barramentos

Operand1_S#[31:0], Operand2_S#[31:0] e Operandb_S# (operando 1 e 2 do GR e

operando BR respetivamente). Em alguns tipos de instruções é usado um operando do tipo imediato, quando isto acontece é ignorado o operando 2 proveniente do banco GR e o barramento Operand2_#[31:0] assume o valor do imediato que se encontra embebido na sílaba.

O operando Branch Offset immediate é disponibilizado à unidade Control através do barramento Offset[11:0]. O sinal Target_#[2:0] transporta para a unidade WriteBack a informação do destino de escrita da sílaba a que corresponde. O sinal PosOff[1:0] tem como funcionalidade informar a unidade Memory Control, no caso de uma escrita ao byte, de qual o byte a ser escrito dentro dos 32 bits do registo alvo. Por fim, o sinal

TargetIsBr_S# informa a unidade Execute que o alvo de escrita, do resultado da

operação da sílaba a que corresponde, é um registo do banco BR.

Unidade Execute 4.5.3.

Os operandos necessários para execução das instruções chegam por meio dos barramentos Operand_1_#[31:0], Operand_2_#[31:0] e OperandBr_# (operandos 1 e 2 do GR e operando BR respetivamente). O resultado das operações é colocado nos barramentos Result_#[31:0] e BrResult_#. O sinal BrDest_# tem como função informar a unidade Execute que o resultado da operação da sílaba correspondente é do tipo BR, isto permite saber se o resultado da operação realizada nos módulos ALU deve ser colocado no barramento Result_#[31:0] ou no BrResult_#.

Unidade Control 4.5.4.

Apenas a sílaba_0 está habilitada a utilizar a unidade Control, pois apenas as instruções do tipo CTRL são executadas nesta unidade. Este tipo de instruções, ao gerarem saltos na execução do código, originam alterações aos valores do program

counter, link register e stack pointer. Os barramentos Pc[31:0], Lr[31:0] e Sp[31:0]

contêm os valores atuais destes registos, que, após execução da instrução, seguem atualizados para os barramentos ProgramCntr[31:0], LinkReg[31:0] e StackPt[31:0]. A atualização apenas será efetuada mediante autorização da unidade WriteBack. O valor do branch immediate necessário neste tipo de instruções encontra-se no barramento

63

Offset[11:0]. O sinal Br transporta o operando Br que será testado como condição nas

instruções “BR” e “BRF”.

Unidade Memory Control 4.5.5.

A unidade Memory Control é responsável pela gestão do acesso à memória de dados. No caso de uma leitura da memória, os dados chegam através do barramento

DataMemLoaded[31:0] e são enviados para a unidade WriteBack pelo barramento DataToReg[31:0] por forma a serem guardados no banco de registos interno. No caso

de operações de escrita na memória, o barramento DataFromReg[31:0] faz chegar os operandos provenientes do GR e estes seguem para a unidade WriteBack pelo barramento DataToMem[31:0].

Unidade WriteBack 4.5.6.

O barramento Target_#[2:0] contém a informação do destino de escrita do resultado de cada uma das sílabas, a Tabela 4.12 mostra os destinos possíveis para escrita e que valores este barramento assume dependendo do destino.

Tabela 4.12 - Destinos da escrita da unidade WriteBack

Target_# Destino da escrita Mnemónica

000 Não escrever WRITE_NOP

001 Banco de registos GR WRITE_GR

010 Banco de registos BR WRITE_BR

011 Banco de registos GR e

Banco de registos BR

WRITE_GR_BR

100 Program counter WRITE_PC

101 Program counter e

Banco de registos GR

WRITE_PC_GR

110 Memória de dados WRITE_DATAMEM

111 Banco de registos GR

(leitura da memória)

64

Os barramentos DataInGr_#[31:0] e DataInBr_# fazem chegar os resultados das instruções executadas na unidade “Execute” cujo destino seja o banco GR ou BR. Estes dados seguem para escrita pelos barramentos DataOutGr_#[31:0] e DataOutBr_#. Caso o barramento target_3[2:0] seja “WRITE_GR_MEMLOAD” significa que o barramento DataInMemToGr_3[31:0] contém também dados a escrever no banco GR. No barramento DataOutMem[31:0] estão os dados provenientes da unidade Memory Control que se pretende guardar na memória de dados, o barramento

DataToMemIn_3[31:0] envia estes dados para a memória. A escrita dos registos program counter, link register e stack pointer é feita de modo diferente, em vês da

unidade Control enviar os dados a escrever para a unidade WriteBack, estes são enviados para as unidades que os armazenam, o que a unidade WriteBack faz é autorizar a escrita através dos sinais WritePc_Enable, WriteLr_Enable e

WriteSp_Enable. Garante-se assim que todas as escritas ocorrem em simultâneo.

Caches

4.6.

Como foi referido, as caches são memórias que têm por função permitir um acesso à memória a uma velocidade próxima das memórias mais rápidas. A figura 4.12 mostra o esquemático de referente ao interface das caches com o processador e com as memórias de dados e código principais.

65

Figura 4.13 - Interface das caches

Cache de código 4.6.1.

Inicialmente foi criada uma máquina de estados que descrevesse o comportamento da cache de código, em que foram previstas as condições de transição de estado e os registos afetados em cada estado.

66

A máquina de estados da Figura 4.12 descreve o funcionamento da cache de código. São necessários apenas dois estados para que a cache obtenha o funcionamento que é pretendido. Após Reset, o estado de funcionamento por defeito é “IDLE”. No estado “IDLE” os registos permanecem inalterados e, no caso de existir um pedido de leitura de uma instrução que esteja guardada em cache, ela é disponibilizada para leitura sem para isso necessitar de sair do estado “IDLE”. O estado de funcionamento permanece inalterado até que a cache receba da unidade Fetch um pedido de leitura de uma instrução que não esteja em armazenada na cache. Verificando-se esta condição de transição, o estado de funcionamento da cache passa a ser “READ_MEM” e neste estado a cache faz um pedido de leitura à memória principal, requisitando a instrução pretendida pelo Fetch, aguarda pela mesma, e assim que esteja disponível, armazena-a numa linha da cache e simultaneamente disponibiliza a instrução ao Fetch, no fim disto, a cache de código regressa ao estado de funcionamento IDLE.

As Tabela 4.13 e Tabela 4.14 descrevem com detalhe os registos internos, barramentos e sinais da cache.

Tabela 4.13- Registos internos da cache de código

Registos Descrição

[127:0]Cache_set[63:0[ Matriz de memória para armazenar as instruções lidas da memória de

código.

[25:0]Cache_tag[63:0]

Espaço de memória para armazenar a informação correspondente ao endereço da memória principal a que correspondem as instruções guardadas no registo Cache_set. Deste modo é possível identificar o bloco da memória principal a que corresponde cada instrução armazenada na cache.

ValidCache[63:0] Registo que guarda a informação da validade das linhas de código guardadas

no registo Cache_set, tendo o valor “1” caso a esteja válida.

Tabela 4.14 - Sinais e barramentos da Cache de Código

Sinais / Barramentos Entrada / Saída

Unidade de

interface Descrição

AddressInstr_p[31:0] Entrada Fetch Endereço da instrução que o processador pretende ler da memória

67 Código ler da memória de código

InstrOut_p[127:0] Saída Fetch Instrução VLIW enviada para o processador

InstrIn_m[127:0] Entrada Memória de Código

Instrução VLIW recebida da memória de código

ICacheReady Saída Informação de que a cache esta disponível para acesso.

InstrReady_p Saída Fetch State Control

Informação de que a instrução pedida pelo processador está disponível para leitura.

InstrRequest_p Entrada Fetch Pedido de leitura de instrução por parte do processador.

InstrRequest_m Saída Memória de código

Pedido à memória de código para leitura de instrução

InstrReady_m Entrada Memória de código

Informação de que a memória de código tem a instrução disponível para acesso por parte da cache

A cache implementada é do tipo Direct Mapped, o que significa que cada posição da memória principal é mapeada diretamente numa linha de cache predefinida. Constituída por 64 linhas de cache o mapeamento da memória de dados nas linhas de

cache processa-se da seguinte forma: os 6 bits menos significativos do endereço da

instrução são utilizados para endereçar as 64 linhas de cache, os restantes bits do endereço constituem o Tag e têm como função identificar o bloco da memória principal a que pertence a linha endereçada (Figura 4.13).

Endereço da linha Endereço Tag

0 5

31

Figura 4.15 – Decode de uma instrução da cache de código

Cache de dados 4.6.2.

A máquina de estados da Figura 4.15 apesenta o comportamento da cache de dados. Para a implementação desta cache houve a necessidade de recorrer a 3 estados de funcionamento, o facto de se tratar de uma cache do tipo Read/Write faz com que a

68

implementação seja mais complexa comparativamente à cache de instruções, porque obriga a que se implementem políticas de escrita e de substituição de dados. O método de acesso à cache de dados é do tipo 4-way set associative, a política de escrita é Write-

Back policy e o algoritmo de substituição utilizado foi Pseudo Least recently used

(Pseudo-LRU).

Bit_a

Bit_b1 Bit_b2

Cache

Set_ 0 Cache Set_1 Cache Set_2 Cache Set_3

0 – Segue pela esquerda 1 – Segue pela direita

0 1

0 1 0 1

Bit_a Bit_b1 Bit_b2 Bit_a Bit_b1 Bit_b2

Cache Set_0 comuta bit_b1 0 0 0 0 1 0

Cache Set_1 comuta bit_a e bit_b1 0 1 0 1 0 0

Cache Set_2 comuta bit_b2 1 0 0 1 0 1

Cache Set_3 comuta bit_a e bit_b2 1 0 1 0 0 0

Combinação que leva ao uso do Cache set

Valor dos bits após acesso ao cache Set

Ação

Figura 4.16 – Árvore binária e comportamento do método de substituição Pseudo-LRU

O estado após Reset é o estado “IDLE”, neste estado a cache está disponível para acesso por parte do processador, quer para leitura quer para escrita. Os sinais

ReadRequest_p e WriteRequest_p são sinais de interface entre o processador e a cache,

servindo o primeiro para receber pedidos de leitura e o segundo pedidos de escrita. O endereço da posição de memória a que o processador pretende aceder é passado pelo barramento AddressData_p[31:0]. Através do sinal CacheReady a cache informa o processador de que está disponível para acesso, o sinal DataReady_p informa o processador que os dados pedidos estão disponíveis para leitura. Um novo pedido de leitura ou de escrita só é aceite quando a cache está no estado de funcionamento “IDLE”. Em cada novo pedido de leitura ou escrita, por forma a respeitar a política de

69 escrita Write-back, é verificado se os dados a que se pretende aceder estão em cache (sinal Hit). É também verificado se a linha de cache tem valor diferente da linha de dados do bloco da memória a que corresponde (o sinal Dirty tem valor “1” quando os valores forem diferentes).

70

O comportamento da cache no estado de funcionamento “IDLE” depende do tipo de condição satisfeita, quando (ReadRequest_p & Hit) igual a “1” os dados pretendidos pelo processador são disponibilizados para leitura, quando (WriteRequest_p & Hit) igual a “1” os dados enviados pelo processador são guardados em cache, quando (!Hit

& Dirty) for “1”, ou seja, quando os dados do endereço pretendido não estiverem em cache e a linha de cache que eles forem ocupar estiver marcada como dirty (diferente do

seu homogéneo na memória principal), o estado de funcionamento passa a ser “WRITE_MEM”, e quando (!Hit & !Dirty) for “1” significa que o endereço do qual se pretendem ler os dados não está em cache e que a posição que será ocupada no interior da cache não necessita de ser salvaguardada, neste caso o estado de funcionamento passa a ser “READ_MEM”. No estado “WRITE_MEM” os dados de uma linha de

cache que esteja marcada como dirty é restituída à memória, antes que seja substituída,

de forma a não se perder a coerência entre cache e memória. Após concluída a escrita na memória principal, o estado transita para “READ_MEM” onde é feita a leitura de uma nova linha de dados da memória principal e guardada em cache. No caso de se tratar de um pedido de leitura, os dados são disponibilizados ao processador em simultâneo. Assim que a leitura da nova linha de dados estiver concluída, o estado de funcionamento volta a ser “IDLE” e assim permanece até que se verifiquem as condições de transição descritas anteriormente.

As Tabela 4.15 e Tabela 4.16 descrevem as características dos registos, sinais e barramentos da cache de dados.

Tabela 4.15 - Registos internos da cache de dados

Registos Descrição

[255:0]Cache_set_#[63:0] Matriz de registos onde estão armazenados os dados. Por se tratar de uma

cache do tipo 4-way set associative existem 4 matrizes deste tipo.

[22:0][Cache_tag_#[63:0]

Espaço de memória para armazenar a informação correspondente ao endereço da memória principal a que correspondem os dados guardados no registo Cache_set correspondente. Deste modo é possível identificar o bloco da memória principal a que corresponde cada linha de dados armazenada na cache.

ValidCache_#[63:0] Registo que possui informação de que a linha de cache correspondente

71

Registos Descrição

DirtyCache_#[63:0]

Registo que possui informação de que a linha de cache correspondente possui dados que não estão em conformidade com o seu homólogo na memória principal.

State[1:0] Estado de funcionamento da cache de dados.

Tabela 4.16 - Sinais e barramentos da cache de dados

Sinal / Barramento Entrada / Saída

Unidade de

interface Descrição ReadRequest_p Entrada Memory

Control Pedido de leitura por parte do processador.

WriteRequest_p Entrada WriteBack Pedido de escrita por parte do processador.

CacheReady Saída WriteBack Cache disponível para acesso.

DataReady_p Saída Memory Control

Dados requeridos pelo processador estão disponíveis no barramento.

ReadRequest_m Saída Memória de

dados Pedido de leitura à memória de dados principal.

WriteRequest_m Saída Memória de dados

Pedido para escrita na memória de dados principal.

ReadReady_m Entrada Memória de dados

Informação de que os dados que a cache pretende ler da memória principal estão disponíveis no barramento.

WriteReady_m Entrada Memória de dados

Informação de que a escrita na memória de dados principal está concluída.

DataIn_p[31:0] Entrada WriteBack Dados que o processador pretende guardar na cache.

AddressData_p[31:0] Entrada Decode Endereço da posição de memória de dados que o processador pretende aceder.

DataOut_p[31:0] Saída Memory

Control Dados que o processador requereu à cache.

AddressData_m[31:0] Saída Memória de dados

Endereço a que a cache pretende aceder na memória de dados principal.

72

Sinal / Barramento Entrada / Saída

Unidade de

interface Descrição

dados principal

DataIn_m[255:0] Entrada Memória de

dados Dados que a cache requereu à memória principal.

A cache foi implementada sendo do tipo 4-way set associative, o que significa que cada bloco da memória principal pode ser mapeada em 4 localizações diferentes. De modo a possibilitar o endereçamento à word (32 bits), os 3 bits menos significativos do endereço identificam qual das 8 words é a pretendida. Para endereçar as 64 linhas da

cache são necessários o 6 bits, serão portanto os bits AddressData_p[8:3] , os restantes

bits do endereço constituem o Tag e têm como função identificar o bloco da memória principal a que pertence a linha endereçada ( Figura 4.16).

73

CAPÍTULO 5

Assembler

Introdução

5.1.

Este capítulo é dedicado ao Assembler desenvolvido para o Hc-Vex. O assembler foi desenvolvido com o objetivo de acompanhar a configurabilidade do processador, ou seja, o facto de se ter um assembler open-source dedicado ao processador permite que se altere facilmente o código máquina gerado de acordo com as alterações efetuadas no processador. O design do assembler teve por base o assembler X-asm desenvolvido na Universidade do Minho e foi alterado de acordo com o ISA VEX para que o código máquina pudesse ser utilizado no Hc-Vex. É descrita a implementação do analisador léxico, sintático e a geração de código.

Análise

5.2.

O compilador é responsável por identificar o paralelismo no código a executar, o código assembly gerado pelo compilador é por isso isento de dependências entre operações. Sendo que a arquitetura do Hc-Vex admite a execução de 4 operações em paralelo (pelos motivos descritos anteriormente) o assembler gerado é composto por até 4 operações em cada instrução VLIW, a separação de instruções é realizada pelo identificador “;;”. A organização das operações no interior da instrução é realizada pelo compilador. Em alguns casos isso não se verifica, foi portanto necessário atribuir essa funcionalidade ao assembler por forma a garantir que o layout da instrução VLIW é respeitado.

De modo a gerar código nativo para executar no processador implementado, revelou-se necessário desenvolver um assembler que, com base na semântica das operações existentes no instruction set VEX (Anexo A) e ajustando-o às especificações microarquiteturais da implementação, gere código objeto compatível com o

74

processador. A Figura 5.1 demonstra os passos de compilação de um código escrito em linguagem C até à sua execução no processador.

Figura 5.1 - Etapas de compilação do código

O código que se pretende executar no processador começa por ser escrito em linguagem C e, com recurso ao compilador VEX-3.43, é feita a compilação do código C e gerado código Assembly, fica então a faltar efetuar a assemblagem do código por forma a obter código nativo. Foi a necessidade de gerar código máquina dedicado ao processador implementado e o desconhecimento do source code do assembler existente na toolchain da qual o compilador faz parte que levou a que fosse desenvolvido um

assembler dedicado ao processador Hc-Vex.

Embora o Vex-3.43 efetue assemblagem, o código gerado não é binariamente compatível com o processador Hc-Vex, para além disso, o facto de se tratar de uma implementação paramétrica permite que se possam efetuar alterações ao processador, ter um assembler open-source dedicado ao processador permite que se altere facilmente o código máquina gerado de acordo com as alterações efetuadas no processador.

75

Design

5.3.

Neste tópico será abordado o design do assembler que se pretende desenvolver, será apresentada a sua estrutura e funcionamento com recurso a diagramas de blocos e fluxogramas.

O assembler desenvolvido tem por base o assembler X-Asm desenvolvido na Universidade do Minho, embora este assembler seja dedicado a um processador de arquitetura RISC, o X-soc, com a alteração da tabela de símbolos de acordo com o ISA VEX (Anexo A) e da geração de código tentar-se-á fazer dele um assembler dedicado ao Hc-Vex.

A estratégia de desenvolvimento do assembler é a utilizada no X-Asm sendo a assemblagem dividida em duas etapas, a primeira consiste em ler o código fonte linha a linha analisando-o lexicalmente e sintaticamente e gerando um código intermédio. Na segunda etapa, este código intermédio é relido, processado com a ajuda da tabela de símbolos e convertido em código objeto.

Código Fonte Tabela de símbolos Etapa 1 Código intermédio Etapa 2 Geração de código Tabela de símbolos Código objecto

Figura 5.2 - Estrutura do Cel-Asm

A Figura 5.2 apresenta um diagrama que descreve a estrutura de desenvolvimento do assembler, os fluxogramas das Figura 5.3 e Figura 5.4 descrevem o funcionamento do primeiro e segundo passo respetivamente.

76

Código Fonte

Abre o ficheiro e inicia a leitura linha a linha

Decompõe cada linha em tokens

Verifica a validade da frase de acordo com a gramática e caso

seja valido devolve o pacote

O opcode é uma diretiva (.org) ?

Inicia o LC e o endereço inicial com a constante especificada na

diretiva Atualiza a representação intermédia com a estrutura da

frase atual processa a próxima linha

Atualiza o LC e o endereço inicial com o valor zero

Processa as restantes linhas

Atualiza a representação intermedia com a estrutura da

ultima linha Calcula e guarda o cumprimento

do módulo

Fim

Não

Sim

Figura 5.3 - Fluxograma do primeiro passo

Representação intermédia

Lê a primeira entrada

Obtêm o opcode da entrada em processamento

O opcode é uma diretiva (.org) ?

Escreve o cabeçalho do código objeto

Inicializa o primeiro registo de codificação da instrução

Escreve a linha de listagem e lê a proxima entrada da representação intermédia

Processa as restantes representações do código

intermédio Escreve o último registo de codificação da instrução do

código objeto