module Zemu

Zemu is a module providing an interface to build and interact with configurable Z80 emulators.

Configurations can be defined in a declarative syntax, from which an emulator executable can be compiled. This executable can then be run from within Zemu and controlled programmatically.

@example

require 'zemu'

# A simple configuration with a ROM block
# and a RAM block.
conf = Zemu::Config.new do
    name "zemu_emulator"

    add_memory Zemu::Config::ROM.new do
        name "rom"
        address 0x0000
        size 0x4000

        contents from_binary("app.bin")
    end

    add_memory Zemu::Config::RAM.new do
        name "ram"
        address 0x8000
        size 0x1000
    end
end

# Start a new instance with this configuration.
instance = Zemu.start(conf)

# Program breakpoint.
# Will trigger if the emulator is about to execute an
# instruction at 0x102.
instance.break 0x102, :program

# Continue. Emulator will run until HALT or until
# the breakpoint (set above) is hit.
instance.continue

# Display the value of the A register (accumulator)
# at the breakpoint.
puts instance.register["A"]

# Close the instance.
instance.quit

Constants

SRC

Location of C source for the emulator.

Public Class Methods

build(configuration) click to toggle source

Builds a library according to the given configuration.

@param [Zemu::Config] configuration The configuration for which an emulator will be generated.

@returns true if the build is a success, false (build failed) or nil (compiler not found) otherwise.

# File lib/zemu.rb, line 87
def Zemu::build(configuration)
    # Create the output directory unless it already exists.
    unless Dir.exist? configuration.output_directory
        Dir.mkdir configuration.output_directory
    end

    # Generate the autogenerated source files.
    generate(configuration)

    output = File.join(configuration.output_directory, "#{configuration.name}.so")

    autogen = File.join(configuration.output_directory, "autogen_#{configuration.name}")

    compiler = configuration.compiler

    inputs = [
        "main.c",                       # main library functionality
        "debug.c",                      # debug functionality
        "interrupt.c",                  # interrupt functionality
        "external/z80/sources/Z80.c"    # z80 core library
    ]

    inputs_str = inputs.map { |i| File.join(SRC, i) }.join(" ")

    inputs_str += " " + File.join(autogen, "memory.c") + " " + File.join(autogen, "io.c")

    defines = {
        "CPU_Z80_STATIC" => 1,
        "CPU_Z80_USE_LOCAL_HEADER" => 1
    }

    defines_str = defines.map { |d, v| "-D#{d}=#{v}" }.join(" ")

    includes = [
        "external/Z/API",
        "external/z80/API",
        "external/z80/API/emulation/CPU",
        "."
    ]

    includes_str = includes.map { |i| "-I#{File.join(SRC, i)}" }.join(" ")

    includes_str += " -I" + autogen

    command = "#{compiler} -O2 -Werror -Wno-unknown-warning-option -fPIC -shared -Wl,-undefined -Wl,dynamic_lookup #{includes_str} #{defines_str} -o #{output} #{inputs_str}"
    
    # Run the compiler and generate a library.
    return system(command)
end
generate(configuration) click to toggle source

Generates the prerequisite source and header files for a given configuration.

@param [Zemu::Config] configuration The configuration for which an emulator will be generated.

# File lib/zemu.rb, line 140
def Zemu::generate(configuration)
    generate_memory(configuration)
    generate_io(configuration)
end
generate_io(configuration) click to toggle source

Generates the io.c and io.h files for a given configuration.

# File lib/zemu.rb, line 164
def Zemu::generate_io(configuration)
    header_template = ERB.new File.read(File.join(SRC, "io.h.erb"))
    source_template = ERB.new File.read(File.join(SRC, "io.c.erb"))

    autogen = File.join(configuration.output_directory, "autogen_#{configuration.name}")

    unless Dir.exist? autogen
        Dir.mkdir autogen
    end

    File.write(File.join(autogen, "io.h"),
               header_template.result(configuration.get_binding))

    File.write(File.join(autogen, "io.c"),
               source_template.result(configuration.get_binding))
end
generate_memory(configuration) click to toggle source

Generates the memory.c and memory.h files for a given configuration.

# File lib/zemu.rb, line 146
def Zemu::generate_memory(configuration)
    header_template = ERB.new File.read(File.join(SRC, "memory.h.erb"))
    source_template = ERB.new File.read(File.join(SRC, "memory.c.erb"))

    autogen = File.join(configuration.output_directory, "autogen_#{configuration.name}")

    unless Dir.exist? autogen
        Dir.mkdir autogen
    end

    File.write(File.join(autogen, "memory.h"),
               header_template.result(configuration.get_binding))

    File.write(File.join(autogen, "memory.c"),
               source_template.result(configuration.get_binding))
end
start(configuration) click to toggle source

Build and start an emulator according to the given configuration.

@param [Zemu::Config] configuration The configuration for which an emulator will be generated.

# File lib/zemu.rb, line 66
def Zemu::start(configuration)
    build(configuration)

    return Instance.new(configuration)
end
start_interactive(configuration) click to toggle source

Starts an interactive instance of an emulator, according to the given configuration.

@param [Zemu::Config] configuration The configuration for which an emulator will be generated.

# File lib/zemu.rb, line 75
def Zemu::start_interactive(configuration)
    instance = start(configuration)

    interactive = InteractiveInstance.new(instance)
    interactive.run
end