The FreeWPC Manual

Table of Contents

Introduction

This is the manual for FreeWPC, a free platform for developing replacement game ROMs for Bally/Williams pinball machines based on the WPC (Williams Pinball Controller) chip. FreeWPC provides the foundation for building new ROMs with custom game rules. This document describes version 1.10-dev of the software.

FreeWPC is developed by Brian Dominy (brian@oddchange.com) and is licensed under the GNU General Public License version 2 or later. The latest version of the software can be obtained at http://www.oddchange.com/freewpc. Developers should be familiar with basic embedded systems programming concepts and the C programming language. Familiarity with 6809 assembler is recommended but not necessary. Also, knowledge of WPC pinball machines is very helpful.

1 Overview

FreeWPC is a toolkit for building brand new software for pinball machines. It primarily targets the Williams WPC family of games, which was used in all of that company's games from 1990-1999, starting with ‘Funhouse’ and ending with ‘Cactus Canyon’.

This document provides an overview of the FreeWPC architecture for developers who wish to understand how the system works. FreeWPC is constantly evolving and the information here is subject to change.

FreeWPC only replaces your game ROM (the U6 chip on the CPU board), which contains all of the game code and dot matrix text/graphics. In particular, sounds and music are NOT included in this device, so FreeWPC games use the same sounds as the real machines.

FreeWPC is 100% Williams-free code, written from scratch. It is not a hack on existing ROMs and does not contain any software extracted from real games. Most of the code is written mostly in the C programming language. (Actual WPC games were programmed in assembly language). FreeWPC uses the freely available GCC6809 C compiler. There are some assembly language routines for really low-level, or high performance parts of the program.

All generations of WPC are supported. FreeWPC is also architected into a number of distinct layers which makes it highly portable to other types of pinball machines than just WPC games, including new custom hardware. The platform files describe the circuit boards. The CPU files are specific to the microprocessor, and can be shared between platforms (WPC and WhiteStar both use a 6809). The machine files are unique to a particular game.

Development requires a UNIX-like environment, such as Linux or Cygwin for Microsoft Windows users. Many common UNIX tools like bc and perl are required.

FreeWPC has become more portable over the years, so that it can be used to develop pinball software for platforms other than WPC. This support is still evolving rapidly. In addition to building ROMs, the toolkit supports native mode compilation, which builds an executable that can be run directly on your development machine. This lets you test and debug the software without requiring a real machine or even an emulator. Basically, native mode replaces the lowest level of hardware access with builtin emulation. In place of actual I/O being controlled, it displays the results of the simulation on your screen, using either the console, ncurses, or gtk.

2 Installation

This chapter explains how to install the software.

FreeWPC is mainly a toolkit, and thus is provided in source code format. However, periodically binary ROMs are compiled and published as well. If you are not a developer, but would like to experiment with FreeWPC, you can just download the binary packages and install them in your machine or in your emulator.

If you are a developer and want to write your own game code, for one of the existing games that FreeWPC has been ported to or for a brand new game, you need a source code package.

If you just want to browse the source code, you can view it online at the GitHub repository.

2.1 Binary Packages

Binary packages are available as .zip files, which contain .rom files. These ROMs replace the game ROM only (the U6 chip on the CPU board). If you want to test in a real machine, you need to burn a real EPROM from this file.

If you want to test under PinMAME, you need to copy the ROM file into your PinMAME roms directory; where this is depends on how you installed PinMAME. These directories typically contain .zip files of both the game ROM and sound ROMs together. You'll need to unzip the original file, replace the game ROM, then zip it back. Make sure to save your original game ROM file for when you want to revert back to the Williams code.

2.2 Source Code Packages

The following is a partial list of other programs which are used during the build process: gcc6809, dd, bc, zip, unzip, bash, Perl.

2.3 Directory Layout

The FreeWPC source code tree is organized into the following directories:

kernel/
The core pinball APIs, which are the user-level APIs used to read inputs and control outputs. As a game developer you mostly use the core APIs to interface to the hardware of the machine.

On hardware which requires bank switching (like WPC), these functions are kept in the fixed region of ROM that can be called at any time.

common/
Secondary pinball APIs, also called the Common Pinball Library. These functions are not in fixed ROM and may require bank switching to call.

They are mostly software-related APIs for common features that pinball machines have, like replay, match, and dot-matrix effects.

machine/machine/
The machine-specific files. Every machine has its own directory of files that are specific to it. Each file may or may not be placed into a bank switched region.
platform/platform/
The platform-specific files. These are mostly used during initialization. Every new pinball hardware platform must define some files here.
cpu/cpu/
The CPU-specific files. These are mostly assembly language files.

The native directory here is used in simulation, to obtain support that is based on portable POSIX support.

fonts/
All font files are kept here. Fonts are stored as normal C code that is compiled as usual, just with a .fon extension to denote that they are actually fonts. These files are autogenerated from more standard font files (like TrueType fonts) and then cleaned up.
drivers/
Driver templates for things like jets, slings, kickbacks, etc. These can be shared across all machines.
include/
All of the include files are kept in here or one of its subdirectories.

3 Compiling

3.1 Configuration

FreeWPC can be compiled in many different ways with lots of optional features. You cannot just type make at the top of the directory tree without first specifying what you want to build. This is done by creating a file named .config. The file config.example is provided as an example of the kinds of things that can be tuned.

.config is written in standard Makefile syntax. (The top-level Makefile includes this file.) You set various options by writing variable assignments, such as:

     MACHINE := wcs

You must specify at least a MACHINE. All other configuration variables are optional; suitable defaults will be used.

Here is a list of the most important build options.

MACHINE
Names the machine that you want to build for. This corresponds to the name of the subdirectory in the machine directory that has the machine specific files.
MAKEFLAGS
This is a feature of the make utility, not FreeWPC per se, but it can be useful: if you want to pass special flags to make, include them here. In particular, the -j option can be used to run multiple commands in parallel, which can speed up the build significantly.
FREEWPC_DEBUGGER
Set to y if you want to include debug messages in the build.
CONFIG_BPT
Set to y if you want to enable the breakpoint module.
GCC_VERSION
Used only when building for the 6809 target, this lets you specify the version of the GCC6809 compiler.
EXTRA_CFLAGS
Used to pass arbitrary options to the C compiler. If you add special logic to the code under an #ifdef, you can enable those flags by including them in EXTRA_CFLAGS.
DEBUG_COMPILER
Set to y if you are debugging the 6809 compiler. This causes many temporary files to be saved for later analysis.
SAVE_ASM
Set to y if you just want to see the assembly language code that GCC6809 generates. This is similar to DEBUG_COMPILER but produces much less GCC internals. If you understand 6809 assembly language, this can be useful.
NATIVE
Set to y to enable native mode, or simulation.
CONFIG_UI
In native mode, this option controls the user-interface that is used. The default is curses, which works within most internals and is fairly easy to understand. Use console for a much more raw, printf style output. gtk support is in progress.
PINMAME
Set to the pathname where your PinMAME executable is, if you plan to use that for testing.
EXTRA_PINMAME_FLAGS
Set to flags that you want to pass to PinMAME when testing; for example, sound driver options or DMD color schemes.
TARGET_ROMPATH
Set to the directory where PinMAME ROM files are kept.

3.2 Building

After configuring, type make. This will build a ROM and optionally install it into your PinMAME ROM directory if you have said where that is.

The exact commands are not printed, but you can force the details to be shown by setting the environment variable Q to the empty string, like this:

     make Q=""

The build procedure is complicated, but it can be broken down into the following steps:

  1. Create Blank File

    If the size of the ROM is larger than the number of pages that the compiler generates, then the final ROM image must be padded with blank sections. The dd command is used to generate a file named blankxxx.bin, where xxx is the size of the file in kilobytes (KB). This file is later concatenated with the linker output to produce a final ROM of the required size.

  2. Create Linker Command Files

    The linker is invoked several times, once per page or bank of ROM. Different options are passed each time to place the correct object files into that section, and to resolve references correctly. All of these options are written to linker command files, which have the .lnk extension and are placed in the build directory. They are lists of command-line options to the linker, but placed into a file because of the huge number of them.

  3. Setting the Machine Symbolic Links

    The symbol link mach is set to point to the correct machine code directory, based on the value of the MACHINE make variable. Likewise, include/mach is pointed to the machine includes.

  4. Generating Defines

    Some #defines are generated automatically by scanning the code for their uses. These include files begin with the prefix gendefine and are created by a script also named gendefine in the tools directory.

    At present this is only used to autogenerate the task group IDs.

  5. Generating Callsets

    Callsets are a mechanism for implementing a simple event subscription/invocation mechanism that is fully described at compile-time. Event handling code is emitted in a C file named callset.c.

  6. Compiling and Assembling Source Code

    Source code is compiled using the GCC6809 compiler. The compiler generates assembler code with the .s extension. These files are then assembled using the asxxxx asembler tools into object files with the .o extension.

  7. Compiling Page Headers

    Because the linker requires each section to contain at least one object file, a dummy file is assembly per section to ensure that this doesn't happen. The page header is 1 byte long and contains the page number.

  8. Linking Pages

    The aslink utility is used to create one S-record file, with the .s19 extension, for each page of ROM.

  9. Converting Pages to Binary

    The tools/srec2bin utility included with FreeWPC is run to convert the S-records into raw binary files.

  10. Concatenating Pages

    The binary files are concatenated, along with any blank files, to form the final ROM image.

3.3 Build Results

If successful, build/var.rom will contain the ready-to-burn ROM image. var is a string composed of the machine's short name and the version number.

Warnings and errors during the build are display on the console as they occur; they are also log to a file named err.

In native mode, instead of a ROM, a file named freewpc_var is generated.

4 Software Environment

FreeWPC provides a standalone operating system environment when compiling to run on real hardware. It does not require the use of any existing OS and has its own multitasker and memory manager. This chapter describes the key features of the FreeWPC programming environment.

4.1 Multitasking

FreeWPC implements a runtime, round-robin, non-preemptive task scheduler. The system manages multiple tasks, each of which has its own call stack. You can create a new task when you want to run some code in parallel with the current thread of execution. The current task has complete control of the CPU and will continue to run until it explicit exits or sleeps (waiting for a certain amount of time). The minimum sleep time is defined by IRQS_PER_TICK and is currently 16ms.

