Prezado leitor, daremos continuidade ao artigo anterior. Assim sendo, o presente artigo tem o intuito de discutir aspectos da organização de um processador multi-ciclo, de 16 bits, descrito em Verilog com um grupo (set) de 24 instruções e orientado a acumulador.
Antes de avançarmos diretamente sobre a organização é necessário estabelecer o conceito de Unidade de Controle (control) ou Bloco de Controle e o conceito de Caminho de Dados (datapath) ou Bloco Operacional. Segundo [1] podemos assumir de forma resumida que:
- Unidade de Controle (control) ou Bloco de Controle comanda o Caminho de Dados, memórias e outros dispositivos que possuem entrada e saída (I\Os);
- Caminho de dados realiza prioritariamente o armazenamento dos dados, operações (e.g. aritmética, multiplicação, rotação de bits, etc) e envolve tipos de dados não booleanos;
Já a Unidade de Controle, estamos falando agora de uma máquina de estados finitos ou FSM (Finite State Machine) que possui entradas, saídas, estados, ações executadas pelos estados e transições decorrentes da mudança desses estados. No decorrer deste artigo vamos retomar um pouco sobre esse assunto com exemplos concretos do nosso processador.
PENSANDO NA ORGANIZAÇÃO
Tendo em vista a arquitetura proposta no primeiro artigo, podemos começar estabelecendo os blocos funcionais de controle e caminho de dados e considerar que o nosso processador vai interagir com uma memória ROM e com uma RAM. A figura 1 ilustra os dois blocos funcionais do nosso projeto bem como a sua interface com o mundo externo. Na figura 1 é possível observar que os blocos funcionais control e datapath estão interligados às duas memórias e à interface do sistema através de barramentos de dados e fios/linha de dados, bem como a sinais de sistemas como o sinal de relógio e reset.
Em um projeto de sistema digital uma das primeiras etapas que deve ser prevista (e não menos importante) é a interface do sistema. A opinião do presente autor é que: “uma interface bem definida poupa muita aspirina”. Então caro leitor, uma interface bem documentada e bem escrita poupa muito tempo e dinheiro de projeto. Neste momento já temos uma amostra de como será o nosso processador.
INTERFACE DO PROCESSADOR
Como mencionado anteriormente vamos detalhar melhor a interface do nosso sistema. Estabelecemos, portanto uma tabela com as informações mais relevantes para o nosso leitor. Na tabela 1 descrevemos o nome do sinal (NOME), qual o bloco que o sinal é utilizado (INTERFACE), se o sinal é uma entrada ou saída de dados (I/O), o tamanho do sinal em bits (BITS) e descrição funcional do sinal no sistema (DESCRIÇÃO).
|
Tabela 1: Interface do Processador | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
NOME |
INTERFACE |
I/O |
BITS |
DESCRIÇÃO |
CLOCK_i |
sistema |
input |
1 |
clock do sistema |
RESET_n_i |
sistema |
input |
1 |
Reset assíncrono – ativo em zero |
ADDR_im_o |
mem.instr. |
output |
10 |
Endereço para memória de instrução |
DATA_im_i |
mem.instr. |
input |
16 |
Dados vindos da memória de instrução |
CEnable_im_o |
mem.instr. |
output |
1 |
Habilita memória de instrução – ativa em zero |
OEnable_im_o |
mem.instr. |
output |
1 |
Habilita saída de dados da memória de instrução – ativa em zero |
WEnable_dm_o |
mem.dados |
output |
1 |
Habilita escrita na memória de dados – ativa em zero |
CEnable_dm_o |
mem.dados |
output |
1 |
Habilita a Memória de dados – ativa em zero |
OEnable_dm_o |
mem.dados |
output |
1 |
Habilita saída de dados da memória de dados – ativa em zero |
ADDR_dm_o |
mem.dados |
output |
11 |
Endereço da memória de dados |
DATA_dm_i |
mem.dados |
input |
16 |
Dados vindos da memória de dados |
DATA_dm_o |
mem.dados |
output |
16 |
Dados para memória de dados | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
NOME |
INTERFACE |
I/O |
BITS |
DESCRIÇÃO | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
CLOCK_i |
sistema |
input |
1 |
clock do sistema | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
RESET_n_i |
sistema |
input |
1 |
Reset assíncrono – ativo em zero | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
ADDR_im_o |
mem.instr. |
output |
10 |
Endereço para memória de instrução | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
DATA_im_i |
mem.instr. |
input |
16 |
Dados vindos da memória de instrução | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
CEnable_im_o |
mem.instr. |
output |
1 |
Habilita memória de instrução – ativa em zero | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
OEnable_im_o |
mem.instr. |
output |
1 |
Habilita saída de dados da memória de instrução – ativa em zero | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
WEnable_dm_o |
mem.dados |
output |
1 |
Habilita escrita na memória de dados – ativa em zero | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
CEnable_dm_o |
mem.dados |
output |
1 |
Habilita a Memória de dados – ativa em zero | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
OEnable_dm_o |
mem.dados |
output |
1 |
Habilita saída de dados da memória de dados – ativa em zero | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
ADDR_dm_o |
mem.dados |
output |
11 |
Endereço da memória de dados | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
DATA_dm_i |
mem.dados |
input |
16 |
Dados vindos da memória de dados | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
DATA_dm_o |
mem.dados |
output |
16 |
Dados para memória de dados |
Vale ressaltar que sempre que precisarmos de mais detalhes sobre o funcionamento de qualquer (e.g. memórias, RTCs, barramentos) sinal de uma interface externa podemos obtê-los através da folha de dados (datasheet) ou através de diagramas descritos por especialistas. Com relação às memória, neste artigo vamos optar em descrever graficamente os seus funcionamentos, tendo em vista que o protocolo utilizado tenta ser o mais genérico possível sem perder o foco em manter a similaridade com o que existe na indústria.
BLOCO DE CONTROLE ou UNIDADE DE CONTROLE
Amigo leitor, vamos focar agora em entender o bloco de controle. Neste momento já conseguimos vislumbrar mais detalhes das unidades funcionais do nosso projeto. Percebemos a necessidade de um bloco de somador figura 2 (a), um multiplexador figura 2 (b), uma forma de armazenarmos o endereço da instrução corrente (contador de programa ou PC figura 2 (c) ) e por fim precisamos do bloco de decodificação figura 2 (d). Para o leitor não achar que magicamente deduzimos isso fica a dica em observar como processador BIP [2] foi pensado, cuja arquitetura e organização nos inspiramos para esse artigo.
Ainda discutindo a figura 2, dois conjuntos de blocos precisam necessariamente ser descritos com mais detalhes. O conjunto que possui o bloco somador, multiplexador e PC e o bloco de decodificação. O nosso somador é responsável por incrementar o endereço da memória de programa. Este somador incrementa sempre em um bit (1 bit) o valor do nosso registro de 10 bits, ou seja, ele incrementa somando 1 até o registro atingir o fim das 1024 posições da memória (e.g. é importante observar que a arquitetura proposta tem o fim didático, portanto difere de arquiteturas que incrementam em um nibble o PC). Além disso, o somador não utiliza o transporte de bit (carry) e caso chegue ao fim do tamanho do registro ele reinicia com o valor zero (fica a dica ao leitor tratar esse overflow com mais propriedade). Seguindo o fluxo, o resultado do somador é então enviado de acordo com a entrada de seleção do multiplexador para o PC que por sua vez registra o próximo endereço da memória de instrução.
Já o bloco de decodificação nos é apresentado o conceito muito importante para um projetista de hardware, o de máquina de estados finitos. Vamos abordar com mais detalhes esse conceito a seguir. Para o nosso projeto vamos utilizar uma máquina de estados baseada em Mealy devido a algumas características interessantes dessa abordagem. Cabe, portanto ao nosso bloco de decodificação controlar o ritmo do nosso processador, controlando todos os sinais do bloco de Caminho de Dados.
MÁQUINA DE ESTADOS FINITOS – FSM (Finite State Machine)
Segundo [1] uma FSM é definida como um conjunto de estados que representa todos os estados, ou modos, possíveis de um sistema. Assim, uma FSM representa uma sequência de padrões de estados definidos em uma ordem pré-determinada pelo projetista de hardware. Existem dois modelos muito consolidados de máquinas estados, são as máquinas de estados de Moore e Mealy, vamos elaborar melhor a ideia a seguir.
MÁQUINA DE ESTADOS DE MOORE
Para [1] um circuito sequencial é chamado de máquina de estados de Moore quando são em função somente da saída.
Uma alternativa para uma FSM Moore é uso de da FSM Mealy.
MÁQUINA DE ESTADOS DE MEALY
Um circuito sequencial é chamado de máquina de estados de Mealy quando a saída deste circuito além de depender do estado corrente ela também depende da entradas do circuito [1].
MÁQUINA DE ESTADOS DO PROCESSADOR
Tendo em vista o que foi exposto, vamos agora definir qual será a “cara” da máquina de estados do nosso processador. Como mencionado anteriormente, o nosso processador deve obrigatoriamente executar uma sequência de operações armazenada na memória de programa. Assim, a Figura 5 ilustra essa sequência de estados de busca da instrução na memória de programa, decodificação dessa informação e execução da referida instrução conforme o opcode.
No estado de BUSCA iremos interagir com sinais da memória de instrução de forma a buscar a instrução conforme o endereço registrado no PC. O estado DECODIFICA tem o objetivo de “separar” o campo OPCODE e o campo OPERANDO, ambos oriundos do barramento de dados da memória de instrução – DATA_im_i. Em posse de todas essas informações a nossa máquina de estados vai agora para o estado EXECUTA, que por sua vez tem o objetivo de executar a operação do referido OPCODE. Por fim a máquina de estados retorna para o estado de BUSCA, repetindo todo o ciclo novamente.
Aconselho ao amigo leitor a dar uma boa olhada em [3]. Este é um excelente trabalho que utilizei como referência para esse projeto. Neste trabalho, existe uma tabela na página 30 que mostra uma forma de documentar os sinais que serão trabalhados na parte 3 deste artigo e o estado de decodificação.
COMPARATIVO ENTRE MOORE E MEALY
Segundo [1] em uma máquina de estados de alto nível dizemos que Moore tem suas ações associadas apenas aos estados, ao passo que Mealy tem suas ações associadas a transições.
Moore também se caracteriza pela ausência de caminho combinacional entre a entrada e saída e por ser propensos a ter um caminho crítico mais curto. Já Mealy podemos fazer uma lista: (i) menor área* quando comparado a Moore, (ii) a saída computada automaticamente assim que ocorre um mudança nas entradas, (iii) Mealy é mais fácil de combinar circuitos maiores, (iv) a resposta de uma FSM Mealy é mais rápido que uma FSM Moore, entre outras características. Convido o leitor listar nos comentários deste artigo mais características entre as duas máquinas, fique à vontade. A figura 6 é uma boa forma de ilustrar os conceitos mencionados acima.
Que os deuses da computação me perdoem, mas vou passar uma dica simples para lembrar a diferença entre as duas máquias (agradeço ao professor Daniel de BD02 – programa CI-brasil). Para lembrar de alguns conceitos entre as duas máquinas de estados basta associar Moore a uma vaca (vacas produzem mugidos, logo podemos associar ao som do mugido ao nome MOOre) e vacas ficam paradas. Já a máquina de estados de Mealy podemos associar a uma ovelha (ovelhas são mais lépidas e ágeis). Assim, vacas são lentas e ficam mais paradas, o que nos leva a deduzir que a resposta de uma máquina de Moore é mais lenta ou parada quando comparada a Mealy, que por sua vez é mais saltitante. Já uma ovelha por ser menor é fisiologicamente mais complexa que uma vaca (eu pelo menos acho isso) teremos que inserir mais um sinal, no caso a saída também depende do sinal de entrada (não me pergunte como cheguei a essa associação).
INTERFACE DO BLOCO DE CONTROLE
A tabela 2 descreve as informações mais importantes do nosso bloco de controle.
|
Tabela 2: Interface do bloco de controle do processador | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
NOME |
INTERFACE |
I/O |
BITS |
DESCRIÇÃO |
CLOCK_i |
Sistema |
input |
1 |
clock do sistema |
RESET_n_i |
Sistema |
input |
1 |
Reset assíncrono – ativo em zero |
HOLD_n_i |
Sistema |
input |
1 |
Mantém o estado do processador – ativo em zero |
ADDR_im_o |
Mem.instr. |
output |
10 |
Endereço para memória de instrução |
DATA_im_i |
Mem.instr. |
input |
16 |
Dados vindos da memória de instrução |
CEnable_im_o |
Mem.instr. |
output |
1 |
Habilita memória de instrução – ativa em zero |
OEnable_im_o |
Mem.instr. |
output |
1 |
Habilita saída de dados da memória de instrução – ativa em zero |
WEnable_dm_o |
Mem.dados |
output |
1 |
Habilita escrita na memória de dados – ativa em zero |
CEnable_dm_o |
Mem.dados |
output |
1 |
Habilita a Memória de dados – ativa em zero |
OEnable_dm_o |
Mem.dados |
output |
1 |
Habilita saída de dados da memória de dados – ativa em zero |
Operand_o |
Controle |
output |
11 |
Operando para mem.dados ou extensão de dados |
Ext2PC_i |
Datapath |
input |
16 |
Extensão de dados para o PC |
N_i |
Datapath |
input |
1 |
Sinal indicando resultado negativo da ULA |
Z_i |
Datapath |
input |
1 |
Sinal indicando resultado zero da ULA |
Op_o |
Controle |
output |
1 |
Operação ULA: 1 – adição/0 – subtração |
WrAcc_o |
Controle |
output |
1 |
Habilita a saída do registro Acumulador |
SelB_o |
Controle |
output |
1 |
Seleciona saída do mux2x1 |
SelA_o |
Controle |
output |
3 |
Seleciona saída do mux3x1 |
branch |
Controle |
internal |
1 |
Seleciona saída do mux2x1 de acordo com a operação de branch |
WrPC |
Controle |
internal |
1 |
Habilita a saída do registro Contador de Programa |
opcode |
Controle |
internal |
5 |
Código da operação | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
NOME |
INTERFACE |
I/O |
BITS |
DESCRIÇÃO | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
CLOCK_i |
Sistema |
input |
1 |
clock do sistema | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
RESET_n_i |
Sistema |
input |
1 |
Reset assíncrono – ativo em zero | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
HOLD_n_i |
Sistema |
input |
1 |
Mantém o estado do processador – ativo em zero | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
ADDR_im_o |
Mem.instr. |
output |
10 |
Endereço para memória de instrução | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
DATA_im_i |
Mem.instr. |
input |
16 |
Dados vindos da memória de instrução | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
CEnable_im_o |
Mem.instr. |
output |
1 |
Habilita memória de instrução – ativa em zero | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
OEnable_im_o |
Mem.instr. |
output |
1 |
Habilita saída de dados da memória de instrução – ativa em zero | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
WEnable_dm_o |
Mem.dados |
output |
1 |
Habilita escrita na memória de dados – ativa em zero | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
CEnable_dm_o |
Mem.dados |
output |
1 |
Habilita a Memória de dados – ativa em zero | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
OEnable_dm_o |
Mem.dados |
output |
1 |
Habilita saída de dados da memória de dados – ativa em zero | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Operand_o |
Controle |
output |
11 |
Operando para mem.dados ou extensão de dados | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Ext2PC_i |
Datapath |
input |
16 |
Extensão de dados para o PC | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
N_i |
Datapath |
input |
1 |
Sinal indicando resultado negativo da ULA | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Z_i |
Datapath |
input |
1 |
Sinal indicando resultado zero da ULA | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Op_o |
Controle |
output |
1 |
Operação ULA: 1 – adição/0 – subtração | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
WrAcc_o |
Controle |
output |
1 |
Habilita a saída do registro Acumulador | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
SelB_o |
Controle |
output |
1 |
Seleciona saída do mux2x1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
SelA_o |
Controle |
output |
3 |
Seleciona saída do mux3x1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
branch |
Controle |
internal |
1 |
Seleciona saída do mux2x1 de acordo com a operação de branch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
WrPC |
Controle |
internal |
1 |
Habilita a saída do registro Contador de Programa | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
opcode |
Controle |
internal |
5 |
Código da operação |
Vale a pena descrever um pouco mais alguns sinais são que internos descritos na tabela 2. O barramento de cinco bits opcode corresponde aos cinco bits mais significativos oriundos do barramento DATA_im_i, ou seja, o dado oriundo da memória de dados. Já o sinal branch tem por objetivo selecionar a saída do multiplexador 2×1 caso a instrução decodificada seja de desvio (e.g. JMP, BEQ, etc). Por fim o sinal WrPC tem por objetivo habilitar a saída do contador de programa (PC) após o fim de um ciclo do estágio e execução da máquina de estados.
DATAPATH ou BLOCO OPERACIONAL
Prezado leitor, antes de avançarmos sobre a nossa proposta de datapath, vamos esclarecer mais o conceito de processador de propósitos gerais. Segundo [1] um processador de propósito gerais difere de um processador de propósito único por armazenar a tarefa de processamento em uma memória ao invés de ser construída no próprio circuito. Neste sentido devemos considerar que em todo o processamento os dados devem ser carregados, transformados e por fim armazenados conforme a operação executada. Nesse sentido um processador programável deve ter a capacidade de alimentar um bloco operacional para transformar esses dados recuperados da memória de programa. Tudo isto se dá utilizando circuitos básicos dentro do datapath utilizando uma ULA e um registrador de armazenamento dos resultados executados pela ULA e outros blocos funcionais. A figura 7 ilustra como foi organizado o datapath do nosso processador.
Descrevendo melhor a Figura 7 podemos observar alguns blocos muito interessantes. O bloco EXTENSION figura 7 (c) tem o intuito de preencher os 5 bits mais significativos do campo operando (Operando_i) com bits 1. Já os blocos de multiplexação figura 7 (b) e (c) atuam conforme o opcode que chega no bloco de controle (na parte 3 deste artigo será mais fácil visualizar como isso ocorre). O bloco ACC figura 7 (d) corresponde ao nosso bloco de acumulação do resultado da ULA ou da oriunda informação da memória de dados. O bloco ACC possui 16 bits (dois octetos), armazena temporariamente a informação e sua saída é ligada diretamente em uma das entradas da ULA. Para o leitor que curte acompanhar um pouco sobre a familia Intel x86, vai lembrar que em 1979 o primeiro processador Intel 8086 também possui um acumulador de 16 bits o registro AX, vale a pena dar uma olhada na folha de dados [4].
Agora vamos focar um pouco nas operações da ULA – Unidade Lógica Aritimética. A ULA da figura 7 (e) necessariamente recebe dois dados de 16 bits e transforma essa informação em outro resultado de 16 bits conforme a operação selecionada. As operações trabalhadas no nosso projeto são: (i) soma, (ii) subtração, (iii) negação, (iv) (v) lógica and, (vi) lógica ou, (vii) lógica ou-exclusivo, (viii) rotação para a esquerda e (ix) rotação para a direita. A figura 8 ilustra a organização escolhida para a ULA.
Na figura 8 é possível observar as operações típicas da ULA, sendo a saída de cada operação dependente necessariamente do valor do sinal Sel_i.
INTERFACE DO BLOCO DE CONTROLE
A tabela 3 descreve as informações mais importantes do nosso bloco de caminho de dados.
|
Tabela 3: Interface do bloco de caminho de dados | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
NOME |
INTERFACE |
I/O |
BITS |
DESCRIÇÃO |
CLOCK_i |
Sistema |
input |
1 |
clock do sistema |
RESET_n_i |
Sistema |
input |
1 |
Reset assíncrono – ativo em zero |
ADDR_dm_i |
Mem.dados. |
input |
11 |
Endereço para memória de dados |
DATA_dm_i |
Mem.dados. |
input |
16 |
Dados vindos da memória de instrução |
DATA_dm_o |
Mem.dados |
output |
16 |
Dados enviados para a memória de dados |
Ext2PC_o |
Datapath |
output |
16 |
Envia operando ao bloco de controle |
Op_i |
Controle |
input |
1 |
Seleciona o modo de operação da ULA |
WrAcc_i |
Controle |
input |
1 |
Habilita a saída do registro Acumulador |
SelA_i |
Controle |
input |
2 |
Seleciona qual a saída do multiplexador 3×1 |
SelB_i |
Controle |
input |
1 |
Seleciona qual a saída do multiplexador 2×1 |
N_o |
Datapath |
output |
1 |
Flag de número negativo ou não do resultado da ULA |
Z_o |
Datapath |
output |
1 |
Flag de zero ou não do resultado da ULA |
waluout2mux3x1 |
Datapath |
interno |
16 |
Conecta ULA com MUX3x1 |
wdata2mux3x1 |
Datapath |
interno |
16 |
Conecta o barramento DATA_dm_i com MUX3x1 |
woperand |
Datapath |
interno |
16 |
Conecta o sinal Operand_i com ADDR_dm_i e o bloco de extensão de sinal |
waccout |
Datapath |
interno |
16 |
Conecta a saída do acumulador com o barramento DATA_dm_o |
wmux2x1alu |
Datapath |
interno |
16 |
Conecta a saída do MUX2x1 com a ULA | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
NOME |
INTERFACE |
I/O |
BITS |
DESCRIÇÃO | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
CLOCK_i |
Sistema |
input |
1 |
clock do sistema | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
RESET_n_i |
Sistema |
input |
1 |
Reset assíncrono – ativo em zero | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
ADDR_dm_i |
Mem.dados. |
input |
11 |
Endereço para memória de dados | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
DATA_dm_i |
Mem.dados. |
input |
16 |
Dados vindos da memória de instrução | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
DATA_dm_o |
Mem.dados |
output |
16 |
Dados enviados para a memória de dados | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Ext2PC_o |
Datapath |
output |
16 |
Envia operando ao bloco de controle | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Op_i |
Controle |
input |
1 |
Seleciona o modo de operação da ULA | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
WrAcc_i |
Controle |
input |
1 |
Habilita a saída do registro Acumulador | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
SelA_i |
Controle |
input |
2 |
Seleciona qual a saída do multiplexador 3×1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
SelB_i |
Controle |
input |
1 |
Seleciona qual a saída do multiplexador 2×1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
N_o |
Datapath |
output |
1 |
Flag de número negativo ou não do resultado da ULA | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Z_o |
Datapath |
output |
1 |
Flag de zero ou não do resultado da ULA | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
waluout2mux3x1 |
Datapath |
interno |
16 |
Conecta ULA com MUX3x1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
wdata2mux3x1 |
Datapath |
interno |
16 |
Conecta o barramento DATA_dm_i com MUX3x1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
woperand |
Datapath |
interno |
16 |
Conecta o sinal Operand_i com ADDR_dm_i e o bloco de extensão de sinal | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
waccout |
Datapath |
interno |
16 |
Conecta a saída do acumulador com o barramento DATA_dm_o | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
wmux2x1alu |
Datapath |
interno |
16 |
Conecta a saída do MUX2x1 com a ULA |
Prezado leitor, como você pode perceber foi estipulado um padrão na nomenclatura dos sinais internos, portas de entrada e saída para este projeto. Os sinais internos são em letra minúsculas, iniciam com a letra w, seguido do nome do bloco de origem para (número 2) o destino, por exemplo: wdata2mux3x1, um sinal interno (w) que liga dados para o MUX3x1. Os sinais de entrada e saída começam com o nome do sinal em letra maiúscula, seguido do caracter unde-line e o nome do bloco em minúsculo e por fim a letra i (input) ou o (output), por exemplo: DATA_dm_i, barramento de dados(DATA) do bloco memória de dados (dm) e por fim é um sinal de entrada (i).
Cada equipe de projeto, empresa e universidade utiliza um padrão específico. Contudo acredito que a melhor referência serão sempre os padrões IEEE, como Verilog-95, Verilog 2001 e 2005. Em média são 800 páginas de bons conhecimentos, vale a pena pesquisar no google sobre o assunto (sugiro a seguinte pesquisa: IEEE 1364 filetype: pdf ) os resultados são interessantes. Na próxima publicação vamos trabalhar um pouco mais sobre as linguagens de descrição de hardware.
INTERFACE DE MEMÓRIAS
As memórias são um caso a parte durante a fase da especificação projeto. Os requisitos de projeto devem estar bem definidos, bem como o escopo do projeto. Existem engenheiros especializados no assunto (o que não é o caso do presente autor, por enquanto) e levaram anos para conseguir um conhecimento sólido no assunto, portanto iremos abordar pouco, quando comparado ao tamanho e relevância do assunto.
No contexto deste projeto, necessitamos de uma memória onde as tarefas de processamento (firmware**) devem ficar armazenadas mesmo após o completo desligamento do sistema, portanto necessitamos de uma memória de somente leitura – ROM (Read-Only Memory). O mesmo pode ser aplicado às informações já processadas pelo nosso sistema. Essas informações precisam ser armazenadas ou lidas novamente em determinadas situações em determinadas áreas de memória, são portanto armazenadas em uma memória de acesso randômico – RAM. Explicando de forma resumida, os dados podem ser armazenados (escrita do dado) e recuperados (leitura do dado) em qualquer endereço da RAM.
Os protocolo dessas memórias são baseados em diagramas de memórias de uma fabricante de semi-condutores (fica a dica para o leitor procurar mais detalhes sobre a XFAB, Samsung, TSMC , UMC, entre outras). Como existem restrições jurídicas para reprodução dessas informações, as figuras a seguir foram então adaptadas para esse artigo. Devemos dar o mérito do conhecimento sobre o assunto ao programa CI-Brasil em especial aos instrutores e ao corpo administrativo que me toleraram por um ano.
ROM
A figura 9 ilustra o comportamento necessário para a leitura das informações armazenadas na memória ROM do nosso sistema.
Vamos agora descrever um pouco o comportamento dessa memória. Podemos observar que assim que a nossa memória é habilitada através do sinal CEnable_im_o em nível baixo (ou seja, o sinal se desloca para nível lógico zero) o endereço é escrito no barramento ADDR_im_o (leitor lembre que o endereço é a informação armazenada do nosso PC) e na subida seguinte do ciclo de relógio é a vez do sinal OEnable_im_o se deslocar para o nível zero. Por fim no ciclo seguinte de relógio o dado requisitado é disponibilizado no barramento DATA_im_i.
RAM
A figura 10 ilustra o comportamento necessário para a leitura das informações armazenadas na memória RAM do nosso sistema. É possível observar que ela se assemelha ao comportamento da leitura da memória ROM (figura 9).
Em se tratando da escrita na memória RAM temos algumas particularidades. As informações de endereço e os dados a serem escritos devem estar disponíveis nos respectivos barramentos ADDR_dm_o e DATA_dm_o antes que o sinal WEnable_dm_o transite do nível um para o zero. Vale observar que enquanto o sinal WEnable_dm_o estiver em nível alto, ambos os sinais podem transitar sem prejuízo de escrita indevida na memória.
Em nenhum momento foi discutido o aspecto de timming do projeto bem como as quantidades de ciclos que necessariamente uma memória pode requerer, tanto para escrita quanto para a leitura. Lembrando novamente que o projeto proposto é didático e muito do mundo real foi suprimido para um melhor entendimento e leitura do artigo. Nesse sentido convido ao leitor desbravar sobre o assunto e nos ajudar com bons comentários.
PRÓXIMA PUBLICAÇÃO
A próxima publicação (Implementação em VERILOG e simulação utilizando MODELSIM – Parte 3) iremos dar continuidade a este artigo. Iremos elaborar mais a ideia de como codificar em VERILOG, os aspectos peculiares da linguagem e por fim como produzir uma simulação da arquitetura proposta até agora. Agradeço o tempo que o amigo leitor tem dedicado a este artigo e espero que a leitura esteja suave e útil, afinal de contas, se não é útil para alguém então não tem necessidade de ser escrito.
*Mealy pode resultar em menos estados quando comparado com Moore, o que nos leva a concluir que menos lógica pode ser inserida no projeto.














Realmente muito bom o artigo, parabéns pelo trabalho.
Obrigado Willian