Lesson 01-Fundamentals of Computer Architecture and Number System Conversion

Introduction: Why You Need to Understand the Machine

Let me tell you something that might ruffle a few feathers in the computer science education community: most computer science graduates today have no idea how a computer actually works. They can write Python scripts, they understand object-oriented programming, they can spin up a Docker container and deploy a microservice to the cloud. But if you asked them to explain what happens when a CPU executes a single instruction, or why 0xFFFFFFFE represents -2 in a 32-bit signed integer, you’d get a lot of blank stares and nervous laughter.

This isn’t entirely their fault. The industry has collectively decided that abstraction is king. We’ve built layer upon layer of abstraction — high-level languages, virtual machines, containers, orchestration platforms — all designed to isolate programmers from the cold, harsh reality of what’s actually happening on the silicon. And for 95% of software development, that’s fine. You don’t need to understand cache line alignment to write a CRUD application in JavaScript.

But here’s the thing: if you want to understand computing deeply — if you want to be the person who can debug a segfault in a C program, who can reverse engineer malware, who can optimize critical code paths to within an inch of their lives, who can understand why your “simple” Java program is consuming 4GB of RAM just to print “Hello World” — you need to understand assembly language. And to understand assembly language, you need to understand computer architecture and number systems.

This article is the foundation. We’re going to strip away every layer of abstraction and look at the machine itself. We’re going to understand how data is represented, how the CPU processes it, and why the decisions made by engineers in the 1940s and 1950s still dictate how your multi-billion-transistor modern processor works today.

Grab a coffee. This is going to be a deep dive.

What is Computer Architecture? Von Neumann vs Harvard Architectures

The Fundamental Question

Computer architecture, at its core, is about answering a deceptively simple question: how do you build a machine that can compute anything?

In the early days of computing (we’re talking the 1940s here), computers weren’t general-purpose machines. They were purpose-built calculators. If you wanted to compute artillery firing tables, you built a machine that computed artillery firing tables. If you wanted to break Enigma codes, you built a machine that broke Enigma codes. Switching from one task to another meant physically rewiring the machine. Imagine having to rewire your laptop every time you wanted to switch from browsing the web to playing a game. That was computing in the 1940s.

The breakthrough — and I’m not exaggerating when I call it one of the most important breakthroughs in human history — was the realization that you could store the program itself in memory, right alongside the data. This is the stored-program computer, and it’s the foundation of every computing device you’ve ever used.

The Von Neumann Architecture

John von Neumann (yes, that von Neumann — the guy was a legitimate genius who made contributions to mathematics, physics, economics, and computer science before dying at age 53) described this architecture in a 1945 paper. The Von Neumann architecture has five main components:

  1. Central Processing Unit (CPU): The brain of the operation
  2. Memory: Stores both data AND instructions (this is the key insight)
  3. Input/Output (I/O): How the computer talks to the outside world
  4. System Bus: The connections that tie everything together
  5. Control Unit: Orchestrates the whole operation

Here’s the critical characteristic of Von Neumann architecture: the CPU uses the same bus and memory to handle both instructions and data. There’s one memory space, and it contains a mix of program code and data. The CPU fetches an instruction from memory, decodes it, executes it (possibly reading or writing data from/to memory in the process), and then moves on to the next instruction.

graph TB
    subgraph "Von Neumann Architecture"
        CPU[Central Processing Unit]
        MEM[Memory<br/>Instructions + Data Mixed]
        IO[Input/Output Devices]
        BUS[System Bus<br/>Shared by Instructions and Data]
        
        CPU <--> BUS
        MEM <--> BUS
        IO <--> BUS
    end
    
    style CPU fill:#e1f5fe
    style MEM fill:#fff3e0
    style BUS fill:#fce4ec

This design is elegant in its simplicity. You don’t need separate memory systems for code and data. You can write a program that modifies itself (though this is generally a terrible idea for reasons we’ll discuss later). You can treat code as data, which is how compilers and JIT compilers work.

But Von Neumann architecture has a fundamental limitation, known as the Von Neumann bottleneck. Since instructions and data share the same bus, the CPU can either fetch an instruction OR read/write data at any given time — not both simultaneously. When the CPU is fetching instructions, it can’t access data, and vice versa. This serialization is a major performance limiter.

Modern CPUs mitigate this with techniques like:

  • Separate instruction and data caches (L1i and L1d caches)
  • Branch prediction to guess which instructions will be needed next
  • Out-of-order execution to keep the pipeline full
  • Prefetching to load instructions and data before they’re needed

But at the conceptual level, all modern general-purpose computers (your x86, ARM, RISC-V processors) are Von Neumann machines.

The Harvard Architecture

The Harvard architecture, named after the Harvard Mark I computer (built in 1944), takes a different approach. In Harvard architecture, instruction memory and data memory are physically separate. There are separate buses for instructions and data, which means the CPU can fetch an instruction and read/write data simultaneously.

graph TB
    subgraph "Harvard Architecture"
        CPU[Central Processing Unit]
        IMEM[Instruction Memory<br/>Only Instructions]
        DMEM[Data Memory<br/>Only Data]
        IO[Input/Output Devices]
        IBUS[Instruction Bus]
        DBUS[Data Bus]
        
        CPU <--> IBUS
        CPU <--> DBUS
        IMEM <--> IBUS
        DMEM <--> DBUS
        IO <--> DBUS
    end
    
    style CPU fill:#e1f5fe
    style IMEM fill:#e8f5e9
    style DMEM fill:#fff3e0
    style IBUS fill:#fce4ec
    style DBUS fill:#f3e5f5

The advantages are clear:

  • Parallelism: Can fetch instruction and access data at the same time
  • Different word widths: Instructions and data can have different bit widths
  • Security: Harder to execute data as code (no self-modifying code)

The disadvantages are also clear:

  • Complexity: Requires separate memory systems
  • Inflexibility: Can’t treat code as data easily
  • Cost: More hardware

So where is Harvard architecture used today? Almost exclusively in digital signal processors (DSPs) and microcontrollers. If you’ve ever programmed an Arduino (which uses an AVR microcontroller), you’ve worked with a Harvard architecture machine. The AVR has separate flash memory for program code and SRAM for data.

Most modern processors actually use a modified Harvard architecture. They have separate L1 caches for instructions and data (giving the parallelism benefits), but a unified L2 and L3 cache, and a single main memory (giving the flexibility benefits of Von Neumann). It’s the architectural equivalent of having your cake and eating it too.

Why This Matters for Assembly Programmers

You might be wondering: “This is all very interesting from a computer architecture history perspective, but why do I need to know this to write assembly language?”

Fair question. Here’s why:

  1. Self-modifying code: In pure Von Neumann architecture, you can write code that modifies itself. This was once a common technique (and a common source of bugs and security vulnerabilities). Understanding the architecture helps you understand why this was possible and why modern systems make it difficult.
  2. Cache behavior: Understanding the separation between instruction and data caches (a Harvard-like modification) helps you understand cache timing effects, which can be critical for performance optimization and is sometimes exploited in side-channel attacks.
  3. Harvard microcontrollers: If you’re writing assembly for embedded systems (which is where a surprising amount of assembly programming still happens), you’re likely dealing with a Harvard architecture machine with separate code and data spaces.
  4. Mental model: When you’re staring at a segfault or a weird arithmetic error in your assembly code, having a correct mental model of how the machine actually works is the difference between randomly trying things until it works and systematically debugging the problem.

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here

Share your love