Documente Academic
Documente Profesional
Documente Cultură
Prof. Responsável:
Jorge Luiz e Silva
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
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.
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.
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.
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
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
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).
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.
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.
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
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:
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
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.
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;
LIBRARY ieee;
USE ieee.std_logic_1164.all;
entity decode is
port(
Clk : in std_logic;
Clear : in std_logic;
LIBRARY ieee;
USE ieee.std_logic_1164.all;
entity fetchdata is
port(
Clk : in std_logic;
Clear : in std_logic;
4
fetchdata é melhor visualizado no diagrama .bdf, e não na descrição VHDL em si.
16
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;
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);
-- Valor Output
OutputData : out std_logic_vector(3 downto 0) );
end store;
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
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;
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;
BEGIN
b2v_inst : progrom
PORT MAP(clock => InvClk,
address => SYNTHESIZED_WIRE_0,
q => SYNTHESIZED_WIRE_1);
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
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.
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
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.
Temos os resultados:
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:
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.
Figura 14 – Diagrama .bdf para simulação da busca de dados (incluindo busca de instrução e decodificação).
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
Vemos que a os dados corretos foram colocados na saída da unidade fetchdata, seguindo a estrutura de
pipeline.
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:
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
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
Figura 20 – Simulação do pipeline com o programa 4 (sem tratamento de leituras/escritas em posições iguais)
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
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