Atenção: Todos os arquivos que você precisa para essa atividade estão disponíveis em /home/staff/rodolfo/mc723/download.
Em cada parte, leia todo o enunciado uma vez antes de executar suas atividades.
Uma visão geral da plataforma até agora: 3 componentes (processador, caches, roteador e memória) que são códigos executados no seu computador (x86) e um programa que foi compilado 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. Você deve ter notado algo estranho acontecendo, seus valores mudaram como esperado? O problema aqui é que a cache está guardando os dados da última leitura, cumprindo fielmente a funcionalidade que você espera dela.
Para sanar o problema da parte anterior, você deve alterar a codificação da sua cache, do exercício anterior, para apenas armazenar os valores se eles estiverem num determinado intervalo de endereço. Pelo exemplo de codificação anterior, você não pode armazenar valores que estiverem fora do limite de 5Mb iniciais da memória.
Este limite de endereçamento na cache é prática comum nos processadores quando se trata de endereçar periféricos.
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.
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:
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.
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.