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:
- Instruction decoder: Maps opcode bytes to control signals
- Register file: Fast storage for operands and results
- ALU (Arithmetic Logic Unit): Core computation engine
- Memory interface: Bus protocols for RAM/ROM access
- Interrupt controller: Handles external signals
- 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:
- Unit tests for instruction execution
- Known program validation (run original Lisa system code)
- Cycle-counting verification (compare against published timing data)
- 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:
- Implement matching peripheral controllers (video, disk, serial)
- Load original system ROMs from source hardware
- Add debugging features (instruction trace, breakpoints)
- 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.