Build computer from 0 to 1 (5 / 10) -- implement hack: computer architecture, memory, CPU and instruction set

After the first two chapters, we have realized all the combinational logic chips and timing chips, and have the foundation to realize the hack computer. This is the last chapter of the hardware part of the project. In this chapter, we will define the operations that hack can perform, i.e. the instruction set of hack; realize the memory and CPU of hack, and then combine them to form a complete computer hardware architecture.

Computer architecture

Hack is based on the classical von Neumann architecture. The key part of hack is to control the execution of CPU through the program instructions stored in the central processing unit and memory. It has the following characteristics:

  • Cell centric
  • Using stored program principle
  • Memory is a linear address space accessed by address
  • Control flow generated by instruction flow
  • Instruction consists of operation code and address code
  • Data encoded in binary

Instruction set (machine language)

We can understand CPU from two perspectives: one is from the perspective of machine language, and the other is from the perspective of physical implementation. Machine language is the function abstraction of CPU, physical realization is the concrete realization of CPU, and machine language is also the interface of hardware layer and software layer of computer system.

To implement the CPU, we must first define what operations the CPU can perform, and then abstract these operations into the instruction set of the CPU. The instruction set of CPU is a series of operation conventions. They describe how to use CPU to operate memory with formatted instructions. For example, we have x86 instruction set. X86 instruction set is very complex. There are hundreds of instructions, including data transmission instruction, arithmetic operation instruction, logic operation instruction, displacement instruction, string operation instruction, processor control instruction, control transfer instruction, etc

We will abandon many complex instructions and only realize the most necessary functions required by a CPU to facilitate the implementation, but more importantly, it is convenient for us to understand the most basic principles of computer system hardware design and operation. We can sort out the four most necessary functions:

  • Read data (including instruction data) of an address from memory and transfer it to CPU
  • CPU performs basic arithmetic logic operation
  • CPU writes data to memory or register
  • CPU jumps to an address for execution

In the implementation, we only need to design two instructions to meet the above four functions. You have heard right, only two are needed. Before we introduce these two instructions, we first introduce the two registers that need to be used in the CPU: A register: used to store an immediate number (usually the value of the memory address to be used); D register: used to store the intermediate data in the calculation process. Looking back at the previous two articles, we realize that the maximum word length of the chip is 16 bits, so our instructions are also 16 bits.

A directive

A instruction, that is, Address instruction. Its function is very simple, that is to transfer a value to a register in the CPU. In most cases, this number will be used as an Address to locate the memory unit. Let's take a look at the format of instruction A. instruction a divides 16 bit data into two parts: the first bit is the flag bit, when it is 0, it means that the instruction is instruction a, and the remaining 15 bits are the values that need to be passed into register a. As you can see, instruction a implements the first point of the above four functions.

C directive

The C instruction is complex and divided into four parts. Instruction A realizes the last three functions of the above four points

  • The first bit is the flag bit: when it is 1, it means that the instruction is a C instruction
  • 3-10 bits are comp bits:: these bits are arranged to represent the execution of different elements & logical operations
  • Bit 11-13 is dest bit: indicates where the calculated data needs to be transferred (register or memory)
  • 14-16 bits are jump bits: indicates the conditions for the CPU to execute jump instructions

comp bit and copm bit are mainly used for Alu in CPU, indicating ALU to perform some arithmetic and logical operations. The operand is the data in A register, D register or memory unit pointed by the address in A register. The list of operations is as follows:

The value calculated by the instruction ALU of comp bit is transmitted to the storage unit indicated by dest bit. This storage unit is A register, D register, or the memory unit pointed by the address in A register, or their combination.

In most cases, the CPU is executed in sequence, but jump execution is also required. The execution logic of jump is to use the jump bit to determine whether to jump according to the data output by comp bit. If necessary, jump to the address instruction in A register. Otherwise, execute the next instruction in sequence.

Some symbols

At the same time, we introduce some predefined symbols in advance. The function of symbols is to facilitate our subsequent writing of hack assembly language. The required symbol types are as follows:

  • Predefined symbols: symbols with special meanings in assembly language
  • Virtual register: represents the address in some virtual registers
  • Predefined pointer: points to some specific memory
  • I/O pointer: refers to the actual address of I/O in memory
  • Label symbol: used to indicate the position of instruction (convenient for writing jump logic)
  • Variable symbols: easy to declare variables in assembly code


We have realized the RAM chip in the timing chip, so the work of realizing the memory has been basically completed. This article is mainly about the use, combination and division of memory. Memory is divided into data memory and instruction memory according to functions. Normally, data memory and instruction memory are divided under one block of large memory, which is allocated and managed by the operating system for developers. Here, in order to achieve more convenient implementation, we divide hack's internal memory into blocks: one block of data memory, one block of ROM, which is specially used for storing programs, and one block of ROM is used for storing programs Write operation is not supported during runtime.

At the same time, Memory is divided into three parts: RAM (16K) for data storage; Screen (8K) Memory mapped to the display; Keyboard (16bit) Memory mapped to the Keyboard.

