Lesson 10: Adding Custom Instructions to Agilla

Last Updated on May 16, 2006 9:03 PM .

Adding new instructions to Agilla is easy. This tutorial describes the process.

For this example, we will create a new instruction, findmatch, that takes a variable as a parameter, and searches through the heap to find an equivalent variable. If it finds a match, it stores the index on the opstack and sets the condition code equal to 1, otherwise it sets the condition code equal to 1.

Adding the Instruction to the Firmware

The built-in Agilla instructions are located in <agilla>/opcodes. All instructions must provide interface BytecodeI, which is located in <agilla>/interfaces/BytecodeI.nc. ByteCodeI defines a single command:

command result_t execute(uint8_t instr, AgillaAgentContext* context);

This command is called when the instruction is executed. It is passed the instruction's byte code to allow a single component to implement multiple instructions. It is also passed the context of the agent that executed the instruction.

Usually, an instruction is implemented using a configuration and a module. Here is the implementation of findmatch:

Configuration:

configuration OPfindMatch{
provides interface BytecodeI;
}
implementation {
components OPfindMatchM, OpStackC, TupleUtilC, ErrorMgrProxy;
BytecodeI = OPfindMatchM;
OPfindMatchM.OpStackI -> OpStackC;
OPfindMatchM.TupleUtilI -> TupleUtilC;
OPfindMatchM.Error -> ErrorMgrProxy;
}

Module:

includes Agilla;

/**
 * Searches through the heap to find the index of a matching variable.
 */
module OPfindMatchM {
  provides interface BytecodeI;
  uses {
    interface OpStackI;
    interface TupleUtilI;   // For comparing variables
    interface ErrorMgrI as Error;
  }
}
implementation {

  command result_t BytecodeI.execute(uint8_t instr, 
  	AgillaAgentContext* context) 
  {
    AgillaVariable arg; // the variable to search for    
    dbg(DBG_USR1, "VM (%i:%i): Executing findMatch\n", 
		context->id.id, context->pc-1);
    if (call OpStackI.popOperand(context, &arg)) 
    {
      int16_t i;
      for (i = 0; i < AGILLA_HEAP_SIZE; i++) 
      {
        if (context->heap.pos[i].vtype != AGILLA_TYPE_INVALID)
        {
          if (call TupleUtilI.fEquals(&arg, &context->heap.pos[i]))
          {
            call OpStackI.pushValue(context, i); // save index on stack
            context->condition = 1; // indicate match found
            return SUCCESS;
          }
        }
        
      }
      context->condition = 0; // indicate no match found
      return SUCCESS;
    } else
    return FAIL;    
  }
}

Next choose a bytecode for your instruction. Open <Agilla>/AgillaOpcodes.h. This file contains enumerations of all instructions in Agilla. The main enumeration is BasicInstruction. These are the primary instructions in Agilla that are listed here. Since all of the bytecodes in the Basic instruction set are used, Agilla provides 13 extended instruction sets, each of which can contain an additional 256 instructions. In this case, we will assign findmatch bytecode 0x1c within enumeration ExtendedISA1.

typedef enum {
	...
	IOPfindMatch = 0x1c,
	...
} ExtendedISA1;

Now open <Agilla>/Agilla.nc and wire the opcode to AgillaEngineC.

implementation {
	...
	components OPfindMatch;
	...
	AgillaEngineC.ExtendedISA1[IOPfindMatch] -> OPfindMatch;
	...
}

You now have to recompile and install the Agilla firmware on the motes.

Adding the instruction to the Agilla Assembler (Java)

The final step is to modify the AgentInjector to recognize the new instruction. Go into edu.wustl.mobilab.agilla.opcodes and open the opcode class that the new instruction is part of (in this case ExtendedOpcodes1.java). Add the following line to it:

public static final short OPfindMatch = 0x1c;

This specifies the instruction and its opcode to the Agilla assembler.

If the instruction contains embedded operands, you need to specify the number operands and the number of bits dedicated to it using:

public static final int ArgNumOP<opcode> = <num operands>;
public static final int ArgNumOP<opcode> = <num bits>; 

Sometimes instructions have predefined arguments. For example, you can pushc photo. To specify this, create a two-dimensional array where each row contains the argument and its corresponding value. For example, the specification for pushc is as follows:

	public static final String[][] ArgValOPpushc = new String[][] {
		{"temperature", "1"},
		{"temp", "1"},
		{"photo", "2"},
		{"mic", "3"},
		{"microphone", "3"},
		{"magx", "4"},
		{"magnetometerx", "4"},
		{"magy", "5"},
		{"magnetometery", "5"},
		{"accelx", "6"},
		{"accelerometerx", "6"},
		{"accely", "7"},
		{"accelerometery", "7"},
		
		// the following are for changing an agent's description
		{"unknown", "3"},
		{"unspecified","0"},
		{"cargo","1"},
		{"fire","2"}
	};

 


This work is supported by the ONR MURI Project CONTESSA and the NSF under grant number CCR-9970939.