This topic explores how a computer performs basic operations using machine code and assembly language, including common instructions and how data is manipulated and moved.
What are machine code and assembly operations?
Machine code refers to the binary instructions that a computer's processor understands directly. These instructions are composed of opcodes (operation codes) and operands. The opcode specifies the operation to be performed (e.g. addition, branching), and the operand provides the data, or the address of the data, to be used.
While machine code is highly efficient for the computer to interpret, it is difficult for humans to read. To address this, programmers use assembly language, which is a low-level programming language that uses mnemonics to represent machine code instructions. For example, instead of writing 00010010, a programmer can write ADD R1, R2.
Assembly language provides a clear way for programmers to control a computer's hardware. It gives access to registers, memory locations, and allows manipulation of data at a very granular level. Each assembly language is processor-specific, meaning the instructions and formats depend on the design of the particular CPU.
Basic assembly operations
Practice Questions
FAQ
Bitwise operations are essential in low-level programming because they provide precise control over data at the bit level, which is critical for performance, memory efficiency, and hardware interaction. In embedded systems, resources are limited—memory and processing power are minimal compared to general-purpose computers. Bitwise operations allow manipulation of individual bits to control specific hardware features, configure registers, or set flags without using more expensive arithmetic operations. For instance, setting or clearing specific bits in control registers is often achieved using AND, OR, or XOR. These operations are also faster as they are typically executed in a single processor cycle. Bitwise logic is used in masks, where particular bits are isolated or modified, such as in interrupt control, communication protocols, or sensor configuration. Understanding how to apply AND, OR, XOR, NOT, and bit shifts gives developers fine-tuned access to hardware and allows them to write efficient, compact code that interacts directly with devices and system components.
Labels in assembly language act as named references to memory addresses or lines of code, making it easier to implement control flow such as loops, conditionals, or function jumps. Without labels, programmers would have to refer to absolute memory addresses, which is inefficient and hard to maintain. A label is defined by writing a name followed by a colon (e.g. LOOP:) before a line of code. Branching instructions then use these labels to jump to different parts of the program. For instance, B LOOP will jump to the instruction marked by LOOP:. In conditional branches, such as BEQ END, the program only jumps to the label END: if a specific condition (like zero flag being set) is met. Labels make assembly code more readable, modular, and easier to debug. They are essential for implementing iteration and decision-making logic, as they provide clear structure and flow within what would otherwise be a linear sequence of instructions.
The processor determines what operation to perform based on the opcode part of the machine code instruction. Each type of instruction, whether it's logical, arithmetic, shift, load, store, or control-related, is assigned a unique binary opcode that tells the processor how to interpret the instruction. When the instruction is fetched and decoded during the fetch-decode-execute cycle, the control unit interprets the opcode and directs the appropriate component (e.g. ALU for logical and shift operations) to execute the instruction. For example, a binary opcode might indicate an AND operation, while another might indicate an LSL. Even though both types of instructions might use similar operand formats (e.g. two registers and a destination), the binary opcode ensures that the processor applies the correct logic. This design keeps the instruction format consistent while enabling a wide range of operations. The distinction is critical in instruction decoding, where efficiency and precision are vital.
Yes, multiple instructions can and often do affect the same register. Registers are temporary storage locations, and their contents can be overwritten by any instruction that uses them as a destination. This means that if a value stored in a register is still needed later in the program, it must be copied to another register or stored in memory before it is overwritten. Overwriting register values unintentionally is a common source of bugs in assembly programming. For example, if R1 contains an important value and the next instruction performs ADD R1, R2, the original value in R1 is replaced with the sum, potentially leading to errors in later calculations. Careful management of register use is therefore essential. Programmers must plan register usage in advance, track which registers are holding what data, and make sure to preserve values when necessary, either by using additional registers or by pushing values to memory temporarily.
When a conditional branch is not taken, the processor simply proceeds to the next sequential instruction, but some time is still consumed processing the branch instruction itself. In modern processors, conditional branches can impact performance due to branch prediction and pipelining. Although in a simple CPU architecture like that typically studied in low-level assembly, the time cost is minimal, the instruction must still be fetched and decoded to check the condition. If the branch condition fails, the control unit allows the program counter (PC) to increment normally. However, if the processor is pipelined and a mispredicted branch occurs (i.e. it assumed the branch would be taken but it wasn’t), it may need to flush the pipeline and correct the instruction flow, which causes a performance penalty. Even in simple models, understanding that every instruction—including those not taken—uses processor cycles is crucial for optimising program execution and understanding how control flow is managed internally.
