How to Simulate Retro CPU Architecture in FPGA Using Verilog: Apple Lisa Case Study

How to Simulate Retro CPU Architecture in FPGA Using Verilog: Apple Lisa Case Study

Recreating vintage computing systems in FPGA has become a viable approach for hardware enthusiasts and embedded systems developers who want to understand legacy architectures without access to original silicon. The recent recreation of the Apple Lisa computer in FPGA demonstrates that modern hardware description languages can faithfully reproduce decades-old processors and peripherals.

This guide walks you through the core concepts and practical steps needed to simulate retro CPU architectures using Verilog on modern FPGA platforms.

Why FPGA Simulation of Retro CPUs Matters

FPGA-based emulation differs fundamentally from software emulation. While software emulators like MAME execute legacy code through interpretation or JIT compilation, FPGA implementations recreate the actual hardware behavior at the logic gate level. This approach offers:

  • Cycle-accurate execution: Exactly matching original CPU timing and behavior
  • Peripheral integration: Running original ROM and disk controllers without modification
  • Educational value: Understanding how vintage architectures actually worked
  • Preservation: Keeping obsolete systems functional for future generations

The Apple Lisa project showcases this by implementing the Motorola 68000 processor logic in Verilog, complete with memory management and I/O systems.

Understanding the Architecture Layer

Before writing Verilog code, you need to decompose your target CPU into functional blocks:

  1. Instruction decoder: Maps opcode bytes to control signals
  2. Register file: Fast storage for operands and results
  3. ALU (Arithmetic Logic Unit): Core computation engine
  4. Memory interface: Bus protocols for RAM/ROM access
  5. Interrupt controller: Handles external signals
  6. Timing sequencer: Manages multi-cycle instructions

For the 68000, this means implementing:

  • 16 32-bit data registers (D0-D7) and address registers (A0-A7)
  • Complex addressing modes (8 variants)
  • 56-instruction base set with multiple encoding formats
  • Asynchronous bus interface

Basic Verilog Structure for CPU Simulation

Here's a minimal framework for a retro CPU in Verilog:

module retro_cpu #(
    parameter ADDR_WIDTH = 24,
    parameter DATA_WIDTH = 16
) (
    input clk,
    input reset,
    output [ADDR_WIDTH-1:0] address_bus,
    inout [DATA_WIDTH-1:0] data_bus,
    output read_write,
    output data_strobe,
    input interrupt
);

    // Register file
    reg [31:0] data_reg[0:7];  // D0-D7
    reg [31:0] addr_reg[0:7];  // A0-A7
    reg [31:0] pc;             // Program counter
    reg [7:0] sr;              // Status register
    
    // Control signals
    reg [2:0] state;           // Instruction fetch/decode/execute states
    reg [15:0] opcode;         // Current instruction
    
    // Memory interface
    wire [ADDR_WIDTH-1:0] mem_addr;
    wire [DATA_WIDTH-1:0] mem_data_out;
    wire mem_read, mem_write;
    
    always @(posedge clk or negedge reset) begin
        if (!reset) begin
            pc <= 32'h00000000;
            state <= 3'b000;
        end else begin
            case(state)
                3'b000: begin  // Fetch
                    mem_read <= 1'b1;
                    state <= 3'b001;
                end
                3'b001: begin  // Decode
                    opcode <= mem_data_out;
                    state <= 3'b010;
                end
                3'b010: begin  // Execute
                    execute_instruction(opcode);
                    state <= 3'b000;
                end
            endcase
        end
    end
    
    task execute_instruction(input [15:0] instr);
        // Instruction decoding and execution logic
    endtask
    
endmodule

Key Implementation Challenges

Challenge 1: Complex Addressing Modes

The 68000 supports 8 addressing modes per instruction. Verilog's case statements handle this, but you'll need careful bit manipulation:

always @(*) begin
    case(addressing_mode)
        3'b000: eff_addr = data_reg[reg_num];           // Register direct
        3'b001: eff_addr = addr_reg[reg_num];           // Address direct
        3'b010: eff_addr = mem_read(addr_reg[reg_num]); // Indirect
        3'b011: eff_addr = addr_reg[reg_num] + 4;       // Post-increment
        // ... handle remaining modes
    endcase
end

Challenge 2: Multi-Cycle Instructions

Instructions like DIVS (divide) require many clock cycles. Implement a cycle counter:

reg [4:0] cycle_count;
reg [4:0] cycle_max;

always @(posedge clk) begin
    if (cycle_count == cycle_max) begin
        state <= FETCH;
        cycle_count <= 0;
    end else begin
        cycle_count <= cycle_count + 1;
    end
end

Challenge 3: Asynchronous Bus Protocols

Retro systems often used handshake signals. Model the timing explicitly:

always @(posedge clk) begin
    if (bus_request && !bus_grant) begin
        bus_request <= 1'b1;  // Hold request until acknowledged
    end
end

Testing Your Implementation

Validate your design with:

  1. Unit tests for instruction execution
  2. Known program validation (run original Lisa system code)
  3. Cycle-counting verification (compare against published timing data)
  4. Memory protection checks (ensure address ranges are correct)

FPGA Platform Selection

| Platform | Capacity | Cost | Best For | |----------|----------|------|----------| | Xilinx Artix-7 | 215K LUTs | $80-150 | Full 68000 + peripherals | | Lattice ECP5 | 85K LUTs | $50-100 | Minimal 68000 core | | Intel Cyclone V | 110K LUTs | $100-200 | High-speed simulation | | Tang Nano | 20K LUTs | $25-40 | 8-bit CPUs only |

For Apple Lisa, you'll need at least 100K LUTs to accommodate the 68000, ROM controller, and memory management unit.

Performance Considerations

FPGA clock speeds for complex retro CPUs typically max at 50-100 MHz, even on modern devices, due to long combinational paths in instruction decoding. This is still 5-50x faster than the original hardware running at 5-10 MHz.

Optimize by:

  • Pipelining instruction fetch/decode/execute stages
  • Using block RAM for instruction ROMs
  • Registering intermediate results between logic stages

Next Steps

After building your CPU core:

  1. Implement matching peripheral controllers (video, disk, serial)
  2. Load original system ROMs from source hardware
  3. Add debugging features (instruction trace, breakpoints)
  4. Connect to host computer via USB for program loading

The Apple Lisa FPGA project demonstrates that faithful hardware recreation is achievable with modern tooling and moderate FPGA capacity, opening possibilities for preserving and studying vintage computer architectures.

Conclusion

Simulating retro CPU architectures in FPGA requires understanding both the legacy instruction sets and modern hardware description language capabilities. By breaking the problem into manageable Verilog modules and validating against known behavior, you can successfully recreate systems like the Apple Lisa and run their original software without modification.