Assembler para o Hack Assembly Language1, feito em C para o projeto #6 do curso Nand2Tetris. O curso Nand2Tetris visa construir um computador de uso geral a partir do portão lógico Nand até o famoso jogo de Tetris.
Consiste em duas instruções, especificadas na seguinte imagem:

Modifica o registro A para um valor de 15 bits. A versão binária consiste em dois campos, opcode e
o valor não negativo em binário. Por exemplo: @5 ou 0000000000000101 em binário, armazena o
valor 5 no registro A.
É usado para três operações:
- acessar valores constantes2
- acessar um registo de memória e consequentemente seu valor
- selecionar um endereço caso o jump ocorra
Diz o que computar, onde armazenar e o que será feito a seguir. Na versão binária o bit mais à esquerda é o opcode dessa instrução, que é 1. Os próximos dois bits não são utilizados e por convenção são deixados com o valor de 1. Os próximos 7 bits são os de computação. Os próximos 3 bits são de destino e os últimos 3 são para a instrução de jump.
O Hack ALU foi projetado para computar um conjunto fixo de funções dado dois inputs de 16 bits. Apesar de potencialmente poder computar até 128 funções3, apenas 28 funções são listadas na especificação.
Exemplos:
-
Para computar o valor de D menos 1, olhando a espeficicação, isso pode ser feito utilizando a instrução simbólica
D-1, que equivale em binário 1110001110000000. -
Para computar o bitwise or entre os valores de D e de M, podemos utilizar a instrução simbólica
D|M, que equivale em binário 1111010101000000.
Campo opcional, pode ser utilizado para armazenar o resultado da computação nos 3 registros simultaneamente. O primeiro d-bit diz se deve armazenar o resultado no registro A, o segundo no D e o terceiro no M, que é o registro de memória selecionado naquele momento.
Exemplo: caso se queira incrementar um valor que está na mémoria, é preciso selecionar o registro da memória com que se irá trabalhar e depois realizar as operações necessárias:
0000000000000111 // @7
1111110111011000 // DM=M+1
Observe que neste caso, o resultado também é salvo no registro D, que geralmente é utilizado para operações futuras.
A execução das instruções geralmente seguem-se uma após a outra, o campo jump permite que esse
fluxo seja alterado, porém antes é necessário dizer qual instrução será executada, caso a condição
seja realizada, com um A-instruction. O jump incondicional, realiza-se com 0;JMP, o que em
binário equivale a 1110000000000111.
Observação: como uma A-instruction seleciona tanto um endereço na RAM como ROM, e fazemos uso dele na C-instruction, seja para acessar o valor da RAM ou para realizar um jump, como boa prática não se faz referência ao registro M quando se tem uma instrução de jump.
Utilizados para representar endereços na memória RAM ou ROM. São divididos em três categorias:
- Predefinidos
- Rótulos
- Variáveis
Pode ser qualquer qualquer sequência de letras, dígitos, _, ., $ e : que não
comece com um dígito.
- R0 a R15
RAM[0] até a RAM[15] - SP4
RAM[0] - LCL4
RAM[1] - ARG4
RAM[2] - THIS4
RAM[3] - THAT4
RAM[4] - SCREEN
RAM[16384] - KBD
RAM[24576]
Declarados (LABEL), liga o símbolo LABEL ao endereço da próxima instrução. Instruções jump
que fazem uso dos rótulos podem aparecer em qualquer parte do programa, inclusive antes deles serem
declarados. Como convenção, os rótulos são escritos em letras maiúsculas. É uma pseudo-instrução já
que nenhum código binário é gerado a partir dela.
Qualquer símbolo que não seja predefinido ou que seja um rótulo é tratado como variável. Ela é associada
a um endereço de memória RAM decidido pelo assembler, por ordem de aparição começando pelo endereço
RAM[16]. Como convenção, as variáveis são escritas em letras minúsculas.
São escritos na Hack Machine Language, amarzenados em texto com a extensão hack, por exemplo
Prog.hack. Cada linha do arquivo contém uma instrução, usando uma sequência de 16 dígitos de 0
ou 1. Quando um programa é carregado na memória de instrução, cada instrução é associada ao número
respectivo da linha que está no arquivo começando pelo 0.
São escritos na simbólica Hack Assembly Language, amarzenados em texto com a extenção asm, por
exemplo Prog.asm. O arquivo é composto por instruções A e C, declaração de rótulos e comentários.
Os comentários podem ser feitos usando // no início da linha. Espaços em branco e linhas em
branco são ignorados. Todos os mnemônicos da linguagem símbolica devem ser
escritos em letras maiúsculas.
Ver código
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/4/Mult.asm
// Multiplies R0 and R1 and stores the result in R2.
// (R0, R1, R2 refer to RAM[0], RAM[1], and RAM[2], respectively.)
// The algorithm is based on repetitive addition.
// n = R1
@R1
D=M
@n
M=D
// v = R0
@R0
D=M
@v
M=D
// i = 0
@i
M=0
// R2 = 0 - assert correct value
@R2
M=0
(LOOP)
// if i == n goto END
@i
D=M
@n
D=D-M
@END
D;JEQ
// R2 = R2 + v
@R2
D=M
@v
D=D+M
@R2
M=D
// i++
@i
M=M+1
@LOOP
0;JMP
(END)
@END
0;JMP
É uma implementação em 16 bits da máquina de Von Neumann projetada para executar programas escritos em Hack Machine Language. Para realizar a tarefa, ela é composta por:
- CPU
- ROM 32k
- RAM 16K + 8K + 2 byte (ou 1 word)
- SCREEN
- KEYBOARD
Responsável pela execução das instruções dos programas, cada instrução diz a CPU que computação realizar, que registro acessar e qual instrução será a próxima. É composta por ALU e um conjunto de registros.
A CPU é projetado para executar instruções A e C, sendo A os 16 bits são tratados como binários e armazenados na registro A; sendo C, é tratada como uma "cápsula" de bits de controle que especifica várias microoperações para serem realizadas em várias partes da CPU.
É o chip que realiza as operações lógicas e matemáticas de baixo nível que são próprias da implementação do Hack Computer. A quantidade de funcionalidades em grande parte é uma decisão de projeto, as que não forem implementadas no hardware são implementadas via software, com o custo de se tornarem mais lentas pois precisam de mais ciclos para serem concluídas.
Para que cálculos sejam feitos, é necessário que dados intermediários sejam guardados. Em tese esses dados poderiam ser amarzenados na RAM, o problema é que a RAM é outro chip e isso aumentaria consideravelmente o tempo de execução. Os registros funcionam como uma memória de baixa latência, porém são mais caros por residirem dentro da CPU, por isso costumam suportar pouca informação. No caso dessa CPU há apenas 3 registros: Data, Address e Program Counter.
Usado para amarzenar valores.
Usado para amarzenar valores ou selecionar um endereço de instrução ou na memória RAM.
Usado para controlar qual instrução será executada.
O chip é semelhante ao da RAM, com a exceção de ser uma memóra de leitura somente, é utilizada para carregar os programas que serão executados no Hack Computer. Também de 32K.
Utilizada como memória de uso geral, possui 32k endereços de registros de 16 bits5, também funciona como interface entre a CPU e os dispositivos de entrada/saída do computador. Os dois dispositivos, SCREEN e KEYBOARD interagem com o computador a partir de áreas mapeadas nesta memória.
Usado como dispositivo de saída, a tela é mapeada na RAM nos endereços de 16,384 a 24,575, possui uma resolução de 512x256, cores preto ou branco, totalizando 8k6 bits de informação. É continuamente atualizada para refletir as mudanças que são feitas no seu espaço de memória por uma lógica externa ao computador.
Cada linha da tela começa no canto superior esquerdo e é representado por 32 words consecutivas.
Seguindo a convenção, o canto superior esquerdo é considerada a linha 0 e coluna 0. Para encontrar
um pixel em específico, na linha l, coluna c, utiliza-se a fórmula c % 16 (do LSB ao MSB)7 da
word mapeada na RAM[SCREEN + l*32 + col/16]. O pixel pode ser lido ou escrito, sendo 1 para
preto e 0 para branco.
Usado como dispositivo de entrada, é mapeado na RAM no endereço 24,576 e reflete a tecla que está sendo pressionada no teclado físico, usado para leitura somente.
- Tenha o Ceedling instalado.
- Obtenha uma cópia do repositório
git clone https://github.com/RenanGalvao/hack-machine-assembler.git. - Entre na pasta e execute o build
cd hack-machine-assembler && ceedling release.
Para gerar o código hack, utilize dentro da pasta raiz: ./build/release/assembler <source-file>.
Caso não tenha nenhum tipo de erro no código fonte, o arquivo .hack estará na mesma pasta que o código fonte.
| Directory: | src/ |
|---|---|
| Date: | 2025-03-20 14:10:30 |
| Coverage: | low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0% |
| Exec | Total | Coverage | |
|---|---|---|---|
| Lines: | 388 | 425 | 91.3% |
| Functions: | 28 | 28 | 100.0% |
| Branches: | 125 | 154 | 81.2% |
| File | Lines | Functions | Branches | ||||
|---|---|---|---|---|---|---|---|
| code.c | 97.7 | 97.7% | 42 / 43 | 100.0% | 4 / 4 | 90.0% | 9 / 10 |
| hash-map.c | 70.8 | 70.8% | 63 / 89 | 100.0% | 2 / 2 | 59.1% | 26 / 44 |
| parser.c | 97.7 | 97.7% | 84 / 86 | 100.0% | 3 / 3 | 97.2% | 35 / 36 |
| tables.c | 100.0 | 100.0% | 72 / 72 | 100.0% | 4 / 4 | 100.0% | 2 / 2 |
| utils.c | 94.1 | 94.1% | 127 / 135 | 100.0% | 15 / 15 | 85.5% | 53 / 62 |
Footnotes
-
Linguagem simbólica da Hack Machine Language. ↩
-
Os valores constantes vão de 0 até 32767 (2^15 - 1). ↩
-
2^7 = 128. ↩
-
Usados no código intermediário gerados com o Jack Compiler. ↩ ↩2 ↩3 ↩4 ↩5
-
Na prática é possível acessar até o endereço <= 0x6000. ↩
-
Para cada pixel é utilizado apenas um bit e cada um deles possui 2 possibilidades, portanto: (512x256 / 8 ) / 2 = 8192 bytes ou 8kb em binário. ↩
-
O bit mais significado é o primeiro da esquerda. ↩







