1. Introduction

This document provides a detailed description of F Prime Prime, also known as FPP or F Double Prime. FPP is a modeling language for the F Prime flight software framework. A paper presented at SmallSat 2018 provides an overview of F Prime.

The goals of FPP are as follows:

  • To provide a modeling language for F Prime that is simple, easy to use, and well-tailored to the domain.

  • To provide semantic checking and error reporting for F Prime models.

  • To generate code in the various formats that F Prime uses, e.g., C++, Python, XML.

The purpose of this document is to describe FPP with enough precision and detail to guide an implementation. It is primarily aimed at language implementers, although FPP users may also benefit from the level of precision and detail that it provides. It is recommended that FPP users read The FPP User’s Guide before consulting this document.

2. Syntax Notation

Throughout this document, we use the following notation to describe language syntax:

  • fixed-width font denotes literal model text.

  • italics denote grammar nonterminals.

  • Italicized brackets [ ] enclose optional elements.

3. Lexical Elements

Before parsing an FPP model, the compiler converts the source text into a list of tokens. This process is called lexing.

A token is one of the following:

3.1. Reserved Words

The following are reserved words in FPP. They may not be used as identifiers, except when preceded by the $ character, as discussed below.

F32
F64
I16
I32
I64
I8
U16
U32
U64
U8
active
activity
always
array
assert
async
at
base
block
bool
change
command
component
connections
constant
container
cpu
default
diagnostic
drop
enum
event
false
fatal
format
get
guarded
health
high
id
import
include
input
instance
internal
locate
low
match
module
on
opcode
orange
output
param
passive
phase
port
priority
private
product
queue
queued
record
recv
red
ref
reg
request
resp
save
send
serial
set
severity
size
stack
string
struct
sync
telemetry
text
throttle
time
topology
true
type
update
warning
with
yellow

3.2. Symbols

The following sequences of characters are symbol tokens in FPP:

(
)
*
+
,
-
->
.
/
:
;
=
[
]
{
}

3.3. Identifiers

Definition: An identifier is an unqualified name consisting of one or more characters. The first character must be a letter or an underscore. Characters after the first character may be letters, digits, and underscores.

For example:

  • identifier, identifier3, and __identifier3 are valid identifiers.

  • 3identifier is not a valid identifier, because it starts with a digit.

  • identifier% is not a valid identifier, because it contains the character %.

Escaped keywords: Any identifier may be preceded by the character $, with no intervening space. An identifier $ I has the same meaning as I, except that if I is a reserved word, then I is scanned as an identifier and not as a reserved word.

For example:

  • $identifier is a valid identifier. It has the same meaning as identifier.

  • $time is a valid identifier. It represents the character sequence time treated as an identifier and not as a reserved word.

  • time is a reserved word. It may not be used as an identifier.

3.4. End-of-Line Tokens

An end-of-line token is a sequence of one or more newlines. A newline (or line break) is the NL character (ASCII code 0x0A), optionally preceded by a CR character (ASCII code 0x0D). End-of-line tokens separate the elements of element sequences.

3.5. Comments

The lexer ignores comments. Specifically:

  • A comment followed by a newline is treated as a newline.

  • A comment at the end of a file, not followed by a newline, is treated as no text.

3.6. Whitespace and Non-Printable Characters

Apart from end-of-line tokens, the lexer treats whitespace as follows:

  • Space characters are ignored, except to separate tokens.

  • No other whitespace or non-printable characters are allowed outside of a string, comment, or annotation. In particular, the tab character may not appear in an FPP model outside of a string, comment, or annotation.

3.7. Explicit Line Continuations

The character \, when appearing before a newline, suppresses the newline. Both the \ and the following newline are ignored, and no end-of-line token, is created. For example, this

constant a \
  = 1

is equivalent to this:

constant a = 1

Note that \ is required in this case. For example, the following is not syntactically correct:

constant a # Error
  = 1

The newline indicates the end of an element sequence, but constant a is not a valid element sequence.

3.8. Automatic Suppression of Newlines

The following symbols consume sequences of newlines that follow them, without creating an end-of-line token:

(
*
+
,
-
->
/
:
;
=
[
{

For example, the following code is legal:

module M {
  constant a = 0
}

It is equivalent to this code:

module M { constant a = 0 }

The newline after the { symbol is consumed by the symbol. The newline after the constant definition is consumed by the element sequence member.

The following code is also legal, because the newline is consumed by the = symbol:

constant a =
  0

Similarly, the following code is legal, because the newline is consumed by the + symbol:

constant a = 1 +
  2

3.9. Collapsing of Newlines

To simplify parsing, the lexer may collapse a sequence of one or more end-of-line tokens into a single token, or into no token, if the operation does not change the meaning of the result according to the parse rules. For example, the lexer may treat this code

constant a = [
  1
  2
  3

]

as if it were this

constant a = [
  1
  2
  3
]

or this

constant a = [
  1
  2
  3 ]

According to the rules for parsing element sequences, all three code sections are equivalent.

4. Element Sequences

An element sequence is a sequence of similar elements, e.g., the elements of a translation unit.

Each element of an element sequence has optional terminating punctuation. The punctuation is either a comma or a semicolon, depending on the kind of sequence.

For each element e in the sequence:

  • You can always terminate e with a sequence of one or more newlines. In this case no terminating punctuation is required.

  • You can omit the newline. In this case the terminating punctuation is required, unless the element is last in the sequence or the element is followed by a post-annotation.

Examples:

A translation unit is a kind of element sequence. Here are some examples of element sequences using constant definitions as the elements of a translation unit:

# No terminating punctuation
constant a = 0
constant b = 1
# Optional terminating punctuation present
constant a = 0;
constant b = 1;
# Terminating punctuation required after the first element but not the second
constant a = 0; constant b = 1
# Error, because terminating punctuation is missing
constant a = 0 constant b = 1
# No terminating punctuation required because of the post-annotation
constant a = 0 @< This is OK
constant b = 1

An enum constant sequence is another example of an element sequence. Here are some element sequences using enumerated constant definitions as elements of an enum constant sequence:

# No terminating punctuation
enum E {
  X = 0
  Y = 1
}
# Optional terminating punctuation
enum E {
  X = 0,
  Y = 1,
}
# Terminating punctuation required after the first element but not the second
enum E { X = 0, Y = 1 }

5. Definitions

A definition is a unit of syntax that introduces a name N and associates some code with N.

5.1. Abstract Type Definitions

An abstract type definition associates a name with a type without specifying the type. In generated code, the type is specified externally (e.g., as a C++ class).

5.1.1. Syntax

type identifier

5.1.2. Semantics

The identifier is the name N of the type. The definition associates the name N with an unspecified type T'. The name N can be used as a type elsewhere in the model. There are no explicit values associated with N in the model.

5.1.3. Examples

# Defines an abstract type A
type A
# Defines a struct type B whose member x has type A
struct B {
  x: A
  y: F32
} default { y = 1 }

5.2. Array Definitions

An array definition defines a new array type and associates a name with it.

5.2.1. Syntax

array identifier = [ expression ] type-name [ default expression ] [ format string-literal ]

5.2.2. Semantics

The identifier is the name N of the new type. The first expression must evaluate to a compile-time constant numeric value n whose value is greater than zero and less than or equal to 256. type-name names the type T of each array element.

The definition associates the name N with a new type T' that represents an array of n elements of type T.

The expression following the keyword default is optional. If present, it specifies the default value associated with the type. The type of the expression must be convertible to T'.

The optional format specifier specifies a format string. When displaying the array, the format is applied to each element of the array. There is one argument to the format string, which is an array member.

5.2.3. Examples

# Defines an array type A of 3 U8 elements with default value [ 0, 0, 0 ]
array A = [3] U8

# Defines an array type B of 2 A elements with default value
# [ [ 0, 0, 0 ], [ 0, 0, 0 ] ]
array B = [3] A

# Defines an array type C of 3 F32 elements with default value [ 1, 2, 3 ]
array C = [3] F32 default [ 1, 2, 3 ]

# Defines an array type C of 3 U32 values with default value
# [ 1, 1, 1 ] after promoting 1 to [ 1, 1, 1 ]
array D = [3] U32 default 1

# Defines an array type E of 3 U32 values with default value
# [ 1, 1, 1, ] and element format {.03f}
array E = [3] U32 default 1 format "{.03f}"

5.3. Component Definitions

A component definition defines an F Prime component. A component is a unit of function in the F Prime framework. Component instances communicate with each other across ports.

5.3.1. Syntax

component-kind component identifier { component-member-sequence }

component-kind is one of the following:

  • active

  • passive

  • queued

component-member-sequence is an element sequence in which each element is a component member, and the terminating punctuation is a semicolon. A component member is one of the following:

The command, event, parameter, telemetry, record, and container specifiers are collectively referred to as dictionary specifiers. The record and container specifiers are collectively referred to as data product specifiers.

5.3.2. Semantics

The identifier is the name of the component. The definitions inside the body of the component are qualified with the component name, as for module definitions.

The component members must satisfy the following rules:

  1. All names appearing in any general port specifiers, special port specifiers, and internal port specifiers must be distinct.

  2. No passive component may have an async general or special port specifier, an internal port specifier, or an async command specifier.

  3. Each active or queued component must have at least one async general or special port specifier, internal port specifier, or async command specifier.

  4. Each component may have at most one of each special port kind.

  5. Each component must provide ports as shown in the table below.

Condition Requirement

If there are any command specifiers

There must be specifiers for ports command recv, command reg, and command resp.

If there are any event specifiers

There must be specifiers for ports event, text event, and time get.

If there are any parameter specifiers

There must be specifiers for ports command recv, command reg, command resp, param get, and param set.

If there are any telemetry specifiers

There must be specifiers for ports telemetry and time get.

If there are any data product specifiers

There must be a specifier for either port product get or port product request. There must be specifiers for ports product send and time get.

If there is a specifier for product request

There must be a specifier for port product recv.

The dictionary specifiers must satisfy the following rules:

  1. All commands must have distinct names and opcodes.

  2. All events must have distinct names and identifier values.

  3. All parameters must have distinct names and identifier values.

  4. All telemetry channels must have distinct names and identifier values.

  5. All records must have distinct names and identifier values.

  6. All containers must have distinct names and identifier values.

  7. If there are any container specifiers, then there must be at least one record specifier, and vice versa.

5.3.3. Examples

@ Receives commands from the ground or from a sequencer
@ Dispatches commands to other components
active component CommandDispatcher {

  # ----------------------------------------------------------------------
  # Constants
  # ----------------------------------------------------------------------

  @ The number of com command input ports
  constant numComCmdInPorts = 10

  @ The number of command output ports
  constant numCmdOutPorts = 20

  # ----------------------------------------------------------------------
  # General ports
  # ----------------------------------------------------------------------

  @ Dispatches commands
  output port cmdOut: [numCmdOutPorts] Fw.Cmd

  @ Forwards received command responses
  output port cmdResponseInOut: [numComCmdInPorts] Fw.CmdResponse

  @ Receives com packets containing serialized commands
  async input port comCmdIn: [numComCmdInPorts] Fw.Com

  @ Receives command registration requests
  guarded input port cmdRegIn: [numCmdOutPorts] Fw.CmdReg

  @ Receives command responses
  async input port cmdResponseIn: Fw.CmdResponse

  # ----------------------------------------------------------------------
  # Special ports
  # ----------------------------------------------------------------------

  @ Command receive port
  command recv port cmdIn

  @ Command registration port
  command reg port cmdRegOut

  @ Command response port
  command resp cmdResponseOut

  @ Event port
  event port eventOut

  @ Telemetry port
  telemetry port tlmOut

  @ Text event port
  text event port textEventOut

  @ Time get port
  time get port timeGetOut

  @ Product get port
  product get port productGetOut

  @ Product request port
  product request port productRequestOut

  @ Product receive port
  product recv port productRecvIn

  @ Product send port
  product send port productSendOut

  # ----------------------------------------------------------------------
  # Commands
  # ----------------------------------------------------------------------

  @ No-op command
  async command NO_OP

  @ No-op string command
  async command NO_OP_STRING(
      arg1: string size 256 @< The string command argument
  )

  ...

  # ----------------------------------------------------------------------
  # Events
  # ----------------------------------------------------------------------

  @ Opcode registered event
  event OpcodeRegistered(
      regOpcode: Opcode @< The opcode to register
      regPort: U32 @< The registration port
      dispatchSlot: U32 @< The dispatch slot
  ) severity diagnostic \
    format "Opcode {} registered to port {} slot {}"

  @ Opcode dispatched event
  event OpcodeDispatched(
      dispatchOpcode: Opcode @< The opcode dispatched
      dispatchPort: U32 @< The dispatch port
  ) severity command \
    format "Opcode {} dispatched to port {}"

  ...

  # ----------------------------------------------------------------------
  # Telemetry
  # ----------------------------------------------------------------------

  @ Number of commands dispatched
  telemetry CommandsDispatched: U32 update on change

  @ Number of command errors
  telemetry CommandErrors: U32 update on change

  # ----------------------------------------------------------------------
  # Data products
  # ----------------------------------------------------------------------

  @ A container for images
  product container Images

  @ A record for holding an image
  product record ImageRecord: Image

}

5.4. Component Instance Definitions

A component instance definition defines an instance of a component that you can refer to in a topology definition.

5.4.1. Syntax

instance identifier : qual-ident base id expression [ type string-literal ] [ at string-literal ] [ queue size expression ] [ stack size expression ] [ priority expression ] [ cpu expression ] [ { init-specifier-sequence } ]

init-specifier-sequence is an element sequence in which each element is an init specifier, and the terminating punctuation is a semicolon.

5.4.2. Semantics

  1. The identifier names the component instance.

  2. The qualified identifier must refer to a component definition D. This component definition D is called the component definition associated with the component instance I. We also say that I is an instance of D.

  3. The expression following the keywords base id must have a numeric type. It associates a base identifier with the component instance.

    1. The base identifier must evaluate to a nonnegative integer after type conversion.

    2. For each component instance, for each command, event, telemetry, or parameter identifier, the identifier associated with the instance is computed by adding the base identifier specified here to the relative identifier specified in the component. For this purpose, command opcodes are identifiers.

    3. For each instance, this procedure creates a range of identifiers, from the base identifier to the largest identifier associated with the instance. If the component has no identifiers, then this range is empty.

    4. No instance may have a base identifier that lies within the identifier range of another instance.

  4. If present, the string literal following the keyword type names the implementation type of the instance. The type must be a valid type in the target language (e.g., a type name in C++). If the implementation type is not present, then the code generator infers the name of the implementation type from the component name when generating the constructor for the instance. For example:

    1. Suppose an FPP model has a component C defined in module M. Suppose I is an instance of component M.C. By default, the implementation type associated with I is M::C.

    2. Specifying type "M::D" causes FPP to generate a constructor for I with name "M::D" instead of "M::C".

    Specifying an implementation type is useful in cases where the name of the component implementation does not match the component name, e.g., because there are several implementations of the same FPP component.

  5. If present, the string literal following the keyword at must specify a file path, relative to the location of the component instance definition. The file path must name a file in the target language (e.g., a C++ header file) that provides the implementation associated with the instance. If no such path is given, then the translator uses the location of the component instance and the name of the component to generate a default implementation path.

  6. If present, the expression following the keywords queue size must have a numeric type and must evaluate to a nonnegative integer after type conversion. It specifies the queue size for active and queued components. The queue size is required for active and queued components and is not allowed for passive components.

  7. If present, the expression following the keywords stack size must have a numeric type and must evaluate to a nonnegative integer after type conversion. It specifies the stack size in bytes for active components. The stack size is optional for active components and is not allowed for queued or passive components.

  8. If present, the expression following the keyword priority must have a numeric type. It specifies the thread priority for active components. The priority is optional for active components and is not allowed for queued or passive components.

  9. If present, the expression following the keyword cpu must have a numeric type. It specifies the CPU affinity for active components. The CPU affinity is optional for active components and is not allowed for queued or passive components.

  10. If present, the init specifiers govern C++ code generation for the component instance being defined. See the section on init specifiers for further information.

5.4.3. Examples

instance commandDispatcher: Svc.CommandDispatcher \
  base id 0x100 \
  queue size 10 \
  stack size 4096 \
  priority 30 \
  cpu 0

This example defines an instance commandDispatcher of component Svc.CommandDispatcher. It specifies the base identifier, queue size, stack size, priority, and CPU.

instance timer: Svc.Timer at "../../Timers/HardwareTimer.hpp"

This example defines an instance timer of component Svc.Timer. It specifies that the component implementation is located at path ../../Timers/HardwareTimer.hpp instead of in the default location for the component. The path is resolved relative to the location of the instance definition.

5.5. Constant Definitions

A constant definition associates a name with a compile-time constant value. You can use the name in place of the value elsewhere in the model.

5.5.1. Syntax

constant identifier = expression

5.5.2. Semantics

expression must evaluate to a compile-time constant value v. At any model point where the qualified identifier Q refers to the constant definition according to the scoping rules for names, you can use Q as a name for v.

5.5.3. Examples

constant a = 0 # a has value 0
constant b = 1.0 # b has value 1.0
constant c = a # c has value 0

5.6. Enum Definitions

An enum definition does two things:

  1. It defines a type \$T\$ and associates a name \$N\$ with \$T\$. Elsewhere in the model, you can use \$N\$ to refer to \$T\$.

  2. It associates several named constants \$C_1, ..., C_n\$ with \$T\$. These constants, called the enumerated constants of \$T\$, are the values that an expression of type \$T\$ may attain. Elsewhere in the model, you can use the qualified identifiers \$N\$ . \$C_1, ..., N\$ . \$C_n\$ to refer to the enumerated constants.

5.6.1. Syntax

enum identifier [ : type-name ] { enum-constant-sequence } [ default expression ]

enum-constant-sequence is an element sequence in which the elements are enumerated constant definitions, and the terminating punctuation is a comma.

5.6.2. Semantics

The enumerated constants have the enum type defined in the enum definition. During analysis, they are represented as values of a primitive integer type, called the representation type.

There must be at least one enumerated constant. No two enumerated constants may have the same identifier. Each enumerated constant must evaluate to a compile-time constant whose type is convertible to the representation type. No two enumerated constants may have the same value after the conversion.

The expression following the keyword default is optional. If present, it specifies the default value associated with the enum definition. The type of the expression must be the type of the enum definition.

5.6.3. Inferred Representation Type

If type-name does not appear after the identifier, then the implied representation type is I32.

5.6.4. Explicit Representation Type

If type-name appears after the identifier, then the semantic analyzer does the following:

  1. Check that type-name names a primitive integer type. If not, throw an error.

  2. Use \$T\$ as the representation type.

5.6.5. Examples

The following example shows two definitions. In the first one, the implied representation type is I32. In the second one, the representation type is explicitly given as U8.

enum Gunfighters {
  IL_BUONO
  IL_BRUTTO
  IL_CATTIVO
}

enum U8Gunfighters: U8 {
  IL_BUONO
  IL_BRUTTO
  IL_CATTIVO
}

The next example shows an enum definition with an explicit default value.

enum Status {
  YES
  NO
  MAYBE
} default MAYBE

5.7. Enumerated Constant Definitions

An enumerated constant definition is an element of an enum definition. Like a constant definition, it associates a value with a named constant. It also establishes that the constant is one of the values of the type defined in the enum definition.

5.7.1. Syntax

5.7.2. Semantics

If present, expression must evaluate to a compile-time constant value v. At any model point where the qualified identifier Q refers to the enumerated constant definition according to the scoping rules for names, you can use Q as a name for the value that results from converting v to the type of the enclosing enum definition.

The expression must be present or absent for all enumerated constant definitions appearing in an enum definitions. If there are no expressions, then the enumerated constant definitions are assigned increasing values starting with zero, as for enumerations in C.

Note that the type of an enumerated constant value is the enum type defined by the enclosing enum definition. This may be converted to the representation type of the enum. However, the reverse conversion is not allowed: you can convert an enum type to a U32 (for example), but not a U32 to an enum type. Nor can you convert one enum type to a different one.

5.7.3. Example

The examples given for enum definitions include enumerated constant definitions.

5.8. Module Definitions

A module definition provides a named scope that encloses and qualifies other definitions, including other module definitions. It is similar to a namespace in C++ or a package in Java or Scala.

5.8.1. Syntax

module identifier { module-member-sequence }

module-member-sequence is an element sequence in which each element is a module member, and the terminating punctuation is a semicolon. A module member is one of the following:

5.8.2. Semantics

A module definition D qualifies the names of all the definitions inside it with its own name. Inside D, you can refer to definitions in D by their unqualified name (the identifier appearing in the definition) or by their qualified name. Outside D, you have to use the qualified name. We say that the scope of the identifiers in the definitions in D is limited to the inside of D.

For further information about name scoping and qualification, see the section on Scoping of Names.

5.8.3. Example

module M {
  constant a = 0
  constant b = a # Inside M, we can refer to M.a as a
  constant c = M.a # We can also say M.a here
}
constant d = M.a # Outside M, we have to say M.a
constant e = a # Error: a is not in scope here

5.9. Port Definitions

An port definition defines an F Prime port. A port is the endpoint of a connection between F Prime components.

5.9.1. Syntax

port identifier [ ( param-list ) ] [ -> type-name ]

5.9.2. Semantics

  • The identifier is the name of the port.

  • The optional parameter list specifies the formal parameters of the port. Each formal parameter is a piece of data carried on the port. If there are no formal parameters, then the parameter list may be omitted.

  • If the annotation ref appears in a formal parameter P, then P is passed by reference in a synchronous port invocation and by value in an asynchronous port invocation.

  • The optional type name appearing after the arrow specifies an optional return type for the port. If present, the return type is the type of the data returned when the port is invoked in a synchronous context.

5.9.3. Examples

@ Port 1
port Port1(
    a: U32 @< Parameter a
    b: F64 @< Parameter b
)

@ Port 2
port Port2(
    ref a: Fw.Com @< Ref parameter a
)

@ Port 3
port Port3(
    a: U32 @< Parameter a
) -> U32

5.10. Struct Definitions

A struct definition defines a new structure type and associates a name with it.

5.10.1. Syntax

struct identifier { struct-type-member-sequence } [ default expression ]

struct-type-member-sequence is an element sequence in which the elements are struct type members, and the terminating punctuation is a comma. A struct type member has the following syntax:

5.10.2. Semantics

The identifier is the name N of the type. The definition associates the name N with a struct type T representing a structure with named members, each of the specified type. Each identifier appearing in the struct type member sequence must be distinct.

The optional expression [ e ] for a struct member specifies the size of the member, i.e., the number of elements stored in the member. If present, e must have a numeric type, and it must evaluate to a value greater than zero after conversion to Integer. If e is not present, then default size is one. If a member m has size greater than one, then m is translated to an array in the implementation language.

The optional format specifier for a struct member specifies a format string. The format is applied when displaying the struct member. There is one argument to the format string, which is the member value. If the size of the member is greater than one, then the translator applies the format to each element.

The expression following the keyword default is optional. If present, it specifies the default value associated with the type. The type of the expression must be convertible to T. If the expression specifies a value for a member with size greater than one, then the value is applied to each element.

5.10.3. Examples

# Defines a struct type A with members x and y
# and default value { x = 0, y = 0 }
struct A {
  x: U32
  y: F32
}

# Defines a struct type B with members x and y
# and default value { x = 0, y = 1 }
struct B {
  x: U32
  y: F32
} default { y = 1 }

# Defines a struct type C with format specifiers
struct C {
  x: U32 format "{} counts"
  y: F32 format "{} m/s"
}

# Defines a struct type D with member x
# After translation, x is an array of 3 U32 elements
# The associated FPP struct type is { x: U32 }
# The default value of D stores 1 into each of the 3 elements of x
struct D {
  x: [3] U32
} default { x = 1 }

5.11. Topology Definitions

A topology definition defines an F Prime topology, that is, a set of component instances and the connections between their ports. The connections are divided into named connection graphs. The different graphs capture different aspects of FSW function, such as commanding and telemetry.

One topology can be imported into another one. The connections are merged graph by graph.

5.11.1. Syntax

topology identifier { topology-member-sequence }

topology-member-sequence is an element sequence in which each element is a topology member, and the terminating punctuation is a semicolon. A topology member is one of the following:

5.11.2. Semantics

A topology definition D must be resolvable to a topology T, according to the following algorithm:

Resolving to a Partially Numbered Topology

A partially numbered topology is a topology in which port number assignments may or may not exist for each connection. A port number assignment exists if it is specified in the model source.

  1. For each endpoint E (output or input) in each connection C appearing in D, if E has a port number expression e in the FPP source, then evaluate e, convert the result to a value n of type Integer, and assign the port number n at that position. Check that n is in bounds for the port instance being numbered.

  2. Recursively resolve all the topologies directly imported into D.

  3. Construct the connection graphs of T given by the direct connection graphs appearing in D, after merging each set of connection graphs with the same name into a single graph. For example, the two connection graphs

    connections C { a.b -> c.d }

    and

    connections C { e.f -> g.h }

    are treated as the single graph

    connections C { a.b -> c.d, e.f -> g.h }
  4. Compute the set S of topologies transitively imported into T.

  5. For each topology T' in S, import the instances of T' into T.

  6. Check that the connections discovered in step 3 are between port instances present in T.

  7. For each topology T' in S, import the connections of T' into T.

  8. Resolve graph pattern specifiers, adding connections to T. Add only connections that are not already present in T. For example, if a command pattern indicates a command registration connection between two ports, and there is already a command registration connection between those ports, then do not add the connection.

Automatic Numbering of Ports

FPP automatically assigns port numbers as follows.

Check output port numbers: At each output port p of each component instance I, check that

  1. The number of connections is in bounds for the size of I . p.

  2. There is no pair of connections \$c_1\$ and \$c_2\$ at the same port number of I . p. For example, the following pair of connections is not allowed:

    a.p[0] -> b.p
    a.p[0] -> c.p

Apply matched numbering: Assign matching numbers to matched pairs of ports. For each instance I in the topology:

  1. Let C be the component type of I.

  2. For each port matching specifier match \$p_1\$ with \$p_2\$ appearing in the definition of C:

    1. For each connection \$c_1\$ with an endpoint at I . \$p_1\$:

      1. Let I' be the component instance at the other endpoint of \$c_1\$.

      2. Check that there is one and only one connection \$c_2\$ from I' to I . \$p_2\$.

    2. Check that the connections \$c_2\$ computed in the previous step are all the connections at I . \$p_2\$.

    3. For each pair \$(c_1,c_2)\$ that has both port numbers assigned, check that the port numbers match. For each pair \$(c_1,c_2)\$ that has exactly one port number assigned, assign the other one to match.

    4. Traverse the pairs \$(c_1,c_2)\$ according to the order of the connections \$c_1\$, least to greatest. For each pair \$(c_1,c_2)\$ that does not yet have assigned port numbers, find the lowest available port number and assign it at I . \$p_1\$ and I . \$p_2\$. A port number is available if (a) it is in bounds for the port instance being numbered; and (b) it is not already assigned to the same port instance in the topology.

Note that in the last step, the two ports I . \$p_1\$ and I . \$p_2\$. have the same array size and the same port numbers assigned so far, so the lowest available port number is the same for both.

Apply general numbering: Fill in any remaining port numbers.

  1. Traverse the connections in order, least to greatest.

  2. For each output endpoint P in each connection C, if no port number is already assigned, then assign the lowest available port number at position P.

  3. For each input endpoint P in each connection C, if no port number is already assigned, then assign the port number zero.

See Example 4 in the Examples section.

Ordering of Connections

For purposes of port numbering, FPP orders connections as follows.

Connection endpoints: A connection endpoint is I . p or I . p [ n ], where

  • I refers to a component instance.

  • p is an identifier that names a port instance specified in the component definition associated with I.

  • n is an optional port number that is present if and only if it appears in the model source.

When a connection endpoint e has the form I . p [ n ], we say that the endpoint has source port number n.

Each connection endpoint has a fully qualified name. The fully qualified name is Q . p, where Q is the fully qualified name of the instance I.

FPP orders connection endpoints \$e_1\$ and \$e_2\$ as follows:

  1. If the fully qualified name of \$e_1\$ is lexically less than (respectively greater than) the fully qualified name of \$e_2\$, then \$e_1\$ is less than (respectively greater than) \$e_2\$.

  2. Otherwise if \$e_1\$ and \$e_2\$ have source port numbers port numbers \$n_1\$ and \$n_2\$, then the ordering of \$e_1\$ and \$e_2\$ is the same as the numerical ordering of \$n_1\$ and \$n_2\$.

  3. Otherwise \$e_1\$ and \$e_2\$ are equal in the ordering.

Connections: A connection is \$e_1\$ -> \$e_2\$, where \$e_1\$ and \$e_2\$ are the connection endpoints. FPP orders connections \$c_1\$ and \$c_2\$ as follows:

  1. Let connection \$c_1\$ be \$e_1\$ -> \$e'_1\$.

  2. Let connection \$c_2\$ be \$e_2\$ -> \$e'_2\$.

  3. If \$e_1\$ is less than (respectively greater than) \$e_2\$, then \$c_1\$ is less than (respectively greater than) \$c_2\$.

  4. Otherwise if \$e'_1\$ is less than (respectively greater than) \$e'_2\$, then \$c_1\$ is less than (respectively greater than) \$c_2\$.

  5. Otherwise \$c_1\$ and \$c_2\$ are equal in the ordering.

5.11.3. Examples

Example 1.

@ Command and data handling topology
topology CDH {

  # ----------------------------------------------------------------------
  # Public instances
  # ----------------------------------------------------------------------

  instance commandDispatcher
  instance commandSequencer
  instance engineeringRateGroup
  instance engineeringTelemetryLogger
  instance engineeringTelemetryConverter
  instance engineeringTelemetrySplitter
  instance eventLogger
  instance rateGroupDriver
  instance telemetryDatabase
  instance timeSource

  # ----------------------------------------------------------------------
  # Private instances
  # ----------------------------------------------------------------------

  private instance socketGroundInterface

  # ----------------------------------------------------------------------
  # Connection patterns
  # ----------------------------------------------------------------------

  command connections instance commandDispatcher
  event connections instance eventLogger
  time connections instance timeSource

  # ----------------------------------------------------------------------
  # Connection graphs
  # ----------------------------------------------------------------------

  connections CommandSequences {
    commandSequencer.comCmdOut -> commandDispatcher.comCmdIn
  }

  connections Downlink {
    eventLogger.comOut -> socketGroundInterface.comEventIn
    telemetryDatabase.comOut -> socketGroundInterface.comTlmIn
  }

  connections EngineeringTelemetry {
    commandDispatcher.tlmOut -> engineeringTelemetrySplitter.tlmIn
    commandSequencer.tlmOut -> telemetryDatabase.tlmIn
    engineeringRateGroup.tlmOut -> engineeringTelemetrySplitter.tlmIn
    engineeringTelmetryConverter.comTlmOut -> engineeringTelemetryLogger.comTlmIn
    engineeringTelemetrySplitter.tlmOut -> engineeringTelemetryConverter.tlmIn
    engineeringTelemetrySplitter.tlmOut -> telemetryDatabase.tlmIn
  }

  connections RateGroups {
    engineeringRateGroup.schedOut -> commandSequencer.schedIn
    engineeringRateGroup.schedOut -> telemetryDatabase.schedIn
    rateGroupDriver.cycleOut -> engineeringRateGroup.cycleIn
  }

  connections Uplink {
    socketGroundInterface.comCmdOut -> commandDispatcher.comCmdIn
  }

}

Example 2.

@ Attitude control topology
topology AttitudeControl {

  # ----------------------------------------------------------------------
  # Imported topologies
  # ----------------------------------------------------------------------

  import CDH

  # ----------------------------------------------------------------------
  # Public instances
  # ----------------------------------------------------------------------

  instance acsRateGroup
  instance attitudeControl
  ...

  # ----------------------------------------------------------------------
  # Private instances
  # ----------------------------------------------------------------------

  instance socketGroundInterface

  # ----------------------------------------------------------------------
  # Connection patterns
  # ----------------------------------------------------------------------

  command connections instance commandDispatcher
  event connections instance eventLogger
  time connections instance timeSource


  # ----------------------------------------------------------------------
  # Connection graphs
  # ----------------------------------------------------------------------

  connections AttitudeTelemetry {
    ...
  }

  connections Downlink {
    eventLogger.comOut -> socketGroundInterface.comEventIn
    telemetryDatabase.comOut -> socketGroundInterface.comTlmIn
  }

  connections EngineeringTelemetry {
    acsRateGroup.tlmOut -> engineeringTelemetrySplitter.tlmIn
    ...
  }

  connections RateGroups {
    acsRateGroup.schedOut -> attitudeControl.schedIn
  }

  connections Uplink {
    socketGroundInterface.comCmdOut -> commandDispatcher.comCmdIn
  }

}

Example 3.

@ Release topology
topology Release {

  # ----------------------------------------------------------------------
  # Imported topologies
  # ----------------------------------------------------------------------

  import AttitudeControl
  import CDH
  import Communication
  ...

}

Example 4.

Here is the topology that results from automatic numbering of ports applied to topology B in the example for topology import specifiers:

topology B {

  instance a
  instance c
  instance d
  instance e
  instance f

  connections C1 {
    a.p1[0] -> c.p[0]
    a.p1[1] -> d.p[0]
  }

  connections C2 {
    a.p2[0] -> e.p[0]
  }

  connections C3 {
    a.p3[0] -> f.p[0]
  }

}

6. Specifiers

A specifier is a unit of syntax that specifies some information about an FPP model. It may associate a local name with the specification. However, unlike a definition, it does not create a globally unique qualified name.

6.1. Command Specifiers

A command specifier specifies a command as part of a component definition.

6.1.1. Syntax

command-kind command identifier [ ( param-list ) ] [ opcode expression ] [ priority expression ] [ queue-full-behavior ]

command-kind is one of the following:

  • async

  • guarded

  • sync

queue-full-behavior is as for port instance specifiers.

6.1.2. Semantics

  • The command kind specifies the kind of the command. It is similar to the kind of a port instance specifier, except that different commands on the same port can have different kinds.

  • The identifier names the command.

  • The parameter list specifies the command parameters. If there are no parameters, the list may be omitted. ref may not appear in any of the parameters.

  • The optional expression e following opcode specifies the numeric opcode for the command. If e is present, its type must be convertible to Integer, and e must evaluate to a nonnegative integer. If e is not present, then the default opcode is either zero (for the first opcode appearing in a component) or the previous opcode plus one.

  • The optional expression e appearing after the keyword priority specifies a priority for the command on the input queue. The type of e must be convertible to Integer. The priority expression is valid only if the kind of the command is async.

  • The optional queue-full-behavior specifies the behavior of the command when the input full is queue. This specifier is valid only if the kind of the command is async. If no specifier appears, then the default behavior is assert.

6.1.3. Examples

@ A sync command with no parameters
sync command SyncNoParams opcode 0x00

@ An async command with no parameters
async command AsyncNoParams opcode 0x01

@ A sync command with parameters
sync command SyncParams(
    param1: U32 @< Param 1
    param2: string @< Param 2
) opcode 0x02

@ An async command with parameters
async command AsyncParams(
    param1: U32 @< Param 1
    param2: string @< Param 2
) opcode 0x03

@ An async command with priority
async command AsyncPriority(
    param1: U32 @< Param 1
    param2: string @< Param 2
) opcode 0x04 priority 10

@ An async command with priority and drop on queue full
async command AsyncPriorityDrop(
    param1: U32 @< Param 1
    param2: string @< Param 2
) opcode 0x05 priority 10 drop

6.2. Component Instance Specifiers

A component instance specifier specifies that a component instance is part of a topology.

6.2.1. Syntax

[ private ] instance qual-ident

6.2.2. Semantics

  • The qualified identifier must refer to a component instance.

  • The optional keyword private, if present, specifies a private instance of a topology. This means that the instance is private to the topology T in which the specifier appears. A private instance appears only in T; it does not appear in any topology T' into which T is imported.

  • If an instance is not declared private, then it is implicitly a public instance. This means the instance appears in each topology T' into which T is imported.

6.2.3. Example

component A { ... }
component B { ... }
component C { ... }

instance a: A base id 0x100 ...
instance b: B base id 0x200 ...
instance c: B base id 0x300 ...

Topology T {

  # ----------------------------------------------------------------------
  # Public instances
  # ----------------------------------------------------------------------

  instance a
  instance b

  # ----------------------------------------------------------------------
  # Private instances
  # ----------------------------------------------------------------------

  private instance c

  ...

}

6.3. Connection Graph Specifiers

A connection graph specifier specifies one or more connection graphs as part of a topology definition. A connection graph is a named set of connections. A connection connects an output port instance of one component instance to an input port instance of another.

6.3.1. Syntax

A connection graph specifier is one of the following:

  • A direct graph specifier: connections identifier { connection-sequence }

  • A pattern graph specifier: pattern-kind connections instance qual-ident [ { instance-sequence } ]

connection-sequence is an element sequence in which each element is a connection, and the terminating punctuation is a comma. A connection is the following:

pattern-kind is one of the following:

  1. command

  2. event

  3. health

  4. param

  5. telemetry

  6. time

instance-sequence is an element sequence in which each element is a qualified identifier, and the terminating punctuation is a comma.

6.3.2. Semantics

Direct graph specifiers. A direct graph specifier directly specifies a named connection graph.

  1. The identifier following the keyword connections names the connection graph.

  2. The connection sequence specifies the set of connections in the graph. For each connection C:

    1. For each of the two port instance identifiers I appearing in C:

      1. The component instance named in I must be available in the enclosing topology, either through direct specification or through import.

      2. The optional expression e following the identifier, if it is present, represents a port number, i.e., an index into an array of port instances. The type of e must be a numeric type, and e must evaluate to a compile-time constant that becomes a non-negative integer n when converted to type Integer. n must lie in the range [0,231) and must be in bounds for the array size of the port instance specifier named in I.

    2. The arrow represents the direction of the connection (left to right).

    3. The connection must go from an output port instance to an input port instance.

    4. The port instance types specified in the two port instances must match, except that a serial port at either end can be connected to any port at the other end.

    5. If a serial port instance S is connected to a typed port instance T, then the port type specified in T may not have a return type.

Pattern graph specifiers. A pattern graph specifier indirectly specifies one or more named connection graphs by specifying a source component instance, a set of target component instances, and a pattern for connecting the source instance to each of the target instances.

  1. Each topology may contain at most one of each kind of graph pattern.

  2. The qualified identifier following the keyword instance must refer to a component instance that is available in the enclosing topology, either through direct specification or through import.

  3. If the instance sequence I appears, then each qualified identifier Q appearing in I must refer to a component instance that is available in the enclosing topology, and each instance must be valid for the pattern (i.e., must have a port of the type required for the pattern). The instances in the sequence name the target instances for the pattern. If no instance sequence appears, then the target instances are all instances specified directly in the enclosing topology (not via import) that are valid for the pattern.

The meaning of specifier depends on the pattern kind, as follows:

  1. command: The source instance I is a command dispatcher. The following connection graphs are generated:

    1. A connection graph named Command consisting of all connections from the output port of type Fw::Cmd of I to the command input port of each target component.

    2. A connection graph named CommandRegistration consisting of all connections from the command registration output port of each target component to the command registration input port of I.

    3. A connection graph named CommandResponse consisting of all connections from the command response output port of each target component to the command response input port of I.

  2. event: The source instance I is an event logger with an input port of type Fw.Log. The generated connection graph has name Events and contains all connections for sending events to I through an event output port.

  3. health: The source instance I is a health component. The generated connection graph has name Health and contains all connections between the health component and ping ports of the target components of type Svc.Ping.

  4. param: The source instance I is a parameter database component. The generate connection graph has name Parameters and contains all connections for (a) getting parameters from the database and (b) saving parameters to the database.

  5. telemetry: The source instance I is a telemetry database with an input port of type Fw.Tlm. The generated connection graph has name Telemetry and contains all connections for sending telemetry to I through a telemetry output port.

  6. text event: The source instance I is a text event logger with an input port of type Fw.LogText. The generated connection graph has name TextEvents and contains all connections for sending events to I through an event output port.

  7. time: The source instance I is a time component. The generated connection graph has name Time and contains all connections for getting the time from I through a time get output port.

6.3.3. Example

Assume the following instances are available in the enclosing topology, and all have command ports:

commandDispatcher
commandSequencer
engineeringTelemetryLogger
eventLogger
telemetryDatabase
timeSource

Here is a pattern graph specifier:

command connections instance commandDispatcher

It is equivalent to the following direct graph specifiers:

connections CommandRegistration {
  commandDispatcher.cmdRegOut -> commandDispatcher.cmdRegIn
  commandSequencer.cmdRegOut -> commandDispatcher.cmdRegIn
  engineeringTelemetryLogger.cmdRegOut -> commandDispatcher.cmdRegIn
  eventLogger.cmdRegOut -> commandDispatcher.cmdRegIn
  telemetryDatabase.cmdRegOut -> commandDispatcher.cmdRegIn
  timeSource.cmdRegOut -> commandDispatcher.cmdRegIn
}

connections Command {
  commandDispatcher.cmdOut -> commandDispatcher.cmdIn
  commandDispatcher.cmdOut -> commandSequencer.cmdIn
  commandDispatcher.cmdOut -> engineeringTelemetryLogger.cmdIn
  commandDispatcher.cmdOut -> eventLogger.cmdIn
  commandDispatcher.cmdOut -> telemetryDatabase.cmdIn
  commandDispatcher.cmdOut -> timeSource.cmdIn
}

connections CommandResponse {
  commandDispatcher.cmdRespOut -> commandDispatcher.cmdRespIn
  commandSequencer.cmdRespOut -> commandDispatcher.cmdRespIn
  engineeringTelemetryLogger.cmdRespOut -> commandDispatcher.cmdRespIn
  eventLogger.cmdRespOut -> commandDispatcher.cmdRespIn
  telemetryDatabase.cmdRespOut -> commandDispatcher.cmdRespIn
  timeSource.cmdRespOut -> commandDispatcher.cmdRespIn
}

6.4. Container Specifiers

A container specifier specifies a data product container as part of a component definition. Containers are units of data that can be downlinked and then deleted. They hold data product records.

6.4.1. Syntax

product container identifier [ id expression ] [ default priority expression ]

6.4.2. Semantics

  1. The identifier names the container.

  2. The optional expression e after the keyword id specifies the numeric identifier for the container. If e is present, then the type of e must be convertible to Integer, and e must evaluate to a nonnegative integer. If e is not present, then the default identifier is either zero (for the first container appearing in a component) or the previous container identifier plus one.

  3. The optional expression e after the keywords default priority specifies a downlink priority for the container. This priority can be overridden by the command that downlinks the data. If e is present, then the type of e must be convertible to Integer, and e must evaluate to a nonnegative integer.

6.4.3. Examples

@ Container 0
@ Implied id is 0x00
product container Container0

@ Container 1
product container Container1 id 0x02

@ Container 2
@ Implied id is 0x03
product container Container2 default priority 10

6.5. Event Specifiers

An event specifier specifies an event report as part of a component definition.

6.5.1. Syntax

event identifier [ ( param-list ) ] severity severity [ id expression ] format string-literal [ throttle expression ]

severity is one of the following:

  • activity high

  • activity low

  • command

  • diagnostic

  • fatal

  • warning high

  • warning low

6.5.2. Semantics

  • The identifier names the event.

  • severity specifies the severity of the event.

  • The parameter list specifies the event parameters. If there are no parameters, the list may be omitted. ref may not appear in any of the parameters.

  • The optional expression e following id specifies the numeric identifier for the event. If e is present, then the type of e must be convertible to Integer, and e must evaluate to a nonnegative integer. If e is not present, then the default identifier is either zero (for the first event appearing in a component) or the previous event identifier plus one.

  • The string following format is a format string that formats the event for display on the ground. The arguments to the format string are the values bound to the event parameters. A numeric format is allowed for any argument whose type is a numeric type.

  • The optional expression e following throttle specifies the maximum number of times to emit the event before throttling it. The type of e must be convertible to Integer and must evaluate to an integer in the range [0,231).

6.5.3. Examples

@ An array of 3 F64 values
array F64x3 = [3] F64

@ An enumeration of cases
enum Case { A, B, C }

@ Event 0
event Event0 \
  severity activity low \
  id 0x00 \
  format "Event 0 occurred"

@ Event 1
@ Sample output: "Event 1 occurred with argument 42"
event Event1(
  arg1: U32 @< Argument 1
) \
  severity activity high \
  id 0x01 \
  format "Event 1 occurred with argument {}"

@ Event 2
@ Sample output: "Saw value [ 0.001, 0.002, 0.003 ] for case A"
event Event2(
  case: Case @< The case
  value: F64x3 @< The value
) \
  severity warning low \
  id 0x02 \
  format "Saw value {} for case {}" \
  throttle 10

6.6. Include Specifiers

An include specifier specifies that a file should be included in a translation unit.

6.6.1. Syntax

include string-literal

6.6.2. Semantics

The string literal specifies a file path relative to the location of the include specifier.

During parsing, the translator does the following:

  1. Resolve the path to an absolute file name that refers to a file F.

  2. Parse F, recursively resolving any include specifiers that appear in F.

  3. Include its parsed elements as if they appeared in the enclosing translation unit at the point where the include specifier appears.

The suffix .fppi is conventional for included files, to distinguish them from files presented directly for analysis or translation.

6.6.3. Examples

Example 1.

File a.fppi contains the following:

constant a = 1

File b.fpp contains the following:

include "a.fppi"
constant b = a

File b.fpp is translated identically to this translation unit:

constant a = 1
constant b = a

Example 2.

File a.fppi contains the following:

constant a = 1

File b.fpp contains the following:

module M { include "a.fppi" }
b = M.a

File b.fpp is translated identically to this translation unit:

module M { constant a = 1 }
b = M.a

6.7. Init Specifiers

An init specifier associates some code with a component instance. Usually this is initialization code, hence the name. It may also serve another purpose (e.g., teardown).

6.7.1. Syntax

6.7.2. Semantics

  • The expression following the keyword phase must have a numeric type. It provides an integer identifier for an initialization phase.

  • Each component instance may have at most one init specifier for each distinct numeric phase.

  • The code string specifies some code in a target language that is associated with the instance.

The meaning of the initialization phase and the code depends on the translation context.

6.7.3. Example

@ Phases of initialization
enum Phases {
  @ When components are constructed
  CONSTRUCTION
  @ After components are constructed, and before connections are established
  BEFORE_CONNECTIONS
  @ After connections are established
  AFTER_CONNECTIONS
  @ When components are deallocated
  TEARDOWN
}

instance commandDispatcher: Svc.CommandDispatcher \
  base id 0x100 \
  queue size 10 \
  stack size 4096 \
  priority 30 \
{

  phase BEFORE_CONNECTIONS  """
  commandDispatcher.init(QueueDepth::commandDispatcher);
  """

}

In this example, the code generator provides three phases, CONSTRUCTION, BEFORE_CONNECTIONS, AFTER_CONNECTIONS, and TEARDOWN. In the CONSTRUCTION phase, the code generator generates a default constructor invocation that looks like this:

Svc::CommandDispatcher commandDispatcher("commandDispatcher");

By default, the code generator might generate this code to run before connections are established:

commandDispatcher.init(
    QueueDepth::commandDispatcher,
    InstanceID::commandDispatcher
);

The code shown above overrides the code generated for the second phase to remove the instance ID from the arguments of the init method.

6.8. Internal Port Specifiers

An internal port specifier specifies a single-use port for use in handlers of the enclosing component. A component can use an internal port to send a message to itself.

6.8.1. Syntax

internal port identifier [ ( param-list ) ] [ priority expression ] [ queue-full-behavior ]

queue-full-behavior has the same syntax as for port instance specifiers.

6.8.2. Semantics

The identifier is the name of the port. The parameter list specifies the formal parameters of the port. Each formal parameter is a piece of data carried on the port. The names of the formal parameters must be distinct. No formal parameter may be a ref parameter.

The optional priority and queue full behavior have the same semantics as in async input port instance specifiers.

6.8.3. Examples

@ Port 1
internal port Port1(
    a: U32 @< Parameter a
    b: F64 @< Parameter b
)

@ Port 2
internal port Port2(
    a: U8 @< Parameter a
    b: I32 @< Parameter b
) priority 10 drop

6.9. Location Specifiers

A location specifier specifies the location of a definition.

6.9.1. Syntax

A location specifier is one of the following:

6.9.2. Semantics

  1. The qualified identifier Q is resolved like a use that refers to a definition as follows:

    1. A constant location specifier refers to a constant definition.

    2. A type location specifier refers to an array definition, enum definition, struct definition, or abstract type definition.

    3. A port location specifier refers to a port definition.

    4. A component location specifier refers to a component definition.

    5. A component instance location specifier refers to a component instance definition.

    6. A topology location specifier refers to a topology definition.

  2. When a location specifier appears inside a module definition M, Q is implicitly qualified by the qualified name of M.

  3. Q need not actually refer to any definition. This rule allows the specification of dependencies for a larger set of files than the ones involved in a particular analysis or translation.

  4. The string literal must specify the path of an FPP source file, relative to the location of the specifier. The file must exist. After resolving include specifiers, the file must contain the definition referred to in the location specifier.

  5. Multiple location specifiers for the same definition are allowed in a single model, so long as the locations are all consistent.

6.9.3. Examples

Example 1:

File a.fpp contains the following:

constant a = 1

File b.fpp contains the following:

locate constant a at "a.fpp"
constant b = a

When analyzing b.fpp, the analyzer knows that the definition of constant a is available in a.fpp.

Example 2:

File a.fpp is as in the previous example. File b.fpp contains the following:

module M { constant b = 0 }

File c.fpp contains the following:

locate constant a at "a.fpp"
module M { locate constant b at "b.fpp" }

The first location specifier refers to the constant a. The second location specifier refers to the constant M.b.

6.10. Parameter Specifiers

A parameter specifier specifies a parameter as part of a component definition.

6.10.1. Syntax

param identifier : type-name [ default expression ] [ id expression ] [ set opcode expression ] [ save opcode expression ]

6.10.2. Semantics

  • The identifier names the parameter.

  • The type name specifies the type T of the parameter.

  • The optional expression e following the keyword default specifies a default value for the parameter. If e is present, then the type of e must be convertible to T. If e is not present, then there is no default value for the parameter: When the parameter is used, then either (1) a value is available in the parameter database or (2) the use is invalid.

  • The optional expression e after the keyword id specifies the numeric identifier for the parameter. If e is present, then the type of e must be convertible to Integer, and e must evaluate to a nonnegative integer. If e is not present, then the default identifier is either zero (for the first parameter appearing in a component) or the previous parameter identifier plus one.

  • The optional expression e after the keywords set opcode specifies the opcode of the command for setting the parameter. If e is present, then the type of e must be convertible to Integer. If e is not present, then the default value is either zero (for the first command appearing in a component) or the previous opcode plus one.

  • The optional expression e after the keywords save opcode specifies the opcode of the command for saving the parameter. If e is present, then the type of e must be convertible to Integer. If e is not present, then the default value is either zero (for the first command appearing in a component) or the previous opcode plus one.

6.10.3. Examples

@ Parameter 1
param Parameter1: U32 \
  id 0x00 \
  set opcode 0x80 \
  save opcode 0x81

@ Parameter 2
param Parameter2: F64 \
  default 1.0 \
  id 0x01 \
  set opcode 0x82 \
  save opcode 0x83

6.11. Port Instance Specifiers

A port instance specifier specifies an instantiated port as part of a component definition.

6.11.1. Syntax

A port instance specifier is one of the following:

general-port-kind is one of the following:

  1. async input

  2. guarded input

  3. output

  4. sync input

port-instance-type is one of the following:

  1. qual-ident

  2. serial

queue-full-behavior is one of the following:

  1. assert

  2. block

  3. drop

special-port-input-kind is one of the following:

  1. async

  2. guarded

  3. sync

special-port-kind is one of the following:

  1. command recv

  2. command reg

  3. command resp

  4. event

  5. param get

  6. param set

  7. product get

  8. product recv

  9. product request

  10. product send

  11. telemetry

  12. text event

  13. time get

The following port instances are input port instances:

  • Any general port with input in its name.

  • Special ports command recv and product recv.

The others are output port instances.

6.11.2. Semantics

General port instances:

  1. The kind specifies the kind of the port instance.

  2. The identifier specifies the name of the port instance.

  3. The optional expression e enclosed in brackets specifies the number of port instances in the port instance array. If no such expression appears, the default value is 1. The type of e must be convertible to Integer. e must evaluate to a value n after conversion to Integer. n must be greater than zero.

  4. port-instance-type specifies the type of the port instance.

    1. If the type is a qualified identifier q, then q must refer to a port definition D. If the kind is async input, then D may not specify a return type.

    2. If the type is serial, then the port instance is a serial port instance. A serial port instance does not specify a type. It may be connected to a port of any type. Serial data passes through the port. The data may be converted to or from a specific type at the other end of the connection.

  5. The optional expression e appearing after the keyword priority specifies a priority for the port instance. The type of e must be convertible to Integer. The priority applies to the component’s message queue and may appear only for async input ports. The meaning of the priority value is operating system-dependent.

  6. The optional queue-full-behavior specifies the behavior when a message is received and the queue is full:

    1. assert means that an assertion fails, terminating FSW.

    2. block means that the sender is blocked until there is space on the queue for the message.

    3. drop means that the message is dropped.

    This specifier is valid only for async input ports. If no specifier appears, then the default behavior is assert.

Special port instances: The special port instance have special functions in the code generated from an FPP model. The types and other attributes of the special ports are specified by F Prime. The identifier specifies the name of the port.

There must be at most one of each kind of special port instance.

  1. The optional input kind must be present for product recv ports and may not be present for other ports.

  2. The optional expression e appearing after the keyword priority and the optional queue-full-behavior behavior have the same meaning as described above for general ports. These elements are valid only for async product recv ports.

Each special port instance represents an implied use of a port as shown in the table below. These ports are defined by the F Prime framework.

Special port kind Implied port use

command recv

Fw.Cmd

command reg

Fw.CmdReg

command resp

Fw.CmdResponse

event

Fw.Log

param get

Fw.PrmGet

param set

Fw.PrmSet

product get

Fw.DpGet

product recv

Fw.DpResponse

product request

Fw.DpRequest

product send

Fw.DpSend

telemetry

Fw.Tlm

text event

Fw.LogText

time get

Fw.Time

6.11.3. Examples

General ports:

@ Async input port of type Fw.Com
@ It has priority 10.
@ It drops input received when the queue is full.
async input port asyncComIn: Fw.Com priority 10 drop

@ Async input serial port
async input port serialIn: serial

@ Guarded input port of type Fw.Com
guarded input port guardedComIn: Fw.Com

@ Output port array of 10 Fw.Com ports
output port comOut: [10] Fw.Com

@ Sync input port of type Fw.Com
sync input port syncComIn: Fw.Com

Special ports:

@ A port for receiving commands from the command dispatcher
command recv port cmdIn

@ A port for sending command registration requests to the command dispatcher
command reg port cmdRegOut

@ A port for sending responses to the command dispatcher
command resp port cmdRespOut

@ A port for emitting events
event port eventOut

@ A port for emitting text events
text event port textEventOut

@ A port for getting parameter values from the parameter database
param get port paramGetOut

@ A port for sending parameter values to the parameter database
param set port paramSetOut

@ A port for emitting telemetry channels
telemetry port tlmOut

@ A port for getting the current time
time get port timeGetOut

@ An async port for receiving requested data product buffers
async product recv port productRecvIn

@ A port for requesting data product buffers
product request port productRequestOut

@ A port for sending data products
product send port productSendOut

6.12. Port Matching Specifiers

A port matching specifier is part of a component definition. It specifies that when the component is instantiated and connected into a topology, the corresponding port numbers of two of its port instances should match. For example, a port matching specifier could specify that for a health monitoring component, any component connected to its ping output port at port number n should also be connected to its ping input port at the same port number n.

6.12.1. Syntax

match identifier with identifier

6.12.2. Semantics

  1. Each of the identifiers must name a general port instance specified in the enclosing component.

  2. The two port instances must be distinct and must have the same array size.

6.12.3. Example

queued component Health {

  @ Number of ping ports
  constant numPingPorts = 10

  @ Ping output port
  output port pingOut: [numPingPorts] Svc.Ping

  @ Ping input port
  async input port pingIn: [numPingPorts] Svc.Ping

  @ Corresponding port numbers of pingOut and pingIn must match
  match pingOut with pingIn

}

6.13. Record Specifiers

A record specifier specifies a data product record as part of a component definition. A record is a unit of data that is stored in a container.

6.13.1. Syntax

product record identifier : type-name [ array ] [ id expression ]

6.13.2. Semantics

  1. The identifier names the record.

  2. The type name T following the identifier specifies the type of the data stored in the record.

    1. If the keyword array appears after the type name, then the record stores a variable number of elements of type T. In this case the record consists of a size s followed by a data payload consisting of s elements of type T.

    2. Otherwise the record stores a single value of type T.

  3. The optional expression e after the keyword id specifies the numeric identifier for the record. If e is present, then the type of e must be convertible to Integer, and e must evaluate to a nonnegative integer. If e is not present, then the default identifier is either zero (for the first record appearing in a component) or the previous record identifier plus one.

6.13.3. Examples

@ An array of 3 F64 values
array F64x3 = [3] F64

@ Record 0: A variable number of F32 values
@ Implied id is 0x00
product record Record0: F32 array

@ Record 1: A single U32 value
product record Record1: U32 id 0x02

@ Record 2: A single F64x3 value
@ Implied id is 0x03
product record Record2: F64x3

6.14. Telemetry Channel Specifiers

A telemetry channel definition defines a telemetry channel as part of a component definition.

6.14.1. Syntax

telemetry identifier : type-name [ id expression ] [ update telemetry-update ] [ format string-literal ] [ low { telemetry-limit-sequence } ] [ high { telemetry-limit-sequence } ]

telemetry-update is one of the following:

  • always

  • on change

telemetry-limit-sequence is an element sequence in which the elements are telemetry limits, and the terminating punctuation is a comma. A telemetry limit is one of the following:

6.14.2. Semantics

  1. The identifier names the telemetry channel.

  2. The type name specifies the type of the telemetry channel.

  3. The optional expression e after the keyword id specifies the numeric identifier for the channel. If e is present, then the type of e must be convertible to Integer, and e must evaluate to a nonnegative integer. If e is not present, then the default identifier is either zero (for the first channel appearing in a component) or the previous channel identifier plus one.

  4. The optional update specifier specifies whether the telemetry channel is emitted always or on change. If the specifier is not present, the default behavior is always.

  5. The optional format specifier specifies a format string. There is one argument to the format string, which is the channel value.

  6. The optional high and low limit specifiers specify the high and low limits for the channel. The following rules apply:

    1. At most one of each kind of limit (red, orange, yellow) may appear in each specifier.

    2. The type of the expression in each limit must be a numeric type and must be convertible to the type of the channel.

    3. The limit is applied to each telemetry channel with type T and value v as follows:

      1. If T is a numeric type, then the limit is applied directly to v.

      2. If T is not itself a numeric type (e.g., it is an array), then the limit is applied recursively to each member value of v.

6.14.3. Examples

@ An array of 3 F64 values
array F64x3 = [3] F64

@ Telemetry channel 0
telemetry Channel0: U32 id 0x00

@ Telemetry channel 1
telemetry Channel1: U32 \
  id 0x01 \
  update on change

@ Telemetry channel 2
telemetry Channel2: F64 \
  id 0x02 \
  format "{.3f}"

@ Telemetry channel 3
telemetry Channel3: F64x3 \
  id 0x03 \
  low { yellow -1, orange -2, red -3 } \
  high { yellow 1, orange 2, red 3 }

6.15. Topology Import Specifiers

A topology import specifier imports one topology into another one.

6.15.1. Syntax

import qual-ident

6.15.2. Semantics

The qualified identifier must refer to a topology definition.

Importing instances: FPP uses the following algorithm to import the instances of topology T' into topology T:

  1. Let I be the set of instances of T.

  2. Let I' be the set of public instances of T'.

  3. Let I'' be the set union of I and I'. That means that if either or both of I and I' contain the instance S, then I'' contains the instance S once. Each instance in I'' has private visibility if it is private in either I or I', otherwise public visibility.

Importing connections: FPP uses the following algorithm to import the connections of topology T' into topology T.

  1. For each connection graph name \$N_i\$ that appears in either T or T':

    1. Let \$G_i\$ be the connection graph named \$N_i\$ in T. If no such graph exists, then let \$G_i\$ be the empty connection graph with name \$N_i\$.

    2. Let \$G'_i\$ be the connection graph named \$N_i\$ in T'. If no such graph exists, then let \$G'_i\$ be the empty connection graph with name \$N_i\$.

    3. Let \$C_i\$ be the set of connections of \$G_i\$ such that each of the two instances at the ends of the connection is in I''.

    4. Let \$C'_i\$ be the set of connections of \$G'_i\$ such that each of the two the instances at the ends of the connection is in I'', and the connection is defined by a direct or pattern specifier in T' (i.e., not imported into T' from another topology).

    5. Let \$C''_i\$ be the disjoint union of \$C_i\$ and \$C'_i\$. That means that if \$C_i\$ contains n connections between port p and port p', and \$C'_i\$ contains m connections between port p and port p', then \$C_i\$ contains n + m connections between port p and port p'.

    6. Let \$G''_i\$ be the connection graph with name \$N_i\$ and connections \$C''_i\$.

  2. Return the connection graphs \$G''_i\$.

6.15.3. Example

topology A {

  instance a
  private instance b
  instance c

  connections C1 {
    a.p1 -> c.p
  }

  connections C2 {
    b.p -> c.p
  }

}

topology B {

  import A

  instance d
  instance e
  instance f

  connections C1 {
    a.p1 -> d.p
  }

  connections C2 {
    a.p2 -> e.p
  }

  connections C3 {
    a.p3 -> f.p
  }

}

After importing, topology B is equivalent to this topology:

topology B {

  instance a
  instance c
  instance d
  instance e
  instance f

  connections C1 {
    a.p1 -> c.p
    a.p2 -> d.p
  }

  connections C2 {
    a.p2 -> e.p
  }

  connections C3 {
    a.p3 -> f.p
  }

}

Note the following:

  • The connections from topologies A and B are merged graph by graph.

  • Because instance b is private to topology A, neither it nor any of its connections appear in topology B.

7. Port Instance Identifiers

A port instance identifier identifies a port instance that is part of a component instance. Port instance identifiers appear in connection graph specifiers.

7.1. Syntax

7.2. Semantics

For each port instance identifier Q . P:

  1. The qualified identifier Q must refer to a component instance I.

  2. I must refer to a component instance definition I'.

  3. I' must refer to a component definition C.

  4. The identifier P must refer to a port instance specifier of C.

7.3. Examples

a.b
A.b.c

In the first example, a names a component instance, and b names a port instance. In the second example, A.b names a component instance, and c names a port instance.

8. Type Names

Type names are the syntactic names of types. In some cases, e.g., U32, there is a one-to-one correspondence between type names and types. In other cases, e.g., array types, there is no one-to-one correspondence. For example, depending on the enclosing scope, the name a.b and the name b may refer to the same array type.

8.1. Primitive Integer Type Names

Primitive integer type names are the names of the primitive integer types. There are two kinds of primitive integer types: unsigned integer types and signed integer types.

  • The names of the unsigned integer types are U8, U16, U32, and U64. The U stands for “unsigned.” The number after the U is the width of the representation in bits, using the standard binary representation of an unsigned integer.

  • The names of the signed integer types are I8, I16, I32, and I64. The I stands for “[signed] integer.” The number after the I is the width of the representation in bits, using the standard binary two’s complement representation of a signed integer.

8.2. Floating-Point Type Names

The floating-point type names are F32 and F64. These refer to the types of IEEE floating-point values of width 32 and 64 bits, respectively.

8.3. The Boolean Type Name

The type name bool represents the type of the two Boolean values true and false.

8.4. String Type Names

A string type name consists of the keyword string optionally followed by the keyword size and an expression. For example:

string
string size 256

The optional expression following the keyword size is called the size expression of the string type name. If present, it must have a numeric type, and it must evaluate to a value in the range [0,231) after conversion to Integer. The size expression specifies the maximum length of a string value represented by the type. If the size expression is not present, then the translator uses a default maximum string length.

8.5. Qualified Identifier Type Names

A qualified identifier type name is a qualified identifier that refers to an abstract type definition, array definition, enum definition, or struct definition via the rules for name scoping. It names the type defined in the definition to which it refers.

For example:

module M {
  type T
  array A = [3] T # T is a qualified identifier t type name. It names the type M.T.
}
module M {
  array A = [3] U8
  array B = [3] A # A is an array type name. It names the type M.A.
}
module M {
  enum E { X = 0, Y = 1 }
  array A = [3] E # E is a qualified identifier type name. It names the type M.E.
}
module M {
  struct S {
    x: U32
    y: F32
  }
  struct T {
    s: S # S is a qualified identifier type name. It names the type M.S.
  }
}

9. Expressions

9.1. Arithmetic Expressions

FPP includes the following arithmetic expressions:

Syntax Meaning

- \$e\$

Negate \$e\$

\$e_1\$ + \$e_2\$

Add \$e_1\$ and \$e_2\$

\$e_1\$ - \$e_2\$

Subtract \$e_2\$ from \$e_1\$

\$e_1\$ * \$e_2\$

Multiply \$e_1\$ by \$e_2\$

\$e_1\$ / \$e_2\$

Divide \$e_1\$ by \$e_2\$

Each subexpression of an arithmetic expression must have numeric type. Evaluation is by either standard integer arithmetic or standard floating-point arithmetic, depending on the type of the expression.

Example:

constant a = -1
constant b = 2 + 3
constant c = a * b

9.2. Array Expressions

An array expression is an expression that represents an array value.

9.2.1. Syntax

[ array-element-sequence ]

array-element-sequence is an element sequence in which the elements are expressions, and the terminating punctuation is a comma.

9.2.2. Semantics

  1. The sequence must have at least one element.

  2. The types of the expressions in the sequence must be convertible to a common type.

  3. The value of the expression is formed by computing the value of each element and then converting all the elements to the common type.

9.2.3. Example

constant a = [ 0, 1, 2 ] # a is an array value with elements 0, 1, 2

9.3. Boolean Literals

A Boolean literal expression is one of the values true and false.

9.4. Dot Expressions

A dot expression is a use that refers to a constant definition or enumerated constant definition.

9.4.1. Syntax

9.4.2. Semantics

The following rules give the meaning of a dot expression \$e\$.x:

  1. If \$e\$.x is a qualified identifier that represents one of the uses listed above according to the rules for resolving qualified identifiers, then it evaluates to the value stored in the corresponding definition.

  2. Otherwise \$e\$.x is invalid.

9.4.3. Examples

Example 1
module M {
  constant a = 1
}
constant b = M.a # M.a evaluates to 1
Example 2
enum E { X = 0, Y = 1 }
constant a = E.X # E.X evaluates to 0

9.5. Floating-Point Literals

A floating-point literal expression is a C-style representation of an IEEE floating-point number.

Examples:

1e-10
0.001
3.14159
6.02E23

9.6. Identifier Expressions

An identifier expression is an identifier that refers to a constant definition or enumerated constant definition, according to the rules for resolving identifiers.

Example:

constant a = 42
constant b = a # a is an identifier expression

9.7. Integer Literals

An integer literal expression is one of the following:

  • A sequence of decimal digits 0 through 9 denoting the decimal representation of a nonnegative integer.

  • 0x or 0X followed by a sequence of hexadecimal digits 0 through 9, A through F, or a through f denoting the hexadecimal representation of a nonnegative integer.

An integer literal value \$v\$ is represented in the model as an integer of arbitrary width. During code generation, the width is narrowed (if necessary) to some machine width less than or equal to 64 bits.

Examples:

1234
0xABCD

9.8. Parenthesis Expressions

A parenthesis expression is an expression surrounded by parentheses in order to group subexpressions and to force evaluation order.

9.8.1. Syntax

9.8.2. Semantics

The type and value of the expression are the type and value of the subexpression.

9.8.3. Example

constant a = (1 + 2) * 3

The expression on the right-hand side of the constant definition evaluates to 9.

9.9. String Literals

A string literal expression is a single-line string literal or a multiline string literal. In this section

  • Literal string means a literal string appearing in the FPP source text.

  • Interpreted string means the sequence of characters represented by the literal string.

  • String interpretation is the process of converting a literal string to an interpreted string.

  • A newline character is the NL character (ASCII code 0x0A).

  • An extended newline character is the NL character (ASCII code 0x0A), optionally preceded by a CR character (ASCII code 0x0D).

9.9.1. Single-Line String Literals

A single-line string literal is a sequence of single-line literal characters enclosed in double quote characters ". A single-line literal character is one of the following:

  • Any character other than the newline character, ", or \.

  • \ followed by any character c other than the newline character. In this case we say that the character c is escaped.

The characters of the interpreted string correspond one-to-one to the literal characters of the string, in the same order, according to the following rule: if the literal character is c or \ c, then the corresponding character of the interpreted string is c.

Examples:

  • "abc" is a valid string consisting of the characters a, b, and c.

  • "ab\"" is a valid string consisting of the characters a, b, and ".

  • "ab\\\"" is a valid string consisting of the characters a, b, \, and ".

  • "ab\c" is a valid string consisting of the characters a, b, c.

  • "abc is not a valid string, because it is missing the terminating ".

  • "\" is not a valid string, because it is missing the terminating ".

  • "ab"c" is the valid string "ab" followed by the identifier c and an unmatched double quote character.

9.9.2. Multiline String Literals

A multiline string literal is a sequence of multiline literal characters enclosed in sequences """ of three double quote characters. A multiline literal character is one of the following:

  • Any character other than " or \.

  • \ followed by any character.

Before trimming whitespace, the characters of the interpreted string correspond one-to-one to the literal characters of the string, in the same order, according to the following rule: if the literal character is c or \ c, then the corresponding character of the interpreted string is c.

Whitespace inside a multiline string literal is trimmed as follows:

  1. The first extended newline character, if any, after the first """ is deleted from the interpreted string.

  2. Any whitespace occurring in columns to the left of the first """ is deleted from the result of step 1.

  3. Any leading and trailing extended newline characters are deleted from the result of step 2.

Example 1

constant s = """\"\"\""""

The string literal uses escaped quotation marks. The value of the string constant s is """.

Example 2

  """
  // This is a multiline string literal
  // It represents some C++ code
  instance.create(0, 1);
  """

The interpreted string consists of the following lines, each terminated by a newline:

  1. // This is a multiline string literal

  2. // It represents some C++ code

  3. instance.create(0, 1);

9.10. Struct Expressions

An struct expression is an expression that represents a struct value.

9.10.1. Syntax

{ struct-element-sequence }

struct-element-sequence is an element sequence in which the elements are struct elements, and the terminating punctuation is a comma. A struct element has the following syntax:

9.10.2. Semantics

The following must be true of the struct element sequence S:

  1. No two identifiers appearing in S may be the same.

  2. Each expression appearing in S must have a valid type.

The expression is evaluated by evaluating each member expression to a value and then constructing the struct value with the corresponding member names and values.

9.10.3. Example

# s is a struct value with members x = 0, y = 1
constant s = {
  x = 0
  y = 1
}

9.11. Precedence and Associativity

Ambiguity in parsing expressions is resolved with the following precedence table. Expressions appearing earlier in the table have higher precedence. For example, -a.b is parsed as -(a.b) and not (-a).b. Where necessary, each element in the ordering provides an associativity for resolving expressions with equal precedence.

Expression Associativity

Dot expressions \$e\$ . \$i\$

None

Unary negation expressions - \$e\$

None

Multiplication expressions \$e_1\$ * \$e_2\$ and division expressions \$e_1\$ / \$e_2\$

Left

Addition expressions \$e_1\$ + \$e_2\$ and subtraction expressions \$e_1\$ - \$e_2\$

Left

10. Formal Parameter Lists

A formal parameter list is an element sequence in which the elements are formal parameters, and the terminating punctuation is a comma.

A formal parameter is a typed symbol to which a value becomes bound at runtime. Formal parameters appear, for example, in port definitions.

10.1. Syntax

Formal parameters have the following syntax:

[ ref ] identifier : type-name

10.2. Semantics

If present, the keyword ref specifies that the value bound to the formal parameter is to be passed by reference when it is used in a synchronous port invocation.

The identifier is the name of the formal parameter. No two parameters in the same formal parameter list may have the same name.

The type name specifies the type T of the formal parameter P.

10.3. Examples

a: U32
b: string size 100
ref c: Fw.Com

11. Format Strings

A format string is a string that specifies the display format of a struct member, array element, event report, or telemetry channel.

An FPP format string is similar to a Python format string. As in python, a format string consists of text containing one or more replacement fields surrounded by curly braces { }. In the output, each replacement field is replaced by the value of an argument. Characters outside of replacement fields are copied to the output unchanged, except that the escape sequences {{ and }} are converted to single braces.

The number of arguments must match the number of replacement fields. Each replacement field must also match the type of its argument, as discussed below.

The following replacement fields are allowed:

  1. You can use the default replacement field {} to convert any argument. It causes the argument to be displayed in a standard way for F Prime according to its type.

  2. If the type of an argument a is an integer type, then you can use an integer replacement field to convert a. An integer replacement field is one of the following:

    1. {c}: Display a as a character value.

    2. {d}: Display a as a decimal integer value

    3. {x}: Display a as a hexadecimal integer value.

    4. {o}: Display a as an octal integer value.

  3. If the type of an argument a is a floating-point type, then you can use a rational replacement field to convert a. A rational replacement field is one of the following:

    1. {e}: Display a as a rational number using exponent notation, e.g., 1.234e2

    2. {f}: Display a as a rational number using fixed-point notation, e.g., 123.4.

    3. {g}: Display a as a rational number using general format. This format uses fixed-point notation for numbers up to some size s and uses exponent notation for numbers larger than s. The value of s is implementation-dependent.

    4. One of the replacement fields denoted above, but with a period and a literal decimal integer after the opening brace. The integer value n specifies the precision, i.e., the number of digits after the decimal point for fixed-point notation, or before the e for exponent notation. For example, the replacement field {.3f}, specifies fixed-point notation with a precision of 3. n must be in the range [0,100].

No other replacement fields are allowed. No other use of { or } is allowed, except in the escape sequences {{ and }}.

12. Comments and Annotations

12.1. Comments

A comment is model text that is ignored by the translator. It provides information to human readers of the source model.

Comments begin with the character # and go to the end of the line. For example:

# This is a comment

To write a multiline comment, precede each line with #:

# This is a multiline comment.
# It has two lines.

12.2. Annotations

An annotation is similar to a comment, but it is attached to a syntactic element of a model, and it is preserved during analysis and translation. The precise use of annotations depends on the translator. A typical use is to include annotations as comments in generated code.

12.2.1. Where Annotations Can Occur

Annotations can occur at the following syntax elements:

These elements are called the annotatable elements of an FPP model.

12.2.2. Kinds of Annotations

There are two kinds of annotations: pre-annotations and post-annotations.

A pre-annotation starts with @ and goes to the end of the line. Whitespace characters after the initial @ are ignored. A pre-annotation must occur immediately before an annotatable element e. It is attached to e during translation.

A post-annotation starts with @< and goes to the end of the line. Whitespace characters after the initial @< are ignored. A post-annotation must occur immediately after an annotatable element e. It is attached to e during translation.

You may follow either kind of annotation by one or more blank lines. In this case the blank lines are ignored.

Example
@ This is module M
module M {
  constant a = 0 @< This is constant M.a
  constant b = 1 @< This is constant M.b
  @ This is an enum
  enum E {
    a @< This is enumerated constant M.E.a
    b @< This is enumerated constant M.E.b
  }
}

12.2.3. Multiline Annotations

You can write several pre-annotations in a row before an annotatable element e. In this case, all the pre-annotations are attached to the element, in the order that they appear.

Similarly, you can write several post-annotations in a row after an annotatable element e. In this case, all the post-annotations are attached to the element, in the order that they appear.

Example
@ This is module M
@ Its pre-annotation has two lines
module M {
  constant a = 0 @< This is constant M.a
                 @< Its post-annotation has two lines
  constant b = 1 @< This is constant M.b
                 @< Its post-annotation has two lines
  @ This is an enum
  @ Its pre-annotation has two lines
  enum E {
    a @< This is enumerated constant M.E.a
      @< Its post-annotation has two lines
    b @< This is enumerated constant M.E.b
      @< Its post-annotation has two lines
  }
}

13. Translation Units and Models

13.1. Translation Units

A translation unit forms part of a model at the top level. Translation units usually correspond to source files of a program. However, translation units may be specified in other ways, e.g., on standard input.

13.1.1. Syntax

A translation unit is an element sequence in which each element is a translation unit member, and the terminating punctuation is a semicolon. A translation unit member is syntactically identical to a module member.

13.1.2. Example

Here is a translation unit:

module M1 { constant a = 0 }

And here is another one:

module M1 { constant b = 0 }

And here is a third one:

module M2 {
  constant a = M1.a
  constant b = M1.b
}

Call these translation units 1, 2, and 3 for purposes of the example in the following section.

13.2. Models

A model is a collection of one or more translation units. A model is presented to one or more analyzers or translators for analysis or translation. How this is done depends on the analyzer or translator. Typically, you ask a translator to read a single translation unit from standard input and/or to read one or more translation units stored in files, one unit per file.

Example:

Translation units 1-3 in the previous section, taken together, form a single model. That model is equivalent to the following single translation unit:

module M1 { constant a = 0 }
module M1 { constant b = 1 }
module M2 {
  constant a = M1.a
  constant b = M1.b
}

According to the semantics of module definitions, this is also equivalent:

module M1 {
  constant a = 0
  constant b = 1
}
module M2 {
  constant a = M1.a
  constant b = M1.b
}

Note that translation unit 3 alone is not a valid model, because it uses free symbols defined in the other translation units. Similarly, the translation unit 3 together with just translation unit 1 or translation unit 2 is not a valid model.

13.3. Locations

Every syntactic element E in a source model has an associated location L. The location is used when resolving location specifiers and include specifiers during analysis.

  1. If E is read in from a file, then L is the absolute path of the file.

  2. If E is read from standard input, then L is the absolute path of the current directory in the environment where the analysis occurs.

14. Scoping of Names

14.1. Qualified Identifiers

A qualified identifier is one of the following:

  1. An identifier.

  2. Q . I, where Q is a qualified identifier and I is an identifier.

Examples:

a
a.b
a.b.c

14.2. Names of Definitions

Every definition D appearing in an FPP model has a unique qualified name. The qualified name is a qualified identifier formed as follows:

  • If D appears outside any definition with a brace-delimited body, then the qualified name is the identifier I appearing in D.

  • Otherwise, the qualified name is N . I, where N is the qualified name of the enclosing definition, and I is the identifier appearing in D.

For example:

module M { # Qualified name is M
  module N { # Qualified name is M.N
    enum Maybe { # Qualified name is M.N.Maybe
      NO # Qualified name is M.N.Maybe.NO
      YES # Qualified name is M.N.Maybe.YES
    }
  }
}

14.3. Name Groups

The qualified names of definitions and reside in the following name groups:

  1. The component instance name group

  2. The component name group

  3. The port name group

  4. The topology name group

  5. The type name group

  6. The value name group

Definitions reside in name groups as follows:

  • Abstract type definitions reside in the type name group.

  • Array type definitions reside in the type name group.

  • Component definitions reside in the component name group, the value name group, and the type name group.

  • Component instance definitions reside in the component instance name group.

  • Constant definitions reside in the value name group.

  • Enum definitions reside in both the value name group and the type name group.

  • Enumerated constant definitions reside in the value name group.

  • Module definitions reside in all the name groups.

  • Port definitions reside in the port name group.

  • Struct type definitions reside in the type name group.

  • Topology definitions reside in the topology name group.

Notice that elements with scopes reside in the name groups of items that they may enclose. For example, enums reside in the value name group because they enclose constant definitions.

14.4. Multiple Definitions with the Same Qualified Name

14.4.1. Different Name Groups

Two definitions with the same qualified name are allowed if they are in different name groups. For example:

struct s { x: U32 } # Defines s in the type name group
constant s = 0 # Defines s in the value name group

14.4.2. Module Definitions

Multiple syntactic module definitions with the same qualified name are allowed. The semantic analysis combines all such definitions into a single module definition with that qualified name. For example, this model is legal

module M { constant a = 0 }
module M { constant b = 1 }
a = M.a
b = M.b

It is equivalent to this model:

module M {
  constant a = 0
  constant b = 1
}
a = M.a
b = M.b

Because the order of definitions is irrelevant, this is also equivalent:

module M { constant a = 0 }
a = M.a
b = M.b
module M { constant b = 1 }

14.4.3. Conflicting Definitions

Within the same name group, two definitions with the same qualified name are not allowed, unless they are both module definitions as described above. For example:

module M {
  constant a = 0
  constant a = 1 # Error: Name M.a is redefined
}

Two definitions with the same identifier are allowed if they have different qualified names, for example:

constant a = 0
module M {
  constant a = 1 # OK, qualified name is M.a =/= a
}

14.5. Resolution of Identifiers

The following rules govern the resolution of identifiers, i.e., associating identifiers with definitions:

  1. Use the context to determine which name group S to use. For example, if we are expecting a type name, then use the type name group.

  2. At the top level (outside the brace-delimited body of any definition), the identifier I refers to the unique definition with qualified name I if it exists in name group S. Otherwise an error results.

  3. Inside the brace-delimited body of a definition with qualified name N appearing at the top level:

    1. The identifier I refers to the definition with qualified name N . I if it exists in name group S.

    2. Otherwise I refers to the definition with qualified name I if it exists in name group S.

    3. Otherwise an error results.

  4. Inside the brace-delimited body of a definition with qualified name N appearing inside the body of a definition D:

    1. The identifier I refers to the definition with qualified name N . I if it exists in name group S.

    2. Otherwise proceed as if I were appearing inside D.

Example:

S refers to the value name group.

# Identifier M is in scope in S and refers to the qualified name M
# Identifier a is in scope in S and refers to qualified name a

constant a = 1 # Unique definition in S with qualified name a

module M {
  # Identifier M is in scope in S and refers to the qualified name M
  # Identifier N is in scope in S and refers to the qualified name N
  # Identifier a is in scope in S and refers to qualified name a
  # Identifier b is in scope in S and refers to qualified name M.b
  constant b = 2 # Unique definition in S with qualified name M.b
}

# Identifier M is in scope in S and refers to the qualified name M
# Identifier a is in scope in S and refers to qualified name a

module M {

  # Identifier M is in scope in S and refers to the qualified name M
  # Identifier N is in scope in S and refers to the qualified name M.N
  # Identifier a is in scope and refers to qualified name a
  # Identifier b is in scope and refers to qualified name M.b

  module N {
    # Identifier M is in scope in S and refers to the qualified name M
    # Identifier N is in scope in S and refers to the qualified name M.N
    # Identifier a is in scope in S and refers to qualified name a
    # Identifier b is in scope in S and refers to qualified name M.N.b
    constant b = 3 # Unique definition in S with qualified name M.N.b
  }

}

# Identifier M is in scope in S and refers to the qualified name M
# Identifier a is in scope in S and refers to qualified name a

14.6. Resolution of Qualified Identifiers

The following rules govern the resolution of qualified identifiers, i.e., associating qualified identifiers with definitions:

  1. If a qualified identifier is an identifier, then resolve it as stated in the previous section.

  2. Otherwise, the qualified identifier has the form Q . I, where Q is a qualified identifier and I is an identifier. Do the following:

    1. Recursively resolve Q.

    2. If Q refers to a definition with a brace-delimited body, then do the following:

      1. Determine the name group S of Q . I.

      2. Look in D for a definition with identifier I in name group S. If there is none, issue an error.

    3. Otherwise the qualified identifier is invalid. Issue an error.

Example:

module M {
  constant a = 0
  enum E {
    b = 2
    c = b # Refers to M.E.b
    d = E.b # Refers to M.E.b
    e = M.E.b # Refers to M.E.b
  }
  constant f = a # Refers to M.a
  constant g = M.a # Refers to M.a
  constant h = E.b # Refers to M.E.b
  constant i = M.E.b # Refers to M.E.b
}

15. Definitions and Uses

15.1. Uses

A use is a qualified identifier that refers to a definition according to the scoping rules for names.

A use is a qualifying use if it qualifies another use. For example, in the following code

  • The expression M.a contains the expression M, which is a qualifying use of the module M. It qualifies the use M.a.

  • The expression M.a is not a qualifying use.

module M {

  constant a = 0

  constant b = M.a

}

If a use u is not a qualifying use, then we say it is a non-qualifying use.

15.2. Use-Def Graph

The set of definitions and non-qualifying uses in an FPP model induces a directed graph called the use-def graph. In this graph,

  1. The nodes are the definitions.

  2. There is an edge from each definition d to the definitions \$d_1, ..., d_n\$ corresponding to the non-qualifying uses \$u_1, ..., u_n\$ appearing in d.

For example, in the following code, the use-def graph has two nodes a and b and one edge b \$rarr\$ a.

constant a = 0
constant b = a

In a legal FPP model, the use-def graph must be acyclic. For example, this model is illegal:

constant a = b
constant b = a

This model is also illegal:

constant a = a

This model is legal:

module M {

  constant a = 0

  constant b = M.a

}

The use M appears inside the definition of M. However, it is a qualifying use.

15.3. Order of Definitions and Uses

So long as the use-def graph is acyclic, there is no constraint either on the ordering of definitions and uses within a translation unit, or on the distribution of definitions and uses among translation units. For example, if the definition constant c = 0 appears anywhere in any translation unit of a model M, then the use of c as a constant value of type Integer is legal anywhere in any translation unit of M. In particular, this model is legal:

constant b = a
constant a = 0

The model consisting of two translation units

constant b = a

and

constant a = 0

is also legal, and the order in which the units are presented to the translator does not matter.

16. Types

16.1. Primitive Integer Types

The primitive integer types correspond to the primitive integer type names and are written in the same way, e.g., U32.

16.2. Floating-Point Types

The floating-point types correspond to the floating-point type names and are written in the same way, e.g., F64.

16.3. Primitive Numeric Types

The primitive integer types together with the floating-point types are called the primitive numeric types.

16.4. The Boolean Type

The Boolean type corresponds to the Boolean type name. It is written bool.

16.5. Primitive Types

Together, the primitive numeric types and the Boolean type are called the primitive types.

16.6. String Types

The string types correspond to the string type names. A string type is written string or string size n, where n is an integer value in the range [1,2^31-1]. There is one string type string and one string type string n for each legal value of n.

16.7. Array Types

An array type is a type associated with an array definition. There is one array type per array definition. Each array type is computed after resolving constants, so an array type has a constant integer value for its size. An array type is written using its unique qualified name.

16.8. Enum Types

An enum type is a type associated with an enum definition. There is one enum type per enum definition. An enum type is written using its unique qualified name.

16.9. Struct Types

A struct type is a type associated with a struct definition. There is one struct type per struct definition. A struct type is written using its unique qualified name.

16.10. Abstract Types

An abstract type is a type associated with an abstract type definition. There is is one abstract type per abstract type definition. An abstract type is written using its unique qualified name.

16.11. Internal Types

Internal types do not have syntactic names in FPP source models. The compiler assigns these types to expressions during type checking.

16.11.1. Integer

The type Integer represents all integer values, without regard to bit width.

16.11.2. Integer Types

Integer together with the primitive integer types are called the integer types.

16.11.3. Numeric Types

Integer together with the primitive numeric types are called the numeric types.

16.11.4. Anonymous Array Types

The type [ n ] T, where n is an integer and T is a type, represents an array of n elements, each of type T.

16.11.5. Anonymous Struct Types

The type { \$m_1\$ : \$T_1, ...,\$ \$m_n\$ : \$T_n\$ }, where each \$m_i\$ is an identifier and each \$T_i\$ is a type, represents a struct with members \$m_i\$ : \$T_i\$.

The order of the members is not significant. For example, { a: U32, b: F32 } represents the same type as { b : F32, a: U32 }.

16.12. Types with Numeric Members

A type has numeric members if it is one of the following:

16.13. Default Values

Every type T with a syntactic name in FPP has an associated default value. In generated C++ code, this is the value that is used to initialize a variable of type T when no other initializer is specified. Default values are important, because they ensure that in generated code, every variable is initialized when it is created.

  • The default value associated with each primitive numeric type is zero.

  • The default value associated with bool is false.

  • The default value associated with any string type is the empty string.

  • The default value associated with an array type T is (1) the default value specified in the array definition, if one is given; otherwise (2) the unique value of type T that has the default value of the member type of T at each member. See the section on array definitions for examples.

  • The default value associated with an enum type is (1) the default value specified in the enum definition, if one is given; otherwise (2) the first enumerated constant appearing in the enum definition.

  • The default value associated with a struct type T is (1) the default value specified in the struct definition, if one is given; otherwise (2) the unique value of type T that has the default value of the member type \$T_i\$ for each member \$m_i\$ : \$T_i\$ of T. See the section on struct definitions for examples.

  • The default value associated with an abstract type T is the single value associated with T. This value is left abstract in the FPP model; the implementation of T must provide a concrete value.

17. Type Checking

In this section, we explain the rules used to assign types to expressions. FPP is a statically typed language. That means the following:

  • Type checking of expressions occurs during analysis.

  • If the type checking phase detects a violation of any of these rules, then translation halts with an error message and does not produce any code.

Each type represents a collection of values. The type checking rules exist to ensure that whenever an expression of type \$T\$ is evaluated, the result is a value of type \$T\$.

17.1. Integer Literals

To type an integer literal expression, the semantic analyzer does the following:

  1. Evaluate the expression to an unsigned integer value \$v\$.

  2. Assign the type Integer to the expression.

Examples:

constant a = 0 # Type is Integer
constant b = 1 # Type is Integer
constant c = 256 # Type is Integer
constant d = 65536 # Type is Integer
constant e = 0x100000000 # Type is Integer

17.2. Floating-Point Literals

The type of a floating-point literal expression is F64.

17.3. Boolean Literals

The type of a boolean literal expression is bool.

17.4. String Literals

The type of a string literal expression is string.

17.5. Identifier Expressions

To type an identifier expression \$e\$, the semantic analyzer resolves the identifier to a definition and uses the type given in the definition.

Example:

constant a = 42 # a is a constant with type Integer
constant b = a # The expression a refers to the constant a and has type Integer

17.6. Dot Expressions

To type a dot expression \$e\$ .x, the semantic analyzer does the following:

  1. If \$e\$.x is a qualified identifier that represents the use of a definition according to the rules for resolving qualified identifiers, and the use is a valid dot expression, then use the type given in the definition.

  2. Otherwise \$e\$.x is invalid. Throw an error.

Example:

module M {
  constant a = 0 # The constant M.a has type Integer
}
constant b = M.a # Expression M.a represents a use of the definition M.a.
                 # Its type is Integer.

17.7. Array Expressions

To type an array expression [ \$e_1\$ , \$...\$ , \$e_n\$ ], the semantic analyzer does the following:

  1. For each \$i in [1,n\$], compute the type \$T_i\$ of \$e_i\$.

  2. Compute the common type \$T\$ of the list of types \$T_i\$.

  3. Assign the type [ \$n\$ ] \$T\$ to the expression.

Examples:

constant a = [ 1, 2, 3 ] # Type is [3] Integer
constant b = [ 1, 2, 3.0 ] # Type is [3] F64

17.8. Struct Expressions

To type a struct expression { \$m_1\$ = \$e_1\$ , \$...\$ , \$m_n\$ = \$e_n\$ }, the semantic analyzer does the following:

  1. Check that the member names \$m_i\$ are distinct.

  2. For each \$i in [1,n\$], compute the type \$T_i\$ of \$e_i\$.

  3. Assign the type { \$m_1\$ = \$T_1\$ , \$...\$ , \$m_n\$ = \$T_n\$ } to the expression.

Examples:

constant a = { x = 1, y = 2.0 } # Type is { x: Integer, y: F64 }
constant b = { x = 1, x = 2 } # Error

17.9. Unary Negation Expressions

To type a unary negation expression - \$e\$, the semantic analyzer does the following:

  1. Compute the type \$T\$ of \$e\$.

  2. If \$T\$ is a numeric type, then use \$T\$.

  3. Otherwise if \$T\$ may be converted to Integer, then use Integer.

  4. Otherwise throw an error.

Examples:

constant a = -1.0 # Type is F64
constant b = -1 # Type is Integer
constant c = -c # Type is Integer
constant d = -0xFFFFFFFF # Type is Integer
enum E { X = 1 }
constant e = -E.X # Type is Integer
constant f = -true # Error

17.10. Binary Arithmetic Expressions

To type a binary arithmetic expression \$e_1\$ op \$e_2\$, the semantic analyzer does the following:

  1. Compute the common type \$T\$ of \$e_1\$ and \$e_2\$.

  2. If \$T\$ is a numeric type, then use \$T\$.

  3. Otherwise if \$T\$ may be converted to Integer, then use Integer.

  4. Otherwise throw an error.

Examples:

constant a = 1 + 2 # Type is Integer
constant b = 3 + 4 # Type is Integer
constant c = -0xFFFFFFFF # Type is Integer
enum E { X = 1, Y = 2 }
constant d = X + Y # Type is Integer
constant e = true + "abcd" # Error

17.11. Parenthesis Expressions

To type a parenthesis expression ( \$e\$ ), the semantic analyzer does the following:

  1. Compute the type \$T\$ of \$e\$.

  2. Use \$T\$ as the type of the expression.

Examples:

constant a = (1.0 + 2) # Type is F64
constant b = (3 + 4) # Type is Integer
constant c = (true) # Type is bool
constant d = ("abcd") # Type is string
constant e = ([ 1, 2, 3]) # Type is [3] Integer

17.12. Identical Types

We say that types \$T_1\$ and \$T_2\$ are identical if one of the following holds:

  1. \$T_1\$ and \$T_2\$ are numeric types with the same name, e.g., U32 or Integer. In each case of a numeric type, the name of the type uniquely identifies the type.

  2. \$T_1\$ and \$T_2\$ are both the Boolean type.

  3. \$T_1\$ and \$T_2\$ are both the same string type.

  4. Each of \$T_1\$ and \$T_2\$ is an array type, enum type, or struct type, and both types refer to the same definition.

17.13. Type Conversion

We say that a type \$T_1\$ may be converted to another type \$T_2\$ if every value represented by type \$T_1\$ can be converted into a value of type \$T_2\$.

Here are the rules for type conversion:

  1. \$T_1\$ may be converted to \$T_2\$ if \$T_1\$ and \$T_2\$ are identical types.

  2. Any string type may be converted to any other string type.

  3. Any numeric type may be converted to any other numeric type.

  4. An enum type may be converted to a numeric type.

  5. If \$T_1\$ or \$T_2\$ or both are array types, then \$T_1\$ may be converted to \$T_2\$ if the conversion is allowed after replacing the array type or types with structurally equivalent anonymous array types.

  6. An anonymous array type \$T_1 =\$ [ \$n\$ ] \$T'_1\$ may be converted to the anonymous array type \$T_2 =\$ [ \$m\$ ] \$T'_2\$ if \$n = m\$ and \$T'_1\$ may be converted to \$T'_2\$.

  7. A numeric type type, Boolean type, enum type, or string types \$T\$ may be converted to an anonymous array type \$T_1\$ may be converted to the member type of \$T_2\$.

  8. If \$T_1\$ or \$T_2\$ or both are struct types, then \$T_1\$ may be converted to \$T_2\$ if the conversion is allowed after replacing the struct type or types with structurally equivalent anonymous struct types. For purposes of structural equivalence, the member sizes of struct types are ignored. For example, the struct type S defined by struct S { x: [3] U32 } is structurally equivalent to the anonymous struct type { x: U32 }.

  9. An anonymous struct type \$T =\${ \$m_1\$ : \$T_1\$ , \$...\$ , \$m_1\$ : \$T_n\$ } may be converted to the anonymous struct type \$T'\$ if for each \$i in [1,n]\$,

    1. \$m_i\$ : \$T'_i\$ is a member of \$T'\$; and

    2. \$T_i\$ may be converted to \$T'_i\$.

  10. A numeric type type, Boolean type, enum type, or string type \$T\$ may be converted to an anonymous struct type \$T'\$ if for each member \$m_i\$ : \$T_i\$ of \$T'\$, \$T\$ may be converted to \$T_i\$.

  11. Type convertibility is transitive: if \$T_1\$ may be converted to \$T_2\$ and \$T_2\$ may be converted to \$T_3\$, then \$T_1\$ may be converted to \$T_3\$.

17.14. Computing a Common Type

17.14.1. Pairs of Types

Here are the rules for resolving two types \$T_1\$ and \$T_2\$ (e.g., the types of two subexpressions) to a common type \$T\$ (e.g., the type of the whole expression):

  1. If \$T_1\$ and \$T_2\$ are identical types, then let \$T\$ be \$T_1\$.

  2. Otherwise if \$T_1\$ and \$T_2\$ are both numeric types, then do the following:

    1. If \$T_1\$ and \$T_2\$ are both floating-point types, then use F64.

    2. Otherwise use Integer.

  3. Otherwise if \$T_1\$ and \$T_2\$ are both string types, then use string.

  4. Otherwise if \$T_1\$ or \$T_2\$ or both are enum types, then replace the enum type or types with the representation type specified in the enum definitions and and reapply these rules.

  5. Otherwise if \$T_1\$ or \$T_2\$ or both are array types, then replace the array type or types with structurally equivalent anonymous array types and reapply these rules.

  6. Otherwise if \$T_1\$ and \$T_2\$ are anonymous array types with the same size \$n\$ and with member types \$T'_1\$ and \$T'_2\$, then apply these rules to resolve \$T'_1\$ and \$T'_2\$ to \$T'\$ and let \$T\$ be [ \$n\$ ] \$T'\$.

  7. Otherwise if one of \$T_1\$ and \$T_2\$ is a type \$T'\$ that is convertible to an anonymous array type and the other one is an anonymous array type [ \$n\$ ] \$T''\$, then apply these rules to resolve \$T'\$ and \$T''\$ to a common type \$T'''\$. Let \$T\$ be the array type [ \$n\$ ] \$T'''\$.

  8. Otherwise if \$T_1\$ or \$T_2\$ or both are struct types, then replace the struct type or types with structurally equivalent anonymous struct types and reapply these rules.

  9. Otherwise if \$T_1\$ and \$T_2\$ are both anonymous struct types, then use the anonymous struct type \$T\$ with the following members:

    1. For each member \$m_1\$ : \$T'_1\$ of \$T_1\$, if \$T_2\$ has a member \$m_1\$ : \$T'_2\$, then apply these rules to convert \$T'_1\$ and \$T'_2\$ to a common type \$T'\$ and use \$m_1\$ : \$T'\$. Otherwise use \$m_1\$ : \$T'_1\$.

    2. For each member \$m_2\$ : \$T'_2\$ of \$T_2\$ such that \$T_1\$ has no member named \$m_2\$, use \$m_2\$ : \$T'_2\$.

  10. Otherwise if one of \$T_1\$ and \$T_2\$ is a type \$T'\$ that is convertible to an anonymous struct type and the other one is an anonymous struct type \$S\$, then apply these rules to resolve \$T'\$ and each of the struct member types to a common type. Let \$T\$ be the struct type whose member names are the member names of \$S\$ and whose member types are the corresponding common types.

  11. Otherwise the attempted resolution is invalid. Throw an error.

17.14.2. Lists of Types

To compute a common type for a list of types \$T_1, ... , T_n\$, do the following:

  1. Check that \$n > 0\$. If not, then throw an error.

  2. Compute the type \$T_1\$ of \$e_1\$.

  3. For each \$i in [2,n\$]

    1. Compute the type \$T\$ of \$e_i\$.

    2. Compute the common type \$T_i\$ of \$T_(i-1)\$ and \$T\$.

  4. Use \$T_n\$ as the common type of the list.

18. Values

A value is one of the following:

  • A primitive integer value

  • An integer value

  • A floating-point value

  • A Boolean value

  • A string value

  • An abstract type value

  • An anonymous array value

  • An array value

  • An enumeration value

  • An anonymous struct value

  • A struct value

Every value belongs to exactly one type.

18.1. Primitive Integer Values

A primitive integer value is an ordinary (mathematical) integer value together with a primitive integer type. Formally, the set of primitive integer values is the disjoint union over the integer types of the values represented by each type:

  • An unsigned integer type of width \$w\$ represents integers in the range \$[0, 2^w-1]\$. For example, U8 represents the integers \$[0, 255]\$.

  • A signed integer type of width \$w\$ represents integers in the range \$[-2^(w-1), 2^(w-1)-1]\$. For example, I8 represents the integers \$[-128, 127]\$.

We represent a primitive integer value as an expression followed by a colon and a type. For example, we write the value 1 at type U32 as 1: U32. The value 1: U32 is distinct from the value 1: U8.

18.2. Integer Values

An integer value is an ordinary (mathematical) integer value. It has type Integer. We represent an integer value as an integer number, with no explicit type. For example, 1 is an integer value.

18.3. Floating-Point Values

A floating-point value is an IEEE floating-point value of 4- or 8-byte width. Formally, the set of floating-point values is the disjoint union over the types F32 and F64 of the values represented by each type:

  • The type F32 represents all IEEE 4-byte floating-point values.

  • The type F64 represents all IEEE 8-byte floating-point values.

We write a floating-point values analogously to primitive integer values. For example, we write the value 1.0 at type F32 as 1.0: F32.

18.4. Boolean Values

A Boolean value is one of the values true and false. Its type is bool.

18.5. String Values

A string value is a sequence of characters that can be represented as a string literal expression. It is written in the same way as a string literal expression, e.g., "abc". Its type is string.

18.6. Abstract Type Values

An abstract type value is a value associated with an abstract type. There is one value associated with each abstract type \$T\$. We write the value value of type \$T\$.

18.7. Anonymous Array Values

An anonymous array value is a value associated with an anonymous array type. We write an anonymous array value similarly to an array expression: an anonymous array value has the form [ \$v_1\$ , \$...\$ , \$v_n\$ ], where for each \$i in [1,n\$], \$v_i\$ is a value of type \$T\$ for some \$T\$. The type of the value is [ \$n\$ ] \$T\$.

18.8. Array Values

An array value is a value associated with an array type. We write an array value like an anonymous array value, except that the value is annotated with an array type.

An array value has the form [ \$v_1\$ , \$...\$ , \$v_n\$ ] : \$Q\$, where

  1. \$Q\$ is a qualified identifier that refers to a array definition with member type \$T\$.

  2. For each \$i in [1,n\$], \$v_i\$ is a value of type \$T\$.

The type of the value is \$Q\$.

18.9. Enumeration Values

An enumeration value is a value associated with an enumerated constant definition. It is a pair consisting of the name and the integer value specified in the enumerated constant definition. Its type is the type associated with the enum definition in which the enumerated constant definition appears.

18.10. Anonymous Struct Values

An anonymous struct value is a value associated with an anonymous struct type. We write an anonymous struct value \$v\$ similarly to a struct expression: a struct value has the form { \$m_1\$ = \$v_1\$ , \$...\$ , \$m_n\$ = \$v_n\$ }, where for each \$i in [1,n\$], \$v_i\$ is a value of type \$T_i\$. The type of \$v\$ is { \$m_1\$ : \$T_1\$ , \$...\$ , \$m_n\$ : \$T_n\$ }.

18.11. Struct Values

A struct value is a value associated with a struct type. We write a struct value similarly to an anonymous struct value, except that we annotate the value with a struct type: a struct value has the form { \$m_1\$ : \$v_1\$ , \$...\$ , \$m_n\$ : \$v_n\$ } : \$Q\$, where

  1. \$Q\$ is a qualified identifier that refers to a struct definition.

  2. The members of \$Q\$ are \$m_i\$ : \$T_i\$ for \$i in [1,n]\$.

  3. For each \$i in [1,n\$], \$v_i\$ is a value of type \$T_i\$.

All the members must be explicitly assigned values.

18.12. Serialized Sizes

Every value v whose type has a syntactic representation in FPP has a serialized size. This is the number of bytes required to represent v in the standard F Prime serialized format. The serialized size s of a value v depends on the type T of v:

  • If T is a primitive numeric type, then s is the byte width of the type. For example, the serialized size of a value of type F64 is 8.

  • If T is bool, then s is 1.

  • If T is a string type, then s is the number of bytes used to represent the length of a string plus the length of the string in characters. The number of bytes used to represent the length of a string is implementation-specific.

  • If T is an array type, then s is sum of the serialized sizes of the elements of v.

  • If T is an enum type, then s is the byte width of the representation type of T.

  • If T is a struct type, then s the sum of the serialized sizes of the members of v

  • If T is an abstract type, then s is not specified in FPP. It is up to the implementer of T to provide the serialized size.

19. Evaluation

Evaluation is the process of transforming an expression into a value. In FPP, all evaluation happens during analysis, in resolving expressions to compile-time constant values.

19.1. Evaluating Expressions

Evaluation of expressions occurs as stated in the expression descriptions. Evaluation of integer expressions occurs at type Integer, using enough bits to represent the result without overflow. Evaluation of floating-point expressions occurs using 64-bit floating-point arithmetic.

19.2. Type Conversion

The following rules govern the conversion of a value \$v_1\$ of type \$T_1\$ to a value \$v_2\$ of type \$T_2\$.

19.2.1. Unsigned Primitive Integer Values

  1. If \$T_1\$ and \$T_2\$ are both unsigned primitive integer types and \$T_2\$ is narrower than \$T_1\$, then construct \$v_2\$ by truncating the unsigned binary representation of \$v_1\$ to the width of \$v_2\$. For example, converting 0x1234: U16 to U8 yields 0x34: U8.

  2. Otherwise if \$T_1\$ and \$T_2\$ are both unsigned primitive integer types, then \$v_2\$ is the integer value of \$v_1\$ at the type of \$v_2\$. For example, converting 0x12: U8 to U16 yields 0x12: U16.

19.2.2. Signed Primitive Integer Values

  1. If \$T_1\$ and \$T_2\$ are both signed primitive integer types and \$T_2\$ is narrower than \$T_1\$, then construct \$v_2\$ by truncating the two’s complement binary representation of \$v_1\$ to the width of \$v_2\$. For example, converting -0x1234: I16 to I8 yields -0x34: I8.

  2. Otherwise if \$T_1\$ and \$T_2\$ are both signed primitive integer types, then \$v_2\$ is the integer value of \$v_1\$ at the type of \$v_2\$. For example, converting -0x12: I8 to I16 yields -0x12: I16.

19.2.3. Primitive Integer Values of Mixed Sign

If \$T_1\$ and \$T_2\$ are primitive integer types with one signed and one unsigned, then do the following:

  1. Construct the value \$v\$ by converting \$v_1\$ to the type \$T\$, where \$T\$ is signed if \$T_1\$ is signed and unsigned if \$T_1\$ is unsigned, and \$T\$ has the same width as \$T_2\$.

  2. Construct \$v_2\$ by converting \$v\$ to \$T_2\$.

For example converting -1: I8 to U16 yields 0xFFFF: U16

19.2.4. Primitive and Non-Primitive Integer Values

If \$T_1\$ is Integer and \$T_2\$ is a primitive integer type, then proceed as if \$T_1\$ were a signed primitive integer type of the narrowest bit width that will hold \$v_1\$. For example, converting -0x1234 to I8 yields -0x34: I8.

If \$T_1\$ is a primitive integer type and \$T_2\$ is Integer, then \$v_2\$ is the integer value of \$v_1\$ at type Integer. For example, converting 0xFFFF: U32 to Integer yields 0xFFFF: Integer.

19.2.5. Floating-Point Values

We use the standard rules for IEEE floating-point values to convert among integer values to and from floating-point values and floating-point values to and from each other.

19.2.6. Array Values

If \$T_2\$ is an array type and \$T_1 = T_2\$, then let \$v_2 = v_1\$.

Otherwise if \$T_1\$ is an anonymous array type and \$T_2\$ is an anonymous array type or array type, both with \$n\$ elements, then

  1. Let \$T'_2\$ be the element type of \$T_2\$.

  2. For each \$i in [1,n]\$, \$v'_i\$ be the result of converting \$v_i\$ to type \$T'_2\$.

  3. Let \$v_2\$ be the unique array value of type \$T_2\$ with value \$v'_i\$ at each element.

Otherwise the conversion is not valid.

19.2.7. Structure Values

If \$T_2\$ is a struct type and \$T_1 = T_2\$, then let \$v_2 = v_1\$.

Otherwise if \$T_1\$ is an anonymous struct type and \$T_2\$ is an anonymous struct type or struct type such that for each member \$m\$ : \$v_m\$ of \$T_1\$ there is a member \$m\$ : \$T_m\$ in \$T_2\$, then use the value of \$T_2\$ with the following members:

  1. For each member \$m\$ : \$T_m\$ of \$T_2\$ such that there is a member \$m\$ : \$v_m\$ in \$v_1\$, add the member \$m\$ : \$v'_m\$, where \$v'_m\$ is the result of converting \$v_m\$ to \$T_m\$.

  2. For each member \$m\$ : \$T_m\$ of \$T_2\$ such that there is no member \$m\$ : \$v_m\$ in \$v_1\$, add the member \$m\$ : \$v'_m\$, where \$v'_m\$ is the default value at type \$T_m\$.

Otherwise the conversion is invalid.

20. Analysis and Translation

20.1. Analysis

Analysis is the process of checking a source model. It usually involves the following steps:

  1. Lexing and parsing to generate an abstract syntax tree (AST).

  2. If step (1) succeeded, semantic analysis of the AST.

20.2. Analysis Tools

An analysis tool is a tool that reads in and analyzes FPP source files, but does not generate any code. Source files for analysis are provided in one of two ways:

  1. Via command-line arguments; or

  2. On standard input, if there are no arguments.

For example, the command analyze file1.fpp file2.fpp says to read in the translation units file1.fpp and file2.fpp and perform analysis on the model consisting of these two translation units. The command cat file1.fpp file2.fpp | analyze is functionally equivalent.

20.3. Translation

Translation is the process of performing analysis and generating code.

Translation usually involves the following steps:

  1. Analysis.

  2. If step (1) succeeded, code generation.

FPP is intended to support a variety of translation strategies. For example, we need to generate (1) C++ code for FSW and (2) XML, Python, or other code to export to ground tools.

20.4. Translation Tools

A translation tool is a tool that translates FPP source files. A translation tool typically accepts the following two kinds of input:

  1. Source files to be translated.

  2. Source files that are imported for their symbol definitions, but are not translated.

For example, when translating a component C, a tool may read in files containing the definitions of the ports used in C, but not translate those files.

Source files for translation are provided as for analysis tools. Imported source files are specified as arguments to a -i flag.

For example, the command translate -i file1.fpp,file2.fpp file3.fpp says to import file1.fpp and file2.fpp and translate file3.fpp. The command translate -i file1.fpp,file2.fpp < file3.fpp is functionally equivalent.