Escolar Documentos
Profissional Documentos
Cultura Documentos
FACULDADE DE TECNOLOGIA - FT
DEPARTAMENTO DE ELETRÔNICA E COMPUTAÇÃO - DTEC
RELATÓRIO DE LABORATÓRIO
Carlos Gabriel
Fábio Arthur Soares
Juliano Salvattore
Nathália Colares
MANAUS
2021
Carlos Gabriel
Fábio Arthur Soares
Juliano Salvattore
Nathália Colares
RELATÓRIO DE LABORATÓRIO
PROJETO 2: PROCESSADOR PROGRAMÁVEL EM VHDL
MANAUS
2021
SUMÁRIO
2
1 Introdução 4
2 Desenvolvimento 5
3 Resultados 15
4 Conclusão 23
Referências Bibliográficas
1 Introdução
3
Como pode-se estudar durante a disciplina, o VHDL, que é uma linguagem de
descrição de hardware, é bastante utilizado tanto para síntese, simulação, especificação, e
propriedade intelectual. O objetivo deste segundo projeto, é descrever um processador
programável de 4 instruções na linguagem VHDL, e para tal, precisamos elucidar alguns
conceitos.
Um processador programável , que também é chamado de processador de propósito
geral, é um circuito digital que é responsável por armazenar uma memória. Alguns mais
conhecidos são:Atmel, Intel, ARM, Ryzen/AMD etc. Um processador contém dois blocos
principais: a unidade de controle (Control Unit) e o Caminho de dados (Datapath), como
pode-se observar na imagem abaixo:
Fonte: Embarcados(2015)
O bloco de controle é uma máquina de estados finitos ou FSM, que como sabemos,
possui entradas e saídas; estados; ações executadas por esses estados e transições decorrentes
da mudança desses estados. Como o nome sugere, o bloco de controle comanda: o caminho de
dados; memórias e outros dispositivos que possuem entrada e saída, as chamadas I/O’s. O
bloco de Caminho de dados, realiza prioritariamente o armazenamento dos dados, operações,
podendo ser: aritmética, multiplicação, rotação de bits e etc, e envolve tipos de dados que não
sejam booleanos.
2 Desenvolvimento
Para desenvolver o código com as instruções desejadas no código VHDL (de acordo
com a tabela abaixo), precisamos declarar e definir cada instrução, onde as mesmas serão
4
armazenadas na Memória de Instruções. A unidade de Controle, lê as instruções definidas e as
executa. Nesse mesmo bloco, temos:
➢ IR, o Registrador de Instruções - que vai guardar a próxima instrução que deve ser
executada. Por exemplo, a unidade de controle está executando a instrução: D[1] = 3 ,
o IR, então sabe que a próxima instrução a ser executada é a D[2] = 4.
➢ PC, o Contador de programa - um contador que guarda a localização da próxima
instrução que será executada.
Memória de Dados
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity data_mem is
port(clk: in std_logic;
D_addr: in std_logic_vector(7 downto 0);
D_rd, D_wr: in std_logic;
W_data: in std_logic_Vector(15 downto 0);
R_data: out std_logic_Vector(15 downto 0)
);
end data_mem;
architecture bhv_data_mem of data_mem is
signal i: integer;
type data_mem is array (0 to 255) of std_logic_vector (15
downto 0);
signal RAM: data_mem;
begin
process(D_addr, D_rd, D_wr, W_data)
begin
if (rising_edge(clk)) then
if (D_wr='1') then
RAM(to_integer(unsigned(D_addr))) <= W_data;
end if;
end if;
end process;
R_data <= RAM(to_integer(unsigned(D_addr))) when (D_rd='1')
else x"0000";
end bhv_data_mem;
Em entity definimos:
Como temos uma entrada vinda da memória de dados de 8 bits, que pode ser
observado em (7 downto 0) e uma saída de 16 bits (15 downto 0), significa que pode-se
guardar até 256 palavras. Já na parte de architecture convertemos o sinal de D_addr para
inteiro, além de pegar o valor de W_data e adicioná-lo na RAM, que é o data_mem, quando
tivermos uma subida no pulso de clock (clk) e o sinal de D_wr for igual a 1.
ULA
library ieee;
use ieee.std_logic_1164.all;
--use ieee.std_logic_signed.all;
use ieee.std_logic_unsigned.all;
entity alu is
port(a,b : in std_logic_vector(15 downto 0);
alu_s0 : in std_logic_vector(2 downto 0);
alu_result: out std_logic_vector(15 downto
0);
zero: out std_logic -- Flag que identificar
resultado zero
);
end alu;
architecture bhv_alu of alu is
signal result: std_logic_vector(15 downto 0);
begin
process(a, b, alu_s0)
begin
case alu_s0 is
when "001" => result <= a + b; -- operacao Soma
when "010" => result <= a - b; -- operacao Subtracao
when "011" => result <= a and b; -- operacao and
when "100" => result <= a or b; -- operacao OR
when "101" => result <= not (a); -- operacao NOT
when "110" => result <= not(a) + '1'; -- Operacao
Complemento2 = CMP1 + '1'
when others => result <= x"0000";
end case;
end process;
zero <= '1' when result = x"0000" else '0';
alu_result <= result;
end bhv_alu;
Banco de Registradores
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity register_file is
port(clk,rst: in std_logic;
RF_W_wr: in std_logic; -- sinal de Ra escrita
RF_W_addr: in std_logic_vector(3 downto 0); -- endereco
de Ra
W_data: in std_logic_vector(15 downto 0); -- endereco de
entrada de dados do reg
RF_Rp_addr: in std_logic_vector(3 downto 0); -- endereco
de Rb
RF_Rp_rd: in std_logic; -- sinal de Rb leitura
Rp_data: out std_logic_vector(15 downto 0); -- endereco
de saida de dados do reg
RF_Rq_addr: in std_logic_vector(3 downto 0); -- endereco
de Rc
RF_Rq_rd: in std_logic; -- sinal de Rb leitura
Rq_data: out std_logic_vector(15 downto 0) -- endereco Rc
);
end register_file;
architecture bhv_register_file of register_file is
type reg_type is array (0 to 16) of std_logic_vector (15 downto
0);
signal reg_array: reg_type;
begin
process(clk,rst)
begin
if(rst='1') then
for i in 0 to 15 loop
reg_array(i) <= x"0000";
end loop;
elsif(rising_edge(clk)) then
if(RF_W_wr = '1') then
reg_array(to_integer(unsigned(RF_W_addr))) <=
W_data;
end if;
end if;
end process;
Rp_data <= reg_array(to_integer(unsigned(RF_Rp_addr))) when
RF_Rp_rd = '1' else x"0000";
Rq_data <= reg_array(to_integer(unsigned(RF_Rq_addr))) when
RF_Rq_rd = '1' else x"0000";
end bhv_register_file;
7
O código do Banco de Registradores estabelece os sinais de entrada “W_data” que será a
informação a ser armazenada, “RF_W_addr” o endereço onde ela será armazenada e
“RF_W_wr” que indicará que a informação será escrita. Assim como também insere os sinais
de saída “RF_Rq_rd” que indicará que a informação será lida, “RF_Rq_addr” que determina
o endereço de onde ela será lida e “Rq_data” que será a saída da informação.
Unidade de Controle
Como vimos sobre, nessa parte, que é a Unidade de Controle (Control Unit) é a
responsável por validar a leitura na Memória de Instruções e então executar as instruções. As
atividades são realizadas em três fases:
➢ Busca: Que carrega a próxima instrução no IR; O PC, que irá apresentar a
próxima instrução que deverá ser realizada; E o IR, que guarda a instrução que
deverá ser executada.
➢ Decodificação: Irá determinar em qual operação está sendo especificada na
instrução guardada.
➢ Execução: Esta irá alimentar os componentes que deverão ser utilizados da
ULA para a devida operação;
8
opcode := ir(15 downto 12);
if (reset = '1') then
D_rd <= '0';
D_wr <= '0';
RF_s <= "00";
RF_w_wr <= '0';
RF_rp_rd <= '0';
RF_rq_rd <= '0';
alu_sl0 <= "000";
else
case opcode is
when "0000" => -- MOV Ra, d
D_rd <= '1';
D_wr <= '0';
RF_s <= "00";
RF_w_addr <= ir(11 downto 8);
RF_w_wr <= '1';
RF_rp_rd <= '0';
RF_rq_rd <= '0';
alu_sl0 <= "000";
when "0001" => -- MOV d, Rb
D_rd <= '0';
D_wr <= '1';
RF_s <= "01";
RF_w_wr <= '0';
RF_rp_addr <= ir(11 downto 8);
RF_rp_rd <= '1';
RF_rq_rd <= '0';
alu_sl0 <= "000";
when "0010" => -- ADD Ra, Rb, Rc
D_rd <= '0';
D_wr <= '0';
RF_s <= "10";
RF_w_addr <= ir(11 downto 8);
RF_w_wr <= '1';
RF_rp_addr <= ir(7 downto 4);
RF_rp_rd <= '1';
RF_rq_addr <= ir(3 downto 0);
RF_rq_rd <= '1';
alu_sl0 <= "001";
when "0011" => -- MOV Ra, #c
D_rd <= '0';
D_wr <= '0';
RF_s <= "11";
RF_w_addr <= ir(11 downto 8);
RF_w_wr <= '1';
RF_rp_rd <= '0';
RF_rq_rd <= '0';
alu_sl0 <= "000";
when "0100" => -- SUB RA, Rb, RC
D_rd <= '0';
D_wr <= '0';
RF_s <= "10";
RF_w_addr <= ir(11 downto 8);
RF_w_wr <= '1';
RF_rp_addr <= ir(7 downto 4);
RF_rp_rd <= '1';
RF_rq_addr <= ir(3 downto 0);
RF_rq_rd <= '1';
alu_sl0 <= "010";
when "0101" => -- AND Ra, Rb, RC
9
D_rd <= '0';
D_wr <= '0';
RF_s <= "10";
RF_w_addr <= ir(11 downto 8);
RF_w_wr <= '1';
RF_rp_addr <= ir(7 downto 4);
RF_rp_rd <= '1';
RF_rq_addr <= ir(3 downto 0);
RF_rq_rd <= '1';
alu_sl0 <= "011";
when "0110" => -- OR Ra, Rb, RC
D_rd <= '0';
D_wr <= '0';
RF_s <= "10";
RF_w_addr <= ir(11 downto 8);
RF_w_wr <= '1';
RF_rp_addr <= ir(7 downto 4);
RF_rp_rd <= '1';
RF_rq_addr <= ir(3 downto 0);
RF_rq_rd <= '1';
alu_sl0 <= "100";
when "0111" => -- NOT Ra
D_rd <= '0';
D_wr <= '0';
RF_s <= "10";
RF_w_addr <= ir(11 downto 8);
RF_w_wr <= '1';
RF_rp_addr <= ir(7 downto 4);
RF_rp_rd <= '1';
RF_rq_addr <= "0000";
RF_rq_rd <= '0';
alu_sl0 <= "101";
when "1000" => -- CMP Ra
D_rd <= '0';
D_wr <= '0';
RF_s <= "10";
RF_w_addr <= ir(11 downto 8);
RF_w_wr <= '1';
RF_rp_addr <= ir(7 downto 4);
RF_rp_rd <= '1';
RF_rq_addr <= "0000";
RF_rq_rd <= '0';
alu_sl0 <= "110";
when others =>
D_rd <= '0';
D_wr <= '0';
RF_s <= "XX";
RF_w_wr <= '0';
RF_rp_rd <= '0';
RF_rq_rd <= '0';
alu_sl0 <= "000";
end case;
end if;
end process;
end bhv_control_unit;
Como podemos ver no código acima, a unidade de controle possui apenas uma
entrada, que neste caso é o IR, o qual possui 16 bits como mencionado anteriormente, e mais
10 saídas que variam entre 2 e 4 bits. Algumas das saídas presentes são:
10
➢ RF_w_addr: Mostra o endereço do multiplexador;
➢ RF_w_wr: Mostra a informação que será armazenada no registrador;
➢ RF_s: Irá selecionar o multiplexador correspondente;
➢ alu_s10: Irá mostrar qual operação será realizada pela ULA.
De acordo com o valor retornado pela variável opcode, que pega os primeiros 4 bits, ele é o
que vai especificar qual instrução deve ser feita. A primeira condicional (if-else) no código
verifica se o processador foi resetado, fazendo assim o opcode retornar o valor 1, porém
quando retorna o valor 0, é que entramos na segunda condicional, que irá verificar pra qual
instrução aquele valor deve ser realizado.
Memória de Instruções
library ieee;
use ieee.std_logic_1164.ALL;
use ieee.numeric_std.all;
entity instruction_mem is
);
end instruction_mem;
"0000000000000000",
"0000000000000000",
"0000000000000000",
11
"0000000000000000",
"0000000000000000",
"0000000000000000",
"0000000000000000",
"0000000000000000");
begin
end bhv_instruction_mem;
Processador
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_signed.all;
entity processador is
port(clk: in std_logic;
reset: in std_logic;
pc_out: out std_logic_vector(3 downto 0);
alu_res_out: out std_logic_vector(15 downto 0)
);
end processador;
architecture bhv_processador of processador is
signal pc_next: std_logic_vector(3 downto 0);
signal ir: std_logic_vector(15 downto 0);
signal data_rd: std_logic;
signal data_wr: std_logic;
signal reg_sel: std_logic_vector(1 downto 0);
signal reg_w_addr: std_logic_vector(3 downto 0);
signal reg_w_wr: std_logic;
signal reg_w_data: std_logic_vector(15 downto 0);
signal reg_rp_addr: std_logic_vector(3 downto 0);
signal reg_rp_rd: std_logic;
signal reg_rp_data: std_logic_vector(15 downto 0);
signal reg_rq_addr: std_logic_vector(3 downto 0);
signal reg_rq_rd: std_logic;
signal reg_rq_data: std_logic_vector(15 downto 0);
signal alu_sel: std_logic_vector(2 downto 0);
12
signal alu_out: std_logic_vector(15 downto 0);
signal zero_flag: std_logic;
signal read_data: std_logic_vector(15 downto 0);
begin
process(clk,reset)
begin
if (reset='1') then
pc_next <= "0000";
elsif rising_edge(clk) then
pc_next <= pc_next + '1';
-- Saidas
pc_out <= pc_next;
alu_res_out <= alu_out;
end if;
end process;
-- Instrucao (IR) da Memoria de Instrucoes
Instruction_Memory: entity work.instruction_mem port map(
pc=> pc_next,
instruction => ir
);
-- Sinais de Controle (Unidade de Controle)
Control: entity work.control_unit port map(
reset => reset,
ir => ir,
D_rd => data_rd,
D_wr => data_wr,
RF_s => reg_sel,
RF_w_addr => reg_w_addr,
RF_w_wr => reg_w_wr,
RF_rp_addr => reg_rp_addr,
RF_rp_rd => reg_rp_rd,
RF_rq_addr => reg_rq_addr,
RF_rq_rd => reg_rq_rd,
alu_sl0 => alu_sel
);
-- Multiplexador
process(reg_sel)
begin
if reg_sel = "00" then
reg_w_data <= read_data;
end if;
if reg_sel = "01" then
reg_w_data <= reg_rp_data;
end if;
if reg_sel = "10" then
reg_w_data <= alu_out;
end if;
if reg_sel = "11" then
reg_w_data <= "00000000" & ir(7 downto 0); --
constante
end if;
end process;
-- Register File
Reg_file: entity work.register_file port map(
clk => clk,
rst => reset,
RF_W_wr => reg_w_wr,
RF_W_addr => reg_w_addr,
W_data => reg_w_data,
RF_Rp_addr => reg_rp_addr,
RF_Rp_rd => reg_rp_rd,
13
Rp_data => reg_rp_data,
RF_Rq_addr => reg_rq_addr,
RF_Rq_rd => reg_rq_rd,
Rq_data => reg_rq_data
);
-- ULA
Alu_Process: entity work.alu port map (
a => reg_rp_data,
b => reg_rq_data,
alu_s0 => alu_sel,
alu_result => alu_out,
zero => zero_flag
);
-- Memoria de Dados
Data_Memory: entity work.data_mem port map(
clk => clk,
D_addr => ir(7 downto 0),
D_rd => data_rd,
D_wr => data_wr,
W_data => reg_w_data,
R_data => read_data
);
end bhv_processador;
Temos duas entradas clock e reset, e duas saídas pc_out, que exibe qual a última
posição do contador de programa. e a variável alu_res_out para apresentar o resultado de uma
operação. Caso o processador seja resetado ou seja, reset =’1’, então o contador do nosso
programa, declarado como pc_next é zerado, do contrário é lido o sinal de borda de subida do
clock, que incrementa +1 na variável pc_next.
3 Resultados
TESTE 1
14
TESTE 2
TESTE 3
15
TESTE 4
TESTE 5
16
TESTE 6
Resultado do Teste 01
Como é possível notar, após 13 ciclos de clock foi realizada a operação soma com
1001.
17
Resultado do Teste 01
Aqui para o segundo resultado do Teste 01, temos que após 14 ciclos de clock a
operação realizada foi a subtração do número 1010 para 0110.
Resultado do Teste 02
Já neste caso, temos a saída "0010", referente a operação lógica AND, que ocorre
após 10 ciclos com o número “0110”.
Resultado do Teste 02
Resultado do Teste 03
Resultado do Teste 03
19
Já neste caso, o número “0000000000000011” saiu como resultado após 13 ciclos, se tratando
da operação OR aplicada no número “1001”.
Resultado do Teste 04
Para o teste 4, que a cada 14 ciclos realiza a instrução de ADD que é a R3, R2 e R3,
retornou o valor “0000000000101010”.
Resultado do Teste 05
Para o teste 5, na instrução para a operação SUB, temos que R3, R2 e R3, retornaram
o valor “0000000000100011”.
20
Resultado do Teste 06
Para o primeiro resultado do teste 06, o número ‘0000000010010001” foi obtido após
12 ciclos de clock, o que indica o uso da operação lógica AND
Resultado do Teste 6
Resultado do Teste 06
Ainda no teste 6, agora para a operação da instrução NOT, de R4 e R2, tal operação
retornou o valor “1111111111011000”.
21
4 Conclusão
Referências Bibliográficas
22
FERRARI, Rafael. Processadores de Propósito Geral. Unicamp. Campinas, 2017. Disponível
em: <https://www.dca.fee.unicamp.br/~rferrari/EA075_2s2017/Cap.%203%20-
%20Processadores_Proposito_Geral.pdf > .Acesso em: 26 de nov. de 2021.
23