Tuesday, February 22, 2011

Speaking my language

I spend the bulk of my day writing code.

I would hazard a guess that most people understand that a computer understands only one language, machine language, but I doubt that most people have ever been introduced to it or seen what it looks like.  Well let me introduce you.

Here is a small piece of code that my processor would understand

5,56,229,56,211,148,15,64,5,228,245,56

You will notice that it is simply a list of numbers where each number is between the values of 0 and 255.  Since the values are constrained to be less than 256, it means that this particular microcontroller is an 8-bit processor . 

You may know that most computers can be classified as either 8, 16, 32 or 64 bits.  What this classification means is simply how large the numbers in the list can be.  For 16 bit computers the numbers would range from 0 to 65535.  For a 32 bit computer the numbers would range from 0 to 4294967295.  Finally for a 64 bit computer the numbers would range from 0 to 18446744073709551615.

That is all well and good, but it is only a list of numbers, what does it mean?

Well this is where it can get complicated since every different type of processor interprets the numbers differently.  There are literally millions of different ways to understand what that list of numbers means.

So in order to decode the list you need to know two things.  One, what specific processor is this list of numbers encoded for and two where does the list begin.

The first requirement makes sense from what I have just told you, but why do you need to know where the beginning of the list is?

It isn’t a trick question.  Knowing where the list begins is important because each and every number in the list above can be classified as one of two different types.  The number could be an instruction to the computer, or the number could be data the computer is to use in a computation.

For example:  1+2

The numbers 1 and 2 in this case are data, the computer uses these numbers in the computation.

The + symbol is an instruction to the computer to add 1 and 2.

The problem is that the computer understands numbers, but the computer has no idea what a + symbol is.  So what are we to do?

Well the easiest thing is to encode the + symbol as a number.  For example we could give the + symbol the number 36.

Rewriting the above example so that the computer understands it we could write:

1,36,2

This would be wrong however.  The problem arise with how to know which numbers are data and which numbers are instructions.  Let’s rewrite it like this.

36,1 2

So now we know that instructions always come first and the data follows.  In this example it means ADD 1 with 2.  See how simple that was?

Going back to the purpose for this example we understand now why we need to know where the list begins, since the first number in the list will always be an instruction.  Furthermore each instruction always has a known amount of data associated with it  so we can calculate as we move down the list which numbers are instructions and which numbers are data.

Lets go back to my original example.

5,56,229,56,211,148,15,64,5,228,245,56

So the first thing we need to know is what processor these numbers are coded for.  I will give that to you.  It is for an Intel 8051 microcontroller.

Secondly based on the list as given we know that the list begins with the number 5 and that the first number is always an instruction.

So what do we do now?  Well here is where we need a little help from the processor's manual.  The manual will tell us what each instruction code means and how many numbers worth of data are used with it.  Let’s begin.

For now I will skip over the data bytes

5 = "inc direct", and it uses one byte of data, so skip the 56 in the list

229 = "mov a, direct", and it uses one byte of data, so skip the 56

211 = "setb c", and it uses no data

148 = "subb a, immediate", and it uses one byte of data, so skip the 15

64 = "jc relative", and it uses one byte of data, so skip the 5

228 = "clr a", and it uses no data

245 = "mov direct, a", and it uses one byte of data

Clear as mud I am sure.

Now we need to go one step deeper, what does “direct”, “immediate” and “relative” mean?

In order to understand these concepts we need to discuss addressing.

Let’s begin with memory.  We know that all computers have some amount of memory.  My microcontroller has 64KB of memory, but your computer at home probably has something more like 1GB of memory (refresher, 1GB = 1024 MB = 1024 KB = 1024 bytes). 

Now each byte of memory contains a number between 0 and 255.  So your computer at home with 1GB of memory contains 1,073,741,824 of these numbers.  That is a lot of numbers, so how do we find any particular number among all of those possibilities.

The answer is through its address.  Every single one of those numbers has a unique address (which is yet another number) through which we can retrieve the number.

So this is how a computer works.  You turn on the power and the processor reads from a known starting address (most likely 0), and interprets that number as an instruction.  Based on this instruction the next address may contain either data or the next instruction.  The computer continually keeps incrementing through each address, doing what the instructions ask it to do and manipulating the data it is given.

So back to the terms “direct”, “immediate” and “relative”.

Immediate is the easiest.  It simply means that the next number in the list is just a number and that the computer should do something with this number.

Direct means the next number in the list is an address.  The computer needs to read this address, then “dereference” the address to discover the number which resides at the memory location “pointed” to by the address and then do something with this number.

Finally relative means that the next number is a relative offset from the current address, and that the computer should interpret the next number as a signed number ranging from –128 through + 127.  The computer is then to compute a new address as follows:  current address = current address + relative offset.

So going back once more to our example

5,56 = inc 56 – increment the number at address 56

229,56 = mov a, 56 – move the number in memory at address 56 into the accumulator

211 = setb c – set the carry bit

148,15 = subb a, #15 – subtract 15 from the accumulator

64,5 = jc +5 – if the carry bit is set jump to address = current address + 5

228 = clr a – clear the accumulator / set to 0

245,56 = mov 56, a – move the accumulator into memory at address 56

Are we beginning to understand what is going on?

Almost, but there are just 2 more things you need to know, what is the accumulator and what is the carry bit.

The 8051 is referred to as an accumulator architecture.  In English this means that all operations on data has to occur in the accumulator (a special memory location within the processor).  That is why the code above copies data into the accumulator, does the subtraction on the accumulator and then copies the accumulator back out to memory. 

The 8051 is a fairly old processor (circa 1980) and this style of architecture was popular at that time.  More modern architectures are more efficient and reduce the amount of copying of data back and forth from memory.

The last new thing is the carry bit.  As its name implies the carry indicates if an addition caused a carry to occur.  Since each number can only range from 0-255 what happens if you add 2 numbers together and their sum is greater than 255.  Well the result would still only range from 0-255 but the carry bit would be set to indicate that a carry was done.

In our case we are doing a subtraction, so in opposite fashion we set the carry bit before the subtraction.  Then the carry bit will be cleared if the value is greater than 0.  If the result goes below 0 then the carry bit will remain set.

Different instructions can then make use of this bit and do different things.  In our case we skip the following instruction if the subtraction returned a positive result.

So now in summary this list of numbers:

5,56,229,56,211,148,15,64,5,228,245,56

can be interpreted to mean

inc 56 / mov a, 56 / setb c / subb a, #15 / jc +5 / clr a / mov 56, a

Which means – increment a value in memory, check to see if it is greater than 15, and if so reset the value back to 0.

Wasn’t that interesting!

Now for full disclosure, I didn’t write this code in machine language.  Very little code is done in machine language.  More often a language more akin to English is used and then a computer translates it into machine language.

For your pleasure here is the original source code that was translated into machine code for our example.

#define FIRST_LED_CODE   (0)

#define LAST_LED_CODE   (15)

led_code = led_code + 1;

if (led_code > LAST_LED_CODE)
{
    led_code = FIRST_LED_CODE;
}

A much nicer representation from a human perspective.  But as far as the computer is concerned it only ever knows:

5,56,229,56,211,148,15,64,5,228,245,56

No comments: