• English
  • 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:

    SET R0, 6 // multiplicand

    The default summary artifact shows every real instruction produced by the pseudoinstruction:

    0000000000000110 // ROM[0000] L4 [1/4] SET R0, 6 => @6
    1110110000010000 // ROM[0001] L4 [2/4] SET R0, 6 => D=A
    0000000000000000 // ROM[0002] L4 [3/4] SET R0, 6 => @R0
    1110001100001000 // ROM[0003] L4 [4/4] SET R0, 6 => M=D

    [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:

    0000000000000110 // ROM[0000] L4 [1/4] SET R0, 6 => @6 // multiplicand

    Only after reloading the .hack file does the generated driver receive the same provenance on a raw ROM load:

    ROM[0] = 0b0000000000000110; // ROM[0000] L4 [1/4] SET R0, 6 => @6 // multiplicand

    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:

    pixi run just hack assemble multiply
    pixi run just hack assemble multiply full
    pixi run just hack run multiply full

    Name the layer before changing it

    LayerExamplesDoes standard Hack machine code change?
    Assembly surface and toolingNew Hack+ pseudoinstructions, diagnostics, disassembler, debugger, trace outputNo; everything still lowers to standard A/C words
    Runtime or platformScreen and keyboard behavior, timer, serial port, startup conventionUsually no; device behavior or execution environment changes
    ISANew shift instruction, a real HALT encoding, new register or addressing modeYes; encoding or architectural state changes
    Implementation or verificationPipeline experiment, gate-level implementation, property tests, differential testsNot necessarily; the same ISA may have another implementation

    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 hack16 behavior.
    • 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.sail cleanly while keeping small profile configuration/mapping clauses, its own project, manifest identity, and tests/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:

    CLR target
    COPY source, target
    PUSH source
    POP target

    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 .hack disassembler 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 HALT word 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:

    1. exact bit layout and assembly syntax;
    2. decode result and architectural state transition;
    3. invalid or reserved encodings;
    4. assembler encoding and any pseudoinstruction interaction;
    5. known-word, decode, semantic, and end-to-end tests;
    6. 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

    ChangeMinimum implementation surface
    Hack+ pseudoinstructionassembler.py parser/lowering, expansion tests, examples, package reference
    Artifact explanationwriter/loader contract, driver comment propagation, comment-level tests, docs
    Execution observationexecutor/driver, deterministic tests, execution guide
    Memory-mapped deviceplatform state and read/write semantics, driver input/output, Sail and end-to-end tests
    New encoded instructionshared/profile Sail boundary, profile mapping/project, assembler encoding, independent known words, conformance and integration tests, ISA guide
    New frontend such as VM-to-Hackseparate translator, canonical .asm boundary, source provenance, translator and end-to-end tests

    Use an experiment contract

    Before coding, write a small durable specification:

    Goal:
    Layer being changed:
    Source syntax or external interface:
    Encoding, if any:
    State read and written:
    Errors and reserved cases:
    Compatibility promise:
    Independent test vectors:
    End-to-end example:
    Documentation to update:

    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 CALL and RET as 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 .hack comments.
    • 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 hack32 profile—or design hack64—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.