The Elements Of Computing Systems Pdf | Elements Of Computer System 370 개의 새로운 답변이 업데이트되었습니다.

당신은 주제를 찾고 있습니까 “the elements of computing systems pdf – ELEMENTS OF COMPUTER SYSTEM“? 다음 카테고리의 웹사이트 https://ppa.charoenmotorcycles.com 에서 귀하의 모든 질문에 답변해 드립니다: ppa.charoenmotorcycles.com/blog. 바로 아래에서 답을 찾을 수 있습니다. 작성자 Lester Flores 이(가) 작성한 기사에는 조회수 11,831회 및 좋아요 233개 개의 좋아요가 있습니다.

Table of Contents

the elements of computing systems pdf 주제에 대한 동영상 보기

여기에서 이 주제에 대한 비디오를 시청하십시오. 주의 깊게 살펴보고 읽고 있는 내용에 대한 피드백을 제공하세요!

d여기에서 ELEMENTS OF COMPUTER SYSTEM – the elements of computing systems pdf 주제에 대한 세부정보를 참조하세요

This topic covers the different important elements of computer system.

the elements of computing systems pdf 주제에 대한 자세한 내용은 여기를 참조하세요.

Building a Modern Computer from First Principles – Files CC

The elements of computing systems: building a modern computer from first principles … test scripts carry out all the necessary initializations in a manual.

+ 여기에 보기

Source: f.javier.io

Date Published: 8/4/2021

View: 3801

The Elements of Computing Systems … – Free Computer Books

This book gives students an integrated and rigorous picture of applied computer science, as its comes to play in the construction of a simple yet powerful …

+ 여기에 자세히 보기

Source: freecomputerbooks.com

Date Published: 7/18/2021

View: 9459

The Elements of Computing Systems, second edition: Building …

The Elements of Computing Systems, second edition: Building a Modern Computer from First Principles [2 ed.] 0262539802, 9780262539807.

+ 여기에 더 보기

Source: dokumen.pub

Date Published: 11/3/2021

View: 4997

The Elements Of Computing Systems – Internet Archive

The Elements of Computing Systems Building a Modern Computer from First Principles Noam Nisan and Shimon Schocken.

+ 여기에 표시

Source: archive.org

Date Published: 11/4/2022

View: 2117

The Elements Of Computing Systems: Building A … – VDOC.PUB

The Elements Of Computing Systems: Building A Modern Computer From First Principles [PDF]. Authors: Noam Nisan , Shimon Schocken; PDF; Art , Design: …

+ 여기를 클릭

Source: vdoc.pub

Date Published: 2/17/2022

View: 5752

The Elements of Computing Systems – E-Books Directory

The Elements of Computing Systems – free book at E-Books Directory. You can download the book or read it online. … (3.5MB, PDF) …

+ 여기에 보기

Source: www.e-booksdirectory.com

Date Published: 7/29/2022

View: 6540

The Elements of Computing Systems: Building a … – EBIN.PUB

The Elements of Computing Systems: Building a Modern Computer from First Principles … You can publish your own PDF file online for free in a few minutes!

+ 자세한 내용은 여기를 클릭하십시오

Source: ebin.pub

Date Published: 11/11/2021

View: 515

The Elements of Computing Systems

Preface. 0. Introduction: Hello, World Below. 1. Boolean Logic. 2. Boolean Arithmetic. 3. Sequential Logic. 4. Machine Language. 5. Computer Architecture.

+ 자세한 내용은 여기를 클릭하십시오

Source: minnie.tuhs.org

Date Published: 2/28/2021

View: 9773

주제와 관련된 이미지 the elements of computing systems pdf

주제와 관련된 더 많은 사진을 참조하십시오 ELEMENTS OF COMPUTER SYSTEM. 댓글에서 더 많은 관련 이미지를 보거나 필요한 경우 더 많은 관련 기사를 볼 수 있습니다.

ELEMENTS OF COMPUTER SYSTEM
ELEMENTS OF COMPUTER SYSTEM

주제에 대한 기사 평가 the elements of computing systems pdf

  • Author: Lester Flores
  • Views: 조회수 11,831회
  • Likes: 좋아요 233개
  • Date Published: 2020. 10. 5.
  • Video Url link: https://www.youtube.com/watch?v=aKVfkjDY3bs

What are the elements of computer system?

It is generally composed of three major elements: the processor (central processing unit or CPU), the memory, and the input output (I/O) devices. The immediate forerunner of the electronic computer was an electromechanical computer that became operational in 1944.

What are the six 6 important elements of a computer system?

Elements of a Computer System:
  • (i) Hardware:
  • (ii) Software:
  • (iii) People:
  • (iv) Procedures:
  • (v) Data:
  • (vi) Connectivity:
  • (i) Input Unit:
  • (ii) Central Processing Unit (CPU):

How many elements does the computer system have?

Elements of a Computer System. There are six main elements that make up a computer system. They all interact with each other and perform the task at hand.

What are the 4 elements of a computer system?

There are four elements that make up a personal computer system. The user, software, hardware and electricity all work together to form the whole system. Each element is as crucial as the others are. The System Unit or Box is a important part of the computer system.

What are the 5 elements of a system?

According to some traditions, everything in the universe comes from the five elements: wood, fire, earth, water, and metal.

In this cycle:
  • water controls fire.
  • fire controls metal.
  • metal controls wood.
  • wood controls earth.
  • earth controls water.

What are the two major elements of the computer system?

Every computer is composed of two basic components: hardware and software.

What are the three fundamental elements of the computer system?

There are three major components of a computer system:
  • hardware.
  • software.
  • humanware.

What are the elements of computer explain with diagram?

Computer systems consist of three components as shown in below image: Central Processing Unit, Input devices and Output devices. Input devices provide data input to processor, which processes data and generates useful information that’s displayed to the user through output devices. This is stored in computer’s memory.

What are the different elements of a computer system class 11?

These components of computer system are diagrammatically represented as :
  • Input Unit. This unit is used to provide data and instructions to computer. …
  • 2 Central Processing Unit (CPU) CPU is also called brain of the computer. …
  • Memory Unit. Memory unit is used to store data and instructions. …
  • Secondary Storage unit. …
  • Output Unit.

What are the types of computer systems?

Types of Computer
  • Mainframe Computer. It is high capacity and costly computer. …
  • Super Computer. This category of computer is the fastest and also very expensive. …
  • Workstation Computer. …
  • Personal Computer (PC) …
  • Apple Macintosh (Mac) …
  • Laptop computer (notebook) …
  • Tablet and Smartphone.

What are the different elements of a computer system class 11?

These components of computer system are diagrammatically represented as :
  • Input Unit. This unit is used to provide data and instructions to computer. …
  • 2 Central Processing Unit (CPU) CPU is also called brain of the computer. …
  • Memory Unit. Memory unit is used to store data and instructions. …
  • Secondary Storage unit. …
  • Output Unit.

What are the elements of computer explain with diagram?

Computer systems consist of three components as shown in below image: Central Processing Unit, Input devices and Output devices. Input devices provide data input to processor, which processes data and generates useful information that’s displayed to the user through output devices. This is stored in computer’s memory.

What are the types of computer systems?

Types of Computer
  • Mainframe Computer. It is high capacity and costly computer. …
  • Super Computer. This category of computer is the fastest and also very expensive. …
  • Workstation Computer. …
  • Personal Computer (PC) …
  • Apple Macintosh (Mac) …
  • Laptop computer (notebook) …
  • Tablet and Smartphone.

What are the 3 fundamental elements of a computer?

There are three major components of a computer system: hardware. software. humanware.

Free Computer, Programming, Mathematics, Technical Books, Lecture Notes and Tutorials

In the early days of computer science, the interactions of hardware, software, compilers, and operating system were simple enough to allow students to see an overall picture of how computers worked. With the increasing complexity of computer technology and the resulting specialization of knowledge, such clarity is often lost.

Unlike other texts that cover only one aspect of the field, The Elements of Computing Systems gives students an integrated and rigorous picture of applied computer science, as its comes to play in the construction of a simple yet powerful computer system. Indeed, the best way to understand how computers work is to build one from scratch, and this textbook leads students through twelve chapters and projects that gradually build a basic hardware platform and a modern software hierarchy from the ground up.

In the process, the students gain hands-on knowledge of hardware architecture, operating systems, programming languages, compilers, data structures, algorithms, and software engineering. Using this constructive approach, the book exposes a significant body of computer science knowledge and demonstrates how theoretical and applied techniques taught in other courses fit into the overall picture.

Designed to support one or two-semester courses, the book is based on an abstraction-implementation paradigm; each chapter presents a key hardware or software abstraction, a proposed implementation that makes it concrete, and an actual project. The emerging computer system can be built by following the chapters, although this is only one option, since the projects are self-contained and can be done or skipped in any order.

All the computer science knowledge necessary for completing the projects is embedded in the book, the only pre-requisite being a programming experience.The book’s web site provides all tools and materials necessary to build all the hardware and software systems described in the text, including two hundred test programs for the twelve projects. The projects and systems can be modified to meet various teaching needs, and all the supplied software is open-source.

The Elements of Computing Systems, second edition: Building a Modern Computer from First Principles [2 ed.] 0262539802, 9780262539807

Table of contents :

Title Page

Copyright

Dedication

Table of Contents

Preface

I. Hardware

1. Boolean Logic

2. Boolean Arithmetic

3. Memory

4. Machine Language

5. Computer Architecture

6. Assembler

II. Software

7. Virtual Machine I: Processing

8. Virtual Machine II: Control

9. High-Level Language

10. Compiler I: Syntax Analysis

11. Compiler II: Code Generation

12. Operating System

13. More Fun to Go

Appendices

1. Boolean Function Synthesis

2. Hardware Description Language

3. Test Description Language

4. The Hack Chip Set

5. The Hack Character Set

6. The Jack OS API

Index

Citation preview

Noam Nisan and Shimon Schocken

The Elements of Computing Systems Building a Modern Computer from First Principles Second Edition

The MIT Press Cambridge, Massachusetts London, England

© 2021 Massachusetts Institute of Technology All rights reserved. No part of this book may be reproduced in any form by any electronic or mechanical means (including photocopying, recording, or information storage and retrieval) without permission in writing from the publisher. Library of Congress Cataloging-in-Publication Data Names: Nisan, Noam, author. | Schocken, Shimon, author. Title: The elements of computing systems : building a modern computer from first principles / Noam Nisan and Shimon Schocken. Description: Second edition. | Cambridge, Massachusetts : The MIT Press, [2021] | Includes index. Identifiers: LCCN 2020002671 | ISBN 9780262539807 (paperback) Subjects: LCSH: Electronic digital computers. Classification: LCC TK7888.3 .N57 2020 | DDC 004.16—dc23 LC record available at https://lccn.loc.gov/2020002671

d_r0

To our parents, for teaching us that less is more.

Contents

Preface

I HARDWARE 1 Boolean Logic 2 Boolean Arithmetic 3 Memory 4 Machine Language 5 Computer Architecture 6 Assembler

II SOFTWARE 7 Virtual Machine I: Processing 8 Virtual Machine II: Control 9 High-Level Language 10 Compiler I: Syntax Analysis 11 Compiler II: Code Generation 12 Operating System 13 More Fun to Go Appendices 1 Boolean Function Synthesis 2 Hardware Description Language 3 Test Description Language 4 The Hack Chip Set

5 The Hack Character Set 6 The Jack OS API Index

List of Figures Figure I.1 Major modules of a typical computer system, consisting of a hardware platform and a software hierarchy. Each module has an abstract view (also called the module’s interface) and an implementation. The right-pointing arrows signify that each module is implemented using abstract building blocks from the level below. Each circle represents a Nand to Tetris project and chapter— twelve projects and chapters altogether. Figure 1.1 Three elementary Boolean functions. Figure 1.2 All the Boolean functions of two binary variables. In general, the number of Boolean functions spanned by n binary variables (here ) is (that’s a lot of Boolean functions). Figure 1.3 Truth table and functional definitions of a Boolean function (example). Figure 1.5 Composite implementation of a three-way And gate. The rectangular dashed outline defines the boundary of the gate interface. Figure 1.6 Xor gate interface (left) and a possible implementation (right). Figure 1.7 Gate diagram and HDL implementation of the Boolean function Xor (And (a, Not (b)), And (Not (a), b)), used as an example. A test script and an output file generated by the test are also shown. Detailed descriptions of HDL and the testing language are given in appendices 2 and 3, respectively. Figure 1.8 A screenshot of simulating an Xor chip in the supplied hardware simulator (other versions of this simulator may have a slightly different GUI). The simulator state is shown just after the test script has completed running. The pin values correspond to the last simulation step Not shown in this screenshot is a compare file that lists the expected output of the simulation specified by this particular test script. Like the test script, the compare file is typically supplied by the client who wants the chip built. In this particular example, the output file generated by the simulation (bottom right of the figure) is identical to the supplied compare file.

Figure 1.9 Multiplexer. The table at the top right is an abbreviated version of the truth table. Figure 1.10 Demultiplexer. Figure 2.1 Two’s complement representation of signed numbers, in a 4-bit binary system. Figure 2.2 Half-adder, designed to add 2 bits. Figure 2.3 Full-adder, designed to add 3 bits. Figure 2.4 16-bit adder, designed to add two 16-bit numbers, with an example of addition action (on the left). Figure 2.5a The Hack ALU, designed to compute the eighteen arithmetic-logical functions shown on the right (the symbols !, &, and | represent, respectively, the 16-bit operations Not, And, and Or). For now, ignore the zr and ng output bits. Figure 2.5b Taken together, the values of the six control bits zx, nx, zy, ny, f, and no cause the ALU to compute one of the functions listed in the rightmost column. Figure 2.5c The Hack ALU API. Figure 3.1 The memory hierarchy built in this chapter. Figure 3.2 Discrete time representation: State changes (input and output values) are observed only during cycle transitions. Within cycles, changes are ignored. Figure 3.3 The data flip-flop (top) and behavioral example (bottom). In the first cycle the previous input is unknown, so the DFF’s output is undefined. In every subsequent time unit, the DFF outputs the input from the previous time unit. Following gate diagramming conventions, the clock input is marked by a small triangle, drawn at the bottom of the gate icon. Figure 3.4 Sequential logic design typically involves DFF gates that feed from, and connect to, combinational chips. This gives sequential chips the ability to respond to current as well as to previous inputs and outputs. Figure 3.5 1-bit register. Stores and emits a 1-bit value until instructed to load a new value. Figure 3.6 16-bit Register. Stores and emits a 16-bit value until instructed to load a new value. Figure 3.7 A RAM chip, consisting of n 16-bit Register chips that can be selected and manipulated separately.

The register addresses are not part of the chip hardware. Rather, they are realized by a gate logic implementation that will be discussed in the next section. Figure 3.8 Program Counter (PC): To use it properly, at most one of the load, inc, or reset bits should be asserted. Figure 3.9 The Bit (1-bit register) implementation: invalid (left) and correct (right) solutions. Figure 4.2 Conceptual model of the Hack memory system. Although the actual architecture is wired somewhat differently (as described in chapter 5), this model helps understand the semantics of Hack programs. Figure 4.3 Hack assembly code examples. Figure 4.4 A Hack assembly program (example). Note that RAM[0] and RAM[1] can be referred to as R0 and R1. Figure 4.5 The Hack instruction set, showing symbolic mnemonics and their corresponding binary codes. Figure 4.6 A Hack assembly program that computes a simple arithmetic expression. Figure 4.7 Array processing example, using pointer-based access to array elements. Figure 4.8 The CPU emulator, with a program loaded in the instruction memory (ROM) and some data in the data memory (RAM). The figure shows a snapshot taken during the program’s execution. Figure 5.1 A generic von Neumann computer architecture. Figure 5.2 The Hack Central Processing Unit (CPU) interface. Figure 5.3 The Hack instruction memory interface. Figure 5.4 The Hack Screen chip interface. Figure 5.5 The Hack Keyboard chip interface. Figure 5.6 The Hack data memory interface. Note that the decimal values 16384 and 24576 are 4000 and 6000 in hexadecimal. Figure 5.7 Interface of Computer, the topmost chip in the Hack hardware platform.

Figure 5.8 Proposed implementation of the Hack CPU, showing an incoming 16-bit instruction. We use the instruction notation cccccccccccccccc to emphasize that in the case of a C-instruction, the instruction is treated as a capsule of control bits, designed to control different CPU chip-parts. In this diagram, every c symbol entering a chip-part stands for some control bit extracted from the instruction (in the case of the ALU, the c’s input stands for the six control bits that instruct the ALU what to compute). Taken together, the distributed behavior induced by these control bits ends up executing the instruction. We don’t specify which bits go where, since we want readers to answer these questions themselves. Figure 5.9 Proposed implementation of Computer, the topmost chip in the Hack platform. Figure 5.10 Testing the Computer chip on the supplied hardware simulator. The stored program is Rect, which draws a rectangle of RAM[0] rows of 16 pixels each, all black, at the top-left of the screen. Figure 6.1 Assembly code, translated to binary code using a symbol table. The line numbers, which are not part of the code, are listed for reference. Figure 6.2 The Hack instruction set, showing both symbolic mnemonics and their corresponding binary codes. Figure 6.3 Testing the assembler’s output using the supplied assembler. Figure II.1 Manipulating points on a plane: example and Jack code. Figure II.2 Jack implementation of the Point abstraction. Figure II.3 Road map of part II (the assembler belongs to part I and is shown here for completeness). The road map describes a translation hierarchy, from a high-level, object-based, multi-class program to VM code, to assembly code, to executable binary code. The numbered circles stand for the projects that implement the compiler, the VM translator, the assembler, and the operating system. Project 9 focuses on writing a Jack application in order to get acquainted with the language. Figure 7.1 The virtual machine framework, using Java as an example. High-level programs are compiled into intermediate VM code. The same VM code can be shipped to, and executed on, any hardware platform equipped with a suitable JVM implementation. These VM implementations are typically realized as client-side programs that translate the VM code into the machine languages of the target devices. Figure 7.2 Stack processing example, illustrating the two elementary operations push and pop. The setting consists of two data structures: a RAM-like memory segment and a stack. Following convention, the stack is drawn as if it grows downward. The location just following the stack’s top value is referred to by a pointer called sp, or stack pointer. The x and y symbols refer to two arbitrary memory locations.

Figure 7.3a Stack-based evaluation of arithmetic expressions. Figure 7.3b Stack-based evaluation of logical expressions. Figure 7.4 Virtual memory segments. Figure 7.5 The arithmetic-logical commands of the VM language. Figure 7.6 The VM emulator supplied with the Nand to Tetris software suite. Figure 8.1 Branching commands action. (The VM code on the right uses symbolic variable names instead of virtual memory segments, to make it more readable.) Figure 8.2 Run-time snapshots of selected stack and segment states during the execution of a three-function program. The line numbers are not part of the code and are given for reference only. Figure 8.3 The global stack, shown when the callee is running. Before the callee terminates, it pushes a return value onto the stack (not shown). When the VM implementation handles the return command, it copies the return value onto argument 0, and sets SP to point to the address just following it. This effectively frees the global stack area below the new value of SP. Thus, when the caller resumes its execution, it sees the return value at the top of its working stack. Figure 8.4 Several snapshots of the global stack, taken during the execution of the main function, which calls factorial to compute 3!. The running function sees only its working stack, which is the unshaded area at the tip of the global stack; the other unshaded areas in the global stack are the working stacks of functions up the calling chain, waiting for the currently running function to return. Note that the shaded areas are not “drawn to scale,” since each frame consists of five words, as shown in figure 8.3. Figure 8.5 Implementation of the function commands of the VM language. All the actions described on the right are realized by generated Hack assembly instructions. Figure 8.6 The naming conventions described above are designed to support the translation of multiple .vm files and functions into a single .asm file, ensuring that the generated assembly symbols will be unique within the file. Figure 9.1 Hello World, written in the Jack language. Figure 9.2 Typical procedural programming and simple array handling. Uses the services of the OS classes Array, Keyboard, and Output.

