[Part 1] Unit 4.2 – Machine Languages: Elements

[Part 1] Unit 4.2 – Machine Languages: Elements


In the previous unit, we talked about
the general idea of a machine language and how they control computers. What we want to do in this unit is
discuss basic elements that appear in all machine languages. The main reason is to
give you some context for what you will learn about the HACK
machine language in the next unit. Because most of the elements that you find
in the HACK machine language are of course elements that appear in some way, in some
form, in all the other machine languages. So, we first start with
the general description of the kind of elements you
find in machine languages. A very elementary description, that is. Me, missing a lot of more sophisticated
features that you’d find in many computers, and
then you’ll have this context for the, for the HACK machine language. So, the first important thing to remember
is that the machine language is a most important interface probably in
the whole world of computer science. It is the interface between
hardware are software, it’s ex, ex,exactly the way that
software can control the hardware. This kind of machine language needs to
specify what are the operations that our hardware performs? What does, that, what, where does it
get the data that it operates on, what is the control of the operations,
and so on. In principle, and usually, these,
this kind of interface is done in a almost one to one correspondence with
the actual hardware implementation. The idea is that the hardware is built
in a way that directly corresponds to the type of functionality that it
provides to the software layer above it. This need not happen always. Sometimes, you can want to provide nice
functionality, even at this level, even to, just so the compiler writers will be
happy about the codes they need to emit. And the hardware will be eh,
another layer removed from it. But we’re not going to talk about it. And in first, from first principles,
basically the machine language specifies exactly the kind of things
that the hardware can do for us. Of course, when we actually go
to design a machine language, the basic element is
a cost-performance tradeoff. The more sophisticated operations that
we want to give our machine language, the more large data types, or
sophisticated data types it operates on, the more costly it will be
to actually build this. Costly, in terms of area of silicon, costly in terms of time that the hardware
actually needs to operate, and so on. Of course, in our computer, we’re always taking this kind
of trade off to the simplest. We’re trying to present
the simplest kind of thing and not really worrying
about real performance. But, in any real machine, the whole thing
that drives the design of the machine language is a cost-performance tradeoff. So let’s talk about the type of
operations that our hardware can perform. Each machine language defines
a set of operations and these fall into several categories. For example, the arithmetic operations. For example, addition of two numbers, subtraction,
maybe also multiplication or division. There are logical operations. For example, taking the and of two bits or
maybe the bitwise and of two words. And then there are also the operations
that control the flow control. That tell the, the hardware when
to jump inside the program. So these are the type of
instruction that we usually will have in any machine language. And different machine languages define
different sets, sets of such operations which may defer from each other
in terms of their richness. For example, some machines may allow, may provide division as a basic operation,
while other machines will decide not to do that because it’s
too expensive in terms of silicon. But rather the software will of course,
have to provide that functionality. Probably even more
important is the question, what data types do,
can our hardware access, primitively? So there’s a big difference of course, between adding 8-bit numbers and
adding 64-bit numbers. If your software program really
needs arithmetic on 64-bit values, then of course our hardware
that performs in one operation. In addition of 64-bit values, will be at least eight times faster
than hardware that needs to actually implement addition of 64-bit values by
a sequence of additions of 8-bit values. Similarly, some computers can also provide
a, do also provide richer data types. For example, you may have a, your hardware support
directly floating point operations. Numbers that are not integer, but
rather real numbers, and deal with them, provide addition, multiplication,
division of them as a basic operation. If you want to do scientific computation,
which works with this kind of flow, floating point numbers or
real numbers rather than just integers. Of course, such machines will be much
faster than machines that can only handle integers, basically. While the difference and set of
operations that we’ve seen previously is quite obvious the next issue is
probably even more important, although slightly more subtle,
and that is a question. How do we decide what data to work on? How does the hardware allow us
to specify what type of data, values are we going to work on? The basic problem that we have
here is that, what we’re going to work on resides in memory, and accessing
memory is an expensive operation. It’s expensive in at least
two related points of view. First of all, if you have a large memory, specifying what part of the memory
do you want to work to operate on, requires a large amount of just
as many bits to specify it. Because you need to give
an address in a very large memory. And that’s going to be wasteful
in terms of the instruction. If I just want to say oh,
add the last two numbers, I’m, can’t, won’t be able to just do that because
I will have to specify two very large addresses in order to tell
the hardware what to operate on. The second element, which is closely
related, is the fact that just accessing a value from a large memory takes
relatively large, a large amount of time. Com, compared to the state of
the speed of the CPU itself, of the arithmetic operations themselves. So the way to handle these two things,
the way to give us good control over what type of a, what,
what type of data are we working on. Without requiring all these costs
of specifying the large address and getting the information from
a far away place if you wish. In terms of time, the basic solution
was whats called a memory hierarchy. And this was already figured out by
when he built the first computer. The basic idea is instead of having just
one large block of memory, we’re going to have a whole sequence of memories
that are getting bigger and bigger. The smallest memories are going
to be very easy to access. First of all, because we don’t have to
specify large address space because there are only going to be a very few of them. Second of all,
because there are only very few of them, we can actually get information
from them very quickly. And then, there is going to be slightly
larger memories, usually called cache, and even larger memories,
sometimes called the big, the main memory. And maybe even, even larger memories
that are going to sit on disk. At each time we get farther away
from the arithmetic unit itself, our memory be, gets bigger. Accessing it becomes harder borth, both in
terms of giving a larger, a wider address. And in terms of the time we need
to wait until we get the value. But we have more information there. The ways that the different levels of
the memory hierarchy are handled differs according to the different levels. But, what we’re going to discuss
now is the way that registers, the smallest, the smallest memory that
usually resides really inside the CPU, and how we handle that. So eh, almost every CPU has a few,
very small amount of memory registers that are located
really inside the CPU. Their type and functionality’s
really part of the machine language. And the main point is that since there are
so few of them, everything then requires very few bits, and getting the information
of them is extremely quickly. They are built from the fastest technology
available and it’s, they are already inside the CPU, so there is no delay in
getting in, any information from there. So, what types of registers do we have? The first kind of things that we will
do with these memory location registers that are inside our CPU is
just use them for data. For example, we can put numbers in them, and have operations saying something
like add the register 1 to register 2. In this situation, basically,
what will happen is the contents of register 1 will be added to
the contents of register 2, if that is the meaning of this
operation in our machine language. So, once we have a vi, a small
number of registers inside the CPU, we can do lots of operations on a small
amount of memory, very, very quickly. The second kind of things we do
within these, with these registers, is use them as addresses. We can also sometimes
put into one of these registers an address into main memory. Which will allow us to specify at
which part of the re of the bigger memory we want to access for
operations if we want to access. For example, if we have
a operation like store register 1 into memory address that is
specified by that register called A. Then, what will happen is, once we,
once we actually perform the hardware, perform this operation, that number 77
will be written into the main memory. This can be an operation that
takes a larger amount of time than internal operation to the CPU. But, the important point is
that the address into which we write this information was
actually given by the A register. This is another type of usage we have for
registers that are inside our main memory. Once we have these registers, now we can think about,
go back to the original question. How do we decide which data to work upon? How do we tell the computer for
us, an operation, let’s say a simple add operation,
what is it supposed to operate upon? And there are a bunch of
different possibilities. Here are base,
here are four possibilities. These are sometimes called
addressing modes, and there, some computers have
other possibilities as well. Sometimes, we just want to
work on these registers. So we, for example, we can say,
add register 1 to register 2, and this means, of course, it’s
addition operation is on two registers. Sometimes we have direct access to memory. We can have an operation saying,
add register 1 to memory location 200. In which case we’re telling the computer
to directly address not just the register 1, but also a memory address that we
just specified inside the command. Yet another possibility is what’s
called indirect addressing. This is a example we had previously for
using the A register where the at memory address that we access is not
specified as part of instruction. But rather is already written inside
the address register that already was previously loaded inside
the CPU with some correct value. And yet another possibility is that
we actually have a value inside the, inside the instruction itself. For example,
we can say add 73 to register 1, and 73 is a constant 73 is
part of the instruction. So all these are different ways we can
actually tell the computers which values, which data to work on in each instruction. While were talking about how to where to
take the data from, and where to put it. We might also add something that
is usually piggybacked at upon it, which is how do we deal with input and
output in most machine languages? So as we all know, there are an enormous
amount of input and output devices. Printers and screens and sens,
various sensors and keyboards and mice and mouses and so on. So one way to actually access
these input or output devices is to actually connect them,
connect the registers which control them, these output devices,
as part of your memory. For example, we may have a mouse that is
connected in a way that whenever the user moves the mouse, that last movement is
written into some kind of a register. And that register is accessible
by the computer in a pre, in a certain address
as part of the memory. This gives us access to input and
output as though we are accessing the memory itself and of course the
software that actually deals with this. Software that are usually part of
the drivers in an operating system, must know exactly, not only, what
are the addresses to which this input or output device is connected,
but also how to speak with it. What does the values in that locations,
what do they really mean? The final element that I wish to
discuss is what’s called flow control. How can we tell the hardware what
instruction to execute next? So, usually it’s very simple. Usually, if I now was in instruction 73,
the next instruction will be 74, that’s a normal situation. But, there are situations where of course,
we need to change the flow of control and not just continue doing
instructions one after another. The first reason is sometimes we
just need to go back to a previous, to another location. For example, doing a loop. Or maybe jumping to another part of
the software just because now is a time to jump to another part of the program. So, this will be what sometime
is called an unconditional jump. And one of the main uses for
it will be actually be to do a loop. Suppose we want to actually eh,
start and do something for values one, two, three,
four, five, six and so on. The way we do it will have some kind
of register holding these values. Each time we want to add
1 to R1 to let’s say, if that is the register we chose
to actually have this value, and then we do whatever we need
to do with this new value. Now, we next want to do the same
thing with the next value of R1. The way we do it,
we have to tell the computer oh, after a, after you do a,
instruction number 156 in our example. Don’t continue to instruction 157,
but go back to instruction 102, which basically adds 1 to R1,
giving us the next value of R1. And then continue doing whatever
you did for, with the new value. So, this allows you to do a loop and
we should and machine languages always have
some kind of capability. That the software tells the hardware
to do something again or to return back or
to jump to a different direction. Notice that the actually addresses, 101,
102 and 156 are not really that important. What is really important was that
when we jumped to address 102, we need it to be the address
that we’re actually meaning. So, we could have, do this in a symbolic
manner in the same, in the following way. Just give a name to important locations. For example, location 102,
I give it the name loop. And then I say, jump to loop. This doesn’t really change anything. This is exactly the same thing
when we actually write it in bits in our machine language. But it’s more convenient for
humans to look at, so we’ll just do that as part of
the way of describing programs. Then, there are other cases,
eh which we need to handle flow control, where we need to do what is
sometimes called unconditional jump. In some cases,
we want to jump to another location. While in other cases, depending on let’s say the last the last
instruction that we performed or according to the value of some register or
according to some other condition. In some cases, we want to jump to
another location and other cases, we just want to continue for
the next instruction. And this is called a conditional jump. For example,
suppose I want to do something on the, on the absolute value of a number,
so if the number is positive, I just want to do some
operation on the number. But if the number is negative, I want to
first turn it into to be positive and then work on the positive version of it. The way we can do that,
we can have a conditional jump. Jump greater than,
which means if R1 is greater than 0, I want you to jump to the label cont. This is just a number,
just a name that I made up. Otherwise, just continue. This means that if we
have a positive number, then we are not going to do the next
instruction, the subtract instruction. But if we have a negative number, we’re just going to continue directly
to do the subtract instruction. Which really does eh, takes R1 and
negates its value, makes it positive. Now, in both cases, we are continuing
with the same sequence of instructions. And now, we already have an R1
anyway of positive value. So this is another, is an example why we
sometimes will need the conditional jump. And machine languages always have some
kind of a, some kind of a practice, some kind of a way to actually
instruct the hardware to do these kind of conditional jump. At this point, we’ve finished very,
very quick, very high level, very basic overview of the type of instructions
that machine languages provide. And now we’re ready to actually talk about
our computer, the HACK machine language.