Why programs and data can be placed in the same memory

In the previous lecture, we saw the full picture of the von Neumann architecture—five major components and three buses.

In this lecture, we focus on its most revolutionary idea:Treat program instructions as a special kind of data, and store them together with data in the same memory。


Life-oriented analogy: sheet music and music players

Imagine two different music devices:

Device A: Music Box

A spring-driven music box contains a metal cylinder covered with tiny bumps. Each bump plucks a steel comb tooth to produce a note.

Want to change the tune? You have to take apart the music box and replace the cylinder with another one whose bumps are arranged differently. The cylinder itself is a physical fusion of the "program" and the "player."

Device B: Music app on smartphone

Your phone stores hundreds of MP3 files. Tap one and it plays.

The MP3 file (program/data) and the player app (executor) are separate—to change a song, you only need to change the file, not the phone.

A music box is a pre-von Neumann computer: the program and hardware are bound together, and changing the task means changing the machine.

A smartphone is a post-von Neumann computer: programs are replaceable content in storage, and the hardware is a general-purpose execution platform.

Comparison DimensionMusic box (external program control)Smartphone (stored program)
Stored-program approachFixed in mechanical structureStored in memory as data
Change trackDisassemble and replace the drumJust tap the screen
Is the hardware universal?One music box = one tuneOne phone = unlimited tunes
FlexibilityAlmost zeroextremely high

What von Neumann did was just this: he extracted the "score" from the hardware and turned it into a "data file" that could be freely replaced. From then on, hardware was hardware, and software was software.


Historical background: from "external program control" to "stored program"

2.1 Computers before von Neumann

In the early 1940s, some electronic computers were already in use, but they all shared a common problem:

ENIAC (1946)—The world's first general-purpose electronic computer, astonishingly fast, but every time the computation task changed, engineers had to spend several daysManually plugging and unplugging hundreds of cables and setting thousands of switchesTo "reprogram."

Harvard Mark I (1944)—Programs were input via punched paper tape, but the program storage (tape reader) and data storage were two physically separate devices; the CPU could not access program instructions the way it accessed data.

Common shortcomings of these machines:

  • Changing programs was extremely time-consuming—requiring manual rewiring of hardware or replacement of physical media.
  • Program instructions could not be modified by the program itself—this limited the emergence of advanced programming techniques (such as compilers and linkers).
  • Hardware design was complex—different tasks might require different hardware configurations.

2.2 von Neumann's breakthrough

In 1945, von Neumann proposed a bold idea in the "First Draft of a Report on the EDVAC":

Encode program instructions into binary numbers and store them together with data in the same addressable memory.

This idea brought three decisive advantages:

  1. Hardware unification: The CPU does not need to know whether a given memory location holds an instruction or data—it simply reads and writes by address. This greatly simplified hardware design.
  2. Software exists independently: Programs became "files" that could be stored, copied, and transferred. This was the birth of the "software" concept.
  3. Programs can modify themselves: A program can modify its own instructions or those of other programs just as it operates on data. This laid the foundation for epoch-making technologies such as assemblers, compilers, and operating systems.

Detailed principle: the coexistence of instructions and data in memory

3.1 Memory module diagram: which is an instruction? which is data?

The figure below shows a snapshot of a memory region. Each cell is a storage unit with a unique address number.

Please observe—Instructions and data are physically exactly the same; they are all 0s and 1s.The only way to distinguish them is who accesses it, and when.

Instruction color gradient Data color gradient Memory module background Main memory (RAM)header Address Stored content Type Actual meaningData row Address 0: instruction LOAD A 5 0x0000 10110001 00000101 [ Instruction ] LOAD A, 5Address 2: Data 5 (value of A) 0x0001 00000000 00000101 [ Data ] Immediate value 5Address 3: Instruction LOAD B 3 0x0002 10110010 00000011 [ Instruction ] LOAD B, 3Address 5: Data 3 (value of B) 0x0003 00000000 00000011 [ Data ] Immediate value 3Address 4: Instruction ADD A B 0x0004 00100000 00000001 [ Instruction ] ADD A, BAddress 6: Instruction STORE A 0x0005 00110000 01100100 [ Instruction ] STORE 100Address 100: data storage result 0x0064 00000000 00001000 [ Data ] Calculation result: 8Legend area Legend: Blue highlight = instruction unit (opcode + operand) Orange highlight = data unit (immediate values, variable values, calculation results) Note: Instructions and data are exactly the same in physical storage; they are all binary bits. Only the PC pointer when the CPU fetches an instruction determines whether it is an instruction or data.

3.2 Key question: how does the CPU distinguish instructions from data?