Figure 9.3a Fraction API (top) and sample Jack class that uses it for creating and manipulating Fraction objects. Figure 9.3b A Jack implementation of the Fraction abstraction. Figure 9.4 Linked list implementation in Jack (left and top right) and sample usage (bottom right). Figure 9.5 Operating system services (summary). The complete OS API is given in appendix 6. Figure 9.6 The syntax elements of the Jack language. Figure 9.7 Variable kinds in the Jack language. Throughout the table, subroutine refers to either a function, a method, or a constructor. Figure 9.8 Statements in the Jack language. Figure 9.9 Screenshots of Jack applications running on the Hack computer. Figure 10.1 Staged development plan of the Jack compiler. Figure 10.2 Definition of the Jack lexicon, and lexical analysis of a sample input. Figure 10.3 A subset of the Jack language grammar, and Jack code segments that are either accepted or rejected by the grammar. Figure 10.4a Parse tree of a typical code segment. The parsing process is driven by the grammar rules. Figure 10.4b Same parse tree, in XML. Figure 10.5 The Jack grammar. Figure 11.1 The Point class. This class features all the possible variable kinds (field, static, local, and argument) and subroutine kinds (constructor, method, and function), as well as subroutines that return primitive types, object types, and void subroutines. It also illustrates function calls, constructor calls, and method calls on the current object (this) and on other objects. Figure 11.2 Symbol table examples. The this row in the subroutine-level table is discussed later in the chapter. Figure 11.3 Infix and postfix renditions of the same semantics.

Figure 11.4 A VM code generation algorithm for expressions, and a compilation example. The algorithm assumes that the input expression is valid. The final implementation of this algorithm should replace the emitted symbolic variables with their corresponding symbol table mappings. Figure 11.5 Expressions in the Jack language. Figure 11.6 Compiling if and while statements. The L1 and L2 labels are generated by the compiler. Figure 11.7 Object construction from the caller’s perspective. In this example, the caller declares two object variables and then calls a class constructor for constructing the two objects. The constructor works its magic, allocating memory blocks for representing the two objects. The calling code then makes the two object variables refer to these memory blocks. Figure 11.8 Object construction: the constructor’s perspective. Figure 11.9 Compiling method calls: the caller’s perspective. Figure 11.10 Compiling methods: the callee’s perspective. Figure 11.11 Array access using VM commands. Figure 11.12 Basic compilation strategy for arrays, and an example of the bugs that it can generate. In this particular case, the value stored in pointer 1 is overridden, and the address of a[i] is lost. Figure 12.1 Multiplication algorithm. Figure 12.2 Division algorithm. Figure 12.3 Square root algorithm. Figure 12.4 String-integer conversions. (appendChar, length, and charAt are String class methods.) Figure 12.5a Memory allocation algorithm (basic). Figure 12.5b Memory allocation algorithm (improved). Figure 12.6 Drawing a pixel. Figure 12.7 Line-drawing algorithm: basic version (bottom, left) and improved version (bottom, right).

Figure 12.8 Circle-drawing algorithm. Figure 12.9 Example of a character bitmap. Figure 12.10 Handling input from the keyboard. Figure 12.11 A trapdoor enabling complete control of the host RAM from Jack. Figure 12.12 Logical view (left) and physical implementation (right) of a linked list that supports dynamic memory allocation. Figure A1.1 Synthesizing a Boolean function from a truth table (example). Figure A2.1 HDL program example. Figure A2.2 Buses in action (example). Figure A2.3 Built-in chip definition example. Figure A2.4 DFF definition. Figure A2.5 A chip that activates GUI-empowered chip-parts. Figure A2.6 GUI-empowered chips demo. Since the loaded HDL program uses GUI-empowered chip-parts (step 1), the simulator renders their respective GUI images (step 2). When the user changes the values of the chip input pins (step 3), the simulator reflects these changes in the respective GUIs (step 4). Figure A3.1 Test script and compare file (example). Figure A3.2 Variables and methods of key built-in chips in Nand to Tetris. Figure A3.3 Testing the topmost Computer chip. Figure A3.4 Testing a machine language program on the CPU emulator. Figure A3.5 Testing a VM program on the VM emulator.

Preface

What I hear, I forget; What I see, I remember; What I do, I understand. —Confucius (551–479 B.C.)

It is commonly argued that enlightened people of the twenty-first century ought to familiarize themselves with the key ideas underlying BANG: Bits, Atoms, Neurons, and Genes. Although science has been remarkably successful in uncovering their basic operating systems, it is quite possible that we will never fully grasp how atoms, neurons, and genes actually work. Bits, however, and computing systems at large, entail a consoling exception: in spite of their fantastic complexity, one can completely understand how modern computers work, and how they are built. So, as we gaze with awe at the BANG around us, it is a pleasing thought that at least one field in this quartet can be fully laid bare to human comprehension. Indeed, in the early days of computers, any curious person who cared to do so could gain a gestalt understanding of how the machine works. The interactions between hardware and software were simple and transparent enough to produce a coherent picture of the computer’s operations. Alas, as digital technologies have become increasingly more complex, this clarity is all but lost: the most fundamental ideas and techniques in computer science —the very essence of the field—are now hidden under many layers of obscure interfaces and proprietary implementations. An inevitable consequence of this complexity has been specialization: the study of applied computer science became a pursuit of many niche courses, each covering a single aspect of the field. We wrote this book because we felt that many computer science students are missing the forest for the trees. The typical learner is marshaled through

a series of courses in programming, theory, and engineering, without pausing to appreciate the beauty of the picture at large. And the picture at large is such that hardware, software, and application systems are tightly interrelated through a hidden web of abstractions, interfaces, and contractbased implementations. Failure to see this intricate enterprise in the flesh leaves many learners and professionals with an uneasy feeling that, well, they don’t fully understand what’s going on inside computers. This is unfortunate, since computers are the most important machines in the twenty-first century. We believe that the best way to understand how computers work is to build one from scratch. With that in mind, we came up with the following idea: Let’s specify a simple but sufficiently powerful computer system, and invite learners to build its hardware platform and software hierarchy from the ground up. And while we are at it, let’s do it right. We are saying this because building a general-purpose computer from first principles is a huge enterprise. Therefore, we identified a unique educational opportunity to not only build the thing, but also illustrate, in a hands-on fashion, how to effectively plan and manage large-scale hardware and software development projects. In addition, we sought to demonstrate the thrill of constructing, through careful reasoning and modular planning, fantastically complex and useful systems from first principles. The outcome of this effort became what is now known colloquially as Nand to Tetris: a hands-on journey that starts with the most elementary logic gate, called Nand, and ends up, twelve projects later, with a generalpurpose computer system capable of running Tetris, as well as any other program that comes to your mind. After designing, building, redesigning, and rebuilding the computer system several times ourselves, we wrote this book, explaining how any learner can do the same. We also launched the www.nand2tetris.org website, making all our project materials and software tools freely available to anyone who wants to learn, or teach, Nand to Tetris courses. We are gratified to say that the response has been overwhelming. Today, Nand to Tetris courses are taught in numerous universities, high schools, coding boot camps, online platforms, and hacker clubs around the world. The book and our online courses became highly popular, and thousands of

learners—ranging from high school students to Google engineers— routinely post reviews describing Nand to Tetris as their best educational experience ever. As Richard Feynman famously said: “What I cannot create, I do not understand.” Nand to Tetris is all about understanding through creation. Apparently, people connect passionately to this maker mentality. Since the publication of the book’s first edition, we received numerous questions, comments, and suggestions. As we addressed these issues by modifying our online materials, a gap developed between the web-based and the book-based versions of Nand to Tetris. In addition, we felt that many book sections could benefit from more clarity and a better organization. So, after delaying this surgery as much as we could, we decided to roll up our sleeves and write a second edition, leading to the present book. The remainder of this preface describes this new edition, ending with a section that compares it to the previous one.

Scope The book exposes learners to a significant body of computer science knowledge, gained through a series of hardware and software construction tasks. In particular, the following topics are illustrated in the context of hands-on projects: Hardware: Boolean arithmetic, combinational logic, sequential logic, design and implementation of logic gates, multiplexers, flip-flops, registers, RAM units, counters, Hardware Description Language (HDL), chip simulation, verification and testing. Architecture: ALU/CPU design and implementation, clocks and cycles, addressing modes, fetch and execute logic, instruction set, memorymapped input/output. Low-level languages: Design and implementation of a simple machine language (binary and symbolic), instruction sets, assembly programming, assemblers.

Virtual machines: Stack-based automata, stack arithmetic, function call and return, handling recursion, design and implementation of a simple VM language. High-level languages: Design and implementation of a simple objectbased, Java-like language: abstract data types, classes, constructors, methods, scoping rules, syntax and semantics, references. Compilers: Lexical analysis, parsing, symbol tables, code generation, implementation of arrays and objects, two-tier compilation. Programming: Implementation of an assembler, virtual machine, and compiler, following supplied APIs. Can be done in any programming language. Operating systems: Design and implementation of memory management, math library, input/output drivers, string processing, textual output, graphical output, high-level language support. Data structures and algorithms: Stacks, hash tables, lists, trees, arithmetic algorithms, geometric algorithms, running time considerations. Software engineering: Modular design, the interface/implementation paradigm, API design and documentation, unit testing, proactive test planning, quality assurance, programming at the large. A unique feature of Nand to Tetris is that all these topics are presented cohesively, with a clear, over-arching purpose: building a modern computer system from the ground up. In fact, this has been our topic selection criterion: the book focuses on the minimal set of topics necessary for building a general-purpose computer system, capable of running programs written in a high-level, object-based language. As it turns out, this critical set includes most of the fundamental concepts and techniques, as well as some of the most beautiful ideas, in applied computer science.

Courses Nand to Tetris courses are typically cross-listed for both undergraduate and graduate students, and are highly popular among self-learners. Courses based on this book are “perpendicular” to the typical computer science

curriculum and can be taken at almost any point during the program. Two natural slots are CS-2—an introductory yet post-programming course—and CS-99—a synthesis course coming at the end of the program. The former course entails a forward-looking, systems-oriented introduction to applied computer science, while the latter is an integrative, project-based course that fills gaps left by previous courses. Another increasingly popular slot is a course that combines, in one framework, key topics from traditional computer architecture courses and compilation courses. Whichever purpose they are made to serve, Nand to Tetris courses go by many names, including Elements of Computing Systems, Digital Systems Construction, Computer Organization, Let’s Build a Computer, and, of course, Nand to Tetris. The book and the projects are highly modular, starting from the top division into Part I: Hardware and Part II: Software, each comprising six chapters and six projects. Although we recommend going through the full experience, it is entirely possible to learn each of the two parts separately. The book and the projects can support two independent courses, each six to seven weeks long, a typical semester-long course, or two semester-long courses, depending on topic selection and pace of study. The book is completely self-contained: all the necessary knowledge for building the hardware and software systems described in the book is given in its chapters and projects. Part I: Hardware requires no prerequisite knowledge, making projects 1–6 accessible to any student and self-learner. Part II: Software and projects 7–12 require programming (in any high-level language) as a prerequisite. Nand to Tetris courses are not restricted to computer science majors. Rather, they lend themselves to learners from any discipline who seek to gain a hands-on understanding of hardware architectures, operating systems, compilation, and software engineering—all in one course. Once again, the only prerequisite (for part II) is programming. Indeed, many Nand to Tetris students are nonmajors who took an introduction to computer science course and now wish to learn more computer science without committing themselves to a multicourse program. Many other learners are software developers who wish to “go below,” understand how the enabling technologies work, and become better high-level programmers.

Following the acute shortage of developers in the hardware and software industries, there is a growing demand for compact and focused programs in applied computer science. These often take the form of coding boot camps and clusters of online courses designed to prepare learners for the job market without going through the full gamut of an academic degree. Any such solid program must offer, at minimum, working knowledge of programming, algorithms, and systems. Nand to Tetris is uniquely positioned to cover the systems element of such programs, in the framework of one course. Further, the Nand to Tetris projects provide an attractive means for synthesizing, and putting to practice, much of the algorithmic and programmatic knowledge learned in other courses.

Resources All the necessary tools for building the hardware and software systems described in the book are supplied freely in the Nand to Tetris software suite. These include a hardware simulator, a CPU emulator, a VM emulator (all in open source), tutorials, and executable versions of the assembler, virtual machine, compiler, and operating system described in the book. In addition, the www.nand2tetris.org website includes all the project materials —about two hundred test programs and test scripts—allowing incremental development and unit testing of each one of the twelve projects. The software tools and project materials can be used as is on any computer running Windows, Linux, or macOS.

Structure Part I: Hardware consists of chapters 1–6. Following an introduction to Boolean algebra, chapter 1 starts with the elementary Nand gate and builds a set of elementary logic gates on top of it. Chapter 2 presents combinational logic and builds a set of adders, leading up to an ALU. Chapter 3 presents sequential logic and builds a set of registers and memory devices, leading up to a RAM. Chapter 4 discusses low-level programming and specifies a machine language in both its symbolic and binary forms.

Chapter 5 integrates the chips built in chapters 1–3 into a hardware architecture capable of executing programs written in the machine language presented in chapter 4. Chapter 6 discusses low-level program translation, culminating in the construction of an assembler. Part II: Software consists of chapters 7–12 and requires programming background (in any language) at the level of introduction to computer science courses. Chapters 7–8 present stack-based automata and describe the construction of a JVM-like virtual machine. Chapter 9 presents an object-based, Java-like high-level language. Chapters 10–11 discuss parsing and code generation algorithms and describe the construction of a two-tier compiler. Chapter 12 presents various memory management, algebraic, and geometric algorithms and describes the implementation of an operating system that puts them to practice. The OS is designed to close gaps between the high-level language implemented in part II and the hardware platform built in part I. The book is based on an abstraction-implementation paradigm. Each chapter starts with an Introduction describing relevant concepts and a generic hardware or software system. The next section is always Specification, describing the system’s abstraction, that is, the various services that it is expected to deliver, one way or another. Having presented the what, each chapter proceeds to discuss how the abstraction can be realized, leading to a proposed Implementation section. The next section is always Project, providing step-by-step guidelines, testing materials, and software tools for building and unit-testing the system described in the chapter. The closing Perspective section highlights noteworthy issues left out from the chapter.

Projects The computer system described in the book is for real. It is designed to be built, and it works! The book is geared toward active readers who are willing to get their hands dirty and build the computer from the ground up. If you’ll take the time and effort to do so, you will gain a depth of understanding and a sense of accomplishment unmatched by mere reading.

The hardware devices built in projects 1, 2, 3, and 5 are implemented using a simple Hardware Description Language (HDL) and simulated on a supplied software-based hardware simulator, which is exactly how hardware architects work in industry. Projects 6, 7, 8, 10, and 11 (assembler, virtual machine , and compiler ) can be written in any programming language. Project 4 is written in the computer’s assembly language, and projects 9 and 12 (a simple computer game and a basic operating system) are written in Jack—the Java-like high-level language for which we build a compiler in chapters 10 and 11. There are twelve projects altogether. On average, each project entails a weekly homework load in a typical rigorous university-level course. The projects are self-contained and can be done (or skipped) in any desired order. The full Nand to Tetris experience entails doing all the projects in their order of appearance, but this is only one option. Is it possible to cover so much ground in a one-semester course? The answer is yes, and the proof is in the pudding: more than 150 universities teach semester-long Nand to Tetris courses. The student satisfaction is exceptional, and Nand to Tetris MOOCs are routinely listed at the top of the top-rated lists of online course. One reason why learners respond to our methodology is focus. Except for obvious cases, we pay no attention to optimization, leaving this important subject to other, more specific courses. In addition, we allow students to assume error-free inputs. This eliminates the need to write code for handling exceptions, making the software projects significantly more focused and manageable. Dealing with incorrect input is of course critically important, but this skill can be honed elsewhere, for example, in project extensions and in dedicated programming and software design courses.

The Second Edition Although Nand to Tetris was always structured around two themes, the second edition makes this structure explicit: The book is now divided into two distinct and standalone parts, Part I: Hardware and Part II: Software. Each part consists of six chapters and six projects and begins with a newly written introduction that sets the stage for the part’s chapters. Importantly,

the two parts are independent of each other. Thus, the new book structure lends itself well to quarter-long as well as semester-long courses. In addition to the two new introduction chapters, the second edition features four new appendices. Following the requests of many learners, these new appendices give focused presentations of various technical topics that, in the first edition, were scattered across the chapters. Another new appendix provides a formal proof that any Boolean function can be built from Nand operators, adding a theoretical perspective to the applied hardware construction projects. Many new sections, figures, and examples were added. All the chapters and project materials were rewritten with an emphasis on separating abstraction from implementation—a major theme in Nand to Tetris. We took special care to add examples and sections that address the thousands of questions that were posted over the years in Nand to Tetris Q&A forums.

Acknowledgments The software tools that accompany the book were developed by our students at IDC Herzliya and at the Hebrew University. The two chief software architects were Yaron Ukrainitz and Yannai Gonczarowski, and the developers included Iftach Ian Amit, Assaf Gad, Gal Katzhendler, Hadar Rosen-Sior, and Nir Rozen. Oren Baranes, Oren Cohen, Jonathan Gross, Golan Parashi, and Uri Zeira worked on other aspects of the tools. Working with these student-developers has been a great pleasure, and we are proud to have had the opportunity to play a role in their education. We also thank our teaching assistants, Muawyah Akash, Philip Hendrix, Eytan Lifshitz, Ran Navok, and David Rabinowitz, who helped run early versions of the course that led to this book. Tal Achituv, Yong Bakos, Tali Gutman and Michael Schröder provided great help with various aspects of the course materials, and Aryeh Schnall, Tomasz Różański and Rudolf Adamkovič gave meticulous editing suggestions. Rudolf’s comments were particularly enlightening, for which we are very grateful. Many people around the world got involved in Nand to Tetris, and we cannot possibly thank them individually. We do take one exception. Mark

Armbrust, a software and firmware engineer from Colorado, became the guarding angel of Nand to Tetris learners. Volunteering to manage our global Q&A forum, Mark answered numerous questions with great patience and graceful style. His answers never gave away the solutions; rather, he guided learners how to apply themselves and see the light on their own. In doing so, Mark has gained the respect and admiration of numerous learners around the world. Serving at the forefront of Nand to Tetris for more than ten years, Mark wrote 2,607 posts, discovered dozens of bugs, and wrote corrective scripts and fixes. Doing all this in addition to his regular day job, Mark became a pillar of the Nand to Tetris community, and the community became his second home. Mark died in March 2019, following a struggle with heart disease that lasted several months. During his hospitalization, Mark received a daily stream of hundreds of emails from Nand to Tetris students. Young men and women from all over the world thanked Mark for his boundless generosity and shared the impact that he has had on their lives. In recent years, computer science education became a powerful driver of personal growth and economic mobility. Looking back, we feel fortunate that we decided, early on, to make all our teaching resources freely available, in open source. Quite simply, any person who wants to do so can not only learn but also teach Nand to Tetris courses, without any restrictions. All you have to do is go to our website and take what you need, so long as you operate in a nonprofit setting. This turned Nand to Tetris into a readily available vehicle for disseminating high-quality computer science education, freely and equitably. The result became a vast educational ecosystem, fueled by endless supplies of good will. We thank the many people around the world who helped us make it happen.

