Evolve Hack Yourself
← ISA guide · Assembler internals → · 中文
The model in this workspace is a starting point, not a museum piece. Once the standard Hack programs pass, try changing the language around the machine, the platform connected to it, or the ISA itself. The important habit is to say which layer you are changing and then carry that decision through the model, tools, tests, and documentation.
First see what lowering preserves
Given this Hack+ source:
The default summary artifact shows every real instruction produced by the pseudoinstruction:
[i/n] is expansion lineage: this word is result i of n from one source pseudoinstruction. It is not part of Hack machine code. A normal A or C instruction has no expansion marker.
Requesting full preserves the complete original line as well:
Only after reloading the .hack file does the generated driver receive the same provenance on a raw ROM load:
The driver does not decode this word. hack_step() later fetches it from model-owned ROM and follows the Sail decode/execute path.
Run these commands to inspect both forms:
Name the layer before changing it
This distinction prevents a common mistake: making the parser accept a mnemonic and calling it a new instruction even though neither the encoding nor Sail execution semantics changed.
Modify in place, or create a derived profile?
The module already contains two durable identities: hack16, the canonical nand2tetris baseline, and hack32, the Verylogic extension. Choose the smallest boundary that keeps them honest:
- A personal branch or class exercise: modify one profile locally and keep the other profile's regressions as a compatibility reference.
- Tooling that preserves both encodings: implement it in the existing module and keep profile selection explicit. New pseudoinstructions, traces, and diagnostics should preserve default
hack16behavior. - A platform experiment: prefer driver/platform configuration rather than changing either ISA encoding.
- A durable ISA change: add a named profile only when it can include
model/core.sailcleanly while keeping small profile configuration/mapping clauses, its own project, manifest identity, andtests/sail/<profile>/gate. If the semantics no longer fit that boundary, create a separately named ISA family instead.
In short: the canonical baseline must not be silently redefined, and every durable variant needs an identity. Artifacts use isa=hack with profile=hack16 or hack32—never standard—so one profile cannot masquerade as another.
A project ladder
1. Add one safe pseudoinstruction
Good first candidates include:
Specify the exact standard Hack expansion and register side effects first. Then update the parser, structured expansion lineage, assembler tests, one end-to-end program, and the Hack+ table. The Sail model files should remain unchanged because the ISA did not change.
For PUSH, POP, CALL, or RET, first write down a stack and calling convention. A convenient syntax without a convention is not yet a complete design.
2. Improve observation and debugging
Possible projects:
- a driver trace that records
PC, the decoded instruction, and changed state; - breakpoints or watchpoints implemented in the driver;
- a
.hackdisassembler that displays canonical A/C syntax; - a step-by-step HTML trace linking source, ROM word, and state transition;
- assertion diagnostics that show actual and expected values together.
These projects teach executor and tooling design while leaving Hack compatibility intact.
3. Complete more of the Hack platform
The current model gives SCREEN and KBD their standard addresses but does not emulate device side effects. You could add:
- framebuffer rendering for the screen region;
- keyboard input snapshots;
- a timer or serial-output memory-mapped device;
- deterministic scripted device input for tests.
Document these as platform extensions, not instruction extensions. Define address ranges, read/write behavior, reset values, and deterministic test inputs.
4. Add a real ISA instruction
Interesting candidates include:
- logical or arithmetic shifts;
- a real
HALTword instead of the current Hack+ self-loop convention; - another condition or ALU operation;
- a deliberately small extension register.
Before taking an unused-looking C-instruction bit pattern, enumerate canonical encodings and circuit aliases. Then define:
- exact bit layout and assembly syntax;
- decode result and architectural state transition;
- invalid or reserved encodings;
- assembler encoding and any pseudoinstruction interaction;
- known-word, decode, semantic, and end-to-end tests;
- whether old standard Hack programs remain binary compatible.
A real instruction must be implemented consistently at the correct shared/profile model boundary, in the assembler, both affected conformance suites, examples, artifact identity, and the ISA reference.
5. Try a more radical machine
Once compatibility is no longer a goal, questions become broader:
- What would Hack look like with a wider PC or more addressable memory?
- Could one encoding support immediate arithmetic without losing simple decoding?
- Would separate load/store instructions make data movement clearer?
- How would a flags register change branches?
- What is the smallest change that makes function calls substantially easier?
- Can the machine remain easy to build from gates after the extension?
At this point, give the dialect or ISA a new name rather than silently redefining nand2tetris Hack.
Change map
Use an experiment contract
Before coding, write a small durable specification:
Keep the standard regression suite passing unless incompatibility is the explicit subject of the experiment. New behavior should have at least one direct semantic test and one runnable program with .assert contracts.
Creative challenge ideas
- Draw a pattern into screen memory and build a minimal viewer.
- Design
CALLandRETas Hack+ first, then compare them with true ISA support. - Add shifts and use them to implement faster multiplication or bit extraction.
- Build a VM-to-Hack translator that preserves VM source locations in
.hackcomments. - Add a deterministic serial device and write a program that emits text.
- Generate random legal A/C instructions and check encode/decode or execution properties.
- Compare two independent implementations of one instruction on thousands of states.
- Extend the existing
hack32profile—or designhack64—and explain exactly which simplicity of Hack is gained or lost.
The goal is not to maximize features. A small extension with a precise contract, readable artifacts, and convincing tests teaches more than a large extension whose layer and semantics are unclear.
Next: follow how the teaching assembler represents and lowers source.