Cesnet Liberouter
  • Projects
  • Liberouter
  • Scampi
  • FlowMon
  • NIC
  • NIFIC
  • IDS
  • NetCOPE
  • VHDL design
  • System software
  • Testing
  • Formal verification
  • Netopeer
  • Documents
  • Our hardware
  • Card Availability
  • Our partners
Main page -> System software -> NSIM -> nanoprogramming
NSIM - nanoprogramming

Introduction

This document is a quick introduction for creators of nanoprograms. All examples in this document may be directly processed by nsim. Compile by `nsim my_program -b', debug by `nsim my_program -i'.

Simple instruction set definition

Prior to writing nanoprograms, an instruction set must be defined. Definitions of instructions are recommended to be in a separate file (not directly in a nanoprogram), i.e. `instruction.def'.

Instructions are defined by `#define' directive. Before the first definition, there must be `#instrlen' directive which determines the length of an instruction in bits.

Example 1 -- definitions of some instructions:

#instrlen 12 
#define instruction1 $par1,$par211 $par1[2] 01 $par2[3] $x[3]
#define JUMP $address 1000 $address[8]
#define nop 000000000000

Let's have a look at instruction1. It has two parameters; par1 is two bits long and par2 is three bits long. Operation code is split by par1 into two parts: 11 and 01. The last three bits of instruction1 are undetermined.

These simple definitions allow nanoprograms to be compiled (only).

Writing a nanoprogram

Nanoprograms are written in programming language called Nanoprocessor Assembler (or shortly Nanoassembler). Its full specification is available through [2] in REFERENCES. Following examples will introduce you to its most important constructs.

Example 2 -- a nanoprogram using instructions defined in Example 1:

#include "instruction.def"
instruction1 1,3

Example 3 -- a nanoprogram with macros:

#include "instruction.def"
#define default_value 0
#define default_values default_value,default_value
instruction1 default_value,3
instruction1 default_values

Example 4 -- a nanoprogram with labels and comments:

#include "instruction.def"
start:
instruction1 start,1 ; These instructions are
instruction1 2,3 ; in an infinite loop
JUMP start
end:

Advanced instruction set definition

Advanced instruction set definitions allow nanoprograms to be compiled, debugged and interpreted. That is done by enriching the simple definitions by semantics of instructions in the C programming language.

Example 5 -- advanced instruction definition:

#instrlen 12 #define JUMP $address : 1000 $address[8]{\ ip=code1&0xff;\ }

This definition utilizes two most important automatic variables: ip (instruction pointer), code1 (compiled instruction). List of all automatic variables is in Appendix A of [1].

Often, we need to store the state of nanoprogram in a memory; usually, we emulate a memory that actually exists in hardware. Following example shows how to declare a global variable.

Example 6 -- a global variable and its use in definitions of instructions:

#define{int memory[16];}
#instrlen 12
#define CLR $register : 1001 $x[4] $register[4]{\
memory[code1&0x0f]=0;\
ip++;\
}
#define INC $register : 1010 $x[4] $register[4]{\
memory[code1&0x0f]++;\
ip++;\
}
#define MOVR $reg1,$reg2 : 1011 $reg1[4] $reg2[4]{\
memory[(code1&0xf0)>>4]=memory[code1&0x0f];\
ip++;\
}

In many cases, there are special registers, i.e. accumulator. It is convenient to have them mapped into memory. If this mapping is not a part of hardware design, it is recommended to extend the memory array by these registers. See following example.

Example 7 -- mapping accumulator into memory:

#define ACCUMULATOR 16
#define MEMORY_SIZE 16
#define{int memory[MEMORY_SIZE+1];int *p_accumulator=&(memory[ACCUMULATOR]);}
#instrlen 12
#define CLRACC : 000000000001{\
*p_accumulator=0;\
ip++;\
}

Simulation of inputs and outputs is an important issue. One possible solution will be shown -- we will only use two nanoprocessor assembler directives. The first one references a file with C functions. The second one defines the initial part of the interpreter.

Example 8 -- simulation of inputs and outputs:

#define {#include "simio.c"} ; This file contains functions Save and Load.
#define {\
int memory[16];\
int i,awaiting_in=0,awaiting_out=0;\
char *in_filename=NULL,*out_filename=NULL;\
FILE *input=NULL,*output=NULL;\
for (i=1;i<argc;i++){\
if (argv[i][0]=='-'){\
switch(argv[i][1]){\
case 'd':awaiting_in=1;break;\
case 's':awaiting_out=1;break;\
}\
}else{\
if (awaiting_in){\
in_filename=(char *)malloc((strlen(argv[i])+1));\
strcpy(in_filename,argv[i]);\
awaiting_in=0;\
}else if (awaiting_out){\
out_filename=(char *)malloc((strlen(argv[i])+1));\
strcpy(out_filename,argv[i]);\
awaiting_out=0;\
}\
}\
}\
if (in_filename!=NULL){\
if (NULL==(input=fopen(in_filename,"rb"))){\
exit(ERROR_FILE);\
}\
}\
if (out_filename!=NULL){\
if (NULL==(output=fopen(out_filename,"wb"))){\
exit(ERROR_FILE);\
}\
}\
}
#instrlen 12
#define IO : 000000000010{\
Save(output,memory);\
Load(input,memory);\
}

Important! There should not be any definitions of C variables below the initial part of the interpreter.

How to customize the debugger

Open file `quick_start' and read Section DEBUGGING to get an idea how the debugger works.

The basic debugging functionality is to check and control the run of a program by the means of instruction pointer and line number. The next step is to inspect whether the nanoprogram returns correct results. That may be done by reading the file that stores output (see Example 8 in this file). Another way is to check memory contents on the fly. The debugger can manage three memories: input memory, internal memory and output memory and accesses them linearly. To identify and handle these memories, nanoprocessor assembler source code must contain these directives:
#define { #define PRINT_MEM memory}
#define { #define PRINT_TYPE type}
#define { #define PRINT_MIN minimum_address}
#define { #define PRINT_MAX maximum_address}
Directives regarding input and output memory are analogous, just replace PRINT by PRINTIN or PRINTOUT, respectively. These directives are recommended to be placed in the file with instruction definitions.

Example 9 -- customizing the debugger

#define ACCUMULATOR 16
#define MEMORY_SIZE 16
#define {int memory[MEMORY_SIZE+1];int *p_accumulator=&(memory[ACCUMULATOR]);}
#define { #define PRINT_MEM memory}
#define { #define PRINT_TYPE uint16_t}
#define { #define PRINT_MIN 1} ;address 0 is reserved
#define { #define PRINT_MAX MEMORY_SIZE}

If we debug a program that contains (directly or by the means of #include) definitions from Example 9, we may use commands such as `print 1' or `print ACCUMULATOR'. These commands will show contents of memory[1] and accumulator, respectively.

References

[1] Filip Hofer, CESNET technical report number 5/2003, available at http://www.cesnet.cz/doc/techzpravy/2003/ipv6pktanalysis/ipv6pktanalysis.pdf Chapter 6, Chapter 7 and Appendix A
[2] The Liberouter project, NSIM group, http://www.liberouter.org

Main Page About Liberouter Team Mailing list SVN Contacts