I Hardware

The true voyage of discovery consists not of going to new places, but of having a new pair of eyes. —Marcel Proust (1871–1922)

This book is a voyage of discovery. You are about to learn three things: how computer systems work, how to break complex problems into manageable modules, and how to build large-scale hardware and software systems. This will be a hands-on journey, as you create a complete and working computer system from the ground up. The lessons you will learn, which are far more important than the computer itself, will be gained as side effects of these constructions. According to the psychologist Carl Rogers, “The only kind of learning which significantly influences behavior is self-discovered or self-appropriated—truth that has been assimilated in experience.” This introduction chapter sketches some of the discoveries, truths, and experiences that lie ahead.

Hello, World Below If you have some programming experience, you’ve probably encountered something like the program below early in your training. And if you haven’t, you can still guess what the program is doing: it displays the text Hello World and terminates. This particular program is written in Jack—a simple, Java-like high-level language:

Trivial programs like Hello World are deceptively simple. Did you ever stop to think about what it takes to actually run such a program on a computer? Let’s look under the hood. For starters, note that the program is nothing more than a sequence of plain characters, stored in a text file. This abstraction is a complete mystery for the computer, which understands only instructions written in machine language. Thus, if we want to execute this program, the first thing we must do is parse the string of characters of which the high-level code is made, uncover its semantics—figure out what the program seeks to do—and then generate low-level code that reexpresses this semantics using the machine language of the target computer. The result of this elaborate translation process, known as compilation, will be an executable sequence of machine language instructions. Of course, machine language is also an abstraction—an agreed upon set of binary codes. To make this abstraction concrete, it must be realized by some hardware architecture. And this architecture, in turn, is implemented by a certain set of chips—registers, memory units, adders, and so on. Now, every one of these hardware devices is constructed from lower-level, elementary logic gates. And these gates, in turn, can be built from primitive gates like Nand and Nor. These primitive gates are very low in the hierarchy, but they, too, are made of several switching devices, typically implemented by transistors. And each transistor is made of—Well, we won’t go further than that, because that’s where computer science ends and physics starts. You may be thinking: “On my computer, compiling and running programs is much easier—all I have to do is click this icon or write that command!” Indeed, a modern computer system is like a submerged iceberg: most people get to see only the top, and their knowledge of computing systems is sketchy and superficial. If, however, you wish to explore beneath the surface, then Lucky You! There’s a fascinating world down there, made

of some of the most beautiful stuff in computer science. An intimate understanding of this underworld is what separates naïve programmers from sophisticated developers—people who can create complex hardware and software technologies. And the best way to understand how these technologies work—and we mean understand them in the marrow of your bones—is to build a complete computer system from the ground up.

Nand to Tetris Assuming that we want to build a computer system from the ground up, which specific computer should we build? As it turns out, every generalpurpose computer—every PC, smartphone, or server—is a Nand to Tetris machine. First, all computers are based, at bottom, on elementary logic gates, of which Nand is the most widely used in industry (we’ll explain what exactly is a Nand gate in chapter 1). Second, every general-purpose computer can be programmed to run a Tetris game, as well as any other program that tickles your fancy. Thus, there is nothing unique about either Nand or Tetris. It is the word to in Nand to Tetris that turns this book into the magical journey that you are about to undertake: going all the way from a heap of barebone switching devices to a machine that engages the mind with text, graphics, animation, music, video, analysis, simulation, artificial intelligence, and all the capabilities that we came to expect from generalpurpose computers. Therefore, it doesn’t really matter which specific hardware platform and software hierarchy we will build, so long as they will be based on the same ideas and techniques that characterize all computing systems out there. Figure I.1 describes the key milestones in the Nand to Tetris road map. Starting at the bottom tier of the figure, any general-purpose computer has an architecture that includes a ALU (Arithmetic Logic Unit) and a RAM (Random Access Memory). All ALU and RAM devices are made of elementary logic gates. And, surprisingly and fortunately, as we will soon see, all logic gates can be made from Nand gates alone. Focusing on the software hierarchy, all high-level languages rely on a suite of translators (compiler/interpreter, virtual machine, assembler) for reducing high-level code all the way down to machine-level instructions. Some high-level

languages are interpreted rather than compiled, and some don’t use a virtual machine, but the big picture is essentially the same. This observation is a manifestation of a fundamental computer science principle, known as the Church-Turing conjecture: at bottom, all computers are essentially equivalent.

Figure I.1 Major modules of a typical computer system, consisting of a hardware platform and a software hierarchy. Each module has an abstract view (also called the module’s interface) and an implementation. The right-pointing arrows signify that each module is implemented using abstract building blocks from the level below. Each circle represents a Nand to Tetris project and chapter— twelve projects and chapters altogether.

We make these observations in order to emphasize the generality of our approach: the challenges, insights, tips, tricks, techniques, and terminology that you will encounter in this book are exactly the same as those encountered by practicing hardware and software engineers. In that respect, Nand to Tetris is a form of initiation: if you’ll manage to complete the journey, you will gain an excellent basis for becoming a hardcore computer professional yourself. So, which specific hardware platform, and which specific high-level language, shall we build in Nand to Tetris? One possibility is building an industrial-strength, widely used computer model and writing a compiler for a popular high-level language. We opted against these choices, for three reasons. First, computer models come and go, and hot programming

languages give way to new ones. Therefore, we didn’t want to commit to any particular hardware/software configuration. Second, the computers and languages that are used in practice feature numerous details that have little instructive value, yet take ages to implement. Finally, we sought a hardware platform and a software hierarchy that could be easily controlled, understood, and extended. These considerations led to the creation of Hack, the computer platform built in part I of the book, and Jack, the high-level language implemented in part II. Typically, computer systems are described top-down, showing how highlevel abstractions can be reduced to, or realized by, simpler ones. For example, we can describe how binary machine instructions executing on the computer architecture are broken into micro-codes that travel through the architecture’s wires and end up manipulating the lower-level ALU and RAM chips. Alternatively, we can go bottom-up, describing how the ALU and RAM chips are judiciously designed to execute micro-codes that, taken together, form binary machine instructions. Both the top-down and the bottom-up approaches are enlightening, each giving a different perspective on the system that we are about to build. In figure I.1, the direction of the arrows suggests a top-down orientation. For any given pair of modules, there is a right-pointing arrow connecting the higher module with the lower one. The meaning of this arrow is precise: it implies that the higher-level module is implemented using abstract building blocks from the level below. For example, a high-level program is implemented by translating each high-level statement into a set of abstract VM commands. And each VM command, in turn, is translated further into a set of abstract machine language instructions. And so it goes. The distinction between abstraction and implementation plays a major role in systems design, as we now turn to discuss.

Abstraction and Implementation You may wonder how it is humanly possible to construct a complete computer system from the ground up, starting with nothing more than elementary logic gates. This must be a humongous enterprise! We deal with this complexity by breaking the system into modules. Each module is

described separately, in a dedicated chapter, and built separately, in a standalone project. You might then wonder, how is it possible to describe and construct these modules in isolation? Surely they are interrelated! As we will demonstrate throughout the book, a good modular design implies just that: you can work on the individual modules independently, while completely ignoring the rest of the system. In fact, if the system is well designed, you can build these modules in any desired order, and even in parallel, if you work in a team. The cognitive ability to “divide and conquer” a complex system into manageable modules is empowered by yet another cognitive gift: our ability to discern between the abstraction and the implementation of each module. In computer science, we take these words concretely: abstraction describes what the module does, and implementation describes how it does it. With this distinction in mind, here is the most important rule in system engineering: when using a module as a building block—any module—you are to focus exclusively on the module’s abstraction, ignoring completely its implementation details. For example, let’s focus on the bottom tier of figure I.1, starting at the “computer architecture” level. As seen in the figure, the implementation of this architecture uses several building blocks from the level below, including a Random Access Memory. The RAM is a remarkable device. It may contain billions of registers, yet any one of them can be accessed directly, and almost instantaneously. Figure I.1 informs us that the computer architect should use this direct-access device abstractly, without paying any attention to how it is actually realized. All the work, cleverness, and drama that went into implementing the direct-access RAM magic—the how— should be completely ignored, since this information is irrelevant in the context of using the RAM for its effect. Going one level downward in figure I.1, we now find ourselves in the position of having to build the RAM chip. How should we go about it? Following the right-pointing arrow, we see that the RAM implementation will be based on elementary logic gates and chips from the level below. In particular, the RAM storage and direct-access capabilities will be realized using registers and multiplexers, respectively. And once again, the same abstraction-implementation principle kicks in: we will use these chips as abstract building blocks, focusing on their interfaces, and caring naught

about their implementations. And so it goes, all the way down to the Nand level. To recap, whenever your implementation uses a lower-level hardware or software module, you are to treat this module as an off-the-shelf, black box abstraction: all you need is the documentation of the module’s interface, describing what it can do, and off you go. You are to pay no attention whatsoever to how the module performs what its interface advertises. This abstraction-implementation paradigm helps developers manage complexity and maintain sanity: by dividing an overwhelming system into well-defined modules, we create manageable chunks of implementation work and localize error detection and correction. This is the most important design principle in hardware and software construction projects. Needless to say, everything in this story hinges on the intricate art of modular design: the human ability to separate the problem at hand into an elegant collection of well-defined modules, each having a clear interface, each representing a reasonable chunk of standalone implementation work, each lending itself to an independent unit-testing program. Indeed, modular design is the bread and butter of applied computer science: every system architect routinely defines abstractions, sometimes referred to as modules or interfaces, and then implements them, or asks other people to implement them. The abstractions are often built layer upon layer, resulting in higher and higher levels of functionality. If the system architect designs a good set of modules, the implementation work will flow like clear water; if the design is slipshod, the implementation will be doomed. Modular design is an acquired art, honed by seeing and implementing many well-designed abstractions. That’s exactly what you are about to experience in Nand to Tetris: you will learn to appreciate the elegance and functionality of hundreds of hardware and software abstractions. You will then be guided how to implement each one of these abstractions, one step at a time, creating bigger and bigger chunks of functionality. As you push ahead in this journey, going from one chapter to the next, it will be thrilling to look back and appreciate the computer system that is gradually taking shape in the wake of your efforts.

Methodology The Nand to Tetris journey entails building a hardware platform and a software hierarchy. The hardware platform is based on a set of about thirty logic gates and chips, built in part I of the book. Every one of these gates and chips, including the topmost computer architecture, will be built using a Hardware Description Language. The HDL that we will use is documented in appendix 2 and can be learned in about one hour. You will test the correctness of your HDL programs using a software-based hardware simulator running on your PC. This is exactly how hardware engineers work in practice: they build and test chips using software-based simulators. When they are satisfied with the simulated performance of the chips, they ship their specifications (HDL programs) to a fabrication company. Following optimization, the HDL programs become the input of robotic arms that build the hardware in silicon. Moving up on the Nand to Tetris journey, in part II of the book we will build a software stack that includes an assembler, a virtual machine, and a compiler. These programs can be implemented in any high-level programming language. In addition, we will build a basic operating system, written in Jack. You may wonder how it is possible to develop these ambitious projects in the scope of one course or one book. Well, in addition to modular design, our secret sauce is reducing design uncertainty to an absolute minimum. We’ll provide elaborate scaffolding for each project, including detailed APIs, skeletal programs, test scripts, and staged implementation guidelines. All the software tools that are necessary for completing projects 1–12 are available in the Nand to Tetris software suite, which can be downloaded freely from www.nand2tetris.org. These include a hardware simulator, a CPU emulator, a VM emulator, and executable versions of the hardware chips, assembler, compiler, and OS. Once you download the software suite to your PC, all these tools will be at your fingertips.

The Road Ahead

The Nand to Tetris journey entails twelve hardware and software construction projects. The general direction of development across these projects, as well as the book’s table of contents, imply a bottom-up journey: we start with elementary logic gates and work our way upward, leading to a high-level, object-based programming language. At the same time, the direction of development within each project is top-down. In particular, whenever we present a hardware or software module, we will always start with an abstract description of what the module is designed to do and why it is needed. Once you understand the module’s abstraction (a rich world in its own right), you’ll proceed to implement it, using abstract building blocks from the level below. So here, finally, is the grand plan of part I of our tour de force. In chapter 1 we start with a single logic gate—Nand—and build from it a set of elementary and commonly used logic gates like And, Or, Xor, and so on. In chapters 2 and 3 we use these building blocks for constructing an Arithmetic Logic Unit and memory devices, respectively. In chapter 4 we pause our hardware construction journey and introduce a low-level machine language in both its symbolic and binary forms. In chapter 5 we use the previously built ALU and memory units for building a Central Processing Unit (CPU) and a Random Access Memory (RAM). These devices will then be integrated into a hardware platform capable of running programs written in the machine language presented in chapter 4. In chapter 6 we describe and build an assembler, which is a program that translates lowlevel programs written in symbolic machine language into executable binary code. This will complete the construction of the hardware platform. This platform will then become the point of departure for part II of the book, in which we’ll extend the barebone hardware with a modern software hierarchy consisting of a virtual machine, a compiler, and an operating system. We hope that we managed to convey what lies ahead, and that you are eager to get started on this grand voyage of discovery. So, assuming that you are ready and set, let the countdown start: 1, 0, Go!

1 Boolean Logic

Such simple things, and we make of them something so complex it defeats us, Almost. —John Ashbery (1927–2017)

Every digital device—be it a personal computer, a cell phone, or a network router—is based on a set of chips designed to store and process binary information. Although these chips come in different shapes and forms, they are all made of the same building blocks: elementary logic gates. The gates can be physically realized using many different hardware technologies, but their logical behavior, or abstraction, is consistent across all implementations. In this chapter we start out with one primitive logic gate—Nand—and build all the other logic gates that we will need from it. In particular, we will build Not, And, Or, and Xor gates, as well as two gates named multiplexer and demultiplexer (the function of all these gates is described below). Since our target computer will be designed to operate on 16-bit values, we will also build 16-bit versions of the basic gates, like Not16, And16, and so on. The result will be a rather standard set of logic gates, which will be later used to construct our computer’s processing and memory chips. This will be done in chapters 2 and 3, respectively. The chapter starts with the minimal set of theoretical concepts and practical tools needed for designing and implementing logic gates. In particular, we introduce Boolean algebra and Boolean functions and show how Boolean functions can be realized by logic gates. We then describe how logic gates can be implemented using a Hardware Description Language (HDL) and how these designs can be tested using hardware simulators. This introduction will carry its weight throughout part I of the

book, since Boolean algebra and HDL will come into play in every one of the forthcoming hardware chapters and projects.

1.1 Boolean Algebra Boolean algebra manipulates two-state binary values that are typically labeled true/false, 1/0, yes/no, on/off, and so forth. We will use 1 and 0. A Boolean function is a function that operates on binary inputs and returns binary outputs. Since computer hardware is based on representing and manipulating binary values, Boolean functions play a central role in the specification, analysis, and optimization of hardware architectures. Boolean operators: Figure 1.1 presents three commonly used Boolean functions, also known as Boolean operators. These functions are named And, Or, and Not, also written using the notation , , and , or and ¬x, respectively. Figure 1.2 gives the definition of all the possible Boolean functions that can be defined over two variables, along with their common names. These functions were constructed systematically by enumerating all the possible combinations of values spanned by two binary variables. Each operator has a conventional name that seeks to describe its underlying semantics. For example, the name of the Nand operator is shorthand for Not-And, coming from the observation that Nand (x, y) is equivalent to Not (And (x, y)). The Xor operator—shorthand for exclusive or—evaluates to 1 when exactly one of its two variables is 1. The Nor gate derives its name from Not-Or. All these gate names are not terribly important.

Figure 1.1 Three elementary Boolean functions.

Figure 1.2 All the Boolean functions of two binary variables. In general, the number of Boolean functions spanned by n binary variables (here ) is (that’s a lot of Boolean functions).

Figure 1.2 begs the question: What makes And, Or, and Not more interesting, or privileged, than any other subset of Boolean operators? The short answer is that indeed there is nothing special about And, Or, and Not. A deeper answer is that various subsets of logical operators can be used for expressing any Boolean function, and {And, Or, Not} is one such subset. If you find this claim impressive, consider this: any one of these three basic operators can be expressed using yet another operator—Nand. Now, that’s impressive! It follows that any Boolean function can be realized using Nand

gates only. Appendix 1, which is an optional reading, provides a proof of this remarkable claim. Boolean Functions Every Boolean function can be defined using two alternative representations. First, we can define the function using a truth table, as we do in figure 1.3. For each one of the 2n possible tuples of variable values (here ), the table lists the value of f . In addition to this data-driven definition, we can also define Boolean functions using Boolean expressions, for example, And Not (z).

Figure 1.3 Truth table and functional definitions of a Boolean function (example).

How can we verify that a given Boolean expression is equivalent to a given truth table? Let’s use figure 1.3 as an example. Starting with the first row, we compute f (0, 0, 0), which is (0 Or 0) And Not (0). This expression evaluates to 0, the same value listed in the truth table. So far so good. A similar equivalence test can be applied to every row in the table—a rather tedious affair. Instead of using this laborious bottom-up proof technique, we can prove the equivalence top-down, by analyzing the Boolean expression (x Or y) And Not (z). Focusing on the left-hand side of the And operator, we observe that the overall expression evaluates to 1 only when ((x is 1) Or (y

is 1)). Turning to the right-hand side of the And operator, we observe that the overall expression evaluates to 1 only when (z is 0). Putting these two observations together, we conclude that the expression evaluates to 1 only when (((x is 1) Or (y is 1)) And (z is 0)). This pattern of 0’s and 1’s occurs only in rows 3, 5, and 7 of the truth table, and indeed these are the only rows in which the table’s rightmost column contains a 1. Truth Tables and Boolean Expressions Given a Boolean function of n variables represented by a Boolean expression, we can always construct from it the function’s truth table. We simply compute the function for every set of values (row) in the table. This construction is laborious, and obvious. At the same time, the dual construction is not obvious at all: Given a truth table representation of a Boolean function, can we always synthesize from it a Boolean expression for the underlying function? The answer to this intriguing question is yes. A proof can be found in appendix 1. When it comes to building computers, the truth table representation, the Boolean expression, and the ability to construct one from the other are all highly relevant. For example, suppose that we are called to build some hardware for sequencing DNA data and that our domain expert biologist wants to describe the sequencing logic using a truth table. Our job is to realize this logic in hardware. Taking the given truth table data as a point of departure, we can synthesize from it a Boolean expression that represents the underlying function. After simplifying the expression using Boolean algebra, we can proceed to implement it using logic gates, as we’ll do later in the chapter. To sum up, a truth table is often a convenient means for describing some states of nature, whereas a Boolean expression is a convenient formalism for realizing this description in silicon. The ability to move from one representation to the other is one of the most important practices of hardware design. We note in passing that although the truth table representation of a Boolean function is unique, every Boolean function can be represented by many different yet equivalent Boolean expressions, and some will be shorter and easier to work with. For example, the expression (Not (x And y) And (Not (x) Or y) And (Not (y) Or y)) is equivalent to the expression Not

(x). We see that the ability to simplify a Boolean expression is the first step toward hardware optimization. This is done using Boolean algebra and common sense, as illustrated in appendix 1.

