Principles of Programming

GCSE — Unit 1: Understanding Computer Science

Characteristics of high-level and low-level languages

Programming languages are divided into two broad categories: high-level and low-level. The level refers to how close the language is to human language (high) or machine code (low).

High-Level Languages

High-level languages are designed to be easy for humans to read and write. They use English-like keywords and mathematical notation.

  • Each statement typically carries out multiple low-level operations
  • Code is portable — the same source code can run on different types of hardware with the appropriate translator
  • Must be translated into machine code before the CPU can execute it, using a compiler or interpreter
  • Examples include Python, Java, C#, Visual Basic and JavaScript
total = 0
for i in range(10):
    total = total + i
print(total)

High-level language — a programming language that uses English-like syntax and abstracts away hardware details. It must be translated into machine code before execution.

Low-Level Languages

Low-level languages are much closer to the binary instructions that the CPU directly understands. There are two types:

Machine Code

  • The only language the CPU can execute directly — no translation needed
  • Written entirely in binary (1s and 0s)
  • Extremely difficult for humans to read, write and debug
  • Specific to a particular processor architecture (not portable)

Assembly Language

  • Uses short mnemonics (e.g. LDA, ADD, STO, OUT) to represent machine code instructions
  • Each mnemonic corresponds to one machine code instruction
  • Must be translated into machine code by an assembler
  • Still specific to a particular processor architecture
LDA 5
ADD 6
STO 7
OUT

Assembly language — a low-level language that uses short mnemonic codes to represent individual machine code instructions. It requires an assembler to translate it into executable machine code.

Comparison Table

Feature High-Level Language Low-Level Language
Readability Easy to read and write Difficult to read and write
Portability Portable across platforms Tied to specific processor
Translation Needs compiler or interpreter Needs assembler (or none for machine code)
Speed of execution Generally slower (translation overhead) Very fast (close to or is machine code)
Lines of code Fewer lines needed Many more lines needed
Debugging Easier to find and fix errors Much harder to debug
Hardware access Limited direct hardware control Direct access to hardware and memory
Abstraction High — hides hardware details Low — programmer manages hardware details

Remember that “low-level” does not mean low quality. It means the language is close to the machine. Low-level languages are extremely powerful for tasks that need direct hardware control.


Situations requiring high-level or low-level language

Different programming tasks call for different types of language. The choice depends on what the software needs to achieve.

When to Use a High-Level Language

High-level languages are the best choice for most software development because they are faster to write, easier to maintain, and portable.

Situation Why High-Level is Suitable
Developing desktop applications (e.g. word processor, spreadsheet) Large, complex programs benefit from readable, maintainable code
Web development (e.g. websites, web apps) Languages like JavaScript and Python are designed for rapid development
Mobile apps Frameworks in high-level languages enable cross-platform development
School/educational projects Easier syntax allows students to focus on problem-solving rather than hardware details
Rapid prototyping Quicker to write and test ideas before committing to a full build
Working in a team Readable code is easier for multiple programmers to understand and collaborate on

When to Use a Low-Level Language

Low-level languages are used when performance, hardware control or resource efficiency is critical.

Situation Why Low-Level is Suitable
Writing device drivers Drivers need to communicate directly with specific hardware components
Programming embedded systems Embedded devices (e.g. washing machine controllers) have very limited memory and processing power
Operating system development OS kernels need direct access to CPU registers, memory and hardware
Real-time systems (e.g. aircraft control, medical devices) Execution speed and predictable timing are critical for safety
Game engine optimisation Performance-critical sections benefit from fine-tuned low-level code
Security-sensitive code (e.g. encryption routines) Precise control over memory and execution prevents vulnerabilities

Exam questions often present a scenario and ask you to recommend a high-level or low-level language. Always justify your choice by linking the language characteristics to the requirements of the scenario. For example: “A low-level language is appropriate for programming the embedded system in a heart rate monitor because it needs to run on a processor with very limited memory and must respond in real time.”

Key Decision Factors

When deciding which type of language to use, consider these questions:

  • Does the program need direct access to hardware? If yes, use a low-level language.
  • Is memory extremely limited? If yes, low-level gives finer control over memory usage.
  • Does the program need to run on multiple platforms? If yes, a high-level language offers portability.
  • Is development speed important? If yes, high-level languages are much faster to code in.
  • Will the code need to be maintained or updated by others? If yes, high-level code is easier to read and modify.
  • Is execution speed absolutely critical? If yes, low-level code runs faster because it is closer to machine code.

Portability — the ability of source code to be run on different types of hardware or operating systems without modification. High-level languages are portable; low-level languages are not.


Little Man Computer (LMC)

The Little Man Computer (LMC) is a simplified model of a computer, used to teach the fundamental concepts of how a CPU executes machine code instructions. It was designed as an educational tool to demonstrate the fetch-decode-execute cycle, assembly language, and how data moves between memory and registers.

The Little Man Computer (LMC) is an instructional model of a simple computer. It has a limited instruction set, a single accumulator register, an input tray, an output tray, and 100 mailboxes (memory locations numbered 00–99).

In the LMC model, a “little man” inside the computer reads instructions from mailboxes one at a time, performs the operation, and moves to the next instruction. This mirrors how a real CPU fetches, decodes, and executes instructions.

LMC Instruction Set

The LMC uses mnemonics (short codes) to represent instructions, just like real assembly language. Each instruction has a three-digit numeric machine code equivalent.

Mnemonic Numeric Code Instruction Description
INP 901 Input Takes a value from the input tray and stores it in the accumulator
OUT 902 Output Outputs the value in the accumulator to the output tray
STA 3xx Store Stores the value in the accumulator into mailbox address xx
LDA 5xx Load Loads the value from mailbox address xx into the accumulator
ADD 1xx Add Adds the value in mailbox address xx to the accumulator
SUB 2xx Subtract Subtracts the value in mailbox address xx from the accumulator
BRA 6xx Branch always Unconditionally jumps to the instruction at address xx
BRZ 7xx Branch if zero Jumps to address xx only if the accumulator value is zero
BRP 8xx Branch if positive Jumps to address xx only if the accumulator value is zero or positive
HLT 000 Halt Stops the program
DAT Data Defines a named memory location for storing data (used as a label)

You must memorise all the LMC mnemonics and what they do. In the exam, you may be asked to trace an LMC program (showing the accumulator value and outputs at each step), write an LMC program for a given task, or explain what an existing LMC program does.

Worked Example: Add Two Numbers

Task: Take two numbers as input, add them together, and output the result.

        INP         // Read first number into accumulator
        STA FIRST   // Store it in mailbox labelled FIRST
        INP         // Read second number into accumulator
        ADD FIRST   // Add the value stored in FIRST to the accumulator
        OUT         // Output the result
        HLT         // Stop the program
FIRST   DAT         // Reserve a mailbox labelled FIRST

Step-by-step trace (inputs: 25, 17):

Step Instruction Accumulator Output Notes
1 INP 25 User inputs 25
2 STA FIRST 25 Stores 25 in FIRST
3 INP 17 User inputs 17
4 ADD FIRST 42 17 + 25 = 42
5 OUT 42 42 Outputs 42
6 HLT 42 Program stops

Worked Example: Subtract Two Numbers and Output

Task: Input two numbers, subtract the second from the first, and output the result.

        INP         // Read first number into accumulator
        STA FIRST   // Store it in FIRST
        INP         // Read second number into accumulator
        STA SECOND  // Store it in SECOND
        LDA FIRST   // Load FIRST back into accumulator
        SUB SECOND  // Subtract SECOND from accumulator
        OUT         // Output the result
        HLT         // Stop the program
FIRST   DAT         // Reserve mailbox for first number
SECOND  DAT         // Reserve mailbox for second number

Worked Example: Countdown from Input to Zero

Task: Input a number, output a countdown from that number to zero.

        INP         // Read the starting number
LOOP    OUT         // Output the current value
        STA COUNT   // Store current value
        SUB ONE     // Subtract 1
        BRP LOOP    // If result >= 0, branch back to LOOP
        HLT         // Stop when negative
ONE     DAT 1       // Constant value 1
COUNT   DAT         // Storage for current count

When writing LMC programs, always put your DAT labels at the end of the program. If you place them in the middle, the CPU will try to execute them as instructions and the program will not work correctly. Remember that BRZ and BRP are used to create loops and conditional behaviour.