Help

About

Neutrino is a simple 8085 assembler written in plain ol' JavaScript. The goal while creating neutrino was to provide high level functionality but in a simple to use format, so that almost anyone can use it, while still allowing advanced use cases. Since the 8085 does not support any native support for relocatable code, neither does neutrino. However it does have other features to make up for it:
  1. Symbols, in the form of EQUs
  2. Multiple ways to EQU
  3. Full parsing and expression solving, even at instruction level
  4. Duplication using DUP
  5. Conditional Assembly
  6. A fully featured Macro system, with proper name mangling and recursion.
  7. A simple, understandable language based on popular microprocessor texts on 8085, with duck-typing and automatic conversion from decimal numbers.

Basic Syntax

Structure of a statement

Every instruction in neutrino is of the form
[label:]keyword [<number|name>[,<number|name>]].
This simply means that there may be a label, which should always be followed by a colon, followed by a keyword to which this label points. The keyword may be any valid 8085 instruction, or any neutrino directive (which are discussed later).

Label

Labels should always be followed by a colon:
MyLabel: …
Labels can generally be anything, and include anything, including numbers. However, some restrictions appply:
  1. Lables are case-sensitive, that is,
    MyLabel: …
    is different from
    mylabel: …
  2. A label must not be entirely made of numbers and the letters A, B, C, D, E or F (case-insensitive), as that might be confused with a number, due to neutrino's duck-typing.
For best practises, it is recommended that you follow the same naming rule as is used in naming C variables, along with not using any proper
name
s, that is, the names of the registers (namely, a, b, c, d, e, h, l, sp, psw, pc)

Keywords

Every documented 8085 instruction is included in neutrino. In addition, keywords are not case-sensitive (unlike labels). That is, these are roughly equivalent:
INR
Inr
inr

Numbers

Neutrino does two things that are different from a regular 8085 assembler, and both of these are to be more compatible with microprocessor textbooks (well, one of them is. The other is for convenience of people getting used to assembly for the first time.)
  1. It assumes that any hexadecimal number is suffixed with a
    h
    or a
    H
  2. Any number that is
    • not suffixed with a
    H
    , and
    • able to be parsed as a decimal number (contains only the digits
    0-9
    )
    Is parsed as a decimal number, and is then converted into hexadecimal
  3. If a number does not have a trailing
    H
    and cannot be parsed as decimal, will be treated as hexadecimal, if possible.
  4. Otherwise neutrino throws an error.

The BRK Directive

The
BRK
Directive creates a breakpoint that, in the Neutrino Runtime emulator, will cause the program to break at that point. After that you can step through the program. However, you MUST step once before pressing, say, the
EXEC
button, as trying to execute it again would immediately trigger the breakpoint again.

The ORG Directive

The
ORG
directive simply starts writing the following code from whatever address it is called with. It is not necessary, but in case it is not included, the program will be located starting from address
0000H
. It is recommended you choose a higher address for
ORG
as many kits will not allow writing in lower addresses which, usually, is occupied by monitor ROM. Keep in mind that you can write two programs one after the other, with a
ORG
directive with the same addresses. In that case, ORG will simply overwrite the previous contents, with a simple warning. The Listing with Code produced by your frontend will probably show duplicates, however, the actual listing will only have the part that overwrote the previous one.

A Sample Program

This is a simple example program to loop 100 times, and output the count to port 85h
	MVI A, 64H
  LOOP: OUT 85h 
	DCR A
	JNZ LOOP
This is another fully valid way to write the same program, albeit in a way that adheres less strictly to texts on the topic, utilizing neutrino's features:
	mvi a, 100
  LOOP: out 85h 
	dcr a
	jnz LOOP

Slightly advanced features of Neutrino

DUP

The
DUP
directive does exactly what its name suggests: DUP licate a block of code. The block below:
DUP 4
RAL
ENDD
will be expanded into
RAL
RAL
RAL
RAL
ENDD
marks the end of a
DUP
block.
DUP
s also support an expression as their count, which we'll encounter later. Both
DUP
and
ENDUP
are equivalent to their lowercase counterparts, which is common to all directives. These can be used as simple looping constructs with the help of variables LINK section EQU and strings.

