Módulo 3: Exercícios — Conjunto de Instruções e Modos de Endereçamento

Estes três exercícios foram elaborados para que você aplique, de forma progressiva, os conceitos estudados no material do Módulo 3. Leia cada enunciado com atenção antes de começar a resolver — muitas respostas erradas surgem de uma leitura precipitada do enunciado, não de desconhecimento do conteúdo. Registre por escrito todos os seus raciocínios, mesmo os que parecerem óbvios: a prática de articular ideias técnicas em texto é uma competência fundamental para qualquer engenheiro de software.


Exercício 1 — Nível Básico

Decodificando Instruções e Identificando Modos de Endereçamento

Durante a primeira sessão de tutoria do Projeto Integrador, sua equipe analisa o código C que controla o display LCD Sunstar 2004A e percebe que o compilador MPLAB XC8 gerou assembly com instruções que você nunca havia visto de perto. O arquivo de listagem (.lst) produzido pelo compilador mostra instruções como MOVLW, MOVWF, LFSR, MOVF INDF0, ADDWF e MOVF PLUSW0, todas em sequência. Você decide, naquele momento, parar e entender exatamente o que cada instrução faz e como ela encontra seus dados.

Para responder às questões abaixo, consulte o material do Módulo 3 sempre que necessário e mostre seu raciocínio em cada etapa.

Parte A — Anatomia de instruções do PIC18F4550

Considere as seguintes instruções em assembly para o PIC18F4550. Para cada uma, identifique: (i) o nome da operação (o que ela faz em linguagem natural); (ii) quais são os operandos e o que cada um representa; (iii) qual é o modo de endereçamento utilizado para cada operando; e (iv) em quantos ciclos de clock a instrução executa, assumindo que não há desvio de fluxo.

As instruções a analisar são as seguintes:

MOVLW  0x4A
MOVWF  PORTD, ACCESS
MOVF   temperatura, W, ACCESS
LFSR   FSR0, leituras
MOVF   POSTINC0, W
MOVF   PLUSW0, W
GOTO   loop_principal
CALL   atualiza_display

Parte B — Formato binário de uma instrução

O material apresenta o formato binário de 16 bits da instrução ADDWF f, d, a do PIC18F4550:

  15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 | 0 | 1 | 0 | 0 | 1 | 1 | d | a | f | f | f | f | f | f | f |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

Dado que o registrador temperatura está alocado pelo compilador no endereço 0x20 do Access Bank, e considerando que o destino da operação deve ser o próprio registrador temperatura (não W), escreva o padrão completo de 16 bits da instrução ADDWF temperatura, F, ACCESS e converta o resultado para hexadecimal. Identifique explicitamente os valores dos bits d, a e do campo f, justificando cada escolha.

Parte C — ISA como contrato entre hardware e software

O material afirma que a ISA é o “contrato” entre hardware e software, e que a compatibilidade binária permite que programas compilados hoje executem em processadores futuros que respeitem a mesma ISA.

Considere o seguinte cenário: você compilou o firmware do Projeto Integrador para o PIC18F4550 e obteve um arquivo .hex. Um colega sugere que você poderia gravar esse mesmo arquivo .hex em um PIC18F2550 (microcontrolador da mesma família, com menos pinos). Explique, com base nos conceitos de ISA e implementação discutidos no material, se isso seria possível ou não em princípio. Em sua resposta, diferencie claramente o que é propriedade da ISA e o que é propriedade da implementação, e identifique qual aspecto seria determinante para a viabilidade do cenário descrito.

Parte D — CISC versus RISC: reconhecendo características

O PIC18F4550 e o processador x86 (presente no seu computador) são exemplos de arquiteturas com filosofias de projeto distintas. Com base no que você estudou no material, preencha a tabela abaixo indicando para cada característica se ela é típica de arquiteturas RISC, CISC ou de ambas. Em seguida, classifique o PIC18F4550 e justifique sua classificação com pelo menos duas evidências concretas extraídas do material.

Característica RISC CISC Ambas
Instruções de comprimento fixo (ou quase fixo)
Grande número de opcodes (centenas ou mais)
Execução da maioria das instruções em 1 ciclo
Muitos modos de endereçamento por instrução
Instruções que acessam memória e fazem aritmética simultaneamente
Instruções de comprimento variável (1 a 15 bytes)
Filosofia “deixe o compilador fazer o trabalho”

