Exercício 4 - Ampliação da plataforma, inclusão de roteador e periféricos

Informações Gerais

Dicas sobre o Exercício

Motivação

Objetivos

Esse exercício vai ser dividido em partes e, ao final delas, vocês devem ser capazes de:

Contextualização

Atenção: Todos os arquivos que você precisa para essa atividade estão disponíveis em /home/staff/rodolfo/mc723/download.

Em cada parte, leita todo o enunciado uma vez antes de executar suas atividades.

Parte 1 - Inclusão de um roteador

Utilizando como base a plataforma do exercício 3 (parte 1), você deve incluir um roteador entre o processador e a memória. O código deste roteador deve ser colocado na pasta is da plataforma. Utilize um dos programas do exercício 3.

Veja um pouco mais de detalhes a seguir, sobre a plataforma, antes das recomendações para seu código.

Todo o código da plataforma está separado em componentes, como você já deve ter notado. Cada um dos componentes fica num diretório isolado e eles são agrupados em diretórios segundo sua classificação. Esta organização por diretórios é apenas para facilitar a localização e também o entendimento, não é necessária para a montagem da plataforma (muito embora vocês devem segui-la sempre).

Sua primeira plataforma possuia apenas dois módulos: um processador e uma memória. Agora é hora de incluir um terceiro módulo, o roteador, que fará o papel de barramento do sistema. Ele está sendo chamado de roteador pois não implementará as funcionalidades de contenção de tráfego que o barramento, nem algumas outras funcionalidades interessantes. Então vocês trabalharão com algo simplificado neste momento.

A comunicação entre o processador e a memória se faz através do padrão TLM que, basicamente, realiza a ligação entre os componentes do sistema. Só que o padrão TLM é ponto a ponto, exigindo uma porta de cada lado para cada canal de comunicação. Além disto, o TLM inclui o conceito de mestre e escravo, onde o mestre sempre faz as solicitações e o escravo apenas as atende. No caso original, o processador é o mestre e a memória é escravo. O grande problema desta configuração inicial é a dificuldade em incluir um novo periférico pois será necessário modificar o processador para que ele se comunique com dois dispositivos diferentes. Assim, a solução é inluir um periférico no meio do caminho que apenas fará o papel de roteamento, este é o roteador que você vai fazer.

Do ponto de vista do TLM, as conexões são feitas sempre entre portas, o processador tem uma porta Mestre e a memória tem uma porta Escravo. No programa principal (main.cpp da plataforma), você vê a ligação entre o processador e a memória através da linha:

mips1_proc1.DM_port(mem.target_export);

esta linha liga a porta DM_port do processador à porta target_export da memória. Olhando um pouco antes neste código, você pode ver a declaração do processador mips1_proc1 e da memória mem. Sua tarefa é criar um novo componente, chamado roteador, que será conectado a mips1_proc1 e também à mem. Para isto, ele precisará ser Escravo na sua conexão com o processador e Mestre na sua conexão com a memória. Além disto, todas as solicitações do processador devem ser transferidas para a memória neste momento.

A forma mais simples de implementar este roteador é começar pelo código da memória, removendo a parte relacionada ao armazenamento de dados e incluindo a parte da conexão Mestre que você poderá seguir o exemplo do código do processador (procure pela declaração da classe mips1). você trabalhará sempre ao redor e na implementação do método transport.

É esperado, nesta atividade, que você localize e monte o código correto do seu roteador. Todos os exemplos de código fonte já estão dados e você já utilizou recentemente (exercício 3).

Meça o slowdown factor e coloque no seu relatório. Compare com o do exercicio 3. Como você colocou mais um componente no meio do caminho, você deve ter notado um aumento deste número. Anote também o número de instruções por segundo que o simulador consegue executar.

Parte 2 - Inclusão de um periférico básico

Uma visão geral da plataforma até agora: 3 componentes (processador, roteador e memória) que são códigos executados no seu computador (x86) e um programa que foi compilador para MIPS e está sendo executado (simulado) dentro da plataforma.

O próximo passo é criar um novo periférico para incluir na plataforma. Este periférico terá o código bem similar à memória, exceto pela parte do armazenamento, e será usado fortemente no seu trabalho. A especificação é bem simples: ele só armazena um valor de 32 bits (inicialmente zerado), toda leitura retorna o valor armazenado e muda o valor para 1. Toda escrita grava o valor solicitado. O funcionamento deste periférico permite simular uma instrução de load and increment, cuja principal finalidade é o controle de concorrência. Veja o exemplo: suponha dois processadores executando exatamente o mesmo código praticamente ao mesmo tempo:

volatile int *lock = (int *) ENDERECO_LOCK;

// Aguarda que o valor seja 0
while (*lock);
// Executa algo na região crítica
...
// Libera a região crítica
*lock = 0;

Um dos processadores chegará ao while primeiro e lerá o valor 0, passando diretamente para a região crítica. Neste momento, ele poderá executar o código que quiser sem que o outro processador atrapalhe. Enquanto isto, o outro processador estará executando o while (lembre-se que a primeira leitura do periférico retornará 0 e mudará o valor para 1). Após o primeiro processador encerrar a região crítica, ele pode liberar o lock, permitindo que o segundo entre na região crítica. Isto vale para quntos processadores forem necessários. Você pode, inclusive, definir uma macro AquireLock com o while e outra ReleaseLock com a última linha do exemplo.

Para incluir o periférico, inicie do código da memória, implemente o novo periférico, inclua uma nova porta mestre no roteador e altere o código do roteador para enviar leituras ao endereço do Lock para o Lock e as demais para a memória. Qual o endereço desejado do Lock? Olhando o código fonte da plataforma e do MIPS, nota-se que eles utilizam 5Mb de memória, então você pode utilizar a próxima palavra como endereço base.

Faça um programa que leia várias vezes do periférico e mostre na tela os valores lidos. Numa atividade futura você terá uma plataforma multicore e poderá testar o código como o acima. Meça novamente o slowdown factor e o número de instruções por segundo.

Parte 3 - Redefinição do endereço base do periférico

A forma mais simples de indicar o endereço de um periférico é através do roteador, que deve checar pelo valor do campo endereço e enviar para o periférico. O grande problema aqui é que cada periférico pode ter vários endereços alocados a ele. Veja o exemplo da memória que tem 5M endereços. Sempre que um periférico tem mais de um endereço, o seu endereço interno é sempre considerado como começando em zero. Assim, na atividade da parte anterior, o endereço 5M deve ser visto como endereço base e enviado como 0 para o Lock. Implemente isto na sua plataforma.

Trabalhar com endereços base diferentes e manter os endereços internos sempre iniciando em zero permite que você mova um periférico de um endereço para outro sem ter que se preocupar com mais nada, pois tudo continuará funcionando.

Meça novamente o slowdown factor e o número de instruções por segundo.

Parte 4 - Inclusão de um periférico mais elaborado

Agora é hora de utilizar um periférico para acelerar o desempenho da plataforma. Antes vamos definir um programa como métrica. Implemente um programa que realiza uma multiplicação de matrizes. Para facilitar, utilize matrizes quadradas, geradas com a seguinte propriedade: a[i,j] = i+j.

A primeira versão do seu programa deve trabalhar com números inteiros, defina um tamanho de matriz para que o código execute em torno de 5s. Agora transforme sua matriz de inteiro para double. Em quanto tempo seu programa executou? Esta diferença de tempo se deve ao fato do simulador utilizar uma biblioteca de emulação de números de ponto flutuante, não tendo uma implementação nativa. Por isto, agora você deve criar 1 periférico que realize a multiplicação de dois números do tipo double. Veja o que você precisa:

  1. Definir os parâmetros de entrada e saída. Uma multiplicação precisa de 2 entradas e 1 saída
  2. Definir um endereço para cada um destes parâmetros. Qual é o tamanho de um double?
  3. Definir um protocolo de comunicação com este periférico. Minha sugestão é utilizar o mais simples de todos: envie o primeiro número, depois o segundo, depois leia a resposta. A execução da funcionalidade do periférico pode ser feita no momento da solicitação da resposta.
Este periférico terá que tratar a diferença de endian entre a arquitetura local (x86) e o MIPS. Você não precisou se preocupar até agora com isto pois o próprio simulador resolve os problemas de endian de 32 bits mas um double é maior que isto. Como corrigir o problema?

Meça o tempo agora novamente e veja se seu periférico está mais rápido.

Crie um novo periférico com a funcionalidade de soma de dois números double e veja o quanto seu desempenho melhora.

Entrega

O código desta atividade terá que ser entregue. Envie o relatório pelo Susy e guarde o código na sua área até que eu solicite.
Enviar um relatório de apenas 1 página descrevendo a atividade realizada e os resultados obtidos.