1.2 Logic Gates A gate is a physical device that implements a simple Boolean function. Although most digital computers today use electricity to realize gates and represent binary data, any alternative technology permitting switching and conducting capabilities can be employed. Indeed, over the years, many hardware implementations of Boolean functions were created, including magnetic, optical, biological, hydraulic, pneumatic, quantum-based, and even domino-based mechanisms (many of these implementations were proposed as whimsical “can do” feats). Today, gates are typically implemented as transistors etched in silicon, packaged as chips. In Nand to Tetris we use the words chip and gate interchangeably, tending to use the latter for simple instances of the former. The availability of alternative switching technologies, on the one hand, and the observation that Boolean algebra can be used to abstract the behavior of logic gates, on the other, is extremely important. Basically, it implies that computer scientists don’t have to worry about physical artifacts like electricity, circuits, switches, relays, and power sources. Instead, computer scientists are content with the abstract notions of Boolean algebra and gate logic, trusting blissfully that someone else—physicists and electrical engineers—will figure out how to actually realize them in hardware. Hence, primitive gates like those shown in figure 1.4 can be viewed as black box devices that implement elementary logical operations in one way or another—we don’t care how. The use of Boolean algebra for analyzing the abstract behavior of logic gates was articulated in 1937 by Claude Shannon, leading to what is sometimes described as the most important M.Sc. thesis in computer science.

Figure 1.4 Standard gate diagrams of three elementary logic gates.

Primitive and Composite Gates Since all logic gates have the same input and output data types (0’s and 1’s), they can be combined, creating composite gates of arbitrary complexity. For example, suppose we are asked to implement the three-way Boolean function And (a, b, c), which returns 1 when every one of its inputs is 1, and 0 otherwise. Using Boolean algebra, we can begin by observing that or, using prefix notation, And (And (a, b), c). Next, we can use this result to construct the composite gate depicted in figure 1.5.

Figure 1.5 Composite implementation of a three-way And gate. The rectangular dashed outline defines the boundary of the gate interface.

We see that any given logic gate can be viewed from two different perspectives: internal and external. The right side of figure 1.5 gives the gate’s internal architecture, or implementation, whereas the left side shows the gate interface, namely, its input and output pins and the behavior that it exposes to the outside world. The internal view is relevant only to the gate builder, whereas the external view is the right level of detail for designers who wish to use the gate as an abstract, off-the-shelf component, without paying attention to its implementation. Let us consider another logic design example: Xor. By definition, Xor (a, b) is 1 exactly when either a is 1 and b is 0 or a is 0 and b is 1. Said otherwise, Xor , And (Not (a), b)). This definition is implemented in the logic design shown in figure 1.6.

Figure 1.6 Xor gate interface (left) and a possible implementation (right).

Note that the interface of any given gate is unique: there is only one way to specify it, and this is normally done using a truth table, a Boolean expression, or a verbal specification. This interface, however, can be realized in many different ways, and some will be more elegant and efficient than others. For example, the Xor implementation shown in figure 1.6 is one possibility; there are more efficient ways to realize Xor, using less logic gates and less inter-gate connections. Thus, from a functional standpoint, the fundamental requirement of logic design is that the gate implementation will realize its stated interface, one way or another. From an efficiency standpoint, the general rule is to try to use as few gates as possible, since fewer gates imply less cost, less energy, and faster computation. To sum up, the art of logic design can be described as follows: Given a gate abstraction (also referred to as specification, or interface), find an efficient way to implement it using other gates that were already implemented.

1.3 Hardware Construction We are now in a position to discuss how gates are actually built. Let us start with an intentionally naïve example. Suppose we open a chip fabrication shop in our home garage. Our first contract is to build a hundred Xor gates. Using the order’s down payment, we purchase a soldering gun, a roll of copper wire, and three bins labeled “And gates,” “Or gates,” and “Not gates,” each containing many identical copies of these elementary logic gates. Each of these gates is sealed in a plastic casing that exposes some

input and output pins, as well as a power supply port. Our goal is to realize the gate diagram shown in figure 1.6 using this hardware. We begin by taking two And gates, two Not gates, and one Or gate and mounting them on a board, according to the figure’s layout. Next, we connect the chips to one another by running wires among them and soldering the wire ends to the respective input/output pins. Now, if we follow the gate diagram carefully, we will end up having three exposed wire ends. We then solder a pin to each one of these wire ends, seal the entire device (except for the three pins) in a plastic casing, and label it “Xor.” We can repeat this assembly process many times over. At the end of the day, we can store all the chips that we’ve built in a new bin and label it “Xor gates.” If we wish to construct some other chips in the future, we’ll be able to use these Xor gates as black box building blocks, just as we used the And, Or, and Not gates before. As you have probably sensed, the garage approach to chip production leaves much to be desired. For starters, there is no guarantee that the given chip diagram is correct. Although we can prove correctness in simple cases like Xor, we cannot do so in many realistically complex chips. Thus, we must settle for empirical testing: build the chip, connect it to a power supply, activate and deactivate the input pins in various configurations, and hope that the chip’s input/output behavior delivers the desired specification. If the chip fails to do so, we will have to tinker with its physical structure— a rather messy affair. Further, even if we do come up with a correct and efficient design, replicating the chip assembly process many times over will be a time-consuming and error-prone affair. There must be a better way! 1.3.1 Hardware Description Language Today, hardware designers no longer build anything with their bare hands. Instead, they design the chip architecture using a formalism called Hardware Description Language, or HDL. The designer specifies the chip logic by writing an HDL program, which is then subjected to a rigorous battery of tests. The tests are carried out virtually, using computer simulation: A special software tool, called a hardware simulator, takes the HDL program as input and creates a software representation of the chip logic. Next, the designer can instruct the simulator to test the virtual chip on

various sets of inputs. The simulator computes the chip outputs, which are then compared to the desired outputs, as mandated by the client who ordered the chip built. In addition to testing the chip’s correctness, the hardware designer will typically be interested in a variety of parameters such as speed of computation, energy consumption, and the overall cost implied by the proposed chip implementation. All these parameters can be simulated and quantified by the hardware simulator, helping the designer optimize the design until the simulated chip delivers desired cost/performance levels. Thus, using HDL, one can completely plan, debug, and optimize an entire chip before a single penny is spent on physical production. When the performance of the simulated chip satisfies the client who ordered it, an optimized version of the HDL program can become the blueprint from which many copies of the physical chip can be stamped in silicon. This final step in the chip design process—from an optimized HDL program to mass production—is typically outsourced to companies that specialize in robotic chip fabrication, using one switching technology or another. Example: Building an Xor Gate: The remainder of this section gives a brief introduction to HDL, using an Xor gate example; a detailed HDL specification can be found in appendix 2. Let us focus on the bottom left of figure 1.7. An HDL definition of a chip consists of a header section and a parts section. The header section specifies the chip interface, listing the chip name and the names of its input and output pins. The PARTS section describes the chip-parts from which the chip architecture is made. Each chip-part is represented by a single statement that specifies the part name, followed by a parenthetical expression that specifies how it is connected to other parts in the design. Note that in order to write such statements, the HDL programmer must have access to the interfaces of all the underlying chip-parts: the names of their input and output pins, as well as their intended operation. For example, the programmer who wrote the HDL program listed in figure 1.7 must have known that the input and output pins of the Not gate are named in and out and that those of the And and Or gates are named a, b, and out. (The APIs of all the chips used in Nand to Tetris are listed in appendix 4).

Figure 1.7 Gate diagram and HDL implementation of the Boolean function Xor (And (a, Not (b)), And (Not (a), b)), used as an example. A test script and an output file generated by the test are also shown. Detailed descriptions of HDL and the testing language are given in appendices 2 and 3, respectively.

Inter-part connections are specified by creating and connecting internal pins, as needed. For example, consider the bottom of the gate diagram, where the output of a Not gate is piped into the input of a subsequent And gate. The HDL code describes this connection by the pair of statements Not(…, out=nota) and And(a=nota, …). The first statement creates an internal pin (outbound connection) named nota and pipes the value of the out pin into it. The second statement pipes the value of nota into the a input of an And gate. Two comments are in order here. First, internal pins are created “automatically” the first time they appear in an HDL program. Second, pins may have an unlimited fan-out. For example, in figure 1.7, each input is simultaneously fed into two gates. In gate diagrams, multiple connections are described by drawing them, creating forked patterns. In HDL programs, the existence of forks is inferred from the code. The HDL that we use in Nand to Tetris has a similar look and feel to industrial strength HDLs but is much simpler. Our HDL syntax is mostly self-explanatory and can be learned by seeing a few examples and consulting appendix 2, as needed.

Testing Rigorous quality assurance mandates that chips be tested in a specific, replicable, and well-documented fashion. With that in mind, hardware simulators are typically designed to run test scripts, written in a scripting language. The test script listed in figure 1.7 is written in the scripting language understood by the Nand to Tetris hardware simulator. Let us give a brief overview of this test script. The first two lines instruct the simulator to load the Xor.hdl program and get ready to print the values of selected variables. Next, the script lists a series of testing scenarios. In each scenario, the script instructs the simulator to bind the chip inputs to selected data values, compute the resulting output, and record the test results in a designated output file. In the case of simple gates like Xor, one can write an exhaustive test script that enumerates all the input values that the gate can possibly get. In this case, the resulting output file (right side of figure 1.7) provides a complete empirical test that the chip is well behaving. The luxury of such certitude is not feasible in more complex chips, as we will see later. Readers who plan to build the Hack computer will be pleased to know that all the chips that appear in the book are accompanied by skeletal HDL programs and supplied test scripts, available in the Nand to Tetris software suite. Unlike HDL, which must be learned in order to complete the chip specifications, there is no need to learn our testing language. At the same time, you have to be able to read and understand the supplied test scripts. The scripting language is described in appendix 3, which can be consulted on a need-to-know basis. 1.3.2 Hardware Simulation Writing and debugging HDL programs is similar to conventional software development. The main difference is that instead of writing code in a highlevel language, we write it in HDL, and instead of compiling and running the code, we use a hardware simulator to test it. The hardware simulator is a computer program that knows how to parse and interpret HDL code, turn it into an executable representation, and test it according to supplied test scripts. There exist many such commercial hardware simulators in the

market. The Nand to Tetris software suite includes a simple hardware simulator that provides all the necessary tools for building, testing, and integrating all the chips presented in the book, leading up to the construction of a general-purpose computer. Figure 1.8 illustrates a typical chip simulation session.

Figure 1.8 A screenshot of simulating an Xor chip in the supplied hardware simulator (other versions of this simulator may have a slightly different GUI). The simulator state is shown just after the test script has completed running. The pin values correspond to the last simulation step Not shown in this screenshot is a compare file that lists the expected output of the simulation specified by this particular test script. Like the test script, the compare file is typically supplied by the client who wants the chip built. In this particular example, the output file generated by the simulation (bottom right of the figure) is identical to the supplied compare file.

1.4 Specification We now turn to specify a set of logic gates that will be needed for building the chips of our computer system. These gates are ordinary, each designed to carry out a common Boolean operation. For each gate, we’ll focus on the gate interface (what the gate is supposed to do), delaying implementation details (how to build the gate’s functionality) to a later section.

1.4.1 Nand The starting point of our computer architecture is the Nand gate, from which all other gates and chips will be built. The Nand gate realizes the following Boolean function:

Or, using API style:

Throughout the book, chips are specified using the API style shown above. For each chip, the API specifies the chip name, the names of its input and output pins, the chip’s intended function or operation, and optional comments. 1.4.2 Basic Logic Gates The logic gates that we present here are typically referred to as basic, since they come into play in the construction of more complex chips. The Not, And, Or, and Xor gates implement classical logical operators, and the multiplexer and demultiplexer gates provide means for controlling flows of information. Not: Also known as inverter, this gate outputs the opposite value of its input’s value. Here is the API:

And: Returns 1 when both its inputs are 1, and 0 otherwise:

Or: Returns 1 when at least one of its inputs is 1, and 0 otherwise:

Xor: Also known as exclusive or, this gate returns 1 when exactly one of its inputs is 1, and 0 otherwise:

Multiplexer: A multiplexer is a three-input gate (see figure 1.9). Two input bits, named a and b, are interpreted as data bits, and a third input bit, named sel, is interpreted as a selection bit. The multiplexer uses sel to select and output the value of either a or b. Thus, a sensible name for this device could have been selector. The name multiplexer was adopted from communications systems, where extended versions of this device are used for serializing (multiplexing) several input signals over a single communications channel.

Figure 1.9 Multiplexer. The table at the top right is an abbreviated version of the truth table.

Demultiplexer: A demultiplexer performs the opposite function of a multiplexer: it takes a single input value and routes it to one of two possible outputs, according to a selector bit that selects the destination output. The other output is set to 0. Figure 1.10 gives the API.

Figure 1.10 Demultiplexer.

1.4.3 Multi-Bit Versions of Basic Gates Computer hardware is often designed to process multi-bit values—for example, computing a bitwise And function on two given 16-bit inputs. This section describes several 16-bit logic gates that will be needed for constructing our target computer platform. We note in passing that the logical architecture of these n-bit gates is the same, irrespective of n’s value (e.g., 16, 32, or 64 bits). HDL programs treat multi-bit values like single-bit values, except that the values can be indexed in order to access individual bits. For example, if in and out represent 16-bit values, then out sets the 3rd bit of out to the value of the 5th bit of in. The bits are indexed from right to left, the rightmost bit being the 0’th bit and the leftmost bit being the 15’th bit (in a 16-bit setting). Multi-bit Not: An n-bit Not gate applies the Boolean operation Not to every one of the bits in its n-bit input:

Multi-bit And: An n-bit And gate applies the Boolean operation And to every respective pair in its two n-bit inputs:

Multi-bit Or: An n-bit Or gate applies the Boolean operation Or to every respective pair in its two n-bit inputs:

Multi-bit multiplexer: An n-bit multiplexer operates exactly the same as a basic multiplexer, except that its inputs and output are n-bits wide:

1.4.4 Multi-Way Versions of Basic Gates Logic gates that operate on one or two inputs have natural generalization to multi-way variants that operate on more than two inputs. This section describes a set of multi-way gates that will be used subsequently in various chips in our computer architecture. Multi-way Or: An m-way Or gate outputs 1 when at least one of its m input bits is 1, and 0 otherwise. We will need an 8-way variant of this gate:

Multi-way/Multi-bit multiplexer: An m-way n-bit multiplexer selects one of its m n-bit inputs, and outputs it to its n-bit output. The selection is specified by a set of k selection bits, where Here is the API of a 4way multiplexer:

Our target computer platform requires two variants of this chip: a 4-way 16bit multiplexer and an 8-way 16-bit multiplexer:

Multi-way/Multi-bit demultiplexer: An m-way n-bit demultiplexer routes its single n-bit input to one of its m n-bit outputs. The other outputs are set to 0. The selection is specified by a set of k selection bits, where Here is the API of a 4-way demultiplexer:

Our target computer platform requires two variants of this chip: a 4-way 1bit demultiplexer and an 8-way 1-bit demultiplexer:

1.5 Implementation The previous section described the specifications, or interfaces, of a family of basic logic gates. Having described the what, we now turn to discuss the how. In particular, we’ll focus on two general approaches to implementing logic gates: behavioral simulation and hardware implementation. Both approaches play important roles in all our hardware construction projects. 1.5.1 Behavioral Simulation The chip descriptions presented thus far are strictly abstract. It would have been nice if we could experiment with these abstractions hands-on, before setting out to build them in HDL. How can we possibly do so? Well, if all we want to do is interact with the chips’ behavior, we don’t have to go through the trouble of building them in HDL. Instead, we can opt for a much simpler implementation, using conventional programming. For example, we can use some object-oriented language to create a set of classes, each implementing a generic chip. We can write class constructors

for creating chip instances and eval methods for evaluating their logic, and we can have the classes interact with each other so that high-level chips can be defined in terms of lower-level ones. We could then add a nice graphical user interface that enables putting different values in the chip inputs, evaluating their logic, and observing the chip outputs. This software-based technique, called behavioral simulation, makes a lot of sense. It enables experimenting with chip interfaces before starting the laborious process of building them in HDL. The Nand to Tetris hardware simulator provides exactly such a service. In addition to simulating the behavior of HDL programs, which is its main purpose, the simulator features built-in software implementations of all the chips built in the Nand to Tetris hardware projects. The built-in version of each chip is implemented as an executable software module, invoked by a skeletal HDL program that provides the chip interface. For example, here is the HDL program that implements the built-in version of the Xor chip:

Compare this to the HDL program listed in figure 1.7. First, note that regular chips and built-in chips have precisely the same interface. Thus, they provide exactly the same functionality. In the built-in implementation though, the PARTS section is replaced with the single statement BUILTIN Xor. This statement informs the simulator that the chip is implemented by Xor.class. This class file, like all the Java class files that implement built-in chips, is located in the folder nand2tetris/tools/builtIn. We note in passing that realizing logic gates using high-level programming is not difficult, and that’s another virtue of behavioral simulation: it’s inexpensive and quick. At some point, of course, hardware engineers must do the real thing, which is implementing the chips not as

software artifacts but rather as HDL programs that can be committed to silicon. That’s what we’ll do next. 1.5.2 Hardware Implementation This section gives guidelines on how to implement the fifteen logic gates described in this chapter. As a rule in this book, our implementation guidelines are intentionally brief. We give just enough insights to get started, leaving you the pleasure of discovering the rest of the gate implementations yourself. Nand: Since we decided to base our hardware on elementary Nand gates, we treat Nand as a primitive gate whose functionality is given externally. The supplied hardware simulator features a built-in implementation of Nand, and thus there is no need to implement it. Not: Can be implemented using a single Nand gate. Tip: Inspect the Nand truth table, and ask yourself how the Nand inputs can be arranged so that a single input signal, 0, will cause the Nand gate to output 1, and a single input signal, 1, will cause it to output 0. And: Can be implemented from the two previously discussed gates. Or / Xor: The Boolean function Or can be defined using the Boolean functions And and Not. The Boolean function Xor can be defined using And, Not, and Or. Multiplexer / Demultiplexer: Can be implemented using previously built gates. Multi-bit Not / And / Or gates: Assuming that you’ve already built the basic versions of these gates, the implementation of their n-ary versions is a matter of arranging arrays of n basic gates and having each gate operate separately on its single-bit inputs. The resulting HDL code will be somewhat boring and repetitive (using copy-paste), but it will carry its weight when these multi-bit gates are used in the construction of more complex chips, later in the book.

Multi-bit multiplexer: The implementation of an n-ary multiplexer is a matter of feeding the same selection bit to every one of n binary multiplexers. Again, a boring construction task resulting in a very useful chip. Multi-way gates: Implementation tip: Think forks. 1.5.3 Built-In Chips As we pointed out when we discussed behavioral simulation, our hardware simulator provides software-based, built-in implementations of most of the chips described in the book. In Nand to Tetris, the most celebrated built-in chip is of course Nand: whenever you use a Nand chip-part in an HDL program, the hardware simulator invokes the built-in tools/builtIn/Nand.hdl implementation. This convention is a special case of a more general chip invocation strategy: whenever the hardware simulator encounters a chippart, say, Xxx, in an HDL program, it looks up the file Xxx.hdl in the current folder; if the file is found, the simulator evaluates its underlying HDL code. If the file is not found, the simulator looks it up in the tools/builtIn folder. If the file is found there, the simulator executes the chip’s built-in implementation; otherwise, the simulator issues an error message and terminates the simulation. This convention comes in handy. For example, suppose you began implementing a Mux.hdl program, but, for some reason, you did not complete it. This could be an annoying setback, since, in theory, you cannot continue building chips that use Mux as a chip-part. Fortunately, and actually by design, this is where built-in chips come to the rescue. All you have to do is rename your partial implementation Mux1.hdl, for example. Each time the hardware simulator is called to simulate the functionality of a Mux chippart, it will fail to find a Mux.hdl file in the current folder. This will cause behavioral simulation to kick in, forcing the simulator to use the built-in Mux version instead. Exactly what we want! At a later stage you may want to go back to Mux1.hdl and resume working on its implementation. At this point you can restore its original file name, Mux.hdl, and continue from where you left off.

