- Building Machines In Code
- Building Machines in Code – Part 2
- Building Machines In Code – Part 3
- Building Machines In Code – Part 4
- Building Machines In Code – Part 5
- Building Machines In Code – Part 6
- Building Machines In Code – Part 7
- Building Machines In Code – Part 8
- Building Machines In Code – Part 9
Post Stastics
- This post has 1147 words.
- Estimated read time is 5.46 minute(s).
Programming the Tiny-P
Programmers are often confused by the terms machine language and assembly language. Many developers consider these two terms interchangeable. But in fact, they mean very different things.
If you go back to the Tiny-P Opcode table presented in part-3 of this series, you can see how the various machine code values are created:
The Tiny-P CPU has a very simple instruction set shown in the table below:
OPCODE | INSTRUCTION MNEMONIC | DESCRIPTION |
0 | NOP | No Operation |
1 | LDA 10 | Load the accumulator with the value stored in memory location 10 |
2 | STA 12 | Store the value of the accumulator in memory location 12 |
3 | AND 10 | AND the contents of Acc (accumulator) with the value stored in memory location 10. Then place the results back into ACC. |
4 | OR 12 | AND the contents of Acc (accumulator) with the value stored in memory location 10. Then place the results in Acc. |
5 | NOT | Perform a logical NOT (Invert) on Acc and store the results in Acc |
6 | ADD 10 | Add the value stored in memory location 10 to Acc and store the result in ACC. |
7 | SUB 12 | Subtract the value stored in memory location 12 and store the result in ACC. |
8 | BRZ 5 | Branch to program location 5 if the Zero flag is set. |
9 | BRP 7 | Branch to program location 7 if the value in ACC is zero or positive. |
Recall our ROM values are made up of two parts. The Opcode and the operand. The operand is the memory address that we wish to load or store data to/from. These numerical values are our machine code. Now, if we had to read and write machine code all day it would be very tedious, and remembering all the instruction values for a CPU with hundreds of instructions would quickly become impossible. To make developing programs much easier, developers came up with assembly languages.
Looking back at our Opcode table, the instruction mnemonic column contains simple names for each opcode. The CPU doesn’t really care what mnemonic we assign to the opcode as long as it gets translated into the correct opcode. The CPU works only on the machine values, not the mnemonics. However, writing a program using mnemonics is much easier for humans.
Let’s write a small program to add two numbers. The first number is stored in data memory location 0, and the second number is stored in data-memory location (address) 1. We need to retrieve these values, add them together, and store the result into data memory address 2. Here’s the program in machine code:
ROM Address | Machine Code | Description |
00 | 100 | Load ACC with the value found in data memory address 0 |
01 | 601 | Add the value in ACC to the value found in data memory (RAM) address 1. |
02 | 202 | Store the value in ACC to RAM address 2. |
If you programmed the ROM with the values found in the Machine Code column and placed some data values in RAM at addresses 0 and 1, you would find this program adds those values together and stores the result into RAM address 2. A more truthful representation of our machine code program would be as follows:
100, 601, 202
Seeing this, however, you would have no idea what it represented! A more meaningful mnemonic representation might be this:
# Tiny-P Assembly program to add two numbers # Assumes on entry value 1 has already stored in RAM[0] # and value 2 has already been placed in RAM[1]. # Results will be saved in RAM[2]. LDA 0 # Load ACC with value stored in RAM[0] ADD 1 # Add ACC to RAM[1] STA 2 # Save ACC to RAM[2]
You must admit, this is a much simpler program to read and write. Now let’s see how we can translate the assembly language to machine code.
First, we will write our program using the mnmemonics presented in the OPcode table above. We will call this our assembly language program for now. Then to translate, we begin by writing down the current address our program is at. Since all our instructions take three digits and our ROM cells can hold three digits in a single address, we will need to increment our address count on each instruction. Next, we look up the opcode of the mnemonic and add the operand value to it. We place the opcode + operand value to the right of the address, leaving them separated by a single space to make it easier for us humans to read. If we follow this recipe, our program above looks like this:
00 100 01 601 02 202
I should note here that every processor out there has its own machine code and most processor families have their own mnemonics to represent them. This means that you learn machine or assembly programming for a particular processor and it is not a general language like C. Pascal or C#. With these languages, you can say you are a C# programmer and you’re done. If you program for the ARM in assembly you must say I am an Arm assembly programmer.
Ok, now for a few exercises.
Exercise 0:
Write a Tiny-P assembly program to subtract the number stored in RAM[1] from the number stored in RAM[0] and save the result into RAM[3]. Then translate that program into machine code as we did above.
Exercise 1:
Write a Tiny-P assembly program to multiply two single-digit numbers and stored the result in RAM[2].
Exercise 2:
Write a Tiny-P assembly program to logically AND two single-digit numbers stored the result in RAM[9].
Exercise 3:
Write a Tiny-P assembly program to logically OR two single-digit numbers stored the result in RAM[6].
Exercise 4:
Write a Tiny-P assembly program to logically NOT (Invert) a single-digit number and store the result in RAM[10].
Exercise 5:
Write a Tiny-P assembly program to Add two single-digit numbers stored in RAM[0] and RAM[1]. Then stored the result in RAM[0]. Branch back to program address 0 using a BRP instruction. Follow the BRP instruction with a BRZ to the location ROM of the BRZ instruction. This creates an inifinte loop at this instruction, effectively pausing the program. Single-step through this program using the CPU’s step() method.
Exercise 6:
Write a Tiny-P assembly program to AND two single digit numbers stored in RAM[0] and RAM[1]. Stored the result into RAM[1]. Then branch back to ROM address 0 using the BRP instruction. Follow this instruction with a BRZ instruction that branches back to itself. Single-step through this program using the CPU’s step() method.
Conclusion
Ok, that’s enough exercise. In our next installment, we will look at writing a simple tool to make programming the Tiny-P much simpler. Don’t think, however, that the Tiny-P is the end of our CPU emulation. We will continue to develop more simulators and emulators of greater and greater complexity. However, while the CPU is still small and simple I want us to get some practice writing tools to aid our development and to build a deeper understanding of how all these pieces fit together.
Until next time, Happy Coding!
Resources
The code for this installment can be found at: https://github.com/Monotoba/Building-Machines-In-Code