From the diagram above, a key fact can be seen: instructions and data are indistinguishable at the physical storage level.Completely indistinguishable—They are all binary bit strings.

So how does the CPU know whether to treat a memory unit as an instruction or as data?

The answer is simple:Depends on access timing and the visitor.

  • Instruction fetch stage: CPU'sProgram counter (PC)The address is provided, and the control unit sends a "read instruction" control signal. At this point, the content read from memory is treated as an "instruction" and sent to the instruction register for decoding.
  • Execution stage: The CPU calculates the operand's address based on the decoding result (which may come from a register or an immediate value in the instruction), and the controller issues a "read data" control signal. At this point, the content read from memory is sent to the ALU as "data" to participate in the computation.

In a sentence: for the same memory address, when the PC points to it, it is an "instruction"; when an operand address points to it, it is "data." This is the essence of the "stored program"—instructions and data are physically indistinguishable and logically distinguished by access context.


Interactive demonstration: explore instructions and data in memory yourself

The Python code below creates a memory system that can be interactively explored—you can write instructions or data, then view the content of any address in memory and determine whether it is an instruction or data.

Example

"""
Demonstration of mixed storage of instructions and data in memory (example demo)
Demonstrating the 'stored-program' concept: instructions and data in the same memory cannot be distinguished by their content.
"""