1.6 Project This section describes the tools and resources needed for completing project 1 and gives recommended implementation steps and tips. Objective: Implement all the logic gates presented in the chapter. The only building blocks that you can use are primitive Nand gates and the composite gates that you will gradually build on top of them. Resources: We assume that you’ve already downloaded the Nand to Tetris zip file, containing the book’s software suite, and that you’ve extracted it into a folder named nand2tetris on your computer. If that is the case, then the nand2tetris/tools folder on your computer contains the hardware simulator discussed in this chapter. This program, along with a plain text editor, are the only tools needed for completing project 1 as well as all the other hardware projects described in the book. The fifteen chips mentioned in this chapter, except for Nand, should be implemented in the HDL language described in appendix 2. For each chip Xxx, we provide a skeletal Xxx.hdl program (sometimes called a stub file) with a missing implementation part. In addition, for each chip we provide an Xxx.tst script that tells the hardware simulator how to test it, along with an Xxx.cmp compare file that lists the correct output that the supplied test is expected to generate. All these files are available in your nand2tetris/projects/01 folder. Your job is to complete and test all the Xxx.hdl files in this folder. These files can be written and edited using any plain text editor. Contract: When loaded into the hardware simulator, your chip design (modified .hdl program), tested on the supplied .tst file, should produce the outputs listed in the supplied .cmp file. If the actual outputs generated by the simulator disagree with the desired outputs, the simulator will stop the simulation and produce an error message. Steps: We recommend proceeding in the following order:

0. The hardware simulator needed for this project is available in nand2tetris/tools. 1. Consult appendix 2 (HDL), as needed. 2. Consult the Hardware Simulator Tutorial (available at www.nand2tetris .org), as needed. 3. Build and simulate all the chips listed in nand2tetris/projects/01. General Implementation Tips (We use the terms gate and chip interchangeably.) Each gate can be implemented in more than one way. The simpler the implementation, the better. As a general rule, strive to use as few chipparts as possible. Although each chip can be implemented directly from Nand gates only, we recommend always using composite gates that were already implemented. See the previous tip. There is no need to build “helper chips” of your own design. Your HDL programs should use only the chips mentioned in this chapter. Implement the chips in the order in which they appear in the chapter. If, for some reason, you don’t complete the HDL implementation of some chip, you can still use it as a chip-part in other HDL programs. Simply rename the chip file, or remove it from the folder, causing the simulator to use its built-in version instead. A web-based version of project 1 is available at www.nand2tetris.org.

1.7 Perspective This chapter specified a set of basic logic gates that are widely used in computer architectures. In chapters 2 and 3 we will use these gates for building our processing and storage chips, respectively. These chips, in turn, will be later used for constructing the central processing unit and the memory devices of our computer.

Although we have chosen to use Nand as our basic building block, other logic gates can be used as possible points of departure. For example, you can build a complete computer platform using Nor gates only or, alternatively, a combination of And, Or, and Not gates. These constructive approaches to logic design are theoretically equivalent, just like the same geometry can be founded on alternative sets of agreed-upon axioms. In principle, if electrical engineers or physicists can come up with efficient and low-cost implementations of logic gates using any technology that they see fit, we will happily use them as primitive building blocks. The reality, though, is that most computers are built from either Nand or Nor gates. Throughout the chapter, we paid no attention to efficiency and cost considerations, such as energy consumption or the number of wire crossovers implied by our HDL programs. Such considerations are critically important in practice, and a great deal of computer science and technology expertise focuses on optimizing them. Another issue we did not address is physical aspects, for example, how primitive logic gates can be built from transistors embedded in silicon or from other switching technologies. There are of course several such implementation options, each having its own characteristics (speed, energy consumption, production cost, and so on). Any nontrivial coverage of these issues requires venturing into areas outside computer science, like electrical engineering and solid-state physics. The next chapter describes how bits can be used to represent binary numbers and how logic gates can be used to realize arithmetic operations. These capabilities will be based on the elementary logic gates built in this chapter.

2 Boolean Arithmetic

Counting is the religion of this generation, its hope and salvation. —Gertrude Stein (1874–1946)

In this chapter we build a family of chips designed to represent numbers and perform arithmetic operations. Our starting point is the set of logic gates built in chapter 1, and our ending point is a fully functional Arithmetic Logic Unit. The ALU will later become the computational centerpiece of the Central Processing Unit (CPU)—the chip that executes all the instructions handled by the computer. Hence, building the ALU is an important milestone in our Nand to Tetris journey. As usual, we approach this task gradually, starting with a background section that describes how binary codes and Boolean arithmetic can be used, respectively, to represent and add signed integers. The Specification section presents a succession of adder chips designed to add two bits, three bits, and pairs of n-bit binary numbers. This sets the stage for the ALU specification, which is based on a surprisingly simple logic design. The Implementation and Project sections provide tips and guidelines on how to build the adder chips and the ALU using HDL and the supplied hardware simulator.

2.1 Arithmetic Operations General-purpose computer systems are required to perform at least the following arithmetic operations on signed integers:

addition sign conversion subtraction comparison multiplication division We’ll start by developing gate logic that carries out addition and sign conversion. Later, we will show how the other arithmetic operations can be implemented from these two building blocks. In mathematics as well as in computer science, addition is a simple operation that runs deep. Remarkably, all the functions performed by digital computers—not only arithmetic operations—can be reduced to adding binary numbers. Therefore, constructive understanding of binary addition holds the key to understanding many fundamental operations performed by the computer’s hardware.

2.2 Binary Numbers When we are told that a certain code, say, 6083, represents a number using the decimal system, then, by convention, we take this number to be:

Each digit in the decimal code contributes a value that depends on the base 10 and on the digit’s position in the code. Suppose now that we are told that the code 10011 represents a number using base 2, or binary representation. To compute the value of this number, we follow exactly the same procedure, using base 2 instead of base 10:

Inside computers, everything is represented using binary codes. For example, when we press the keyboard keys labeled 1, 9, and Enter in response to “Give an example of a prime number,” what ends up stored in the computer’s memory is the binary code 10011. When we ask the computer to display this value on the screen, the following process ensues. First, the computer’s operating system calculates the decimal value that 10011 represents, which happens to be 19. After converting this integer value to the two characters 1 and 9, the OS looks up the current font and gets the two bitmap images used for rendering these characters on the screen. The OS then causes the screen driver to turn on and off the relevant pixels, and, don’t hold your breath—the whole thing lasts a tiny fraction of a second— we finally see the image 19 appear on the screen. In chapter 12 we’ll develop an operating system that carries out such rendering operations, among many other low-level services. For now, suffice it to observe that the decimal representation of numbers is a human indulgence explained by the obscure fact that, at some point in ancient history, humans decided to represent quantities using their ten fingers, and the habit stuck. From a mathematical perspective, the number ten is utterly uninteresting, and, as far as computers go, is a complete nuisance. Computers handle everything in binary and care naught about decimal. Yet since humans insist on dealing with numbers using decimal codes, computers have to work hard behind the scenes to carry out binary-todecimal and decimal-to-binary conversions whenever humans want to see, or supply, numeric information. At all other times, com

The Elements Of Computing Systems : Free Download, Borrow, and Streaming : Internet Archive

Can You Chip In?

The Internet Archive is growing rapidly, and we need your help. As an independent nonprofit, we build and maintain all our own systems, but we don’t charge for access, sell user information, or run ads. Instead, we’re powered by donations averaging $30. Unfortunately, fewer than 1 in 1000 of our users donate.

Access to knowledge is more crucial than ever—so if you find all these bits and bytes useful, please chip in.

The Elements Of Computing Systems: Building A Modern Computer From First Principles [PDF] [55requfvttt0]

E-Book Overview

E-Book Content

The Elements of Computing Systems

Noam Nisan and Shimon Schocken

The Elements of Computing Systems Building a Modern Computer from First Principles

The MIT Press Cambridge, Massachusetts London, England

6 2005 Massachusetts Institute of Technology All rights reserved. No part of this book may be reproduced in any form by any electronic or mechanical means (including photocopying, recording, or information storage and retrieval) without permission in writing from the publisher. This book was set in Times New Roman on 3B2 by Asco Typesetters, Hong Kong. Printed and bound in the United States of America. Library of Congress Cataloging-in-Publication Data Nisan, Noam. The elements of computing systems: building a modern computer from first principles / Noam Nisan and Shimon Schocken. p. cm. Includes bibliographical references and index. ISBN 0-262-14087-X (alk. paper) 1. Electronic digital computers. I. Schocken, Shimon. II. Title. TK7888.3.N57 2005 004.16—dc22 2005042807 10

9 8 7 6 5

4 3 2 1