Exercício 2 — Nível Intermediário

Modos de Endereçamento em Profundidade: Análise e Tradução

O firmware do Projeto Integrador precisa implementar uma função que percorre o vetor de leituras do sensor ADC — armazenado em um array de 16 posições na RAM — e calcula o valor máximo. Ao mesmo tempo, outra parte do código acessa campos de uma estrutura que representa o estado atual do sistema (temperatura, umidade e modo de operação). Sua equipe decidiu estudar o código assembly gerado pelo compilador para entender por que certas versões em C são mais eficientes do que outras.

Parte A — Traduzindo C para modos de endereçamento

Considere o seguinte fragmento de código C para o PIC18F4550:

#include <xc.h>
#include <stdint.h>

uint8_t leituras[16];        /* vetor na RAM */
uint8_t indice;              /* variável global */
uint8_t maximo;              /* variável global */

void encontra_maximo(void) {
    uint8_t i;
    maximo = 0;                     /* Linha 1 */
    for (i = 0; i < 16; i++) {
        if (leituras[i] > maximo) { /* Linha 2 */
            maximo = leituras[i];   /* Linha 3 */
        }
    }
}

Para cada acesso a memória marcado pelas linhas comentadas, responda: (i) qual modo de endereçamento o compilador tende a usar para realizar esse acesso e por quê; (ii) qual instrução do PIC18F4550 seria gerada (ou qual família de instruções, usando os registradores FSR/INDF/PLUSW quando aplicável); e (iii) quantos ciclos de clock esse acesso consome. Você não precisa escrever o assembly completo — concentre-se nos acessos de memória marcados.

Em seguida, responda: por que o acesso leituras[i] com i variável gera código diferente do acesso leituras[3] com índice fixo? Explique a diferença em termos de modo de endereçamento e discuta o impacto no desempenho.

Parte B — Pilha de hardware: limites e implicações práticas

O material descreve que o PIC18F4550 possui uma pilha de hardware de 31 níveis, destinada exclusivamente ao armazenamento de endereços de retorno de sub-rotinas. Um colega de equipe propôs a seguinte estrutura para o firmware:

/* Proposta do colega para leitura encadeada de sensores */
void ler_sensor_temperatura(void);
void ler_sensor_umidade(void);
void processar_dados(void);
void atualizar_display(void);
void ciclo_principal(void);

void ciclo_principal(void) {
    ler_sensor_temperatura();      /* chamada 1 */
    ler_sensor_umidade();          /* chamada 2 */
    processar_dados();             /* chamada 3 */
    atualizar_display();           /* chamada 4 */
    ciclo_principal();             /* chamada 5: recursão! */
}

Analise essa estrutura do ponto de vista da arquitetura do PIC18F4550. Sua análise deve: (i) identificar o problema arquitetural presente na proposta; (ii) explicar com precisão o que acontece na pilha de hardware a cada chamada de ciclo_principal, indicando quantos níveis são consumidos por iteração e quando o estouro ocorre; (iii) propor uma solução correta para o problema usando os recursos da ISA do PIC18F4550 disponíveis no material; e (iv) justificar por que a solução proposta é arquiteturalmente adequada para sistemas embarcados em geral, não apenas para o PIC18F4550.

Parte C — Estimando custo de código

O material apresenta a fórmula do tempo de execução de um programa:

T_{exec} = N \times CPI \times T_{clock}

onde N é o número de instruções executadas, CPI é a média de ciclos por instrução e T_{clock} é o período do clock.

O PIC18F4550 do Kit ACEPIC PRO V8.2 opera com um cristal de 20 MHz mas utiliza internamente um multiplicador de PLL que eleva o clock para 48 MHz na USB. Para o core do processador, cada instrução consome 4 ciclos do oscilador primário, o que significa que o clock de instrução efetivo é de f_{osc}/4.

Assuma que o PIC18F4550 está configurado com oscilador de 8 MHz (clock de instrução de 2 MHz, período de 500 ns por ciclo de instrução).

