Loading header...

Von Neumann, Harvard, and Modified Harvard Architecture

Processor architecture determines how a CPU fetches instructions, reads data, writes results, and reaches memory. Before writing embedded firmware, you should understand whether instructions and data share one memory path or use separate paths.

Learning Objectives

By the end of this lesson, you should be able to:

  • Explain Von Neumann, Harvard, Princeton, and modified Harvard architectures.
  • Describe the fetch-decode-execute cycle.
  • Explain the Von Neumann bottleneck.
  • Connect architecture choices to microcontroller Flash, SRAM, stack, constants, and peripheral registers.
  • Avoid common firmware mistakes caused by memory-space assumptions.

The Problem

A processor needs access to two kinds of information:

  1. Instructions: the program bytes that tell the CPU what to do.
  2. Data: variables, stack values, buffers, peripheral registers, and constants.

The architecture question is simple: do instructions and data use one shared memory path, or do they use separate paths?

Von Neumann Architecture

Von Neumann architecture stores instructions and data in the same memory space and accesses them through a shared bus. Princeton architecture is another name commonly used for the same idea.

flowchart TD subgraph CPU ["CPU"] CU[Control Unit] ALU[ALU] REG[Registers] CU <--> ALU ALU <--> REG CU <--> REG end BUS["Shared address and data bus"] subgraph MEM ["Unified Memory"] PROG[Program instructions] DATA[Data and variables] end CPU <--> BUS BUS <--> MEM

The strength is simplicity: one address space and one memory system. The weakness is contention: instruction fetches and data accesses compete for the same path.

Fetch-Decode-Execute

Most processors repeatedly run this cycle:

flowchart LR F["Fetch\nread instruction at PC"] --> D["Decode\nidentify operation and operands"] --> E["Execute\nALU, branch, load, store"] --> W["Write back\nupdate register or memory"] --> F

In a simple Von Neumann machine, a load instruction may require one memory transaction to fetch the instruction and another memory transaction to read the data.

sequenceDiagram participant CPU participant Bus participant Memory CPU->>Bus: Fetch instruction at PC Bus->>Memory: Read instruction address Memory-->>Bus: LOAD R1, [0x2000] Bus-->>CPU: Instruction CPU->>Bus: Read data at 0x2000 Bus->>Memory: Read data address Memory-->>Bus: Data value Bus-->>CPU: Data loaded into R1

This shared-path limit is called the Von Neumann bottleneck. Modern CPUs reduce it with caches, pipelines, prefetching, burst transfers, and out-of-order execution, but the bottleneck still explains why memory bandwidth is often a system limit.

Harvard Architecture

Harvard architecture separates instruction memory and data memory. Each has its own bus.

flowchart TD subgraph CPU ["CPU"] CU[Control Unit] ALU[ALU] REG[Registers] end IMEM["Instruction Memory\nprogram Flash or ROM"] DMEM["Data Memory\nSRAM"] IBUS["Instruction bus"] DBUS["Data bus"] CPU <-->|fetch| IBUS IBUS <--> IMEM CPU <-->|read/write| DBUS DBUS <--> DMEM

The CPU can fetch an instruction while reading or writing data.

sequenceDiagram participant CPU participant IBus as Instruction Bus participant DBus as Data Bus participant Flash as Program Flash participant SRAM as Data SRAM Note over CPU,SRAM: Same cycle in a Harvard-style core CPU->>IBus: Fetch next instruction IBus->>Flash: Read program address CPU->>DBus: Read data operand DBus->>SRAM: Read data address Flash-->>CPU: Instruction SRAM-->>CPU: Data

This is a natural fit for microcontrollers because program code normally lives in non-volatile Flash, while variables live in volatile SRAM.

Microcontroller Example

flowchart TD subgraph MCU ["Microcontroller"] CPU2["CPU core"] FLASH["Program Flash\nfirmware and constants"] SRAM["SRAM\nstack, heap, globals"] PER["Peripheral registers\nGPIO UART SPI ADC PWM"] BOOT["System ROM\nbootloader"] end CPU2 <-->|instruction fetch| FLASH CPU2 <-->|data read/write| SRAM CPU2 <-->|register access| PER CPU2 --> BOOT

Important embedded consequences:

  • Program code survives reset because it is stored in Flash.
  • RAM variables disappear after reset or power loss.
  • Stack and heap share limited SRAM.
  • Peripheral registers look like addresses, but reads and writes control hardware.
  • Some architectures require special instructions or compiler attributes to read constants from program memory.

AVR is a classic example where program memory and data memory are distinct enough that reading constants from Flash may require special handling such as PROGMEM and pgm_read_byte(). ARM Cortex-M devices generally present a more unified programmer's memory map, but internally still use separate buses for instruction and data paths.

