Universidade de Brası́lia (UnB)
Departamento de Ciência da Computação (CIC)
Organização e Arquitetura de
Computadores
Laboratório 2 - CPU RISC-V UNICICLO
Professor:
Prof. Carla Maria Chagas e Cavalcante Koike
Autores:
Felipe de Moraes Vilhena 211066042
Isabela de Souza Climaco 190088931
João Marcos Melo Monteiro 130143031
Luis Felipe Hilario Carmona 212007153
Luis Fernando Gomes Alvim 211066285
Brası́lia-DF
10 de novembro de 2025
Conteúdo
1 Questão 1 1
1.0.1 b) Levante os requisitos fı́sicos e temporais do seu processador com-
pleto. Verifique se os slacks de setup e hold estão sendo cumpridos. 11
[12pt]article [utf8]inputenc [T1]fontenc lmodern geometry a4paper, margin=2cm array
booktabs graphicx
1 Questão 1
Figura 1: Implemente o processador Uniciclo com ISA Reduzida com as instruções: add,
sub, and, or, slt, lw, sw, beq, jal, e ainda as instruções jalr, addi e lui.
O projeto [Link] possui o arcabouço para o desenvolvimento e teste do seu pro-
cessador.
1.1 – Análise do Programa de Teste (de1.s)
Objetivo
Validar a implementação das instruções da ISA RV32I reduzida por meio da execução
do programa de1.s no simulador RARS. O programa foi projetado para exercitar todas
as instruções requeridas (9 + 3) e utiliza o registrador t0 (x5) como referência para
observação dos resultados.
Especificação funcional
• Entradas: Código em Assembly RISC-V (de1.s) contendo instruções nos formatos
R, I, S, B, U e J.
• Saı́das: Valores esperados no registrador t0 após cada instrução, confirmando a
corretude da execução.
1
Resumo das instruções testadas
• R-type: add, sub, and, or, slt
• I-type: addi, jalr (via ret), lw
• S-type: sw
• B-type: beq
• U/J-type: lui (comentada), jal
Análise do código
O programa inicia carregando o ponteiro gp para a área de dados (0x10010000) e
executa operações aritméticas, lógicas, comparação, desvio condicional, salto e acesso à
memória. O fluxo principal é:
1. Carregar t1 com a palavra 0xFFFFFF0F da memória.
2. Inicializar t2 com 0x00000777.
3. Executar operações sobre t0: and, or, add, sub, slt (duas vezes).
4. Testar desvio com beq.
5. Chamar procedimento via jal e retornar com ret.
6. Realizar operações de carga e armazenamento (lw, sw).
7. Finalizar com um loop infinito (FIM: j FIM), mantendo o último valor de t0.
Resultados esperados em t0
Etapa Instrução Valor esperado
1 and t0,t1,t2 0x00000707
2 or t0,t1,t2 0xFFFFFF7F
3 add t0,t2,t1 0x00000686
4 sub t0,t2,t1 0x00000868
5 slt t0,t1,t2 0x00000001
6 slt t0,t2,t1 0x00000000
7 Após PROC 0x0000007F, 0xFFFFFF0F, 0x0000007F
Final addi t0,zero,0xFFFFFCCC 0xFFFFFCCC
Conclusão
O programa de1.s cobre todas as instruções exigidas e permite validar a CPU uni-
ciclo. A sequência de valores em t0 confirma a execução correta das operações lógicas,
aritméticas, de desvio e acesso à memória. O valor final (0xFFFFFCCC) serve como re-
ferência para testes no hardware.
2
1.2 - Banco de Registradores (3 leituras simultâneas)
Objetivo
Implementar o Banco de Registradores RV32I com 32 registradores de 32 bits (x0..x31),
com: (i) três leituras assı́ncronas simultâneas (rs1, rs2 e disp para debug/visua-
lização), (ii) uma escrita sı́ncrona em rd, (iii) x0 sempre zero (escrita ignorada), e
(iv) sp (x2) inicializado em 0x1001 03FC no reset, conforme enunciado.
Especificação funcional
• Entradas
– clk: clock (borda de subida).
– rst: reset (sı́ncrono ativo em 1).
– we: habilita escrita.
– wa[4:0]: endereço de escrita (write address, registrador rd).
– wd[31:0]: dado de escrita (write data).
– ra1[4:0], ra2[4:0], ra3[4:0]: endereços de leitura (rs1/rs2/disp).
• Saı́das
– rd1[31:0], rd2[31:0], rd3[31:0]: dados lidos de ra1, ra2, ra3.
Implementação em Verilog
‘timescale 1ns/1ps
module regfile #(
parameter XLEN = 32
)(
input wire clk,
input wire rst,
input wire we,
input wire [4:0] wa,
input wire [XLEN-1:0] wd,
input wire [4:0] ra1,
input wire [4:0] ra2,
input wire [4:0] ra3,
output wire [XLEN-1:0] rd1,
output wire [XLEN-1:0] rd2,
output wire [XLEN-1:0] rd3
);
reg [XLEN-1:0] regs 0:31[1]();
integer i;
3
always @(posedge clk) begin
if (rst) begin
for (i = 0; i < 32; i = i + 1)
regs[i] <= {XLEN{1’b0}};
regs[2] <= 32’h1001_03FC;
end else begin
if (we && (wa != 5’d0)) begin
regs[wa] <= wd;
end
end
end
wire bypass_ra1 = we && (wa == ra1) && (wa != 5’d0);
wire bypass_ra2 = we && (wa == ra2) && (wa != 5’d0);
wire bypass_ra3 = we && (wa == ra3) && (wa != 5’d0);
assign rd1 = (ra1 == 5’d0) ? {XLEN{1’b0}} :
(bypass_ra1) ? wd : regs[ra1];
assign rd2 = (ra2 == 5’d0) ? {XLEN{1’b0}} :
(bypass_ra2) ? wd : regs[ra2];
assign rd3 = (ra3 == 5’d0) ? {XLEN{1’b0}} :
(bypass_ra3) ? wd : regs[ra3];
endmoduleule
Testbench e verificação
• Abordagem: Estı́mulos cobrem (i) reset e sp, (ii) escrita/leitura, (iii) tentativa de
escrita em x0, (iv) bypass de leitura durante escrita.
• Critérios de aceitação:
– Após o reset, x0 = 0 e x2 = 0x1001 03FC.
– Escrita em x5 persiste após a borda de clock.
– Escrita em x0 é ignorada.
– Durante escrita em wa == ra*, as leituras rd* devolvem wd (bypass).
Integração com o datapath
• Conectar rd1 e rd2 como operandos A/B da ULA; ra1 = rs1, ra2 = rs2.
• ra3 recebe o ı́ndice do registrador que será observado (por exemplo, via switches);
rd3 segue para o bloco de display/debug (ex.: mostrar t0 = x5).
• A escrita (we, wa, wd) vem do write-back (ALU/Memória/PC+4) controlado pelo
bloco de controle.
4
• O reset deve estar sincronizado com o restante do sistema; sp (x2) inicia em
0x1001 03FC.
Observações importantes
• x0 imutável: toda tentativa de escrita em x0 é ignorada (hardware força zero).
• Leituras assı́ncronas: necessárias no uniciclo para ter operandos disponı́veis no
mesmo ciclo em que a instrução é decodificada.
• Bypass de leitura: resolve dependência no mesmo ciclo entre a porta de escrita e
qualquer porta de leitura (ra1/ra2/ra3).
• Inicialização do sp: x2 recebe 0x1001 03FC no reset, conforme enunciado.
1.3 - (Gerador de Imediatos)
Objetivo
Descrever a implementação do gerador de imediatos (Immediate Generator) requerido
pela ISA RV32I reduzida. O módulo deve extrair e estender corretamente os campos de
imediato das instruções RISC-V nos formatos: I, S, B, U e J, produzindo sinais de 32 bits
com sinal estendido quando aplicável.
Especificação funcional
• Entradas:
– instr[31:0]: palavra de instrução (32 bits).
– imm sel[2:0]: seletor de tipo de imediato (codificação local: I=000, S=001,
B=010, U=011, J=100).
• Saı́das:
– imm out[31:0]: imediato resultante, sinal estendido quando necessário.
Tabelas de formatos (resumo)
I-type imm[11:0] = instr31:20[1]() (sign-extend)
S-type imm[11:0] = instr31:25[2]() <<5, — instr[11:7] (sign-extend)
B-type imm[12:1] = instr[31], instr[7], instr30:25[3](), instr[11:8] <<1 (sign-extend
com LSB zero)
U-type imm31:12[4]() = instr31:12[5]() (upper 20 bits; sem extensão de sinal para des-
locamento de 20 bits)
J-type imm[20:1] = instr[31], instr19:12[6](), instr[20], instr30:21[7]() <<1 (sign-
extend)
5
Implementação em Verilog
Inclua o seguinte módulo no seu projeto (exemplo claro e compacto):
// immediate_generator.v module immediate_generator ( input wire [31:0] instr,
input wire [2:0] imm_sel,
// 000=I,001=S,010=B,011=U,100=J output reg [31:0] imm_out );
wire [31:0] imm_i, imm_s, imm_b, imm_u, imm_j;
assign imm_i = {{20{instr[31]}}, instr31:20[8]()};
assign imm_s = {{20{instr[31]}}, instr31:25[9](), instr[11:7]};
assign imm_b = {{19{instr[31]}}, instr[31], instr[7], instr30:25[10](), instr[11:8], 1
assign imm_u = {instr31:12[11](), 12’b0};
assign imm_j = {{11{instr[31]}}, instr[31], instr19:12[12](), instr[20], instr30:21[13
always @(*) begin
case (imm_sel)
3’b000: imm_out = imm_i; // I
3’b001: imm_out = imm_s; // S
3’b010: imm_out = imm_b; // B
3’b011: imm_out = imm_u; // U
3’b100: imm_out = imm_j; // J
default: imm_out = 32’b0;
endcase
end
endmodule
Testbench e verificação
• Escreva um testbench simples que injete instruções exemplo cobrindo cada formato
e compare imm out com valores esperados.
• Gere formas de onda (ModelSim/Quartus) e inclua trechos no relatório: cada teste
deve mostrar a instrução de entrada, o seletor imm sel e a saı́da imm out com a
extensão correta.
• Exemplo de vetores de teste (valores em hex):
– I-type: instr = 0xFFF70713 (addi x14,x14,-1) -> imm = 0xFFFFFFFF
– S-type: instr = 0x00F78223 (sw x15,4(x15)) -> imm = 0x00000004
– B-type: instr = 0xFE079AE3 (beq x15,x0,-24) -> imm = 0xFFFFFFE8
– U-type: instr = 0x000FF037 (lui x0,0x000FF) -> imm = 0x000FF000
– J-type: instr = 0x004000EF (jal x1,4) -> imm = 0x00000004
6
Integração com o datapath
Explique sucintamente como conectar o imm out ao somador da ULA (para cálculos
I-type, load/store), ao gerador do endereço de desvio (B e J) e ao sinal de escrita do
registrador de destino (para JAL/JALR). Indique também o sinal de controle imm sel
vindo do Bloco Controlador.
Observações importantes
• Tenha atenção especial às rotações de bits e ordenação correta ao concatenar os
campos (indices no Verilog).
• Valide casos de sinal negativo (bit 31 = 1) para todos os formatos que exigem
extensão de sinal.
1.4 - Dump de memória (.mif ) e integração no projeto)
Objetivo
Descrever os passos para gerar os arquivos MIF (Memory Initialization File) a partir
do Rars16 Custom2 e integrar os arquivos de1 [Link] e de1 [Link] nas memórias
do seu projeto Quartus/TopDE.
Passos no Rars16 Custom2
1. Abra o RARS16 Custom2 e carregue o seu arquivo de texto (.s) com o programa de
teste (por exemplo, de1.s ou de1 text).
2. Na barra de menu, vá em File → Dump Memory.
3. Escolha o formato MIF 32 Format e defina o endereço de inı́cio do segmento de
texto como 0x00400000 (conforme enunciado).
4. Salve o arquivo sem extensão (nome sugerido: de1) — o RARS criará automatica-
mente de1 [Link] e de1 [Link].
Formato MIF (exemplo)
Abaixo um exemplo reduzido do conteúdo de um arquivo MIF com palavras de 32 bits
(endereços em hexadecimal):
-- de1_text.mif DEPTH = 1024; WIDTH = 32; ADDRESS_RADIX = HEX; DATA_RADIX = HEX; CONT
Explique que o endereço 0x000 no MIF corresponde à primeira palavra do bloco de
memória que o Quartus irá mapear no endereço base definido no seu sistema (no enunci-
ado: .text começa em 0x0040 0000 e .data em 0x1001 0000).
7
Integração no Quartus / TopDE
• Copie os arquivos de1 [Link] e de1 [Link] para a pasta do projeto (por exem-
plo, project/memory/).
• Na criação das memórias (IP Blocks ou blocos RTL já providos no TopDE), configure
o arquivo de inicialização (MIF) para cada memória:
– Memória de instruções (ROM/ROM-like): inicializar com de1 [Link].
– Memória de dados (RAM): inicializar com de1 [Link] (se desejar dados
pré-carregados).
• Verifique os parâmetros: WIDTH = 32, DEPTH = 1024; caso use blocos de memória
do FPGA, certifique-se do endianness e da correspondência de palavra.
Ajuste de clock e latência de memória
Como citado no enunciado: as memórias FPGA quantizadas costumam ter latência
de 2 ciclos para operação de leitura/escrita. Logo:
• Ajuste o clock da CPU para metade da frequência da memória, ou implemente um
estágio extra de latch para acomodar a latência (solução recomendada: dividir o
clock com um flop/contador quando fizer simulação temporal no .vwf).
• Documente no relatório experimental como você tratou a latência: alteração de
CLOCK no arquivo .vwf, inserção de registradores pipeline (se aplicável) ou uso de
controle de enable para memória.
Geração dos arquivos finais
Na entrega, inclua no ZIP:
• [Link] (ou diretório do projeto Quartus completo);
• de1 [Link] e de1 [Link];
• Fontes Verilog (.v) incluindo immediate generator.v;
• Arquivos .vwf usados nas simulações (formas de onda), e screenshots das simulações
e do RTL netlist.
Exemplo: verificação prática
Descreva brevemente (e inclua em relatório):
1. Abra o .vwf no ModelSim/Quartus e rode a simulação funcional com o programa
de1.s carregado.
2. Capture a forma de onda que mostra o fetch das instruções no endereço inicial
(0x0040 0000) e o carregamento de um valor do .data em 0x1001 0000.
3. Mostre que, quando a memória exige 2 ciclos para ler, o valor aparece no ciclo
esperado (documente os ı́ndices de tempo nas imagens).
8
Observações finais
• Sempre verifique o mapeamento de endereços entre o MIF e os módulos de memória
do Quartus;
• Inclua prints do RARS mostrando o dump e os nomes gerados dos arquivos para
evidência;
• Em caso de divergência entre simulação e sı́ntese (comportamento diferente no
FPGA), investigue sinais de inicialização e ’reset’ das memórias.
1.5)
Na questão 1.5 é pedido a implementação da ULA mı́nima necessária, com add,sub,and,
or, slt, zero. Para o controle da ULA foi considerado que a ógica das linhas de controle
descritas na imagem da figura 2. O zero foi definido como a excessão pelo default. Desse
modo foi implementado o código que pode ser visto nas figuras 3 e 4.
Figura 2: Tabela de linhas de comando para o controlador da ULA retirado dos slides da
disciplina.
Figura 3: Primeira parte do código em verilog para a implementação da ULA.
9
Figura 4: Segunda parte do código em verilog para a implementação da ULA.
1.6)
Na questão 1.6 do relatório é pedido a implementação do controlador da ULA e do
banco de controle do processador. Os mesmos podem ser vizualizados nas figuras 3 a 10.
Figura 5: Primeira parte do código em verilog para implementação do controlador da
ULA.
10
Figura 6: Segunda parte do código em verilog para implementação do controlador da
ULA.
1.7)Implemente o Processador Uniciclo Completo
a)Visualize o netlist RTL view.
A figura 11 mostra a visão RTL do módulo TopDE, que representa o sistema completo
incluindo a CPU Uniciclo, memória de instruções e memória de dados.
A figura 12 apresenta a visão expandida do módulo Uniciclo, onde é possı́vel observar
todos os componentes internos do processador e suas interconexões.
A figuras 13 mostram os módulos individuais que compõem o processador:
1.0.1 b) Levante os requisitos fı́sicos e temporais do seu processador com-
pleto. Verifique se os slacks de setup e hold estão sendo cumpridos.
Os requisitos fı́sicos do processador foram obtidos através do relatório de compilação
do Quartus II e são apresentados na Tabela 1.
Recurso Quantidade Utilização
Total logic elements [32 / 114,480] [ ¡1%]
Total registers [31]
Total memory bits [0 / 3,981,312] [0%]
Total pins [108 / 529] [20%]
Tabela 1: Recursos fı́sicos utilizados pela implementação do processador RISC-V uniciclo.
A análise temporal foi realizada utilizando o TimeQuest Timing Analyzer, com os
resultados apresentados na Tabela 2.
Análise dos Resultados:
• Setup Slack: O valor positivo de [VALOR] ns indica que o tempo de estabilização
dos sinais antes da borda de clock é satisfeito, garantindo operação confiável.
11
Figura 7: Terceira parte do código em verilog para implementação do controlador da
ULA.
Parâmetro Valor CLOCK
Setup Slack [-2.155] ns [-0.040]
Hold Slack [-0,427] ns [0.063]
Fmax Máxima [316.96] MHz
Tabela 2: Análise temporal do processador - Slack de setup e hold.
• Hold Slack: O valor positivo de [VALOR] ns assegura que os sinais permanecem
estáveis após a borda de clock, prevenindo metaestabilidade.
• Frequência Máxima: A frequência de [VALOR] MHz representa o limite opera-
cional do processador dentro das restrições de timing.
Conclusão: Os resultados demonstram que o processador implementado atende a
todos os requisitos de timing, com ambos os slacks de setup e hold sendo cumpridos.
A arquitetura desenvolvida é capaz de operar na frequência máxima especificada sem
violações temporais, validando o correto funcionamento do projeto.
b)
c)
d)
12
Figura 8: Primeira parte do código em verilog para implementação do bloco de controle.
Figura 9: Segunda parte do código em verilog para implementação do bloco de controle.
13
Figura 10: Terceira parte do código em verilog para implementação bloco de controle.
Figura 11: Visão RTL do módulo TopDE mostrando a hierarquia completa do sistema.
14
Figura 12: Visão RTL detalhada do módulo Uniciclo com todos os submodules.
Figura 13: Módulos
15