Você está na página 1de 38

Universidade de São Paulo

Instituto de Ciências Matemáticas de São Carlos


Departamento de Sistemas de Computação – SSC
SSC-114 Arquitetura de Computadores

CPU 4 bits com Pipeline


André Márcio de Lima Curvello
Fernando Pasquini Santos
Leonardo Almeida Bonetti

São Carlos, Junho de 2010


2

André Márcio de Lima Curvello


Fernando Pasquini Santos
Leonardo Almeida Bonetti

Projeto de CPU 4Bits com Pipeline

Projeto de uma CPU com arquitetura de 4bits,


executando programas sob um pipeline de cinco estágios,
implementado em VHDL pelo software Altera Quartus II.

Prof. Responsável:
Jorge Luiz e Silva

São Carlos, Junho de 2010


3

"Computadores são ferramentas magníficas para a


realização de nossos sonhos, mas nenhuma máquina pode
substituir a faísca do espírito humano, compaixão, amor, e
compreensão." (Louis Gerstner)
4

Este projeto visa a implementação de um processador 4 bits com todas as suas funcionalidades e a presença de um
pipeline com cinco estágios (fetch, decode, fetchdata, exec e store) utilizando a linguagem de descrição de hardware
VHDL e o software Altera Quartus II. Para tanto, informações gerais como conjunto de instruções escolhidas e os
estágios do pipeline serão apresentados. As instruções serão poucas, de modo que possibilitem apenas a construção
de programas simples para observarmos a execução nesta arquitetura. Em seguida, cada estrutura responsável pelo
funcionamento da CPU será apresentada juntamente com seu código implementado. Para validação do projeto como
um processador funcional, serão apresentados testes realizados no software junto aos valores esperados para cada
entrada, dados programas simples na memória de programa.

Sumário
1. Introdução ................................................................................................................................................................. 6
1.1 CPU 4 bits .......................................................................................................................................................... 6
1.2 Pipeline.............................................................................................................................................................. 6
1.3 Organização do Texto e Outros Detalhes ......................................................................................................... 6
2. Características da Arquitetura .................................................................................................................................. 6
2.1 Características Gerais ........................................................................................................................................ 6
2.2 Instruções e Formato ........................................................................................................................................ 7
2.3 Pipeline.............................................................................................................................................................. 8
2.4 Problemas e Cuidados Especiais na Construção dos Programas ...................................................................... 9
3. Implementação ....................................................................................................................................................... 11
3.1 Memória de Progama, Memória de Dados e Banco de Registradores ........................................................... 11
3.2 Unidade fetch .............................................................................................................................................. 13
3.3 Unidade decode............................................................................................................................................ 14
3.4 Unidade fetchData .................................................................................................................................... 15
3.5 Unidade exec ................................................................................................................................................ 16
3.6 Unidade store.................................................................................................................................................. 17
3.7 Diagrama .bdf Completo e Código VHDL Correspondente ............................................................................. 19
4. Simulações .............................................................................................................................................................. 25
4.1 Memórias e Registradores .............................................................................................................................. 25
4.2 Busca de Instrução e Decodificação................................................................................................................ 26
4.3 Busca de Dados ............................................................................................................................................... 27
4.4 Operações na ULA ........................................................................................................................................... 29
4.5 Armazenamento ............................................................................................................................................. 31
4.6 Jumps e Condicionais ...................................................................................................................................... 35
5. Discussão e Conclusão ............................................................................................................................................ 37
Bibliografia ...................................................................................................................................................................... 38
5

Figura 1 – Problema no pipeline quando duas instruções querem ler e escrever na mesma posição de memória. As
instruções estão em sequência e são representadas pelos números 000, 001, etc. ....................................................... 9
Figura 2 – Solução para o problema descrito na Figura 1. .............................................................................................. 10
Figura 3 – Problema da leitura de valores antes da modificação real ............................................................................ 10
Figura 4 – Trecho do arquivo .mif para o conteúdo da ROM de programa. Repare que o programa de teste 1 está
colocado no início da memória. ...................................................................................................................................... 11
Figura 5 – Janela do MegaWizard Plug-In Manager definindo a memória de programa. .............................................. 12
Figura 6 – Bloco funcional da memória de dados (RAM 2-port). ................................................................................... 12
Figura 7 – Bloco funcional do banco de registradores.................................................................................................... 13
Figura 8 – Diagrama .bdf do pipeline (nível mais alto). (continua) ................................................................................. 19
Figura 9 – Diagrama .bdf simples para simulação das memórias e banco de registradores .......................................... 25
Figura 10 – Resultados da simulação do comportamento das memórias e banco de registradores. ............................ 26
Figura 11 - Diagrama .bdf para simulação da busca de instrução e decodificação. ....................................................... 26
Figura 12 – Resultados da simulação da busca de instrução e decodificação ................................................................ 27
Figura 13 – Resultados da simulação anterior aplicando-se um sinal de Reset. ............................................................ 27
Figura 14 – Diagrama .bdf para simulação da busca de dados (incluindo busca de instrução e decodificação). .......... 28
Figura 15 – Resultados da simulação da busca de dados ............................................................................................... 29
Figura 16 – Diagrama .bdf para simulação da execução das instruções (incluindo estágios anteriores)....................... 30
Figura 17 - Resultados da simulação da execução de instruções ................................................................................... 31
Figura 18 - Diagrama .bdf para simulação do armazenamento (incluindo estágios anteriores) .................................... 33
Figura 19 - Resultados da simulação do armazenamento na CPU.................................................................................. 34
Figura 20 – Simulação do pipeline com o programa 4 (sem tratamento de leituras/escritas em posições iguais) ....... 35
Figura 21 – Resultados da simulação do pipeline com um programa envolvendo Jump If Equal. ................................. 37

Tabela 1 – Especificações Gerais da Arquitetura .............................................................................................................. 7


Tabela 2 – Especificação do Conjunto de Instruções ........................................................................................................ 8

Programa 1 – Exemplo de programa vulnerável a erros de leitura/escrita do pipeline e sua correção, inserindo
instruções Nop para evitar os problemas descritos. ...................................................................................................... 10
Programa 2 – Programa de teste simples para simular a busca de dados ..................................................................... 28
Programa 3 – Teste de operações na ULA (sem respeitar as condições de acesso à memória e banco de
registradores). ................................................................................................................................................................. 29
Programa 4 – Programa genérico para teste geral do pipeline sem o uso da instrução JIE. ....................................... 31
Programa 5 – Semelhante ao Programa 3 – Programa genérico para teste geral do pipeline sem o uso da instrução
JIE. mas sem o tratamento especial para pipeline (remove-se as instruções Nop). ................................................... 35
Programa 6 – Programa de teste para a instrução JIE no pipeline. As instruções após 008 serão utilizadas como
prova de que a CPU não as executa (caso elas executem, os valores serão acusados no registrador Output). ......... 36
6

1. Introdução
1.1 CPU 4 bits
CPU é o componente responsável por realizar todas as operações que compõem a execução das instruções de
um programa. Normalmente, sua estrutura mais básica é composta por um banco de registradores que armazenam
os dados em execução, alguns multiplexadores que servem para orientar o fluxo dos dados, uma Unidade Lógica
Aritmética para os cálculos lógicos e matemáticos envolvendo estes dados, e barramentos de dados para acesso à
memória, para leitura e gravação das instruções e dados. E para ordenar tudo isso, temos uma Unidade de Controle,
que coordena o funcionamento dos componentes de modo a executar as instruções.

Ao dizermos que uma CPU é do tipo 4 bits, estamos ressaltando que todos os dados com os quais a CPU lida são
do tamanho de 4 bits; ou seja, 4 dígitos binários. Este também será o tamanho dos registradores internos e dos
dados interpretáveis a serem armazenados na memória.

