Runtime Programmers Reference

The Runtime API provides the interface to perform accelerated search operations.

The software model can be partitioned into three layers.

  1. The Runtime API configures the driver, loads and unloads automata, scans data, and reads match events.
  2. The driver manages communication with the physical hardware and provides the low level abstraction of the hardware.
  3. The physical hardware performs the actual search.
Layers in the device programming model


D480 Automata Processor and Ranks

Micron's Automata Processor is a technology capable of performing very high speed searches over an immense search space. D480 is the name given to the first generation silicon which implements the Automata Processor.

D480 uses a standard DDR3 protocol for its IO interface in a x8 configuration. D480 are organized into ranks (similar to DRAM) with the number of D480 per rank given by the rank memory bus width divided by eight. For example a 64 bit bus would contain 8 x D480 per rank.

Physical Device

A physical device refers to the actual hardware containing one or more D480 ranks. For example Micron's D480 PCI-e development card can manage 4 ranks of 8 x D480. The physical device in this example is the PCI-e card.

The physical device is represented as a device node in the operating system. In Linux, device nodes are located in the file system under /dev. Device nodes are labeled frioN, where N is numbered from 0 to the number of physical devices less 1. For example /dev/frio0.

Device Driver

The Device Driver enables communication between the physical device and the Runtime API. The Device Driver is a loadable kernel module called ap.ko and is responsible for management of all physical devices.

Traditionally UNIX uses static device nodes created during installation of the operating system. Most legacy devices, for example disk drives, still conform to this model. Linux however discourages this model for all new devices and provides a dynamic mechanism to create device nodes. Dynamic allocation means entries under /dev cannot be created until the driver is loaded. The SDK provides the shell script apinsmod to load the kernel module and create the device nodes. Legacy mode is supported via a command line parameter specified during module load. In legacy mode the device node must be created manually using /sbin/mknod.

To load or unload the driver, open a root shell and choose one of the options below.

  • To load the driver execute the command:
  • To load the driver in legacy mode using a major device number of 4 execute the command:
        /sbin/modprobe ap major=4
  • To unload the driver execute the command:
        /sbin/rmmod ap

Alternatively, if legacy mode is desired, you can list the major parameter in an existing or newly-created file in the /etc/modprobe.d/ directory. This makes the major parameter persistent by ensuring it is set each time the module is loaded, such as after every reboot or modprobe without any parameters. See for more information regarding the /etc/modprobe.d/ directory.

Once loaded into the operating system the ap.ko module allows querying of physical device characteristics using the /proc file system. For example:

ls /proc/ap/deviceslists the attached physical device node names
cat /proc/ap/devices/frio0/rankcountDisplays the number of ranks in physical device frio0
cat /proc/ap/devices/frio0/devsperrankDisplays the number of D480 per rank
cat /proc/ap/devices/frio0/descriptionDisplays a description of the physical device

The runtime function AP_QueryDeviceMetrics() uses the proc file system to gather information about attached physical devices. If /proc/ap does not exist then the driver is not loaded.

Configuration and Load Regions

Each physical device must be configured before any data processing can occur. The Runtime API provides the function AP_ConfigureDevice() for this purpose. Configuration involves the partitioning of the ranks of D480 into load regions. When configuring it is best to model the physical device as a two dimensional array of D480. The first dimension locates the offset of a D480 within a rank and the second dimension specifies the rank within the physical device.

Physical device viewed as a 2D array of D480

The dimensions of the 2D model space can be determined programmatically through use of the function AP_QueryDeviceMetrics(). This function returns the number of ranks and number of D480 per rank in the ap_device_metrics structure passed as a parameter to the function call. Alternatively the /proc file system can be used to obtain the same information.

Configuration involves the partitioning of the 2D model space into rectangular regions of D480. All D480 in the model space must be mapped to a rectangular region. If no configuration is supplied before a call to AP_OpenDevice() then the configuration defaults to a single rectangular region which covers all D480 in the physical device. Regions are numbered in any order from 0 to the desired number of regions less one. The figure below shows a mapping of the example physical device from above, partitioned into 3 load regions.