class MemoryInspector:
    Memory Inspector — intuitively shows the coexistence of instructions and data in memory

    def __init__(self, size=256):
        # Initialize memory: each cell stores an integer value (simulating 8-bit)
        self.memory = [0] * size
        # Tag array: records the type of each address — 'instruction' or 'data'
        # Note: This marker does not exist in real hardware! It's here only for demonstration convenience.
        self.type_tags = ['empty'] * size
        # Record mnemonic explanations for each address (also to facilitate demonstration)
        self.annotations = [''] * size

    def write_instruction(self, address, opcode, operand, annotation=""):
        """Write an instruction to memory (occupies two units: opcode + operand)"""
        self.memory[address] = opcode
        self.type_tags[address] = 'instruction'
        self.annotations[address] = annotation

        self.memory[address + 1] = operand
        self.type_tags[address + 1] = 'instruction'
        self.annotations[address + 1] = f" └ Operand: {operand}"

    def write_data(self, address, value, annotation=""):
        """Write a data unit to memory"""
        self.memory[address] = value
        self.type_tags[address] = 'data'
        self.annotations[address] = annotation

    def inspect(self, start=0, end=16):
        """View memory contents in a specified range"""
        print("=" * 68)
        print(f'{Address':>6} | {'Stored Value':>8} | {'Binary':>10} | {'Type':>12} | Meaning)
        print("-" * 68)
        for addr in range(start, min(end, len(self.memory))):
            val = self.memory[addr]
            binary = format(val, '08b')  # 8-bit binary representation
            tag = self.type_tags[addr]
            anno = self.annotations[addr]

            # Use markers to distinguish display colors (no color in terminal, use symbols instead)
            marker = "[I]" if tag == 'instruction' else \
                     "[D]" if tag == 'data' else \
                     "[ ]"

            if val != 0 or tag != 'empty':
                print(f"  {addr:4d} | {marker} {val:5d} |  {binary}  | {tag:>12} | {anno}")

        print("=" * 68)
        print([I] = Instruction [D] = Data [ ] = Free\n")

    def compare_bytes(self, addr1, addr2):
        """
Compare the raw bytes of two addresses — you'll find they are indistinguishable at the binary level
        """

        v1 = self.memory[addr1]
        v2 = self.memory[addr2]
        b1 = format(v1, '08b')
        b2 = format(v2, '08b')

        print(f"\n--- Comparing address {addr1} and address {addr2} ---")
        print(fAddress {addr1}: Binary {b1} (Type: {self.type_tags[addr1]}))
        print(fAddress {addr2}: Binary {b2} (Type: {self.type_tags[addr2]}))
        if b1 == b2 and self.type_tags[addr1] != self.type_tags[addr2]:
            print(f>>> Key finding: The binary contents of these two addresses are exactly the same ({b1})!)
            print(f>>> But their types are different: address {addr1} is {self.type_tags[addr1]},)
            print(f>>> Address {addr2} is {self.type_tags[addr2]}.)
            print(f>>> This shows that — with only binary content, it is impossible to distinguish instructions from data!)
        else:
            print(f"The contents of these two addresses are different.")

    def simulate_instruction_data_ambiguity(self, addr):
        """
Demonstrate that the same binary value is interpreted as different meanings in different contexts
        """

        val = self.memory[addr]
        binary = format(val, '08b')
        print(f"\n--- Demonstration of the ambiguity of address {addr} ---")
        print(fStored binary value: {binary} (decimal: {val}))
        print(f"")
        print(f"If the CPU's PC points here (instruction fetch stage):")
        print(f" -> this byte is interpreted as the 'instruction opcode'")
        print(f" -> For example, {binary} could represent a 'LOAD' instruction")
        print(f"")
        print(f"If the ALU references here via the operand address (execution stage):")
        print(f" -> This byte is interpreted as 'data'")
        print(f-> For example, decimal {val} is just an ordinary numeric value)
        print(f"")
        print(f"The same bit sequence, read by different roles, has completely different meanings!")


# ================================================================
# Demo 1: Building In-Memory Hybrid Storage
# ================================================================
print(>>> Demo 1: Mixing instructions and data in memory\n")
mem = MemoryInspector()

# Write instruction sequence
mem.write_instruction(0, 0xB1, 5, "LOAD A, 5")
mem.write_instruction(2, 0xB2, 3, "LOAD B, 3")
mem.write_instruction(4, 0x20, 1, "ADD A, B")
mem.write_instruction(6, 0x30, 100, "STORE 100")

# Write data
mem.write_data(100, 8, Calculation result: 5 + 3 = 8)

# View memory overview
mem.inspect(0, 8)
mem.inspect(100, 102)

# ================================================================
# Demo 2: Instruction/data ambiguity of the same value
# ================================================================
print(>>> Demo 2: The same binary value may be an instruction or data\n")

# Intentionally write a value 5 at the address (instruction area), and also write 5 at address 200 (data area)
mem.write_instruction(10, 5, 0, "This 5 is the opcode")
mem.write_data(200, 5, "This 5 is the data value")

mem.compare_bytes(10, 200)

# Ambiguity demonstration
mem.simulate_instruction_data_ambiguity(10)

# ================================================================
# Demo 3: Concept of self-modifying code (advanced topic)
# ================================================================
print(">>> Demo 3: Concept of self-modifying programs\n")
print("Because instructions and data are in the same memory, a program can modify its own instructions!")
print(This is exactly the capability brought by the stored-program architecture.)
print("")
print(For example, the program can change the instruction STORE 100 at address 6 to STORE 200:)
print(f"Before modification, address 6: {mem.memory[6]} (opcode)")
print(f"Before modification, address 7: {mem.memory[7]} (operand)")

# Program modifies itself: change STORE's operand from 100 to 200
mem.memory[7] = 200
mem.type_tags[7] = 'data'
mem.annotations[7] = Modified by the program itself: operand 100 -> 200

print(fAfter modification, address 7: {mem.memory[7]} (operand has changed))
print("")
mem.inspect(6, 8)
print(>>> This capability is the foundation of technologies such as compilers, dynamic linkers, and virtual machines.)

Run this program and you will clearly see: instructions and data are just different "roles" in the same block of memory. They share the same storage and addressing mechanism—this is the most subtle elegance at the heart of the von Neumann architecture.

3.3 Interactive demonstration: click a memory cell to view its content (example)

Below is a simulation of a 64-byte memory region.Blue cells represent instructions,Orange cells represent data, gray areas are free. Hover over any cell to view its raw stored value,Click cellview the detailed explanation—including whether the address actually stores an "instruction" or "data," as well as its actual meaning.

Instruction Data Free (Empty)
Grid cells dynamically generated by JS
Click the memory cell above to view the type and meaning of the content stored at this address

Comparison with the Harvard architecture

The von Neumann architecture is not the only computer architecture. Its main "competitor" is the Harvard architecture.Harvard Architecture。

Comparison DimensionVon Neumann architectureHarvard architecture
MemorySingle memory, instructions and data shareTwo independent memories, instructions and data are separate
busOne set of address/data buses; instructions and data share the bus in time divisionTwo independent sets of buses; instructions and data can be accessed simultaneously
AdvantagesSimple hardware design, high memory utilization, flexible softwareInstruction fetch and data fetch can happen simultaneously, faster (pipeline-friendly)
DisadvantagesInstruction fetch and data fetch may conflict (von Neumann bottleneck)More complex hardware; instruction storage and data storage cannot be dynamically allocated
Typical applicationsGeneral-purpose computers such as PC, servers, etc.Embedded MCUs, DSP digital signal processors

In actual implementations, modern CPUs often merge the two architectures: externally they present the unified memory model of the von Neumann architecture (for programming convenience), while internally they use separate instruction and data caches at the L1 Cache level (borrowing the speed advantages of the Harvard architecture). This is the so-called "modified Harvard architecture."

other extensions