Modified Harvard Architecture

Many high-performance processors use a hybrid. They may have separate instruction and data caches close to the CPU, but a unified main memory below the cache hierarchy.

flowchart TD CPU3[CPU Core] IC["L1 instruction cache"] DC["L1 data cache"] L2["Unified L2 or L3 cache"] RAM["Main DRAM"] CPU3 --> IC CPU3 --> DC IC --> L2 DC --> L2 L2 --> RAM

This gives parallel instruction and data access near the core while keeping the larger system simpler. It also introduces cache coherency and memory ordering issues that bare-metal microcontroller developers may not see on small parts.

Side-by-Side Comparison

Von Neumann or Princeton:

  • Instructions and data use unified memory.
  • The bus is shared.
  • The programming model is simple.
  • The main weakness is shared-bus contention.
  • Examples include simple CPUs and the desktop main-memory model.

Harvard:

  • Instruction and data memory are separate.
  • Instruction and data buses are separate.
  • Parallel fetch and data access are possible.
  • The memory model is more complex.
  • Examples include AVR, PIC, and many DSPs.

Modified Harvard:

  • Paths are separate near the CPU but unified lower in the hierarchy.
  • Split caches or buses feed shared memory.
  • Performance is high with practical memory cost.
  • Cache coherency and ordering require care.
  • Examples include ARM Cortex-A, modern x86, and many high-end MCUs.

Worked Example: Why Stack Size Matters

Assume a microcontroller has:

  • 128 KiB Flash.
  • 16 KiB SRAM.
  • A 2 KiB communication receive buffer.
  • A 4 KiB display frame buffer.
  • Global variables using 3 KiB.

Remaining SRAM before stack and heap:

$$
16 - 2 - 4 - 3 = 7 \text{ KiB}
$$

If an interrupt handler and a nested function chain require more than this, the stack can collide with heap or global data. On many bare-metal systems there is no operating system to stop the damage. The firmware may simply behave unpredictably.

Memory Map Awareness

Embedded C often hides architecture details, but the compiled program still has sections:

Section Typical Location Contains
.text Flash Instructions
.rodata Flash Constants and lookup tables
.data Flash image copied to SRAM at startup Initialized writable globals
.bss SRAM, zeroed at startup Uninitialized globals
Stack SRAM Function calls and local variables
Heap SRAM Dynamic allocation, if used

The startup code initializes these regions before main() runs. That is why embedded startup files, linker scripts, and memory maps matter.

Common Mistakes

  • Assuming const always costs no RAM on every compiler and architecture.
  • Allocating large local arrays on a tiny SRAM stack.
  • Forgetting that DMA may need buffers in a memory region visible to the peripheral.
  • Treating peripheral registers like normal variables; some bits clear on read or trigger actions on write.
  • Ignoring instruction/data cache maintenance when using DMA on cached processors.
  • Calling a system "Harvard" or "Von Neumann" without checking the actual programmer-visible memory model.

Practice

  1. Explain why a shared bus can slow a load-heavy program.
  2. Identify where firmware, stack, globals, and peripheral registers usually live in a microcontroller.
  3. A system has separate L1 instruction and data caches but shared DRAM. Classify it.
  4. Why can a power cycle erase a variable but not the firmware image?
  5. List two bugs that can happen when stack usage is not measured.

Summary

Von Neumann architecture uses one memory path for instructions and data. Harvard architecture separates instruction and data paths, which improves parallel access and maps naturally to microcontrollers with Flash and SRAM. Modified Harvard systems combine separate near-core paths with unified lower memory. For embedded developers, these ideas explain program memory, RAM limits, startup code, stack risk, constants, peripherals, and cache-related bugs.

Further Reading

  • John L. Hennessy and David A. Patterson, Computer Architecture: A Quantitative Approach.
  • ARM Cortex-M architecture reference manuals for memory map, bus, and exception behavior.
  • Atmel/Microchip AVR documentation for program memory and data memory examples.
  • Joseph Yiu, The Definitive Guide to ARM Cortex-M3 and Cortex-M4 Processors.
  • Linker script and startup-code chapters in embedded toolchain manuals for GCC and LLVM.

Mind Map

mindmap root((Processor Memory Architecture)) Core Instructions plus data CPU fetch decode execute Buses limit throughput Von Neumann Unified memory Shared bus Princeton same idea Bottleneck on memory path Harvard Separate instruction memory Separate data memory Parallel fetch and data access Common in MCUs and DSPs Modified Harvard Split L1 caches Unified main memory Higher performance Cache coherency concerns Embedded checks Flash holds code SRAM holds stack and globals Startup copies data section Peripheral registers are special DMA may need cache care Common mistakes Stack overflow ignored Const location assumed Wrong DMA buffer memory Register side effects missed