Estime o tempo total de execução de cada uma das seguintes sequências de código. Para cada caso, conte o número de instruções executadas (N), estime o CPI médio com base nos tempos apresentados no material, calcule T_{exec} e determine quantas vezes por segundo a operação poderia ser repetida se fosse o único código em execução:

  1. Uma varredura completa do vetor leituras[16] para encontrar o máximo, assumindo que o laço executa 16 iterações com 5 instruções de 1 ciclo cada por iteração, mais 1 instrução de 2 ciclos de teste condicional por iteração.

  2. A função atualiza_display(), que envia 80 caracteres ao display LCD Sunstar 2004A. Cada caractere requer: 1 instrução MOVF (1 ciclo) para carregar o byte, 1 instrução de chamada à sub-rotina envia_byte (2 ciclos), e a sub-rotina envia_byte executa internamente 8 instruções de 1 ciclo e 1 RETURN (2 ciclos).

Com base nos valores calculados, responda: qual das duas operações é o gargalo do sistema se ambas precisarem ser executadas a cada atualização de tela? Justifique.


Exercício 3 — Nível Desafiador

ISA, Desempenho e Projeto de Código para Sistemas Embarcados

Este exercício integra todos os conceitos do Módulo 3 em um cenário de análise e projeto que você encontrará na prática do Projeto Integrador: compreender profundamente como as decisões de ISA afetam o desempenho do software, e como escrever código C que colabore com o compilador para gerar instruções eficientes.

Parte A — Análise comparativa de ISA: PIC18 e x86

O material apresenta o PIC18F4550 como arquitetura RISC e o x86 como CISC, mas afirma que os processadores x86 modernos implementam internamente um núcleo RISC. Essa dicotomia aparente merece análise cuidadosa.

Considere que você precisa implementar a seguinte operação: calcular a soma de todos os elementos de um array de 16 inteiros de 8 bits sem sinal, armazenando o resultado em um inteiro de 16 bits.

  1. Para a ISA do PIC18F4550, esboce a sequência de instruções assembly necessária (você não precisa escrever código completo — descreva os tipos de instruções e os modos de endereçamento envolvidos). Identifique quantas instruções aproximadamente seriam necessárias para inicializar o ponteiro, executar o laço de 16 iterações e armazenar o resultado. Use os símbolos das instruções do PIC18 apresentados no material.

  2. Para a ISA x86-64, a mesma operação poderia ser realizada com um número menor de instruções graças ao modo de endereçamento indexado com deslocamento e às instruções que combinam acesso à memória com aritmética. Explique, sem escrever código assembly x86, de que forma a arquitetura CISC do x86 permite reduzir o valor de N na fórmula de desempenho. Qual é a contrapartida dessa redução, em termos de complexidade do hardware de decodificação?

  3. O material afirma que os processadores x86 modernos decompõem internamente as instruções CISC em micro-operações RISC. Se isso é verdade, por que o x86 ainda mantém sua ISA CISC em vez de migrar para uma ISA RISC nativa? Formule uma resposta que considere o conceito de compatibilidade binária discutido no material.

Parte B — Projetando código C para gerar assembly eficiente

O firmware do Projeto Integrador precisa de uma função que copie N bytes de um buffer de recepção para um buffer de exibição, aplicando uma transformação simples: se o byte for menor que 0x20 (caractere de controle ASCII), ele deve ser substituído pelo espaço (0x20); caso contrário, é copiado sem alteração. Essa operação é comum em sistemas que recebem dados seriais e precisam exibir apenas caracteres imprimíveis.

Considere duas implementações em C:

/* Implementação A */
void sanitiza_buffer_A(uint8_t *entrada, uint8_t *saida, uint8_t n) {
    uint8_t i;
    for (i = 0; i < n; i++) {
        if (entrada[i] < 0x20) {
            saida[i] = 0x20;
        } else {
            saida[i] = entrada[i];
        }
    }
}

/* Implementação B */
void sanitiza_buffer_B(uint8_t *entrada, uint8_t *saida, uint8_t n) {
    uint8_t byte_lido;
    while (n--) {
        byte_lido = *entrada++;
        *saida++ = (byte_lido < 0x20) ? 0x20 : byte_lido;
    }
}

Para cada implementação, analise: (i) qual modo de endereçamento é utilizado para acessar entrada[i] / *entrada em cada iteração; (ii) se o compilador XC8 pode utilizar os registradores FSR com auto-incremento (POSTINC) para o acesso ao array, e em qual implementação isso é mais provável; (iii) quantas instruções adicionais são necessárias por iteração na Implementação A em comparação à B, especificamente para atualizar o índice i e calcular os endereços entrada[i] e saida[i]; e (iv) qual implementação você recomendaria para um sistema embarcado em que o tempo de execução é restrito e por quê.