1.2 Pipeline
“Pipeline é uma técnica de hardware que permite que a CPU realize a busca de uma ou mais instruções além da
próxima a ser executada. Estas instruções são colocadas em uma fila de memória dentro da (CPU) onde aguardam o
momento de serem executadas. É uma técnica semelhante a uma linha de produção de uma fábrica. É utilizada para
acelerar a velocidade de operação da CPU, uma vez que a próxima instrução a ser executada está normalmente
armazenada dentro da CPU e não precisa ser buscada da memória, normalmente muito mais lenta que a CPU” [4].

Um processador com pipeline é desenvolvido com a unidade de controle (UC) dividida em diversos estágios,
cada um responsável por um tipo de operação controlada a ser realizada no hardware. Cada estágio é intercalado
por um registrador que armazena as informações de cada instrução que está sendo executada; e a cada pulso de
clock os dados são enviados para os estágios subsequentes. A execução completa das instruções se dá pela
passagem completa da mesma por todos os estágios.

1.3 Organização do Texto e Outros Detalhes


O seguinte trabalho será dividido na seção 2, onde descreveremos aspectos gerais do processador, incluindo seu
conjunto de instruções e os estágios do pipeline. A seção 3 se encarregará de mostrar o código do processador, em
VHDL, e outros detalhes de implementação. A seção 4 mostrará uma série de testes realizados, demonstrando o
funcionamento da CPU. Enfim, a seção 5 apresentará uma discussão rápida sobre a implementação e questões de
desempenho, obtendo conclusões acerca do trabalho realizado.

2. Características da Arquitetura
2.1 Características Gerais
A arquitetura da CPU 4 bits consistirá em uma unidade central de controle, contendo o pipeline; uma memória
ROM exclusiva para programas, uma memória RAM exclusiva para armazenamento de dados de variáveis1, um
banco de registradores e sinais de entrada (Clock e Reset).

A memória ROM de programa irá conter 256 palavras de 16 bits cada uma. Como o formato das instruções não
passará de 16 bits, este foi o tamanho de palavra escolhido.

A memória RAM de armazenamento de dados terá 256 palavras de 4 bits cada uma2; de forma que os endereços
contém 8 bits. O tamanho de 4 bits para cada palavra faz com que cada uma delas tenha o valor de um registrador

1
Uma arquitetura com duas memórias reservadas para programa e dados também é conhecida por arquitetura Harvard.
7

que pode ser armazenado. A memória necessariamente deve suportar a leitura e escrita simultâneas de dados (dual-
port ou 2-port), para permitir o paralelismo em nível de pipeline. Ou seja, enquanto uma unidade faz a leitura de
uma posição de memória, a outra deve ser capaz de escrever em outra, no mesmo pulso de clock. Note que podem
ocorrer problemas com esta abordagem; e para isto, cabe ao programador ou compilador se assegurar que não há
instruções lendo e escrevendo dados no mesmo lugar simultaneamente. Veremos as precauções a serem tomadas
quando descrevermos do pipeline em si.

O banco de registradores irá conter 16 registradores, todos endereçáveis com 4 bits. Este banco de registradores
não só deve permitir que dados sejam lidos e escritos simultaneamente, como também deve permitir duas leituras
simultâneas, apresentando duas saídas de dados (leituras de dois registradores no caso de operações aritméticas,
por exemplo). Novamente, devem ser tomadas precauções quanto à leitura e escrita simultâneas em posições iguais.

Veremos abaixo um resumo das características principais da arquitetura descrita.

Tabela 1 – Especificações Gerais da Arquitetura

Resumo da Especificação da Arquitetura


N⁰ de Registradores 16 (4 bits de endereço)
N⁰ de Instruções 8 (4 bits para representação)
ROM de Programa 256 palavras de 16 bits (8 bits para end.)
RAM de Dados 256 palavras de 4 bits (8 bits para end.)
Estágios do Pipeline 5

2.2 Instruções e Formato


A CPU terá poucas instruções, já que queremos verificar somente o funcionamento do pipeline com programas
simples. São 8 instruções definidas, com exceção da instrução Nop, que indica que o processador fica ocioso. O
código de operação desta última pode ser definido como “0000”, no entanto, podemos representá-la, na
implementação, por qualquer sequência de bits que não estão utilizados por outras instruções.

 Add (RX <= RX + RY): Esta instrução básica funciona adicionando o valor contido em um registrador RY em
um registrador RX e armazenando o resultado no próprio RX.
 Sub (RX <= RX - RY ; CmpFlag <= 1 caso RX = 0): Funciona de maneira semelhante à instrução Add,
porém subtrai o valor em RY de RX e armazena o resultado em RX. A instrução também possui uma
funcionalidade adicional de realizar comparação entre dois registradores; subtraindo um pelo outro e
quando o resultado for zero, um flag chamado CmpFlag é colocado em 1, indicando que os valores são
iguais. Isto será verificado durante a execução da instrução JIE (Jump If Equal), a ser descrita a seguir.
 Loadx (RX <= (RAM)): Carrega um valor da memória de dados em um registrador RX. O endereço de 8
bits da RAM é indicado diretamente na instrução.
 Storex ((RAM) <= RX): Armazena um valor contido no registrador RX em uma posição de memória cujo
endereço é indicado.
 Set (RX <= (valor literal)): Armazena em RX um valor inteiro representado em binário diretamente na
instrução (4 bits).
 Move (RX <= RY): Copia o conteúdo de um registrador RY para um registrador RX. Note que há duplicação
dos dados.
 Jump If Equal (Program Counter <= (valor)): Pula para um trecho do código caso o registrador
CmpFlag esteja em ‘1’. Isto ocorre atualizando o valor do PC na unidade fetch e limpando tudo o que

2
Utilizaremos memórias ROM e RAM destes tipos para fins práticos, já que o foco do trabalho está na arquitetura do pipeline.
Isto, porém, não indica que necessariamente existam memórias assim no mercado; sendo elas no máximo possíveis de serem
simuladas, como no caso do software que utilizaremos no trabalho, Altera Quartus II.
8

está sendo processado nas outras unidades (que são instruções que ocorrem depois do jump). Esta
instrução também requer um tratamento especial na construção do programa, devendo-se evitar que o
registrador CmpFlag mude inesperadamente na hora de sua execução na unidade de store. Veremos
isto adiante.
 Output (Saída <= RX): Esta instrução simplesmente coloca o valor de um registrador RX num
barramento de saída logo que ela sai do pipeline (a instrução está em store e há uma subida de clock).

Abaixo vemos uma tabela contendo o formato de cada instrução em 16 bits. Os quatro primeiros bits
representam o código de operação da instrução (opcode). Um bit representado por um traço significa que é
irrelevante.

Tabela 2 – Especificação do Conjunto de Instruções

Formato das Instruções


Nop |00|0|0|0|4-|-|-|-|8-|-|-|-|12-|-|-|15-|
Add |00|1|1|1|4R|x| | |8R|y| | |12-|-|-|15-|
Sub |00|1|1|0|4R|x| | |8R|y| | |12-|-|-|15-|
Loadx |01|1|0|0|4R|x| | |8E|n|d|R|12A|M| |15 |
Storex |01|1|0|1|4R|x| | |8E|n|d|R|12A|M| |15 |
Set |00|1|0|1|4R|x| | |8V|a|l| |12-|-|-|15-|
Move |00|1|0|0|4R|x| | |8R|y| | |12-|-|-|15-|
JIE |01|0|0|0|4E|n|d|R|8O|M| | |12-|-|-|15-|
Output |01|1|1|1|4R|x| | |8-|-|-|-|12-|-|-|15-|
EndRAM – Endereço da RAM
EndROM – Endereço da ROM
Val – Valor numérico literal
Rx / Ry – Endereço de um registrador

