mirror of
https://github.com/nasa/fpp.git
synced 2026-04-28 17:53:27 -05:00
1819 lines
50 KiB
Plaintext
1819 lines
50 KiB
Plaintext
== Defining Topologies
|
|
|
|
In F Prime, a *topology* or connection graph is the
|
|
highest level of software architecture in a FSW application.
|
|
A topology specifies what
|
|
<<Defining-Component-Instances,component instances>>
|
|
are used in the application and how their
|
|
<<Defining-Components_Port-Instances,port instances>>
|
|
are connected.
|
|
|
|
An F Prime FSW application consists of a topology _T_;
|
|
all the types, ports, and components used by _T_;
|
|
and a small amount of top-level {cpp} code that you write by hand.
|
|
In the section on
|
|
<<Writing-C-Plus-Plus-Implementations_Implementing-Deployments,
|
|
implementing deployments>>, we will explain more about the top-level
|
|
{cpp} code.
|
|
In this section we explain how to define a topology in FPP.
|
|
|
|
=== A Simple Example
|
|
|
|
We begin with a simple example that shows how many of the pieces
|
|
fit together.
|
|
|
|
[source,fpp]
|
|
----
|
|
port P
|
|
|
|
passive component C {
|
|
sync input port pIn: P
|
|
output port pOut: P
|
|
}
|
|
|
|
instance c1: C base id 0x100
|
|
instance c2: C base id 0x200
|
|
|
|
@ A simple topology
|
|
topology Simple {
|
|
|
|
@ This specifier says that instance c1 is part of the topology
|
|
instance c1
|
|
@ This specifier says that instance c2 is part of the topology
|
|
instance c2
|
|
|
|
@ This code specifies a connection graph C1
|
|
connections C1 {
|
|
c1.pOut -> c2.pIn
|
|
}
|
|
|
|
@ This code specifies a connection graph C2
|
|
connections C2 {
|
|
c2.pOut -> c1.pIn
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
In this example, we define a <<Defining-Ports,port>> `P`.
|
|
Then we define a <<Defining-Components,passive component>> `C`
|
|
with an input port and an output port, both of type `P`.
|
|
We define two <<Defining-Component-Instances,instances>> of
|
|
`C`, `c1` and `c2`.
|
|
We put these instances into a topology called `Simple`.
|
|
|
|
As shown, to define a topology, you write the keyword `topology`,
|
|
the name of the topology, and the members of the topology
|
|
definition enclosed in curly braces.
|
|
In this case, the topology has two kinds of members:
|
|
|
|
. Two *instance specifiers* specifying that instances
|
|
`c1` and `c2` are part of the topology.
|
|
|
|
. Two *graph specifiers* that specify connection graphs
|
|
named `C1` and `C2`.
|
|
|
|
As shown, to write an instance specifier, you write the
|
|
keyword `instance` and the name of a component instance
|
|
definition.
|
|
In general the name may be a qualified name such as `A.B`.
|
|
if the instance is defined inside a
|
|
<<Defining-Modules,module>>; in this simple
|
|
example it is not.
|
|
Each instance specifier states that the instance it names
|
|
is part of the topology.
|
|
The instances appearing in the list must be distinct.
|
|
For example, this is not correct:
|
|
|
|
[source,fpp]
|
|
--------
|
|
topology T {
|
|
instance c1
|
|
instance c1 # Error: duplicate instance c1
|
|
}
|
|
--------
|
|
|
|
A graph specifier specifies one or more connections
|
|
between component instances.
|
|
Each graph specifier has a name.
|
|
By dividing the connections of a topology into named
|
|
graphs, you can organize the connections in a meaningful way.
|
|
For example you can have one graph group
|
|
for connections that send commands, another one
|
|
for connections that send telemetry, and so forth.
|
|
We will have more to say about this in a later section.
|
|
|
|
As shown, to write a graph specifier, you may write the keyword `connections`
|
|
followed by the name of the graph; then you may list
|
|
the connections inside curly braces.
|
|
(In the next section, we will explain another way to write a graph specifier.)
|
|
Each connection consists of an endpoint, an arrow `pass:[->]`,
|
|
and another endpoint.
|
|
An endpoint is the name of a component instance
|
|
(which in general may be a qualified name), a dot,
|
|
and the name of a port of that component instance.
|
|
|
|
In this example there are two connection graphs, each containing
|
|
one connection:
|
|
|
|
. A connection graph `C1` containing a connection from `c1.pOut` to `c2.pIn`.
|
|
|
|
. A connection graph `C2` containing a connection from `c2.pOut` to `c1.pIn`.
|
|
|
|
As shown, topologies and their members are
|
|
<<Writing-Comments-and-Annotations_Annotations,annotatable elements>>.
|
|
The topology members form an
|
|
<<Defining-Constants_Multiple-Definitions-and-Element-Sequences,
|
|
element sequence>> in which the optional
|
|
terminating punctuation is a semicolon.
|
|
|
|
=== Connection Graphs
|
|
|
|
In general, an FPP topology consists of a list of instances
|
|
and a set of named connection graphs.
|
|
There are two ways to specify connection graphs:
|
|
*direct graph specifiers* and *pattern graph specifiers*.
|
|
|
|
==== Direct Graph Specifiers
|
|
|
|
A direct graph specifier provides a name and a list
|
|
of connections.
|
|
We illustrated direct graph specifiers in the
|
|
previous section, where the simple topology example
|
|
included direct graph specifiers for graphs named
|
|
`C1` and `C2`.
|
|
Here are some more details about direct graph specifiers.
|
|
|
|
As shown in the previous section, each connection consists
|
|
of an output port specifier, followed by an arrow, followed
|
|
by an input port specifier.
|
|
For example:
|
|
|
|
[source,fpp]
|
|
--------
|
|
connections C {
|
|
a.p -> b.p
|
|
}
|
|
--------
|
|
|
|
Each of the two port specifiers consists of a component
|
|
instance name, followed by a dot, followed the name of a port instance.
|
|
The component instance name must refer to a
|
|
<<Defining-Component-Instances,component instance definition>>
|
|
and may be qualified by a module name.
|
|
For example:
|
|
|
|
[source,fpp]
|
|
--------
|
|
connections C {
|
|
M.a.p -> N.b.p
|
|
}
|
|
--------
|
|
|
|
Here component instance `a` is defined in module `M` and component
|
|
instance `b` is defined in module `N`.
|
|
In a port specifier `a.p`, the port instance name `p` must refer to a
|
|
<<Defining-Components_Port-Instances,port instance>> of the
|
|
component definition associated with the component instance `a`.
|
|
|
|
Each component instance named in a connection must be part of the
|
|
instance list in the topology.
|
|
For example, if you write a connection `a.b pass:[->] c.d` inside
|
|
a topology `T`, and the specifier `instance a` does not
|
|
appear inside topology `T`, then you will get an error --
|
|
even if `a` is a valid instance name for the FPP model.
|
|
The reason for this rule is that in flight code we need
|
|
to be very careful about which instances are included
|
|
in the application.
|
|
Naming all the instances also lets us check for
|
|
<<Analyzing-and-Translating-Models_Checking-Models,
|
|
unconnected ports>>.
|
|
|
|
You may use the same name in more than one direct
|
|
graph specifier in the same topology.
|
|
If you do this, then all specifiers with the same
|
|
name are combined into a single graph with that name.
|
|
For example, this code
|
|
|
|
[source,fpp]
|
|
--------
|
|
connections C {
|
|
a.p -> b.p
|
|
}
|
|
connections C {
|
|
c.p -> d.p
|
|
}
|
|
--------
|
|
|
|
is equivalent to this code:
|
|
|
|
[source,fpp]
|
|
--------
|
|
connections C {
|
|
a.p -> b.p
|
|
c.p -> d.p
|
|
}
|
|
--------
|
|
|
|
The members of a direct graph specifier form an
|
|
<<Defining-Constants_Multiple-Definitions-and-Element-Sequences,
|
|
element sequence>> in which the optional
|
|
terminating punctuation is a comma.
|
|
For example, you can write this:
|
|
|
|
[source,fpp]
|
|
--------
|
|
connections C { a.p -> b.p, c.p -> d.p }
|
|
--------
|
|
|
|
The connections appearing in direct graph specifiers must obey the
|
|
following rules:
|
|
|
|
* Each connection must go from an output port instance to
|
|
an input port instance.
|
|
|
|
* The types of the ports must match, except that a
|
|
<<Defining-Components_Port-Instances_Serial-Port-Instances,
|
|
serial port instance>> may be connected to a port of any
|
|
type.
|
|
In particular, serial to serial connections are allowed.
|
|
|
|
* If a typed port _P_ is connected to a serial port in either direction,
|
|
then the port type of _P_ may not specify a
|
|
<<Defining-Ports_Returning-Values,return type>>.
|
|
|
|
==== Pattern Graph Specifiers
|
|
|
|
A few connection patterns are so common in F Prime that they
|
|
get special treatment in FPP.
|
|
For example, an F Prime topology typically includes an
|
|
instance of the component `Svc.Time`.
|
|
This component has a port `timeGetPort`
|
|
of type `Fw.Time` that other components can use to get the system
|
|
time.
|
|
Any component that gets the system time
|
|
(and there are usually several) has a connection to
|
|
the `timeGetPort` port of the `Svc.Time` instance.
|
|
|
|
Suppose you are constructing a topology in which
|
|
(1) `sysTime` is an instance of `Svc.Time`; and (2)
|
|
each of the instances
|
|
`a`, `b`, `c`, etc., has a
|
|
<<Defining-Components_Special-Port-Instances_Time-Get-Ports, time get port>>
|
|
`timeGetOut` port connected to `sysTime.timeGetPort`,
|
|
If you used a direct graph specifier to write all these connections,
|
|
the result might look like this:
|
|
|
|
[source,fpp]
|
|
--------
|
|
connections Time {
|
|
a.timeGetOut -> sysTime.timeGetPort
|
|
b.timeGetOut -> sysTime.timeGetPort
|
|
c.timeGetOut -> sysTime.timeGetPort
|
|
...
|
|
}
|
|
--------
|
|
|
|
This works, but it is tedious and repetitive. So FPP provides
|
|
a better way: you can use a *pattern graph specifier*
|
|
to specify this common pattern.
|
|
You can write
|
|
|
|
[source,fpp]
|
|
--------
|
|
time connections instance sysTime
|
|
--------
|
|
|
|
This code says the following:
|
|
|
|
. Use the instance `sysTime` as the instance of `Fw.Time`
|
|
for the time connection pattern.
|
|
|
|
. Automatically construct a direct graph specifier named `Time`.
|
|
In this direct graph specifier, include one connection
|
|
from each component instance that has a time get port
|
|
to the input port of `sysTime` of type `Fw.Time`.
|
|
|
|
The result is as if you had written the direct graph specifier
|
|
yourself.
|
|
All the other rules for direct graph specifiers apply: for example,
|
|
if you write another direct graph specifier with name `Time`, then
|
|
the connections in that specifier are merged with the connections
|
|
generated by the pattern specifier.
|
|
|
|
In the example above, we call `time` the *kind* of the pattern
|
|
graph specifier.
|
|
We call `sysTime` the *source instance* of the pattern.
|
|
It is the source of all the time pattern connections
|
|
in the topology.
|
|
We call the instances that have time get ports (and so contribute
|
|
connections to the pattern) the *target instances*.
|
|
They are the instances targeted by the pattern once the source
|
|
instance is named.
|
|
|
|
Table <<pattern-graph-specifiers>> shows the pattern graph
|
|
specifiers allowed in FPP.
|
|
The columns of the table have the following meanings:
|
|
|
|
* *Kind:* The keyword or keywords denoting the kind.
|
|
When writing the specifier, these appear just before
|
|
the keyword `connections`, as shown above for the time example.
|
|
|
|
* *Source Instance:* The source instance for the pattern.
|
|
|
|
* *Target Instances:* The target instances for the pattern.
|
|
|
|
* *Graph Name:* The name of the connection graph
|
|
generated by the pattern.
|
|
|
|
* *Connections:* The connections generated by the pattern.
|
|
|
|
The command pattern specifier generates three connection graphs:
|
|
`Command`, `CommandRegistration`, and `CommandResponse`.
|
|
|
|
[[pattern-graph-specifiers]]
|
|
.Pattern Graph Specifiers
|
|
|===
|
|
|Kind|Source Instance|Target Instances|Graph Name|Connections
|
|
|
|
|
|
|
|
|
|
|
|
|
|`Command`
|
|
|All connections from the unique output port of type `Fw::Cmd`
|
|
of the source instance to the
|
|
<<Defining-Components_Special-Port-Instances_Command-Ports,
|
|
`command` `recv` port>>
|
|
of each target instance.
|
|
|
|
|`command`
|
|
|An instance of `Svc.CommandDispatcher` or a similar component for
|
|
dispatching commands.
|
|
The instance must have a unique output port of type `Fw.Cmd`,
|
|
a unique input port of type `Fw.CmdReg`, and a unique
|
|
input port of type `Fw.CmdResponse`.
|
|
|Each instance that has
|
|
<<Defining-Components_Special-Port-Instances_Command-Ports,
|
|
command ports>>.
|
|
|`CommandRegistration`
|
|
|All connections from the
|
|
<<Defining-Components_Special-Port-Instances_Command-Ports,
|
|
`command` `reg` port>> of each target instance to the
|
|
unique input port of type `Fw.CmdReg` of the source instance.
|
|
|
|
|
|
|
|
|
|
|
|
|
|`CommandResponse`
|
|
|All connections from the
|
|
<<Defining-Components_Special-Port-Instances_Command-Ports,
|
|
`command` `resp` port>> of each target instance to the
|
|
unique input port of type `Fw.CmdResponse` of the source instance.
|
|
|
|
|`event`
|
|
|An instance of `Svc.ActiveLogger` or a similar component for
|
|
logging event reports.
|
|
The instance must have a unique input port of type
|
|
`Fw.Log`.
|
|
|Each instance that has an
|
|
<<Defining-Components_Special-Port-Instances_Event-Ports,
|
|
`event` port>>.
|
|
|`Events`
|
|
|All connections from the
|
|
<<Defining-Components_Special-Port-Instances_Event-Ports,
|
|
`event` port>> of each target instance to the unique
|
|
input port of type `Fw.Log` of the source instance.
|
|
|
|
|`health`
|
|
|An instance of `Svc.Health` or a similar component for
|
|
health monitoring.
|
|
The instance must have a unique output port of type
|
|
`Svc.Ping` and a unique input port of type `Svc.Ping`.
|
|
|Each instance other than the source instance
|
|
that has a unique output port of type
|
|
`Svc.Ping` and a unique input port of type `Svc.Ping`.
|
|
|`Health`
|
|
|(1) All connections from the unique output port of type
|
|
`Svc.Ping` of each target instance to the unique input
|
|
port of type `Svc.Ping` of the source instance.
|
|
(2) All connections from the unique output port of type
|
|
`Svc.Ping` of the source instance to the unique
|
|
input port of type `Svc.Ping` of each target instance.
|
|
|
|
|`param`
|
|
|An instance of `Svc.PrmDb` or a similar component representing
|
|
a database of parameters.
|
|
The instance must have a unique input port of type `Fw.PrmGet`
|
|
and a unique input port of type `Fw.PrmSet`.
|
|
|Each instance that has
|
|
<<Defining-Components_Special-Port-Instances_Parameter-Ports,
|
|
parameter ports>>.
|
|
|`Parameters`
|
|
|(1) All connections from the
|
|
<<Defining-Components_Special-Port-Instances_Parameter-Ports,
|
|
`param` `get` port>> of each target instance
|
|
to the unique input port of type `Fw.PrmGet` of the source instance.
|
|
(2) All connections from the
|
|
<<Defining-Components_Special-Port-Instances_Parameter-Ports,
|
|
`param` `set` port>> of each target instance
|
|
to the unique input port of type `Fw.PrmSet` of the source instance.
|
|
|
|
|`telemetry`
|
|
|An instance of `Svc.TlmChan` or a similar component for
|
|
storing channelized telemetry.
|
|
The instance must have a unique input port of type `Fw.Tlm`.
|
|
|Each instance that has a <<Defining-Components_Special-Port-Instances_Telemetry-Ports,
|
|
telemetry port>>.
|
|
|`Telemetry`
|
|
|All connections from the
|
|
<<Defining-Components_Special-Port-Instances_Telemetry-Ports,
|
|
`telemetry` port>> of each target instance to the unique input
|
|
port of type `Fw.Tlm` of the source instance.
|
|
|
|
|`text` `event`
|
|
|An instance of `Svc.PassiveTextLogger` or a similar component
|
|
for logging event reports in textual form.
|
|
The instance must have a unique input port of type `Fw.LogText`.
|
|
|Each instance that has a <<Defining-Components_Special-Port-Instances_Event-Ports,
|
|
`text` `event` port>>.
|
|
|`TextEvents`
|
|
|All connections from the
|
|
<<Defining-Components_Special-Port-Instances_Event-Ports,
|
|
`text` `event` port>> of each target instance to the unique
|
|
input port of type `Fw.LogText` of the source instance.
|
|
|
|
|`time`
|
|
|An instance of `Svc.Time` or a similar component for providing
|
|
the system time.
|
|
The instance must have a unique input port of type `Fw.Time`.
|
|
|Each instance that has a
|
|
<<Defining-Components_Special-Port-Instances_Time-Get-Ports,
|
|
`time` `get` port>>.
|
|
|`Time`
|
|
|All connections from the
|
|
<<Defining-Components_Special-Port-Instances_Time-Get-Ports,
|
|
`time` `get` port>> of each target instance to the unique
|
|
input port of type `Fw.Time` of the source instance.
|
|
|
|
|===
|
|
|
|
Here are some rules for writing graph pattern specifiers:
|
|
|
|
. At most one occurrence of each pattern kind is allowed in
|
|
each topology.
|
|
|
|
. For each pattern, the required ports shown in the table
|
|
must exist and must be unambiguous.
|
|
For example, if you write a time pattern
|
|
+
|
|
[source,fpp]
|
|
--------
|
|
time connections instance sysTime
|
|
--------
|
|
+
|
|
then you will get an error if `sysTime` has no
|
|
input ports of type `Fw.Time`,
|
|
You will also get an error if `sysTime` has two or more
|
|
such ports.
|
|
|
|
The default behavior for a pattern is
|
|
to generate the connections for all target instances
|
|
as shown in the table.
|
|
If you wish, you may generate connections for a selected
|
|
set of target instances.
|
|
To do this, you write a list of target instances enclosed in
|
|
curly braces after the source instance.
|
|
For example, suppose a topology contains instances
|
|
`a`, `b`, and `c` each of which has an output port
|
|
that satisfies the time pattern.
|
|
And suppose that `sysTime` is an instance of `Svc.Time`.
|
|
Then if you write this pattern
|
|
|
|
[source,fpp]
|
|
--------
|
|
time connections instance sysTime
|
|
--------
|
|
|
|
you will get a connection graph `Time` containing
|
|
time connections from each of `a`, `b`, and `c` to `sysTime`.
|
|
But if you write this pattern
|
|
|
|
[source,fpp]
|
|
--------
|
|
time connections instance sysTime {
|
|
a
|
|
b
|
|
}
|
|
--------
|
|
|
|
then you will just get the connections from `a` and `b`
|
|
to `sysTime`.
|
|
The instances `a` and `b` must be valid target instances
|
|
for the pattern.
|
|
|
|
As with connections, you can write the instances `a` and `b`
|
|
each on its own line, or you can separate them with commas:
|
|
|
|
[source,fpp]
|
|
--------
|
|
time connections instance sysTime { a, b }
|
|
--------
|
|
|
|
=== Port Numbering
|
|
|
|
As discussed in the
|
|
<<Defining-Components_Port-Instances_Arrays-of-Port-Instances,
|
|
section on defining components>>,
|
|
each named port instance is actually an array of
|
|
one or more port instances.
|
|
When the size of the array exceeds one, you
|
|
must specify the port number (i.e., the array index)
|
|
of each connection going into or out of the port instance.
|
|
In FPP, there are three ways to specify port numbers:
|
|
explicit numbering, matched numbering, and general numbering.
|
|
|
|
==== Explicit Numbering
|
|
|
|
To use explicit numbering, you provide an explicit port number
|
|
for a connection endpoint.
|
|
You write the port number as a
|
|
<<Defining-Constants_Expressions,numeric expression>>
|
|
in square brackets, immediately following the port name.
|
|
The port numbers start at zero.
|
|
|
|
For example, the `RateGroups` graph of the Ref (reference) topology in the F Prime
|
|
repository defines the rate group connections.
|
|
It contains the following connection:
|
|
|
|
[source,fpp]
|
|
--------
|
|
rateGroupDriverComp.CycleOut[Ports.RateGroups.rateGroup1] -> rateGroup1Comp.CycleIn
|
|
rateGroup1Comp.RateGroupMemberOut[0] -> SG1.schedIn
|
|
rateGroup1Comp.RateGroupMemberOut[1] -> SG2.schedIn
|
|
rateGroup1Comp.RateGroupMemberOut[2] -> chanTlm.Run
|
|
rateGroup1Comp.RateGroupMemberOut[3] -> fileDownlink.Run
|
|
--------
|
|
|
|
The first line says to connect the port at index
|
|
`Ports.RateGroups.rateGroup1` of `rateGroupDriverComp.CycleOut`
|
|
to `rateGroup1Comp.CycleIn`.
|
|
The symbol `Ports.RateGroups.rateGroup1` is an enumerated constant, defined
|
|
like this:
|
|
|
|
[source,fpp]
|
|
----
|
|
module Ports {
|
|
|
|
enum RateGroups {
|
|
rateGroup1
|
|
rateGroup2
|
|
rateGroup3
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
The second and following lines say to connect the ports of
|
|
`rateGroup1Comp.RateGroupMemberOut` at the indices 0, 1, 2, and 3
|
|
in the manner shown.
|
|
|
|
As another example, the `Downlink` graph of the reference topology
|
|
contains the following connection:
|
|
|
|
[source,fpp]
|
|
--------
|
|
downlink.framedAllocate -> staticMemory.bufferAllocate[Ports.StaticMemory.downlink]
|
|
--------
|
|
|
|
This line says to connect `downlink.framedAllocate` to
|
|
`staticMemory.bufferAllocate` at index
|
|
`Port.StaticMemory.downlink`.
|
|
Again the port index is a symbolic constant.
|
|
|
|
If you wish, you may write two explicit port numbers,
|
|
one at each endpoint.
|
|
For example:
|
|
|
|
[source,fpp]
|
|
--------
|
|
a.b[0] -> c.d[1]
|
|
--------
|
|
|
|
Here are some rules to keep in mind when using explicit numbering:
|
|
|
|
. You can write any numeric expression as a port number.
|
|
Each port number must be in bounds for the port (greater than
|
|
or equal to zero and less than the size of the port array).
|
|
If you write a port number that is out of bounds, you will get an error.
|
|
|
|
. Use symbolic constants judiciously.
|
|
Avoid scattering "magic" literal
|
|
constants throughout the topology definition.
|
|
For example:
|
|
|
|
.. The Ref topology uses the symbolic constants
|
|
`Ports.RateGroups.rateGroup1` and `Ports.StaticMemory.downlink`, as shown
|
|
above.
|
|
Because these constants appear in several different places, it is
|
|
better to use symbolic constants here.
|
|
Using literal constants would decrease readability and increase
|
|
the chance of using incorrect or inconsistent numbers.
|
|
|
|
.. The Ref topology uses the literal constants 0, 1, 2, and 3
|
|
to connect the ports of `rateGroup1Comp.RateGroupMemberOut`.
|
|
Here there are no obvious names to associate with the numbers,
|
|
the numbers go in sequence, and all the numbers appear together in one place.
|
|
So there is no clear benefit to giving them names.
|
|
|
|
. Remember that in F Prime, multiple connections can go to the same
|
|
input port, but only one connection can go from each output port.
|
|
For example, this code is allowed:
|
|
+
|
|
[source,fpp]
|
|
--------
|
|
c1.p1 -> c2.p[0]
|
|
c1.p2 -> c2.p[0] # OK: Two connections into c2.p[0]
|
|
--------
|
|
+
|
|
But this code is incorrect:
|
|
+
|
|
[source,fpp]
|
|
--------
|
|
c1.p[0] -> c2.p1
|
|
c1.p[0] -> c2.p2 # Error: Two connections out of c1.p[0]
|
|
--------
|
|
|
|
. Use explicit numbering as little as possible.
|
|
Instead, use matched numbering or general numbering
|
|
(described in the next sections) and let FPP
|
|
do the numbering for you.
|
|
In particular, avoid writing zero indices such as
|
|
`c.p[0]` except in cases where you need to control the assignment
|
|
of numbers, such as in the rate group example shown above.
|
|
In other cases, write `c.p` and let FPP infer
|
|
the zero index.
|
|
For example, this is what we did in the section on
|
|
<<Defining-Topologies_Connection-Graphs_Direct-Graph-Specifiers,
|
|
direct graph specifiers>>.
|
|
|
|
==== Matched Numbering
|
|
|
|
*Automatic matching:*
|
|
After resolving
|
|
<<Defining-Topologies_Port-Numbering_Explicit-Numbering,
|
|
explicit numbering>>, the FPP translator applies
|
|
*matched numbering*.
|
|
In this step, the translator numbers all pairs of
|
|
<<Defining-Components_Matched-Ports,matched ports>>.
|
|
|
|
Matched numbering is essential for resolving the command and health
|
|
<<Defining-Topologies_Connection-Graphs_Pattern-Graph-Specifiers,
|
|
patterns>>, each of which has matched ports.
|
|
You can also use matched numbering in conjunction with direct
|
|
graph specifiers.
|
|
For example, the Ref topology contains the following connections:
|
|
|
|
[source,fpp]
|
|
--------
|
|
connections Sequencer {
|
|
cmdSeq.comCmdOut -> cmdDisp.seqCmdBuff
|
|
cmdDisp.seqCmdStatus -> cmdSeq.cmdResponseIn
|
|
}
|
|
|
|
connections Uplink {
|
|
...
|
|
uplink.comOut -> cmdDisp.seqCmdBuff
|
|
cmdDisp.seqCmdStatus -> uplink.cmdResponseIn
|
|
...
|
|
}
|
|
--------
|
|
|
|
The port `cmdDisp.seqCmdBuff` port of the command dispatcher receives
|
|
command input from the command sequencer or from the ground.
|
|
The corresponding command response goes out on
|
|
port `cmdDisp.seqCmdStatus`.
|
|
These two ports are matched in the definition of the Command
|
|
Sequencer component.
|
|
|
|
When you use matched numbering with direct graph specifiers, you
|
|
must obey the following rules:
|
|
|
|
. When a component has the matching specifier
|
|
`match p1 with p2`, for every connection between `p1`
|
|
and another component, there must be a corresponding
|
|
connection between that other component and `p2`.
|
|
|
|
. You can use explicit numbering, and the automatic matching
|
|
will work around the numbers you supply if it can.
|
|
However, you may not do this in a way that makes the matching impossible.
|
|
For example, you may not connect `p1[0]` to another component
|
|
and `p2[1]` to the same component, because this connection
|
|
forces a mismatch.
|
|
|
|
. Duplicate connections at the same port number of `p1` or
|
|
`p2` are not allowed, even if `p1` or `p2` are input ports.
|
|
|
|
If you violate these rules, you will get an error during
|
|
analysis.
|
|
You can relax these rules by writing unmatched connections,
|
|
as described below.
|
|
|
|
*Unmatched connections:*
|
|
Occasionally you may need to relax the rules for using matched ports.
|
|
For example, you may need to match pairs of connections that use
|
|
the F Prime hub pattern to cross a network boundary.
|
|
In this case, although the connections are logically matched
|
|
at the endpoints, they all go through a single hub instance
|
|
on the side of the boundary that has the matched ports, and so they do not obey the
|
|
simple rules for matching given here.
|
|
|
|
When a connection goes to or from a matched port,
|
|
we say that it is *match constrained*.
|
|
Ordinarily a match constrained connection must obey the
|
|
rules for matching stated above.
|
|
To relax the rules, you can write an *unmatched* connection.
|
|
To do this, write the keyword `unmatched` at the start of the connection
|
|
specifier.
|
|
Here is an example:
|
|
|
|
[source,fpp]
|
|
--------
|
|
Port P
|
|
|
|
passive component Source {
|
|
sync input port pIn: [2] P
|
|
output port pOut: [2] P
|
|
|
|
match pOut with pIn
|
|
}
|
|
|
|
passive component Target {
|
|
sync input port pIn: [2] P
|
|
output port pOut: [2] P
|
|
}
|
|
|
|
instance source: Source base id 0x100
|
|
instance target: Target base id 0x200
|
|
|
|
topology T {
|
|
|
|
instance source
|
|
instance target
|
|
|
|
connections C {
|
|
unmatched source.pOut[0] -> target.pIn[0]
|
|
unmatched target.pOut[0] -> source.pIn[0]
|
|
unmatched source.pOut[1] -> target.pIn[1]
|
|
unmatched target.pOut[1] -> source.pIn[1]
|
|
}
|
|
|
|
}
|
|
--------
|
|
|
|
In this example, there are two pairs of connections between the
|
|
`pIn` and `pOut` connections of the instances `source` and `target`.
|
|
The ports of `source` are match constrained, so ordinarily
|
|
the connections would need to obey the matching rules.
|
|
The connections do partially obey the rules: for example,
|
|
there are no duplicate numbers, and the numbers match.
|
|
However, both pairs of connections go to and from the same
|
|
instance `target`; ordinarily this is not allowed for
|
|
match constrained connections.
|
|
To allow it, we need to use unmatched ports as shown.
|
|
|
|
Note the following about using unmatched ports:
|
|
|
|
. When connections are marked `unmatched`, the analyzer cannot check that the
|
|
port numbers assigned to the connections conform to any particular pattern.
|
|
If you need the port numbers to follow a pattern, as in the example shown
|
|
above, then you must use explicit numbering.
|
|
For a suggestion on how to do this, see the discussion of manual matching
|
|
below.
|
|
|
|
. Unmatched ports must still obey the rule that distinct
|
|
connections at a matched port must have distinct port numbers.
|
|
|
|
. The `unmatched` keyword is allowed only for connections that
|
|
are match constrained, i.e., that go to or from a matched port.
|
|
If you try to write an unmatched connection and the connection
|
|
is not match constrained, then you will get an error.
|
|
|
|
*Manual matching:*
|
|
Port matching specifiers work well when each matched pair of connections
|
|
goes between the same two components, one of which
|
|
has a matched pair of ports.
|
|
If the matching does not follow this pattern, then automatic matched
|
|
numbering will not work, and it is usually better not to
|
|
use a port matching specifier at all.
|
|
Instead, you can use explicit port numbers to express the matching.
|
|
For example, the Ref topology contains these connections:
|
|
|
|
[source,fpp]
|
|
--------
|
|
comm.allocate -> staticMemory.bufferAllocate[Ports.StaticMemory.uplink]
|
|
comm.$recv -> uplink.framedIn
|
|
uplink.framedDeallocate -> staticMemory.bufferDeallocate[Ports.StaticMemory.uplink]
|
|
--------
|
|
|
|
In this case the `staticMemory` instance requires that pairs of
|
|
allocation and deallocation requests for the same memory
|
|
go to the same port.
|
|
But the allocation request comes from `comm`,
|
|
and the deallocation request comes from `uplink`.
|
|
Since the allocation and deallocation connections go to different
|
|
component instances, we can't used automatic matched numbering.
|
|
Instead we define a symbolic constant `Ports.StaticMemory.uplink`
|
|
and use that twice to do the matching by hand.
|
|
|
|
==== General Numbering
|
|
|
|
After resolving
|
|
<<Defining-Topologies_Port-Numbering_Explicit-Numbering,
|
|
explicit numbering>> and
|
|
<<Defining-Topologies_Port-Numbering_Matched-Numbering,
|
|
matched numbering>>,
|
|
the FPP translator applies
|
|
*general numbering*.
|
|
In this step, the translator uses the following algorithm to
|
|
fill in any remaining unassigned
|
|
port numbers:
|
|
|
|
. Traverse the connections in a deterministic order.
|
|
The order is fully described in _The FPP Language Specification_.
|
|
|
|
. For each connection
|
|
|
|
.. If the output port number is unassigned, then set it to the
|
|
lowest available port number.
|
|
|
|
.. If the input port number is unassigned, then set it to zero.
|
|
|
|
For example, consider the following connections:
|
|
|
|
[source,fpp]
|
|
--------
|
|
a.p -> b.p
|
|
a.p -> c.p
|
|
--------
|
|
|
|
After general numbering, the connections could be numbered
|
|
as follows:
|
|
|
|
[source,fpp]
|
|
--------
|
|
a.p[0] -> b.p[0]
|
|
a.p[1] -> c.p[0]
|
|
--------
|
|
|
|
=== Importing Topologies
|
|
|
|
It is often useful to decompose a flight software project
|
|
into several topologies.
|
|
For example, a project might have the following topologies:
|
|
|
|
. A topology for command and data handling (CDH) with
|
|
components such as a command dispatcher, an event logger, a telemetry data
|
|
base,
|
|
a parameter database, and components for managing files.
|
|
|
|
. Various subsystem topologies, for example power, thermal,
|
|
attitude control, etc.
|
|
|
|
. A release topology.
|
|
|
|
Each of the subsystem topologies might include the CDH topology.
|
|
The release topology might include the CDH topology
|
|
and each of the subsystem topologies.
|
|
Further, to enable modular testing, it is useful for
|
|
each topology to be able to run on its own.
|
|
|
|
In FPP, the way we accomplish these goals is to *import*
|
|
one topology into another one.
|
|
In this section of the User Guide, we explain how to do that.
|
|
|
|
==== Importing Instances and Connections
|
|
|
|
To import a topology `A` into a topology `B`, you write
|
|
`import A` inside topology `B`, like this:
|
|
|
|
[source,fpp]
|
|
--------
|
|
topology B {
|
|
|
|
import A
|
|
|
|
...
|
|
|
|
}
|
|
--------
|
|
|
|
You may add instances and connections as usual to `B`, as shown
|
|
by the dots.
|
|
|
|
When you do this, the FPP translator does the following:
|
|
|
|
. *Resolve `A`:* Resolve all pattern graph
|
|
specifiers in `A`, and resolve all explicit port numbers in `A`.
|
|
Call the resulting topology `T`.
|
|
|
|
. *Form the instances of `B`:*
|
|
Take the union of the instances specified in `T` and
|
|
the instances specified in `B`, counting any duplicates once.
|
|
These are the instances of `B`.
|
|
|
|
. *Form the connections of `B`:*
|
|
Take the union of the connection graphs specified in `T` and
|
|
the connection graphs specified in `B`.
|
|
If each of `T` and `B` has a connection between the same
|
|
ports, then each becomes a separate connection in `B`.
|
|
|
|
. *Resolve `B`:* Resolve the pattern graph specifies of `B`.
|
|
Apply matched numbering and general numbering to `B`.
|
|
|
|
For example, suppose topologies `A` and `B` are defined
|
|
as follows:
|
|
|
|
[source,fpp]
|
|
--------
|
|
topology A {
|
|
|
|
instance a
|
|
instance b
|
|
|
|
connections C1 {
|
|
a.p1 -> b.p
|
|
}
|
|
|
|
}
|
|
|
|
topology B {
|
|
|
|
import A
|
|
|
|
instance c
|
|
|
|
connections C1 {
|
|
a.p1 -> c.p
|
|
}
|
|
|
|
connections C2 {
|
|
a.p2 -> c.p
|
|
}
|
|
|
|
}
|
|
--------
|
|
|
|
After import resolution, `B` is equivalent to this topology:
|
|
|
|
[source,fpp]
|
|
--------
|
|
topology B {
|
|
|
|
instance a
|
|
instance b
|
|
instance c
|
|
|
|
connections C1 {
|
|
a.p1 -> b.p
|
|
a.p1 -> c.p
|
|
}
|
|
|
|
connections C2 {
|
|
a.p2 -> c.p
|
|
}
|
|
|
|
}
|
|
--------
|
|
|
|
Notice that the `C1` connections of `A` are merged with the `C1`
|
|
connections of `B`.
|
|
|
|
==== Multiple Imports
|
|
|
|
Multiple imports are allowed.
|
|
For example:
|
|
|
|
[source,fpp]
|
|
--------
|
|
topology A {
|
|
|
|
import B
|
|
import C
|
|
|
|
...
|
|
|
|
}
|
|
--------
|
|
|
|
This has the obvious meaning: both topology `B` and
|
|
topology `C` are imported into topology `A`, according
|
|
to the rules described above.
|
|
|
|
Each topology may appear at most once in the import list.
|
|
For example, this is incorrect:
|
|
|
|
[source,fpp]
|
|
--------
|
|
topology A {
|
|
|
|
import B
|
|
import B # Error: B imported twice
|
|
|
|
}
|
|
--------
|
|
|
|
==== Transitive Imports
|
|
|
|
In general, transitive imports are allowed.
|
|
For example, topology `A` may import topology `B`,
|
|
and topology `B` may import topology `C`.
|
|
Resolution works bottom-up on the import graph:
|
|
for example, first we resolve `C`, and then we resolve `B`,
|
|
and then we resolve `A`.
|
|
|
|
Cycles in the import graph are not allowed.
|
|
For example, if `A` imports `B` and `B` imports `C`
|
|
and `C` imports `A`, you will get an error.
|
|
|
|
=== Topology Ports
|
|
|
|
In the previous section, we explained the most basic way to import a topology
|
|
`B` into a topology `A`.
|
|
This method of importing topologies erases or flattens the structure implied
|
|
by the separate topologies `A` and `B`.
|
|
In particular, when `B` is imported into `A`,
|
|
|
|
* All component instances that are part of `B` (directly or by import) are visible in `A`.
|
|
|
|
* Connections that span `A` and `B` go directly between
|
|
component instances that are part of `A` and component instances that are
|
|
part of `B`.
|
|
|
|
This approach is simple and flexible.
|
|
It also aligns well with the {cpp} implementation, which is flattened in this
|
|
way.
|
|
However, when specifying connections in the model,
|
|
it is often useful to preserve the separation between
|
|
an importing topology `A` and an imported topology `B`.
|
|
To do that, FPP has a feature called *topology ports*,
|
|
which we explain in this section.
|
|
|
|
With topology ports, the code generator still flattens the
|
|
hierarchy of imported topologies into a single topology, as
|
|
described in the previous section.
|
|
However, before this flattening occurs, you can specify
|
|
connections _to and from subtopologies_.
|
|
For example, you can specify a connection from a component instance
|
|
`a` of `A` to a _port_ of topology `B`, and this connection is automatically
|
|
flattened to a connection between `a` and a component instance `b` of `B`.
|
|
In this way, you can think of imported topologies as
|
|
units of function that have their own interfaces and connections.
|
|
|
|
==== Specifying Topology Ports
|
|
|
|
*Connections to subtopologies:*
|
|
Consider the following example:
|
|
|
|
[source,fpp]
|
|
--------
|
|
topology A {
|
|
|
|
instance a
|
|
|
|
import B
|
|
|
|
connections C {
|
|
a.p -> b.p
|
|
}
|
|
|
|
}
|
|
|
|
topology B {
|
|
|
|
instance b
|
|
|
|
}
|
|
--------
|
|
|
|
As discussed in the previous section, import resolution produces this
|
|
flattened topology:
|
|
|
|
[source,fpp]
|
|
--------
|
|
topology A {
|
|
|
|
instance a
|
|
instance b
|
|
|
|
connections C {
|
|
a.p -> b.p
|
|
}
|
|
|
|
}
|
|
--------
|
|
|
|
Notice that in both the original topology and the flattened topology,
|
|
the connection goes directly from `a.p` to `b.p`.
|
|
|
|
Now let us rewrite this example using topology ports.
|
|
First, we need a way to specify a port of topology `B` and to map it to
|
|
a port of an instance of `B`, so the flattening can occur.
|
|
To do that, we write `port` followed by a name, and equals sign,
|
|
and a port of a component instance, like this:
|
|
|
|
[source,fpp]
|
|
--------
|
|
topology B {
|
|
|
|
instance b
|
|
|
|
port p = b.p
|
|
|
|
}
|
|
--------
|
|
|
|
Here the FPP analyzer checks that `b.p` is a port of instance `b`;
|
|
if not, an error will occur.
|
|
It is also an error to specify multiple ports with
|
|
the same name in the same topology.
|
|
|
|
Now in topology `A`, we can write a connection to port `B.p` instead
|
|
of directly to component instance `b.p`, like this:
|
|
|
|
[source,fpp]
|
|
--------
|
|
topology A {
|
|
|
|
instance a
|
|
|
|
import B
|
|
|
|
connections C {
|
|
a.p -> B.p
|
|
}
|
|
|
|
}
|
|
--------
|
|
|
|
As usual, the FPP analyzer checks that `a.p` is an output port,
|
|
that `B.p` is a input port, and that the types of the ports
|
|
are compatible.
|
|
Here `B.p` has the direction and type of `b.p`, the actual port to which the
|
|
topology port `B.p` is mapped.
|
|
If these requirements aren't met, then an error occurs.
|
|
|
|
After import resolution, the result is the same as before:
|
|
`A` has two instances `a` and `b` and one connection
|
|
from `a.p` to `b.p`.
|
|
However, the source model respects the hierarchical structure
|
|
of `A` and `B`:
|
|
the connection goes to the interface
|
|
of `B`, instead of going directly to an instance of `B`.
|
|
|
|
*Connections from subtopologies:*
|
|
Connections from subtopologies work in the same way,
|
|
with the arrow reversed.
|
|
For example, if we assume that `B.p` is an output port
|
|
and `a.p` is an input port, then we could write this:
|
|
|
|
[source,fpp]
|
|
--------
|
|
topology A {
|
|
|
|
instance a
|
|
|
|
import B
|
|
|
|
connections C {
|
|
B.p -> a.p
|
|
}
|
|
|
|
}
|
|
--------
|
|
|
|
*Connections between subtopologies:*
|
|
Another useful pattern is to have a topology `A` import
|
|
two subtopologies `B1` and `B2` and to specify a connection
|
|
from `B1` to `B2`.
|
|
Here is an example:
|
|
|
|
[source,fpp]
|
|
--------
|
|
topology B1 {
|
|
|
|
instance b1
|
|
|
|
port p = b1.p
|
|
|
|
}
|
|
|
|
topology B2 {
|
|
|
|
instance b2
|
|
|
|
port p = b2.p
|
|
|
|
}
|
|
|
|
topology A {
|
|
|
|
import B1
|
|
import B2
|
|
|
|
connections C {
|
|
B1.p -> B2.p
|
|
}
|
|
|
|
}
|
|
--------
|
|
|
|
After flattening, topology `A` looks like this:
|
|
|
|
[source,fpp]
|
|
--------
|
|
topology A {
|
|
|
|
instance b1
|
|
instance b2
|
|
|
|
connections {
|
|
b1.p -> b2.p
|
|
}
|
|
|
|
}
|
|
--------
|
|
|
|
==== Implementing Port Interfaces
|
|
|
|
A topology is like a component instance, in that it provides
|
|
a port interface.
|
|
We will have more to say about this similarity in the
|
|
<<Defining-Topologies_Topology-Ports_Port-Interface-Instances,
|
|
next section>>.
|
|
|
|
In the case of an instance of a component _C_, we can
|
|
<<Defining-and-Using-Port-Interfaces_Defining-Port-Interfaces,
|
|
define a port interface _I_>> and make _I_ part of the
|
|
interface of _C_.
|
|
We do that by
|
|
<<Defining-and-Using-Port-Interfaces_Using-Port-Interfaces-in-Component-Definitions,
|
|
importing>> _I_ into the definition of _C_.
|
|
In the case of a topology _T_, we can't import an interface _I_ in this way,
|
|
because we have to specify a mapping for each of the ports
|
|
in the interface of _T_ to a port of a component instance or an imported
|
|
topology of _T_.
|
|
However, we can use one or more port interface definitions to specify and check
|
|
that the actual interface of _T_ provides all the ports that it should.
|
|
To do this, we write the keyword `implements` followed by list of names
|
|
that refer to port interface definitions.
|
|
The FPP analyzer will then check that the topology provides
|
|
all the ports required by each of the interfaces in the definitions.
|
|
|
|
Here is an example:
|
|
|
|
[source,fpp]
|
|
----
|
|
port P
|
|
|
|
interface I {
|
|
sync input port p: P
|
|
}
|
|
|
|
passive component C {
|
|
import I
|
|
}
|
|
|
|
instance a: C base id 0x100
|
|
|
|
@ Specify and check that topology T provides the ports of interface I
|
|
topology T implements I {
|
|
|
|
instance a
|
|
|
|
port p = a.p
|
|
|
|
}
|
|
----
|
|
|
|
This code is correct because topology `T` provides the port `p` of interface `I`,
|
|
via the line `port p = a.p`.
|
|
An error would result in each of the following cases:
|
|
|
|
* We delete `port p = a.p` from the model.
|
|
In this case, the `implements` clause is violated, because
|
|
topology `T` provides no port `p`.
|
|
|
|
* We remove `import I` from the definition of `C` and replace it with `output port p: P`.
|
|
In this case, topology `T` does provide a port `p`, but the port does
|
|
not conform to the interface `I`.
|
|
|
|
Try these examples in `fpp-check`, and make sure you understand what is
|
|
happening in each case.
|
|
|
|
You can also write more than one interface name in an `implements` clause.
|
|
In general the interface names form an
|
|
<<Defining-Constants_Multiple-Definitions-and-Element-Sequences,element
|
|
sequence>>
|
|
in which the optional terminating punctuation is a comma.
|
|
For example:
|
|
|
|
[source,fpp]
|
|
----
|
|
port P
|
|
|
|
port Q
|
|
|
|
interface I {
|
|
sync input port p: P
|
|
}
|
|
|
|
interface J {
|
|
output port q: Q
|
|
}
|
|
|
|
passive component C {
|
|
import I
|
|
import J
|
|
}
|
|
|
|
instance a: C base id 0x100
|
|
|
|
@ Specify and check that topology T provides the ports of interfaces I and J
|
|
topology T implements I, J {
|
|
|
|
instance a
|
|
|
|
port p = a.p
|
|
|
|
port q = a.q
|
|
|
|
}
|
|
----
|
|
|
|
Implements clauses are not strictly required: any correct model
|
|
containing an implements clause will also be correct if
|
|
that clause is deleted.
|
|
However, implements clauses are useful for documenting and checking intended
|
|
behavior.
|
|
For example, suppose the intent of topology `T` in the previous example
|
|
is to provide interfaces `I` and `J`.
|
|
If there were no `implements` clause,
|
|
then a developer could inadvertently change the contract of `T` by removing or
|
|
modifying ports `p` or `q` of `T`.
|
|
Because of the implements clause, this can't happen.
|
|
Instead an error will occur,
|
|
alerting the developer to the fact that an interface contract is
|
|
being changed.
|
|
|
|
Finally, an implements clause does not require that a topology implement
|
|
_only_ what is in the clause.
|
|
For example, in the code shown above, we could add any number
|
|
of ports to `T` that are not specified in the interfaces `I` and `J`.
|
|
The only requirement is that all the ports specified in `I` and `J`
|
|
are present in the interface.
|
|
|
|
==== Port Interface Instances
|
|
|
|
As noted in the previous section, both topologies and component
|
|
instances provide port interfaces.
|
|
Therefore we refer to both topologies and component instances as *port
|
|
interface instances*.
|
|
The motivation for this name is as follows:
|
|
|
|
* A port interface instance is a concrete implementation (an instance)
|
|
of a unit of function that has a port interface.
|
|
|
|
* The units of function with port interfaces are component
|
|
instances and topologies.
|
|
|
|
Thus the concept of a port interface instance generalizes
|
|
the concept of a component instance in a way that includes
|
|
the concept of a topology.
|
|
|
|
The concept of a port interface instance will become more obviously useful
|
|
in a future version of FPP, where we will introduce the following
|
|
features:
|
|
|
|
* *Module templates* that you can expand several times to generate
|
|
different sections of of FPP code (for example, different topologies) that are
|
|
largely the same, but that differ in particular ways that you specify.
|
|
|
|
* *Template parameters* that let you bind concrete values to parameters
|
|
when instantiating templates.
|
|
|
|
When instantiating templates, it will be useful to treat component
|
|
instances and topologies in the same way, so that a single `instance`
|
|
parameter can be bound to either a component instance or a topology,
|
|
as long as the constraints of the parameter are satisfied.
|
|
|
|
For now, we note that the concept of a port interface instance exists.
|
|
We also note that imported topologies are port interface instances.
|
|
For this reason, you can use the keyword `instance` to import a topology.
|
|
For example, instead of writing `import A` to import topology `A`,
|
|
you can write `instance A`.
|
|
Here is one of the FPP models from
|
|
<<Defining-Topologies_Topology-Ports_Specifying-Topology-Ports,the section on
|
|
specifying topology ports>>,
|
|
written using `instance` instead of `import`:
|
|
|
|
[source,fpp]
|
|
--------
|
|
topology B {
|
|
|
|
instance b
|
|
|
|
port p = b.p
|
|
|
|
}
|
|
|
|
topology A {
|
|
|
|
instance a
|
|
|
|
# Here we write instance B to import topology B into A
|
|
# Previously we wrote import B
|
|
# Both are valid syntax as of FPP v3.2.0
|
|
instance B
|
|
|
|
connections C {
|
|
a.p -> B.p
|
|
}
|
|
|
|
}
|
|
--------
|
|
|
|
Notice how this syntax emphasizes that both the component instance `a`
|
|
and the imported topology `B` provide port interfaces
|
|
that can participate in connections.
|
|
|
|
The concept of a port interface instance is new as of v3.2.0 of FPP.
|
|
In previous versions, there were no topology ports, so topologies
|
|
were not port interface instances.
|
|
Further, the only way to import a topology was to
|
|
use the `import` keyword, as discussed in the previous sections.
|
|
For backwards compatibility, we do not require you to use `instance`
|
|
when importing a topology; you may still use `import`.
|
|
However, it may be useful to use `instance` in cases where you specify
|
|
connections to and from the ports of an imported topology `T`, without
|
|
specifying any connections directly to or from the instances
|
|
of `T`.
|
|
In this case, you may wish to think of `T` as a pure port interface,
|
|
with the details of its implementation (its component instances,
|
|
imported subtopologies, and connections) abstracted away.
|
|
|
|
=== Include Specifiers
|
|
|
|
You can include code from another file in a topology definition.
|
|
You do this by writing an *include specifier*.
|
|
Here is a simple example, in which the body of a topology
|
|
definition is specified in a file `T.fppi` and then included
|
|
in a topology definition:
|
|
|
|
[source,fpp]
|
|
--------
|
|
topology T {
|
|
|
|
include "T.fppi"
|
|
|
|
}
|
|
--------
|
|
|
|
We will explain more about this feature in the section on
|
|
<<Specifying-Models-as-Files_Include-Specifiers,include specifiers>>
|
|
below.
|
|
|
|
=== Telemetry Packets
|
|
|
|
When defining a topology, you may (but are not required to)
|
|
specify how the <<Defining-Components_Telemetry,telemetry channels>>
|
|
defined in the topology are grouped into **telemetry packets**.
|
|
If you do this, then you can use an F Prime component called
|
|
the Telemetry Packetizer to construct telemetry packets and transmit
|
|
them to the ground.
|
|
This approach is usually more space efficient than transmitting
|
|
individual channels.
|
|
In this section we explain how to specify telemetry packets in FPP.
|
|
|
|
==== Telemetry Packet Sets
|
|
|
|
To group the channels of a topology _T_ into packets, you write a
|
|
**telemetry packet set** as part of the definition of _T_.
|
|
Here is a simple example showing a complete topology with a
|
|
telemetry packet set:
|
|
|
|
[source,fpp]
|
|
----
|
|
passive component C {
|
|
|
|
telemetry port tlmOut
|
|
|
|
time get port timeGetOut
|
|
|
|
telemetry T1: U32
|
|
|
|
telemetry T2: F32
|
|
|
|
}
|
|
|
|
instance c1: C base id 0x100
|
|
instance c2: C base id 0x200
|
|
|
|
topology T {
|
|
|
|
instance c1
|
|
instance c2
|
|
|
|
@ Telemetry packet set PacketSet
|
|
telemetry packets PacketSet {
|
|
|
|
@ Packet P1
|
|
packet P1 group 0 {
|
|
c1.T1
|
|
c2.T1
|
|
c1.T1
|
|
}
|
|
|
|
@ Packet P2
|
|
packet P2 group 1 {
|
|
c1.T2
|
|
c2.T2
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
In this example, we have defined a passive component `C` with two telemetry
|
|
channels, `T1` and `T2`.
|
|
We have defined two instances of `C`, `c1` and `c2`, so that there
|
|
are four telemetry channels overall: `c1.T1`, `c1.T2`, `c2.T1`, and `c2.T2`.
|
|
We have defined a topology `T` that uses the instances `c1` and `c2`
|
|
and defines a telemetry packet set `PacketSet`.
|
|
(In a realistic topology, there would be other instances and connections,
|
|
for example the telemetry packetizer instance and the ground interface
|
|
component. Here we have omitted these instances
|
|
and connections to focus on illustrating the FPP features.)
|
|
|
|
Notice the following about this example:
|
|
|
|
. To write a telemetry packet set, you write the keywords `telemetry` `packets`
|
|
followed by the name of the packet set, here `PacketSet`.
|
|
Then you write the body of the telemetry packet set inside curly braces
|
|
`{ ... }`.
|
|
|
|
. In the body of the packet set, you specify packets.
|
|
The packet specifiers are
|
|
<<Writing-Comments-and-Annotations_Annotations,annotatable elements>>.
|
|
The body of the packet set is an
|
|
<<Defining-Constants_Multiple-Definitions-and-Element-Sequences,
|
|
element sequence>> in which the optional
|
|
terminating punctuation is a comma.
|
|
|
|
. Each packet specifier consists of the keyword `packet`, the packet
|
|
name, the keyword `group`, a group identifier, and the body of the packet.
|
|
Within a telemetry packet set, the packet names must be distinct.
|
|
The group identifiers must be numeric values.
|
|
In particular they can be
|
|
<<Defining-Constants_Writing-a-Constant-Definition,defined constants>>,
|
|
<<Defining-Enums,enumerated constants>>, or
|
|
<<Defining-Constants_Expressions_Value-Arithmetic-Expressions,
|
|
arithmetic expressions>>.
|
|
The Telemetry Packetizer uses the group identifiers to select
|
|
which packets get transmitted.
|
|
For example, it is possible to transmit packets from group 0 but not group 1.
|
|
More than one packet may have the same group identifier.
|
|
|
|
. Inside each packet, you specify telemetry channels
|
|
enclosed in curly braces `{ ... }`.
|
|
The channel specifiers are
|
|
<<Writing-Comments-and-Annotations_Annotations,annotatable elements>>.
|
|
The body of the packet is an
|
|
<<Defining-Constants_Multiple-Definitions-and-Element-Sequences,
|
|
element sequence>> in which the optional
|
|
terminating punctuation is a comma.
|
|
|
|
When the packets are serialized and transmitted, each packet with name
|
|
_P_ contains data from
|
|
the channels listed in the specification for _P_, in the order that
|
|
the channels are specified.
|
|
As shown for packet `P1`, a single channel may appear more than
|
|
once in a packet; that means that data from that channel appears at
|
|
two or more offsets in that packet.
|
|
|
|
The form of a telemetry channel specifier
|
|
is an instance name followed by a dot and a channel name.
|
|
The instance name must refer to an instance of the topology.
|
|
The channel name must refer to a channel of that instance.
|
|
For example, in the model above, `c1.T` is a valid channel
|
|
for topology `T` because `c1` is an instance of `T`,
|
|
`c1` has type `C`, and `T` is a channel of `C`.
|
|
|
|
The instance name may be a qualified name containing a dot,
|
|
as discussed in
|
|
the section on <<Defining-Modules,defining modules>>.
|
|
For example, if we had written
|
|
|
|
[source,fpp]
|
|
--------
|
|
module M {
|
|
|
|
instance c1: C base id 0x100
|
|
|
|
}
|
|
--------
|
|
|
|
instead of
|
|
|
|
[source,fpp]
|
|
--------
|
|
instance c1: C base id 0x100
|
|
--------
|
|
|
|
to define instance `c1` in the model shown above,
|
|
then in the topology `T` we would write
|
|
`instance M.c1` instead of `instance c`, and we would write
|
|
channel `M.c1.T` instead of channel `c1.T`.
|
|
|
|
==== Telemetry Packet Identifiers
|
|
|
|
Within a telemetry packet set, every packet has a unique numeric identifier.
|
|
Typically the identifiers start at zero and count up by one.
|
|
If you don't specify an identifier, as in the example shown in the previous
|
|
section, then FPP assigns a default identifier: zero for the first packet in the set;
|
|
otherwise one more than the identifier of the previous packet.
|
|
|
|
If you wish, you may explicitly specify one or more packet identifiers. To do
|
|
this, you write the keyword `id` followed by a numeric expression immediately
|
|
after the packet name.
|
|
For example, in the model shown in the previous section, we
|
|
could have written
|
|
|
|
[source,fpp]
|
|
--------
|
|
@ Packet P1
|
|
packet P1 id 0 group 0 {
|
|
c1.T1
|
|
c2.T1
|
|
c1.T1
|
|
}
|
|
|
|
@ Packet P2
|
|
packet P2 id 1 group 1 {
|
|
c1.T2
|
|
c2.T2
|
|
}
|
|
--------
|
|
|
|
for the packet specifiers, and this would have equivalent behavior.
|
|
|
|
==== Omitting Channels
|
|
|
|
By default, every telemetry channel in the topology must appear
|
|
in at least one telemetry packet.
|
|
For example, in the model shown above, if we comment out the definition
|
|
of packet `P2` and run the result through `fpp-check`, then an error will result,
|
|
because neither channel
|
|
`c1.T2` nor channel `c2.T2` appears in any packet.
|
|
The purpose of this behavior is to ensure that you don't omit
|
|
any channels from the telemetry by mistake.
|
|
|
|
If you do want to omit channels from the telemetry packets,
|
|
then you can do so explicitly by writing the keyword `omit` and listing
|
|
those channels after the main specification of the packet set.
|
|
Here is an example:
|
|
|
|
[source,fpp]
|
|
----
|
|
passive component C {
|
|
|
|
telemetry port tlmOut
|
|
|
|
time get port timeGetOut
|
|
|
|
telemetry T1: U32
|
|
|
|
telemetry T2: F32
|
|
|
|
}
|
|
|
|
instance c1: C base id 0x100
|
|
instance c2: C base id 0x200
|
|
|
|
topology T {
|
|
|
|
instance c1
|
|
instance c2
|
|
|
|
@ Telemetry packet set PacketSet
|
|
telemetry packets PacketSet {
|
|
|
|
@ Packet P1
|
|
packet P1 group 0 {
|
|
c1.T1
|
|
c2.T1
|
|
c1.T1
|
|
}
|
|
|
|
} omit {
|
|
c1.T2
|
|
c2.T2
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
The form of the list of omitted channels is the same as the
|
|
form of the list of channels in a telemetry packet.
|
|
It is an error for any channel to appear in a telemetry
|
|
packet and also to appear in the omitted list.
|
|
|
|
==== Specifying Multiple Telemetry Packet Sets
|
|
|
|
You may specify more than one telemetry packet set in a topology.
|
|
Each telemetry packet set must have a distinct name.
|
|
For example:
|
|
|
|
[source,fpp]
|
|
----
|
|
passive component C {
|
|
|
|
telemetry port tlmOut
|
|
|
|
time get port timeGetOut
|
|
|
|
telemetry T: U32
|
|
|
|
}
|
|
|
|
instance c1: C base id 0x100
|
|
instance c2: C base id 0x200
|
|
|
|
topology T {
|
|
|
|
instance c1
|
|
instance c2
|
|
|
|
@ Telemetry packet set PacketSet1
|
|
telemetry packets PacketSet1 {
|
|
|
|
@ Packet P
|
|
packet P1 group 0 {
|
|
c1.T
|
|
}
|
|
|
|
} omit {
|
|
c2.T
|
|
}
|
|
|
|
@ Telemetry packet set PacketSet2
|
|
telemetry packets PacketSet2 {
|
|
|
|
@ Packet P
|
|
packet P1 group 0 {
|
|
c2.T
|
|
}
|
|
|
|
} omit {
|
|
c1.T
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
When you run the F Prime ground data system, you can tell it which
|
|
telemetry packet set to use to decode the packets.
|
|
|
|
==== Include Specifiers
|
|
|
|
When writing a telemetry packet set, you can
|
|
<<Defining-Topologies_Include-Specifiers,include code
|
|
from another file>> in the following places:
|
|
|
|
. You can include code inside a packet set specifier.
|
|
The code must contain packet specifiers.
|
|
|
|
. You can include code inside a packet specifier.
|
|
The code must list telemetry channels.
|
|
|
|
For example:
|
|
|
|
[source,fpp]
|
|
--------
|
|
telemetry packets PacketSet {
|
|
|
|
@ Include some packets
|
|
include "packets.fppi"
|
|
|
|
packet P group 0 {
|
|
c1.T1
|
|
# Include some channels
|
|
include "channels.fppi"
|
|
}
|
|
|
|
}
|
|
--------
|
|
|
|
The files that you include can themselves include code
|
|
from other files, if you wish.
|