Skip to content

Contador de Programa

Diogo Valadares Reis dos Santos edited this page Aug 26, 2025 · 1 revision

[English]

[← Página Anterior | Próxima Página →]

O Contador de Programa

O Contador de Programa (PC) é o componente responsável por armazenar o endereço de memória da instrução atual e da próxima. Ele também lida com saltos, endereços de carga/armazenamento e substituições em nível de sistema.

Componente PC no Logisim

O PC recebe todas as suas entradas regulares pela esquerda e envia suas saídas pela direita. A parte superior contém a interface CSR, que fornece substituições para certos sinais de controle e envia ao Controlador de CSR informações importantes sobre o PC.

As Saídas

O PC possui quatro saídas de uso geral. A primeira é o Endereço de Saída, que se conecta diretamente aos Dispositivos de E/S por meio do Barramento de Endereço. Essa saída pode conter valores para diferentes funções dependendo do contexto. Durante o processo de busca de instrução, ela fornece o endereço da próxima instrução. Durante instruções de carga e armazenamento, ela encaminha um endereço calculado internamente a partir de suas entradas. Em caso de carga de sistema, um endereço vindo do Controlador de CSR é utilizado.

Depois, há duas saídas, uma para cada registrador do PC. A "próxima" é usada para armazenar o endereço após uma instrução JAL ou JALR, enquanto a "atual" é usada para a instrução AUIPC ou para algumas operações do Controlador de CSR.

Por fim, há o deslocamento de byte, que indica o byte inicial dos dados em operações de carga. Isso é um resquício da arquitetura RISC I, onde a instrução de carga coleta a palavra inteira da memória, mesmo que a instrução exija apenas um valor de byte ou short. Se o byte estiver no início da palavra, o deslocamento é 0; caso contrário, pode haver deslocamento de até 3 bytes para cargas de byte e até 2 bytes para cargas de short. Esse comportamento será explicado com mais detalhes no Capítulo do Buffer de Entrada.

Dentro do Contador de Programa

Circuito Interno do PC

A imagem acima mostra o circuito interno do Contador de Programa. O circuito foi dividido em três partes.

A primeira parte é responsável pelos cálculos de endereço usados em saltos, desvios e cargas. Um pequeno somador é usado para somar o PC atual ou o valor do Barramento A ao valor imediato. Se o Controlador de CSR estiver presente, esse resultado é transmitido a ele para que endereços desalinhados possam ser tratados corretamente.

Ter o Controlador de CSR também ativa a segunda parte do circuito (destacada em vermelho). Essa seção fornece controle adicional para saltos e cargas iniciadas pelo Controlador de CSR. Se o Controlador de CSR não estiver presente, essa parte pode ser ignorada ou até removida completamente — embora alguns sinais possam precisar ser reconectados.

A parte superior do circuito restante contém os registradores próximo e atual. O registrador próximo é implementado como um contador, o que significa que possui sinais de entrada extras que permitem incrementar automaticamente quando o sinal de escrita está ativo e nenhum salto está sendo executado. Para realizar saltos, o próximo PC recebe o endereço de destino do salto a partir do resultado calculado (ou do endereço de destino do sistema, se o Controlador de CSR estiver presente).

Note que, como as instruções no DRISC-V são sempre alinhadas por palavra (4 bytes), os dois bits menos significativos do contador são omitidos. No entanto, durante instruções de carga/armazenamento (sinalizadas pelo sinal Forward Address), esses dois bits são armazenados em um registrador especial chamado Deslocamento de Byte, que se conecta à saída correspondente mencionada anteriormente.

Um dos sinais visíveis no meio do circuito é Usar Deslocamento. Ele é ativado durante instruções de armazenamento porque, ao contrário das cargas, operações de armazenamento envolvendo dados menores que uma palavra não são necessariamente alinhadas por palavra. Nesses casos, o endereço completo calculado é encaminhado ao barramento de endereço.

Implementação em SystemVerilog

timescale 1s/1s
//Entradas e Saídas
module program_counter(
    input reset,
    input clock,
    input write,
    input jump,
    input pc_relative,
    input use_offset,
    input forward_address,
    input [31:0] immediate,
    input [31:0] address_in,
    input system_jump,
    input system_load,
    input [31:0] system_address_target,
    output reg [1:0] data_offset,
    output [31:0] calculated_address,
    output [31:0] next,
    output [31:0] current,
    output [31:0] address_bus
    );

//Registradores
    reg [29:0] next_reg;
    reg [29:0] current_reg;

//Parte 1 do circuito
    assign calculated_address = pc_relative ? current + immediate : address_in + immediate;

//Força a saída para 32 bits
    assign next = {next_reg, 2'b00};
    assign current = {current_reg, 2'b00};

//Seleção da saída para o barramento de endereço
    assign address_bus = forward_address ?
                        (system_load ? system_address_target :
                         use_offset ? calculated_address : {calculated_address[31:2], 2'b00}) :
                        {next_reg, 2'b00};

    always@(posedge clock) begin
        if(reset) begin
            current_reg <= 30'b0;
            next_reg <= 30'b0;
            data_offset <= 2'b0;
        end

//Aqui está o comportamento de atualização dos registradores
        else if(write) begin
            if(system_jump) next_reg <= system_address_target[31:2];
            else if(jump) next_reg <= calculated_address[31:2];
            else next_reg <= next_reg + 1;

            current_reg <= next_reg;
        end

        if(forward_address) begin
            data_offset <= calculated_address[1:0];
        end
    end

endmodule

Clone this wiki locally