2.3 Pipeline
Na CPU 4 bits em questão, teremos cinco estágios, que são chamados fetch, decode, fetchdata, exec e
store, dispostos em ordem. Como já foi dito na seção 1.2, cada uma destas unidades é responsável por um
determinado tipo de operação para cada instrução que está sendo executada. Entre elas, também teremos uma
espécie de registrador que armazena informações sobre a instrução que está no pipeline e, a cada pulso de clock,
envia-as para a unidade posterior com menos ou mais informações, dependendo do que foi executado na unidade.
Nesta seção, analisaremos a função de cada uma das unidades e quais informações serão passadas entre elas.

 fetch (Busca de Instrução): Esta unidade se encarrega da operação básica de, a cada ciclo de clock,
obter uma instrução localizada na memória de programa, transmitindo nada mais do que um fluxo de
bits bruto para a unidade decode. É nesta unidade que está localizado o registrador especial de
contador de programa (Program Counter - PC), responsável por armazenar a próxima instrução a ser
buscada da memória (sendo incrementado a cada instrução processada). Este registrador poderá ser
atualizado com sinais externos, que serão enviados pela unidade store caso tenhamos uma instrução do
tipo Jump a ser executada.
 decode (decodificação): Realiza a tarefa de interpretar o fluxo de bits que chega da memória de
programa, através do fetch, e dividi-las em informações como código de operacão da instrução,
endereços dos registradores, endereços das memória de dados e programa e valores numéricos
literais3. Estas informações são, então, passadas para fetchdata.

3
Lembra-se que nem todas estas informações existirão em cada instrução; e caso alguma não existir, o valor no registrador
correspondente será irrelevante. Por exemplo, uma instrução de adição não possui valores numéricos literais. O valor no
registrador de valores numéricos literais será irrelevante, podendo continuar como já estava, já que, uma vez que alguma
unidade posterior tem o código operacional da adição, ela sequer irá utilizar o valor que estiver ali.
9

 fetchdata (busca de dados): Se encarrega de obter dados a partir dos endereços de


registradores e da memória de dados recebidas. A unidade acessa o banco de registradores e a
memória RAM, enviando os endereços das posições desejadas, e obtendo os dados contidos nas
mesmas. Então, estas informações são passadas adiante, junto com o código operacional da
instrução e os possíveis endereços da memória de programa e valores numéricos literais.
 exec (execução de operações): Recebidos de fetchdata os valores nos registradores
requeridos para as operações, esta unidade funciona como ULA – Unidade Lógico-Aritmética. Nela
são realizadas as operações de adição e subtração entre os registradores e é definido o flag de
comparação (CmpFlag). Caso nenhuma operação lógico-aritmética seja feita, a unidade
simplesmente retransmite o valor necessário para a unidade store. Também, em todas as
ocasiões, são retransmitidos os códigos de operação, endereços e valores numéricos literais.
 store (armazenamento): A última unidade de controle do pipeline se encarrega da tarefa
fundamental de armazenar os valores obtidos na memória RAM ou memória de dados. Ela também
é responsável por analisar o flag de comparação e atualizar o valor do contador de programa
(ProgCount) caso preciso, realizando as instruções de Jump If Equal. Quando isto ocorre, a
unidade libera um sinal Clear para todas as outras unidades, aniquilando as instruções em
andamento (que vêm depois da instrução de Jump). A unidade então entra num estado chamado
denominado ClrCtrl (controle do Clear), onde ela também ignora a instrução vinda de exec e
permanece inativa por um ciclo de clock, desativando o sinal de Clear no final deste. Por fim, a
unidade também define o valor em Output.

2.4 Problemas e Cuidados Especiais na Construção dos Programas


Um processador com pipeline também apresenta dificuldades adicionais de construção dos programas a nível
Assembly, comparado com uma unidade de controle simples. Conforme já foi dito, uma vez que o pipeline necessita
de memórias e bancos de registradores com suporte a leitura e escrita simultâneas, teremos sérios problemas
quando, em um só ciclo de clock, deseja-se ler e escrever numa mesma posição de memória ou registrador. Temos o
problema ilustrado na Figura 1 – Problema no pipeline quando duas instruções querem ler e escrever na mesma
posição de memória. As instruções.

Figura 1 – Problema no pipeline quando duas instruções querem ler e escrever na mesma posição de memória. As instruções estão em
sequência e são representadas pelos números 000, 001, etc.

Isto pode ser facilmente resolvido intercalando o programa com várias instruções Nop, evitando esta situação
no pipeline. Observe a Figura 2 – Solução para o problema descrito na Figura 1.
10

Figura 2 – Solução para o problema descrito na Figura 1.

Note também que temos outro problema sério com o pipeline – não podemos permitir que uma instrução
que necessite do valor de um registrador venha imediatamente após este registrador ser modificado. O problema é
que o valor poderá ser lido na unidade fetch sem que haja tempo para que ele seja realmente modificado, e assim,
teremos um valor indesejável. O problema é ilustrado na Figura 3 – Problema da leitura de valores antes da
modificação real e é resolvido simplesmente adiando a leitura (preenchendo o intervalo com outras instruções ou
simplesmente adicionando Nop).

Figura 3 – Problema da leitura de valores antes da modificação real

Tendo visto os possíveis problemas com a execução sequencial dos programas, podemos concluir que, dada
uma instrução que atualiza um registrador ou posição de memória, devemos realizar uma leitura da mesma somente
a um intervalo de duas instruções da instrução de escrita. Abaixo temos transcrito um exemplo de programa sem a
preocupação do pipeline, e sua modificação respectiva evitando os problemas descritos.

Programa 1 – Exemplo de programa vulnerável a erros de leitura/escrita do pipeline e sua correção, inserindo instruções Nop para evitar os
problemas descritos.

Programa Vulnerável Programa Corrigido


000 Set R1 5 000 Set R1 5
001 Set R2 5 001 Set R2 5
002 Stx R1 &001 002 Nop
003 Add R1 R2 003 Stx R1 &001
004 Set R3 10 004 Nop
005 Mov R0 R1 005 Add R1 R2
006 Lox R1 &001 006 Set R3 10
007 Sub R0 R3 007 Nop
008 Mov R0 R1
009 Lox R1 &001
010 Nop
11 Sub R0 R3
11

3. Implementação
Retornando ao que foi dito na seção 1.3, a implementação da CPU 4 bits foi feita no software Altera Quartus II,
versão 9.1 Build 350 Web Edition. Cada estágio do pipeline constitui um bloco descrito em VHDL (VHSIC Hardware
Description Language); enquanto a conexão entre eles foi feita em um arquivo do tipo .bdf, formato utilizado pelo
software para representar diagramas de blocos (editável pelo próprio software).

As descrições VHDL das unidades de controle foram feitas por um modelo procedural (sequencial) baseado em
máquina de estados; ou seja, o comportamento de cada unidade é moldado por um valor que se encontra na
entrada da mesma, que é a saída da unidade anterior. Este valor (estado) é exatamente o código operacional da
instrução que está no pipeline.

Apresentaremos a seguir uma breve descrição de cada bloco e seus respectivos códigos.

3.1 Memória de Progama, Memória de Dados e Banco de Registradores


O software Quartus II apresenta uma ferramenta interessante de geração automática de blocos funcionais,
chamada MegaWizard Plug-In Manager. Ele se utiliza dos conceitos de megafunction (megafunção), um bloco pré-
montado onde se basta passar alguns valores para que seu código de descrição seja construído. Assim, podemos
criar rapidamente várias unidades que levariam tempo até serem construídas; e foi o que fizemos com as memórias
de programa e dados e o banco de registradores.

