CPU multiciclo e máquina de controlo
Partilhar recursos, seguir estados e sinais e contar ciclos nas instruções executadas.
O CPU multiciclo usa a ALU para calcular PC+4 no fetch e reutiliza-a mais tarde para calcular o endereço de um load. Divide a instrução em etapas e conserva os resultados intermédios em registos internos, e a memória também pode servir código e dados em ciclos diferentes.
Recursos e registos internos
Usamos uma memória partilhada para código e dados, uma ALU e os registos IR, MDR, A, B e ALUOut. IR mantém a instrução; MDR mantém o dado lido; A e B mantêm os operandos lidos do banco; ALUOut mantém um resultado anterior da ALU.
Um resultado disponível na saída de uma unidade combinatória não é o mesmo que o valor já guardado num registo. Por exemplo, ao ler memória num ciclo, o writeback seguinte usa MDR, não uma nova leitura potencialmente diferente.
Para saltos relativos, é necessário conhecer o PC da instrução. A ISA define o destino como PCinst+d, com PCinst o endereço da própria instrução de salto. Neste modelo didático o fetch já fez PC<-PC+4; somar d ao PC avançado daria um destino 4 bytes à frente. Por isso, no decode de beq, o gerador estende d e calcula d-4 para somar ao PC já avançado. Uma organização equivalente guarda PCinst num registo e soma-lhe d. A compensação d-4 é uma adaptação deste modelo, não um campo da folha oficial do CPU.
Seleções e habilitações
| Sinal | Valores |
|---|---|
| IorD | 0: endereço PC; 1: endereço ALUOut |
| ALUSrcA | 0: PC; 1: A |
| ALUSrcB | 00: B; 01: constante 4; 10: imediato |
| PCSource | 0: saída atual da ALU; 1: ALUOut |
| MemtoReg | 0: ALUOut; 1: MDR |
O gerador de imediatos fornece o deslocamento usual para lw e sw. No decode de beq, a compensação d−4 permite usar ALUSrcA=0 e ALUSrcB=10 com o PC avançado. Um circuito alternativo que conserve PCinst precisa de o disponibilizar ao somador do destino. Um desenho sem qualquer uma destas opções desloca incorretamente o branch em quatro bytes.
PC atualiza se . PCWrite permite escrita incondicional; PCWriteCond permite-a apenas na igualdade. IRWrite só se ativa no fetch, para um load não substituir a instrução pelos dados.
Datapath partilhado
No fetch, IorD=0 endereça com o PC e a ALU soma PC+4 com ALUSrcA=0 e ALUSrcB=01. Na leitura de dados, IorD=1 endereça com ALUOut. A e B conservam os operandos lidos no decode; ALUOut conserva o destino do branch enquanto a ALU compara A com B no estado 8.
Exemplo numérico: beq em PCinst=0x00400010 com d=-8. Após o fetch, PC=0x00400014. O decode calcula (d-4)=-12 e faz ALUOut<-0x00400014-12=0x00400008, que é PCinst+d. Sem compensação, obteríamos 0x0040000C, 4 bytes à frente.
Estados e caminhos
| Estado | Ação principal | Controlo ativo ou seleção necessária |
|---|---|---|
| 0, fetch | IR←Mem[PC], PC←PC+4 | MemRead, IRWrite, IorD=0, ALUSrcA=0, ALUSrcB=01, ALUOp=00, PCWrite, PCSource=0 |
| 1, decode | A←Reg[rs1], B←Reg[rs2]; preparar destino | ALUSrcA=0, ALUSrcB=10, ALUOp=00; para beq, ALUOut←PC+(d−4) |
| 2, endereço | ALUOut←A+imediato | ALUSrcA=1, ALUSrcB=10, ALUOp=00 |
| 3, ler dados | MDR←Mem[ALUOut] | MemRead, IorD=1 |
| 4, writeback lw | Reg[rd]←MDR | RegWrite, MemtoReg=1 |
| 5, store | Mem[ALUOut]←B | MemWrite, IorD=1 |
| 6, executar R | ALUOut←A op B | ALUSrcA=1, ALUSrcB=00, ALUOp=10 |
| 7, writeback R | Reg[rd]←ALUOut | RegWrite, MemtoReg=0 |
| 8, branch | Se A=B, PC←ALUOut | ALUSrcA=1, ALUSrcB=00, ALUOp=01, PCWriteCond, PCSource=1 |
As habilitações não indicadas ficam a 0. Os registos internos sem enable podem capturar em todos os ciclos; o controlo tem de garantir que um valor só é usado enquanto ainda está disponível. No estado 8, PCSource=1 usa o ALUOut antigo, que contém o destino, enquanto a ALU calcula a comparação atual.
O decode pode calcular um valor que depois não é usado. Em lw, o estado 2 substitui esse valor pelo endereço efetivo. Não interpretes cada atividade interna como uma alteração de estado arquitetural visível ao programa.
Exemplo de load
Para lw x5,12(x8), com x8=0x10010000 e memória no destino contendo 42:
- Estado 0: IR recebe o código; PC avança 4.
- Estado 1: A recebe x8. A segunda leitura não será usada pelo load.
- Estado 2: ALUOut recebe
0x1001000C. - Estado 3: MDR recebe 42.
- Estado 4: x5 recebe 42. Segue-se o fetch da próxima instrução.
Logo lw demora 5 ciclos, sw 4, R 4 e beq 3. O branch demora 3 mesmo quando não é tomado; só a habilitação condicional do PC muda o resultado do último ciclo.
Contar instruções e ciclos
Começa pelo corpo do ciclo que se repete. Um ciclo com três iterações que executa um lw, um add, um sub de contador e um beq por iteração custa ciclos para essas instruções. Este total ainda não inclui o resto do programa.
Acrescenta depois a inicialização antes do ciclo, os testes finais e quaisquer saltos adicionais conforme o código concreto. Quando um branch salta sobre duas instruções, essas instruções não contam nessa execução, porque não foram executadas.
Uma pseudo-instrução que expanda em duas instruções máquina conta duas vezes, com os ciclos de cada expansão. Na dúvida, escreve a sequência de PCs ou uma tabela com o número de execuções de cada instrução real para não misturar linhas do ficheiro com instruções executadas.
O multiciclo pode ter período menor, pois cada etapa percorre menos módulos em série. O tempo é número de ciclos vezes período. Se o período passar a um terço e o CPI médio passar de 1 para 4, com o mesmo número de instruções, a razão de tempos é : demora mais 33,3%. Partilhar recursos reduz o hardware necessário, mas a rapidez depende desta conta completa.
Exercícios
Estado guardado entre ciclos
Segue um sw no CPU multiciclo e identifica onde ficam o dado e o endereço antes da escrita. Dá a sequência de estados e ciclos.
Pista
Há fetch,decode,endereço,escrita.
Mais uma pista
B e ALUOut conservam valores para o estado 5.
Solução
A sequência é 0,1,2,5, quatro ciclos. No decode B recebe Reg[rs2] e A recebe a base. No estado 2, ALUOut guarda A+imediato. No estado 5, IorD=1 seleciona esse endereço e MemWrite=1 escreve B. Não há writeback ao banco.
Confere a tua resposta:
- Segue os estados 0,1,2,5, num total de quatro ciclos.
- B guarda o dado; ALUOut guarda o endereço; MemWrite escreve B.
Erros frequentes
Não uses a saída atual da ALU no store se o caminho especifica ALUOut.
O fetch já avançou
O branch estava em 0x00400040 com d=-16, mas PC já vale 0x00400044 no decode. Determina o alvo correto e duas maneiras de o calcular.
Pista
A ISA usa o PC da instrução.
Mais uma pista
Usar PC avançado exige compensar quatro bytes.
Solução
O alvo correto é 0x00400030. Pode somar d ao PCinst=0x00400040 conservado no fetch, ou somar d−4=-20 ao PC avançado 0x00400044. Somar -16 ao PC avançado daria 0x00400034 e saltaria para a instrução errada. O caminho e o gerador do imediato têm de implementar a opção escolhida.
Confere a tua resposta:
- Alvo 00400030.
- Conserva PCinst ou soma -20 ao PC avançado.
Erros frequentes
Uma tabela com PC+imm não é suficiente sem definir qual PC está a usar.
Salto tomado e instruções não executadas
A execução passa por duas R, um lw, um beq tomado que salta sobre um sw, e outra R. Quantos ciclos gasta neste modelo?
Pista
Conta apenas a sequência efetivamente executada.
Mais uma pista
R=4,lw=5,beq=3; o sw saltado não conta.
Solução
São três R, um lw e um beq: ciclos. O sw está no programa mas não nesta execução. Todos os fetch/decode já estão incluídos nos custos por instrução.
Erros frequentes
Não somes novamente fetch e decode, porque já estão incluídos nos custos por instrução.
Instrução preservada durante load
Por que IRWrite não pode continuar ativo no estado de leitura dos dados de um lw?
Pista
A memória é partilhada por código e dados.
Solução
IR mantém o lw para conservar rd e outros campos até ao writeback. Se IRWrite estiver ativo no estado 3, o conteúdo lido como dado substitui o código da instrução e pode mudar o destino de escrita. O dado deve ir para MDR, enquanto IR conserva a instrução.
Confere a tua resposta:
- IR conserva a instrução até ao writeback.
- O dado lido vai a MDR, não a IR.
Erros frequentes
Não assumas que o mesmo valor lido pode substituir qualquer registo interno sem mudar o efeito.
A última comparação também custa
Num CPU multiciclo já alargado para suportar addi e jal, usa addi=4, lw=5, add=4, beq=3, jal=3 ciclos. t1 começa em 2. Conta só desde teste até chegar a fim, excluindo qualquer instrução em fim:
teste:
beq t1, zero, fim
lw t3, 0(t0)
add t2, t2, t3
addi t0, t0, 4
addi t1, t1, -1
jal zero, teste
fim:Quantas vezes é executada cada instrução e quantos ciclos são necessários?
Pista
O corpo corre duas vezes, mas o beq corre uma terceira vez para sair do ciclo.
Mais uma pista
jal zero é aqui uma instrução real com custo explicitamente dado.
Solução
beq corre 3 vezes: duas vezes falso e uma vez verdadeiro. lw, add, as duas addi e jal correm 2 vezes cada. O total é ciclos. A sequência tem 13 instruções executadas, contando separadamente as duas addi. Os custos addi/jal são hipóteses do CPU alargado deste exercício; o CPU da lição só especifica R,lw,sw,beq.
Confere a tua resposta:
- beq três vezes; cada outra instrução duas vezes.
- Total 49 ciclos; custos de addi e jal são hipóteses deste exercício.
Erros frequentes
Não contes só dois beq nem atribuas automaticamente os custos deste CPU alargado a qualquer implementação RISC-V.