Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,5 @@ jobs:
- name: Run test suite (parallel)
env:
RSPEC_QUIET_OUTPUT: "1"
RSPEC_TIMEOUT: "20"
run: bundle exec rake pspec
23 changes: 23 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,22 @@ lib/rhdl/codegen/ir/sim/ir_compiler/*.json
.arcilator_build/
.arcilator_gpu_build/

# Metal backend build artifacts
.metal_build/
.metal_probe/

# HDL build artifacts (Verilator/Arcilator for example systems)
.hdl_build/
/examples/8bit/.gem_metal_cpu8bit/
/examples/apple2/.gem_metal_apple2/
/examples/riscv/.gem_metal_riscv/

# Submodule trees
/examples/apple2/reference/
/examples/gameboy/reference/
/examples/riscv/software/xv6/
/examples/riscv/software/linux/
/external/GEM/

# Web simulator generated artifacts
web/assets/fixtures/*
Expand Down Expand Up @@ -95,3 +109,12 @@ web/build/verilator/*
/examples/riscv/software/.buildroot.defconfig
/examples/riscv/software/.docker-config*/
/examples/riscv/software/.tmp_*/

# AO486 local scratch/import artifacts
/examples/ao486/reference/
/examples/ao486/tmp/
/examples/ao486/software/tmp/
/examples/ao486/hdl/tmp/
/examples/ao486/hdl/reports/
/examples/ao486/hdl/vendor/source_hdl/
/examples/ao486/hdl/lib/hdl/modules/frontend_blackbox_stubs__*.rb
8 changes: 8 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
[submodule "examples/apple2/reference"]
path = examples/apple2/reference
url = https://github.com/zf3/neoapple2
ignore = all
[submodule "examples/gameboy/reference"]
path = examples/gameboy/reference
url = https://github.com/MiSTer-devel/Gameboy_MiSTer.git
ignore = all
[submodule "examples/riscv/software/xv6"]
path = examples/riscv/software/xv6
url = https://github.com/michaelengel/xv6-rv32.git
ignore = all
[submodule "examples/riscv/software/linux"]
path = examples/riscv/software/linux
url = https://github.com/torvalds/linux.git
ignore = all
[submodule "external/GEM"]
path = external/GEM
url = https://github.com/skryl/GEM
ignore = all
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -613,14 +613,20 @@ RHDL includes benchmarking tasks to measure simulation performance across backen

```bash
rake bench:native[gates] # Benchmark gate-level simulation
rake bench:native[cpu8bit,cycles] # Benchmark 8-bit CPU FastHarness (compiler vs arcilator_gpu)
rake bench:native[cpu8bit,cycles] # Benchmark 8-bit CPU FastHarness (compiler, arcilator_gpu, GemMetal)
RHDL_BENCH_BACKENDS=gem_metal rake bench:native[cpu8bit,cycles] # Benchmark only GemMetal
rake bench:native[mos6502,cycles] # Benchmark MOS 6502 CPU
rake bench:native[apple2,cycles] # Benchmark Apple II full system
rake bench:native[apple2,cycles] # Benchmark Apple II full system (including GemMetal)
RHDL_BENCH_BACKENDS=gem_metal rake bench:native[apple2,cycles] # Benchmark Apple II with only GemMetal
rake bench:native[riscv,cycles] # Benchmark RISC-V single-cycle core (includes GemMetal)
RHDL_BENCH_BACKENDS=gem_metal rake bench:native[riscv,cycles] # Benchmark only RISC-V GemMetal
rake bench:native[gameboy,frames] # Benchmark GameBoy with Prince of Persia
rake bench:web[apple2,cycles] # Benchmark Apple II web WASM backends
rake bench:web[riscv,cycles] # Benchmark RISC-V web WASM backends
```

GemMetal is part of the default native backend set for CPU8bit, Apple II, and RISC-V. Use `RHDL_BENCH_BACKENDS` to restrict the runners, for example `RHDL_BENCH_BACKENDS=gem_metal bundle exec rake bench:native[cpu8bit,5000000]`, `RHDL_BENCH_BACKENDS=compiler,gem_metal bundle exec rake bench:native[apple2,2000000]`, or `RHDL_BENCH_BACKENDS=gem_metal bundle exec rake bench:native[riscv,500000]`. `gem` remains an alias for `gem_metal`. On RISC-V, the GemMetal path benchmarks an MMU-off synthesized core netlist via `metal_dummy_test`; it does not load xv6 images. For CPU8bit ArcilatorGPU throughput mode, set `RHDL_CPU8BIT_ARCILATOR_GPU_INSTANCES=<n>` or the benchmark-wide fallback `RHDL_BENCH_ARCILATOR_GPU_INSTANCES=<n>`.

**Sample Results (1M cycles):**

| System | JIT | Compiler | Verilator | Compiler Speedup |
Expand Down Expand Up @@ -650,7 +656,13 @@ bundle exec rake pspec[riscv] # Run RISC-V specs in parallel
bundle exec rake spec:bench[riscv,20] # Benchmark 20 RISC-V spec files
bundle exec rake bench:native[ir,5000000] # Benchmark IR runners
bundle exec rake bench:native[gates] # Benchmark gate-level simulation
bundle exec rake bench:native[cpu8bit,5000000] # Benchmark 8-bit CPU compiler vs arcilator_gpu
bundle exec rake bench:native[cpu8bit,5000000] # Benchmark 8-bit CPU compiler vs arcilator_gpu vs GemMetal
RHDL_CPU8BIT_ARCILATOR_GPU_INSTANCES=8 bundle exec rake bench:native[cpu8bit,5000000] # Benchmark CPU8bit ArcilatorGPU with 8 mirrored instances
bundle exec rake bench:native[apple2,2000000] # Benchmark Apple II including GemMetal
bundle exec rake bench:native[riscv,500000] # Benchmark RISC-V including GemMetal
RHDL_BENCH_BACKENDS=gem_metal bundle exec rake bench:native[cpu8bit,5000000] # Benchmark only CPU8bit GemMetal
RHDL_BENCH_BACKENDS=gem_metal bundle exec rake bench:native[apple2,2000000] # Benchmark only Apple II GemMetal
RHDL_BENCH_BACKENDS=gem_metal bundle exec rake bench:native[riscv,500000] # Benchmark only RISC-V GemMetal
bundle exec rake bench:web[apple2] # Benchmark Apple II web compiler vs arcilator vs verilator
bundle exec rake bench:web[riscv] # Benchmark RISC-V web compiler vs arcilator vs verilator

Expand Down
14 changes: 12 additions & 2 deletions docs/simulation.md
Original file line number Diff line number Diff line change
Expand Up @@ -644,15 +644,23 @@ RHDL_BENCH_LANES=128 RHDL_BENCH_CYCLES=1000000 rake bench:native[gates]

# 8-bit CPU FastHarness benchmark
rake bench:native[cpu8bit]
rake bench:native[cpu8bit,5000000] # 5M cycles
rake bench:native[cpu8bit,5000000] # 5M cycles, includes GemMetal by default
RHDL_BENCH_BACKENDS=gem_metal rake bench:native[cpu8bit,5000000] # GemMetal only
RHDL_CPU8BIT_ARCILATOR_GPU_INSTANCES=8 rake bench:native[cpu8bit,5000000] # ArcilatorGPU throughput mode

# MOS6502 CPU benchmark
rake bench:native[mos6502]
rake bench:native[mos6502,5000000] # 5M cycles

# Apple II full system benchmark
rake bench:native[apple2]
rake bench:native[apple2,2000000] # 2M cycles
rake bench:native[apple2,2000000] # 2M cycles, includes GemMetal by default
RHDL_BENCH_BACKENDS=gem_metal rake bench:native[apple2,2000000] # GemMetal only

# RISC-V single-cycle benchmark
rake bench:native[riscv]
rake bench:native[riscv,500000] # includes GemMetal by default
RHDL_BENCH_BACKENDS=gem_metal rake bench:native[riscv,500000] # GemMetal only

# Legacy alias removed (use bench scopes)

Expand All @@ -667,6 +675,8 @@ rake spec:bench:timing # Per-file timing
rake spec:bench:quick # Category summary
```

GemMetal is part of the default native backend set for CPU8bit, Apple II, and RISC-V. Set `RHDL_BENCH_BACKENDS` to restrict the runners; `gem` remains an alias for `gem_metal`. On RISC-V, the GemMetal path benchmarks an MMU-off synthesized core netlist via `metal_dummy_test`; it does not load xv6 images.

### Programmatic Benchmarking

```ruby
Expand Down
120 changes: 95 additions & 25 deletions examples/8bit/hdl/cpu/harness.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,28 @@ def self.arcilator_gpu_status
}
end

def self.arcilator_status
require_relative '../../utilities/runners/arcilator_runner'
RHDL::Examples::CPU8Bit::ArcilatorRunner.status
rescue LoadError, NameError => e
{
ready: false,
missing_tools: [],
missing_capabilities: ["arcilator runner unavailable: #{e.message}"]
}
end

def self.verilator_status
require_relative '../../utilities/runners/verilator_runner'
RHDL::Examples::CPU8Bit::VerilatorRunner.status
rescue LoadError, NameError => e
{
ready: false,
missing_tools: [],
missing_capabilities: ["verilator runner unavailable: #{e.message}"]
}
end

def self.ensure_arcilator_gpu_available!
status = arcilator_gpu_status
return true if status[:ready]
Expand All @@ -89,17 +111,54 @@ def self.ensure_arcilator_gpu_available!
"Install an ArcToGPU-enabled arcilator build plus Metal/SPIR-V toolchain support."
end

def self.ensure_arcilator_available!
status = arcilator_status
return true if status[:ready]

details = []
details << "missing tools: #{status[:missing_tools].join(', ')}" unless status[:missing_tools].empty?
details << "missing capabilities: #{status[:missing_capabilities].join(', ')}" unless status[:missing_capabilities].empty?
raise ArgumentError,
"arcilator backend unavailable (#{details.join('; ')}). " \
"Install an arcilator/firtool-enabled toolchain."
end

def self.ensure_verilator_available!
status = verilator_status
return true if status[:ready]

details = []
details << "missing tools: #{status[:missing_tools].join(', ')}" unless status[:missing_tools].empty?
details << "missing capabilities: #{status[:missing_capabilities].join(', ')}" unless status[:missing_capabilities].empty?
raise ArgumentError,
"verilator backend unavailable (#{details.join('; ')}). " \
"Install a verilator-enabled native build toolchain."
end

def initialize(external_memory = nil, sim: :compile)
require 'rhdl/codegen'

@cycle_count = 0
@halted = false
@sim_backend = normalize_sim_backend(sim)

if arcilator_gpu_mode?
self.class.ensure_arcilator_gpu_available!
require_relative '../../utilities/runners/arcilator_gpu_runner'
@sim = RHDL::Examples::CPU8Bit::ArcilatorGpuRunner.new
if runner_backend_mode?
@sim = case @sim_backend
when :arcilator_gpu
self.class.ensure_arcilator_gpu_available!
require_relative '../../utilities/runners/arcilator_gpu_runner'
RHDL::Examples::CPU8Bit::ArcilatorGpuRunner.new
when :arcilator
self.class.ensure_arcilator_available!
require_relative '../../utilities/runners/arcilator_runner'
RHDL::Examples::CPU8Bit::ArcilatorRunner.new
when :verilator
self.class.ensure_verilator_available!
require_relative '../../utilities/runners/verilator_runner'
RHDL::Examples::CPU8Bit::VerilatorRunner.new
else
raise ArgumentError, "Unsupported runner backend: #{@sim_backend.inspect}"
end
@memory = RunnerMemory64K.new(@sim)
ensure_runner_cpu8bit_mode!
else
Expand Down Expand Up @@ -132,7 +191,17 @@ def native?
end

def backend
arcilator_gpu_mode? ? :arcilator_gpu : @sim.backend
return @sim_backend if runner_backend_mode?

@sim.backend
end

def parallel_instances
return 1 unless runner_backend_mode?
return 1 unless @sim.respond_to?(:runner_parallel_instances)

instances = @sim.runner_parallel_instances.to_i
instances.positive? ? instances : 1
end

# Read CPU state
Expand Down Expand Up @@ -172,7 +241,7 @@ def reset
@halted = false
@cycle_count = 0

if arcilator_gpu_mode?
if runner_backend_mode?
@sim.poke('rst', 1)
run_runner_cycles(1)
@sim.poke('rst', 0)
Expand All @@ -189,7 +258,7 @@ def reset
end

def clock_cycle
if arcilator_gpu_mode?
if runner_backend_mode?
run_runner_cycles(1)
@halted = true if @sim.peek('halted') == 1
return
Expand Down Expand Up @@ -224,7 +293,7 @@ def run_cycles(count, batch_size: 1024)
n = count.to_i
return 0 if n <= 0

unless arcilator_gpu_mode?
unless runner_backend_mode?
ran = 0
n.times do
break if @halted
Expand All @@ -236,22 +305,9 @@ def run_cycles(count, batch_size: 1024)
return ran
end

remaining = n
ran = 0
batch = [batch_size.to_i, 1].max
while remaining.positive?
step = [remaining, batch].min
batch_ran = run_runner_cycles(step)
break if batch_ran <= 0

ran += batch_ran
remaining -= batch_ran
if @sim.peek('halted') == 1
@halted = true
break
end
end

# Native runner backends handle internal batching/scheduling.
# Keep host-side execution as a single call to avoid extra sync points.
ran = run_runner_cycles(n)
@cycle_count += ran
@halted = true if @sim.peek('halted') == 1
ran
Expand Down Expand Up @@ -291,6 +347,8 @@ def get_input(name)
def normalize_sim_backend(sim)
sym = sim.to_sym
return :compile if sym == :compiler
return :arcilator_gpu if sym == :arc_to_gpu
return :arcilator if sym == :arc

sym
end
Expand All @@ -303,11 +361,23 @@ def arcilator_gpu_mode?
@sim_backend == :arcilator_gpu
end

def arcilator_mode?
@sim_backend == :arcilator
end

def verilator_mode?
@sim_backend == :verilator
end

def runner_backend_mode?
arcilator_gpu_mode? || arcilator_mode? || verilator_mode?
end

def ensure_runner_cpu8bit_mode!
return if @sim.runner_mode? && @sim.runner_kind == :cpu8bit

raise ArgumentError,
"arcilator_gpu backend requires native cpu8bit runner mode " \
"#{@sim_backend} backend requires native cpu8bit runner mode " \
"(runner_mode=#{@sim.runner_mode?}, runner_kind=#{@sim.runner_kind.inspect})"
end

Expand Down
Loading
Loading