Para a memória ROM de programa, utilizamos a megafunção ROM 1-PORT. Foram inseridos os parâmetros já
descritos na seção 2.1, e definiu-se um arquivo do tipo .mif (Memory Initialization File) para o conteúdo da ROM,
que é o programa em si. Este tipo de arquivo pode ser aberto no software, podendo-se editar cada posição de
memória com um número real inteiro, sem sinal, correspondente à sequência de dígitos binários da instrução. Por
exemplo, no caso do programa de teste 1, descrito na seção , temos o seguinte trecho do arquivo .mif aberto no
software:

Figura 4 – Trecho do arquivo .mif para o conteúdo da ROM de programa. Repare que o programa de teste 1 está colocado no início da
memória.

Temos também a janela do MegaWizard Plug-In Manager que define a memória ROM, definida pelo nome
progrom:
12

Figura 5 – Janela do MegaWizard Plug-In Manager definindo a memória de programa.

Repare no símbolo do bloco da memória e os latches de clock. No caso da memória acima, a cada ciclo de clock,
o valor na entrada (address[7..0]) é passado para a memória, que joga os dados na saída (q[15..0]).

No caso da memória RAM de programa, utilizamos a megafunção RAM 2-PORT; ou seja, uma RAM dual-port
conforme descrito na seção 2.1. Foram definidos os parâmetros de número e tamanho das palavras, e também que
ela sempre inicia com uma sequência de dados definidos em outro arquivo .mif. Temos o símbolo do bloco
dataram:

Figura 6 – Bloco funcional da memória de dados (RAM 2-port).

Para o banco de registradores, o ideal seria programá-lo diretamente, sem a utilização de uma megafunção. No
entanto, para fins práticos, definimos como a megafunção ALT3PRAM. Esta megafunção é voltada mais para
aplicações como memória RAM, no entanto, como não há preocupação com a latência e/ou rapidez das
transferências, optamos por utilizá-la para um banco de registradores. Temos o símbolo de RegBank:
13

Figura 7 – Bloco funcional do banco de registradores.

Note que, como descrito na seção 2.1, o banco de registradores deve ser capaz de realizar duas leituras e
uma escrita no mesmo pulso de clock (por isto a megafunção ALT3PRAM – uma RAM do tipo 3-port). Definimos
também que ele sempre irá iniciar com todas as posições em zero.

Tendo definido os componentes de armazenamento de dados, partiremos para a descrição do pipeline em


si.

3.2 Unidade fetch

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;

entity fetch is
port(
Clk : in std_logic;
Rst: in std_logic;

-- Valor de ProgCount, passado para a ROM


InstAddrOut : out std_logic_vector(7 downto 0);

-- Recebe o valor na memória ROM e repassa para decode


InstrIn : in std_logic_vector(15 downto 0);
InstrOut : out std_logic_vector(15 downto 0);

-- Sinais para escrita em ProgCount


WrtPC : in std_logic;
PCIn : in std_logic_vector(7 downto 0)
);
end fetch;

architecture behavior of fetch is