HDL implementation: Memory

CHIP Memory {
    IN in[16], load, address[15];
    OUT out[16];

    DMux4Way(in=true, sel[0..1]=address[13..14], a=a, b=b, c=c, d=d);

    Or(a=a, b=b, out=outa);

    And(a=outa, b=load, out=load0);
    And(a=c, b=load, out=load1);

    RAM16K(in=in, load=load0, address[0..13]=address[0..13], out=R0);
    Screen(in=in, load=load1, address[0..12]=address[0..12], out=R1);

    Mux4Way16(a=R0, b=R0, c=R1, d=R2, sel[0..1]=address[13..14], out=out);


After defining the instruction set, what we need to do next is to implement the CPU according to the instruction function. The CPU of hack mainly consists of the following four parts:

  • ALU: mainly used to perform arithmetic & logical operations
  • Register: A register, D register
  • PC: program counter, save next instruction address
  • Control logic: parse instruction type, select register, update PC, etc

The circuit diagram of CPU is as follows (the complete circuit diagram is difficult to draw, so I will not draw it first)

HDL implementation: CPU


    IN  inM[16],         // M value input  (M = contents of RAM[A])
        instruction[16], // Instruction for execution
        reset;           // Signals whether to re-start the current
                         // program (reset==1) or continue executing
                         // the current program (reset==0).

    OUT outM[16],        // M value output
        writeM,          // Write to M? 
        addressM[15],    // Address in data memory (of M)
        pc[15];          // address of next instruction

    // Instruction type resolution
    And(a=instruction[15], b=true, out=ins15);          // ins15 instruction flag bit
    Not(in=instruction[15], out=ins15Not);       // ins15Not: instruction flag bit inversion

    // A instruction and a register
    Mux16(a=instruction, b=ALUOut, sel=instruction[5], out=ARSel);
    Mux16(a=instruction, b=ARSel, sel=ins15, out=ARIn);
    Or(a=ins15Not, b=instruction[5], out=ARLoad);
    ARegister(in=ARIn, load=ARLoad, out=AROut);

    // D register
    And(a=ins15, b=instruction[4], out=DRLoad);
    DRegister(in=ALUOut, load=DRLoad, out=DROut);

    // C instruction type distinction: calculate A or M
    Mux16(a=AROut, b=inM, sel=instruction[12], out=AMOut);

    // ALU logic
    ALU(x=DROut, y=AMOut, zx=instruction[11], nx=instruction[10], zy=instruction[9], ny=instruction[8], f=instruction[7], no=instruction[6], out=ALUOut, zr=ALUzr, ng=ALUng);

    // outM & writeM & addressM
    And16(a=true, b=ALUOut, out=outM);
    And(a=ins15, b=instruction[3], out=writeM);
    And16(a=true, b=AROut, out[0..14]=addressM[0..14]);

    // jump logic
    And(a=ALUng, b=instruction[2], out=j1Out);
    And(a=ALUzr, b=instruction[1], out=j2Out);
    Or(a=ALUng, b=ALUzr, out=po);
    Not(in=po, out=ALUpo);
    And(a=ALUpo, b=instruction[0], out=j3Out);
    Or8Way(in[0]=j1Out, in[1]=j2Out, in[2]=j3Out, in[3..7]=false, out=jump);
    And(a=ins15, b=jump, out=jumpOut);

    // PC
    PC(in=AROut, load=jumpOut, inc=true, reset=reset, out[0..14]=pc[0..14]);


Finally, we can connect the CPU, ROM and RAM according to the overall architecture diagram.

HDL implementation

 * The HACK computer, including CPU, ROM and RAM.
 * When reset is 0, the program stored in the computer's ROM executes.
 * When reset is 1, the execution of the program restarts. 
 * Thus, to start a program's execution, reset must be pushed "up" (1)
 * and "down" (0). From this point onward the user is at the mercy of 
 * the software. In particular, depending on the program's code, the 
 * screen may show some output and the user may be able to interact 
 * with the computer via the keyboard.

CHIP Computer {

    IN reset;

    CPU(inM=memoryOut, instruction=ROMOut, reset=reset, outM=outM, writeM=writeM, addressM=addressM, pc=pc);
    ROM32K(address=pc, out=ROMOut);
    Memory(in=outM, load=writeM, address=addressM, out=memoryOut);


So far, we have finished all the work of hack hardware. Based on two basic people: Nand and DFF, we have realized many chips step by step and finally the whole hardware architecture. Although hack is small and has all five internal organs, we only keep the most core part of the computer architecture, which reduces our workload. More importantly, it is convenient for us to dial out many complex technical details and clearly understand the most core design idea of the computer system. Having completed the definition of hack's hardware system and instruction set, we can write a hello world program written in 0101, but it seems to be a little painful, so we will define hack's assembly language and write an assembler in the next article. A kind of

Published 8 original articles, won praise 0, visited 5218
Private letter follow

Tags: Assembly Language

Posted on Mon, 24 Feb 2020 02:54:01 -0500 by neave