DEF, DDEF and DEFARR

The
DEF
,
DDEF
and
DEFARR
directives are used to place data in memory at assemble time. That is, when you say initialize a emulator with the listing produced by neutrino, these data will be present in the memory along with the instructions.
DEF
and co. do not take any arguments, that is, it isn't possible to place them anywhere you want in memory by just using this directive. You must use
ORG
in conjunction with this directive to directly modify memory. You can, however, attach a label to the
DEF
statement and use that as an address to the data:
	org f500
  data: def 50
…
	org f000
	lxi h, data
…
which produces the code
+--------+-----------+----+
| Address|Instruction|Data|
+--------+-----------+----+
|  F500  |           |  32|
|    …   |     …     |  … |
|  F000  |LXI H, F500|  21|
|  F001  |           |  00|
|  F002  |           |  F5|
+--------+-----------+----+
Keep in mind however, if you overwrite any memory previously occupied by instructions, neutrino will warn you, but will not throw an error. This is (currently) by arbitrary design.

DEF

This simply creates a byte with the given data (or whatever the expression passed to it resolves to) in memory at the point where the Address Pointer is at that line. That is, if a byte
XX
is used, it will insert
XX
into memory at the location where, if there was instead a one byte instruction like
MOV
, an instruction would go.

DDEF

Same as
DEF
but declares a double-word (two bytes) in little-endian fashion.

DEFARR

Same as
DEF
but takes a comma separated list, and places them one by one in memory.

Advanced Features of Neutrino

EQU/= or Symbols

[label:] equ value ;or
label = value
A standard feature of most assemblers is to provide symbols, or what we generally call variables, to aid programming. For example:
  port: equ 86h
		…
	mvi a, 1 ;signal external device 
	out port
		…
Symbols can be declared only in two ways: 1. Decimal, in this case the value will be in decimal This mode allows full arithmetic and/or algebraic equations 2. Hexadecimal, in this case the value will be in hexadecimal No arithmetic is allowed only in declaration of this kind. However, you can use these symbols for expressions in other places perfectly well. For example, the following code is invalid:
  port: equ f0 + 6	;invalid
		…
But the following is:
  base: equ 80h
  port: equ base + 6
		…
This is a general rule of the neutrino assembler: You cannot use hexadecimal and decimal in the same expression. Another general rule: You cannot do arithmetic with hexadecimal, period. You however, can push hexadecimal into variables and then use those variables. Labels are optional in this form of
EQUEQU
statement is as follows:
variableName = value
Both are equivalent

EQU and Arrays

EQU
handles arrays exactly like constant literal arrays in other C-like languages. Constant means no modification after you declare them, or insertion/deletion of values. They are there solely for P.O.D. reasons:
    arr = [4,3,2,1]
	mvi a, arr[2]	;equivalent to mvi a, 3
A sharp symbol
#
signifies length, that is, saying
#arr
will give you the length of the array.
    arr = [4,3,2,1]
	mvi a, #arr	;equivalent to mvi a, 4
Some general constraints for arrays are: 1. Arrays must have atleast a single element, or the
EQU
statement will fail. 2. Empty elements (just commas without values) are simply skipped over. 3. An array cannot contain of only empty elements(
[,,]
is invalid)

EQU and Strings

EQU
can easily handle strings. It first converts the string into an array of its ascii values and then uses that.
    arr = "hello"
	mvi a, arr[2]	;equivalent to mvi a, 6CH
	mvi b, #arr 	;equivalent to mvi b, 05H
Unlike arrays, string literal arrays cannot be used in expressions. You have to separately declare them first.

Expression Format

The expression evaluation engine is silentmatt's excellent expr-eval and supports everything that supports, with the addition of strings as arrays.

An assemble-time string output program

