{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "c743bf15-cd14-46b9-adde-68728bad574e", "metadata": { "jupyter": { "source_hidden": true }, "tags": [ "remove-cell" ] }, "outputs": [], "source": [ "%run -i ../python/common.py" ] }, { "cell_type": "markdown", "id": "428d1fea-2fdb-4459-8d24-6e635c7c30f5", "metadata": { "hide_input": true, "tags": [] }, "source": [ "# Introduction: Assembly Programming\n", " " ] }, { "cell_type": "markdown", "id": "6ed1bc1d-7aec-4b22-946d-9e9594445653", "metadata": { "hide_input": true, "tags": [] }, "source": [ "Assembly Programming means writing code in the language that the hardware of the computer understands -- or at least very close to it. \n", "Most programmers are completely unaware of how the computer really works, as the languages that they program in are not directly supported by the hardware. Rather, most languages, such as JavaScript, Java, Python, Ruby, Rust, C#, C++, and even C must be translated into the native code that the computer does understand -- machine code. \n", "\n", " \n", "## Machine Code\n", " \n", "The hardware of the computer is built out of electrical components that can store and interpret patterns of bits, eg. binary digits. A single bit can be thought of as a switch that can be in two positions \"ON\" -- 1 and \"OFF\" -- 0. A group of eight bits forms a byte. We will say a lot more about bytes a little later. The main thing to observe is that a byte is easily expressed as an 8 digit binary, (base 2), number. Given that there are 8 bits, a byte can take on $2^8=256$ \n", "unique values ranging from $00000000$ to $11111111$.\n", " \n", "Machine code is a binary code -- meaning that binary values are used to encode the operations and values that make up a program. The following table lists twenty of the machine codes of a [MOS 6502 Centeral Processing Unit (CPU)](https://en.wikipedia.org/wiki/MOS_Technology_6502) which has a very simple machine code." ] }, { "cell_type": "markdown", "id": "c0a58729-ec37-47d7-948c-10e12efbb761", "metadata": { "hide_input": true, "tags": [ "remove-input" ] }, "source": [ "\n", "|Binary Value| 6502 Operation|\n", "|------------|---------------|\n", "| $00000000$ | interrupt - impl: Implied i |\n", "| $00000001$ | or with accumulator - X,ind: Zero Page Indexed Indirect (zp,x) |\n", "| $00000101$ | or with accumulator - zpg: Zero Page zp |\n", "| $00000110$ | arithmetic shift left - zpg: Zero Page zp |\n", "| $00001000$ | push processor status (SR) - impl: Implied i |\n", "| $00001001$ | or with accumulator - #: Immediate # |\n", "| $00001010$ | arithmetic shift left - A: Accumulator A |\n", "| $00001101$ | or with accumulator - abs: Absolute a |\n", "| $00001110$ | arithmetic shift left - abs: Absolute a |\n", "| $00010000$ | branch on plus (negative clear) - rel: Program Counter Relative r |\n", "| $00010001$ | or with accumulator - ind,Y: Zero Page Indirect Indexed with Y (zp),y |\n", "| $00010101$ | or with accumulator - zpg,X: Zero Page Index with X |\n", "| $00010110$ | arithmetic shift left - zpg,X: Zero Page Index with X |\n", "| $00011000$ | clear carry - impl: Implied i |\n", "| $00011001$ | or with accumulator - abs,Y: Absolute Indexed with Y a,y |\n", "| $00011101$ | or with accumulator - abs,X: Absolute Indexed with X a,x |\n", "| $00011110$ | arithmetic shift left - abs,X: Absolute Indexed with X a,x |\n", "| $00100000$ | jump subroutine - abs: Absolute a |\n", "| $00100001$ | and (with accumulator) - X,ind: Zero Page Indexed Indirect (zp,x) |\n", "| $00100100$ | bit test - zpg: Zero Page zp |\n", "\n", "
The 6502 was designed and first built in 1975, but continues to be used today. It has 151 simple operations - versus the thousands of complex operations supported by a modern [Intel X86-64 processor](https://en.wikipedia.org/wiki/X86-64), which is widely used in computers ranging from laptops to supercomputers. The following article discusses how to calculate the number of operations an Intel x86-64 processor supports and why it is hard to do so; [\"Enumerating x86-64 – It’s Not as Easy as Counting\"]([https://www.unomaha.edu/college-of-information-science-and-technology/research-labs/_files/enumerating-x86-64-instructions.pdf). Given the simplicity of the 6502 we will often use it to first get our heads around an idea or mechanism, before looking at the same thing on an Intel X86-64 based computer or an [ARM Arch64](https://en.wikipedia.org/wiki/AArch64) based computer. \n",
" \n",
"A program written directly in machine code is a sequence of binary values. The following is a small 6502 machine code program that calculates $1+1$:\n",
"```\n",
"00011000, 10101001, 00000001, 01101001, 00000001\n",
"```\n",
"To execute this small program you need to place these values in the memory of the computer and properly configure the CPU so that they can be found and executed. \n",
"\n",
">
In the [von Neumann Architecture](./vonNeumannArchitecure.inpyb) chapter we will use the book's companion 6502 based computer, [SOL6502](http://jappavoo.github.io/SOL6502), to load and execute this machine code program by hand. \n", "\n", " \n", "## Assembly Code\n", "Assembly code is a slightly more generic human friendly code that we can use to program a computer. Machine code must be expressed purely in numbers, but assembly code uses structured text that can be easily translated by a set of tools into the equivalent machine code. Each group of machine operations of the CPU that does the same function is assigned a human text **mnemonic**, often referred to as an **instruction**. For example the following are the 8 different 6502 machine code operations that add two numbers:" ] }, { "cell_type": "markdown", "id": "2c1bdd8a-51ab-46c9-99fd-410b5f9523f5", "metadata": { "hide_input": true, "tags": [ "remove-input" ] }, "source": [ "\n", "|Mnemonic|Binary Value|6502 Operation|\n", "|--------|------------|--------------|\n", "| ADC | $01100001$ | add with carry - X,ind: Zero Page Indexed Indirect (zp,x) |\n", "| ADC | $01100101$ | add with carry - zpg: Zero Page zp |\n", "| ADC | $01101001$ | add with carry - #: Immediate # |\n", "| ADC | $01101101$ | add with carry - abs: Absolute a |\n", "| ADC | $01110001$ | add with carry - ind,Y: Zero Page Indirect Indexed with Y (zp),y |\n", "| ADC | $01110101$ | add with carry - zpg,X: Zero Page Index with X |\n", "| ADC | $01111001$ | add with carry - abs,Y: Absolute Indexed with Y a,y |\n", "| ADC | $01111101$ | add with carry - abs,X: Absolute Indexed with X a,x |\n", "\n", "
The jump from machine code to assembly code illustrates an important pattern that we will see applied over and over again -- Program Translation. Rather than needing to understand and remember of all the details of machine code, a programmer can simply learn the assembly language and then rely on the assembler to translate it correctly into machine code. The assembler is a program whose job is to do this translation -- in some sense it acts like a machine code programmer that we give an assembly code version of our program. In a similar manner, we can design a new programming language and write a translation program written in assembly language that translates the new language into assembly language, and then use the assembler to translate the result into machine code. A programmer that learns our new language need never know about assembly or machine language! \n" ] } ], "metadata": { "hide_input": false, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.5" } }, "nbformat": 4, "nbformat_minor": 5 }