(Contrast this with desktop operating systems, which use timeslicing and preemption to allow multiple threads to run in parallel. There, the OS switches between the tasks. In FreeWPC task switches only happen explicitly; this provides for more deterministic behavior, it requires less overhead, and it eliminates the need for mutexes (locks) between two tasks.

If a task does not give up control, either by sleeping, exiting, or yielding after a certain amount of time, the fatal error ERR_FCFS_LOCKUP will be asserted.

When a new task is created, it does not begin running immediately; control always continues with the task that started it. This allows you to configure the new task before it can run.

Each task is identified by a process ID, or pid. The PID for a task is assigned by the system when it is created. A task also has a group ID, or gid. The GID is assigned by the programmer, and multiple tasks can share the same GID. GIDs allow you to control a group of related tasks, or to refer to a task using a compile-time ID as opposed to a run-time ID.

In native mode, the multitasking APIs are implemented using the GNU Pth library. A good description of this library can be found at http://www.gnu.org/software/pth/. Note this is not the same as pthreads, which is the predominant preemptive threading library.

For Windows programmers, this threading model is very similar to what Win32 calls fibers.

4.1.1 Periodic Functions

After all tasks have been given a chance to run in a 16ms timeslice, the system runs the periodic functions. These have equal priority to tasks, but they do not have a stack and they cannot sleep. In the code, these are also referred to as idle functions, although that term is slightly incorrect (they are guaranteed to be called even on a fully busy system).

You can register a periodic function at various rates: either every 16ms, 100ms, 1 second, or 10 seconds. Handlers may not be called at the exact rate, but it will be as close as possible. Slippage occurs when it takes longer than 16ms for all tasks to be given a chance to run. If you need exact timing, use a realtime function.

For example, assume that a 1 second handler does not get called until after 1.5 seconds from the previous call, because the system is busy running other tasks. Then the next time it will be called just 500ms later; the scheduler will realize that it is behind and try to catch up.

You cannot dynamically register or deregister periodic handlers at runtime.

4.1.2 Task APIs

task_create_gid
The basic API to create a new task. You specify a code function and a GID. The function must reside in the same bank of ROM. If the function is in a different ROM page, you need to call task_set_rom_page immediately after this call to say where it was placed.
task_create_gid1
Similar, but checks to see if a task with the same GID already exists; if it does, no new task is created.
task_recreate_gid
Similar, but checks to see if a task with the same GID already exists; if it does, the old task is stopped before the new task is created.
task_sleep
Suspends the current running task for at least the given period of time. The parameter is one of the TIME defines which is given in units of approximately 16ms. Because it is 8-bit, this limits it to about 4 seconds.

If you want to sleep for the absolute minimum, instead of task_sleep (0) it is recommended to use task_yield().

task_sleep_sec
Suspends the current running task for the given number of seconds. This handles larger timeouts, but does not allow the same granularity.
task_exit
Exit from the current task. This must be called; task functions cannot just return when they are done (that is guaranteed to cause a crash).
task_find_gid
Returns the first PID found for a task that has a given GID.
task_kill_pid
Stops another task based on PID. This stops exactly one task.
task_kill_gid
Stops another task based on GID. This can stop one or more tasks. If called from a task with the same GID, it stops all other tasks, but keeps the running task alive.
task_set_arg
The creator of a task can pass a single 16-bit value or pointer to it using this call. It should be called before a task switch might occur.
task_get_arg
Called by the newly created task to obtain its argument.

4.2 Real-Time Processing

The multitasking APIs are flexible but do not guarantee realtime response, since any task may run for a long time. Processing that needs to occur at a strict interval is done by real time tasks, sometimes abbreviated in the code as RTTs.

Optimizing the realtime tasks is key to an efficient system. To date, about 35-40% of the 6809 is spent in realtime tasks; the actual value depends on the machine and the number of device drivers needed.

4.2.1 Overview

Real-time tasks are scheduled at compile-time. When you write an RTT, you must also add an entry to the schedule file, either kernel/system.sched for the core parts of the system, or machine/machine/machine.sched for the game-specific RTTs. Each entry in the schedule file gives the rate at which you want your function to be run, for example, once every 8ms. You can schedule to run as frequently as once every 1ms, but you should only schedule as often as you really need it, otherwise the CPU will have little time to do anything else!

Real-time functions run in interrupt context. The periodic interrupt will preempt anything else that is running at non-real-time priority. Realtime functions cannot sleep.

A scheduler, gensched, processes the schedule files at compile-time and generates the interrupt handler code which calls all of the RTTs at the correct frequency. The output can be seen in build/sched_irq.c.

4.2.2 Schedule File Syntax

Each line in the schedule file is formatted as follows:

function frequency duration

function identifies the name of the function that you want called in realtime context. If you prefix the name with an exclamation point '!', the function is assumed to be an inline function; otherwise, it is an ordinary function. You can append a conditional as a suffix, e.g. my_rtt?CONFIG_FOO will call my_rtt, but only if the conditional CONFIG_FOO is set.

frequency says how often the function should be called, in multiples of the system IRQ tick, which is 1ms on WPC. Presently, this value can range from 1 to 2048 ms. It must also be a power of 2.

duration says how long this function takes to run, in the worst case. The schedule tries to reserve enough CPU cycles for all RTTs and to arrange the order in which they are called so that tasks are balanced. The duration is also given in milliseconds and should be less than 1ms. It can also be specified in CPU cycles by appending the 'c' suffix. It is not critical if the value is not exact, but a reasonable approximation should be listed.

Blank lines and lines beginning with the '#' sign are treated as comments.

4.2.3 Function Unrolling

By default, the scheduler will unroll the interrupt handler into 8 smaller handlers. That is, over a period of 8ms, the system will cycle through 8 different interrupt handlers. The reason is that this cuts down on the number of checks which have to be performed on every IRQ, since infrequent (greater than 8ms) actions do not need to be tested every time.

If a function is scheduled every 4ms, then it will be called from half of those interrupt handlers (every other one, either the even-numbered ones or the odd- numbered ones). Likewise for functions scheduled every 2ms. Functions scheduled every 1ms are called every time. For functions scheduled 8ms or more, those are all executed from the last of the 8 handlers, and 'if' statements wrap the blocks of code that need to run less often.

Sometimes you may want to unroll a function manually. An example of where this is used is with the switch detection logic. All switches must be polled every 2ms. However, it takes a long time to poll every switch. Instead, we poll half of the switches during 1ms, and the other half on the next 1ms. This spreads out the processing and keeps the length of any one IRQ call from being too long.

To tell the scheduler to do this, first write two separate rtt functions. By convention these are numbered with a suffix, e.g. switch_rtt_0 and switch_rtt_1. Then in the schedule file:

switch_rtt/2 1 280c

The "/2" says that the function was manually unrolled into two separate RTTs. The frequency is now 1ms, because one or the other will be scheduled twice as often now. The duration is the length of time it takes for one of them to run, and is currently assumed to be the same for both.

4.3 Memory Allocation

FreeWPC does not use dynamic (heap) memory allocation, except in some very rare circumstances. All variables should be declared global, or put on the stack.

Global variables cannot be statically initialized. Variables should be initialized explicitly inside a C function.

Global variables can be divided into a number of categories, depending on their usage. Normal globals are always in scope and behave as you would expect. Additionally, you can tag a global with one of the following attributes:

__fastram__
Used for variables that are used frequently. GCC6809 will generate faster code when accessing these. However, you are limited to about 250 bytes of fastram variables total. These are mostly used by RTTs for performance.

Do not use fastram unless you are working in the core.

__permanent__
Used for variables that should be persistent across reboots. These variables should be initialized inside of a factory_reset event, because factory reset should restore everything to a sane state. GCC6809 places these variables at an address that is not automatically cleared by the startup code.

The init or init_complete

__nvram__
Like __permanent__, but for variables that are also write-protected by default. To modify such a variable, you must first call pinio_nvram_unlock(), change it, and then call pinio_nvram_lock(). This helps to ensure that certain critical variables are not accidentally corrupted.

Do not sleep while inside the critical section. Do not nest calls to these functions either.

This feature makes use of special hardware in the WPC ASIC.

Adjustments and audits kept in NVRAM are managed via special APIs which do the locking/unlocking for you.

This feature works even in native mode; the __nvram__ variables are saved to a file when you exit the program.

__local__
Used for variables that are maintained separately for different players in a multiplayer game. The system software will transparently reload these variables with the correct values whenever the player up changes. These variables should be initialized inside of a start_player event.

There is a limit to how many locals can be declared; if you exceed this, you will get a linker error.

4.4 Bit Variables

Although ordinary variables work well, sometimes you only need to store a single bit. For this, instead of declaring a byte variable and wasting 7 of the 8 bits, you can use a flag. Flags are packed efficiently in the memory to avoid wasting space. This can be used helpful on small memory platforms.

The bit APIs are the most generic. You provide a bit number and a pointer to a bit buffer where the bits are packed.

The flag APIs operate on a per-player flag buffer. They are also automatically cleared at player start. You only provide a flag number.

The global flag APIs are similar, but those flags are global and do not change across players, nor across games.

Flag and global flag IDs are distinct. Be careful not to pass a global flag ID to a flag API, or vice-versa.

bit_on, flag_on, global_flag_on
Sets a bit flag.
bit_off, flag_off, global_flag_off
Clears a bit flag.
bit_toggle, flag_toggle, global_flag_toggle
Toggles a bit flag.
bit_test, flag_test, global_flag_test
Tests a bit flag.

4.5 Event Handling

FreeWPC is mostly an event-driven system, which only acts when there are new inputs to the system. This section describes the basic interface to the application layer software, and then explains how that is implemented internally.

When any module wants to be notified when a particular event occurs, it declares an event catcher function, using the CALLSET_ENTRY macro. Here is an example:

     CALLSET_ENTRY (strobe_multiball, sw_forcefield_target)
     {
     	...
     }

This declares a new catcher for the module named strobe_multiball, which is to be called whenever the event sw_forcefield_target occurs.

The name of the module is arbitrary, but the convention is to use the name of the source code file or something similar. If you need to write two different handlers in the same source file for the same event, they need to have different module names.

The event name must exactly match the name used by the generator of that event. There is a list of predefined system events which are often caught (see System Events). Switch names can also be used as events (e.g. sw_free_kick_target). Normally a switch event is generated on an inactive-to-active transition; you can cause an event on any transition by declaring the switch as ‘edge’ in the machine configuration file. Ball devices also generate events for several reasons (see Ball Tracking), for example, dev_lock_enter.

If there are multiple catchers for the same event, then all of them will be invoked, in some random order. For example, another mode might also make use of the same target shown above:

     CALLSET_ENTRY (mode_start, sw_forcefield_target)
     {
     	...
     }

Then both of these handlers would be called when the switch is closed.

To generate an event, use the callset_invoke() API, passing it the name of the event. You can create your own events for any purpose; event names do not need to be declared. Such events are as full-featured as system-defined events. When defining custom events in a game, it is customary to prefix the event name with the short name of the game (e.g. ‘tz’).

Event catchers are allowed to throw new events. This will result in nested function calls.

4.5.1 Boolean Events

Most event handlers do not return a value. However, in some cases it is desirable to stop calling event handlers once the event has been claimed. You can do this with boolean events.

To throw a boolean event, use callset_invoke_boolean(). This returns TRUE if all of the event handlers return TRUE; else it returns FALSE.

The event handlers are declared using CALLSET_BOOL_ENTRY instead of CALLSET_ENTRY, and they must return either TRUE or FALSE.

The invocation of boolean handlers will immediately stop if one of them returns FALSE (so-called short-circuit evaluation).

4.5.2 Debugging Event Handlers

The runtime code generated by gencallset will call the macro callset_debug before invoking each of the handlers. This macro is passed one argument, which is a 16-bit integer that is unique to every handler. The default implementation is to save this in persistent memory; if a crash occurs, after rebooting the value can be examined to help determine the last function that caused the problem.

Previously, debugging required a compile-time flag set in CALLSET_FLAGS, but this is no longer needed.

For more thorough debugging, you can rewrite the implementation of callset_debug to do something else with those IDs, such as print them or set a breakpoint.

4.5.3 How Event Handlers Are Implemented

When you write a CALLSET_ENTRY, the module name and event name are concatenated to form the name of a function, separated by an underscore.

When you write a callset_invoke, it calls a function named callset_event. gencallset scans all of the source code and creates these functions; you can see the output in build/callset.c. So there is no queueing, memory allocation, or anything complicated — these are just ordinary function calls. The trick is to do all of the work at compile-time.

Because event handlers are just function calls, they can sometimes become deeply nested. For example, a start button press can cause many other events to be thrown. On the 6809 hardware, the stack size is limited and a stack overflow can occur if functions nest too deeply, AND a call to task_sleep is made. As long as you don't sleep, there is no problem; the limitation is in the size of the per-task stack area. start_game and test_start are particularly problematic sometimes. Avoid sleeping in such handlers to be safe; if necessary, use a periodic function instead.

4.6 Template Drivers

A template is a crude implementation of a reusable code block, similar in purpose to C++ templates but much more limited. Were FreeWPC written in C++, regular templates would have been used here.

Templates end in .ct (for C template) and are always kept in the drivers/ directory. A template file is instantiated via commands in the machine description. The script ctemp converts a template to one or more .c or .h files, which are then compiled normally. Thus, templates can contain RTTs or event handlers just like any other file.

4.6.1 Template Syntax

A template contains ordinary C code, plus special template directives. These always begin with two at-signs (@@). The list of valid directives, in the order that they are normally used, is listed below:

@@class
Gives the name of the template; usually this is related to the filename.
@@parameter name
Declares a template parameter. The value is substituted during instantiation.
@@file
Sets the current output filename. A template can generate any number of output files. You can switch between the different output files with this directive.
@@
A template comment. Used to insert a comment line that is not placed into the current output file. Ordinary C-like (or shell) comments will be emitted into the output files like any other non-directive line.

Within the C code, you use single-at (@) references to substitute the values of template variables into the text. For example,

     @@class xyz
     int @class_variable

would be translated into the following plain C code:

     int xyz_variable

Notice that template variable names cannot contain underscores.

You define your own template variables with the @@parameter directive. Some variables are predefined by the code generator:

@class
The name of the template, as given in a @@class directive.
@self
The name of the instance. If a template is instantiated multiple times, each should have a different name. For example, the ‘sling’ class might have instances named ‘left_sling’ and ‘right_sling’.
@instance
Like @self, but a zero-based ID. These are assigned by the code generator during instantiation.

Variable substitutions can occur just about anywhere. For example, you can use a variable as the argument to the @@file directive. In fact, this is generally how you would use it. Consider the following:

     @@class widget
     @@file @class.h
     @@file @self.h
     @@file @class.c
     @@file @self.c

Here, the template named widget is defined. It generates four different source files: two based on the name of the class (widget.c and widget.h), and two based on the name of each instance. If there were 2 widgets instantiated by the machine, say a left widget and a right widget, then we would have left_widget.c, left_widget.h, right_widget.c, and right_widget.h.

4.6.2 Template Usage

The use case for templates is to define reusable device drivers. From game to game, there is commonality on how certain devices should be treated, although the parameters are different.

Typically, a device requires a number of different functions to control it. Most devices need a realtime function if hardware accesses must be frequent. A periodic function can handle less critical actions. APIs are defined for the rest of the logic to access it. All of this can be put into a single template, then instantiated by each machine with values for the machine-specific parameters.

Currently there are device drivers for all of the following:

4.7 Fault Detection

Fatal errors when detected will cause the system to reboot. The system API fatal() can be used to cause a fatal error at any time. A list of the error codes is kept in include/sys/errno.h.

There is also the nonfatal() API for logging errors which will not cause a reset. These are audited and when DEBUGGER is defined, it will display a brief message on the DMD.

The core operating system code throws fatal errors in several key circumstances:

4.8 Timers

There are a number of ways to implement timers. There are slight differences between them all.

4.8.1 Task Timers

The first way is just to use task_sleep from within task context. A task can sleep in between actions to enforce a particular delay. Note that this gives a minimum delay; the actual delay may be longer.

If you are not in a task context that can sleep, you can create a new task which is allowed to sleep. Starting and stopping such tasks is equivalent to starting and stopping a timer. Querying the task by its GID is equivalent to checking if the timer is still running.

If you want to use this model, consider using the timer_xxx APIs, which do this underneath a standard API that clearly shows what you are trying to accomplish.

Because these timers are based on tasks, they do not need to be statically declared in advance. They are limited in number only by the maximum number of tasks.

4.8.2 Free Timers

Sometimes you may want a timer, but without the overhead of a separate task. Then use the free_timer_xxx functions. Free timers are small counters that are updated in realtime context. They do not support pausing (hence, the name) but can be started/stopped. The set of all free timers must be declared in advance in the machine configuration file.

Free timers are useful for the case when you need to run a timer, and no code needs to be executed when the timer expires. You can always write code to query the timer value, but if it expires to zero, then nothing special happens. One common use is for detecting certain playfield shots when a series of switches needs to activate within a certain amount of time, say 1 second apart.

4.8.3 Periodic Functions

4.8.4 Realtime Functions

4.9 Bank Switching

The 6809 processor does not allow all of the ROM image to be accessible at once. 32KB of the ROM (the uppermost portion) is fixed and can be accessed at all times. The remaining ROM is divided into "banks" or "pages" of 16KB each. Access to these areas is a little more complicated.

You can use the page_push and page_pop functions to change the current banked area. You must only call these from code running in the fixed area, otherwise the current function becomes inaccessible! Many kernel functions use these to retrieve data that is placed into specific, named banks (e.g. fonts and DMD transition functions).

The GCC6809 compiler supports function calls across all of the banks without much effort from the programmer. Each C file that is compiled must have all of its code and constant data placed into the same bank. You specify which bank via the Makefiles. For example, suppose you add a new file to the common directory, named newfile.c. In common/Makefile, the line:

COMMON_BASIC_OBJS += common/newfile.o

adds this module into the COMMON_OBJS list; all of these objects are part of the COMMON_PAGE bank. In the top-level Makefile, you can see that COMMON_PAGE is mapped to bank 56.

There are a number of predefined sections, like COMMON, EFFECT, TEST, and TEST2. More can be added by modifying the topmost Makefile. Each of these is then mapped to a physical bank of ROM; it is possible for multiple sections to map to the same physical page.

The Makefiles only control the placement of code and data. To make function calls between sections work correctly, you also need to modify the C prototypes.

When the compiler encounters an ordinary prototype, such as:

void foo (void);

then a call to foo() is an ordinary call with no consideration for banking. However,

__attribute__((page ("56")) void foo (void);

says that foo() will be physically placed in page 56, and a far call might be needed. If the calling function is not in page 56, then the compiler will emit some special code to switch the bank, make the call, and restore the bank. This even works when the caller is in a different bank and not in the fixed region. When each file is compiled, it is told which page it is in using the -mfar-code-page option.

Because this mechanism is used so often, there are macros to make it easier. The above prototype could be rewritten as:

__common__ void foo (void);

See include/env.h for a list of the macros that can be used. In general, every section has an attribute that can be used like this.

Because placement is a somewhat manual procedure, it is possible for a bank to become full. When that happens, code often must be moved or new sections added. This is rare but does happen from time to time.

5 Core APIs

The core APIs provide a common set of hardware access functions that can be used by game designers. The interfaces are designed to be portable to all pinball hardware platforms and all machines. However, the underlying implementation may vary.

These functions do not access the hardware directly, but instead, they call into a device driver which may be platform-specific. See the section on Platform APIs for information on drivers perform the actual I/O.

These APIs are intended to be called from the application layer (what other operating systems might refer to as "user space"). For example, there are core APIs to poll the state of switches (active, or inactive?). Under the hood, the switch driver must deal with the fact that switches are arranged in rows and columns, and that some switches are optos: this is all transparent to the user.

Core APIs are all defined in the kernel directory. Any functions which are not static may be called from game developers from within normal task context at any time.

5.1 Switches

Switches are present in all pinball games to receive input from the ball and the player.

Switches are numbered from 0 to NUM_SWITCHES-1. How the switch numbers are mapped to actual inputs is platform and machine dependent. The machine description names the switches and defines their physical characteristics.

Each switch entry can declare the following flags, which alter the way that the switching code will manage it. The capitalized name is used in the C code; the lowercase name is the spelling used in the machine config file.

In addition to polling their levels, the switch driver will detect when switches have changed state, and invoke their event handlers. The switch entry in the config file names a function to be called when these changes occur.

The driver performs debouncing, so that rapid open and close are not considered. By default, a switch is processed if it remains active for only 4ms. You can declare a larger debounce time using the debounce tag. A transition that lasts for less time is ignored.

Switch entries can also declare an associated playfield lamp; when this is done, valid activations of the switch will cause a brief flicker of the lamp.

switch_poll
Read the stable, physical state of a switch. Returns 1 if it has a high level, or 0 for a low level.

Switch readings are polled by the switch driver continuously; this API just returns the results of the last time the switch was examined. How often switches are polled is platform-dependent; on WPC, it is every 2ms.

switch_poll_logical
Read the stable, logical state of a switch. Returns 1 if it is active, or 0 for inactive. This is the same as switch_poll but it inverts its result for optos.
rt_switch_poll
Like switch_poll, but only for use inside realtime tasks. It is slightly more efficient. It always returns a physical level; drivers must invert for optos.

5.2 Lamps

Controlled lamps are used to highlight shots on the playfield and to keep track of the progress of scoring features. Unlike general illumination, each lamp is individually controllable by the CPU.

Lamps are numbered from 0 to NUM_LAMPS-1. For each lamp, there are several flags that can be set to determine its physical state. The job of the lamp driver is to combine all of these flags into a single output value, and send that to the underlying hardware.

Every lamp has a basic state, which is either on, off, or flashing. The basic state of a lamp is a per-player value and is automatically saved and restored as players change in a multiplayer game. They are controlled by the following APIs:

lamp_on
Turns on a lamp.
lamp_off
Turns off a lamp.
lamp_test
Tests whether a lamp is on/off.
lamp_toggle
Toggles a lamp's on/off state.
lamp_flash_on
Enable lamp flashing. Note that the steady state of a lamp must be off in order for the flashing to be seen. (I.e., if you call lamp_on(), and then lamp_flash_on(), the lamp will be steady.)
lamp_flash_off
Disable lamp flashing.
lamp_flash_toggle
Toggle lamp flashing.
lamp_tristate_on
Ensure the lamp is steady.
lamp_tristate_off
Ensure the lamp is off.
lamp_tristate_flash
Ensure the lamp is flashing.

The basic state may be temporarily overriden via a lamp effect. An effect can allocate a lamp, thereby overriding its basic value. When the effect finishes, the lamp shows its basic state again. See Lamp Effects.

In the machine description, you can attach a color and coordinates to each lamp. These are not used by the core OS, but they can be used by lamp effects that manipulate the lamps in certain patterns.

5.3 Solenoids

Solenoid outputs are used for many purposes. We use the term 'solenoid' or 'coil' generically to refer to any writable, high power output. They could be connected to coils, flashers, or motors. Like the lamps, they are individually controllable.

Unlike the controlled lamps, the core API does not permit you to turn on a solenoid permanently. Instead, you either use a pulsing API, which turns it on for a short period of time (and the system turns it back off for you), or you use a custom driver to control it.

5.3.1 Pulsing

Pulsing is used for solenoids that are operated infrequently, and only for short durations at a time. These are normally used for kickers, poppers, etc. The system queues the requests and executes them one at a time, never in parallel.

The default pulse settings are controlled by the entry in the machine description, in the "drives" section. The time parameter sets the total length of time for the pulse. It can be an integer value in milliseconds, or one of the TIME_xxx values which are in multiples of 16ms. The duty parameter controls the duty cycle; use one of the SOL_DUTY_xxx defines. SOL_DUTY_100 keeps the driver on for the entire interval; SOL_DUTY_50 keeps it on half the time, delivering half the power; and so on. The minimum duty cycle is 1/8th full power.

If you do not specify either of these values, you will get a reasonable, but conservative default. Solenoids and flashers may have different default values. A large portion of getting a new game to work correctly is determining the correct values to use here.

sol_request
Pulse a solenoid, and wait for it to be fired. A request may be delayed for a number of reasons. This should only be used inside of a task context where it is acceptable that the call may block for a while.
sol_request_async
Pulse a solenoid, but don't wait for it to be fired. This is the preferred method to use. This function returns immediately, before the pulse occurs.
sol_req_start_specific
This is the most flexible API, which allows you to specify the exact duty and time parameters that you want, overriding the defaults from the machine config. It is not recommended for ordinary use. It is exposed by test mode to allow you to try different settings to see how they behave.

5.3.2 Flashers

The flashlamp APIs are similar to the solenoid pulse APIs. A flashlamp pulse is always with a 100% duty cycle, but for a much shorter period of time.

Note: it is currently possible to use the solenoid pulse APIs on flashers, but this should not be done. This will likely be enforced in the future.

flasher_pulse
Pulse a flashlamp for a nominal period of time.
flasher_pulse_short
Pulse a flashlamp very quickly.
flasher_start
This is a lower-layer API that allows you to specify the exact duration and duty cycle for a flasher. Times longer than FLASHER_TIME_DEFAULT are not recommended. The duty cycle can be specified using one of the SOL_DUTY_xxx defines to pulse it more dimly than usual.

5.3.3 Custom Drivers

Custom drivers can run for longer periods of time. They are allowed to run in parallel with anything else. They will frequently use duty cycling to avoid keeping the coil 100% active for too long, and thus they often require realtime processing to switch reliably.

These types of drivers are all implemented using templates.

5.4 Dot-Matrix Display (DMD)

Dot matrix APIs should only be called from display effects.

There are two key objects you should understand: pages and windows.

A display page is special RAM which holds the frame buffer that determines which pixels are turned on and off. Display memory cannot be accessed like ordinary memory, though; it does not have a fixed address.

A display window is an address that the CPU can use to read/write display memory – that is, it sees one of the display pages.

There can be more display pages than windows, so not all pages are accessible at all times. Through mapping calls, you decide which pages are mapped to which windows.

You must go through several steps for each frame you want to write. For each step, there are several APIs to choose from.

Other APIs are listed below.

dmd_flip_low_high
Flip the mappings, so that the low page appears in the high region, and vice versa.
dmd_rough_copy
dmd_rough_erase
Erase a rough region of the low page. The x coordinates must be 8-bit aligned.

5.5 Sound Board

At present, sound support is very WPC-specific. The sound board is an external device that is controlled by sending commands over a serial-like interface. Two flavors of sound board are supported: WPC, and DCS. The same APIs are used in either case.

sound_send
Sends a command to the sound board. The command may be 8-bit or 16-bit. The meaning of the command argument varies widely.

See also the section on Sound Effects for other APIs that can be used.

5.6 General Illumination

General illumination allows for additional lighting that is managed in groups of lamps, which are all enabled and disabled at the same time. These are often referred to as GI "strings". FreeWPC supports a maximum of 8 strings.

On the WPC platform, GI can be dimmed, because the controls for these strings are governed through triacs. (On WPC-95, some of the GI are dimmable and some are not.)

gi_enable
Turns on one or more GI strings. The argument given is a bitmask where each bit corresponds to an entire set of lamps (thus, we support at most 8 strings).
gi_disable
Turns off one or more GI strings.
gi_dim
Dims one or more GI strings. This is like gi_enable, but it takes an extra parameter, which specifies the intensity of the lamps. Not all hardware supports dimming. If dimming is not supported, then this API is unavailable and will cause a compiler error.

5.7 Real-Time Clock

On machines with a realtime clock, these APIs let you set and get the current time of day. At present, only WPC is supported.

The chip provides three registers for counting minutes, hours, and days. FreeWPC keeps the calendar time in persistent memory. About once per minute, it polls the RTC and updates the calendar time to reflect any changes.

rtc_read
rtc_write

5.8 Debug Port

6 Effects

Effect processing is a layer above the hardware which provides controlled access to the lamps, display, flashers, and general illumination. It resolves contention when multiple tasks try to access the same object at the same time through the use of priorities.

The system manages three distinct types of effects: display, lamps, and music. They are fully independent of each other. It is not possible to request that all three types do something in sync with one another. There are similarities between the way each type is handled, but all three have fundamental differences.

Lamp effects manage the controlled lamps, plus the GI and flashers.

6.1 Basic Effect Principles

These principles apply to all types of effects.

At any time, there is at most 1 effect that is running of each type. The running effect is the one which has been started with the highest priority. Priorities are simple integer values: the higher the number, the more important the effect is. A list of predefined priority values is defined in priority.h; these files begin with PRI and have been carefully defined relative to one another. Use one of these values when possible.

It is possible for no effect to be running if none has been started. In that case, the current priority is considered to be zero. Actual effects always have a priority that is non-zero.

The running effect is executed in a separate task context, except for music, which is executed on a separate CPU. The currently running display effect always has the group ID GID_DEFF; likewise GID_LEFF for the lamps. Effects that are started but not running do not have a task assigned to them, and are simply marked as started in memory somehow. Because they do not have enough priority to run, there is nothing for them to do.

Effect tasks can be preempted, if a higher priority effect is started later. In that case, the old task is killed, and a new task is started. When the task is killed, there is no guarantee as to what was doing previously, and there is no opportunity for it to do cleanup on its own.

Effect functions must be declared in the machine configuration file. Each entry names the function to be called to run the effect, along with its priority and other optional properties.

The APIs and implementation for display/lamp effects are mostly the same, but there are differences due to the following:

6.2 Background Effects

The display and music should always be doing something, even when nothing really important is happening. Thus, these effect modules implement background effects, a way to determine what to do in these circumstances. Lamp effects do not support a background mode; it is OK for no effect to be running, as the lamps have a default state that shows through.

The strategy for handling background effects is as follows: When there is no explicitly started effect, an event is generated: either display_update or music_refresh. Any module that can provide a background effect should implement these handlers.

The handler can decide which effect, if any, it wants to provide by calling either deff_start_bg or music_request. (If the conditions are not correct, it can simply return without doing anything, as with a mode that is not running.) The handler request includes the ID for the deff or music to be started, plus a priority value. After all of the event handlers have been invoked, the one with the highest priority wins and will be started.

The common code provides many of the implementations to deal with the usual cases. For example, while a game is running, the default effect is to show the game scores. Default music codes can be defined for different circumstances: normal game play, ball at plunger, during bonus.

Because handlers can use the values of other variables in the determination (are we in game, for example?), the calculations need to be redone periodically, in case the conditions change. To force a refresh of both the display and music, call effect_update_request. The update will take place within the next 100ms; the delay is intended to smooth out multiple such requests within a short time, and the delay is not really noticeable.

When a foreground display or music effect exits, an update will take place immediately. In this case, if display update is needed, then music is not updated, and vice-versa.

If an update is not requested, the display and music will still be refreshed periodically by the system, about twice per second, as the conditions checked within the handlers may have changed. Because of this, effect_update_request is really an optional call, and only forces the update to happen faster.

6.3 Display Effects

Display effects are defined in the [deffs] section of the machine configuration file. Both foreground and background deffs are defined here. Each deff has an implementation function, a priority, and one or more of the following properties:

deff_start
Starts a foreground display effect.
deff_stop
Stops a foreground display effect.
deff_exit
Exits from the currently running display effect.
deff_nice
Change the priority of the currently running display effect. If it drops below the priority of something else waiting, this will cause it to be preempted.
deff_update
Update the background display effect.

6.4 Lamp Effects

Lamp effects are defined in the [leffs] section of the machine configuration file. Each lamp effect defines a function that executes when the effect is running, plus it says which of the lamps, flashers, and G.I. strings it is able to modify. Attempts to modify unallocated objects have no effect.

The objects allocated to a lamp effect are guaranteed to be turned off when the function starts, and are restored to their previous states when the function ends.

A lamp effect can also be declared as shared. A shared leff can run in parallel with the current running leff, and with other shared leffs. Shared leffs have a lower priority than normal leffs; the effect can be masked. Also, shared leffs run to completion and are never preempted.

Shared leffs are usually for indicating low-priority game state that requires constant updating, and where it is not appropriate to use the default lamp state. Normal leffs are intended for light shows.

Lamp effects cannot be aborted by the flippers like display effects.

If a normal leff cannot be started, it is forgotten and never queued, like display effects can be.

leff_start
Starts a lamp effect.
leff_stop
Stops a lamp effect.
leff_restart
Stops and then starts a lamp effect again.
leff_on, leff_off, leff_toggle, leff_test
Called from within a lamp effect, sets/gets the state of a single lamp that has been allocated to it.
gi_leff_alloc, gi_leff_enable, gi_leff_disable, gi_leff_free
Called only from within lamps effects. These modify GI strings overriding their default settings.
leff_exit
Exits from the currently running lamp effect.

6.5 Sound and Music Effects

The main difference with sound and music is that the main CPU does not implement the effect; they are generated by a separate board. The main CPU sends commands to the secondary board to request that a particular sound be played. Because of this, no running task is required for the effect. (Platforms other than WPC may differ in this regard.)

Sounds and music may also overlap, depending on the capabilities of the sound board. Each platform defines a set of independent channels. When a sound needs to be played, you can specify a subset of channels that it can be played on. If one of them is free, the sound can be played. The system tracks which channels are free or in use.

There is no easy way to know when the sound has finished playing. The sound board is capable of sending responses back to the CPU board, but this has not been explored thoroughly yet. For now, sound calls need to specify how long the sound will take to complete, so that the channels can be deallocated. Exact precision is not really necessary here, unless trying to string together a sequence of speech calls.

The music channel is treated special, because it is the one used for the running background track. All other sounds are transient, whereas the music plays in a constant loop.

Some sound effects need to preempt the background music, so that both are not heard simultaneously. When a sound effect allocates the music channel, that kills the background music first; when the temporary sound finishes, the background is automatically restarted.

music_update
Request an update of the background music.
sound_start
Start a sound effect. This operates at a layer above sound_send, which is the low-level API to send a command to the sound board. This API handles priority, channel allocation, and tracks the duration of effects.
sample_start
Start a sound sample call.
speech_start
Start a speech call.
music_effect_start
Start a music effect that preempts the background.
music_disable
Disable the background music.
music_enable
Reenable the background music.
music_timed_disable
Disable the background music for a given amount of time; it will be reenabled automatically once the time expires.

6.6 Lamplists

Typically, groups of lamps are often used for a similar purpose. A lamplist is an ordered array of lamps that are often controlled together. Each lamplist has a unique ID, and is internally represented by an array of the lamp values, terminated by a special lamp code named LAMP_END.

The lamplist APIs provide convenience whenever many lamps need to be updated at once.

lamplist_index
Return the nth member of a lamplist.
lamplist_first_entry
Return the first lamp in a lamplist.
lamplist_last_entry
Return the last lamp in a lamplist.
lamplist_previous_entry
Return the entry before a given one.
lamplist_next_entry
Return the entry after a given one.
lamplist_apply
Apply a lamp operator to all members of a lamplist. If a break is encountered, a pause will occur.
lamplist_apply_nomacro
Apply a lamp operator to all members of a lamplist, ignoring breaks.
lamplist_test_all
Return true if all members pass a given boolean test. The test operator lets you apply different test conditions, for example, testing the lamp's default state versus its flashing state.
lamplist_test_any
Return true if any member passes a given boolean test.
lamplist_apply_leff_alternating
Alternatively set and clear every member of a lamplist. You can specify whether the first lamp will be set or cleared. This can only be used inside lamp effects.
lamplist_step_increment
Turn off the lamp that is currently on, and turn on the next one.
lamplist_step_decrement
Turn off the lamp that is currently on, and turn on the previous one.
lamplist_build_increment
Turn on the first lamp in the list that is not on.
lamplist_build_decrement
Turn off the last lamp in the list that is on.
lamplist_rotate_next
Rotate the lamp states forward.
lamplist_rotate_previous
Rotate the lamp states backward.

6.7 DMD Transitions

A transition is an interim effect that runs between the end of one display effect and the beginning of another. By default, a new effect will simply kill the old one and overwrite it with a new image.

Transitions are scheduled – a handler is installed that runs the next time that a new image is shown. Instead of showing the image right away, the transition handler kicks in and displays some combination of the present view and the new one. When the transition finishes, the display should consist entirely of the new image.

Currently, all DMD transitions are contained in common/dmdtrans.c and segment transitions are in kernel/segment.c.

6.8 Background Refresh

In addition to the display and music effect updates, there are a couple of other things that need to be refreshed periodically. The default states of the lamps, outside of any running effect, sometimes need to change depending on a complex combination of factors.

For example, consider a feature that needs to be disabled temporarily during any multiball. Instead of using the lamp bit to track whether the feature is enabled, we should use a separate bit flag, and then draw the lamp if the flag is set and the feature is not masked by a multiball.

The lamp_update event is generated periodically by the system to give modules a chance to recalculate the states of the lamps. Currently, it is generated about once per 500ms. Code can request a faster update by calling effect_update_request as before.

Likewise, sometimes solenoid-driven devices require a periodic update as well. Ball scoops that raise from the playfield, like the trap door on Funhouse, can either be up or down, and the desired state depends on a number of things. A separate device_update event is thrown for the purpose of updating these. It works identically to the lamp_update function; however, these events are generated more frequently, as they are more important to be right in a timely manner.

Note that device update is disabled during ball search, so that search handlers can complete control of the devices to try to find the missing pinball.

7 Ball Tracking

The ball tracking module manages the locations of all of the pinballs in the machine. It is implemented in common/device.c and uses container declarations in the machine description.

A device is defined to be something which can hold and release pinballs. (Earlier versions of the software used the term container.) A device is comprised of one or more switches which count the number of balls in the device, plus a release solenoid that can be pulsed to force one ball to be removed.

Ball troughs, kickouts, poppers, saucers, and locks are all just different shapes that a device can take. Some containers only hold one ball, while others can hold many.

7.1 Device Status

For each device, the ball tracker maintains the following state information:

The desired count is crucial to understanding the module's behavior. Whenever the device has more balls than desired, the ball tracker will initiate one or more releases to try to bring the count down to the desired number. Otherwise, any balls that enter the device are kept there; this is used to implement ball locks.

Software requests to kick a ball are always done by manipulating the desired count. The device module has complete control over the actual release solenoid.

The trough device is the only device which normally desires to keep any balls. Playfield devices generally desire zero balls unless a lock has been enabled.

Device switches must be declared as edge switches, which invoke the event handler on both open and close transitions. The tracker simply updates the current count on either type of transition.

7.2 Global Status

The ball tracker also maintains a count of the number of balls in play, which is the total number of balls installed, minus the number of balls that are seen in devices, minus the number of balls declared "missing". Missing balls are flagged when a game is started but the balls are not all seen.

7.3 Events

The ball tracker also generates new per-device events. The name of the event includes the name of the device: for example, dev_trough_enter.

The enter event is generated when the current count goes up by 1. Game code should normally always register handlers for the enter events rather than individual switches. Note that a switch closure does not always indicate enter: consider an eject that is trying to kick out but the kicker is weak, so that the ball falls back in immediately. Also, when a ball trough releases a ball, a whole series of switch closures occur rapidly, but eventually it stabilizes. The ball tracker waits a little while after switch change before it begins to take action.

When a release is necessary, the module first generates a kick_request event. This is a boolean event; handlers can return FALSE if the request should be postponed for some reason. In that case, the module will retry again later. Once the request is allowed, a kick_attempt event is generated just before the solenoid is pulsed. This is typically caught in order to play a sound effect. If the pulse fails, the attempt event may be thrown multiple times.

The kick_success event is generated when the release is deemed successful: when the device count goes down by 1. The kick_failure event is instead thrown when an attempts fails repeatedly and the device is abandoned.

7.4 The Trough Device

The trough device is special; every game must have one. The system completely handles the device events generated for the ball trough.

The function device_add_live is called whenever a new ball is successfully served. The live count of the machine is the number of balls in play for the purposes of game code (i.e. not counting missing pinballs or balls locked on the playfield from previous games). The single_ball_play event is generated when the live count is reduced to 1. Multiball modes use this to stop properly. There is also a ball_count_change event invoked on every change of the number of balls in play; some games use this to update playfield multipliers.

Likewise, device_remove_live is called whenever a ball drains. It ultimately is the one that generates the end_ball event when all balls have drained.

Live count is not necessarily the actual number of balls in play. If a missing pinball suddenly reappears, the live count is not increased.

7.5 Virtual Balls

The device system generally assumes that there is one ball-sensing switch for every ball that might be in the device. Thus, a 4-ball trough needs 4 switches. The tracker recounts balls periodically and does this just by polling each switch to see if a ball is present or not.

For some devices, this is not possible. Consider the Twilight Zone gumball machine, which can hold 3 or 4 balls but there are no switches inside to verify this. To handle this case, virtual balls are supported. This is a per-device counter that is completely software controlled. APIs exist to add/remove virtual balls that cannot be seen by switches. Machine-specific code must be written to manage this.

When a device driver knows for sure that a ball has entered the device, it calls device_add_virtual. The extra ball will be tracked and counted even though there is no switch to see it anymore. When virtual balls are added, the device enter event is thrown as usual, as if a physical ball had entered.

When it knows that a ball has exited the device, it calls device_remove_virtual. This decrements the virtual ball counter. This behaves exactly like when a real ball is kicked out, so the kick_success event is thrown and the live ball count goes up.

8 Test Mode

The FreeWPC test mode works similarly to the original WPC menu system. However, there are extra options and a few important differences. For testing core changes to FreeWPC, it is often more convenient to use test mode than to play a game.

You can navigate the menus with the flipper buttons in addition to the coin door buttons. Use Start to enter, and double flip to exit.

When compiling with the MACHINE_TEST_ONLY option, the game will boot directly into test mode, and there is no provision to start a game.

8.1 Adjustments

All of the standard adjustments are supported. There are also a few new adjustments.

Timed Game allows you to switch between normal game play and a timed game. Machines can implement different rules for timed play or not. They can also disable it entirely.

Payment Type configures whether the display asks you to insert coins, bills, or swipe a card.

8.2 Audits

All of the standard audits are supported.

Presently, audits are kept as 16-bit integers, so they max out at 65535. This will probably change at some point.

8.3 Tests

The test menu contains roughly the same tests as in the actual WPC games, with some differences.

The solenoid test lets you control the duration and duty cycle of each coil pulse by using the flipper buttons.

The development menu contains a series of additional tests that are probably only relevant to developers and not to end users. Current tests include:

8.3.1 Font Test

Cycles through all of the available fonts. Use the flipper buttons to scroll through the alphabet. The machine will try to display as much as possible at once, depending on the font size.

8.3.2 Display Effect Test

Cycles through all display effects. Press ENTER to activate a deff, as if deff_start were called. For long-running effects, press ENTER again to stop it via deff_stop.

8.3.3 Lamp Effect Test

Cycles through all lamp effects. Press ENTER to activate a leff, as if leff_start were called. Press ENTER again to stop it.

Remember that multiple lamp effects can be active simultaneously if they are declared as shared and they do not overlap in the lamps that they use.

8.3.4 Lamplist Test

Cycles through the lamplists. The flipper buttons cycle through the different ways that the lamplist can be controlled. The default is to turn on all of the lamps. You can also strobe them, flash them, etc.

8.3.5 Ball Device Test

Cycles through each of the ball devices, telling you its current status (how many balls it sees, kickouts pending, errors). The flipper buttons will scroll through a set of actions (empty, kick 1, etc.); press ENTER to perform the action displayed.

This test also continuously shows the global device status, such as how many total balls are detected and whether any balls are missing.

8.3.6 Random Test

Tests the random number generator.

8.3.7 Transition Test

Cycles through all of the dot-matrix or alphanumeric transition effects.

8.3.8 Frame Test

Cycles through all of the display frames.

8.3.9 Force Error Test

Entering this test forces a fatal error, which should restart the system.

8.3.10 Display Stress Test

Starts randomly starting and stopping display effects until exiting the test.

8.3.11 Symbol Test

Cycles through the symbol bitmap, a collection of arrows, boxes, etc.

8.3.12 Score Test

Advance through the various combinations of players per game and player-up, to show how the score screen would look.

8.3.13 PIC Test

Displays vital data about the PIC.

8.3.14 Memory Editor

Lets you interactively scroll through the CPU's memory and alter values.

9 Common Pinball Library

The files in the common directory provide a library of generic pinball functions that can be reused from game to game. Some of the more important ones are described in detail in this chapter.

9.1 Coins and Pricing

FreeWPC implements a weak form of coin switch handling. It is not very robust and does not time the coins as it should.

If FREE_ONLY is defined, it will build a ROM that doesn't require coins.

9.2 Extra Balls

This module tracks lit and collected extra balls. Lit extra balls can be easy (lit until end of game) or hard (lit only until end of ball).

In the machine config file, you should tag the lamp that indicates an extra ball is lit as extra-ball, and the shoot again lamp as shoot-again. Then the lamps will automatically update for you when these APIs are called.

light_easy_extra_ball
Lights an easy extra ball.
light_hard_extra_ball
Lights a hard extra ball.
eb_light_adjustable
Lights an easy or hard extra ball, depending on the value of an adjustment.
collect_extra_ball
Call this from the switch handler of the shot that scores the extra ball.
can_award_extra_ball
Returns true if an extra ball is allowed to be lit/awarded. This consider limits of extra balls per game.

There is also an API special_award to award a special, but it does not manage any lamps automatically. It just obeys the adjustment to award whatever has been configured for special, and fires the knocker.

9.3 Tournament Mode

FreeWPC implements a tournament mode module. Note this is what newer Stern games refer to as competition mode.

Tournament mode can be enabled globally in the adjustments menu, or it can be enabled by holding down the left flipper button briefly before starting the game. A message will indicate that tournament mode has been enabled. It affects all players in the game.

Software should check the tournament_mode_enabled boolean variable to determine if tournament is in effect.

9.4 Ball Search

Machines don't need to do much to produce a working ball search function. The common code knows how to pulse all of the regular solenoids. It also knows which not to fire anything marked as a motor or flasher. You can mark a solenoid in the machine description with nosearch to ignore other solenoids.

Machines that need to handle a particular power driver in a non-standard way (by using a template driver) should implement a ball_search event handler, and mark the associated solenoid as nosearch. The ball_search handlers will be called along with the automatic pulsing.

For example, on Funhouse, the Rudy saucer eject needs to make sure that the mouth is open before kicking.

Solenoids associated with ball devices are also skipped unless they are empty.

Game code can call ball_search_timeout_set to set the amount of idle time that must expire before a ball search will occur. The default is 15 seconds. The timer resets anytime a playfield switch (one marked with the SW_PLAYFIELD flag) triggers. You can also manually reset it manually using ball_search_timer_reset.

9.5 Knocker Driver

Call knocker_fire to fire the knocker. If a coin meter is attached to the knocker coil, it will not be pulsed. If the machine defines a sound effect for knock, that will be played instead of pulsing a solenoid.

9.6 Adjustments

Adjustments are 8-bit variables kept in persistent storage. Each group of related adjustments is checksummed to verify integrity. Adjustments can be checked by just reading the variable; there is no special API to do so.

9.7 Audits

Audits are 16-bit variables kept in the non-volatile area of memory. They are generally incremented via the audit_increment API, which adds 1. They can also be incremented by an arbitrary value, via audit_add; or they can be assigned via audit_assign.

9.8 Scoring

A global playfield multiplier is supported; use score_multiplier_set to change it. It is automatically set to 1 at the beginning of each ball.

Scores can be stated in two ways: as a 5-byte, binary-coded decimal value, or as an 8-bit "score code". The long values allow for arbitrary values up to one billion points. The short values are more compact and index a table of common score values, which are listed in the machine config.

The first set of APIs operate on arbitary BCD score buffers:

score_zero
Zeroes a score.
score_copy
Copies one score to another.
score_add
Adds one score to another.
score_sub
Subtracts one score from another.
score_mul
Multiples a score by an integer.
score_compare
Compares two scores, as memcmp would do.

The second group of APIs increment the current player's score by a fixed value.

score
Award score. The input is an 8-bit score code.
score_long
Also awards score. The input is another BCD score buffer.
score_multiple
Like score(), but also takes a multiplier argument. This multiplier and the global score multiplier are taken into account.
score_long_multiple
Like score_long and score_multiple. This is the most low-level API; all others ultimately call it.
score_long_unmultiplied
Awards score without any multipliers.

The third group of APIs, called the ladder APIs, add score according to some rule.

9.9 Lamp Timers

A lamp timer is a countdown timer which is tied to a playfield lamp. The amount of time remaining controls how fast the lamp flashes. When the timer reaches zero, the effect stops.

You declare a structure of type struct lamptimer_args, which names the lamp and the initial timer value, in seconds.

The lamp is modified as part of an internally generated lamp effect; thus, the basic state of the lamp is retained. While the lamp timer runs, the basic state is overriden by the effect.

lamp_timer_start
lamp_timer_find
lamp_timer_stop

9.10 Diagnostics

9.11 Score Rank

The score rank module is optional. When enabled for a game, the system will monitor the relative changes in player's scores over the course of a multi- player game, and throw a rank_change event whenever the current player moves into a new place. It is up to each game to decide how to handle it.

9.12 Timed Modes

To write a timed mode, you need to do two things:

First, create a structure of type struct timed_mode_ops and fill out all of the required information:

Second, handle several system events which affect the mode's operation:

timed_mode_music_refresh.
Call this from a music_refresh handler, passing it a pointer to the mode ops struct.
timed_mode_display_update.
Call this from a display_update handler, passing it a pointer to the mode ops struct.

From outside the mode itself, other modules call these APIs to interact with your mode. The mode ops struct is part of the public interface.

timed_mode_begin
timed_mode_end
timed_mode_running_p
Returns true if the mode is running, for the purposes of scoring. This returns TRUE while the mode is in its grace period.

9.13 Ball Savers

9.14 Ball Serve

The ball serve module is the preferred API for adding balls to play. It uses the ball tracking APIs to program the ball trough, but adds support for autoplunging and multiball logic.

The system supports games with autoplungers or manual plungers. Auto launch support requires that the machine defines three things: the launch button switch, the launch solenoid, and a shooter switch.

The base API serve_ball simply kicks a ball out of the trough. It also resets the valid playfield flag and refreshes background effects. This is the same call made by the game state machine during start ball.

Use serve_ball_auto instead if you want to ball to be autolaunched as soon as it is served successful. Otherwise, it is identical. If a ball is served without autolaunch, it can be launched later by calling launch_ball. This happens automatically when the launch button is pressed.

You normally do not need to call either of those APIs, except in some rare cases.

The preferred way to start multiballs is to use set_ball_count, which sets the number of balls in play, or add_ball_count if you want to say how many balls to be added. These use serve_ball_auto to do the work. They work on manual plunger games too. Only one ball will ever be placed in the shooter at a time.

serve_ball
serve_ball_auto
launch_ball
set_ball_count
add_ball_count

9.15 Multiball Modes

9.16 Mute and Pause Mode

FreeWPC implements the Mute and Pause feature that was included in the Twilight Zone home ROM. It is optional at compile-time. The machine must have an extra-ball buyin button for it to work.

When compiled in, and the adjustment "MUTE/PAUSE" is set to YES, then pressing buytin during a game will hold the flippers, disable ball kickouts, and turn off the background music. To continue, press the button again. It will also timeout automatically after 15 minutes.

10 Fonts and Graphics

This chapter explains in detail how FreeWPC fonts and graphics work.

FreeWPC uses simple bitmaps to represent fonts, icons, and full-screen graphics. A bitmap is an array of rectangular pixel data. The data bits are prefixed by a short header which contains the width and height information. The image is stored one row at a time, starting from the top. Within a row, the bits are stored in little-endian format, with the least significant bit representing the leftmost pixel.

A font is a set of bitmaps mapped to a contiguous sequence of characters. The font header identifies the ASCII code for the first bitmap that is represented; this saves space by not needing to encode the low-valued ASCII control characters. The nominal height of the characters is also defined, which allows for descenders.

A frame is a full-screen bitmap, 128x32. Since these are fairly common, special APIs are used to draw them that are more optimal, and the width/height is implied and not actually stored in the bitmap. A frame can have 4 colors per pixel (3 shades plus black) so the frame is actually stored as two consecutive bit planes.

10.1 Compression

Each bit plane of a frame can optionally be compressed using run-length encoding (RLE) or zero suppression to save space. An uncompressed frame requires 1KB, which limits the total number of frames in a ROM significantly. Every frame header includes a flags field, which indicates which if any compression method was used. The frame decoders are written in assembly language in platform/wpc/dmd.s.

Compression is a balance between ROM size and processor speed. The best compression methods would take far too long for the 6809 to decode. The methods used strike a compromise. In particular, bitwise operations are avoided in the decoders because of the 6809's inability to do bitlevel operations quickly.

10.2 Using TrueType Fonts

The script fontgen2 converts TrueType font files (.ttf files) into WPC font files (.fon).

10.3 Printing Text

The font_render_string family of APIs is used to draw text to the display. The arguments are a font object, the x and y coordinates where the string should be placed, and a string. The string can be a constant string literal or the global format buffer (see below).

There are three variants which justify the text differently: centered, left-justified, and right-justified. Centering is done both vertically and horizontally; the others only justify left-to-right, and the y coordinate always specifies the top of the print area.

Text printing is CPU intensive. Display effects should take care not to print text more than needed. It is often more efficient to print strings to an overlay buffer, and then copy them to the main display page, if the same text needs to be printed over and over again.

10.4 Formatting Text

FreeWPC contains a printf-like function for formatting text strings with variable data, however, it is not quite the same.

The sprintf() function formats a string into a unique, global buffer named sprintf_buffer. It is like the actual C function of the same name, but the first argument is implied.

The format specifiers are also slightly different. Here is a list of the valid ones:

%b
Print a binary-coded decimal value. The format length gives the total number of digits; for example, %8b would print a 4-byte BCD string containing 8 digits. Also, this format will insert commas (or periods) between digits as necessary.
%c
Print a single 8-bit ASCII character.
%d
Print an 8-bit decimal integer (declared U8).
%E
When this is the first specifier in a string, it will cause all output to be appended to the previous string in the print buffer, rather than overwriting it completely.
%ld
Print a 16-bit decimal integer (declared U16).
%lx
Print a 16-bit hexadecimal integer (declared U16).
%p
Print a pointer.
%s
Print a string.
%w
Print a 32-bit hexadecimal integer (declared U32).
%x
Print an 8-bit hexadecimal integer (declared U8).

Like in C, you can insert a number in front of the format letter to limit the output to a particular width. If the length begins with '0', then it will be padded with leading zeroes if necessary.

The formatter does not support signed numbers, and will print them as if they were declared unsigned.

The formatter is not particularly efficient for printing large decimal values, as the 6809 is not very good at long division.

10.5 Frame List

The list of all frames compiled in the ROM is defined in an image map. This is a machine-specific file that says which images to copy into the final ROM image.

There can be multiple frame list files. The common code provides a frame list of standard images, like the FreeWPC logo, which go into every build.

Each entry in the image map gives an image to be imported, such as a PGM graphics file, plus an optional frame ID. The frame ID becomes a C #define that refers to the image from the source code. The image linker writes a file build/imagemap.h which contains a list of all the frame IDs. Frame IDs are optional for the internal frames in a sequence (a for loop would only need to name the starting and ending frame).

It is the job of the image linker to decide what compression techniques to perform. The linker is told the maximum amount of space that can be used for images, which is the total size of the ROM minus any sections reserved for source code. If all images fit without compression, then all is well.

Otherwise, the linker will perform as much as compression as necessary. At present, images are compressed in the order that they were declared. A future enhancement would be to start with those images that can be compressed the best without requiring much more CPU power to decode them.

In some rare cases, trying to compress an image fails to produce a smaller buffer. The linker notices this and leaves such images uncompressed.

11 System Initialization

Game developers do not generally need to understand all the details of initialization. However, this may be of interest to system developers.

Here is a rough outline of the steps taken to initialize the FreeWPC environment:

12 Platform APIs

The platform APIs define how the hardware is accessed on a particular platform. They only need to be written once, and then all games on that hardware architecture can be supported.

If you are developing code for a WPC machine, you do not need to understand the platform APIs in detail, because they are already written for you. If you plan to port FreeWPC to a different hardware architecture, then you will need to write these functions first before you can create any game code.

Every platform must define a header file in include/platform/platform.h. This file defines a number of inline functions, all of which begin with pinio. It also defines a handful of constants, which begin with PINIO.

Platform APIs are the functions that perform the physical I/O. On WPC, these functions read and write the WPC ASIC registers. All reads and writes to I/O registers should use the readb and writeb macros. How these work depends on the platform. For most platforms, these turn into memory-mapped operations. However, they could also use a parallel port or USB.

Platform APIs are not suitable for game code to call directly, as they are too low-level. Instead, game code uses the core APIs to access a device driver; the device driver then uses the platform APIs.

pinio_write_switch_column
Set the current switch matrix column.
pinio_read_switch_rows
Read all of the switches on the currently selected column.
pinio_read_dedicated_switches
Read all of the dedicated switches.
pinio_read_flippers
Read all of the Fliptronic switches.
pinio_read_flipper_buttons
Read all of the Fliptronic button switches.
pinio_read_flipper_eos
Read all or some of the flipper switches.
pinio_read_locale
Read the locale indicator (jumpers or DIP switches).
pinio_write_lamp_strobe
Set the current lamp matrix strobe.
pinio_write_lamp_data
Write lamp row data for the currently selected column.
pinio_write_solenoid_set (bank, value)
Refresh a set of 8 solenoids.
pinio_read_ac_zerocross (CONFIG_AC)
Return nonzero if currently at a zerocross point.
pinio_reset_sound
Reset the sound board.
pinio_write_sound
Write an 8-bit code to the sound board.
pinio_sound_ready_p
Return true if the sound board has an 8-bit waiting to be read.
pinio_read_sound
Read an 8-bit code from the sound board.
pinio_write_gi (CONFIG_GI)
Write to the set of all general illumination strings. This API is only used when the platform does not support dimming.
pinio_read_triac (CONFIG_TRIAC)
If the platform supports dimming through triacs, this call returns the input-side latch values.
pinio_write_triac (CONFIG_TRIAC)
Write to the triacs. This API is only used when the platform supports dimming.
pinio_watchdog_reset (CONFIG_WATCHDOG)
Reset the hardware watchdog timer.
pinio_active_led_toggle (CONFIG_DIAG_LED)
Toggle the diagnostic LED.
pinio_dmd_window_set (CONFIG_DMD)
Sets the display page that is mapped into a given portion of the CPU address space.
pinio_dmd_window_get (CONFIG_DMD)
Returns the display page that is mapped into a given portion of the CPU address space.
pinio_dmd_window_ptr (CONFIG_DMD)
Returns a pointer to the beginning of the buffer that is mapped into a given portion of the CPU address space.
pinio_dmd_set_visible (CONFIG_DMD)
Sets the display page that is currently visible on the display.
pinio_dmd_request_interrupt (CONFIG_DMD)
Request that an interrupt be generated when the DMD controller finishes refreshing the display.
pinio_parport_write (CONFIG_PARALLEL_PORT)
Write a byte to the parallel port.
pinio_set_bank (CONFIG_BANK_SWITCH)
Writes to a bank switching register. There may be multiple such registers; you specify which register, and the value to be written. The values are also platform specific.
pinio_get_bank (CONFIG_BANK_SWITCH)
Reads the value of a bank switching register.
pinio_read_timer (CONFIG_HIGHRES_TIMER)
pinio_write_timer (CONFIG_HIGHRES_TIMER)

13 Native Mode

Native mode lets you run FreeWPC directly on your host build machine. It is a form of emulation, like PinMAME, except the emulator is "built-in" to the game program itself. It works on both Linux and Cygwin systems.

There are two major differences in a simulation build. First, certain low-level OS and CPU specific features must be replaced by new components that work under your native operating system environment. In particular, 6809 assembly languages are not used at all in this mode. We use either portable C versions of these functions, or comparable functions that are provided by the host OS.

Second, I/O reads and writes do not directly control real hardware, but are instead emulated internally by the program itself. All I/O is initiated by the platform APIs and uses the readb and writeb functions to address the hardware. In native mode, these functions define a separation between the "program" and the "simulation".

Native mode is most useful for debugging and testing the higher layers of FreeWPC, which aren't as hardware or timing dependent. This is only possible because the majority of the code is written in C.

To compile in native mode, just add NATIVE=y to your .config. Instead of a ROM, you'll get an executable that can be run directly.

13.1 Thread Model

Native mode programs are no longer realtime. There are two main concerns related to scheduling.

First, normal multitasking (the task_xxx APIs) is accomplished using the pth library, an open source, nonpreemptive thread library. A thin wrapper is provided which has the same APIs as in 6809 mode, but which calls pth functions for the implementation.

Periodic functions must be called occasionally from a special thread, since FreeWPC no longer provides the scheduler.

Interrupts are also simulated from an ordinary thread. The native operating system probably does not allow threads to sleep for as little as 1ms; on Linux the minimum sleep time is on the order of 10-20ms instead, and under Cygwin, it may be even longer. When the interrupt thread is woken up, it checks the system clock to see how much time has actually elapsed, and invokes the interrupt handler multiple times. The net effect is that interrupt handlers are invoked the same number of times as they would be on real hardware, but not the same way: they are called in batches, rather than being equally spread out.

By default, the simulation runs at the same speed as the native system clock. It is possible to speed up the simulation by a constant multiplier, which is sometimes helpful for rapid testing.

13.2 Persistent Memory

Permanent and non-volatile variables are supported; their values are saved in files across program runs. Non-volatile variables are not actually write-protected though. This may be implemented in the future.

13.3 Input and Output

The native program includes a simple, curses-based user interface to show what it is doing. Switches, lamps, solenoids, and GI states are redrawn in the window as necessary. Also, the DMD or alphanumeric display is emulated.

The UI support is separated into a separate module from the core emulation, and could be replaced by something more sophiscated in the future, for example, using a window library like Qt or GTK.

Switches can be simulated by pressing keys. The ESC key terminates the simulation. Many of the key mappings are the same as what is offered in PinMAME.

Advanced simulation techniques, such as ball movements, are only partially implemented at the moment.

13.4 GDB Debugging

You can use GDB to debug the native mode program.

Because the program uses curses to draw, it is easiest to run gdb from a separate terminal and attach to the running program. See the gdb manual for more information on how this works. Basically, you only need to know its process ID.

If you are in the FreeWPC source tree, you can run make attach to do this automatically. If it finds a running program, it will attach, halt the program (you'll see everything freeze), then give you a debugger prompt.

From here, you can set breakpoints, step through the code, etc. If the program crashes, you can use the bt command to get a backtrace and see exactly where it failed.

13.5 Interrupt Tracing

Since interrupt handlers are really just ordinary functions in native mode, they are much easier to debug. You can use printf and other functions that would normally be off-limits. The interrupt_dbprintf function can be used; in 6809 mode, this is a macro which does nothing, while in native mode it equates to a regular dbprintf.

13.6 Platform Emulation

Much like FreeWPC itself is portable, the builtin simulator is also designed to be portable. Its architecture is structured similarly in layers that isolate the platform and machine specific parts.

The interface between the simulator and the target system are the readb and writeb functions. I/O initiated by the target is intercepted by the simulator. The simulator itself catches these calls. Platforms register callbacks for addresses that can be simulated.

13.7 Timing

13.8 Command Language

The simulator allows you to control it via a command language. While the program is running, you can enter commands via the user-interface. You can also load scripts of commands from files.

Some files are checked automatically at startup and these commands are always run. These are kept in the conf subdirectory of the source tree. First, it will load conf/freewpc.conf. Then it will load a machine-specific file, conf/machine.conf.

In the curses-based simulator, press the colon key (like in vi) to open a command prompt. Other UIs may be different.

The basic commands are listed below. See other sections for more commands that are more specific.

Because commands can be placed in script files, shell-style comments and blank lines are accepted and ignored.

•set var value
•print var
•include file
•sw switch
•swtoggle switch
•key keyname switch
•push value
•pop argcount
•sleep time
•exit

13.9 Variables

The simulator uses variables in certain places to allow you to change its behavior at runtime. The set and print commands work on these variables.

All variables are fundamentally of type int. There is no support for strings.

Simulator modules declare variables in the usual way, then export these to the variable tracker using conf_add. This associates a public name for the variable that the command language sees. When variables are modified in this way, the module's int variables are then modified directly.

13.10 User Interface

13.11 Hardware Emulation

13.12 Signal Tracking

14 Debugging

14.1 dbprintf

You can use the dbprintf() function to print debug messages. dbprintf uses the builtin sprintf function to format the message, and then calls db_puts to print it. How these messages are printed depends on which version of pinmame you are using. At present, there is no way to see these messages when you are running on real hardware.

If you are running Linux, and you obtain the patches to xpinmame, each character is written to a new hardware register, the debugger port. It acts like a serial port and can be used to communicate between the game program and a separate debug console. The program wpcdebug is the console. Run this at the same time as pinmame and you will see the debug messages printed out. The two programs use a local socket to send data back and forth.

The console also accepts keyboard input and passes it back to the running program. See common/db.c for more information.

Under an unpatched emulator, such as on Windows, debug messages are sent to the WPC parallel port. pinmame writes the printer output to a file in the memcard directory. Under Windows, these files cannot be read while the program is still running.

In either case, the code to drive the serial/parallel port is only enough to keep the emulator happy; none of it would work on real hardware.

14.2 gdb

In native mode only, you can use gdb as you would on any other program. Because native mode uses ncurses, however, it is best to run the debugger in a separate window from the program itself.

Start FreeWPC as usual, then from another window, run make attach. (See the Makefile to see what this does; you really don't need to be in the source tree to do it.) gdb starts, searches for the PID of the program, and attaches to it. The program will stop, and you can then control it from the gdb prompt.

There are some special gdb macros provided in gdbmacros. All of the usual breakpoints, stepping, and variable evaluation commands work as you would expect.

14.3 exec09

exec09 is the 6809 emulator that is provided with GCC6809. exec09 supports a subset of the WPC architecture and can be used as a replacement for PinMAME. It is mostly good for debugging straight CPU code that does not access hardware. It provides a command-line monitor with breakpoints, single-step, and symbolic debugging which is sometimes more useful than PinMAME or gdb.

exec09 can measure the length of time a function takes, including interrupt handlers, which is good for determining performance.

14.4 Breakpoints

FreeWPC now supports breaking into the game program when the compile-time flag CONFIG_BPT is turned on. This works both in emulators and on real hardware. When enabled, this feature overrides the normal use of the Escape coin door button. Pressing Escape now halts the system; press Escape again to resume.

While halted, you can use the Up and Down buttons to view memory.

If a fatal error occurs when the debugger is compiled in, the machine will fall into the debugger instead of rebooting. This will show you what error occurred and which task was last running.

When halted, only normal task scheduling and periodic functions cease to run. Interrupt handlers/realtime functions cannot be halted, and thus, if they fail, the system will behave erratically. (Smoke alert.)

You can also dynamically enable and disable breakpoints at various places within the code, so that the debugger will start when the program hits that location, without having to press Escape.

14.5 Switch Stress Test

The switch stress test is used to test switch handling over a long period of time. When enabled, this adjustment causes the game to pretend that switch closures are occurring randomly. No balls are actually put into play, but the machine is fooled into thinking so.

Stress test also exercises ball devices correctly, and simulates 'enter' events instead of individual device switches. Ball locks, trough serves, and multiballs can all be tested.

You can force end-of-ball by pressing the Start Button several seconds after the start of the ball (the delay allows you to still add players to test multiplayer games). If in multiball play, pressing Start simulates the drain of exactly one ball at a time.

Closures are simulated randomly at a rate of about 10 per second. This test has been used to uncover some hard to find bugs in the game logic after extended play.

Naturally, this feature is disabled by default.

Appendix A The WPC Hardware

This appendix provides an overview of the WPC hardware, in terms of its capabilities and how software is able to control everything in the machine.

A much more thorough guide to the WPC and other pinball architectures can be found at http://www.pinrepair.com/wpc/index.htm.

A.1 Overview

WPC pinball machines contain a number of circuit boards, some that are intelligent with microprocessors, and others that are passive. One of these, the CPU board, is the master and houses the main microprocessor, a Motorola 6809 running at 2MHz. All other boards are connected to this one via ribbon cables. The functionality is spread across multiple boards to make maintenance easier and to isolate faults. These data cables carry 5V, generally active low signals. Inputs read as high (inactive) when the cables are disconnected.

From 1990-1999, six different generations of machines were produced, which differed only slightly. In order of appearance:

A.2 CPU Board

The CPU board contains the main processor: a Motorola 68B09E, running at 2Mhz. It is an 8-bit/16-bit CPU with a 64KB address space. Bank switching is required to address more than 64KB. On reset, location 0xFFFE is read to determine the address of the first instruction.

8KB of RAM is located at physical address 0x0000. When power is turned off, three AA batteries on the CPU board maintain the state of the RAM.

The game ROM size varies from 128KB to 1MB, depending on the game. The hardware supports a maximum of 1MB; this upper limit was used in all of the later models to accommodate more and more graphics. The uppermost 32KB is permanently mapped into the 64KB address space at address 0x8000 and contains the core operating system functions. The remaining parts of the ROM must be bank switched in, 16KB at a time; only one 16KB bank at a time is visible at address 0x4000.

The WPC ASIC is the heart of the system and is essentially a giant address decoder. It combines a lot of the functionality that was performed by PIAs and TTL logic in earlier solid state games. All read/write requests from the CPU are first seen by the ASIC, which can then either respond to it directly if it is an internal function, or forward the request to another device. RAM and ROM requests cause those devices enables to be asserted. For I/O, it may be more complicated and cause I/O to other circuit boards.

A.2.1 Blanking

The system blanking circuit protects the circuit boards during system initialization. The ASIC generates the BLANKING signal which is carried to all of the other boards. When asserted, BLANKING means to disable all output circuits.

Blanking is initially asserted by the ASIC at powerup, so that no lamps or solenoids can be turned on, regardless of the states of the outputs from the CPU board. Once the CPU has initialized, it writes to the ASIC to disable blanking; then the other register outputs take effect.

A.2.2 Diagnostics LED

LED 3 is controlled by writing to bit 7 of the WPC_LEDS register; this register can also be read to get the current LED state. The OS toggles this bit periodically to indicate that the system is alive. It is also used during early fatal errors as a primitive way of providing an error message.

A.2.3 Watchdog

The ASIC has a builtin watchdog timer which will reset the CPU board if software does not restart it periodically. The expiration seems to be on the order of 1 or 2ms.

FreeWPC restarts the watchdog on every periodic interrupt, every 1ms. During initialization, when interrupts are disabled, it also restarts it occasionally.

A.2.4 Bit Shifter

The ASIC has a bit shifter which offloads the CPU from having to calculate shifts using multiple CPU instructions. The 6809 does not have a native shift instruction for more than 1 bit at a time.

The bit shifter is used to optimize bit-level operations (set, clear, test, and toggle). This is particularly useful on WPC because RAM is scarce; many variables are stored as bits to save space.

A.2.5 Memory Protection

As an address decoder, the WPC ASIC is capable of restricting access to any part of the address space. The memory protection circuit lets you lock a portion of the system RAM as read-only, so that writes to those addresses are effectively no-ops.

The feature can be enabled/disabled, and the size of the protected region can also be specified. The region must reside in the uppermost part of RAM and must be a power of 2 in size.

FreeWPC uses memory protection to guard adjustments, audits, and some other vital data.

A.2.6 Time of Day Clock

The time-of-day device maintains the current calendar time. It has two read/write registers, which stores hours and minutes. The clock continues to count even when power is turned off.

Software maintains the calendar time in nonvolatile memory.

A.2.7 High Resolution Timer

The high resolution timer is not currently used by FreeWPC. It allows for sub-millisecond accuracy, and was only used in alphanumeric games to do display dimming.

A.2.8 Bank Switching

The bank switch register allows you to select which portion of the ROM is mapped to the banked region of the physical address space, at 0x4000. Only the lower 6-bits of the register are used, allowing for up to 64 pages. These bits become the uppermost address lines sent to the ROM device.

A.2.9 The Switch Matrix

All of the switch inputs terminate directly on the CPU board. This includes up to 64 playfield switches, arranged in an 8x8 matrix; 8 direct inputs used for service buttons and coin switches; and several jumpers/DIP switches used for configuring some factory defaults.

A.2.10 External I/O

All of the other I/O is located on other boards, which are connected to the CPU board via ribbon cables. Generally on these cables, one side of the pins are connected to ground, while the others contain actual inputs/outputs. All logic values measure 0V for a '0', and +5V for a '1'. Inputs have pullup resistors so that reading the signal while the cable is not connected will return '1'. All generations of WPC use the same 17x2 pin connector to the power driver board, which controls lamps and general solenoids. Display, sound, and flipper interfaces differ somewhat between generations.

Special I/O boards exist to drive other miscellaneous devices like ticket dispensers or a printer. These were addons that are not typically installed in all machines.

A.2.11 Interrupts

The ASIC generates the reset, IRQ, and FIRQ signals which are sent to the CPU.

The 6809 instructions to generate soft interrupts — swi, swi2, and swi3, are not used. The handlers for those vectors all throw fatal errors.

A.2.11.1 Reset

The ASIC drives the CPU reset signal. This signal is active low; normally the reset line is high. The ASIC will pull reset low when the watchdog timer expires.

A.2.11.2 IRQ

IRQ is generated 976 times per second, about once every 1.02ms. An oscillator on the CPU board generates the frequency. The periodic interrupt can be disabled/enabled by writing to the ASIC's WPC_ZEROCROSS_IRQ_CLEAR register. Separately, IRQ can be masked/unmasked by writing to the 6809's condition-code (CC) register.

A.2.11.3 FIRQ

FIRQ can be generated in two ways: from the dot matrix controller after a certain scanline is redrawn, or from the high-performance timer. When an FIRQ is received, the CPU has to determine which of these occurred to determine how to process it.

The DMD controller can interrupt via FIRQ when a particular scanline of the display has just been sent to the display. This can be used to tell the CPU when to display a new frame.

The high precision timer can interrupt when its value reaches zero.

Either of these can be enabled/disabled individually, in addition to masking the interrupt at the processor.

A.2.11.4 NMI

The CPU's NMI input is not used and is connected to Vcc.

A.3 Power Driver Board

The power driver board contains all of the high power circuitry. It has no intelligent parts and is controlled completely by the CPU board over a short ribbon cable. It houses the drive transistors which switch current to the high power devices, along with fuses and other power-related circuitry.

A.3.1 Lamp Circuits

WPC supports up to 64 individually controllable lamps. The lamps are arranged in an 8x8 matrix. At any given instant, only 8 of the lamps can receive power. A column strobe is written to determine which set of 8 lamps is being addressed. A row output is written to specify the on/off states of that set of lamps. Software must repeatedly strobe each of the lamp columns in order to update all 64 lamps.

The filaments in incandescent lamps operate at around 6V. The lamp circuitry switches a much higher 18V, but strobing only enables that voltage for 1/8 of the time, and filaments do not instantly turn off when power is removed, so no flicker is perceived. LEDs do not have this property and this explains the flicker that is often seen when people replace normal bulbs with LEDs.

A.3.2 Solenoid Circuits

All versions of the power driver board support 28 controlled outputs for solenoids, motors, etc. These are divided into four groups: 8 high power drivers, 8 low power drivers, 8 flashlamp drivers, and 4 general purpose drivers. Each bank operates at a different voltage, somewhere between 20V and 50V.

The CPU board enables/disable a driver by writing a command to the power driver board. All values are latched on the driver board and thus retain their states until the CPU changes them. The latches are not readable, so software must maintain the last value written in RAM. A CPU board reset will assert a blanking signal to reset the latches; this helps in the event of a software crash.

On WPC-95, 4 additional low voltage outputs running at 5V are added to the general purpose group, which can be used for miscellaneous I/O like small DC motors.

A.3.3 General Illumination Circuits

The GI circuitry allows for 5 strings of up to 18 lamps each. These are separate from the controlled lamps described previously; individual lamps are not controllable, only entire strings of lamps. Each string can independently be turned on and off by a triac. The lamps all run off 6.3V AC.

Starting with WPC-95, two of the GI strings are always on and not under software control, saving the cost of the triacs, but eliminating the ability to dim them.

When enabled, the triacs illuminate the lamps until the AC voltage crosses zero. They must be constantly re-enabled to keep the lamps on.

A.3.4 Zero Cross Circuit

The zero-cross circuit tells the CPU when the AC voltage is near zero.

Alternating current (AC) rapidly changes between a positive and negative voltage many times per second. In the US, the frequency is 60Hz, which means that the voltage is near zero about once every 8.33ms. Outside the US, line voltage is 50Hz and so zero crossing occurs every 10ms.

The zero cross data is used to dim the general illumination.

A.4 Sound Board

Both the WPC and DCS sound boards are connected via a ribbon cable to the CPU board, which sends it both read and write commands. The actual values vary greatly depending on the board type. These boards are intelligent and have processors running their own operating system dedicated to sound tasks.

1-byte or 2-byte commands are written to request particular sounds to be played. The sound board stores all of the sound data on additional EPROMs, and has its own CPU that decodes the data and writes it to various audio devices.

The sound board can also send data back to the CPU, notably to tell it when a sound has finished playing, allowing the CPU to synchronize its actions to the sounds and music.

A.5 Fliptronic Board

On all but the earliest games, flipper control is located on a separate board, called the Fliptronic Board. It has 8 drivers for up to 4 flippers. Half of the drivers switch high power 50V, used when initially turning on a flipper; the other half switch a lower 'holding' power that is adequate for keeping the flipper in the raised position for a long period of time. The board also supports 8 switches that can be read by the CPU board: 4 for the flipper buttons and 4 end-of-stroke (EOS) switches. The button switches are wired to the cabinet buttons. The EOS switches are mounted on the flipper and used to determine when the flippers are in the 'up' position, indicating that the lower power voltage can be used.

On pre-Fliptronic games, the flippers are hardwired to the cabinet buttons, so flipping occurs without any CPU involvement. The CPU can control a relay on the power driver board to cut flipper power, though.

On WPC-95, the Fliptronic Board was eliminated and its components moved elsewhere: switch inputs moved to the CPU board, and flipper outputs moved to the driver board.

A.6 Auxiliary Driver Board

Some games use an auxiliary driver board which allows up to 8 additional flashlamps/low power devices to be controlled. These machines must have the following line in the machine config:

     define MACHINE_SOL_EXTBOARD1

The auxiliary board plugs into the slot formerly used by the alphanumeric display board; thus, alphanumeric games cannot use this. This port was later removed on WPC-95.

A.7 Dot Matrix Controller Board

The dot matrix display is 128 columns x 32 rows. The display itself is very dumb and expects a serial bitstream of pixels to be clocked in. The controller board has the display RAM and the serialization logic.

The controller fetches 1 byte (8 pixels) every 32 CPU cycles (16 microseconds). At this rate, it takes 256 microseconds per row and a little more than 8 milliseconds per complete frame. Thus, the refresh rate is about 122MHz.

The display RAM holds 8KB. A full DMD bit plane requires 128x32 pixels, that is, 4096 bits, or 512 bytes. Thus, there is enough RAM to hold a total of 16 planes. At any time, at most two planes can be addressed by the CPU (changed to six in WPC-95).

The SRAM is designed to allow both the 6809 and the serial decoder to access it simultaneously, at nonoverlapping phases of the system clocks.

Bits are encoded within a byte such that the leftmost pixel is in the least significant bit. This is backwards from how binary numbers are normally visualized.

The DMD controller exposes the following registers:

A.8 Memory Map

This section describes the WPC memory map in detail. In particular, it gives the function of each of the WPC ASIC registers, along with the names given to them by FreeWPC.

WPC_RAM_BASE (0x0000)
System RAM (2K)
WPC_DMD_RAM_BASE (0x3800)
Display RAM (1K). Which 1K portion of the 16K SRAM appears here is controlled by writing to two display paging registers, WPC_DMD_HIGH_PAGE and WPC_DMD_LOW_PAGE. On WPC-95 only, up to 3K can be mapped, starting at 0x3000.
WPC_DEBUG_DATA_PORT (0x3D60)
WPC_DEBUG_CONTROL_PORT (0x3D61)
WPC_SERIAL_CONTROL_PORT (0x3E66)
WPC_SERIAL_DATA_PORT (0x3E67)
WPC_DMD_3200_PAGE (0x3FB8) [WPC-95 only]
3-0: W: The page of display RAM mapped into the 2nd region, from 0x3200-0x33FF.
WPC_DMD_3000_PAGE (0x3FB9) [WPC-95 only]
3-0: W: The page of display RAM mapped into the 1st region, from 0x3000-0x31FF.
WPC_DMD_3600_PAGE (0x3FBA) [WPC-95 only]
WPC_DMD_3400_PAGE (0x3FBB) [WPC-95 only]
WPC_DMD_HIGH_PAGE (0x3FBC)
3-0: W: The page of display RAM mapped into the 2nd (6th on WPC95) region, from 0x3A00-0x3BFF.
WPC_DMD_SCANLINE (0x3FBD)
7-0: W: Request an FIRQ after a particular scanline is drawn 5-0: R: The last scanline that was drawn
WPC_DMD_LOW_PAGE 0x3FBE
3-0: W: The page of display RAM mapped into the 1st (5th on WPC95) region, from 0x3800-0x39FF.
WPC_DMD_ACTIVE_PAGE 0x3FBF
3-0: W: The page of display RAM to be used for refreshing the display. Writes to this register take effect just prior to drawing scanline 0.
WPC_PARALLEL_STATUS_PORT (0x3FC0)
WPC_PARALLEL_DATA_PORT (0x3FC1)
WPC_PARALLEL_STROBE_PORT (0x3FC2)
WPC_SERIAL_DATA_OUTPUT (0x3FC3)
WPC_SERIAL_CONTROL_OUTPUT (0x3FC4)
WPC_SERIAL_BAUD_SELECT (0x3FC5)
WPC_TICKET_DISPENSE (0x3FC6)
WPC_FLIPTRONIC_PORT_A (0x3FD4)
7: W: Enable upper-left flipper hold 6: W: Enable upper-left flipper power 5: W: Enable upper-right flipper hold 4: W: Enable upper-right flipper power 3: W: Enable lower-left flipper hold 2: W: Enable lower-left flipper power 1: W: Enable lower-right flipper hold 0: W: Enable lower-right flipper power 7: R: Upper-left flipper EOS input 6: R: Upper-left flipper cabinet input 5: R: Upper-right flipper EOS input 4: R: Upper-right flipper cabinet input 3: R: Lower-left flipper EOS input 2: R: Lower-left flipper cabinet input 1: R: Lower-right flipper EOS input 0: R: Lower-right flipper cabinet input
WPC_FLIPTRONIC_PORT_B 0x3FD5
Not used.
WPCS_DATA 0x3FDC
7-0: R/W: Send/receive a byte of data to/from the sound board.
WPCS_CONTROL_STATUS 0x3FDD
7: R: WPC sound board read ready 0: R: DCS sound board read ready
WPC_SOL_GEN_OUTPUT 0x3FE0
7-0: W: Enables for solenoids 25-29
WPC_SOL_HIGHPOWER_OUTPUT 0x3FE1
7-0: W: Enables for solenoids 1-8
WPC_SOL_FLASH1_OUTPUT 0x3FE2
7-0: W: Enables for solenoids 17-24
WPC_SOL_LOWPOWER_OUTPUT 0x3FE3
7-0: W: Enables for solenoids 9-16
WPC_LAMP_ROW_OUTPUT 0x3FE4
7-0: W: Lamp matrix row output
WPC_LAMP_COL_STROBE 0x3FE5
7-0: W: Lamp matrix column strobe At most one bit in this register should be set. If all are clear, then no controlled lamps are enabled.
WPC_GI_TRIAC 0x3FE6
7: W: Flipper enable relay 5: W: Coin door enable relay 4-0: W: General illumination enables
WPC_SW_JUMPER_INPUT 0x3FE7
7-0: R: Jumper/DIP switch inputs
WPC_SW_CABINET_INPUT 0x3FE8
7: R: Fourth coin switch 6: R: Right coin switch 5: R: Center coin switch 4: R: Left coin switch 3: R: Enter (Begin Test) button 2: R: Up button 1: R: Down button 0: R: Escape (Service Credit) button
WPC_SW_ROW_INPUT 0x3FE9 *Pre-security
7-0: R: Readings for the currently selected switch column. Bit 0 corresponds to row 1, bit 1 to row 2, and so on. A '1' indicates active voltage level. For a mechanical switch, this means the switch is closed. For an optical switch, this means the switch is open.
WPCS_PIC_READ 0x3FE9 *WPC-S
WPC_SW_COL_STROBE 0x3FEA *Pre-security
WPCS_PIC_WRITE 0x3FEA *WPC-S
7-0: W: Switch column strobe. For pre-Security games, exactly one bit must be set. For Security games, writing to this register sends a command to the PIC chip and does not directly control the strobe line.
WPC_ALPHA_POS 0x3FEB *Alphanumeric
WPC_EXTBOARD1 0x3FEB *DMD
On DMD games, this is a general I/O that is used for machine-specific purposes.
WPC_ALPHA_ROW1 0x3FEC *Alphanumeric
WPC_EXTBOARD2 0x3FEC *DMD
On DMD games, this is a general I/O that is used for machine-specific purposes.
WPC_EXTBOARD3 0x3FED *DMD
On DMD games, this is a general I/O that is used for machine-specific purposes.
WPC_ALPHA_ROW2 0x3FEE *Alphanumeric
WPC95_FLIPPER_COIL_OUTPUT 0x3FEE *WPC-95
WPC95_FLIPPER_SWITCH_INPUT 0x3FEF *WPC-95
WPC_LEDS 0x3FF2
7: R/W : The state of the diagnostic LED. >0=Off >1=On
WPC_RAM_BANK 0x3FF3 *WPC-95
3-0: W: The page of RAM currently mapped into the banked region.
WPC_SHIFTADDR 0x3FF4
15-0: R/W: The base address for the bit shifter. Writing to this address initializes the shifter. Reading from this address after a shift command returns the shifted address.
WPC_SHIFTBIT 0x3FF6
7-0: W: Sets the bit position for a shift command. 7-0: R: Returns the output of the last shift command as a bitmask.
WPC_SHIFTBIT2 0x3FF7
7-0: R/W:
WPC_PERIPHERAL_TIMER_FIRQ_CLEAR 0x3FF8
WPC_ROM_LOCK 0x3FF9
Not used
WPC_CLK_HOURS_DAYS 0x3FFA
7-0: R/W : The time-of-day hour counter.
WPC_CLK_MINS 0x3FFB
7-0: R/W : The time-of-day minute counter.
WPC_ROM_BANK 0x3FFC
5-0: R/W: The page of ROM currently mapped into the banked region (0x4000-0x7FFF). Pages 62 and 63 correspond to the uppermost 32KB, and are not normally mapped because those pages are accessible in the fixed region (0x8000-0xFFFF). Page numbers are consecutive. Page 0 corresponds to the lowest address in a 1MB device. If a smaller ROM is installed, the uppermost bits of this register are effectively ignored.
WPC_RAM_LOCK 0x3FFD
WPC_RAM_LOCKSIZE 0x3FFE
WPC_ZEROCROSS_IRQ_CLEAR 0x3FFF
7: R: Set to 1 when AC is currently at a zero crossing, or 0 otherwise. 7: W: Writing a 1 here clears the source of the periodic timer interrupt. 4: R/W: Periodic timer interrupt enable >0=Periodic IRQ disabled >1=Periodic IRQ enabled 2: W: Writing a 1 here resets the watchdog.

Appendix B The Machine Definition

Machines define most of their hardware configuration in the form of an "md" file. Long ago, this was done by directly writing a .h file with the required information. The .h file approach was more cumbersome and somewhat error- prone.

The md file is "compiled" by the script genmachine, which writes out a .h file and also some .c files that get linked in to the final program. genmachine will do some consistency checking on the input to make sure it is sane.

It will enforce naming consistency between identifiers, declarations, and strings. For example, the start button gets a define for its switch number (SW_START_BUTTON), an identifier for callback events (sw_start_button), and a string in the menus ("START BUTTON"). gendefine can produce all of these automatically from a single "Start Button:" declaration in the .md file.

Though the syntax is fairly strict, there is a bit of magic in how different categories need to be written. The easiest way to write a config for a new game is to copy from another one. The Twilight Zone and World Cup Soccer configs are the most complete. There is more documentation in those files on how things should be set up.

B.1 Syntax

The "md" file is just a text file. Blank lines and lines beginning with a '#' are ignored, like in a shell script.

Long lines can be broken up like in C, with a backslash at the end of each line. A comma also acts as a line continuation character, which is useful when defining long lists.

The file is divided into sections, which begin with a section header in square brackets. For example:

     global-statements
     
     [section1]
     section1-statements
     
     [section2]
     section2-statements

Only section names that are known to genmachine are permitted. The list of permitted sections is listed near the top of the genmachine script, and includes: switches, lamp, drives, gi, lamplists, containers, etc.

Notice that there is a global section that is in effect at the top of a file, before you declare any sections.

Within a section, you declare items that fit that category. All declarations take this form:

     key: property1 [, property2...]

A declaration begins with a key, followed by a colon, followed by a comma- separated list of properties. The key and/or properties can contain any characters, including spaces, but not commas or colons.

There are two types of declarations: fixed objects and dynamic objects. They are written similarly but not exactly the same.

B.2 Fixed Objects

In one style, used mostly for hardware layout, the key refers to the object's physical identification, such as the switch, lamp, or solenoid number. Here the key is generally numeric, although for solenoids the syntax is slightly different to specify the solenoid bank. The first property of these declarations should always be the human readable name of the object. Depending on the type of declaration, genmachine can validate that the key name is valid.

An example:

     [switches]
     14: Tilt, tilt, ingame, noplay

The key, 14, identifies which switch is being described. For switches and lamps, the key is given as a pair of column/row digits, as it would be listed in the game manual. The first property, Tilt, gives it a name. This is used to generate the defines and strings related to the switch.

Everything else identifies the properties of the switch. Different types of objects will have different properties. Here, we say three things: (1) it is an instance of a well-known switch class, called 'tilt'; this ties it directly to system code for processing tilts automatically. (2) The tilt switch should only be serviced during a game. (3) A closure does not mark valid playfield.

B.3 Variable Objects

In the second type of object definition, there is no physical identifier, and so the name before the colon is the human readable name. These are generally used for software constructs. For these, genmachine automatically assigns a number based on the order of the declarations. There can be an unlimited number of objects of these types, unlike those tied to hardware where there is a physical limit.

For example:

     [deffs]
     Multiball Start: page(MACHINE_PAGE), PRI_GAME_QUICK6

This defines a display effect for multiball start. A #define is generated, DEFF_MULTIBALL_START, which is a numeric ID used to refer to the effect. The IDs for all deffs are assigned sequentially, and do not need to be specified as with the switches above. Everything following the colon is treat as a property just as above.

This example also shows a variation in the property syntax. Above, we saw properties 'ingame' and 'noplay', which are binary properties: just stating them causes them to be turned on. Binary properties can be listed in any order; genmachine knows what all of the allowable binary properties are and will handle them correctly. In the deff declaration above, there is a 'page' property, which is not binary – it has a value, MACHINE_PAGE. For these valued properties, the syntax is always ‘variable(value)’.

B.4 Directives

B.4.1 include

The 'include' directive can be used to include config syntax from another file, much like a C '#include'. This is used to bring in common definitions for the platform, that can be shared across games.

For example, no game defines a switch entry for the "ALWAYS CLOSED" switch, which is the same in every WPC game. This can be put into a file shared by all machines.

By convention, the machine-specific file includes the platform-specific file, which may itself include other files. The WPC platform provides different files for the different hardware generations, one per variation and one that is common for all.

All includes files are read and parsed before any of the output is generated. It is thus possible to override definitions that were seen in an earlier include. The default WPC md file provides names for all of the switches and lamps, so if you omit one from the machine file, you get a default definition. The tester ROM uses this facility.

B.4.2 define

The 'define' directive is used for miscellaneous settings. It gets translated to a C '#define' in the output file mach-config.h. For example,

define MACHINE_NUMBER 531

is converted to:

#define MACHINE_NUMBER 531

Note that the pound sign is not included in the mdfile, as it would be treated as a comment.

B.5 Global Configuration

Certain things need to be defined in the global sections, using Key: Value syntax. The human readable name of the machine, the system type, and a few other things can be given here. These do not appear directly in the mach-config.h, but are used by genmachine to guide the compilation. Again, see any existing config file for an example.

B.6 Section Summary

Here is a list of the sections that can appear.

switches
Defines the physical switches.

The possible attributes for switches are:

opto
edge
noplay
ingame
intest
button
noscore

Additionally, these attributes are used to tag special switches. There should only be one switch of each type.

outhole
shooter
tilt
slam-tilt
buyin-button
launch-button
start-button
trough-stack

lamps
Defines the controlled lamps. You can also give each lamp a color and a playfield position; there are ways of using this data to auto-generate lamp effects.

These attributes are used to tag special lamps:

start
buyin
extra-ball
shoot-again

drives
Defines the solenoids and flashers. Use the flash attribute to say which ones are flashers. Use motor to say which ones are motors.

These attributes are used to tag special drives:

ballserve
knocker
launch

gi
Defines the G.I. string names, for display in test mode.
lamplists
Defines the lamplists. The attribute list defines an ordered list of lamps. Each attribute can be a physical lamp name, as defined in the [lamps] section; a range of lamps in the form lamp_start..lamp_end; or another lamplist name, allowing for nested declarations.
containers
Defines the containers/ball devices. The attribute list begins with the name of the release solenoid, followed by the names of the counting switches (in order from entry to exit).

The trough container should be marked with the attribute trough.

templates
Lists the template instantiations.
tests
Defines additional hardware tests that are put into the Tests menu.
deffs
Define the display effects.
leffs
Define the lamp effects.
adjustments
Define the machine's feature adjustments. For each adjustment, you give a type, which is an object that defines its allowable values, and a default.
audits
Defines feature audit variables.
system_sounds
system_music
highscores
Defines the default high scores.
flags
Declares bit flags. These are handled similarly to lamps; they are per-player. All flags are zero at the start of game.
globalflags
Like ‘flags’, except these are global and not per-player.
scores
Declares the score table, which associate a short 8-bit ID to a binary-coded decimal score value. Some of the scoring APIs use IDs instead of the expanded values for space efficiency.
fonts
Declares which fonts should be included in this game.
timers
Declares the freerunning timers that are needed.
templates
Declares instantiations of template drivers.

B.7 How genmachine works

genmachine is a Perl script. It parses all of the md commands and builds a giant hash with all of the data. genmachine is invoked multiple times with different options, requesting that different output files be generated. All of the output files are C or H files put into the 'build' subdirectory, which are then compiled normally.

For the Perl programmer, each object declaration is itself a Perl anonymous hash, where each property of the object is one of the hash entries. Using the variable(value) syntax, it is possible to put anything into the object definition. However, only certain keys are recognized by the output functions. Adding new properties generally doesn't require a parser change, but only a change to the output routines. Binary properties and well-known object classes do need to be stated – there are constant tables at the top of the script that declare these.

Appendix C System Events

Here is a list of all of the standard events that the system generates.

add_credits
One or more full credits have just been added, via coins or the service switch.
add_partial_credits
One or more partial credits have just been added.
add_player
A new player has been added to the game in progress. This event is thrown for all players, including the first one. For the first player, it is thrown after the start_game event. When called, all player-local variables are in scope.
amode_page
Thrown by the attract mode pager to allow machines to add their own pages into the attract mode display sequence.
amode_start
Attract mode has just started.
amode_stop
Attract mode has just stopped.
any_device_enter
A ball has entered any of the ball devices.
any_kick_attempt
An attempt is about to be made to kick from one of the ball devices.
any_pf_switch
A playfield switch closure occurred.
ball_count_change
The number of balls in play has changed.
boolean ball_drain
A ball has entered the trough; should it be treated as a drain? This event should be received by ballsaver logic. It should return FALSE if this is not to be considered a drain, in which case the handler should also put a ball back into play somehow. Otherwise, it should return TRUE.

The standard ballsaver module takes care of this, but you can hook this if you write an "unlimited ball" mode, like Lost in the Zone.

ball_search
A ball search attempt is in progress. This event is thrown each time all of the solenoids are pulsed to try to find the ball. It should be received by modules which need to do some special processing, such as raising/lowering a motor bank – anything more complicated than just firing a solenoid.
bonus
End of ball bonus has started.
dev_foo_enter
The device "foo" was just entered (its count went up by 1).
dev_foo_kick_attempt
The system is about to try to kick a ball from device "foo". It cannot be stopped at this point; see the kick_request event if you want to delay for some reason.
dev_foo_kick_failure
A kick from device "foo" failed (its count did not change).
dev_foo_kick_request
The system wants to try to kick a ball from device "foo". If you return FALSE from this event, the attempt will be delayed for a while.
dev_foo_kick_success
A kick from device "foo" succeeded (its count went down by 1).
dev_foo_surprise_release
The count of a device went down by 1 when a release was not initiated by software; the ball fell out of the device by other means.
device_update
Called periodically to update any mechanical devices whose state changes frequently depending on a variety of factors.
diagnostic_check
Called after init_complete to perform further diagnostic checks. Note that unlike init_ok, a diagnostic_check failure does not cause factory reset and reboot, but only flags a problem. These errors are reported in test report. The diagnostic check can wait if necessary for powerup tests to complete, but take care that it does not wait forever.
display_update
The running display effect needs to be updated. This is called whenever a display effect exits; it may also be called periodically. Modules which want to display a long-running effect (e.g. for a running mode) should receive this event, see if the condition to start the effect is valid (i.e. the mode is running), and call deff_start_bg() to request a particular background effect be started. That call itself doesn't guarantee that the effect will run; if multiple requests are made, only the one with the highest priority will run.
empty_balls_test
The empty balls test has been initiated. The system will take care of emptying ordinary ball devices; this event is for modules which store balls in some other way (e.g. virtual devices like TZ's gumball machine).
boolean empty_trough_kick
This is thrown prior to serving a new ball from the trough after a ball lock. It allows machines to override this behavior and serve the ball from somewhere else if desired. When this is wanted, the handler should return FALSE. The system automatically handles the case where the trough is empty and the ball must be served from somewhere else anyway.
end_ball
End-of-ball has been asserted. This is thrown just before end of ball bonus starts.
end_game
End-of-game has been asserted (for all players). This is thrown after all of the end game effects, like high score entry and match.
end_player
End-player has been asserted. This is called after end_ball, and before end_game.
extra_ball_award
An extra ball has just been awarded.
factory_reset
A factory reset has been initiated. This event should be received by any module with a __permanent__ variable, so that it can be reinitialized. Audits and adjustments are handled automatically.
flipper_abort
Both flipper buttons were pressed simultaneously.
flipper_code_entered
TBD.
idle
This event will be thrown exactly once every 16ms, after all running tasks have been scanned and given a chance to run. If tasks take a long while, it is possible that this event will run less frequently. However, this event cannot be starved out indefinitely. The idle event is not thrown during early system initialization. As this event happens frequently, it is usually not the right event for non-system modules to receive.
idle_every_100ms
Like idle, but only called once per 100 milliseconds. This is the preferred event for modules to use for periodic processing.
idle_every_second
Like idle, but only called once per second. This can be used instead of the 100ms event for less frequent processing.
idle_every_ten_seconds
Like idle, but only called once per 10 seconds. This can be used for extremely infrequent processing.
init
Called during phase 1 of system initialization. An init receiver cannot assume anything about any other modules than itself. This is normally used to initialize variables, but not to begin device I/O.
init_complete
Called during phase 2 of system initialization. This is intended for modules which require interaction with each other. This is usually the right event for a complex device driver to perform I/O-based initialization, (e.g. calibration of a motor).
init_ok (boolean)
Called after init to determine if initialization failed for any module. Handlers should return FALSE on failure, TRUE on success. If any handler returns FALSE, an automatic factory reset will occur, and the init_complete event will not be generated.
lamp_update
Called periodically to update the lamp matrix. This should be received to update a lamp whose state depends on a variety of conditions which may be constantly changing (e.g. a generic "mode arrow" which could be off, flash, or solid and is used by multiple modes.) In these cases, the state of the lamp is dependent on several variables, and the lamp_update routine recalculates the state of the lamp.
match_awarded
A match was just awarded to one or more players.
minute_elapsed
One minute of real time has elapsed, as detected by the real-time clock.
missing_ball_found
Don't use this... it will probably be deleted.
music_refresh
Called periodically to update the running background music.
replay
A replay was just awarded.
score_deff_start
The default score screen display effect just started.
score_overlay
Thrown just after the default score screen is drawn, but before it is displayed. It allows machines to add an auxiliary effect into the score screen, e.g. the starfield on Twilight Zone or the swimming fish on Fish Tales.
serve_ball
A new ball was just served to the shooter lane. This event is thrown during start_ball, and after locking a ball, but it is not thrown during a ball save. It is intended to be used for relighting a skill shot, or for enabling a ball saver. This event also signals that valid_playfield has been reset; i.e. draining this ball without scoring will cause it to be re-served.
shoot_again
Called during start_ball if an extra ball is being played.
single_ball_play
The game was in multiball, but is now back into 1-ball play. This should be handled by multiball modes to shutdown.
slam_tilt
The machine has been slam-tilted.
special_award
A special was just awarded.
start_ball
Start-of-ball has been asserted.
start_game
A new game has been started.
start_player
Called during the first ball of each player. For player 1, it is called after start_game. This should be received by any module with a __local__ variable declaration, which is per-player, to initialize it correctly.
start_without_credits
The start button has been pressed in attract mode without credits.
status_report
Thrown after all of the default status report pages have been displayed, to allow machines to define their own pages.
stop_game
The game in progress has been cancelled, due to game restart or exiting to test mode.
sw_foo
The switch foo has transitioned. Normally switch events are only generated on a transition from inactive to active. Switches marked "edge" in the machine configuration will also generate events when going back to inactive: for those switches, handlers must use switch_poll_logical() to determine the current state of the switch to do the right thing.
test_start
Test mode has been started.
tilt
The player has just tilted his ball.
tilt_warning
The player has just received a tilt warning.
timed_drain_penalty
In a timed game, the player has drained the ball. This should be received by any modes that want to penalize the player for draining, by changing game features or further decreasing the game timer.
timed_game_tick
In a timed game, the game timer has decreased by 1 second.
valid_playfield
Called after start_ball when the ball has been put into play; a ball drain at this point will now signal end_ball. This is normally asserted on any scoring switch, and is what enables a zero-point ball to be re-served. Certain switches like jets and slingshots, which can fire if misaligned, can be marked "noplay" to disable this. The machine may use other methods to assure that it is eventually asserted, based on number of switch closures or timing.

Appendix D Build Tools

D.1 csum : ROM Checksum Tool

The checksum tool works on FreeWPC ROMs as well as the original Williams factory ROMs. It can verify and update the checksum field located just above the interrupt vector table.

The checksum is a 16-bit value that resides at logical address 0xFFEE (18 bytes from the top of ROM). This value should equal the sum of all the 8-bit byte values in the ROM, modulo 65536. The checksum word is itself included when calculating and verifying it.

csum also uses the word at address 0xFFEC as a fixup word, which can be set to any value to help make the checksum match.

D.2 srec2bin : S-Record Converter

The gcc6809 linker produces S-record files by default. This tool converts S-records to raw binary format.

Each bank of the ROM is linked individually; each produces an S-record file. These files contain nearly everything to generate a binary file, except for the size and the value to place at holes. We use 0xFF for all holes as this is more friendly to EPROMs.

D.3 sched : Static Scheduler

The scheduler reads in a list of schedule files, which by convention end with the extension .sched. Each entry gives a function and a frequency at which it should be called. It generates a C source file that contains a top-level function which then calls all of those functions at the right rate. It does this by keeping a counter of the number of times that the top function was called.

The scheduler supports loop unrolling, where the top function is actually decomposed into a small number of functions. This reduces the number of if-statements needed overall; it does require an extra indirection via function pointer. This can cause code duplication for the sake of speed. The unroller tries to balance all of the functions to be scheduled using performance data in the schedule file.

D.4 fontgen2 : TrueType Font Generator

This is a short Perl script which can transform a TrueType font file (.ttf) into FreeWPC source code. It requires the convert program from the ImageMagick tool suite.

Appendix E Dot-Matrix Performance

On older machines without a dot matrix, driving segmented score displays was a relatively low horsepower task. Up until the late 1980's, 7-segment displays were used that could only display numbers (and a few letters). Including a comma, there are only 8 bits per segment. Assuming 4 players per game and 8-digit scoring, that's still only 256 bits – or 32 bytes of data – that has to be managed by the CPU.

Later, 16-bit segments were used that could display full text, which doubles the data size, but still the quantity of data to be manipulated was low.

The dot matrix display changed that. The DMD is 128 pixels wide by 32 pixels high, for a total of 4096 pixels, or 512 bytes, per frame. Let's look at some of the challenges that are created.

E.1 Frame Rate

Achieving a realistic looking animation means redrawing the display frequently enough so that movements appear smooth. See this article about flicker fusion threshold for more information.

A 20Hz frame rate is decent looking. That requires updating the display 20 times per second, or once every 50ms. For better quality, 25Hz or 30Hz is desirable. However, the 2MHz processor, saturated with other things besides DMD updating, is not well-suited to a high frame rate.

We'll assume a 20Hz frame rate throughout this article, and see how difficult even that is to achieve. We'll find that in some cases, we have to settle for even less than that.

Another way of looking at it is that every 50ms, or every 97600 CPU cycles, we need to draw and commit another display frame. Later, we'll talk about the CPU instruction set and how efficiently it can do some common operations.

E.2 Simulating Color

The display itself has no notion of color. Each pixel is either off, or glows orange. WPC games simulated different shades of orange by using a page flipping technique. By rapidly switching between two pages, up to 4 different colors (3 oranges plus black) are perceived.

The key is that the two pages are not shown equally. One of them is displayed 1/3 of the time; the other 2/3 of the time. If a pixel is off in both pages, it appears black. If a pixel is on in both pages, it appears to be on all the time, at 100% intensity. If a pixel is on in one page but not the other, then it appears either 1/3 or 2/3 of the time, producing either a dark brown or medium orange color.

The controller itself does not implement the page flipping; the software does. The controller can interrupt the CPU (on the FIRQ line) whenever the display has just been refreshed. Software can reprogram the controller to display a different page of data at this time. Doing it at any other time will produce tearing, where parts of two different pages are temporarily displayed at once.

The display refreshes at 122MHz (ref); therefore, the FIRQ occurs about once every 8ms (or 16000 CPU cycles). The FIRQ handler needs to be fast since it is so frequent. All it needs to do though, is to set the active page register in the controller to a new value. (FreeWPC does this in less than 40 cycles.)

The same technique, with more pages, could be used to simulate more colors. For example, 3 pages could produce 8 colors, and 4 pages could produce 16 colors. However, note that as more pages are flipped, the effective refresh rate drops. This means that the page flipping begins to be perceived as movement and not as color change. Showing more colors requires other techniques that are not discussed further.

2 pages = effective frame every 3 redraws = 40Hz frame rate 3 pages = effective frame every 7 redraws = 17Hz frame rate 4 pages = effective frame every 15 redraws = 8Hz frame rate

E.3 6809 Instruction Set

The 6809 does not provide much support for bulk movement of data, which is what is mainly needed to update the display. Assume for now that a single 512-byte, monochrome frame of data is in the game EPROM, which needs to be copied into display memory, i.e.

memcpy (display_memory, image_src, 512);

How long does this simplest of operations take? It all depends on the implementation of the copy. Let's look at some approaches.

A simple way is to copy byte-by-byte. Assume that the source and destination addresses are already in registers, then we can do something like this:

     loop:
     	ldb	,x+
     	stb	,u+
     	dec	count
     	bne	loop

We use the X and U registers, because instructions using Y are one byte longer, and also take one cycle longer.

This actually doesn't work, because count is a byte variable, and we need to copy 512 bytes. Also, it is inefficient because many cycles are spent in the 'dec' and 'bne'. Loop unrolling can help us eliminate overhead:

     	lda	#64
     	sta	count
     loop:
     	ldb	,x+
     	stb	,u+
     	ldb	,x+
     	stb	,u+
     	ldb	,x+
     	stb	,u+
     	ldb	,x+
     	stb	,u+
     	dec	count
     	bne	loop

As each iteration now copies 4 bytes at a time, we can do this in 64 loop iterations.

Obviously, we could unroll some more and it would run even faster, but with diminishing returns and at the expense of a larger program. Code space is not the primary concern here, but it does play a role.

The above example can be improved further by realizing that the 6809 can operate on 16-bit words. Loading/storing a word at a time helps a lot. The number of iterations can be cut in half, too, which reduces loop overhead more.

     	lda	#32
     	sta	count
     loop:
     	ldd	,x++
     	std	,u++
     	ldd	,x++
     	std	,u++
     	ldd	,x++
     	std	,u++
     	ldd	,x++
     	std	,u++
     	dec	count
     	bne	loop

The 'ldd' and 'std' instructions, in the indexed modes used above, take 7 cycles each. dec takes 5 cycles – or 4 if we ensure that it is in the direct page. (footnote here) bne only takes 2 cycles. So one iteration of this loop, which copies 8 bytes, takes 63 cycles. Multiply by 32 and we get 2016 cycles, or a little more than 1ms, just to do a straightforward page copy.

Now consider the following:

Keeping all images uncompressed is not practical on the WPC platform, because of the ROM space limitation. In the earliest dot matrix games, 256KB ROMs were used. With no room for anything but graphics, these could only hold 512 pages of data. If 4-color images are desired, then cut that number in half.
Compression is necessary, and takes CPU power to decode. So the 1ms copy is mostly unrealistic.
In many cases, copying a single page of graphics is not what is needed. We may need to composite several layers of images together, including text. It may take 4 or 5 page-level operations (meaning, where an entire page is modified) to compose the final image. Some of these operations are a little more costly than a single copy, too.
Remember that we wanted a display frame every 50ms. As we add all of this extra complexity, this is starting to take a significant percentage of the total CPU available.

E.4 The Fastest Copy in Town

It turns out that our copying loop is NOT the most efficient that it can be. Remember, the goal is to do bulk copying of data. There actually are instructions that can copy more than 16-bits of data at a time: the stack push/pull instructions!

The PSHS (push) instruction can copy the contents of the A, B, X, Y, U, S, PC, DP, and CC registers onto the stack. Likewise PULS does the opposite, loading from memory into registers. These instructions don't quite work though, because we need a working stack. However, there is another variant: PSHU and PULU. These were intended to be used for a "user stack", but for our purposes, U is just another pointer register. If we point U at our display page, we can do bulk copies using PSHU, and get more than 16-bits done at a time. [Note that this only works for either the source or the destination, but not both.]

These instructions are not without some cost, though. What they improve is the overhead of instruction fetching. Each of them takes 5 cycles plus 1 cycle for each byte transferred. Recall that ldd/std took 7 cycles. So if we use the stack instructions for only 16-bits at a time, the cost is the same. But any additional bytes copied come for only 1 cycle per byte.

We want to push as many values in registers as possible, but we can't use them all. U is already being used as our pointer, so we can't use it for the display data, too. PC is obviously not a good candidate. DP and CC affect other things, so we don't want to trash them. That leaves A, B, X, Y, and S.

FreeWPC chooses not to use S as a data register, although it could if the copy code was protected against interrupts and no stack variables were needed during the copy. In some applications, this might be possible. Because of the large amount of time that a copy takes, I felt that keeping interrupts disabled for a long time was a bad idea. So I only use A, B, and X, and Y: 6 bytes copied at a time. [ Note: I'm not using Y. ]

And that, as far as I can tell, is THE fastest way to copy bytes on the 6809.

E.5 Bit Alignment

For full 128x32 frames, a bytewise or wordwise copy doesn't need to concern itself with the individual bits in the image. However, when copying smaller bitmaps to an arbitrary DMD location – for example, when drawing font glyphs – bit aligment becomes a problem.

First, some background. A single byte of data in the controller's memory addresses 8 consecutive pixels within a single row. The least-significant bit of the data corresponds to the leftmost pixel. That is, the value 0x01 draws one pixel aligned to the left; 0x80 is aligned to the right. If you're used to picturing a byte of data in its binary form, this is completely backwards from that, which can cause some confusion when working with these algorithms.

Now say we have an 8x1 bitmap (8 pixels wide, 1 pixel high) stored in one of these bytes, and we want to place it anywhere on the display. If the leftmost pixel location is byte-aligned, this is easy; we just write the byte to that location.

If the leftmost pixel location is not byte-aligned, you can see that this will take at least two instructions, because two different bytes of the display data are modified. Suppose we want to put this bitmap at a row where x=4 (four pixels in from the left edge of the display). How would we do it?

First, we're going to have to modify two different bytes of data.

Second, note that to be correct, we cannot just overwrite these areas, because that would trash some of the bits that we are not concerned with. The pixels from x=0 to x=3 and from x=12 to x=15 should not be altered. This requires reading those locations, modifying only the appropriate pixels, then writing back the results.

So already this is not trivial. This is an important problem to solve because this is how all fonts get drawn.

The 6809 does not support shifting more than 1 bit in a single instruction.

All this said, there are a number of efficient solutions to the problem, but they all take a lot of CPU cycles. FreeWPC's approach is to examine the size of the source bitmap and the target bit alignment, to determine the most efficient method to use. Three different situations are handled:

1. the source width plus the bit alignment are less than 8 bits. This means no more than 1 byte per row need to be emitted. This is very efficient for small fonts in some cases. [ This is not right. ]

2. the source width is 8 bits or less, and the width plus alignment is less than 16 bits. This is almost as good; we can read 1 byte per row and write 2 bytes per row.

3. Everything else. The generic method always works, but is inefficient for all but the largest of bitmaps.

E.6 Compression Techniques

FreeWPC has not experimented much with various compression algorithms for full-screen images. Compression has not been implemented at all for font data and other small bitmaps, simply because the space savings would not be worth all of the extra computation.

The goal is not perfect compression, but rather to be good enough. The best compressor takes time – and thereby requires time to decompress. We need a fast decompression algorithm that the 6809 can do well. That limits the scope to the simplest of algorithms. Really good compressors work at the bit-level, but because of the 6809's inability to do bit-level manipulation fast, FreeWPC has only considered techniques that work a byte or a word at a time.

There are a number of basic approaches to compression. The first is to use a form of run length encoding (RLE), where a long string of consecutive byte or word values is compressed into a value+count pair. Not all images will have long strings like this, but many do, especially runs of 0x00 and 0xFF.

The second is to use a form of delta encoding, where an image is encoded as the set of differences from a previous image. This is useful when doing animations, in which consecutive images don't differ too much.

More complicated techniques than these are likely to be a burden for the 6809.

Appendix F Historical Notes

FreeWPC was started in 2005 and was written entirely in assembly language, before the availability of the gcc6809 C compiler. An initial attempt to ease programming involved the use of some complicated macros, written in the m4 programming language. Work was halted during the development of gcc6809, then the system was gradually rewritten in C. The early source code repository actually contained the compiler changes as well.

The first time that a FreeWPC ROM was placed into a game was in May, 2008. It did not run OK due to some problems with the watchdog circuit and with the PIC initialization. Those problems were resolved within only a few hours.

Appendix G Design Principles

How to Program a New Game

While the rest of this manual serves as a reference to the FreeWPC system, this chapter takes a different approach and explains systematically how to go about writing up the rules for a new game from the beginning. Details are intentionally skipped here, with pointers back to the relevant sections of the manual if you want to know more.

G.1 Overview

Like any good software program, a game should be broken down into logically separate entities as much as possible. An object-oriented approach can still be taken even when coding in C. I prefer to think of a game as consisting of a set of mostly independent modules, with each module represented by a separate C file.

Each module defines a set of functions that are private to it: the implementation. In C, these are usually coded as "static" functions to prevent accidental use from other files. Other functions are public by default. In FreeWPC, make sure that you have a prototype for all such functions that includes one of the ROM bank modifiers (__common__ or __machine__, for example), otherwise they may not be called correctly. Always check for warnings in the error file when you build a ROM; this type of problem is always pointed out there.

For the most part, the game program is event-driven, meaning that functions are called only when some external event occurs. The OS throws events for just about every event that you will care about, but your code can also create new events too. There are basically three types of events that you will care about: switch events (some playfield switch just activated), timer events (some amount of time has expired), and milestone events (notable points in time like the start of a game).

Use the callset mechanism to write callback handlers to handle all of the events you need to monitor. Nearly every module will require this. These are the main entry points to the module.

G.2 Classes of Modules

Modules can serve different purposes. I like to classify modules into three classes: drivers, shot detectors, and rules. Each of these serves a different purpose, and keeping them in separate modules is good programming design.

Drivers deal with the physical nature of the specific machine. They should do nothing with game rules. Drivers manage the hardware devices and define an API that lets higher layer game code interact with it. The hardware aspect can be the most difficult part of programming a pinball game, as it needs to handle many types of fault conditions.

Shot detection can be viewed as an extension of the switch events. Sometimes, a single switch activation can be considered a shot, but not always. Consider on Twilight Zone the detection of left versus right loops; the same switches are involved, but they are triggered in a different order. In Attack From Mars, the right hole awards a "front shot" or "back shot" differently and used other recent shots to determine which to award. In the shot modules, you write logic that defines what makes a valid shot. You monitor the switches and timers that contribute to the shot, and then you create new events for the shots that you define. By doing this in one module, the rest of the code can just listen for a "right hole front shot" event.

Shot detection can be overdone if not careful. It is best to keep it as simple as possible. Compensation for bad switches can be done here, too.

The game rules are at the top of the module stack. Every timed mode, multiball, or other game feature should generally have its own file. They use the shot detection logic to listen for valid shot events and have their own logic for defining how to award points and what effects to trigger.

Note that drivers and shot detection should be rules-agnostic; thus, once they are coded correctly, it should be possible to rewrite the game rules without changing them.

G.3 Device Drivers

When you start coding a brand new machine, you must write a machine description file. This defines the physical switches, lamps, and solenoids for that game. Doing this is enough to make all of the low-level APIs work. These binary APIs allow you to read switches one by one, turn on/off lamps, and pulse solenoids and flashers.

However, you are not allowed to turn on a solenoid permanently. A generic API to do so would be dangerous. For flashers and ball kicking devices, the pulsing APIs, which ensure that the device is turned off eventually, are enough. But for some devices, you need more control. To do that, you need to write a device driver.

Here are some examples of drivers:

- Diverters which must be held on for a long period of time. - Solenoids that are tied to switches, like slingshots and jets, which need fast response time. Also motor banks which have home switches. - Devices where there are multiple outputs working together. For example, the clock on TZ uses two motor control lines, one to spin forward and another to spin backward.

Ball kicking devices are automatically handled by the ball device module. You write definitions in the [containers] section of the md file to define these. Other device drivers should be written using a template driver.

G.4 Shot Detection

G.5 Game Rules

Most modes are OK to overlap and run in parallel; you must explicitly define how to handle conflicts if this is not wanted. Rules will listen for shot events, update state, add to the score, and start display, lamp, and sound effects.

Rules are also the only modules that will need to keep state per-player. Use the __local__ modifier to create a per-player variable. Define flags (not globalflags) in the machine description to create 1-bit flags. For trivial rules you can sometimes keep state directly in the lamp matrix.

Types of rules: static rules, timed modes, multiball modes

Every rule should define the following types of APIs: - start_game (physical devices only) - start_player - start_ball - available? - qualified? (running? in grace period?)

- plus other APIs as pertinent to that type of rule shot->rule mapping

For timed/multiball modes there are predefined APIs...

G.6 Other Modules

Test modules. These are extensions to the test mode menus and are generally tied to a particular device driver, but not all drivers will need a test option.