Note on Software The book’s Web site (http://www.idc.ac.il/tecs) provides the tools and materials necessary to build all the hardware and software systems described in the book. These include a hardware simulator, a CPU emulator, a VM emulator, and executable versions of the assembler, virtual machine, compiler, and operating system described in the book. The Web site also includes all the project materials—about 200 test programs and test scripts, allowing incremental development and unit-testing of each one of the 12 projects. All the supplied software tools and project materials can be used as is on any computer equipped with either Windows or Linux.

To our parents, For teaching us that less is more.

Contents

Preface

ix 1

Introduction: Hello, World Below 7

1

Boolean Logic

2

Boolean Arithmetic

3

Sequential Logic

4

Machine Language

5

Computer Architecture

6

Assembler

7

Virtual Machine I: Stack Arithmetic

8

Virtual Machine II: Program Control

9

High-Level Language

10

Compiler I: Syntax Analysis

11

Compiler II: Code Generation

12

Operating System

13

Postscript: More Fun to Go

29 41 57 79

103

173 199 223

247 277

121 153

viii

Contents

Appendix A: Hardware Description Language (HDL) Appendix B: Test Scripting Language Index

315

297

281

Preface

What I hear, I forget; What I see, I remember; What I do, I understand. —Confucius, 551–479 BC

Once upon a time, every computer specialist had a gestalt understanding of how computers worked. The overall interactions among hardware, software, compilers, and the operating system were simple and transparent enough to produce a coherent picture of the computer’s operations. As modern computer technologies have become increasingly more complex, this clarity is all but lost: the most fundamental ideas and techniques in computer science—the very essence of the field—are now hidden under many layers of obscure interfaces and proprietary implementations. An inevitable consequence of this complexity has been specialization, leading to computer science curricula of many courses, each covering a single aspect of the field. We wrote this book because we felt that many computer science students are missing the forest for the trees. The typical student is marshaled through a series of courses in programming, theory, and engineering, without pausing to appreciate the beauty of the picture at large. And the picture at large is such that hardware and software systems are tightly interrelated through a hidden web of abstractions, interfaces, and contract-based implementations. Failure to see this intricate enterprise in the flesh leaves many students and professionals with an uneasy feeling that, well, they don’t fully understand what’s going on inside computers. We believe that the best way to understand how computers work is to build one from scratch. With that in mind, we came up with the following concept. Let’s specify a simple but sufficiently powerful computer system, and have the students build its hardware platform and software hierarchy from the ground up, starting with nothing more than elementary logic gates. And while we are at it, let’s do it right. We say this because building a general-purpose computer from first principles is a huge undertaking. Therefore, we identified a unique educational opportunity not only to

x

Preface

build the thing, but also to illustrate, in a hands-on fashion, how to effectively plan and manage large-scale hardware and software development projects. In addition, we sought to demonstrate the ability to construct, through recursive ascent and human reasoning, fantastically complex and useful systems from nothing more than a few primitive building blocks.

Scope The book exposes students to a significant body of computer science knowledge, gained through a series of hardware and software construction tasks. These tasks demonstrate how theoretical and applied techniques taught in other computer science courses are used in practice. In particular, the following topics are illustrated in a hands-on fashion: m

Hardware: Logic gates, Boolean arithmetic, multiplexors, flip-flops, registers, RAM units, counters, Hardware Description Language (HDL), chip simulation and testing. m Architecture: ALU/CPU design and implementation, machine code, assembly language programming, addressing modes, memory-mapped input/output (I/O). m Operating systems: Memory management, math library, basic I/O drivers, screen management, file I/O, high-level language support. m Programming languages: Object-based design and programming, abstract data types, scoping rules, syntax and semantics, references. m Compilers: Lexical analysis, top-down parsing, symbol tables, virtual stackbased machine, code generation, implementation of arrays and objects. m Data structures and algorithms: Stacks, hash tables, lists, recursion, arithmetic algorithms, geometric algorithms, running time considerations. m Software engineering: Modular design, the interface/implementation paradigm, API design and documentation, proactive test planning, programming at the large, quality assurance. All these topics are presented with a very clear purpose: building a modern computer from the ground up. In fact, this has been our topic selection rule: The book focuses on the minimal set of topics necessary for building a fully functioning computer system. As it turns out, this set includes many fundamental ideas in applied computer science.

xi

Preface

Courses The book is intended for students of computer science and other engineering disciplines in colleges and universities, at both the undergraduate and graduate levels. A course based on this book is ‘‘perpendicular’’ to the normal computer science curriculum and can be taken at almost any point during the program. Two natural slots are ‘‘CS-2’’—immediately after learning programming, and ‘‘CS-199’’—a capstone course coming at the end of the program. The former course can provide a systems-oriented introduction to computer science, and the latter an integrative, project-oriented systems building course. Possible names for such courses may be Constructive Introduction to Computer Science, Elements of Computing Systems, Digital Systems Construction, Computer Construction Workshop, Let’s Build a Computer, and the like. The book can support both one- and two-semester courses, depending on topic selection and pace of work. The book is completely self-contained, requiring only programming (in any language) as a prerequisite. Thus, it lends itself not only to computer science majors, but also to computer-savvy students seeking to gain a hands-on view of hardware architectures, operating systems, and modern software engineering in the framework of one course. The book and the accompanying Web site can also be used as a selfstudy learning unit, suitable to students from any technical or scientific discipline following a programming course.

Structure The introduction chapter presents our approach and previews the main hardware and software abstractions discussed in the book. This sets the stage for chapters 1– 12, each dedicated to a key hardware or software abstraction, a proposed implementation, and an actual project that builds and tests it. The first five chapters focus on constructing the hardware platform of a simple modern computer. The remaining seven chapters describe the design and implementation of a typical multi-tier software hierarchy, culminating in the construction of an object-based programming language and a simple operating system. The complete game plan is depicted in figure P.1. The book is based on an abstraction-implementation paradigm. Each chapter starts with a Background section, describing relevant concepts and a generic hardware or software system. The next section is always Specification, which provides a clear

xii

Preface

High-Level Language / Applications (∞)

c9

Operating System

c12

Compiler

c10

c11

Virtual Machine

c7

c8

Assembler

c6

Machine Language

c4

Computer Architecture

c5

Typical software hierarchy

c2

ALU

Memory Elements c3

Boolean Arithmetic c1

Figure P.1

Sequential Logic

Typical hardware platform

Boolean Logic

Book and proposed course map, with chapter numbers in circles.

statement of the system’s abstraction—namely, the various services that it is expected to deliver. Having presented the what, each chapter proceeds to discuss how the abstraction can be implemented, leading to a (proposed) Implementation section. The next section is always Perspective, in which we highlight noteworthy issues left out from the chapter. Each chapter ends with a Project section that provides step-bystep building instructions, testing materials, and software tools for actually building and unit-testing the system described in the chapter.

Projects The computer system described in the book is for real—it can actually be built, and it works! A reader who takes the time and effort to gradually build this computer will gain a level of intimate understanding unmatched by mere reading. Hence, the book is geared toward active readers who are willing to roll up their sleeves and build a computer from the ground up. Each chapter includes a complete description of a stand-alone hardware or software development project. The four projects that construct the computer platform are built using a simple Hardware Description Language (HDL) and simulated on a hardware simulator supplied with the book. Five of the subsequent software projects

xiii

Preface

(assembler, virtual machine I and II, and compiler I and II) can be written in any modern programming language. The remaining three projects (low-level programming, high-level programming, and the operating system) are written in the assembly language and high-level language implemented in previous projects. Project Tips There are twelve projects altogether. On average, each project entails a weekly homework load in a typical, rigorous university-level course. The projects are completely self-contained and can be done (or skipped) in any desired order. Of course the ‘‘full experience’’ package requires doing all the projects in their order of appearance, but this is just one option. When we teach courses based on this book, we normally make two significant concessions. First, except for obvious cases, we pay no attention to optimization, leaving this very important subject to other, more specific courses. Second, when developing the translators suite (assembler, VM implementation, and compiler), we supply error-free test files (source programs), allowing the students to assume that the inputs of these translators are error-free. This eliminates the need to write code for handling errors and exceptions, making the software projects significantly more manageable. Dealing with incorrect input is of course critically important, but once again we assume that students can hone this skill elsewhere, for example, in dedicated programming and software design courses.

Software The book’s Web site (www.idc.ac.il/tecs) provides the tools and materials necessary to build all the hardware and software systems described in the book. These include a hardware simulator, a CPU emulator, a VM emulator, and executable versions of the assembler, virtual machine, compiler, and operating system described in the book. The Web site also includes all the project materials—about two hundred test programs and test scripts, allowing incremental development and unit-testing of each one of the twelve projects. All the supplied software tools and project materials can be used as is on any computer equipped with either Windows or Linux.

Acknowledgments All the software that accompanies the book was developed by our students at the Efi Arazi School of Computer Science of the Interdisciplinary Center Herzliya, a new

xiv

Preface

Israeli university. The chief software architect was Yaron Ukrainitz, and the developers included Iftach Amit, Nir Rozen, Assaf Gad, and Hadar Rosen-Sior. Working with these student-developers has been a great pleasure, and we feel proud and fortunate to have had the opportunity to play a role in their education. We also wish to thank our teaching assistants, Muawyah Akash, David Rabinowitz, Ran Navok, and Yaron Ukrainitz, who helped us run early versions of the course that led to this book. Thanks also to Jonathan Gross and Oren Baranes, who worked on related projects under the excellent supervision of Dr. Danny Seidner, to Uri Zeira and Oren Cohen, for designing an integrated development environment for the Jack language, to Tal Achituv, for useful advice on open source issues, and to Aryeh Schnall, for careful reading and meticulous editing suggestions. Writing the book without taking any reduction in our regular professional duties was not simple, and so we wish to thank esti romem, administrative director of the EFI Arazi School of Computer Science, for holding the fort in difficult times. Finally, we are indebted to the many students who endured early versions of this book and helped polish it through numerous bug reports. In the process, we hope, they have learned first-hand that insight of James Joyce, that mistakes are the portals of discovery. Noam Nisan Shimon Schocken

The Elements of Computing Systems

Human Thought

Application or System Design Chapters 9,12

abstract interface

High-Level Language & Operating System

Compiler Chapters 10–11 abstract interface

Virtual Machine

Software hierarchy

VM Translator Chapters 7–8

abstract interface

Assembly Language

Assembler Chapter 6 abstract interface

Machine Language

Computer Architecture Chapters 4–5

Hardware hierarchy

abstract interface

Hardware Platform

Gate Logic Chapters 1–3

abstract interface

Chips and Logic Gates

Electrical Engineering

Physics

Figure I.1 The major abstractions underlying the design of a typical computing system. The implementation of each level is accomplished using abstract services and building blocks from the level below.

Introduction: Hello, World Below

The true voyage of discovery consists not of going to new places, but of having a new pair of eyes. —Marcel Proust (1871–1922)

This book is a voyage of discovery. You are about to learn three things: how computers work, how to break complex problems into manageable modules, and how to develop large-scale hardware and software systems. This will be a hands-on process as you create a complete and working computer system from the ground up. The lessons you will learn, which are far more important and general than the computer itself, will be gained as side effects of this activity. According to the psychologist Carl Rogers, ‘‘the only kind of learning which significantly influences behavior is selfdiscovered or self-appropriated—truth that has been assimilated in experience.’’ This chapter sketches some of the discoveries, truths, and experiences that lie ahead.

The World Above If you have taken any programming course, you’ve probably encountered something like the program below early in your education. This particular program is written in Jack—a simple high-level language that has a conventional object-based syntax. // First example in Programming 101: class Main { function void main() { do Output.printString(“Hello World”); do Output.println(); // New line. return; } }

2

Introduction

Trivial programs like Hello World are deceptively simple. Did you ever think about what it takes to actually run such a program on a computer? Let’s look under the hood. For starters, note that the program is nothing more than a bunch of dead characters stored in a text file. Thus, the first thing we must do is parse this text, uncover its semantics, and reexpress it in some low-level language understood by our computer. The result of this elaborate translation process, known as compilation, will be yet another text file, containing machine-level code. Of course machine language is also an abstraction—an agreed upon set of binary codes. In order to make this abstract formalism concrete, it must be realized by some hardware architecture. And this architecture, in turn, is implemented by a certain chip set—registers, memory units, ALU, and so on. Now, every one of these hardware devices is constructed from an integrated package of elementary logic gates. And these gates, in turn, can be built from primitive gates like Nand and Nor. Of course every one of these gates consists of several switching devices, typically implemented by transistors. And each transistor is made of— Well, we won’t go further than that, because that’s where computer science ends and physics starts. You may be thinking: ‘‘On my computer, compiling and running a program is much easier—all I have to do is click some icons or write some commands!’’ Indeed, a modern computer system is like a huge iceberg, and most people get to see only the top. Their knowledge of computing systems is sketchy and superficial. If, however, you wish to explore beneath the surface, then lucky you! There’s a fascinating world down there, made of some of the most beautiful stuff in computer science. An intimate understanding of this underworld is one of the things that separate naı¨ve programmers from sophisticated developers—people who can create not only application programs, but also complex hardware and software technologies. And the best way to understand how these technologies work—and we mean understand them in the marrow of your bones—is to build a complete computer system from scratch.

Abstractions You may wonder how it is humanly possible to construct a complete computer system from the ground up, starting with nothing more than elementary logic gates. This must be an enormously complex enterprise! We deal with this complexity by breaking the project into modules, and treating each module separately, in a standalone chapter. You might then wonder, how is it possible to describe and construct these modules in isolation? Obviously they are all interrelated! As we will show

3

Introduction

throughout the book, a good modular design implies just that: You can work on the individual modules independently, while completely ignoring the rest of the system. In fact, you can even build these modules in any desired order! It turns out that this strategy works well thanks to a special gift unique to humans: our ability to create and use abstractions. The notion of abstraction, central to many arts and sciences, is normally taken to be a mental expression that seeks to separate in thought, and capture in some concise manner, the essence of some entity. In computer science, we take the notion of abstraction very concretely, defining it to be a statement of ‘‘what the entity does’’ and ignoring the details of ‘‘how it does it.’’ This functional description must capture all that needs to be known in order to use the entity’s services, and nothing more. All the work, cleverness, information, and drama that went into the entity’s implementation are concealed from the client who is supposed to use it, since they are simply irrelevant. The articulation, use, and implementation of such abstractions are the bread and butter of our professional practice: Every hardware and software developer is routinely defining abstractions (also called ‘‘interfaces’’) and then implementing them, or asking other people to implement them. The abstractions are often built layer upon layer, resulting in higher and higher levels of capabilities. Designing good abstractions is a practical art, and one that is best acquired by seeing many examples. Therefore, this book is based on an abstractionimplementation paradigm. Each book chapter presents a key hardware or software abstraction, and a project designed to actually implement it. Thanks to the modular nature of these abstractions, each chapter also entails a stand-alone intellectual unit, inviting the reader to focus on two things only: understanding the given abstraction (a rich world of its own), and then implementing it using abstract services and building blocks from the level below. As you push ahead in this journey, it will be rather thrilling to look back and appreciate the computer that is gradually taking shape in the wake of your efforts.

The World Below The multi-tier collection of abstractions underlying the design of a computing system can be described top-down, showing how high-level abstractions can be reduced into, or expressed by, simpler ones. This structure can also be described bottom-up, focusing on how lower-level abstractions can be used to construct more complex ones. This book takes the latter approach: We begin with the most basic elements—

4

Introduction

primitive logic gates—and work our way upward, culminating in the construction of a general-purpose computer system. And if building such a computer is like climbing Mount Everest, then planting a flag on the mountaintop is like having the computer run a program written in some high-level language. Since we are going to ascend this mountain from the ground up, let us survey the book plan in the opposite direction—from the top down—starting in the familiar territory of high-level programming. Our tour consists of three main legs. We start at the top, where people write and run high-level programs (chapters 9 and 12). We then survey the road down to hardware land, tracking the fascinating twists and curves of translating high-level programs into machine language (chapters 6, 7, 8, 10, 11). Finally, we reach the low grounds of our journey, describing how a typical hardware platform is actually constructed (chapters 1–5). High-Level Language Land The topmost abstraction in our journey is the art of programming, where entrepreneurs and programmers dream up applications and write software that implements them. In doing so, they blissfully take for granted the two key tools of their trade: the high-level language in which they work, and the rich library of services that supports it. For example, consider the statement do Output.printString(‘‘Hello World’’). This code invokes an abstract service for printing strings—a service that must be implemented somewhere. Indeed, a bit of drilling reveals that this service is usually supplied jointly by the host operating system and the standard language library. What then is a standard language library? And how does an operating system (OS) work? These questions are taken up in chapter 12. We start by presenting key algorithms relevant to OS services, and then use them to implement various mathematical functions, string operations, memory allocation tasks, and input/output (I/O) routines. The result is a simple operating system, written in the Jack programming language. Jack is a simple object-based language, designed for a single purpose: to illustrate the key software engineering principles underlying the design and implementation of modern programming languages like Java and C#. Jack is presented in chapter 9, which also illustrates how to build Jack-based applications, for example, computer games. If you have any programming experience with a modern object-oriented language, you can start writing Jack programs right away and watch them execute on the computer platform developed in previous chapters. However, the goal of chapter

5

Introduction

9 is not to turn you into a Jack programmer, but rather to prepare you to develop the compiler and operating system described in subsequent chapters. The Road Down to Hardware Land Before any program can actually run and do something for real, it must be translated into the machine language of some target computer platform. This compilation process is sufficiently complex to be broken into several layers of abstraction, and these usually involve three translators: a compiler, a virtual machine implementation, and an assembler. We devote five book chapters to this trio, as follows. The translation task of the compiler is performed in two conceptual stages: syntax analysis and code generation. First, the source text is analyzed and grouped into meaningful language constructs that can be kept in a data structure called a ‘‘parse tree.’’ These parsing tasks, collectively known as syntax analysis, are described in chapter 10. This sets the stage for chapter 11, which shows how the parse tree can be recursively processed to yield a program written in an intermediate language. As with Java and C#, the intermediate code generated by the Jack compiler describes a sequence of generic steps operating on a stack-based virtual machine (VM). This classical model, as well as a VM implementation that realizes it on an actual computer, are elaborated in chapters 7–8. Since the output of our VM implementation is a large assembly program, we have to translate it further into binary code. Writing an assembler is a relatively simple task, taken up in chapter 6. Hardware Land We have reached the most profound step in our journey—the descent from machine language to the machine itself—the point where software finally meets hardware. This is also the point where Hack enters the picture. Hack is a general-purpose computer system, designed to strike a balance between simplicity and power. On the one hand, the Hack architecture can be built in just a few hours of work, using the guidelines and chip set presented in chapters 1–3. At the same time, Hack is sufficiently general to illustrate the key operating principles and hardware elements underlying the design of any digital computer. The machine language of the Hack platform is specified in chapter 4, and the computer design itself is discussed and specified in chapter 5. Readers can build this computer as well as all the chips and gates mentioned in the book on their home computers, using the software-based hardware simulator supplied with the book and the Hardware Description Language (HDL) documented in appendix A. All the

6

Introduction

developed hardware modules can be tested using supplied test scripts, written in a scripting language documented in appendix B. The computer that emerges from this construction is based on typical components like CPU, RAM, ROM, and simulated screen and keyboard. The computer’s registers and memory systems are built in chapter 3, following a brief discussion of sequential logic. The computer’s combinational logic, culminating in the Arithmetic Logic Unit (ALU) chip, is built in chapter 2, following a brief discussion of Boolean arithmetic. All the chips presented in these chapters are based on a suite of elementary logic gates, presented and built in chapter 1. Of course the layers of abstraction don’t stop here. Elementary logic gates are built from transistors, using technologies based on solid-state physics and ultimately quantum mechanics. Indeed, this is where the abstractions of the natural world, as studied and formulated by physicists, become the building blocks of the abstractions of the synthetic worlds built and studied by computer scientists. This marks the end of our grand tour preview—the descent from the high-level peaks of object-based software, all the way down to the bricks and mortar of the hardware platform. This typical modular rendition of a multi-tier system represents not only a powerful engineering paradigm, but also a central dogma in human reasoning, going back at least 2,500 years: We deliberate not about ends, but about means. For a doctor does not deliberate whether he shall heal, nor an orator whether he shall persuade . . . They assume the end and consider how and by what means it is attained, and if it seems easily and best produced thereby; while if it is achieved by other means, they consider how it will be achieved and by what means this will be achieved, until they come to the first cause . . . and what is last in the order of analysis seems to be first in the order of becoming. (Aristotles, Nicomachean Ethics, Book III, 3, 1112b)

So here’s the plan, in the order of becoming. Starting with the construction of elementary logic gates (chapter 1), we go bottom-up to combinational and sequential chips (chapters 2–3), through the design of a typical computer architecture (chapters 4–5) and a typical software hierarchy (chapters 6–8), all the way to implementing a compiler (chapters 10–11) for a modern object-based language (chapter 9), ending with the design and implementation of a simple operating system (chapter 12). We hope that the reader has gained a general idea of what lies ahead and is eager to push forward on this grand tour of discovery. So, assuming that you are ready and set, let the countdown start: 1, 0, Go!

1

Boolean Logic

Such simple things, And we make of them something so complex it defeats us, Almost. —John Ashbery (b. 1927), American poet

Every digital device—be it a personal computer, a cellular telephone, or a network router—is based on a set of chips designed to store and process information. Although these chips come in different shapes and forms, they are all made from the same building blocks: Elementary logic gates. The gates can be physically implemented in many different materials and fabrication technologies, but their logical behavior is consistent across all computers. In this chapter we start out with one primitive logic gate—Nand—and build all the other logic gates from it. The result is a rather standard set of gates, which will be later used to construct our computer’s processing and storage chips. This will be done in chapters 2 and 3, respectively. All the hardware chapters in the book, beginning with this one, have the same structure. Each chapter focuses on a well-defined task, designed to construct or integrate a certain family of chips. The prerequisite knowledge needed to approach this task is provided in a brief Background section. The next section provides a complete Specification of the chips’ abstractions, namely, the various services that they should deliver, one way or another. Having presented the what, a subsequent Implementation section proposes guidelines and hints about how the chips can be actually implemented. A Perspective section rounds up the chapter with concluding comments about important topics that were left out from the discussion. Each chapter ends with a technical Project section. This section gives step-by-step instructions for actually building the chips on a personal computer, using the hardware simulator supplied with the book. This being the first hardware chapter in the book, the Background section is somewhat lengthy, featuring a special section on hardware description and simulation tools.

8

1.1

Chapter 1

Background This chapter focuses on the construction of a family of simple chips called Boolean gates. Since Boolean gates are physical implementations of Boolean functions, we start with a brief treatment of Boolean algebra. We then show how Boolean gates implementing simple Boolean functions can be interconnected to deliver the functionality of more complex chips. We conclude the background section with a description of how hardware design is actually done in practice, using software simulation tools. 1.1.1

Boolean Algebra

Boolean algebra deals with Boolean (also called binary) values that are typically labeled true/false, 1/0, yes/no, on/off, and so forth. We will use 1 and 0. A Boolean function is a function that operates on binary inputs and returns binary outputs. Since computer hardware is based on the representation and manipulation of binary values, Boolean functions play a central role in the specification, construction, and optimization of hardware architectures. Hence, the ability to formulate and analyze Boolean functions is the first step toward constructing computer architectures. Truth Table Representation The simplest way to specify a Boolean function is to enumerate all the possible values of the function’s input variables, along with the function’s output for each set of inputs. This is called the truth table representation of the function, illustrated in figure 1.1. The first three columns of figure 1.1 enumerate all the possible binary values of the function’s variables. For each one of the 2 n possible tuples v1 . . . vn (here n ¼ 3), the last column gives the value of f ðv1 . . . vn Þ. Boolean Expressions In addition to the truth table specification, a Boolean function can also be specified using Boolean operations over its input variables. The basic Boolean operators that are typically used are ‘‘And’’ (x And y is 1 exactly when both x and y are 1) ‘‘Or’’ (x Or y is 1 exactly when either x or y or both are 1), and ‘‘Not’’ (Not x is 1 exactly when x is 0). We will use a common arithmetic-like notation for these operations: x y (or xy) means x And y, x þ y means x Or y, and x means Not x. To illustrate, the function defined in figure 1.1 is equivalently given by the Boolean expression f ðx; y; zÞ ¼ ðx þ yÞ z. For example, let us evaluate this expression on the

9

Boolean Logic

x

y

z

f ðx; y; zÞ

0 0 0 0 1 1 1 1

0 0 1 1 0 0 1 1

0 1 0 1 0 1 0 1

0 0 1 0 1 0 1 0

Figure 1.1

Truth table representation of a Boolean function (example).

inputs x ¼ 0, y ¼ 1, z ¼ 0 (third row in the table). Since y is 1, it follows that x þ y ¼ 1 and thus 1 0 ¼ 1 1 ¼ 1. The complete verification of the equivalence between the expression and the truth table is achieved by evaluating the expression on each of the eight possible input combinations, verifying that it yields the same value listed in the table’s right column. Canonical Representation As it turns out, every Boolean function can be expressed using at least one Boolean expression called the canonical representation. Starting with the function’s truth table, we focus on all the rows in which the function has value 1. For each such row, we construct a term created by And-ing together literals (variables or their negations) that fix the values of all the row’s inputs. For example, let us focus on the third row in figure 1.1, where the function’s value is 1. Since the variable values in this row are x ¼ 0, y ¼ 1, z ¼ 0, we construct the term xyz. Following the same procedure, we construct the terms xyz and xyz for rows 5 and 7. Now, if we Or-together all these terms (for all the rows where the function has value 1), we get a Boolean expression that is equivalent to the given truth table. Thus the canonical representation of the Boolean function shown in figure 1.1 is f ðx; y; zÞ ¼ xyz þ xyz þ xyz. This construction leads to an important conclusion: Every Boolean function, no matter how complex, can be expressed using three Boolean operators only: And, Or, and Not. Two-Input Boolean Functions An inspection of figure 1.1 reveals that the number of n Boolean functions that can be defined over n binary variables is 2 2 . For example, the sixteen Boolean functions spanned by two variables are listed in figure 1.2. These functions were constructed systematically, by enumerating all the possible 4-wise combinations of binary values in the four right columns. Each function has a conventional

10

Chapter 1

Function Constant 0 And x And Not y x Not x And y y Xor Or Nor Equivalence Not y If y then x Not x If x then y Nand Constant 1 Figure 1.2

x

1

1

y

1

1

0 x y x y x x y y x yþx y xþ y xþ y x yþx y y xþ y x xþ y x y 1

0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1

0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1

0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1

0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1

All the Boolean functions of two variables.

name that seeks to describe its underlying operation. Here are some examples: The name of the Nor function is shorthand for Not-Or: Take the Or of x and y, then negate the result. The Xor function—shorthand for ‘‘exclusive or’’—returns 1 when its two variables have opposing truth-values and 0 otherwise. Conversely, the Equivalence function returns 1 when the two variables have identical truth-values. The If-x-then-y function (also known as x ! y, or ‘‘x Implies y’’) returns 1 when x is 0 or when both x and y are 1. The other functions are self-explanatory. The Nand function (as well as the Nor function) has an interesting theoretical property: Each one of the operations And, Or, and Not can be constructed from it, and it alone (e.g., x Or y ¼ ðx Nand xÞ Nand ðy Nand yÞ. And since every Boolean function can be constructed from And, Or, and Not operations using the canonical representation method, it follows that every Boolean function can be constructed from Nand operations alone. This result has far-reaching practical implications: Once we have in our disposal a physical device that implements Nand, we can use many copies of this device (wired in a certain way) to implement in hardware any Boolean function.

11

Boolean Logic

1.1.2

Gate Logic

A gate is a physical device that implements a Boolean function. If a Boolean function f operates on n variables and returns m binary results (in all our examples so far, m was 1), the gate that implements f will have n input pins and m output pins. When we put some values v1 . . . vn in the gate’s input pins, the gate’s ‘‘logic’’—its internal structure—should compute and output f ðv1 . . . vn Þ. And just like complex Boolean functions can be expressed in terms of simpler functions, complex gates are composed from more elementary gates. The simplest gates of all are made from tiny switching devices, called transistors, wired in a certain topology designed to effect the overall gate functionality. Although most digital computers today use electricity to represent and transmit binary data from one gate to another, any alternative technology permitting switching and conducting capabilities can be employed. Indeed, during the last fifty years, researchers have built many hardware implementations of Boolean functions, including magnetic, optical, biological, hydraulic, and pneumatic mechanisms. Today, most gates are implemented as transistors etched in silicon, packaged as chips. In this book we use the words chip and gate interchangeably, tending to use the term gates for simple chips. The availability of alternative switching technology options, on the one hand, and the observation that Boolean algebra can be used to abstract the behavior of any such technology, on the other, is extremely important. Basically, it implies that computer scientists don’t have to worry about physical things like electricity, circuits, switches, relays, and power supply. Instead, computer scientists can be content with the abstract notions of Boolean algebra and gate logic, trusting that someone else (the physicists and electrical engineers—bless their souls) will figure out how to actually realize them in hardware. Hence, a primitive gate (see figure 1.3) can be viewed as a black box device that implements an elementary logical operation in one way or another—we don’t care how. A hardware designer starts from such primitive gates and designs more complicated functionality by interconnecting them, leading to the construction of composite gates.

a b

And

Figure 1.3

out

a b

Or

out

in

Not

out

Standard symbolic notation of some elementary logic gates.

12

Chapter 1

Gate interface a b c

Gate implementation a

And

out

If a=b=c=1 then out=1 else out=0

And b c

And

out

Figure 1.4 Composite implementation of a three-way And gate. The rectangle on the right defines the conceptual boundaries of the gate interface.

Primitive and Composite Gates Since all logic gates have the same input and output semantics (0’s and 1’s), they can be chained together, creating composite gates of arbitrary complexity. For example, suppose we are asked to implement the 3-way Boolean function Andða; b; cÞ. Using Boolean algebra, we can begin by observing that a b c ¼ ða bÞ c, or, using prefix notation, Andða; b; cÞ ¼ AndðAndða; bÞ; cÞ. Next, we can use this result to construct the composite gate depicted in figure 1.4. The construction described in figure 1.4 is a simple example of gate logic, also called logic design. Simply put, logic design is the art of interconnecting gates in order to implement more complex functionality, leading to the notion of composite gates. Since composite gates are themselves realizations of (possibly complex) Boolean functions, their ‘‘outside appearance’’ (e.g., left side of figure 1.4) looks just like that of primitive gates. At the same time, their internal structure can be rather complex. We see that any given logic gate can be viewed from two different perspectives: external and internal. The right-hand side of figure 1.4 gives the gate’s internal architecture, or implementation, whereas the left side shows only the gate interface, namely, the input and output pins that it exposes to the outside world. The former is relevant only to the gate designer, whereas the latter is the right level of detail for other designers who wish to use the gate as an abstract off-the-shelf component, without paying attention to its internal structure. Let us consider another logic design example—that of a Xor gate. As discussed before, Xorða; bÞ is 1 exactly when either a is 1 and b is 0, or when a is 0 and b is 1. Said otherwise, Xorða; bÞ ¼ OrðAndða; NotðbÞÞ; AndðNotðaÞ; bÞÞ. This definition leads to the logic design shown in figure 1.5. Note that the gate interface is unique: There is only one way to describe it, and this is normally done using a truth table, a Boolean expression, or some verbal specifica-

13

Boolean Logic

a

a Xor

And

out

b Or a

b

out

0 0 1 1

0 1 0 1

0 1 1 0

Figure 1.5

out

And b

Xor gate, along with a possible implementation.

tion. This interface, however, can be realized using many different implementations, some of which will be better than others in terms of cost, speed, and simplicity. For example, the Xor function can be implemented using four, rather than five, And, Or, and Not gates. Thus, from a functional standpoint, the fundamental requirement of logic design is that the gate implementation will realize its stated interface, in one way or another. From an efficiency standpoint, the general rule is to try to do more with less, that is, use as few gates as possible. To sum up, the art of logic design can be described as follows: Given a gate specification (interface), find an efficient way to implement it using other gates that were already implemented. This, in a nutshell, is what we will do in the rest of this chapter. 1.1.3

Actual Hardware Construction

Having described the logic of composing complex gates from simpler ones, we are now in a position to discuss how gates are actually built. Let us start with an intentionally naı¨ve example. Suppose we open a chip fabrication shop in our home garage. Our first contract is to build a hundred Xor gates. Using the order’s downpayment, we purchase a soldering gun, a roll of copper wire, and three bins labeled ‘‘And gates,’’ ‘‘Or gates,’’ and ‘‘Not gates,’’ each containing many identical copies of these elementary logic gates. Each of these gates is sealed in a plastic casing that exposes some input and output pins, as well as a power supply plug. To get started, we pin figure 1.5 to our garage wall and proceed to realize it using our hardware. First, we take two And gates, two Not gates, and one Or gate, and mount them on a board according to the

14

Chapter 1

figure’s layout. Next, we connect the chips to one another by running copper wires among them and by soldering the wire ends to the respective input/output pins. Now, if we follow the gate diagram carefully, we will end up having three exposed wire ends. We then solder a pin to each one of these wire ends, seal the entire device (except for the three pins) in a plastic casing, and label it ‘‘Xor.’’ We can repeat this assembly process many times over. At the end of the day, we can store all the chips that we’ve built in a new bin and label it ‘‘Xor gates.’’ If we (or other people) are asked to construct some other chips in the future, we’ll be able to use these Xor gates as elementary building blocks, just as we used the And, Or, and Not gates before. As the reader has probably sensed, the garage approach to chip production leaves much to be desired. For starters, there is no guarantee that the given chip diagram is correct. Although we can prove correctness in simple cases like Xor, we cannot do so in many realistically complex chips. Thus, we must settle for empirical testing: Build the chip, connect it to a power supply, activate and deactivate the input pins in various configurations, and hope that the chip outputs will agree with its specifications. If the chip fails to deliver the desired outputs, we will have to tinker with its physical structure—a rather messy affair. Further, even if we will come up with the right design, replicating the chip assembly process many times over will be a time-consuming and error-prone affair. There must be a better way! 1.1.4

Hardware Description Language (HDL)

Today, hardware designers no longer build anything with their bare hands. Instead, they plan and optimize the chip architecture on a computer workstation, using structured modeling formalisms like Hardware Description Language, or HDL (also known as VHDL, where V stands for Virtual ). The designer specifies the chip structure by writing an HDL program, which is then subjected to a rigorous battery of tests. These tests are carried out virtually, using computer simulation: A special software tool, called a hardware simulator, takes the HDL program as input and builds an image of the modeled chip in memory. Next, the designer can instruct the simulator to test the virtual chip on various sets of inputs, generating simulated chip outputs. The outputs can then be compared to the desired results, as mandated by the client who ordered the chip built. In addition to testing the chip’s correctness, the hardware designer will typically be interested in a variety of parameters such as speed of computation, energy consumption, and the overall cost implied by the chip design. All these param-

15

Boolean Logic

eters can be simulated and quantified by the hardware simulator, helping the designer optimize the design until the simulated chip delivers desired cost/performance levels. Thus, using HDL, one can completely plan, debug, and optimize the entire chip before a single penny is spent on actual production. When the HDL program is deemed complete, that is, when the performance of the simulated chip satisfies the client who ordered it, the HDL program can become the blueprint from which many copies of the physical chip can be stamped in silicon. This final step in the chip life cycle—from an optimized HDL program to mass production—is typically outsourced to companies that specialize in chip fabrication, using one switching technology or another. Example: Building a Xor Gate As we have seen in figures 1.2 and 1.5, one way to define exclusive or is Xorða; bÞ ¼ OrðAndða; NotðbÞÞ; AndðNotðaÞ; bÞÞ. This logic can be expressed either graphically, as a gate diagram, or textually, as an HDL program. The latter program is written in the HDL variant used throughout this book, defined in appendix A. See figure 1.6 for the details. Explanation An HDL definition of a chip consists of a header section and a parts section. The header section specifies the chip interface, namely the chip name and the names of its input and output pins. The parts section describes the names and topology of all the lower-level parts (other chips) from which this chip is constructed. Each part is represented by a statement that specifies the part name and the way it is connected to other parts in the design. Note that in order to write such statements legibly, the HDL programmer must have a complete documentation of the underlying parts’ interfaces. For example, figure 1.6 assumes that the input and output pins of the Not gate are labeled in and out, and those of And and Or are labeled a, b and out. This API-type information is not obvious, and one must have access to it before one can plug the chip parts into the present code. Inter-part connections are described by creating and connecting internal pins, as needed. For example, consider the bottom of the gate diagram, where the output of a Not gate is piped into the input of a subsequent And gate. The HDL code describes this connection by the pair of statements Not(…,out=nota) and And(a=nota,…). The first statement creates an internal pin (outbound wire) named nota, feeding out into it. The second statement feeds the value of nota into the a input of an And gate. Note that pins may have an unlimited fan out. For example, in figure 1.6, each input is simultaneously fed into two gates. In gate

16

Chapter 1

a

a in

out

notb

b

And

out

w1

a b in

out

nota

out

a b

b

out

Or

And

out

w2

HDL program (Xor.hdl)

Test script (Xor.tst)

Output file (Xor.out)

/* Xor (exclusive or) gate: If ab out=1 else out=0. */ CHIP Xor { IN a, b; OUT out; PARTS: Not(in=a, out=nota); Not(in=b, out=notb); And(a=a, b=notb, out=w1); And(a=nota, b=b, out=w2); Or(a=w1, b=w2, out=out); }

load Xor.hdl, output-list a, set a 0, set b eval, output; set a 0, set b eval, output; set a 1, set b eval, output; set a 1, set b eval, output;

a

b

out

0 0 1 1

0 1 0 1

0 1 1 0

Figure 1.6

b, out; 0, 1, 0, 1,

HDL implementation of a Xor gate.

diagrams, multiple connections are described using forks. In HDL, the existence of forks is implied by the code. Testing Rigorous quality assurance mandates that chips be tested in a specific, replicable, and well-documented fashion. With that in mind, hardware simulators are usually designed to run test scripts, written in some scripting language. For example, the test script in figure 1.6 is written in the scripting language understood by the hardware simulator supplied with the book. This scripting language is described fully in appendix B. Let us give a brief description of the test script from figure 1.6. The first two lines of the test script instruct the simulator to load the Xor.hdl program and get ready to

17

Boolean Logic

print the values of selected variables. Next, the script lists a series of testing scenarios, designed to simulate the various contingencies under which the Xor chip will have to operate in ‘‘real-life’’ situations. In each scenario, the script instructs the simulator to bind the chip inputs to certain data values, compute the resulting output, and record the test results in a designated output file. In the case of simple gates like Xor, one can write an exhaustive test script that enumerates all the possible input values of the gate. The resulting output file (right side of figure 1.6) can then be viewed as a complete empirical proof that the chip is well designed. The luxury of such certitude is not feasible in more complex chips, as we will see later. 1.1.5

Hardware Simulation

Since HDL is a hardware construction language, the process of writing and debugging HDL programs is quite similar to software development. The main difference is that instead of writing code in a language like Java, we write it in HDL, and instead of using a compiler to translate and test the code, we use a hardware simulator. The hardware simulator is a computer program that knows how to parse and interpret HDL code, turn it into an executable representation, and test it according to the specifications of a given test script. There exist many commercial hardware simulators on the market, and these vary greatly in terms of cost, complexity, and ease of use. Together with this book we provide a simple (and free!) hardware simulator that is sufficiently powerful to support sophisticated hardware design projects. In particular, the simulator provides all the necessary tools for building, testing, and integrating all the chips presented in the book, leading to the construction of a general-purpose computer. Figure 1.7 illustrates a typical chip simulation session.

1.2

Specification This section specifies a typical set of gates, each designed to carry out a common Boolean operation. These gates will be used in the chapters that follow to construct the full architecture of a typical modern computer. Our starting point is a single primitive Nand gate, from which all other gates will be derived recursively. Note that we provide only the gates’ specifications, or interfaces, delaying implementation details until a subsequent section. Readers who wish to construct the specified gates in HDL are encouraged to do so, referring to appendix A as needed. All the gates can be built and simulated on a personal computer, using the hardware simulator supplied with the book.

18

Chapter 1

simulator controls

test script

HDL program

current pin values

typical simulation step

output file

Figure 1.7 A screen shot of simulating an Xor chip on the hardware simulator. The simulator state is shown just after the test script has completed running. The pin values correspond to the last simulation step (a ¼ b ¼ 1). Note that the output file generated by the simulation is consistent with the Xor truth table, indicating that the loaded HDL program delivers a correct Xor functionality. The compare file, not shown in the figure and typically specified by the chip’s client, has exactly the same structure and contents as that of the output file. The fact that the two files agree with each other is evident from the status message displayed at the bottom of the screen.

19

Boolean Logic

1.2.1

The Nand Gate

The starting point of our computer architecture is the Nand gate, from which all other gates and chips are built. The Nand gate is designed to compute the following Boolean function: a

b

Nandða; bÞ

0 0 1 1

0 1 0 1

1 1 1 0

Throughout the book, we use ‘‘chip API boxes’’ to specify chips. For each chip, the API specifies the chip name, the names of its input and output pins, the function or operation that the chip effects, and an optional comment. Chip name: Inputs: Outputs: Function: Comment:

1.2.2

Nand a, b out If a=b=1 then out=0 else out=1 This gate is considered primitive and thus there is no need to implement it.

Basic Logic Gates

Some of the logic gates presented here are typically referred to as ‘‘elementary’’ or ‘‘basic.’’ At the same time, every one of them can be composed from Nand gates alone. Therefore, they need not be viewed as primitive. Not The single-input Not gate, also known as ‘‘converter,’’ converts its input from 0 to 1 and vice versa. The gate API is as follows: Chip name: Inputs: Outputs: Function:

Not in out If in=0 then out=1 else out=0.

20

Chapter 1

And

The And function returns 1 when both its inputs are 1, and 0 otherwise.

Chip name: Inputs: Outputs: Function:

Or

And a, b out If a=b=1 then out=1 else out=0.

The Or function returns 1 when at least one of its inputs is 1, and 0 otherwise.

Chip name: Inputs: Outputs: Function:

Or a, b out If a=b=0 then out=0 else out=1.

Xor The Xor function, also known as ‘‘exclusive or,’’ returns 1 when its two inputs have opposing values, and 0 otherwise. Chip name: Inputs: Outputs: Function:

Xor a, b out If a= /b then out=1 else out=0.

Multiplexor A multiplexor (figure 1.8) is a three-input gate that uses one of the inputs, called ‘‘selection bit,’’ to select and output one of the other two inputs, called ‘‘data bits.’’ Thus, a better name for this device might have been selector. The name multiplexor was adopted from communications systems, where similar devices are used to serialize (multiplex) several input signals over a single output wire. Chip name: Inputs: Outputs: Function:

Mux a, b, sel out If sel=0 then out=a else out=b.

21

Boolean Logic

a

b

sel

out

sel

out

0 0 1 1 0 0 1 1

0 1 0 1 0 1 0 1

0 0 0 0 1 1 1 1

0 0 1 1 0 1 0 1

0 1

a b

a out

Mux b sel

Figure 1.8 Multiplexor. The table at the top right is an abbreviated version of the truth table on the left.

a

sel

a

b

0 1

in 0

0 in

in

DMux b sel

Figure 1.9

Demultiplexor.

Demultiplexor A demultiplexor (figure 1.9) performs the opposite function of a multiplexor: It takes a single input and channels it to one of two possible outputs according to a selector bit that specifies which output to chose. Chip name: Inputs: Outputs: Function:

1.2.3

DMux in, sel a, b If sel=0 then {a=in, b=0} else {a=0, b=in}.

Multi-Bit Versions of Basic Gates

Computer hardware is typically designed to operate on multi-bit arrays called ‘‘buses.’’ For example, a basic requirement of a 32-bit computer is to be able to compute (bit-wise) an And function on two given 32-bit buses. To implement this operation, we can build an array of 32 binary And gates, each operating separately

22

Chapter 1

on a pair of bits. In order to enclose all this logic in one package, we can encapsulate the gates array in a single chip interface consisting of two 32-bit input buses and one 32-bit output bus. This section describes a typical set of such multi-bit logic gates, as needed for the construction of a typical 16-bit computer. We note in passing that the architecture of n-bit logic gates is basically the same irrespective of n’s value. When referring to individual bits in a bus, it is common to use an array syntax. For example, to refer to individual bits in a 16-bit bus named data, we use the notation data[0], data[1],. . ., data[15]. Multi-Bit Not An n-bit Not gate applies the Boolean operation Not to every one of the bits in its n-bit input bus: Chip name: Inputs: Outputs: Function:

Not16 in[16] // a 16-bit pin out[16] For i=0..15 out[i]=Not(in[i]).

Multi-Bit And An n-bit And gate applies the Boolean operation And to every one of the n bit-pairs arrayed in its two n-bit input buses: Chip name: Inputs: Outputs: Function:

And16 a[16], b[16] out[16] For i=0..15 out[i]=And(a[i],b[i]).

Multi-Bit Or An n-bit Or gate applies the Boolean operation Or to every one of the n bit-pairs arrayed in its two n-bit input buses: Chip name: Inputs: Outputs: Function:

Or16 a[16], b[16] out[16] For i=0..15 out[i]=Or(a[i],b[i]).

23

Boolean Logic

Multi-Bit Multiplexor An n-bit multiplexor is exactly the same as the binary multiplexor described in figure 1.8, except that the two inputs are each n-bit wide; the selector is a single bit. Chip name: Inputs: Outputs: Function:

1.2.4

Mux16 a[16], b[16], sel out[16] If sel=0 then for i=0..15 out[i]=a[i] else for i=0..15 out[i]=b[i].

Multi-Way Versions of Basic Gates

Many 2-way logic gates that accept two inputs have natural generalization to multiway variants that accept an arbitrary number of inputs. This section describes a set of multi-way gates that will be used subsequently in various chips in our computer architecture. Similar generalizations can be developed for other architectures, as needed. Multi-Way Or An n-way Or gate outputs 1 when at least one of its n bit inputs is 1, and 0 otherwise. Here is the 8-way variant of this gate: Chip name: Inputs: Outputs: Function:

Or8Way in[8] out out=Or(in[0],in[1],…,in[7]).

Multi-Way/Multi-Bit Multiplexor An m-way n-bit multiplexor selects one of m nbit input buses and outputs it to a single n-bit output bus. The selection is specified by a set of k control bits, where k ¼ log2 m. Figure 1.10 depicts a typical example. The computer platform that we develop in this book requires two variations of this chip: A 4-way 16-bit multiplexor and an 8-way 16-bit multiplexor:

24

Chapter 1

sel[1]

sel[0]

0 0 1 1

0 1 0 1

out a b c d

a b c

4-way Mux

d

out a,b,c,d and out are each 16-bit wide

sel[1] sel[0]

Figure 1.10 4-way multiplexor. The width of the input and output buses may vary.

Chip name: Inputs: Outputs: Function: Comment:

Mux4Way16 a[16], b[16], c[16], d[16], sel[2] out[16] If sel=00 then out=a else if sel=01 then out=b else if sel=10 then out=c else if sel=11 then out=d The assignment operations mentioned above are all 16-bit. For example, “out=a” means “for i=0..15 out[i]=a[i]”.

Chip name: Mux8Way16 Inputs: a[16],b[16],c[16],d[16],e[16],f[16],g[16],h[16], sel[3] Outputs: out[16] Function: If sel=000 then out=a else if sel=001 then out=b else if sel=010 out=c … else if sel=111 then out=h Comment: The assignment operations mentioned above are all 16-bit. For example, “out=a” means “for i=0..15 out[i]=a[i]”.

Multi-Way/Multi-Bit Demultiplexor An m-way n-bit demultiplexor (figure 1.11) channels a single n-bit input into one of m possible n-bit outputs. The selection is specified by a set of k control bits, where k ¼ log2 m. The specific computer platform that we will build requires two variations of this chip: A 4-way 1-bit demultiplexor and an 8-way 1-bit multiplexor, as follows.

25

Boolean Logic

sel[1]

sel[0]

a

b

c

d

0 0 1 1

0 1 0 1

in 0 0 0

0 in 0 0

0 0 in 0

0 0 0 in

4-way DMux

in

sel[1] sel[0]

Figure 1.11 4-way demultiplexor.

1.3

Chip name: Inputs: Outputs: Function:

DMux4Way in, sel[2] a, b, c, d If sel=00 then {a=in, b=c=d=0} else if sel=01 then {b=in, a=c=d=0} else if sel=10 then {c=in, a=b=d=0} else if sel=11 then {d=in, a=b=c=0}.

Chip name: Inputs: Outputs: Function:

DMux8Way in, sel[3] a, b, c, d, e, f, g, If sel=000 then else if sel=001 then else if sel=010 … … else if sel=111 then

h {a=in, b=c=d=e=f=g=h=0} {b=in, a=c=d=e=f=g=h=0}

{h=in, a=b=c=d=e=f=g=0}.

Implementation Similar to the role of axioms in mathematics, primitive gates provide a set of elementary building blocks from which everything else can be built. Operationally, primitive gates have an ‘‘off-the-shelf ’’ implementation that is supplied externally. Thus, they can be used in the construction of other gates and chips without worrying about their internal design. In the computer architecture that we are now beginning

26

Chapter 1

to build, we have chosen to base all the hardware on one primitive gate only: Nand. We now turn to outlining the first stage of this bottom-up hardware construction project, one gate at a time. Our implementation guidelines are intentionally partial, since we want you to discover the actual gate architectures yourself. We reiterate that each gate can be implemented in more than one way; the simpler the implementation, the better. Not: The implementation of a unary Not gate from a binary Nand gate is simple. Tip: Think positive. And:

Once again, the gate implementation is simple. Tip: Think negative.

Or/Xor: These functions can be defined in terms of some of the Boolean functions implemented previously, using some simple Boolean manipulations. Thus, the respective gates can be built using previously built gates. Multiplexor/Demultiplexor: Likewise, these gates can be built using previously built gates. Multi-Bit Not/And/Or Gates: Since we already know how to implement the elementary versions of these gates, the implementation of their n-ary versions is simply a matter of constructing arrays of n elementary gates, having each gate operate separately on its bit inputs. This implementation task is rather boring, but it will carry its weight when these multi-bit gates are used in more complex chips, as described in subsequent chapters. Multi-Bit Multiplexor: The implementation of an n-ary multiplexor is simply a matter of feeding the same selection bit to every one of n binary multiplexors. Again, a boring task resulting in a very useful chip. Multi-Way Gates: Implementation tip: Think forks.

1.4

Perspective This chapter described the first steps taken in an applied digital design project. In the next chapter we will build more complicated functionality using the gates built here.

27

Boolean Logic

Although we have chosen to use Nand as our basic building block, other approaches are possible. For example, one can build a complete computer platform using Nor gates alone, or, alternatively, a combination of And, Or, and Not gates. These constructive approaches to logic design are theoretically equivalent, just as all theorems in geometry can be founded on different sets of axioms as alternative points of departure. The theory and practice of such constructions are covered in standard textbooks about digital design or logic design. Throughout the chapter, we paid no attention to efficiency considerations such as the number of elementary gates used in constructing a composite gate or the number of wire crossovers implied by the design. Such considerations are critically important in practice, and a great deal of computer science and electrical engineering expertise focuses on optimizing them. Another issue we did not address at all is the physical implementation of gates and chips using the laws of physics, for example, the role of transistors embedded in silicon. There are of course several such implementation options, each having its own characteristics (speed, power requirements, production cost, etc.). Any nontrivial coverage of these issues requires some background in electronics and physics.

1.5

Project Objective Implement all the logic gates presented in the chapter. The only building blocks that you can use are primitive Nand gates and the composite gates that you will gradually build on top of them. Resources The only tool that you need for this project is the hardware simulator supplied with the book. All the chips should be implemented in the HDL language specified in appendix A. For each one of the chips mentioned in the chapter, we provide a skeletal .hdl program (text file) with a missing implementation part. In addition, for each chip we provide a .tst script file that tells the hardware simulator how to test it, along with the correct output file that this script should generate, called .cmp or ‘‘compare file.’’ Your job is to complete the missing implementation parts of the supplied .hdl programs. Contract

When loaded into the hardware simulator, your chip design (modified

.hdl program), tested on the supplied .tst file, should produce the outputs listed in the supplied .cmp file. If that is not the case, the simulator will let you know.

28

Chapter 1

Tips The Nand gate is considered primitive and thus there is no need to build it: Whenever you use Nand in one of your HDL programs, the simulator will automatically invoke its built-in tools/builtIn/Nand.hdl implementation. We recommend implementing the other gates in this project in the order in which they appear in the chapter. However, since the builtIn directory features working versions of all the chips described in the book, you can always use these chips without defining them first: The simulator will automatically use their built-in versions. For example, consider the skeletal Mux.hdl program supplied in this project. Suppose that for one reason or another you did not complete this program’s implementation, but you still want to use Mux gates as internal parts in other chip designs. This is not a problem, thanks to the following convention. If our simulator fails to find a Mux.hdl file in the current directory, it automatically invokes a built-in Mux implementation, pre-supplied with the simulator’s software. This builtin implementation—a Java class stored in the builtIn directory—has the same interface and functionality as those of the Mux gate described in the book. Thus, if you want the simulator to ignore one or more of your chip implementations, simply move the corresponding .hdl files out of the current directory. Steps

We recommend proceeding in the following order:

0. The hardware simulator needed for this project is available in the tools directory of the book’s software suite. 1. Read appendix A, sections A1–A6 only. 2. Go through the hardware simulator tutorial, parts I, II, and III only. 3. Build and simulate all the chips specified in the projects/01 directory.

2

Boolean Arithmetic

Counting is the religion of this generation, its hope and salvation. —Gertrude Stein (1874–1946)

In this chapter we build gate logic designs that represent numbers and perform arithmetic operations on them. Our starting point is the set of logic gates built in chapter 1, and our ending point is a fully functional Arithmetic Logical Unit. The ALU is the centerpiece chip that executes all the arithmetic and logical operations performed by the computer. Hence, building the ALU functionality is an important step toward understanding how the Central Processing Unit (CPU) and the overall computer work. As usual, we approach this task gradually. The first section gives a brief Background on how binary codes and Boolean arithmetic can be used, respectively, to represent and add signed numbers. The Specification section describes a succession of adder chips, designed to add two bits, three bits, and pairs of n-bit binary numbers. This sets the stage for the ALU specification, which is based on a sophisticated yet simple logic design. The Implementation and Project sections provide tips and guidelines on how to build the adder chips and the ALU on a personal computer, using the hardware simulator supplied with the book. Binary addition is a simple operation that runs deep. Remarkably, most of the operations performed by digital computers can be reduced to elementary additions of binary numbers. Therefore, constructive understanding of binary addition holds the key to the implementation of numerous computer operations that depend on it, one way or another.

30

2.1

Chapter 2

Background Binary Numbers Unlike the decimal system, which is founded on base 10, the binary system is founded on base 2. When we are given a certain binary pattern, say ‘‘10011,’’ and we are told that this pattern is supposed to represent an integer number, the decimal value of this number is computed by convention as follows: ð10011Þtwo ¼ 1 2 4 þ 0 2 3 þ 0 2 2 þ 1 2 1 þ 1 2 0 ¼ 19

ð1Þ

In general, let x ¼ xn xn1 . . . x0 be a string of digits. The value of x in base b, denoted ðxÞb , is defined as follows: ðxn xn1 . . . x0 Þb ¼

n X

xi b i

ð2Þ

i¼0

The reader can verify that in the case of ð10011Þtwo , rule (2) reduces to calculation (1). The result of calculation (1) happens to be 19. Thus, when we press the keyboard keys labeled ‘1’, ‘9’ and ENTER while running, say, a spreadsheet program, what ends up in some register in the computer’s memory is the binary code 10011. More precisely, if the computer happens to be a 32-bit machine, what gets stored in the register is the bit pattern 00000000000000000000000000010011. Binary Addition A pair of binary numbers can be added digit by digit from right to left, according to the same elementary school method used in decimal addition. First, we add the two right-most digits, also called the least significant bits (LSB) of the two binary numbers. Next, we add the resulting carry bit (which is either 0 or 1) to the sum of the next pair of bits up the significance ladder. We continue the process until the two most significant bits (MSB) are added. If the last bit-wise addition generates a carry of 1, we can report overflow; otherwise, the addition completes successfully: 0

0 1 þ 0 0

0 0 1

1 0 0

1 1

1 1 1 0 no overflow

(carry) x y

1

1 1 þ 0

xþ y

1

1 0 1

1 1 1

1 1

0 0 1 0 overflow

We see that computer hardware for binary addition of two n-bit numbers can be built from logic gates designed to calculate the sum of three bits (pair of bits plus carry bit). The transfer of the resulting carry bit forward to the addition of the next significant pair of bits can be easily accomplished by proper wiring of the 3-bit adder gates.

31

Boolean Arithmetic

Signed Binary Numbers A binary system with n digits can generate a set of 2 n different bit patterns. If we have to represent signed numbers in binary code, a natural solution is to split this space into two equal subsets. One subset of codes is assigned to represent positive numbers, and the other negative numbers. Ideally, the coding scheme should be chosen in such a way that the introduction of signed numbers would complicate the hardware implementation as little as possible. This challenge has led to the development of several coding schemes for representing signed numbers in binary code. The method used today by almost all computers is called the 2’s complement method, also known as radix complement. In a binary system with n digits, the 2’s complement of the number x is defined as follows: n 2 x if x 0 0 x¼ 0 otherwise For example, in a 5-bit binary system, the 2’s complement representation of 2 or ‘‘minusð00010Þtwo ’’ is 2 5 ð00010Þtwo ¼ ð32Þten ð2Þten ¼ ð30Þten ¼ ð11110Þtwo . To check the calculation, the reader can verify that ð00010Þtwo þ ð11110Þtwo ¼ ð00000Þtwo . Note that in the latter computation, the sum is actually ð100000Þtwo , but since we are dealing with a 5-bit binary system, the left-most sixth bit is simply ignored. As a rule, when the 2’s complement method is applied to n-bit numbers, x þ ðxÞ always sums up to 2 n (i.e., 1 followed by n 0’s)—a property that gives the method its name. Figure 2.1 illustrates a 4-bit binary system with the 2’s complement method. An inspection of figure 2.1 suggests that an n-bit binary system with 2’s complement representation has the following properties: Positive numbers 0 1 2 3 4 5 6 7

0000 0001 0010 0011 0100 0101 0110 0111

Figure 2.1

Negative numbers 1111 1110 1101 1100 1011 1010 1001 1000

1 2 3 4 5 6 7 8

2’s complement representation of signed numbers in a 4-bit binary system.

32

Chapter 2

m

The system can code a total of 2 n signed numbers, of which the maximal and minimal numbers are 2 n1 1 and 2 n1 , respectively. m The codes of all positive numbers begin with a 0. m

The codes of all negative numbers begin with a 1.

m

To obtain the code of x from the code of x, leave all the trailing (least significant) 0’s and the first least significant 1 intact, then flip all the remaining bits (convert 0’s to 1’s and vice versa). An equivalent shortcut, which is easier to implement in hardware, is to flip all the bits of x and add 1 to the result.

A particularly attractive feature of this representation is that addition of any two signed numbers in 2’s complement is exactly the same as addition of positive numbers. Consider, for example, the addition operation ð2Þ þ ð3Þ. Using 2’s complement (in a 4-bit representation), we have to add, in binary, ð1110Þtwo þ ð1101Þtwo . Without paying any attention to which numbers (positive or negative) these codes represent, bit-wise addition will yield 1011 (after throwing away the overflow bit). As figure 2.1 shows, this indeed is the 2’s complement representation of 5. In short, we see that the 2’s complement method facilitates the addition of any two signed numbers without requiring special hardware beyond that needed for simple bit-wise addition. What about subtraction? Recall that in the 2’s complement method, the arithmetic negation of a signed number x, that is, computing x, is achieved by negating all the bits of x and adding 1 to the result. Thus subtraction can be easily handled by x y ¼ x þ ð yÞ. Once again, hardware complexity is kept to a minimum. The material implications of these theoretical results are significant. Basically, they imply that a single chip, called Arithmetic Logical Unit, can be used to encapsulate all the basic arithmetic and logical operators performed in hardware. We now turn to specify one such ALU, beginning with the specification of an adder chip.

2.2

Specification 2.2.1

Adders

We present a hierarchy of three adders, leading to a multi-bit adder chip: m

Half-adder:

designed to add two bits

m

Full-adder:

designed to add three bits

m

Adder:

designed to add two n-bit numbers

33

Boolean Arithmetic

Inputs b

carry

sum

0 0 1 1

0 1 0 1

0 0 0 1

0 1 1 0

Chip name: Inputs: Outputs: Function:

Figure 2.2

Outputs

a

a b

Half Adder

sum carry

HalfAdder a, b sum, carry sum = LSB of a + b carry = MSB of a + b

Half-adder, designed to add 2 bits.

We also present a special-purpose adder, called incrementer, designed to add 1 to a given number. Half-Adder The first step on our way to adding binary numbers is to be able to add two bits. Let us call the least significant bit of the addition sum, and the most significant bit carry. Figure 2.2 presents a chip, called half-adder, designed to carry out this operation. Full-Adder Now that we know how to add two bits, figure 2.3 presents a full-adder chip, designed to add three bits. Like the half-adder case, the full-adder chip produces two outputs: the least significant bit of the addition, and the carry bit. Adder Memory and register chips represent integer numbers by n-bit patterns, n being 16, 32, 64, and so forth—depending on the computer platform. The chip whose job is to add such numbers is called a multi-bit adder, or simply adder. Figure 2.4 presents a 16-bit adder, noting that the same logic and specifications scale up as is to any n-bit adder. Incrementer It is convenient to have a special-purpose chip dedicated to adding the constant 1 to a given number. Here is the specification of a 16-bit incrementer:

34

Chapter 2

a

b

c

carry

sum

0 0 0 0 1 1 1 1

0 0 1 1 0 0 1 1

0 1 0 1 0 1 0 1

0 0 0 1 0 1 1 1

0 1 1 0 1 0 0 1

Chip name: Inputs: Outputs: Function:

Figure 2.3

b

sum

Full Adder

carry

c

FullAdder a, b, c sum, carry sum = LSB of a + b + c carry = MSB of a + b + c

Full-adder, designed to add 3 bits.

… … …

Chip name: Inputs: Outputs: Function: Comment:

Figure 2.4 same.’’

a

1 0 1

0 1 0 1 1 0

1 0 1

a + b out

16

a 16

b

16-bit Adder

16

out

Add16 a[16], b[16] out[16] out = a + b Integer 2’s complement addition. Overflow is neither detected nor handled.

16-bit adder. Addition of two n-bit binary numbers for any n is ‘‘more of the

35

Boolean Arithmetic

Chip name: Inputs: Outputs: Function: Comment:

2.2.2

Inc16 in[16] out[16] out=in+1 Integer 2’s complement addition. Overflow is neither detected nor handled.

The Arithmetic Logic Unit (ALU)

The specifications of the adder chips presented so far were generic, meaning that they hold for any computer. In contrast, this section describes an ALU that will later become the centerpiece of a specific computer platform called Hack. At the same time, the principles underlying the design of our ALU are rather general. Further, our ALU architecture achieves a great deal of functionality using a minimal set of internal parts. In that respect, it provides a good example of an efficient and elegant logic design. The Hack ALU computes a fixed set of functions out ¼ fi ðx; yÞ where x and y are the chip’s two 16-bit inputs, out is the chip’s 16-bit output, and fi is an arithmetic or logical function selected from a fixed repertoire of eighteen possible functions. We instruct the ALU which function to compute by setting six input bits, called control bits, to selected binary values. The exact input-output specification is given in figure 2.5, using pseudo-code. Note that each one of the six control bits instructs the ALU to carry out a certain elementary operation. Taken together, the combined effects of these operations cause the ALU to compute a variety of useful functions. Since the overall operation is driven by six control bits, the ALU can potentially compute 2 6 ¼ 64 different functions. Eighteen of these functions are documented in figure 2.6. We see that programming our ALU to compute a certain function f ðx; yÞ is done by setting the six control bits to the code of the desired function. From this point on, the internal ALU logic specified in figure 2.5 should cause the ALU to output the value f ðx; yÞ specified in figure 2.6. Of course, this does not happen miraculously, it’s the result of careful design. For example, let us consider the twelfth row of figure 2.6, where the ALU is instructed to compute the function x-1. The zx and nx bits are 0, so the x input is neither zeroed nor negated. The zy and ny bits are 1, so the y input is first zeroed, and then negated bit-wise. Bit-wise negation of zero, ð000 . . . 00Þtwo , gives ð111 . . . 11Þtwo , the 2’s complement code of 1. Thus the ALU inputs end up being x

36

Chapter 2 zx nx

zy

ny

f

x 16 bits

no

f(x,y)

ALU

y 16 bits

zr

out 16 bits

ng

Chip name: ALU Inputs: x[16], y[16], // Two 16-bit data inputs zx, // Zero the x input nx, // Negate the x input zy, // Zero the y input ny, // Negate the y input f, // Function code: 1 for Add, 0 for And no // Negate the out output Outputs: out[16], // 16-bit output zr, // True iff out=0 ng // True iff out=0, R1>=0, and R0*R1, =,

E-Book Information

Year: 2,005

Pages: 341

Pages In File: 331

Language: English

Topic: 58

Identifier: 026214087X,9780262140874

Paginated: 1

Org File Size: 3,204,621

Extension: pdf

Tags: Информатика и вычислительная техника Организация ЭВМ и архитектура ВС

In the early days of computer science, the interactions of hardware, software, compilers, and operating system were simple enough to allow students to see an overall picture of how computers worked. With the increasing complexity of computer technology and the resulting specialization of knowledge, such clarity is often lost. Unlike other texts that cover only one aspect of the field, The Elements of Computing Systems gives students an integrated and rigorous picture of applied computer science, as its comes to play in the construction of a simple yet powerful computer system. Indeed, the best way to understand how computers work is to build one from scratch, and this textbook leads students through twelve chapters and projects that gradually build a basic hardware platform and a modern software hierarchy from the ground up. In the process, the students gain hands-on knowledge of hardware architecture, operating systems, programming languages, compilers, data structures, algorithms, and software engineering. Using this constructive approach, the book exposes a significant body of computer science knowledge and demonstrates how theoretical and applied techniques taught in other courses fit into the overall picture. Designed to support one- or two-semester courses, the book is based on an abstraction-implementation paradigm; each chapter presents a key hardware or software abstraction, a proposed implementation that makes it concrete, and an actual project. The emerging computer system can be built by following the chapters, although this is only one option, since the projects are self-contained and can be done or skipped in any order. All the computer science knowledge necessary for completing the projects is embedded in the book, the only pre-requisite being a programming experience. The book’s web site provides all tools and materials necessary to build all the hardware and software systems described in the text, including two hundred test programs for the twelve projects. The projects and systems can be modified to meet various teaching needs, and all the supplied software is open-source.

The Elements of Computing Systems

The Elements of Computing Systems

by Noam Nisan, Shimon Schocken

Publisher: MIT Press 2005

ISBN/ASIN: 0262640686

ISBN-13: 9780262640688

Number of pages: 410

Description:

The book exposes students to a significant body of computer science knowledge, gained through a series of hardware and software construction tasks. These tasks demonstrate how theoretical and applied techniques taught in other computer science courses are used in practice.

Download or read it online for free here:

Download link

(3.5MB, PDF)

The Elements of Computing Systems: Building a Modern Computer from First Principles 026214087X, 1110001110000, 9780262140874

DESIGNING BIG DATA PLATFORMS Provides expert guidance and valuable insights on getting the most out of Big Data systems

With the resurgence of neural networks in the 2010s, deep learning has become essential for machine learning practitione

This book presents a collection of chapters in which researchers who have worked in the field of gravity for years revea

A new and extensively revised edition of a popular textbook used in universities, coding boot camps, hacker clubs, and o

Our partners will collect data and use cookies for ad personalization and measurement. Learn how we and our ad partner Google, collect and use data

키워드에 대한 정보 the elements of computing systems pdf

다음은 Bing에서 the elements of computing systems pdf 주제에 대한 검색 결과입니다. 필요한 경우 더 읽을 수 있습니다.

이 기사는 인터넷의 다양한 출처에서 편집되었습니다. 이 기사가 유용했기를 바랍니다. 이 기사가 유용하다고 생각되면 공유하십시오. 매우 감사합니다!

사람들이 주제에 대해 자주 검색하는 키워드 ELEMENTS OF COMPUTER SYSTEM

  • 동영상
  • 공유
  • 카메라폰
  • 동영상폰
  • 무료
  • 올리기

ELEMENTS #OF #COMPUTER #SYSTEM


YouTube에서 the elements of computing systems pdf 주제의 다른 동영상 보기

주제에 대한 기사를 시청해 주셔서 감사합니다 ELEMENTS OF COMPUTER SYSTEM | the elements of computing systems pdf, 이 기사가 유용하다고 생각되면 공유하십시오, 매우 감사합니다.

See also  순 병원 둘 루스 | 관절와순파열 수술? 대부분 수술없이 회복된다. @달려라병원 어깨전문의 박재범 상위 191개 베스트 답변

Leave a Comment