The following program outputs a string to port
86H
:
	port = 86H 			;We output to port 86H

	str = "hello"			;The string to output is hello
	i = 0				;This is our counter, or index, into the string
	
	;The following part does this:
	;First it outputs the current element in the string(array)
	;Then it increments the count
	;this works as arithmetic/algebraic equations are fully supported
	;in EQU statements *as long as* they do not contain any hexadecimal
	;literals
	DUP #str 			;Duplicate statements length of str times
	out str[i]			;Output the element
	i = i + 1			;increment index
	ENDD
	
	hlt					;and halt
 
This generates the following code:
+--------+-----------+----+
| Address|Instruction|Data|
+--------+-----------+----+
|  0000  | out 68H   |  D3|
|  0001  |           |  68|
|  0002  | out 65H   |  D3|
|  0003  |           |  65|
|  0004  | out 6CH   |  D3|
|  0005  |           |  6C|
|  0004  | out 6CH   |  D3|
|  0005  |           |  6C|
|  0004  | out 6FH   |  D3|
|  0005  |           |  6F|
|  0004  | hlt       |  76|
+--------+-----------+----+

If-Elif-Else or Conditionals

Neutrino supports if-elif-else conditionals. The general format is as follows:
	if 
		
	elif 
		
		…
	else
		
	endif
The condition must be a expression without any hexadecimal values, or a atomic hexadecimal value. The assembler has some quirks, one being that the different keywords, while within a
if/endif
block are distinguished on basis of their number of arguments, not the keyword, per se. That means a
elif
with no arguments will be treated as an
else
.

Macros

Neutrino includes a powerful macro subsystem with proper label/variable mangling, general argument substitution, nested macros, macro hoisting, and, with the help of the conditional directives, recursion.

Format

The general format of a macro is:
  name: macro arg1, arg2, …
	locals l1, l2, l3 …
	;body
/mangledLabel: ;body
	…
	endm
For example:
	l2: macro x, y, z
	locals q
	mvi a, x
/atmvb: mov b, y
	mov c, z
	q = 15
	mvi d, q
	endm

	l2 15, B, C
This is a nonsensical macro, but it illustrates how a macro may be used.

Passing Names

As shown in the example, if you want to pass a name to an macro, pass the name of the register in UPPERCASE. This serves as an hint to the assembler to not parse it as an hexadecimal value. However, if used in a place where a hexadecimal value is to be used, it can and will be converted into one.

Local Label Mangling

Notice the label
/atmvb:
in the previous example. It is a local label. As macros are just substituted, two conflicting labels, from say two invocations of the same macro, may overwrite each other and thus only the second label will be used in the final assembly. To solve this issue, macros have the ability to
mangle
labels. A mangled label is declared (within a macro) as
/labelname
, and is replaced with
/labelName_indLocInstNoXX
where
XX
is the current index of the macro. The index of a macro is initialized by 0 and increases by one every time a macro is called. However, this property is internal and cannot be referenced. It is recommended that you do not use labels of this form in your assembly code, to avoid conflicts. The
/
is a hint to create a local. It is not used while actually using the label, that is, a local label declared
/abc
will be used as
abc
.

Locals

The locals directive allows declaration of local symbols, that is, symbols that have their names mangled. This is useful when you do not want to pollute the global namespace with symbol names.

Macro Hoisting

Macros in neutrino are hoisted, that is, a macro can be defined anywhere in code, and be called from everywhere in the code, even before the part in which the macro was defined. The same applies to labels, but not symbols.

Nesting Macros

Macros can be nested in one another. In addition, any macro may call any other macro, or declare another macro. However, it is not possible to conditionally define macros.

Recursive Macros

Macros can call themselves, and thus allow for primitive recursion:
  ssum: macro n
	if n > 1
	ssum n - 1
	endif
	adi n
	endm

	ssum 5
	hlt
Note that the assembler will happily keep recurring till it runs out of memory. It is up to the user to control how deep the recursion is and the assembler will *not* warn you if you do not control how deep your recursion goes. An infinite recursion will crash the assembler