Ao final, escreva um parágrafo sintetizando a relação entre o estilo de código C adotado pelo programador e os modos de endereçamento gerados pelo compilador no PIC18F4550. Seu parágrafo deve ser específico — cite nomes de instruções e modos de endereçamento do material.

Parte C — Especificação completa de uma rotina de despacho por tabela de salto

No Projeto Integrador, o sistema precisa responder a comandos recebidos via interface serial: cada byte recebido é um código de comando (0x00 a 0x07, total de 8 comandos possíveis) e o sistema deve chamar a função correspondente. Uma abordagem ingênua usaria uma cadeia de if-else com 8 comparações. A abordagem eficiente — e a que você deve especificar — é uma tabela de despacho (dispatch table ou jump table).

Uma tabela de despacho é um array de ponteiros para funções: o código de comando é usado como índice para acessar diretamente o endereço da função a ser chamada, eliminando a necessidade de comparações sequenciais.

Especifique completamente (sem escrever o código — apenas a especificação do comportamento, estrutura de dados e lógica) essa rotina para o PIC18F4550, respondendo às seguintes questões:

  1. Qual tipo de dado em C representa um ponteiro para uma função que não recebe parâmetros e não retorna valor? Descreva a estrutura do array de ponteiros e como ele seria declarado e inicializado com oito funções hipotéticas chamadas cmd_00 a cmd_07.

  2. Quando o sistema recebe um código de comando cmd (um uint8_t) e precisa chamar a função correspondente via tabela, qual sequência de operações de memória precisa ocorrer? Descreva em termos de modos de endereçamento do PIC18F4550: primeiro, como o endereço do elemento tabela[cmd] é calculado; segundo, como o conteúdo desse endereço (que é um endereço de função) é lido da memória de programa (Flash); e terceiro, como esse endereço é colocado no Program Counter para que a execução salte para a função correta. Identifique qual tipo especial de instrução do PIC18F4550 realiza esse salto indireto (o material menciona instruções de desvio que usam o registrador PCLAT e operações com a pilha).

  3. Identifique dois casos de borda que a rotina de despacho deve tratar explicitamente para que o firmware seja robusto. Para cada caso, descreva o que aconteceria se o caso de borda não fosse tratado e especifique o comportamento correto esperado. Lembre-se das limitações de pilha e dos modos de falha discutidos no material.

  4. Compare o custo computacional da tabela de despacho com o da cadeia de if-else para 8 comandos, usando a fórmula T_{exec} = N \times CPI \times T_{clock}. Assuma que a tabela de despacho requer aproximadamente 5 instruções de 1 ciclo para o acesso indexado mais 2 ciclos para o desvio, enquanto cada comparação no if-else requer 2 instruções (MOVLW + CPFSEQ) de 1 a 2 ciclos. Calcule o número esperado de instruções no caso médio de cada abordagem (assumindo comandos igualmente prováveis) e expresse a diferença em ciclos de clock.

Dica para o Exercício 3, Parte A: antes de contar instruções para o PIC18F4550, esboce o fluxograma do laço. Lembre-se de que o PIC18 precisa de instruções separadas para: carregar o ponteiro (LFSR), acessar o elemento (MOVF POSTINC), acumular o resultado (ADDWF), decrementar o contador e testar a condição de parada. Cada uma dessas etapas corresponde a uma instrução distinta — algo que arquiteturas CISC podem combinar em uma única instrução.

Dica para a Parte B: a chave para identificar qual implementação favorece o uso de POSTINC está em observar se o ponteiro é avançado de forma explícita e incremental (ptr++) ou se o índice é calculado a cada iteração (entrada[i]). O compilador XC8 reconhece o padrão de ponteiro com incremento pós-fixado como candidato natural para o modo POSTINC0.

Dica para a Parte C: para a tabela de despacho, o conceito central é que a Flash do PIC18F4550 armazena tanto instruções quanto dados constantes (como a própria tabela de endereços). Lembre-se de que endereços de funções no PIC18 têm 21 bits — o que determina o tamanho de cada entrada na tabela.

Entregue um arquivo TXT contendo as respostas

Somente 1 entrega por grupo!!!