I skipped the daily learning notes for the last couple days to enjoy my time at the Southern California Linux Expo (SCaLE), my first real tech conference experience! I have a bunch of notes to sort through (and a bazillion questions!) but I decided to leave them in their raw form for now because every single one of those topics is an enormous rabbit hole, and I have enough trouble avoiding those as it is!
I brought my laptop with me the first two days, but for the last two days I left it at home and focused more on networking with people. So much networking, so many people! I needed a quiet day to decompress and get some work done. I have a bunch of freelance projects on my plate right now, but I always reserve some time for learning new things! (I am very conflicted about how to balance the two… Maybe I’ll write more on that later.)
Anyway, today I started week 4 of NAND2Tetris, woohoo! I’m excited to learn more about computer science and build a computer from the ground up!
Chapter 4 reading notes
For week 4, I have the following materials to go over:
- Chapter 4 of The Elements of Computing Systems (PDF)
- Week 4 lecture slides
- Video lectures on Coursera
- Assembler tutorial (PDF)
- CPU emulator tutorial (PDF)
- Week 4 project
This might take a while. It looks like this week I’ll get my first taste of assembly programming! This mix of fear, confusion and excitement has become a very familiar feeling. I’m doing my best to embrace it.
My first intro to machine language
They begin this chapter by noting the difference between a constructive and an abstract description of a computer; you can show how it’s built from combinations of smaller parts, or you can show what it does. Up until now, I’ve focused on constructing a computer, but in this chapter they say they’ll introduce me to machine language progrms.
What is machine language? An “agreed-upon formalism” for instructions that can be run directly by the computer’s CPU. Its goal is “direct execution” and “total control” of the hardware. (They contrast this with the goal of high-level languages: “generality and power of expression”.)
At the Southern California Linux Expo (SCaLE) this past weekend, I often heard people use the phrase “close to the metal” to describe low-level programming languages. This might be because they had an entire track for talks about embedded systems. (I still don’t completely understand what embedded systems are, but that’s for another day!) Anyway, my understanding is that close-to-the-metal or low-level languages can be faster and more efficient, but also much more tedious and difficult for humans to write. I wonder: what are the other differences and trade-offs between low-level and high-level languages?
I love this quote:
“Machine language is the most profound interface in the overall computer enterprise–the fine line where hardware and software meet.”
I’ve been curious about this hardware-software meeting point for a long time, so this is exciting! I love anything related to the idea of transforming abstract human thought into physical reality. People have explored this idea since the dawn of human civilization; you can find it wrapped up in metaphors throughout history. I’ve explored the idea through literature in the past, but never on a practical or technical level. So excited to finally have a chance to explore this new territory!
OK, enough fluff. Back to reading. I like how they describe this symbiosis: machine languages are designed to “exploit a given hardware platform”, and hardware platforms are designed to execute instructions written in a given machine language.
The abstract machine
In this section on machines, the authors revise their earlier definition of a machine language to be much more specific:
“A machine language can be viewed as an agreed-upon formalism, designed to manipulate a memory using a processor and a set of registers.”
The memory stores data and instructions in a “continuous array of cells of some fixed width” – called words or locations – each with a unique address. I learned about that stuff at the hardware level in chapter 3, and now they’re talking about it in a more abstract way.
The processor refers to the central processing unit (CPU), which I learned a bit about before, but I don’t understand exactly how it processes anything. More on that later.
The registers are near the processor for “high-speed local memory”, because accessing the memory can be pretty slow.
A program is just a set of “coded instructions”, and since binary codes aren’t exactly human-friendly, machine languages are also specified by “symbolic mnemonics” like
ADD R2,R3,R4. Here’s our next level of abstraction, woohoo! This is assembly language!
As they say in the book, it’s important to note that the code for any of these instructions is completely arbitrary and decided by the language designer. (I wonder if “language designer” is a real job…) My friend lent me the book Code: The Hidden Language of Computer Hardware and Software, and the entire first chapter is dedicated to how arbitrary codes can be created to serve a particular purpose, whether the code is human speech or Braille or Morse code.
I’m really interested in the intersection of computer science and linguistics, because both fields could be described as the study of codes – well, in a very general sense. And that reminds me: what is computer science, really? It’s not necessarily about computers and it isn’t really a science. But then maybe I don’t know what science really means either. Ugh, words! The codes of human speech are almost never clearly defined.
Anyway, back to assembly language. They say it’s often called assembly for short, and an assembler is a program that cn translate assembly into binary.
In more detail, they say that the assembler can:
- “parse the symbolic commands into their underlying fields”,
- “translate each field into its equivalent binary representation”,
- and “assemble the resulting codes into binary machine instructions.”
I have no clue how it does all of those things! But I think I might be writing my own assembler later in the course. For now, they’re just introducing the general idea of it.
Common assembly and machine language commands
They say that there’s a “Tower of Babel of machine languages”, but all machine languages support some generic commands. They all do basic arithmetic operations and Boolean operations. (I learned about that in the first two weeks of the course, especially when I built my arithmetic logical unit! That was fun.)
Then there are memory access commands, which can be used with explicit load and store commands or more implicitly with the use of arithemetic and logical commands.
There are several addressing modes to specify the address of a location in memory, and they list three common modes:
- Direct addressing: specifying the number or name of an address like
LOAD R1, 67where
67is the address of a memory location.
- Immediate addressing: specifying a literal value, as in
LOADI R1, 67where
67is the value to be loaded into the register, not the address of a register.
- Indirect addressing: specifying a memory location that contains the address.
They have a couple paragraphs on indirect addressing, but I don’t understand the explanation at all! Well, I’ll worry about that the next time it comes up.
Next, there are commands for branching logic or control flow, like loops and conditional statements. They mention that there are conditional and unconditional jump commands, but I didn’t understand that part either.
Specifications for the Hack machine language
The computer I’ll be building in this course is named the Hack computer. It has two 16-bit wide memories with a 15-bit address space – one memory for instructions and one for data.
Here’s an important point: the CPU only executes what’s in the instruction memory, which is a read-only device! So that means the instructions need to be loaded from some external source, like a ROM chip. (They didn’t specify what a ROM chip is in this chapter, but I looked it up: it stands for read-only memory, and it’s non-volatile, which apparently means it doesn’t need continuous power to store what’s in its memory.) I wonder how you would get the instructions onto the ROM chip in the first place! But that’s beyond the scope of this course.
To deal with that issue, the hardware simulators for this course can load the instruction memory from a text file. (They liken it to replacing a cartridge in a game console.)
Next, the registers: the computer will have two 16-bit registers, one for data (the D register) and one for both data and addresse (the A register).
OK, I felt completely lost when I read this section the first time. But I think I get the gist of it: I can’t fit the code for an instruction and the code for an address into 16 bits, so the workaround is to use one of the registers to store the address of the memory location. Each of the computer’s instructions are designed to assume that
M will always refer to an address stored in the A register. So to perform an operation using a new address, I would need to use two commands: first load the address value into the register, and then perform the desired operation. Depending on context, whatever is stored in the A register could be interpreted as data or as an address pointing to the memory.
The Hack language has two generic instructions: an address instruction (A-instruction) and a compute instruction (C-instruction).
The A-instruction is a 16-bit binary code beginning with 0 that can store a 15-bit value in the A register. The instrcution looks like
value is either a non-negative integer (like
@5) or a symbol referring to one (like
@5 corresponds to
They provide an example program written in C and written in the Hack machine language on page 65, but I only glanced at it for now. I’ll read it line by line and try to understand it when I start on this week’s project.
The C-instruction always begins with a 1, and it contains three fields: the destination in which to store the result of the operation, the computation to execute, and which command to execute next. This chapter of the book includes a table outlining the binary codes and mnemonics for each field – definitely something for me to look at again later!
Hey, now I understand why I had to build single-bit outputs for my ALU to indicate whether its result is zero or negative! Because now we’re going to use those bits for the jump command to jump to different instructions based on different conditions!
They give an example on page 68, and I find it very weird how you have to do everything in multiple steps.
To run the code
if Memory=5 then goto 100, else goto 200 you have to do the following steps:
- first set the address register (A register) to the value representing the memory location’s address,
- then run a command to load that value into the data register (D register),
- then set the A register to the value you want to check for,
- then subtract what’s in the A register from what’s in the D register,
- then set the A register to the address containing the instruction you want to run next if the condition is true,
- then run one of the jump commands that checks if the value of the D register is equal to 0,
- then set the A register to the address containing the instruction you want to run next if the condition is false,
- and then run the unconditional jump command.
Phew! Assembly is weird!
But now I understand why it’s weird; you need to break down all those higher-level concepts (like conditional statements) into tiny steps that either involve loading or storing a value, adding or subtracting or running a basic logical operation, checking if the output is positive or negative or zero, and loading the location of the next instruction to run! It’s tedious, yes, but it’s also incredible that all the complex instructions we can think up in higher-level programming languages can be broken down into such simple steps.
At the end of this chapter, they note that most machine languages can support both an address and a command within one instruction. I assume that’s because their instructions are more than 16 bits long.
Last but not least, they say that in week 6 I will build my own assembler! How fun and scary!
New concepts and jargon
Machine language: “an agreed-upon formalism, designed to code low-level programs as series of machine instructions” by manipulating a memory using a processor and set of registers.
Machine language program: “a series of coded instructions.”
Memory: “refers loosely to the collection of hardware devices that store data and instructions in a computer.”
Processor, central processiong unit or CPU: a device that performs “a fixed set of elementary operations.
Assembly language: a symbolic notation (mnemonics) that represents machine language instructions.
Assembler: “a program that translates from assembly to binary”.
- Addressing modes: “ways of specificying the address of the required memory word.”
- Three common types are direct addressing, immediate addressing, and indirect addressing. (See description in reading notes above.)
Branching logic: another word for describing control flow, which includes loops, conditional statements, and subroutines.
- ROM: read-only memory, a type of non-volatile (meaning its memory isn’t lost if it loses power).
- What are the differences and trade-offs between low-level and high-level languages?
- How exactly does a CPU work?
- What’s the difference between the registers and memory?
- Is “language designer” a real job?
- What does parsing mean?
- How does indirect addressing work?
- What exactly is a subroutine?
- What’s the difference between an unconditional jump and a conditional jump command?
- What is a a von Neumann platform?
- How is it possible to burn data onto a ROM chip?
- Is assembly language a machine language or are they mutually exclusive terms that are both low-level languages?
- Where is the boundary between low-level and high-level languages?
- What are embedded systems?
- What is computer science, anyhow?
Learning summary (#TIL)
Machine language is where the hardware and software meet! It’s a symbiotic relationship: the machine language is meant to use a certain hardware platform, and the hardware platform is designed to execute instructions written in a given machine language.
Machine language manipulates a memory using a processor and a set of registers.
Assembly language uses symbolic mnemonics to make it easier for humans to control the hardware by using an assembler to translate assembly into machine code.
There are three common addressing modes in assembly language to specify a location in memory.
The Hack machine language is a “1/2-adress machine” because each instruction generally requires two steps: specifying the address and then executing an operation.
I realized why assembly is so tedious, and I’ve started to understand how high-level concepts can be broken down into such simple steps!
I learned a few of the commands used in the Hack language and started to learn how to read assembly programs.
- Watch the video lectures on Coursera
- Read the Assembler tutorial (PDF) and CPU emulator tutorial (PDF)
- Complete the project for week 4
- Study time: 4 hours
- Active reading: 4 hours