Material didático para aprender como funciona um computador por dentro, construindo do zero, em Common Lisp:
- uma máquina virtual (VM) que executa código de máquina;
- um montador (assembler) que traduz assembly para código de máquina.
O alvo é o LC-3 (Little Computer 3), um computador didático de 16 bits muito usado em cursos de arquitetura de computadores: pequeno o bastante para caber na cabeça, completo o bastante para rodar jogos de verdade — um rogue e até o 2048.
| Característica | Valor |
|---|---|
| Palavra | 16 bits |
| Registradores | 8 de uso geral (R0–R7) + PC + COND |
| Memória | 65.536 posições (2¹⁶) |
| Instruções | 16 opcodes |
Com tão pouco já dá para entender o ciclo fundamental de todo processador:
BUSCAR → DECODIFICAR → EXECUTAR → (repete)
fetch decode execute
| Arquivo | O que é |
|---|---|
lc3.lisp |
A máquina virtual. Lê um .obj e o executa. Comentado como tutorial (13 partes). |
lc3as.lisp |
O montador. Lê um .asm e gera um .obj. Comentado como tutorial (6 partes). |
rogue.asm |
Jogo rogue em assembly LC-3 (Justin Meiners, 2017). Programa de teste. |
2048.asm |
Jogo 2048 em assembly LC-3 (Ryan Pendleton, 2014). Programa de teste maior (~1000 linhas). |
Tudo em Common Lisp puro (SBCL). Sem dependências externas.
Requer SBCL (Linux).
# 1) Montar: assembly -> código de máquina
sbcl --script lc3as.lisp rogue.asm rogue.obj
# 2) Executar o código de máquina na VM
sbcl --script lc3.lisp rogue.objWASD— mover- Alcance a porta
Dpara vencer Ctrl-C— sair
Legenda do mapa:
| Símbolo | Significado |
|---|---|
@ |
jogador |
D |
porta (saída) |
# |
parede |
| (espaço) | túnel |
sbcl --script lc3as.lisp 2048.asm 2048.obj
sbcl --script lc3.lisp 2048.obj- Responda
yà pergunta "Are you on an ANSI terminal?" WASD— juntar as peças- Combine os números até chegar a 2048
Prova de que o montador aguenta um programa grande: o
2048.asmtem ~1000 linhas e vira 1138 palavras de código de máquina.
O coração é o laço buscar–decodificar–executar:
(loop while *running*
do (let ((instr (mem-read (reg +r-pc+)))) ; 1) BUSCAR a instrução do PC
(setf (reg +r-pc+) (u16 (1+ (reg +r-pc+)))) ; avança o PC
(execute instr))) ; 2-3) DECODIFICAR + EXECUTARPontos que o tutorial explica em detalhe:
- Aritmética de 16 bits — Lisp tem inteiros infinitos, então toda conta é "cortada" de volta para 16 bits com a máscara
#xFFFF(macrou16). - Decodificação por máscara de bits — os 4 bits altos são o opcode; cada campo (registrador, imediato, deslocamento) é extraído com deslocamentos e
logand. - I/O mapeado em memória — ler o endereço mágico do teclado (
0xFE00) consulta o teclado real. - Traps — chamadas de "sistema" para entrada/saída (
GETC,OUT,PUTS,HALT…). - Terminal em modo cru — via FFI (
sb-alien), mexendo nastruct termiospara ler tecla a tecla sem Enter e sem eco.
Problema central: referências para frente (saltar para um rótulo que ainda não apareceu). Solução clássica — duas passagens:
- Passagem 1 — percorre o texto só para mapear cada rótulo ao seu endereço (a tabela de símbolos).
- Passagem 2 — agora que todos os endereços são conhecidos, gera os 16 bits de cada instrução.
A saída é um .obj em big-endian: a primeira palavra é o endereço de carga (.ORIG), seguida do programa.
rogue.asm ──(lc3as.lisp)──► rogue.obj ──(lc3.lisp)──► jogo rodando
assembly montador código VM no terminal
2 passagens de máquina fetch/decode/exec
Diretivas: .ORIG .FILL .BLKW .STRINGZ .END
Instruções: ADD AND NOT LD LDI LDR LEA ST STI STR BR(+n/z/p) JMP JSR JSRR RET RTI TRAP
Traps por nome: GETC OUT PUTS IN PUTSP HALT
Números: x1F (hex), #-1 (decimal), rótulos.
Exemplo mínimo (hi.asm):
.ORIG x3000
LEA R0, MSG
PUTS
HALT
MSG .STRINGZ "Oi!\n"
.ENDsbcl --script lc3as.lisp hi.asm hi.obj
sbcl --script lc3.lisp hi.obj- VM e montador portados para Common Lisp a partir do tutorial Write your own Virtual Machine de Justin Meiners e Ryan Pendleton.
rogue.asmpor Justin Meiners (2017).2048.asmpor Ryan Pendleton (2014), projeto rpendleton/lc3-2048, licença MIT.