Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/ndf-zz/asfv1

Alternate Assembler for Spin Semi FV-1
https://github.com/ndf-zz/asfv1

Last synced: 17 days ago
JSON representation

Alternate Assembler for Spin Semi FV-1

Awesome Lists containing this project

README

        

# asfv1

Alternate Assembler for Spin Semi FV-1

Copyright (C) 2017-2021 Nathan Fraser

An alternate assembler for the Spin Semiconductor FV-1 DSP. This
assembler aims to replicate some of the behaviour of the Spin FV-1
assembler in standard Python, for developers who are unable or
unwilling to use the Spin provided IDE.

## Requirements

- Python \>= 2.6

## Installation

Make sure your system has a python interpreter
(preferably python3), then install from the
[Python Package Index](https://pypi.org/)
using the
[pip](https://pip.pypa.io/en/stable/)
command:

$ pip3 install asfv1

or

$ pip install asfv1

For system-specific installation instructions see
[System Specific Installation](#system-specific-installation)
below.

## Description

asfv1 reads a single FV-1 DSP program then parses and assembles it.
If no errors are encountered, machine code is written to an output
file. If the output filename ends with 'hex', an Intel hex
encoded output is produced, otherwise raw binary data is written.

usage: asfv1 [-h] [-q] [-v] [-c] [-s] [-p {0,1,2,3,4,5,6,7}] [-b]
infile outfile

- `infile` : Filename for an ASCII, utf-8 or utf-16 encoded text file
containing FV-1 assembly (see [Assembly Program Syntax](#assembly-program-syntax) below)

- `outfile` : Filename for assembled output. If filename ends with 'hex',
an Intel hex file is written.

- `-h`, `--help` : Show a help message and exit

- `-q`, `--quiet` : Suppress warning messages

- `-v`, `--version` : Print program version and exit

- `-c`, `--clamp` : Clamp out of range instruction operand values without error.
A warning message is printed for each clamped operand.

- `-s`, `--spinreals` : Interpret integer literals `1` and `2` as 1.0 and 2.0
respectively. This option should be used with SpinASM assembly.

- `-p {0,1,2,3,4,5,6,7}` : Nominate one of the eight available program
slots on an FV-1 eeprom as the target. When this option is used with
binary output, machine code is offset appropriately in the target
file, allowing for assembly into an existing binary bank file. When
Intel HEX output is requested, the output file will include a single
program and relevant offset information for the target program.

- `-b`, `--binary` : Force output in binary format, even if `outfile`
ends with 'hex'.

## Assembly Program Syntax

An FV-1 assembly program recognised by asfv1 closely resembles
the [SpinIDE](http://spinsemi.com/products.html) (.spn) format.
Input is an ASCII, utf-8 or utf-16 encoded
text file containing zero to 128 FV-1
[instructions](#instructions) with optional
[targets](#jump-targets), [labels](#label-assignment),
[comments](#comments) and [assembly directives](#memory-allocation).
All text is matched case-insensitively and runs of whitespace characters
(newline, tab, space) are condensed.
Each of the input instructions is assembled into a single 32 bit
machine code. If less than 128 assembly instructions are input,
the unallocated program space is padded with 'NOP' instructions
(0x00000011).

For [example](example.asm):

; A complete, but useless FV-1 assembly program
MEM delay int(32767*3/5) ; ~0.6 sec delay
EQU input ADCL ; use ADCL for input
EQU output DACL ; use DACL for output
EQU vol REG0 ; use REG0 for volume
start: skp RUN,main ; skip to main after first sample
ldax POT0 ; read from POT0
wrax vol,0.0 ; write volume to register
main: ldax input ; read from input
mulx vol ; scale by volume
wra delay,0.0 ; write to delay
rda delay^,0.5 ; read from delay midpoint
rda delay#,0.5 ; read from delay end
wrax output,0.0 ; write to output

When assembled with asfv1, the resulting machine code contains
9 instructions and padding with NOP instructions:

$ asfv1 -q example.asm example.bin
$ hd example.bin
00000000 80 40 00 11 00 00 02 05 00 00 04 06 00 00 02 85 |.@..............|
00000010 00 00 04 0a 00 00 00 02 20 04 cc c0 20 09 99 80 |........ ... ...|
00000020 00 00 02 c6 00 00 00 11 00 00 00 11 00 00 00 11 |................|
00000030 00 00 00 11 00 00 00 11 00 00 00 11 00 00 00 11 |................|
*
00000200

### Comments

A semicolon character ';' starts comment text. The assembler will
ignore all text including the ';' up to the end of a line.
Examples:

; Comment out a whole line
target: or 0xffffff ; comment to end of line
trget2: ; comment between target and instruction
and 0x000000 ; comment follows instruction
; xor 0xa5a5a5 ; instruction commented out
; excessive commenting:
addr02: cho ; op=0x14 interpolated memory access
rdal, ; type=0x3 read offset(LFO) into ACC
SIN1, ; lfo=0x1 use SIN1 LFO
COS|REG ; flags=0xb register LFO and use COS output

### Label Assignment

EQU LABEL EXPRESSION

Directive 'EQU' assigns the constant value resulting from the
evaluation of 'EXPRESSION' (see
[Operand Expressions](#operand-expressions)
below) to the text label 'LABEL'.
LABEL must begin with one alphabetic character in the set [A-Z,a-z]
followed by any number of alphanumeric characters or underscores:
[A-Z,a-z,0-9,_].
EXPRESSION can contain any previously assigned labels, including
those pre-defined by the assembler (see
[Pre-defined Labels](#pre-defined-labels) below). For
compatibility with SpinASM, the order of 'EQU' and 'LABEL'
may be swapped. Examples:

EQU input ADCL ; assign value of ADCL (0x14) to 'input'
EQU r3_7 3/7 ; assign the value 3/7 to 'r3_7'
inve EQU 1/r3_7 ; assign the inverse of 'r3_7' to 'inve'

EQU does not generate any code in the program, it merely reserves
the name for subsequent use. The parser evaluates all expressions
in-place so a label must be declared before it is used:

or missing ; error
EQU missing 123 ; missing is used before definition

parse error: Undefined label missing on line ...

Re-defining an already assigned label is allowed, but will generate
a warning message:

EQU POT0 POT1 ; point POT0 to POT1

warning: Label POT0 re-defined on line ...

Labels, mnemonics and operators are matched case insensitively:

EQU Label_One -1.0 ; assign 1.0 to 'LABEL_ONE'
eQu lABEL_oNE -1.0 ; assign 1.0 to 'LABEL_ONE' again
Or label_one ; or -1.0
oR LABEL_ONE ; or -1.0
OR LABEL_ONE ; or -1.0
or lAbEl_OnE ; or -1.0

### Memory Allocation

MEM LABEL EXPRESSION

Addresses in the FV-1's 32768 sample circular buffer can be assigned
by the assembler using the 'MEM' directive. MEM reserves a portion
of memory that represents a delay of 'EXPRESSION' samples between
the start point and end point, and assigns three labels:

LABEL start of delay segment
LABEL^ midpoint of delay segment
LABEL# end of delay segment

EXPRESSION must define an integer number of samples
or a parse error will be generated:

MEM invalid 123.4556 ; invalid memory definition

Memory INVALID length 123.4556 not integer on line 42

MEM third 32767//3 ; valid due to integer divide
MEM d0_13 int(0.13*32767) ; valid due to explicit type cast

LABEL has the same requirements as for [EQU](#label-assignment), and
the assigned labels can be used in any expression. Eg:

MEM Del_A 375 ; declare a 375 sample delay called 'DEL_A'
wra DEL_A,0.0 ; write to start of delay, DEL_A=0
rda del_a^,0.5 ; read 0.5*midpoint of delay, DEL_A^=187
rda DeL_A#,0.5 ; add to 0.5*end of delay, DEL_A#=375

The assembler keeps track of allocated memory, placing each new
segment immediately after those previously defined. Each segment
with a delay of LENGTH, will consume LENGTH+1 samples of memory.
An attempt to use more than the available space will trigger
a parse error:

MEM long 0x7f00 ; long:0 long#:0x7f00
MEM short 0x00ff ; short:0x7f01 short#:0x8000 (error)

parse error: Delay exhausted: requested 255 exceeds 254 available on line ...

The caret character '^' is also used in expressions as
the bitwise XOR operator, so expressions which reference
a delay may need to be explicitly parenthesised if used with '^':

or delay^0xffff ; parse error - delay label takes caret

parse error: Unexpected INTEGER 0xffff on line ...

or (delay)^0xffff ; OK - parentheses enforce ordering
or delay^^0xffff ; OK

### Jump Targets

Jump targets label a particular address in the program output
and can be placed between instructions anywhere in a source file.
A jump target is a text label followed by a colon ':' character:

skp 1,TARGET1 ; skip offset is 3
skp 2,TARGET2 ; skip offset is 2
skp 4,TARGET3 ; skip offset is 1
or 0xff Target1: ; target after instr
TARGET2: ; target on its own line
tarGET3: and 0x12 ; all three targets point to this instruction

Use of an already defined label for a target will result in a parse error:

EQU error -1
error: or 0x800000

parse error: Target ERROR already assigned on line ...

Target labels are not assigned values until parsing is complete
and they can only be used as a destination for a
[skip instruction](#skp-conditions-offset). For example,
the following attempt to offset from a target generates a
parse error:

skp NEG,target ; skip to target if negative
skp 0,target+1 ; error - invalid expression
target: clr ; clear ACC
wrax DACL,0.0 ; output only positive

parse error: Unexpected OPERATOR + on line ...

To achieve the desired if/else behaviour, use a second target:

skp NEG,ifpart ; skip to target if negative
skp 0,elsept ; else, skip ahead
ifpart: clr ; clear ACC
elsept: wrax DACL,0.0 ; output >= 0

### Instructions

An instruction is represented by a mnemonic text followed by zero
or more [operand expressions](#operand-expressions) separated by commas:

Mnemonic | Operands | Description
--- | --- | ---
[rda](#rda-address-multiplier) | ADDRESS,MULTIPLIER | multiply delay[ADDRESS] and accumulate
[rmpa](#rmpa-multiplier) | MULTIPLER | multiply delay[(*ADDR_PTR)] and accumulate
[wra](#wra-address-multiplier) | ADDRESS,MULTIPLIER | write delay[ADDRESS] and multiply
[wrap](#wrap-address-multiplier) | ADDRESS,MULTIPLIER | write delay[ADDRESS], multiply and add LR
[rdax](#rdax-register-multiplier) | REGISTER,MULTIPLIER | multiply (*REGISTER) and accumulate
[rdfx](#rdfx-register-multiplier) | REGISTER,MULTIPLIER | subtract (*REGISTER), multiply and add (*REGISTER)
[ldax](#ldax-register) | REGISTER | load (*REGISTER)
[wrax](#wrax-register-multiplier) | REGISTER,MULTIPLIER | write (*REGISTER) and multiply
[wrhx](#wrhx-register-multiplier) | REGISTER,MULTIPLIER | write (*REGISTER) and highpass shelf
[wrlx](#wrlx-register-multiplier) | REGISTER,MULTIPLIER | write (*REGISTER) and lowpass shelf
[maxx](#maxx-register-multiplier) | REGISTER,MULTIPLIER | load maximum of absolute values
[absa](#absa) | | load absolute value of ACC
[mulx](#mulx-register) | REGISTER | multiply by (*REGISTER)
[log](#log-multiplier-offset) | MULTIPLIER,OFFSET | log2(ACC), multiply and offset
[exp](#exp-multiplier-offset) | MULTIPLIER,OFFSET | 2\*\*(ACC), multiply and offset
[sof](#sof-multiplier-offset) | MULTIPLIER,OFFSET | multiply and offset
[and](#and-value) | VALUE | bitwise AND
[clr](#clr) | | clear ACC
[or](#or-value) | VALUE | bitwise OR
[xor](#xor-value) | VALUE | bitwise XOR
[not](#not) | | bitwise negation
[skp](#skp-conditions-offset) | CONDITIONS,OFFSET | skip offset instructions if all conditions met
[jmp](#jmp-offset) | OFFSET | jump offset instructions
[nop](#nop) | | no operation
[wlds](#wlds-lfo-frequency-amplitude) | LFO,FREQUENCY,AMPLITUDE | ajdust SIN LFO
[wldr](#wldr-lfo-frequency-amplitude) | LFO,FREQUENCY,AMPLITUDE | adjust RMP LFO
[jam](#jam-lfo) | LFO | reset LFO
[cho](#cho-rda-lfo-flags-address) | TYPE,LFO,FLAGS,ADDRESS | interpolated memory access
[raw](#raw-u32) | U32 | insert U32 opcode

Each operand must evaluate to a single constant numeric
value. The sizes and types are specific to each instruction
(see [Instruction Reference](#instruction-reference) below).

### Operand Expressions

Operand expressions are any valid combination
of labels, numbers, parentheses and the following
operators, listed from highest to lowest precedence. Operators
on the same line have the same precedence, and are evaluated
left to right - except for '**' (power) which works
[as in the python intepreter](https://docs.python.org/3/reference/expressions.html#the-power-operator).

Operator | Function | Note
--- | --- | ---
`\|` | bitwise or | valid for integers only
`^` | bitwise xor | valid for integers only
`&` | bitwise and | valid for integers only
`<< >>` | shift left, shift right | valid for integers only
`+ -` | add, subtract |
`* // /` | multiply, divide | `//` forces integer divide
`+ - ~ int` | unary plus, minus, invert bits, integer cast | `!` is an alias for `~`
`**` | power | Binds right: `-10**-2` = `-0.01`

The following numeric entry formats are recognised:

Literal | Value | Type
--- | --- | ---
`123` | 123 | Decimal integer
`0x123` | 291 | Hexadecimal integer
`$123` | 291 | Hexadecimal integer
`0b1010_1111` | 175 | Binary integer
`%0101_1111` | 175 | Binary integer ('_' is ignored)
`1.124` | 1.124 | Floating point number
`1.124e-3` | 0.001124 | Floating point number with exponent

The final value of an expression will be either an
integer, which is used for the instruction operand
unchanged or a floating point value which is later converted
to the closest fixed-point integer of the required size
(see [Fixed Point Conversion](#fixed-point-conversion) below).
The unary `int` operator will force a floating-point value
to be rounded and converted to the nearest integer:

MEM d0_23 int(0.23*0x8000) ; ~0.23 second delay = 7537 samples

If the result of the expression is a complex number, or if the
expression cannot be evaluated, a parse error is generated:

EQU j (-1)**(1/2) ; j=sqrt(-1)

parse error: Expression result (6.123233995736766e-17+1j) invalid type on line ...

EQU ns 1024<<(-1) ; impossible negative shift

parse error: negative shift count on line ...

EQU tms (1024/13)&0x123 ; type mismatch

parse error: unsupported operand type(s) for &: 'float' and 'int' on line ...

More formally, a valid operand expression matches the
following grammar:

expression ::= or_expr
or_expr ::= xor_expr | or_expr "|" xor_expr
xor_expr ::= and_expr | xor_expr "^" and_expr
and_expr ::= shift_expr | and_expr "&" shift_expr
shift_expr ::= a_expr | shift_expr "<<" a_expr | shift_expr ">>" a_expr
a_expr ::= m_expr | a_expr "+" m_expr | a_expr "-" m_expr
m_expr ::= u_expr | m_expr "*" u_expr | m_expr "//" u_expr | m_expr "/" u_expr
u_expr ::= power | "-" u_expr | "+" u_expr | "~" u_expr | "int" u_expr
power ::= atom ["**" u_expr]
atom ::= label | literal | "(" expression ")"

Where label is a text label, and literal is a number. Expressions
are parsed and evaluated in-place by asfv1. All labels must be defined
before they are referenced in an expression.

### Fixed Point Conversion

For instructions that require fixed-point real values as
input, asfv1 automatically converts real expression results
from an intermediate floating-point value to the nearest
equivalent signed fixed-point integer. This value is then
masked to the correct number of bits and placed in machine
code. The conversion is performed for all types by computing
the multiplication:

fixed = int(round(floating * REFERENCE)) & MASK

Where REFERENCE is the equivalent integer value of +1.0 in the
desired number format and floating is the saturated intermediate
floating-point value. The following table lists the sizes and range
of each of the FV-1 number formats.

Name Bits Refval Minval Maxval
S4_6 11 64 -16.0 15.984375
S1_9 11 512 -2.0 1.998046875
S_10 11 1024 -1.0 0.9990234375
S1_14 16 16384 -2.0 1.99993896484375
S_15 16 32768 -1.0 0.999969482421875
S_23 24 8388608 -1.0 0.9999998807907104

For example, the following entries all generate the same
code:

or -0.4335784912109375 ; S_23 real value
or -0x377f80&0xffffff ; signed 23bit int to unsigned 24 bit int
or 0xc88080 ; unsigned 24bit int in hexadecimal
or 13140096 ; unsigned 24bit int in decimal
or 1<<23|2**22|1<<19|2**15|1<<7 ; unsigned 24bit int by bitwise or
or 0b110010001000000010000000 ; unsigned 24bit int in binary
or int(-0.4335784912109375*2**23)&0xffffff ; S_23 to unsigned 24bit conversion

### Pre-defined Labels

The following text labels are pre-defined by asfv1.
Refer to the FV-1 datasheet for information on the
function of registers.

Label | Value | Description
--- | --- | ---
`SIN0_RATE` | `0x00` | SIN0 rate control register
`SIN0_RANGE` | `0x01` | SIN0 range control register
`SIN1_RATE` | `0x02` | SIN1 rate control register
`SIN1_RANGE` | `0x03` | SIN1 range control register
`RMP0_RATE` | `0x04` | RMP0 rate control register
`RMP0_RANGE` | `0x05` | RMP0 range control register
`RMP1_RATE` | `0x06` | RMP1 rate control register
`RMP1_RANGE` | `0x07` | RMP1 range control register
`POT0` | `0x10` | POT0 input register
`POT1` | `0x11` | POT1 input register
`POT2` | `0x12` | POT2 input register
`ADCL` | `0x14` | Left AD input register
`ADCR` | `0x15` | Right AD input register
`DACL` | `0x16` | Left DA output register
`DACR` | `0x17` | Right DA output register
`ADDR_PTR` | `0x18` | Delay address pointer
`REG0` - `REG31` | `0x20` - `0x3f` | General purpose registers
`SIN0` | `0x00` | SIN0 LFO selector
`SIN1` | `0x01` | SIN1 LFO selector
`RMP0` | `0x02` | RMP0 LFO selector
`RMP1` | `0x03` | RMP1 LFO selector
`RDA` | `0x00` | CHO type selector
`SOF` | `0x02` | CHO type selector
`RDAL` | `0x03` | CHO type selector
`SIN` | `0x00` | CHO flag
`COS` | `0x01` | CHO flag
`REG` | `0x02` | CHO flag
`COMPC` | `0x04` | CHO flag
`COMPA` | `0x08` | CHO flag
`RPTR2` | `0x10` | CHO flag
`NA` | `0x20` | CHO flag
`RUN` | `0x10` | SKP condition flag
`ZRC` | `0x08` | SKP condition flag
`ZRO` | `0x04` | SKP condition flag
`GEZ` | `0x02` | SKP condition flag
`NEG` | `0x01` | SKP condition flag

Pre-defined labels may be re-defined within a source file,
however, the re-defined value only applies to label references
*following* the assignment. Any re-definition will issue a
warning message:

ldax POT0 ; load POT0 (0x10)
EQU POT0 ADCL ; re-define POT0 to be 0x14
ldax POT0 ; load from ADCL (0x14)

warning: Label POT0 re-defined on line ...

## Instruction Reference

### rda ADDRESS, MULTIPLIER

Multiply and accumulate a sample from delay memory.

ADDRESS: Real S_15 or Unsigned 15bit integer delay address
MULTIPLIER: Real S1_9 or Unsigned 11bit integer
Assembly: MULTIPLIER<<21 | ADDRESS<<5 | 0b00000

Action:

ACC <- ACC + MULTIPLIER * delay[ADDRESS]
PACC <- ACC
LR <- delay[ADDRESS]

Example:

rda pdel^+324,0.1 ; add 0.1 * delay[pdel^+324] to ACC
rda pdel#,0.3 ; add 0.3 * delay[pdel#] to ACC
rda 0.3,0.5 ; add 0.5 * delay[0x2666] to ACC

### rmpa MULTIPLIER

Multiply and accumulate a sample from the delay memory, using
the contents of ADDR_PTR as the delay address.

MULTIPLIER: Real S1_9 or Unsigned 11bit integer
Assembly: MULTIPLIER<<21 | 0b00001

Action:

ACC <- ACC + MULTIPLIER * delay[(*ADDR_PTR)>>8]
PACC <- ACC
LR <- delay[(*ADDR_PTR)>>8]

Notes:

- 15 bit delay addresses in ADDR_PTR are left shifted 8 bits,
so they can be accessed using the real S_23 value 0->0.9999 or
directly by multiplying the desired integer delay address by 256.

Example:

or 1234<<8 ; load 1234*256 into ACC
wrax ADDR_PTR,0.0 ; save to ADDR_PTR and clear ACC
rmpa 0.25 ; add 0.25 * delay[1234] to ACC
clr
or 0.5 ; load 0.5 into ACC
wrax ADDR_PTR,0.0 ; save the address pointer
rmpa 0.7 ; add 0.7 * delay[0x4000]

### wra ADDRESS, MULTIPLIER

Write ACC to delay memory and scale by multiplier.

ADDRESS: Real S_15 or Unsigned 15bit integer delay address
MULTIPLIER: Real S1_9 or Unsigned 11bit integer
Assembly: MULTIPLIER<<21 | ADDRESS<<5 | 0b00010

Action:

delay[ADDRESS] <- ACC
PACC <- ACC
ACC <- MULTIPLIER * ACC

Example:

wra pdel^+324,0.25 ; write ACC to delay[pdel^+324] scale ACC by 0.25
wra pdel#,0.0 ; write ACC to delay[pdel#] clear ACC

### wrap ADDRESS, MULTIPLIER

Write ACC to delay memory, multiply ACC, add to LR and save to ACC.

ADDRESS: Real S_15 or Unsigned 15bit integer delay address
MULTIPLIER: Real S1_9 or Unsigned 11bit integer
Assembly: MULTIPLIER<<21 | ADDRESS<<5 | 0b00011

Action:

delay[ADDRESS] <- ACC
ACC <- LR + MULTIPLIER * ACC
PACC <- ACC

Example:

ldax ADCL ; read from left input
wrap 0x1000,0.3 ; write ACC to delay[0x1000] scale ACC by 0.3 add to LR

### rdax REGISTER, MULTIPLIER

Multiply and accumulate contents of register.

REGISTER: Unsigned 6bit integer register address
MULTIPLIER: Real S1_14 or Unsigned 16bit integer
Assembly: MULTIPLIER<<16 | REGISTER<<5 | 0b00100

Action:

ACC <- ACC + MULTIPLIER * (*REGISTER)
PACC <- ACC

Example:

rdax POT0,0.11 ; add 0.11*POT0 to ACC
rdax REG8,-0.66 ; subtract 0.66*REG8 from ACC

### rdfx REGISTER, MULTIPLIER

Subtract register content from ACC, multiply and add to register content.

REGISTER: Unsigned 6bit integer register address
MULTIPLIER: Real S1_14 or Unsigned 16bit integer
Assembly: MULTIPLIER<<16 | REGISTER<<5 | 0b00101

Action:

ACC <- (*REGISTER) + MULTIPLIER * (ACC - (*REGISTER))
PACC <- ACC

Example:

rdfx ADCL,0.0 ; transfer ADCL content to ACC
rdfx REG0,0.3 ; average using temp reg
wrlx REG0,0.0 ; infinite shelf LPF

### ldax REGISTER

Copy register content to ACC. Assembles to rdax with a multiplier
of 0.0.

REGISTER: Unsigned 6bit integer register address
Assembly: REGISTER<<5 | 0b00101

Action:

ACC <- (*REGISTER)
PACC <- ACC

Example:

ldax ADCL ; load ADCL content into ACC
wrax DACL,0.0 ; write ACC to DACL

### wrax REGISTER, MULTIPLIER

Copy ACC to REGISTER, and multiply ACC.

REGISTER: Unsigned 6bit integer register address
MULTIPLIER: Real S1_14 or Unsigned 16bit integer
Assembly: MULTIPLIER<<16 | REGISTER<<5 | 0b00110

Action:

(*REGISTER) <- ACC
ACC <- MULTIPLIER * ACC
PACC <- ACC

Example:

wrax REG0,-1.0 ; copy ACC into REG0 and invert ACC
wrax DACL,0.0 ; copy ACC into DAC and clear ACC

### wrhx REGISTER, MULTIPLIER

Copy ACC to REGISTER, multiply ACC and add to PACC.

REGISTER: Unsigned 6bit integer register address
MULTIPLIER: Real S1_14 or Unsigned 16bit integer
Assembly: MULTIPLIER<<16 | REGISTER<<5 | 0b00111

Action:

(*REGISTER) <- ACC
ACC <- PACC + MULTIPLIER * ACC
PACC <- ACC

Example:

rdfx REG0,0.3 ; average using temp reg
wrhx REG0,-0.5 ; -6dB shelf highpass filter
wrhx REG1,0.0 ; swap PACC and ACC

### wrlx REGISTER, MULTIPLIER

Copy ACC to REGISTER, subtract ACC from PACC, multiply and add to PACC.

REGISTER: Unsigned 6bit integer register address
MULTIPLIER: Real S1_14 or Unsigned 16bit integer
Assembly: MULTIPLIER<<16 | REGISTER<<5 | 0b01000

Action:

(*REGISTER) <- ACC
ACC <- PACC + MULTIPLIER * (PACC - ACC)
PACC <- ACC

Example:

rdfx REG0,0.3 ; average using temp reg
wrlx REG0,-0.5 ; -6dB shelf lowpass filter
wrlx REG1,0.0 ; swap PACC and ACC

### maxx REGISTER, MULTIPLIER

Copy the maximum of the absolute value of ACC and the
absolute value of REGISTER content times MULTIPLIER into ACC.

REGISTER: Unsigned 6bit integer register address
MULTIPLIER: Real S1_14 or Unsigned 16bit integer
Assembly: MULTIPLIER<<16 | REGISTER<<5 | 0b01001

Action:

ACC <- maximum (abs (ACC), abs (MULTIPLIER * (*REGISTER)))
PACC <- ACC

Example:

ldax ADCL ; copy ADCL to ACC
maxx ADCR,1.0 ; copy max of left and right to ACC
maxx 0,0 ; absolute value of ACC

### absa

Copy the absolute value of ACC back into ACC. Assembles to maxx
with null register and zero multiplier.

Assembly: 0b01001

Action:

ACC <- abs (ACC)
PACC <- ACC

Example:

ldax ADCL ; copy ADCL to ACC
absa ; absolute value of ACC

### mulx REGISTER

Multiply ACC by the content of REGISTER.

REGISTER: Unsigned 6bit integer register address
Assembly: REGISTER<<5 | 0b01010

Action:

ACC <- ACC * (*REGISTER)
PACC <- ACC

Example:

ldax ADCL ; copy ADCL to ACC
mulx POT0 ; scale input by POT0

### log MULTIPLIER, OFFSET

Compute the base 2 log of the absolute value of ACC,
multiply and then add offset. Input ACC is S_23,
result ACC is S4_19.

MULTIPLIER: Real S1_14 or Unsigned 16bit integer
OFFSET: Real S_10 or Unsigned 11bit integer
Assembly: MULTIPLIER<<16 | OFFSET<<5 | 0b01011

Action:

ACC <- OFFSET + MULTIPLIER * log2 (abs (ACC))
PACC <- ACC

Notes:

- OFFSET is input as a real S_10 value, however it represents
an S4_6 offset of the accumulator, which is in S4_19 after the log.

Example:

log 0.5,0.0 ; 2*log2(a) = log2(a**2)
exp 1.0,0.0 ; a = 2**(0.5 * log2(a**2)) [square root]

### exp MULTIPLIER, OFFSET

Raise 2 to the power of ACC, multiply and add OFFSET.
Input ACC is S4_16, result ACC is S_23.

MULTIPLIER: Real S1_14 or Unsigned 16bit integer
OFFSET: Real S_10 or Unsigned 11bit integer
Assembly: MULTIPLIER<<16 | OFFSET<<5 | 0b01100

Action:

ACC <- OFFSET + MULTIPLIER * 2**ACC
PACC <- ACC

Example:

log 1.0,4.0 ; log2(16*a) = log2(16) + log2(a)
exp 1.0,0.0 ; 16*a = 2**(4+log2(a)) [x16 gain]

### sof MULTIPLIER, OFFSET

Scale ACC and then add an offset.

MULTIPLIER: Real S1_14 or Unsigned 16bit integer
OFFSET: Real S_10 or Unsigned 11bit integer
Assembly: MULTIPLIER<<16 | OFFSET<<5 | 0b01101

Action:

ACC <- OFFSET + MULTIPLIER * ACC
PACC <- ACC

Example:

sof 1.5,-0.4 ; multiply acc by 1.5 and subtract 0.4

### and VALUE

Perform a bitwise AND of ACC and VALUE

VALUE: Real S_23 or Unsigned 24bit integer
Assembly: VALUE<<8 | 0b01110

Action:

ACC <- ACC & VALUE
PACC <- ACC

Example:

ldax POT0 ; load POT0 into ACC
and 0x700000 ; mask pot to 8 steps
and 0 ; clear ACC

### clr

Perform a bitwise AND of ACC with zero - clearing ACC

Assembly: 0b01110

Action:

ACC <- 0
PACC <- ACC

Example:

clr ; clear ACC
rda 1234,1.0 ; load delay[1234]

### or VALUE

Perform a bitwise OR of ACC and VALUE

VALUE: Real S_23 or Unsigned 24bit integer
Assembly: VALUE<<8 | 0b01111

Action:

ACC <- ACC | VALUE
PACC <- ACC

Example:

clr ; clear ACC
or -2.3427e-4 ; load an immediate value into ACC
or 0x0a40f1 ; set specific bits in ACC

### xor VALUE

Perform a bitwise XOR of ACC and VALUE

VALUE: Real S_23 or Unsigned 24bit integer
Assembly: VALUE<<8 | 0b10000

Action:

ACC <- ACC ^ VALUE
PACC <- ACC

Example:

ldax POT0 ; load POT0
and 0x7f0000 ; mask off lower bits
xor 0x150000 ; compare with 0x150000
skp ZRO,equal ; if same, skip to equal
xor 0x150000 ; else restore original value

### not

Perform a bitwise negation of ACC by XOR with 0xffffff.

Assembly: 0xffffff<<8 | 0b10000

Action:

ACC <- ~ACC
PACC <- ACC

Example:

ldax POT0 ; load POT0
not ; invert all bits

### skp CONDITIONS, OFFSET

Skip over OFFSET instructions if all flagged CONDITIONS are met.

CONDITIONS: Unsigned 5bit flags
OFFSET: Unsigned 6bit integer or target label
Assembly: CONDITIONS<<27 | OFFSET<<21 | 0b10001

Condition Flags:

NEG 0x01 ACC is less than zero
GEZ 0x02 ACC is greater than or equal to zero
ZRO 0x04 ACC is zero
ZRC 0x08 sign of ACC and PACC differ
RUN 0x10 Program has completed at least one iteration

Notes:

- if no condition flags are set, the skip is always performed.

- if OFFSET starts with a label, it is assumed to be a jump target,
which should be present later in the program. An attempt to skip
backward will raise an error:

start: clr
skp 0,start ; try to skip backward

parse error: Target START does not follow SKP on line ...

- the maximum possible skip offset is 63, an error will be generated if
the named target is out of range:

skp 0,target
[>63 instructions]
target: clr

parse error: Offset from SKP to TARGET (0x40) too large on line ...

- To force computation of an offset, wrap an expression in parentheses:

EQU three 3
skp 0,three+3 ; error - three is not a target

parse error: Unexpected OPERATOR + on line ...
parse error: Undefined target THREE for SKP on line ...

skp 0,(three+3) ; ok, offset is evaluated as expression

- if mutually exclusive conditions are specified, the skip is
assembled but never performed, turning the instruction into NOP:

skp NEG|ZRO,target ; ACC cannot be negative AND zero

Example:

skp 0,target ; unconditionally skip to target
ldax ADCL ; read in ADCL
ldax REG0 ; load a previous value
skp ZRC|NEG,target ; skip to target on positive zero crossing
skp RUN,1 ; skip 1 instruction except on first run

### jmp OFFSET

Perform an unconditional skip over OFFSET instructions.

OFFSET: Unsigned 6bit integer or target label
Assembly: OFFSET<<21 | 0b10001

Notes:

- jmp is assembled as skp 0,OFFSET. See skp above for details on
specifying jmp targets.

Example:

jmp target ; unconditionally skip to target
jmp 3 ; always skip over 3 instructions

### nop

No operation, equivalent to skp 0,0. Use for padding, or blocking.

Assembly: 0b10001

Example:

nop nop nop nop ; reserve 4 instruction slots

### wlds LFO, FREQUENCY, AMPLITUDE

Adjust SIN LFO with coefficients FREQUENCY and AMPLITUDE.

LFO: 1bit integer (0 = SIN0 or 1 = SIN1)
FREQUENCY: Unsigned 9bit integer
AMPLITUDE: Real S_15 or Unsigned 15bit integer
Assembly: LFO<<29 | FREQUENCY<<20 | AMPLITUDE<<5 | 0b10010

Notes:

- FREQUENCY coefficient is related to LFO rate (f) in Hz
by the following:

FREQUENCY = int (2**18 * pi * f / Fs)
f = FREQUENCY * Fs / (2**18 * pi)

Where Fs is the sample rate. For a 32768Hz crystal, the SIN
LFO ranges from 0Hz up to about 20Hz.

- AMPLITUDE coefficient specifies the peak-to-peak amplitude
of the LFO in delay samples, and may be entered using a real
value. Negative amplitudes work as with SINx_RANGE register.

- The frequency and amplitude of SIN LFOs can also be set
by writing to registers: SIN0_RATE, SIN0_RANGE, SIN1_RATE,
and SIN1_RANGE.

Example:

wlds SIN0,511,1 ; Set SIN0 to 20Hz, amplitude 1 sample
wlds SIN1,1,0x7fff ; Set SIN1 to 0.04Hz and full delay length
or 0.5
wrax SIN0_RATE,0.0 ; Set SIN0 to ~10Hz
ldax POT0
wrax SIN0_RANGE,0.0 ; Set SIN0 range from POT0

### wldr LFO, FREQUENCY, AMPLITUDE

Adjust RMP LFO with coefficients FREQUENCY and AMPLITUDE.

LFO: 1bit integer (0 = RMP0 or 1 = RMP1)
FREQUENCY: Real S_15 or Signed 16bit integer
AMPLITUDE: 2bit integer (0=4096, 1=2048, 2=1024, 3=512)
Assembly: (LFO|0x2)<<29 | FREQUENCY<<13 | AMPLITUDE<<5 | 0b10010

Notes:

- LFO may also be set using pre-defined labels RMP0 and RMP1.

- AMPLITUDE may also be set by entering one of the specific
integer values: 4096, 2048, 1024 or 512.

- FREQUENCY may be entered using a real value, which has the same
interpretation as the RMPx_RATE register.

- The frequency and amplitude of RMP LFOs can also be set
by writing to registers: RMP0_RATE, RMP0_RANGE, RMP1_RATE,
and RMP1_RANGE.

Example:

wldr RMP0,32767,0 ; Set RMP0 to max rate, 4096 amplitude
wldr RMP1,-1923,512 ; Set RMP1 to 512 and a negative frequency

### jam LFO

Reset specified ramp LFO to start.

LFO: 1bit integer (0 = RMP0 or 1 = RMP1)
Assembly: (LFO|0x2)<<6 | 0b10011

Note:

- LFO may also be set using pre-defined labels RMP0 and RMP1.

Example:

jam RMP0 ; reset RMP0 lfo

### cho rda, LFO, FLAGS, ADDRESS

Read from delay memory at ADDRESS + offset (LFO) according to
FLAGS, multiply the result by coeff (LFO) and accumulate.

LFO: 2bit integer (SIN0, SIN1, RMP0 or RMP1)
FLAGS: 6bit integer flags
ADDRESS: Real S_15 or Unsigned 16bit integer
Assembly: FLAGS<<24 | LFO<<21 | ADDRESS<<5 | 0x10100

Action:

ACC <- ACC + coeff (LFO) * delay[ADDRESS + offset (LFO)]
PACC <- ACC

Flags:

COS 0x01 use cosine output of SIN LFO
REG 0x02 'register' LFO state (see note below)
COMPC 0x04 complement coefficient: 1 - coeff (LFO)
COMPA 0x08 complement address offset: 1 - offset (LFO)
RPTR2 0x10 use second, half-off ramp
NA 0x20 offset (LFO) = 0.0, coeff (LFO) = crossfade coefficient

Notes:

- offset (LFO) is the coarse LFO delay offset, based on flag settings
to a whole sample

- coeff (LFO) is an interpolation coefficient, based on flag settings
and the LFO fine position between whole samples

- the first use of cho in a program with any LFO must include the
REG flag in order to 'register' the LFO state and get valid data

- flags that are not relevant for the chosen LFO or cho mode are ignored

Example:

cho rda,SIN0,REG|COMPC,20 ; load first half of interpolation
cho rda,SIN0,0,21 ; add second half of interpolation

### cho sof, LFO, FLAGS, OFFSET

Multiply ACC by coeff (LFO), and add OFFSET.

LFO: 2bit integer (SIN0, SIN1, RMP0 or RMP1)
FLAGS: 6bit integer flags (see cho rda)
OFFSET: Real S_15 or Unsigned 16bit integer
Assembly: 0x2<<30 | FLAGS<<24 | LFO<<21 | OFFSET<<5 | 0x10100

Action:

ACC <- coeff (LFO) * ACC + OFFSET
PACC <- ACC

Example:

ldax ADCL ; load AD
cho sof,RMP0,REG|COMPC|NA,0 ; tremolo - scale ACC by RMP0 xfade

### cho rdal, LFO [, FLAGS]

Read the specified LFO address offset value into ACC
according to optional FLAGS. If FLAGS are omitted, a default
value of REG (0x2) is assembled.

LFO: 2bit integer (SIN0, SIN1, RMP0 or RMP1)
FLAGS: 6bit integer flags (see cho rda and notes)
Assembly: 0x3<<30 | FLAGS<<24 | LFO<<21 | 0x10100

Action:

ACC <- ACC + offset (LFO)
PACC <- ACC

Notes:

- REG flag must be set in order to get a meaningful value in ACC
value (this is the default if FLAGS are omitted)

- Only COS flag has any affect on the instruction, and only when used with
SIN LFO.

Example:

cho rdal,SIN0,REG ; load the SIN value and 'register' LFO
wrax DACL,0.0 ; output to left channel
cho rdal,SIN0,COS ; load the COS value
wrax DACR,0.0 ; output to right channel

### raw U32

Copy the unsigned 32 bit value in U32 directly to the output program.

U32: Unsigned 32bit integer
Assembly: U32

Example:

raw 0x4000000f ; manually assemble "or 0.5"
skp 0,1 ; skip over the next instruction
raw 0xa899fbda ; place a signature in the binary

## System Specific Installation

The preferred method for installation is to use your system's
packaged pip3 command to fetch and install asfv1 from
[PyPi](https://pypi.org/) and set it up to work with a python3
interpreter.

### Linux with apt (Debian, Ubuntu)

$ sudo apt install python3-venv python3-pip
$ pip3 install asfv1

### Linux with yum (Fedora 21)

$ sudo yum install python3 python3-wheel
$ pip3 install asfv1

### Linux with dnf (Fedora 22)

$ sudo dnf install python3 python3-wheel
$ pip3 install asfv1

### Arch Linux

$ sudo pacman -S python-pip
$ pip install asfv1

### MacOS

Download a copy of the "Latest Python 3 Release"
for Mac OS from
[python.org](https://www.python.org/downloads/mac-osx/).
Install the package, then open a terminal and run:

$ pip3 install asfv1

### Windows

Download a copy of the "Latest Python 3 Release"
for Windows from
[python.org](https://www.python.org/downloads/windows/).
Install the package, then open a command prompt and run:

C:\> pip3 install asfv1

For more detailed information, please refer to the
[Python package installation documentation](https://packaging.python.org/tutorials/installing-packages/)
and
[installing pip with packaging managers](https://packaging.python.org/guides/installing-using-linux-tools/#installing-pip-setuptools-wheel-with-linux-package-managers)
at
[packaging.python.org](https://packaging.python.org/).

### Install from Source

If you would prefer to not use pip, or if your system is provided with
and older version of Python (eg MacOS), asfv1 can be installed using
the included setup.py script. Fetch a copy of the latest source package,
unpack it and then run the installer as root:

$ sudo python ./setup.py install

Alternatively, the main source file can be run directly with a python
interpreter without the need to install any files:

$ python ./asfv1.py infile.asm outfile.bin

## Links

- FV-1 disassembler:
- FV-1 test suite:
- Dervish eurorack FV-1 module:
- Spin FV-1 website:
- Datasheet:
- AN0001: