Programming

Programming is the discipline of specifying computations as sequences of instructions that a processor can execute — from machine code to high-level languages, the principles remain consistent across all levels.

Why This Matters

Programming transforms hardware into tools. A processor without programs is an expensive collection of transistors that does nothing useful. A processor with well-written programs solves problems: calculating trajectories, controlling machinery, storing records, communicating over networks.

Understanding programming from first principles — not just syntax of a particular language but the underlying concepts — means being able to work at any level of abstraction and to build new tools when existing ones are unavailable. A civilization rebuilder who understands programming fundamentals can write programs in assembly, implement a BASIC interpreter, and eventually build a compiler for a higher-level language, climbing the abstraction ladder from hardware upward.

Programming concepts are also widely applicable beyond computers: the patterns of iteration, recursion, data structures, and algorithms appear in engineering, biology, economics, and management.

Variables, Control Flow, and Functions

Every program manipulates values stored in memory locations. A variable is a named memory location; the name is resolved by the assembler or compiler to an address. In assembly, variables are simply labels pointing to memory addresses; in higher-level languages, the compiler tracks addresses automatically.

Control flow determines the order of execution. Without control flow, a program executes linearly — instruction 1, then 2, then 3. Three control structures are sufficient for any computation:

Sequence: default — execute instructions in order.

Selection (if/else): execute different instructions based on a condition.

    CMP R0, #0      ; compare R0 to zero
    JNZ NONZERO     ; if not zero, jump to NONZERO
    ; else (zero) case:
    LOAD R1, ZERO_VAL
    JMP DONE
NONZERO:
    LOAD R1, NONZERO_VAL
DONE:
    ...

Iteration (loop): repeat instructions while a condition holds.

    LOAD R0, #10    ; counter = 10
LOOP:
    ; body of loop here
    DEC R0          ; counter--
    JNZ LOOP        ; if counter != 0, repeat

Functions (subroutines): named, reusable sequences. The CALL instruction pushes the return address on the stack and jumps to the function; RET pops the address and returns. Functions that call themselves (recursion) are possible when the stack is used correctly for local state.

Data Structures in Memory

Programs organize data in memory using structures that facilitate access patterns:

Array: sequential storage of equal-size elements. Access element i at base address B with element size S: address = B + i*S. Indexed addressing modes make array access efficient: LOAD R0, ARRAY_BASE(R1) where R1 holds the index.

Stack: last-in, first-out storage. Push adds an element, pop removes the most recent. Implemented in contiguous memory with a stack pointer (SP) register. SP grows downward (toward lower addresses) in most architectures. Push: SP—; memory[SP] = value. Pop: value = memory[SP]; SP++.

Linked list: each element contains data and a pointer to the next element. Allows insertion and deletion anywhere in constant time, but random access requires traversal from the beginning. Requires dynamic memory allocation (malloc equivalent) to be practical.

String: array of characters terminated by a null byte (ASCII 0) or length-prefixed. String operations — concatenation, comparison, search — are fundamental but must be implemented explicitly in assembly.

For initial programs on a hand-built computer, arrays and stacks are sufficient. Linked lists and trees require more sophisticated memory management.

Algorithms and Complexity

An algorithm is a finite sequence of steps that solves a defined problem. Multiple algorithms may solve the same problem with different efficiency.

Sorting: arranging elements in order. Simple algorithms:

  • Selection sort: find the minimum, swap to position 0; find next minimum, swap to position 1; repeat. O(N²) comparisons — slow for large N but simple to implement.
  • Bubble sort: repeatedly compare adjacent pairs, swap if out of order. O(N²) worst case, O(N) best case. Simple but rarely optimal.
  • Binary search: search a sorted array by repeatedly halving the search space. O(log N) comparisons vs. O(N) for linear search.

O-notation describes how algorithm cost scales with input size N:

  • O(1): constant time (array index lookup)
  • O(log N): logarithmic (binary search)
  • O(N): linear (sequential search)
  • O(N log N): linearithmic (optimal sorting)
  • O(N²): quadratic (simple sorting, nested loops)

For small N (< 100 elements), even O(N²) algorithms run in milliseconds. As N grows, algorithm choice dominates performance.

Writing Reliable Programs

Top-down decomposition: start with the overall goal, break into sub-problems, implement each sub-problem separately. Assemble the pieces. This prevents being overwhelmed by complexity.

Defensive programming: check assumptions explicitly. Before using a pointer, verify it is not null. Before array access, verify the index is in bounds. At the assembly level, this means testing values and branching to error handlers rather than blindly trusting inputs.

Test incrementally: write a function, test it completely before proceeding. In assembly, testing a subroutine means calling it with known inputs (via front-panel switch entry or a boot ROM test sequence) and verifying outputs match expectations.

Document intent, not mechanics: comments should explain why, not what. ; multiply input by 7 is more useful than ; shift left 3, subtract original.

Keep state minimal: global variables and shared state are sources of bugs. Pass data through function arguments and return values where possible; limit what each function modifies.

The journey from “I understand gates” to “I wrote a working program” is the journey of computing. Each layer — gates, flip-flops, CPU, machine code, assembly, high-level language — builds on the last. No layer is magic; all are understandable engineering.