-- Contador de Programa / Program Counter :
-- armazena o valor corrente na memória de programa
signal ProgCount : std_logic_vector(7 downto 0);
begin
process (Clk)
begin
if (Clk'event and Clk='1') then
if (Rst = '1') then -- Zerar tudo em caso de Reset
ProgCount <= "00000000";
InstrOut <= "0000000000000000";
14

elsif (WrtPC = '1') then


-- Caso deseja-se escrever em ProgCount,
-- já interpreta-se isto como um Clear (como
-- nas outras unidades). ProgCount é definido.
ProgCount <= PCIn;
InstrOut <= "0000000000000000";
else
-- Caso não haja nenhum sinal não-convencional,
-- incrementar o contador.
ProgCount <= std_logic_vector(unsigned(ProgCount)+1);
InstrOut <= InstrIn;
end if;
end if;
end process;
InstAddrOut <= ProgCount;
end behavior;

3.3 Unidade decode

LIBRARY ieee;
USE ieee.std_logic_1164.all;

entity decode is
port(
Clk : in std_logic;
Clear : in std_logic;

-- Sinal de entrada, vem de fetch


DInstrIn : in std_logic_vector(15 downto 0);

-- Informações obtidas (decodificadas),


-- passadas para fetchdata
Opcode : out std_logic_vector(3 downto 0);
DMemAddr : out std_logic_vector(7 downto 0);
PMemAddr : out std_logic_vector(7 downto 0);
RegAddr1 : out std_logic_vector(3 downto 0);
RegAddr2 : out std_logic_vector(3 downto 0);
FixedVal : out std_logic_vector(3 downto 0) );
end decode;

architecture behavior of decode is


begin
process (Clk)
begin
if(Clk'event and Clk = '1') then
if (Clear = '1') then -- Verifica Clear
Opcode <= "0000";
else
Opcode <= DInstrIn(15 downto 12);
-- Abaixo, a unidade decode irá retirar
-- os dados de cada instrução dado o
-- código operacional no início de cada uma
case DInstrIn(15 downto 12) is
when "1100" | "1101" => -- Loadx or Storex
RegAddr1 <= DInstrIn(11 downto 8);
DMemAddr <= DInstrIn(7 downto 0);
when "0111" | "0110" | "0100" =>
15

-- Add, Sub or Mov


RegAddr1 <= DInstrIn(11 downto 8);
RegAddr2 <= DInstrIn(7 downto 4);
when "1000" => -- JIE
PMemAddr <= DInstrIn(11 downto 4);
when "0101" => -- Set
RegAddr1 <= DInstrIn(11 downto 8);
FixedVal <= DInstrIn(7 downto 4);
when "1111" => -- Output
RegAddr1 <= DInstrIn(11 downto 8);
when others =>
end case;
end if;
end if;
end process;
end behavior;

3.4 Unidade fetchData4

LIBRARY ieee;
USE ieee.std_logic_1164.all;

entity fetchdata is
port(
Clk : in std_logic;
Clear : in std_logic;

-- Entradas e saídas da unidade (repassadas)


OpcodeIn : in std_logic_vector(3 downto 0);
OpcodeOut : out std_logic_vector(3 downto 0);

DMemAddrIn : in std_logic_vector(7 downto 0);


DMemAddrOut : out std_logic_vector(7 downto 0);

PMemAddrIn : in std_logic_vector(7 downto 0);


PMemAddrOut: out std_logic_vector(7 downto 0);

RegAddr1In : in std_logic_vector(3 downto 0);


RegAddr2In : in std_logic_vector(3 downto 0);
RegAddr1Out : out std_logic_vector(3 downto 0);
RegAddr2Out : out std_logic_vector(3 downto 0);

FixedValIn : in std_logic_vector(3 downto 0);


FixedValOut : out std_logic_vector(3 downto 0) );

-- OBS: Os valores contidos nas memórias já


-- são enviados direto da saída para a entrada
-- da próxima unidade.
end fetchdata;

architecture behavior of fetchdata is


begin
process (Clk)
begin

if (Clk'event and Clk = '1') then

4
fetchdata é melhor visualizado no diagrama .bdf, e não na descrição VHDL em si.
16

if (Clear = '1') then -- Verifica Clear


OpcodeOut <= "0000";
else
OpcodeOut <= OpcodeIn;
FixedValOut <= FixedValIn;
DMemAddrOut <= DMemAddrIn;
PMemAddrOut <= PMemAddrIn;
RegAddr1Out <= RegAddr1In;
RegAddr2Out <= RegAddr2In;
end if;
end if;
end process;
end behavior;

3.5 Unidade exec

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;

entity exec is
port(
Clk : in std_logic;
Clear : in std_logic;

-- Repassa os valores de Opcode


OpcodeIn : in std_logic_vector(3 downto 0);
OpcodeOut : out std_logic_vector(3 downto 0);

-- Recebe os valores nos registradores e


-- memória RAM
RegData1 : in std_logic_vector(3 downto 0);
RegData2 : in std_logic_vector(3 downto 0);
MemContent : in std_logic_vector(3 downto 0);

-- Valor numérico literal recebido de fetchdata


FixedVal : in std_logic_vector(3 downto 0);

-- Resultado obtido nas operações da ULA,


-- ou simplesmente um dado repassado
Result : out std_logic_vector(3 downto 0);

CmpFlag : out std_logic;

-- Endereços necessários repassados


PMemAddrIn : in std_logic_vector(7 downto 0);
PMemAddrOut: out std_logic_vector(7 downto 0);
DMemAddrIn : in std_logic_vector(7 downto 0);
DMemAddrOut: out std_logic_vector(7 downto 0);
RegAddrIn : in std_logic_vector(3 downto 0);
RegAddrOut: out std_logic_vector(3 downto 0) );
end exec;

architecture behavior of exec is


begin
process (Clk)
begin
if (Clk'event and Clk = '1') then
17

if (Clear = '1') then -- Verifica Clear


OpcodeOut <= "0000";
else
-- Operações com cada instrução
case OpcodeIn is
when "0111" => -- Add
Result <= std_logic_vector(unsigned(RegData1)+
unsigned(RegData2));
when "0110" => -- Sub
Result <= std_logic_vector(unsigned(RegData1)-
unsigned(RegData2));
-- Definindo o flag de comparação
if (unsigned(RegData1)-
unsigned(RegData2) = 0) then
CmpFlag <= '1';
else
CmpFlag <= '0';
end if;
when "0101" =>
-- Set: valor a ser gravado é numérico literal
Result <= FixedVal;
when "0100" =>
-- Move: valor a ser gravado está em RegData2
Result <= RegData2;
when "1101" | "1111" =>
-- Storex / Output: valor está em RegData1
Result <= RegData1;
when "1100" =>
-- Loadx: valor é conteúdo da RAM
Result <= MemContent;
when others =>
end case;
-- Repassa outros valores
OpcodeOut <= OpcodeIn;
RegAddrOut <= RegAddrIn;
DMemAddrOut <= DMemAddrIn;
PMemAddrOut <= PMemAddrIn;
end if;
end if;
end process;
end behavior;

3.6 Unidade store

LIBRARY ieee;
USE ieee.std_logic_1164.all;

entity store is
port(
Clk : in std_logic;

-- Recebe de exec
OpcodeIn : in std_logic_vector(3 downto 0);

-- Repassa os dados para as memórias e


-- banco de registradores. Repare que
-- o valor Result pode ir para as duas
-- memórias e o banco, desde que somente um
18

-- sinal Wren esteja em uma destas unidades


ResultIn : in std_logic_vector(3 downto 0);
ResultOut : out std_logic_vector(3 downto 0);

DMemAddrIn : in std_logic_vector(7 downto 0);


DMemAddrOut : out std_logic_vector(7 downto 0);

RegAddrIn : in std_logic_vector(3 downto 0);


RegAddrOut : out std_logic_vector(3 downto 0);

PMemAddrIn : in std_logic_vector(7 downto 0);


PMemAddrOut : out std_logic_vector(7 downto 0);

-- Wren: Write Enable (quando em 1, autoriza


-- a escrita na memória)
DMemWren : out std_logic;
RegWren : out std_logic;

-- Para execução de JIE


CmpFlagIn : in std_logic;
PMemWren : out std_logic;
ClearAll : out std_logic;

-- Valor Output
OutputData : out std_logic_vector(3 downto 0) );
end store;

architecture behavior of store is

begin
process (Clk)
-- Variável de estado para o Clear
variable ClrCtrl : std_logic := '0';
begin
if (Clk'event and Clk = '1') then
-- verifica se entrou no estado ClrCtrl
if (ClrCtrl = '1') then
ClearAll <= '0';
ClrCtrl := '0';
PMemWren <= '0';
else
-- analisa a instrução e define
-- as escritas
case OpcodeIn is
when "1101" => -- Storex
DMemWren <= '1';
RegWren <= '0';
PMemWren <= '0';
when "0111" | "0110" | "1100" |
"0101" | "0100" =>
-- Add, Sub, Loadx, Set ou Mov
DMemWren <= '0';
RegWren <= '1';
PMemWren <= '0';
when "1000" => -- Jump if Equal
DMemWren <= '0';
RegWren <= '0';
-- verifica o flag de comparação
if (CmpFlagIn = '1') then
19

PMemWren <= '1';


ClearAll <= '1';
-- entrando no estado CrlCrtl
ClrCtrl := '1';
else
PMemWren <= '0';
end if;
when "1111" => -- Output
OutputData <= ResultIn;
DMemWren <= '0';
RegWren <= '0';
PMemWren <= '0';
when others =>
-- Caso nada precise ser gravado
DMemWren <= '0';
RegWren <= '0';
PMemWren <= '0';
end case;
PMemAddrOut <= PMemAddrIn;
DMemAddrOut <= DMemAddrIn;
RegAddrOut <= RegAddrIn;
ResultOut <= ResultIn;
end if;
end if;
end process;
end behavior;

3.7 Diagrama .bdf Completo e Código VHDL Correspondente

Figura 8 – Diagrama .bdf do pipeline (nível mais alto). (continua)


20

Observe o sinal de clock invertido entrando nas memórias RAM e ROM e no banco de registradores. Isto é
necessário para que dados sejam lidos e interpretados por uma unidade de controle em somente um ciclo de clock
(caso a memória seja tão rápida quanto ela).

O software Quartus II também oferece uma funcionalidade interessante de transformar um diagrama .bdf em
uma descrição VHDL correspondente. Isto é útil caso deseja-se utilizar o código em uma plataforma que não suporte
estes diagramas. Temos então, o código:

LIBRARY ieee;
21

USE ieee.std_logic_1164.all;

LIBRARY work;

ENTITY pipeline4bits IS
PORT
(
Clk : IN STD_LOGIC;
Rst : IN STD_LOGIC;
Output : OUT STD_LOGIC_VECTOR(3 DOWNTO 0)
);
END pipeline4bits;

ARCHITECTURE bdf_type OF pipeline4bits IS

COMPONENT progrom
PORT(clock : IN STD_LOGIC;
address : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
q : OUT STD_LOGIC_VECTOR(15 DOWNTO 0)
);
END COMPONENT;

COMPONENT fetch
PORT(Clk : IN STD_LOGIC;
Rst : IN STD_LOGIC;
WrtPC : IN STD_LOGIC;
InstrIn : IN STD_LOGIC_VECTOR(15 DOWNTO 0);
PCIn : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
InstAddrOut : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
InstrOut : OUT STD_LOGIC_VECTOR(15 DOWNTO 0)
);
END COMPONENT;

COMPONENT decode
PORT(Clk : IN STD_LOGIC;
Clear : IN STD_LOGIC;
DInstrIn : IN STD_LOGIC_VECTOR(15 DOWNTO 0);
DMemAddr : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
FixedVal : OUT STD_LOGIC_VECTOR(3 DOWNTO 0);
Opcode : OUT STD_LOGIC_VECTOR(3 DOWNTO 0);
PMemAddr : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
RegAddr1 : OUT STD_LOGIC_VECTOR(3 DOWNTO 0);
RegAddr2 : OUT STD_LOGIC_VECTOR(3 DOWNTO 0)
);
END COMPONENT;

COMPONENT fetchdata
PORT(Clk : IN STD_LOGIC;
Clear : IN STD_LOGIC;
DMemAddrIn : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
FixedValIn : IN STD_LOGIC_VECTOR(3 DOWNTO 0);
OpcodeIn : IN STD_LOGIC_VECTOR(3 DOWNTO 0);
PMemAddrIn : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
RegAddr1In : IN STD_LOGIC_VECTOR(3 DOWNTO 0);
RegAddr2In : IN STD_LOGIC_VECTOR(3 DOWNTO 0);
DMemAddrOut : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
FixedValOut : OUT STD_LOGIC_VECTOR(3 DOWNTO 0);
OpcodeOut : OUT STD_LOGIC_VECTOR(3 DOWNTO 0);
PMemAddrOut : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
RegAddr1Out : OUT STD_LOGIC_VECTOR(3 DOWNTO 0);
RegAddr2Out : OUT STD_LOGIC_VECTOR(3 DOWNTO 0)
);
END COMPONENT;

COMPONENT dataram
22

PORT(wren : IN STD_LOGIC;
clock : IN STD_LOGIC;
data : IN STD_LOGIC_VECTOR(3 DOWNTO 0);
rdaddress : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
wraddress : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
q : OUT STD_LOGIC_VECTOR(3 DOWNTO 0)
);
END COMPONENT;

COMPONENT regbank
PORT(wren : IN STD_LOGIC;
clock : IN STD_LOGIC;
data : IN STD_LOGIC_VECTOR(3 DOWNTO 0);
rdaddress_a : IN STD_LOGIC_VECTOR(3 DOWNTO 0);
rdaddress_b : IN STD_LOGIC_VECTOR(3 DOWNTO 0);
wraddress : IN STD_LOGIC_VECTOR(3 DOWNTO 0);
qa : OUT STD_LOGIC_VECTOR(3 DOWNTO 0);
qb : OUT STD_LOGIC_VECTOR(3 DOWNTO 0)
);
END COMPONENT;

COMPONENT exec
PORT(Clk : IN STD_LOGIC;
Clear : IN STD_LOGIC;
DMemAddrIn : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
FixedVal : IN STD_LOGIC_VECTOR(3 DOWNTO 0);
MemContent : IN STD_LOGIC_VECTOR(3 DOWNTO 0);
OpcodeIn : IN STD_LOGIC_VECTOR(3 DOWNTO 0);
PMemAddrIn : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
RegAddrIn : IN STD_LOGIC_VECTOR(3 DOWNTO 0);
RegData1 : IN STD_LOGIC_VECTOR(3 DOWNTO 0);
RegData2 : IN STD_LOGIC_VECTOR(3 DOWNTO 0);
CmpFlag : OUT STD_LOGIC;
DMemAddrOut : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
OpcodeOut : OUT STD_LOGIC_VECTOR(3 DOWNTO 0);
PMemAddrOut : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
RegAddrOut : OUT STD_LOGIC_VECTOR(3 DOWNTO 0);
Result : OUT STD_LOGIC_VECTOR(3 DOWNTO 0)
);
END COMPONENT;

COMPONENT store
PORT(Clk : IN STD_LOGIC;
CmpFlagIn : IN STD_LOGIC;
DMemAddrIn : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
OpcodeIn : IN STD_LOGIC_VECTOR(3 DOWNTO 0);
PMemAddrIn : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
RegAddrIn : IN STD_LOGIC_VECTOR(3 DOWNTO 0);
ResultIn : IN STD_LOGIC_VECTOR(3 DOWNTO 0);
DMemWren : OUT STD_LOGIC;
RegWren : OUT STD_LOGIC;
PMemWren : OUT STD_LOGIC;
ClearAll : OUT STD_LOGIC;
DMemAddrOut : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
OutputData : OUT STD_LOGIC_VECTOR(3 DOWNTO 0);
PMemAddrOut : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
RegAddrOut : OUT STD_LOGIC_VECTOR(3 DOWNTO 0);
ResultOut : OUT STD_LOGIC_VECTOR(3 DOWNTO 0)
);
END COMPONENT;

SIGNAL Clear : STD_LOGIC;


SIGNAL DMemAddr : STD_LOGIC_VECTOR(7 DOWNTO 0);
SIGNAL DMemWren : STD_LOGIC;
SIGNAL FixedVal : STD_LOGIC_VECTOR(3 DOWNTO 0);
23

SIGNAL InvClk : STD_LOGIC;


SIGNAL Opcode : STD_LOGIC_VECTOR(3 DOWNTO 0);
SIGNAL PMemAddr : STD_LOGIC_VECTOR(7 DOWNTO 0);
SIGNAL PMemWren : STD_LOGIC;
SIGNAL RegAddr1 : STD_LOGIC_VECTOR(3 DOWNTO 0);
SIGNAL RegAddr2 : STD_LOGIC_VECTOR(3 DOWNTO 0);
SIGNAL RegWren : STD_LOGIC;
SIGNAL ToExecDMemAddr : STD_LOGIC_VECTOR(7 DOWNTO 0);
SIGNAL ToExecFixedVal : STD_LOGIC_VECTOR(3 DOWNTO 0);
SIGNAL ToExecOpcode : STD_LOGIC_VECTOR(3 DOWNTO 0);
SIGNAL ToExecPMemAddr : STD_LOGIC_VECTOR(7 DOWNTO 0);
SIGNAL ToExecRAMData : STD_LOGIC_VECTOR(3 DOWNTO 0);
SIGNAL ToExecReg1Addr : STD_LOGIC_VECTOR(3 DOWNTO 0);
SIGNAL ToExecReg1Data : STD_LOGIC_VECTOR(3 DOWNTO 0);
SIGNAL ToExecReg2Data : STD_LOGIC_VECTOR(3 DOWNTO 0);
SIGNAL WDMemAddr : STD_LOGIC_VECTOR(7 DOWNTO 0);
SIGNAL WPMem : STD_LOGIC_VECTOR(7 DOWNTO 0);
SIGNAL WrData : STD_LOGIC_VECTOR(3 DOWNTO 0);
SIGNAL WRegAddr : STD_LOGIC_VECTOR(3 DOWNTO 0);
SIGNAL SYNTHESIZED_WIRE_0 : STD_LOGIC_VECTOR(7 DOWNTO 0);
SIGNAL SYNTHESIZED_WIRE_1 : STD_LOGIC_VECTOR(15 DOWNTO 0);
SIGNAL SYNTHESIZED_WIRE_2 : STD_LOGIC_VECTOR(15 DOWNTO 0);
SIGNAL SYNTHESIZED_WIRE_3 : STD_LOGIC_VECTOR(3 DOWNTO 0);
SIGNAL SYNTHESIZED_WIRE_4 : STD_LOGIC;
SIGNAL SYNTHESIZED_WIRE_5 : STD_LOGIC_VECTOR(7 DOWNTO 0);
SIGNAL SYNTHESIZED_WIRE_6 : STD_LOGIC_VECTOR(3 DOWNTO 0);
SIGNAL SYNTHESIZED_WIRE_7 : STD_LOGIC_VECTOR(7 DOWNTO 0);
SIGNAL SYNTHESIZED_WIRE_8 : STD_LOGIC_VECTOR(3 DOWNTO 0);
SIGNAL SYNTHESIZED_WIRE_9 : STD_LOGIC_VECTOR(3 DOWNTO 0);

BEGIN

b2v_inst : progrom
PORT MAP(clock => InvClk,
address => SYNTHESIZED_WIRE_0,
q => SYNTHESIZED_WIRE_1);

InvClk <= NOT(Clk);

b2v_inst2 : fetch
PORT MAP(Clk => Clk,
Rst => Rst,
WrtPC => PMemWren,
InstrIn => SYNTHESIZED_WIRE_1,
PCIn => WPMem,
InstAddrOut => SYNTHESIZED_WIRE_0,
InstrOut => SYNTHESIZED_WIRE_2);

b2v_inst3 : decode
PORT MAP(Clk => Clk,
Clear => Clear,
DInstrIn => SYNTHESIZED_WIRE_2,
DMemAddr => DMemAddr,
FixedVal => FixedVal,
Opcode => Opcode,
PMemAddr => PMemAddr,
RegAddr1 => RegAddr1,
RegAddr2 => RegAddr2);

b2v_inst4 : fetchdata
PORT MAP(Clk => Clk,
Clear => Clear,
DMemAddrIn => DMemAddr,
24

FixedValIn => FixedVal,


OpcodeIn => Opcode,
PMemAddrIn => PMemAddr,
RegAddr1In => RegAddr1,
RegAddr2In => RegAddr2,
DMemAddrOut => ToExecDMemAddr,
FixedValOut => ToExecFixedVal,
OpcodeOut => ToExecOpcode,
PMemAddrOut => ToExecPMemAddr,
RegAddr1Out => ToExecReg1Addr,
RegAddr2Out => SYNTHESIZED_WIRE_3);

b2v_inst5 : dataram
PORT MAP(wren => DMemWren,
clock => InvClk,
data => WrData,
rdaddress => ToExecDMemAddr,
wraddress => WDMemAddr,
q => ToExecRAMData);

b2v_inst6 : regbank
PORT MAP(wren => RegWren,
clock => InvClk,
data => WrData,
rdaddress_a => ToExecReg1Addr,
rdaddress_b => SYNTHESIZED_WIRE_3,
wraddress => WRegAddr,
qa => ToExecReg1Data,
qb => ToExecReg2Data);

b2v_inst7 : exec
PORT MAP(Clk => Clk,
Clear => Clear,
DMemAddrIn => ToExecDMemAddr,
FixedVal => ToExecFixedVal,
MemContent => ToExecRAMData,
OpcodeIn => ToExecOpcode,
PMemAddrIn => ToExecPMemAddr,
RegAddrIn => ToExecReg1Addr,
RegData1 => ToExecReg1Data,
RegData2 => ToExecReg2Data,
CmpFlag => SYNTHESIZED_WIRE_4,
DMemAddrOut => SYNTHESIZED_WIRE_5,
OpcodeOut => SYNTHESIZED_WIRE_6,
PMemAddrOut => SYNTHESIZED_WIRE_7,
RegAddrOut => SYNTHESIZED_WIRE_8,
Result => SYNTHESIZED_WIRE_9);

b2v_inst8 : store
PORT MAP(Clk => Clk,
CmpFlagIn => SYNTHESIZED_WIRE_4,
DMemAddrIn => SYNTHESIZED_WIRE_5,
OpcodeIn => SYNTHESIZED_WIRE_6,
PMemAddrIn => SYNTHESIZED_WIRE_7,
RegAddrIn => SYNTHESIZED_WIRE_8,
ResultIn => SYNTHESIZED_WIRE_9,
DMemWren => DMemWren,
RegWren => RegWren,
PMemWren => PMemWren,
ClearAll => Clear,
DMemAddrOut => WDMemAddr,
OutputData => Output,
PMemAddrOut => WPMem,
RegAddrOut => WRegAddr,
ResultOut => WrData);
25

END bdf_type;

4. Simulações
Várias simulações foram feitas com a CPU 4 bits para garantir seu funcionamento. Começamos primeiramente
com os blocos individuais e funções básicas e avançamos em seguida para testes de nível mais alto. Os testes foram
realizados pela ferramenta inclusa no software que gera sinais de saída baseados nos sinais de entrada indicados por
um arquivo .vwf (Vector Waveform File). O software também permite definir o modo de simulação a ser feita; e
assim, foi definida simulação funcional, ou seja, queremos simular somente o funcionamento da lógica do
processador, sem envolver tempos de atraso ou quaisquer outros parâmetros.

Nas seções seguintes, apresentaremos uma breve descrição de cada simulação, a montagem utilizada em
diagrama .bdf e as formas de onda resultantes.

4.1 Memórias e Registradores


Em primeiro lugar é necessário verificar o funcionamento das memórias e o banco de registradores a serem
utilizados na arquitetura. Testamos as memórias uma a uma, com todas as operações de leitura e escrita.

O conteúdo inicial na memória ROM foi definido com o programa de teste 1, descrito na seção
(ASDASDASFGSDFS), e disposto na memória conforme na Figura 4 – Trecho do arquivo .mif para o conteúdo da ROM
de programa. Repare que o programa de teste 1 está colocado no início da memória. Os conteúdos na ROM e no
banco de registradores começam todos em zero.

Figura 9 – Diagrama .bdf simples para simulação das memórias e banco de registradores
26

Tivemos os seguintes resultados:

Figura 10 – Resultados da simulação do comportamento das memórias e banco de registradores.

A cada subida de pulso de clock, podemos verificar que a saída da ROM apresentou o conteúdo correto de cada
posição, a RAM gravou corretamente os valores indicados e foi capaz de lê-los, e o banco de registradores também
funcionou corretamente.

4.2 Busca de Instrução e Decodificação


Testaremos agora o funcionamento da CPU ao retirar endereços da memória de programa e enviá-los à unidade
de decodificação, que colocará os dados retirados na saída. Teremos na memória ROM uma sequência qualquer de
instruções. Verificaremos também o modelo de pipeline funcionando nestas duas unidades, ou seja, enquanto uma
instrução é buscada, a outra é decodificada. O teste de atualização do contador de programa na unidade fetch
será testado na seção 4.6. Temos a montagem de simulação (os fios desconectados posteriormente foram ligados no
fio terra):

Figura 11 - Diagrama .bdf para simulação da busca de instrução e decodificação.


27

Temos os resultados:

Figura 12 – Resultados da simulação da busca de instrução e decodificação

Repare que enquanto a saída FetchOut apresenta um código de operação (quatro primeiros bits), a saída
OpCodeOut apresenta o código de operação da última instrução que foi buscada. Isto comprova o funcionamento
do pipeline.

Seguindo a simulação, temos um sinal de reset, que irá zerar o contador de programa. Temos:

Figura 13 – Resultados da simulação anterior aplicando-se um sinal de Reset.

Observa-se que a execução do programa voltou ao início após o sinal de Reset. As unidades fetch e decode
funcionam até aqui.

4.3 Busca de Dados


Após termos as instruções retiradas da memória e decodificadas em dados interpretáveis, iremos realizar a
busca dos dados requeridos da memória RAM e do banco de registradores. Temos o diagrama:
28

Figura 14 – Diagrama .bdf para simulação da busca de dados (incluindo busca de instrução e decodificação).

Utilizaremos o seguinte programa de teste para a simulação:

Programa 2 – Programa de teste simples para simular a busca de dados

Assembly Binário
000 Add R1 R2 000 011100010010xxxx
001 Loadx R3 &00000001 001 1100001100000001
002 Mov R1 R0 002 010000010000xxxx
A memória RAM foi definida iniciando com o valor 5 (0101) na posição 00000001, e os registradores possuem 2
(0010) em R1, 3 (0011) em R2 e 12 em R0 (1100) (o valor de R3 é irrelevante no momento). Temos o seguinte
resultado:
29

Figura 15 – Resultados da simulação da busca de dados

Vemos que a os dados corretos foram colocados na saída da unidade fetchdata, seguindo a estrutura de
pipeline.

4.4 Operações na ULA


Testaremos agora o funcionamento da unidade de execução (exec), que realiza as operações necessárias e
envia os resultados e endereços de armazenamento para a unidade store. Temos um programa de teste que
utilizará todas as instruções da CPU (menos Output, que é semelhante a Storex na execução), embora ele pode
não funcionar quando adicionarmos a unidade store, já que não respeita a condição de não fazer acessos
simultâneos em mesmas posições da memória ou registradores.

Programa 3 – Teste de operações na ULA (sem respeitar as condições de acesso à memória e banco de registradores).

Assembly Binário
000 Add R1 R2 000 011100010010xxxx
001 Sub R3 R4 001 011000110100xxxx
002 Sub R5 R6 002 011001010110xxxx
003 Mov R1 R0 003 010000010000xxxx
004 Set R0 10 004 010100001010xxxx
005 Jie 00000110 005 100000000110xxxx
006 Loadx R7 &00000001 006 1100011100000001
007 Store R8 &00000010 007 1101100000000010

A posição de memória &00000001 terá o valor 1011(11). Temos a seguinte configuração nos registradores:

R0 R1 R2 R3 R4 R5 R6 R7 R8
0010(2) 0011(3) 0100(4) 0110(6) 0101(5) 0111(7) 0111(7) 1001(9) 1010(10)

Diagrama .bdf:
30

Figura 16 – Diagrama .bdf para simulação da execução das instruções (incluindo estágios anteriores)
31

Resultados:

Figura 17 - Resultados da simulação da execução de instruções

Observando os resultados da simulação, vemos que a execução das operações aritméticas e a passagem dos
valores necessários à unidade store ocorreu de maneira correta.

4.5 Armazenamento
Tendo examinado quatro unidades de controle do pipeline, partiremos para a última, que completará o projeto
da CPU. Assim, nesta simulação testaremos a CPU por inteiro e seu comportamento com um programa (Programa 4
– Programa genérico para teste geral do pipeline sem o uso da instrução JIE. que não possui instruções do tipo
Jump If Equal. O banco de registradores e a memória RAM começarão com todas as posições em zero.

Observando o programa, temos que a situação final dos registradores será R0=0, R1=5, R2=5 e R3=10.

Programa 4 – Programa genérico para teste geral do pipeline sem o uso da instrução JIE.

Assembly Binário
000 Set R1 5 000 010100010101xxxx
001 Set R2 5 001 010100100101xxxx
002 Nop 002 0000xxxxxxxxxxxx
003 Stx R1 &001 003 1101000100000001
004 Nop 004 0000xxxxxxxxxxxx
005 Add R1 R2 005 011100010010xxxx
006 Set R3 10 006 010100111010xxxx
007 Nop 007 0000xxxxxxxxxxxx
008 Mov R0 R1 008 010000000001xxxx
009 Lox R1 &001 009 1100000100000001
010 Nop 010 0000xxxxxxxxxxxx
011 Sub R0 R3 011 011000000011xxxx
012 Output R1 012 11110001xxxxxxxx
013 Output R2 013 11110010xxxxxxxx
014 Output R3 014 11110011xxxxxxxx
015 Output R0 015 11110000xxxxxxxx
32

Diagrama .bdf:
33

Figura 18 - Diagrama .bdf para simulação do armazenamento (incluindo estágios anteriores)


34

Resultados (lembrando que OpcodeOut é a saída em exec):

Figura 19 - Resultados da simulação do armazenamento na CPU.

Observando os valores dos sinais de WriteEnable (Wren), os dados que estão sendo gravados, a retirada dos
mesmos, e por fim, os resultados em Output, vemos que o programa funcionou corretamente.

Este é um bom momento para verificarmos o comportamento do simulador caso retiremos as instruções Nop,
fazendo com que haja leituras e escritas simultâneas nas mesmas posições da memória e do banco de registradores.
Temos o programa:
35

Programa 5 – Semelhante ao Programa 4 – Programa genérico para teste geral do pipeline sem o uso da instrução JIE. mas sem o
tratamento especial para pipeline (remove-se as instruções Nop).

Assembly Binário
000 Set R1 5 000 010100010101xxxx
001 Set R2 5 001 010100100101xxxx
002 Stx R1 &001 002 1101000100000001
003 Add R1 R2 003 011100010010xxxx
004 Set R3 10 004 010100111010xxxx
005 Mov R0 R1 005 010000000001xxxx
006 Lox R1 &001 006 1100000100000001
007 Sub R0 R3 007 011000000011xxxx
008 Output R1 008 11110001xxxxxxxx
009 Output R2 009 11110010xxxxxxxx
010 Output R3 010 11110011xxxxxxxx
011 Output R0 011 11110000xxxxxxxx

Temos um trecho da simulação:

Figura 20 – Simulação do pipeline com o programa 4 (sem tratamento de leituras/escritas em posições iguais)

Observe os valores em Result e Output de R1 – o simulador do Quartus II detectou as leituras e escritas


simultâneas e encontrou erros nos dados, representando-os como “XXXX”.

4.6 Jumps e Condicionais


Por fim, testaremos o funcionamento da CPU com a inclusão da instrução Jump If Equal, que deverá zerar todas
as unidades quando chegar ao fim da execução, evitando a execução de instruções posteriores a ela. O diagrama
.bdf para o teste é igual ao utilizado na seção 4.5. A memória de dados e o banco de registradores começarão todos
zerados. Temos o programa (um loop infinito):
36

Programa 6 – Programa de teste para a instrução JIE no pipeline. As instruções após 008 serão utilizadas como prova de que a CPU não as
executa (caso elas executem, os valores serão acusados no registrador Output).

Assembly Binário
000 Set R0 5 000 010100000101xxxx
001 Set R1 15 001 010100011111xxxx
002 Set R2 10 002 010100101010xxxx
003 Nop 003 0000xxxxxxxxxxxx
004 Sub R1 R0 004 011000010000xxxx
005 Nop 005 0000xxxxxxxxxxxx
006 Nop 006 0000xxxxxxxxxxxx
007 Sub R2 R1 007 011000100001xxxx
008 JIE &001 008 100000000001xxxx
009 Output R0 009 11110000xxxxxxxx
010 Output R1 010 11110001xxxxxxxx
011 Output R2 011 11110010xxxxxxxx

A simulação deste programa nos retorna os seguintes resultados:


37

Figura 21 – Resultados da simulação do pipeline com um programa envolvendo Jump If Equal.

Observe o retorno do valor de FetchOut a “010100011110000”, a instrução &001. O Jump If Equal


ocorreu. Também observa-se que o valor de Output permanece inalterado, demonstrando que nenhuma instrução
após JIE é realizada (pelo menos não o suficiente até que altere algum valor nos registradores ou memória).

5. Discussão e Conclusão
A CPU 4 bits projetada ainda contém pouquíssimas funcionalidades e não pode executar vários tipos de
programas por falta de recursos ou instruções. Por exemplo, neste trabalho não fizemos nenhum tratamento de
operações com números negativos ou flags de carry em caso de estouro nas operações aritméticas. Não obstante,
também não é possível definir o flag CmpFlag sem utilizar a instrução Sub, o que adiciona enormes dificuldades na
construção de estruturas condicionais e de loops, tornando programas simples, de certa forma, em programas
enormes e com muita manipulação aritmética. No entanto, a CPU com pipeline ainda assim é mais eficiente do que
uma implementação comum, sem pipeline, de uma CPU 4 bits.

Apesar de ser um projeto simples, a realização do trabalho trouxe uma maior familiarização com a linguagem de
descrição de hardware VHDL e com as etapas de projeto e design de um processador. O grupo achou muito
interessante e vislumbrou de perto os desafios enfrentados por grandes projetos e novas ideias de arquiteturas.
38

Bibliografia

[1] PATTERSON, HENNESY: Computer Organization and Design, 3rd edition


[2] Altera Quartus II Help (incluído na instalação do software)
[3] Altera Quartus II Tutorial, http://lslwww.epfl.ch/pages/teaching/cours_lsl/sl_info/Labo5.b.pdf
[4] Wikipedia, http://pt.wikipedia.org/wiki/Pipeline