You are on page 1of 4

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,$par2 11 $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.

You might also like