Mapping of D480 to three load regions

A load region defines the physical location where compiled automata will be loaded. During a call to the API function AP_Load() the load region must be specified using the indexing scheme defined during configuration. One can think of each load region rectangle as having a width measured in D480, a height measured in ranks, and an offset for the top left coordinate. These parameters along with another, called the mode, define the characteristics of the region. There are two modes for a load region:

  1. Replicate Mode: This mode allows the runtime to run multiple parallel hardware searches against automata loaded in the region. The STE capacity of the region is equal to the width multiplied by the STE capacity of a single D480 (48K). In this mode the loaded automata are replicated across the ranks in the load region. Replication allows the driver to choose any rank in the region for a hardware search leaving the other ranks in the region for parallel searches. The search performance of a load region in this mode is equal to the height multiplied by the search performance of a single D480 (1Gbps).
        performance = height x 1Gbps
        ste-capacity = width x 48K
  2. Standard Mode: This mode enforces a single search against automata loaded in the region. The STE capacity of the region is equal to the width multiplied by the height multiplied by the STE capacity of a single D480 (48K). In this mode no replication occurs. The search performance of a load region in this mode is equal to the search performance of a single D480 (1Gbps).
        performance = 1Gbps
        ste-capacity = height x width x 48K

When defining load regions the following precautions should be observed:

  • A poorly configured load region in standard mode may result in multiple writes to the physical device. Each rank will involve a write operation hence a standard configuration should attempt to maximize the width and minimize the height of the region's rectangle. Using our example above, all regions have eight D480 within, however region 0 and 1 will incur two writes to the hardware while region 2 will incur a single write.
  • Load regions configured in replicate mode only deliver a performance advantage when performing multiple searches on the same region concurrently. The performance on a single search is still 1Gbps.

Additionally it should be noted that a region's mode does not affect the software model from the API perspective. Multiple threads and processes can access the same region concurrently; analogous with single and multiple CPU architectures in which both support the concept of threads and multi-tasking, except that the multiple CPU architecture will have higher performance.

Configuration is system wide global and as such all applications using the runtime to open the same physical device inherit the same configuration. There are restrictions regarding the positioning and width of load regions within the 2D model space. These are:

  • The minimum region width is 2 x D480 devices. This is a limitation of the first generation silicon and may be removed in subsequent generations.
  • The width must be a power of two.
  • The top left D480 offset must be aligned to a boundary related to the width.

The configuration restrictions are summarized in the table below.

Permissible Region settings
20, 2, 4, or 8
40, or 4

Example: Steps involved in setting various fields of device configuration structure (ap_device_config) for the example above. The example illustrates a setup with 3 ranks partitioned into 3 load regions.

#include <micron/ap/ap_exec.h>
void Configure(void)
struct ap_device_config cfg;
// ERRATA: Limitations with current version of SDK.
// (1) AP_STANDARD_LOAD not supported
// (2) Load regions must be 1 rank in width
// (3) This example will not work on current development card due to
// limitations and extra rank on devel card.
memset(&cfg, 0, sizeof(cfg));
cfg.load_region_count = 3;
cfg.load_regions[0].mode = AP_REPLICATE_LOAD;
cfg.load_regions[0].width = 4;
cfg.load_regions[0].height = 2;
cfg.load_regions[0].rank_offset = 0;
cfg.load_regions[0].dev_offset = 0;
cfg.load_regions[1].mode = AP_REPLICATE_LOAD;
cfg.load_regions[1].width = 4;
cfg.load_regions[1].height = 2;
cfg.load_regions[1].rank_offset = 0;
cfg.load_regions[1].dev_offset = 4;
cfg.load_regions[2].mode = AP_REPLICATE_LOAD;
cfg.load_regions[2].width = 8;
cfg.load_regions[2].height = 1;
cfg.load_regions[2].rank_offset = 2;
cfg.load_regions[2].dev_offset = 0;
AP_ConfigureDevice("/dev/frio0", &cfg);
1 from micronap.sdk import *
3 def Configure():
4  cfg = ap_device_config()
6  # AP_STANDARD_LOAD not supported in this version of the SDK
7  cfg.load_region_count = 3
8  cfg.load_regions[0].mode = LoadMode.AP_REPLICATE_LOAD
9  cfg.load_regions[0].width = 4
10  cfg.load_regions[0].height = 2
11  cfg.load_regions[0].rank_offset = 0
12  cfg.load_regions[0].dev_offset = 0
13  cfg.load_regions[1].mode = LoadMode.AP_REPLICATE_LOAD
14  cfg.load_regions[1].width = 4
15  cfg.load_regions[1].height = 2
16  cfg.load_regions[1].rank_offset = 0
17  cfg.load_regions[1].dev_offset = 4
18  cfg.load_regions[2].mode = LoadMode.AP_REPLICATE_LOAD;
19  cfg.load_regions[2].width = 8
20  cfg.load_regions[2].height = 1
21  cfg.load_regions[2].rank_offset = 2
22  cfg.load_regions[2].dev_offset = 0
24  ConfigureDevice('/dev/frio0', cfg)
import com.micron.ap.*;
public class RuntimeExample {
public static void Configure() throws ApException {
LoadRegion cfg[] = new LoadRegion[3];
/* AP_STANDARD_LOAD not supported in this version of the SDK */
cfg[0] = new LoadRegion(LoadMode.AP_REPLICATE_LOAD);
cfg[1] = new LoadRegion(LoadMode.AP_REPLICATE_LOAD);
cfg[2] = new LoadRegion(LoadMode.AP_REPLICATE_LOAD);
ApConfiguration.configureDevice("/dev/frio0", cfg);

As stated previously, the numbering of load regions is arbitrary. For example an alternative mapping using the example above is shown here. Though the numbering is arbitrary the load region index specified during AP_Load() must use the numbering scheme defined during configuration.

Alternative mapping of D480 to load regions

Estimating Load Region Size

The dimensions of a load region depend on the load region mode and the capacity requirements of the automata that will be loaded into that region. The API provides the function AP_CalcLoadSize() to calculate the number of D480 required for a given set of automata. This result can be used to estimate the dimensions of a load region.

There is no general method to estimate the load region requirements for any automata set. To obtain a general result the programmer will need to choose some multiple greater than one of the result above.

Given a load size the load region dimensions can be determined as follows:

  • Replicate Mode
    • Height = Performance in Gbps
    • Width = load size
  • Standard Mode
    • Performance = 1Gbps
    • Choose height and width such that height x width = load size

Runtime Objects

The automaton output from the compiler is a file which can be loaded and executed on the physical device. The runtime model is very similar to executable applications (.exe under Windows) where the operating system loads and relocates executable files into memory before they can be run. From an operating system perspective a running application is referred to as a process.

In the AP model the device driver loads and relocates one or more automata into the physical device. Once the automata are loaded we refer to them as a runtime-object. The Runtime API optionally allows a character string to be associated with a runtime-object. This character string may be used in scenarios where multiple processes require access to the same runtime-object.

Example: Loading an automaton into load region 0.

#include <micron/ap/ap_load.h>
ap_rto_t LoadAutomaton(ap_device_t dev, ap_automaton_t amton)
ap_rto_t rto;
if (0 != AP_Load(dev, &rto, 0, amton, 0, 0))
return NULL;
return rto;
1 from micronap.sdk import *
3 def LoadAutomaton(dev, amton):
4  rto = dev.Load(0, amton)
5  return rto
import com.micron.ap.*;
public class RuntimeExample {
public static RTO Load(Device dev, Automaton amton) throws ApException {
RTO rto = dev.load(0, amton, null);
return rto;

Flows and State Vectors

A state vector represents the state of a search on the D480. The state vector allows the D480 to context switch between two searches much like the register save/restore that allows tasks to context switch on traditional CPU architectures.

A flow is a software abstraction of the state vector and uses the same format as the hardware state vector. Flows also maintain the state of a search but add some extra state variables which are not tracked by the hardware. Flows allow a search to be performed on blocks of disjoint data. Flows contain information such as:

  • The runtime-object associated with the flow.
  • The state of the hardware when the last write to this flow completed.
  • The number of bytes processed.

A single D480 supports a maximum of 512 unique state vectors on-chip. During configuration the D480 are partitioned into load regions and thus this support is extended to the entire region, i.e. for any load region, 512 flows can be stored on the physical device.

A flow must be created for each search operation and closed when the search completes. When creating a flow the programmer must specify the runtime object, which in turn specifies the load region where the automata are loaded.

If the load region width spans an entire rank then the driver is capable of managing millions of independent data flows via a scheduling algorithm. From the runtime perspective the number of flows supported for independent searches is limited only by available memory.

If the load region width spans the rank partially then the driver is capable of managing a maximum of 512 independent data flows. No flow scheduling occurs in this scenario. This maximum may be further reduced depending on the state vector reservation.

Flow data and chunks

To maximize performance it is necessary to search against moderately large data sets since the data write overhead becomes more significant as data size is reduced. Additionally small data sets increase the interrupt frequency between the physical device and the CPU. Linux is not a real time operating system and provides no guarantee regarding interrupt service latency. As the interrupt frequency increases this latency translates into dead time where the hardware is idle thus further reducing performance.

For data set sizes of 32KB and above, write latency and interrupt overhead are not significant. For the reasons described in this section, the programmer should attempt to search 32KB of data or more. The data however does not need to relate to a single search flow, 32 x independent 1KB searches perform similarly to 1 x 32KB search.

To meet hardware performance objectives the runtime provides the vectored search function AP_ScanFlows(). This function performs multiple data searches on the same logical device. The data may be segmented, or chunked, and may relate to one or many flows.

The ap_flow_data structure stores a reference to the flow and a pointer to the data that the flow processes. The reference to the flow is stored in the "flow" field of ap_flow_data. The data is stored in chunks and the total number of chunks of data that the flow processes is stored in the field "chunk_count".

Example: Create a flow data array for processing 2 flows and perform a search.

#include <micron/ap/ap_exec.h>
int SearchTwoFlows(ap_device_t dev, ap_flow_t flow1, ap_flow_t flow2, void* data1, void *data2, unsigned data_size_1, unsigned data_size_2)
struct ap_flow_data fdata[2];
struct ap_flow_chunk chunks[2];
memset(fdata, 0, sizeof(fdata));
memset(chunks, 0, sizeof(chunks));
fdata[0].flow = flow1;
fdata[0].total_length = data_size_1;
fdata[0].chunks = chunks + 0;
fdata[0].chunk_count = 1;
chunks[0].data = data1;
chunks[0].length = data_size_1;
// Set up the second element in fdata array
fdata[1].flow = flow2;
fdata[1].total_length = data_size_1;
fdata[1].chunks = chunks + 1;
fdata[1].chunk_count = 1;
chunks[1].data = data2;
chunks[1].length = data_size_2;
return AP_ScanFlows(dev, fdata, 2, NULL);
1 def SearchTwoFlows(dev, flow1, flow2, data1, data2):
2  # Flow and data stored in a tuple
3  dev.ScanFlows([ (flow1, data1), (flow2, data2) ])
import com.micron.ap.*;
import java.util.ArrayList;
public class RuntimeExample {
public static void SearchTwoFlows(Device dev, Flow flow1, Flow flow2, byte[] data1, byte[] data2) throws ApException {
ArrayList<FlowData> dataFlows = new ArrayList<FlowData>();
dataFlows.add(new FlowData(flow1, data1));
dataFlows.add(new FlowData(flow2, data2));

Multi-Process Support and State Vector Reservation

A physical device has a hardware storage capacity of 512 state vectors per load region.

In order to support multiple processes accessing the same physical device there can be no overlap of on-device state vectors since this would amount to the search state being split across processes. The state vector reservation assigns a quantity of state vectors to a logical device.

During device opening each process must specify how many on-device state vectors will be reserved for that logical device. For example two processes may each open one logical device and choose a reservation of 256/256. The driver supports reservations of 512/0. This is useful if a second process is responsible for loading and unloading automata but never writes data to the device (common in network security).

Example: Support for 2 processes accessing the same device.

#include <micron/ap/ap_exec.h>
ap_device_t OpenShared2(void)
struct ap_device_metrics* metrics = NULL;
ap_device_t dev = NULL;
unsigned count;
if (0 != AP_QueryDeviceCount(&count))
goto __err_shared;
metrics = (struct ap_device_metrics*)malloc(sizeof(metrics)*count);
if (0 != AP_QueryDeviceMetrics(metrics, NULL))
goto __err_shared;
// Open first device - For a D480 metrics[0].sv_storage is 512 but the
// programmer should not rely on this constant as future versions of
// silicon may differ.
AP_OpenDevice(&dev, metrics[0].dev_name, metrics[0].sv_storage/2);
if (metrics) free(metrics);
return dev;
1 from micronap.sdk import *
3 def OpenShared2():
4  # Open first device - For a D480 metrics[0].sv_storage is 512 but the
5  # programmer should not rely on this constant as future versions of
6  # silicon may differ.
8  metrics = QueryDeviceMetrics()
9  dev = Device()
10  dev.OpenDevice(metrics[0].dev_name, metrics[0].sv_storage/2)
11  return dev
import com.micron.ap.*;
public class RuntimeExample {
* Open first device - For a D480 metrics[0].getStateVectorReserve() is
* 512 but the programmer should not rely on this constant as future
* versions of silicon may differ.
public static Device OpenShared2() throws ApException {
DeviceMetrics [] metrics = ApConfiguration.queryDeviceMetrics();
Device dev = new Device();[0].getDeviceName(), metrics[0].getStateVectorReserve()/2);
return dev;

Logical Devices

A logical device represents a communication channel between the physical device and the software application. Multiple logical devices may be opened against the same physical device.

Every thread accessing the same physical device must open its own logical device. This design is the most performant because it requires no locking of user space state however it is limited to a maximum 16 threads due the minimum granularity of the state vector reservation. In this model the thread performing the search must process the match events in the same thread; guaranteed because the API ensures a 1:1 mapping between threads and logical devices.

Any attempt to use a logical device in a thread other than the one it was opened will cause the API function to return AP_ERR_DEVICE_BUSY.

Retrieving Match Events

Match events are returned to the logical device by the function AP_GetMatches(). The programmer can call AP_Wait() to synchronize with hardware. To synchronize:

Completions are implemented as a simple integer marker. They incur zero kernel resource usage until AP_Wait() is called and then they only use kernel resources if the scan operation is still pending or in progress. Since searches are performed in the order called, one only needs to wait on last call to AP_ScanFlows().

Example: Opening a flow, searching data, and retrieving match results.

#include <stdio.h>
#include <micron/ap/ap_exec.h>
int SearchData(ap_device_t dev, ap_rto_t rto, void* data, unsigned data_size)
#define MYSCAN ((void*)1)
ap_flow_t flow;
struct ap_flow_data fdata = { 0 };
struct ap_flow_chunk chunk = { 0 };
struct ap_completion complete;
if (0 != AP_OpenFlow(dev, &flow, rto, MYSCAN))
return 0;
fdata.flow = flow;
fdata.total_length = data_size;
fdata.chunk_count = 1;
fdata.chunks = &chunk;
chunk.length = data_size; = data;
if (0 == AP_ScanFlows(dev, &fdata, 1, &complete))
struct ap_match_result mresult;
AP_Wait(dev, &complete, 0);
while (AP_GetMatches(dev, &mresult, 1) > 0)
if (mresult.uparam == MYSCAN)
printf("Got match at offset %lu for expression id %u\n",
mresult.byte_offset, mresult.report_alias.reportCode);
fprintf(stderr, "Got match for unknown user param [%p]\n", mresult.uparam);
AP_CloseFlow(dev, flow);
return 1;
1 from micronap.sdk import *
3 def SearchData(dev, rto, data):
4  flow = dev.OpenFlow(rto)
5  complete = dev.ScanFlows([ (flow, data) ])
6  dev.Wait(complete)
7  matches = dev.GetMatches()
8  while (matches):
9  print "Got match at offset ", matches[0].byte_offset
10  matches = dev.GetMatches()
11  flow.Close()
import com.micron.ap.*;
import java.util.ArrayList;
public class RuntimeExample {
public static MatchResult[] SearchData(Device dev, RTO rto, byte[] data) throws ApException {
MatchResult mresults[] = new MatchResult[10];
Flow flow = dev.openFlow(rto);
ArrayList<FlowData> lst = new ArrayList<FlowData>();
lst.add(new FlowData(flow, data));
Completion complete = dev.scanFlows(lst);
dev.wait(complete, 0);
mresults = dev.getMatches(10);
return mresults;

Runtime Symbol and Counter Substitution and Device Reloading

The runtime system allows symbol and counter target subsitutions for ANML elements at runtime. This feature allows symbol and counter changes without having to recompile the automaton. The changes can be reloaded to the Automata Processor at runtime. To change the symbol set of an STE element, define the changes using an ap_symbol_change structure then call the AP_SetSymbol() API. To change a set of STEs to the same symbol, use AP_SetSymbolSame(). Symbol sets are specified in the same way as described in the ANML schema:

  • Character – e.g., "A"
  • Character Class – e.g., "[aA]"
  • Line feed – e.g., "\R"
  • Dot class, star class – e.g., ".", "*"" - Empty character class – e.g., [^@\x00-@\xff], "[]", "". This can be used to disable paths from generating matches.

To change the target of a counter element, use the ap_counter_change structure and call the AP_SetCounterTarget() API, and to change a set of counter targets to the same value, use AP_SetCounterTargetSame().

Once the symbol / counter substitutions are done, use the AP_Reload() function to reload the new values onto the device. NOTE: An automaton can only be reloaded after it has been loaded initially. As described in the documentation for AP_Load(), the automaton is invalidated and can no longer be used once it has been loaded. The solution is to use a copy, or clone, of the automaton for the initial load. See AP_Duplicate().

Example: Changing symbols to digits and modifying counter targets. The ANML file used to create the automaton is also shown.

#include <micron/ap/ap_element_map.h>
#include <micron/ap/ap_load.h>
int ChangeToDigits(ap_device_t dev, ap_rto_t rto, ap_automaton_t amta, ap_element_map_t emap)
ap_anml_element_ref_t elementRefs[2];
struct ap_symbol_change symbolChanges[] = {
{ 0, "3", 0 },
{ 0, "2", 0 },
struct ap_counter_change counterChanges[] = {
{ 0, 10, 0 },
{ 0, 15, 0 }
// Change individual symbol values
AP_GetElementRefFromElementId(emap, &symbolChanges[0].element_ref, "an1.s1");
AP_GetElementRefFromElementId(emap, &symbolChanges[1].element_ref, "an1.s2");
AP_SetSymbol(amta, emap, symbolChanges, 2);
// Change symbol values as a group
AP_GetElementRefFromElementId(emap, &elementRefs[0], "an1.s5");
AP_GetElementRefFromElementId(emap, &elementRefs[1], "an1.s6");
AP_SetSymbolSame(amta, emap, elementRefs, 2, "1", NULL);
// Change individual counter targets
AP_GetElementRefFromElementId(emap, &counterChanges[0].element_ref, "an1.cnt1");
AP_GetElementRefFromElementId(emap, &counterChanges[1].element_ref, "an1.cnt2");
AP_SetCounterTarget(amta, emap, counterChanges, 2);
// Change counter targets as a group
AP_GetElementRefFromElementId(emap, &elementRefs[0], "an1.cnt3");
AP_GetElementRefFromElementId(emap, &elementRefs[1], "an1.cnt4");
AP_SetCounterTargetSame(amta, emap, elementRefs, 2, 20, NULL);
// Reload device
AP_Reload(dev, rto, amta);
1 from micronap.sdk import *
3 def ChangeToDigits(dev, rto, amta, emap):
5  # change individual symbol values
6  ref1 = emap.GetElementRefFromElementId("an1.s1")
7  ref2 = emap.GetElementRefFromElementId("an1.s2")
8  symbolChanges = [ap_symbol_change(ref1, "3", 0), ap_symbol_change(ref2, "2", 0)]
9  amta.SetSymbol(emap, symbolChanges)
11  # change symbol values as a group
12  ref1 = emap.GetElementRefFromElementId("an1.s5")
13  ref2 = emap.GetElementRefFromElementId("an1.s6")
14  amta.SetSymbolSame(emap, [ref1, ref2], "1")
16  # change individual counter targets
17  ref1 = emap.GetElementRefFromElementId("an1.cnt1")
18  ref2 = emap.GetElementRefFromElementId("an1.cnt2")
19  counterChanges = [ap_counter_change(ref1, 10, 0), ap_counter_change(ref2, 15, 0)]
20  amta.SetCounterTarget(emap, counterChanges)
22  # change counter targets as a group
23  ref1 = emap.GetElementRefFromElementId("an1.cnt3")
24  ref2 = emap.GetElementRefFromElementId("an1.cnt4")
25  amta.SetCounterTargetSame(emap, [ref1, ref2], 20)
27  # Reload device
28  dev.Reload(rto, amta)
import com.micron.ap.*;
import java.util.ArrayList;
public class RuntimeExample {
public static void ChangeToDigits(Device dev, RTO rto, Automaton amta, ElementMap emap) throws ApException {
ElementRef ref1, ref2;
ArrayList<ElementChange> changes = new ArrayList<ElementChange>();
ArrayList<ElementRef> elementRefs = new ArrayList<ElementRef>();
// change individual symbol values
ref1 = emap.getElementRefFromElementId("an1.s1");
ref2 = emap.getElementRefFromElementId("an1.s2");
changes.add(new ElementChange(ref1, "3", 0, 0));
changes.add(new ElementChange(ref2, "2", 0, 0));
dev.setSymbol(amta, emap, changes, 2);
// change symbol values as a group
dev.setSymbolSame(amta, emap, elementRefs, "1", elementRefs.size(), null);
// change individual counter targets
ref1 = emap.getElementRefFromElementId("an1.cnt1");
ref2 = emap.getElementRefFromElementId("an1.cnt2");
changes.add(new ElementChange(ref1, "", 10, 0));
changes.add(new ElementChange(ref2, "", 15, 0));
dev.setCounterTarget(amta, emap, changes, changes.size());
// change counter targets as a group
dev.setCounterTargetSame(amta, emap, elementRefs, 20, elementRefs.size(), null);
// Reload device
dev.reload(rto, amta);
1 <anml version="1.0" xmlns:xsi="">
2  <automata-network id="an1">
3  <state-transition-element id="s1" symbol-set="a" start="all-input">
4  <activate-on-match element="s2"/>
5  </state-transition-element>
6  <state-transition-element id="s2" symbol-set="b">
7  <activate-on-match element="cnt1:cnt"/>
8  </state-transition-element>
9  <counter id="cnt1" target="4" at-target="roll">
10  <report-on-target/>
11  </counter>
12  <state-transition-element id="s3" symbol-set="c" start="all-input">
13  <activate-on-match element="s4"/>
14  </state-transition-element>
15  <state-transition-element id="s4" symbol-set="d">
16  <activate-on-match element="cnt2:cnt"/>
17  </state-transition-element>
18  <counter id="cnt2" target="5" at-target="roll">
19  <report-on-target/>
20  </counter>
21  <state-transition-element id="s5" symbol-set="e" start="all-input">
22  <activate-on-match element="s6"/>
23  </state-transition-element>
24  <state-transition-element id="s6" symbol-set="f">
25  <activate-on-match element="cnt3:cnt"/>
26  </state-transition-element>
27  <counter id="cnt3" target="6" at-target="roll">
28  <report-on-target/>
29  </counter>
30  <state-transition-element id="s7" symbol-set="g" start="all-input">
31  <activate-on-match element="s8"/>
32  </state-transition-element>
33  <state-transition-element id="s8" symbol-set="h">
34  <activate-on-match element="cnt4:cnt"/>
35  </state-transition-element>
36  <counter id="cnt4" target="7" at-target="roll">
37  <report-on-target/>
38  </counter>
39  </automata-network>
40 </anml>