-
Notifications
You must be signed in to change notification settings - Fork 4
Example 2. SignalFlow
So far we have demonstrated the declarations of data constructors, conformance constraints, model assertions and aliases in the first example. Now let’s move on and talk a bit about rules and querying models in this signal flow domain:
domain SignalFlow
{
Parameter ::= new (name: String, id: String, dataType: String, initValue: String, size: Integer).
ParamFlow ::= new (name: String, id: String, src: Parameter, dst: Parameter).
Processing ::= Primitive + Compound.
Primitive ::= new (name: String, id: String, function: String).
Compound ::= new (name: String, id: String).
ProcessingParent ::= new (child: Processing, parent: Compound).
Port ::= Input + Output.
Input ::= new (name: String, id: String, parent: Processing).
Output ::= new (name: String, id: String, parent: Processing).
// Flows and their parents
Flow ::= new (id: String, src: Port, dst: Port).
FlowParent ::= new (child: Flow, parent: Compound).
// Hardware
HardwareModel ::= new (name: String, id: String).
HWNode ::= new (name: String, id: String).
// Hardware connections
HWConn ::= new (id: String, src: HWNode, dst: HWNode).
ProcessingConn ::= new (id: String, src: Processing, dst: HWNode).
badFlow ::= (flow: Flow).
// A constraint to check if a Flow is legal
// A flow can only go between ports whose "grandparents" are the same element or with only one level's gap
// 1. Find a Flow
// 2. Find the src of the Flow and dst of the Flow
// 3. Get the parent of the parent of the src
// 4. Get the parent of the parent of the dst
// 5. Check that those two elements are the same or with only one level's gap
badFlow(f) :- f is Flow,
src = f.src, dst = f.dst,
p1 = src.parent, p2 = dst.parent,
ProcessingParent(p1, com1),
ProcessingParent(p2, com2),
com1 != com2,
no ProcessingParent(com1, com2),
no ProcessingParent(com2, com1).
conforms no badFlow(f).
}
Signal flows have primitive processes and compound processes. Primitive processes can implement elementary computations and compound processes can contain signals, primitives and/or other compound processes. The only constraint here is to check if the flow is legal. The illegal flows, or “Bad flows” are the ones that skipped more than one level in the signal flow hierarchy. To deal with this conditional statement, we write powerful domain constraints by the computed properties, which we call rules. A basic rule has the following syntax:
head :- body.
The body part can contain anything a query can contain. The head part is a sequence of constructor applications applied to constants and variables. A rule means: “Whenever the body is provable for some variable assignment, then the head is also provable for that variable assignment.” Thus, a rule is a logical statement, but it can also be executed (like a query) to grow the set of provable values. So if the parents of the parents of the input and output are not the same or parent-child relation, they must be bad flows with at least two level gap in the signal flow hierarchy.
Now let's look at an instance model of SignalFlow
:
model FMRadio of SignalFlow
{
//Primitives and Compounds
receiver is Compound("FM Receiver", "/00000").
speakers is Primitive("Speakers", "/00000/00000", "CollectAudio").
primitive is Primitive("Primitive", "/00000/00001", "Unknown").
localOscillator is Primitive("Local Oscllator", "/00000/00002", "Oscllator").
lowPassFilter is Primitive("LowPass Filter", "/00000/00003", "Filter").
gain is Primitive("Gain", "/00000/00004", "Unknown").
usrp is Primitive("USRP Source", "/00000/00005", "Source").
mixer is Compound("Mixer", "/00000/00006").
squelch is Compound("Squelch", "/00000/00007").
display is Compound("FFT Dispaly", "/00000/00008").
demod is Compound("WBFM Demod", "/00000/00009").
threshold is Primitive("Threshold", "/00000/00007/00000", "Threshold").
placeHolder is Primitive("PlaceHolder", "/00000/00009/00000", "PlaceHolder").
multiply is Primitive("Multiply", "/00000/00006/00000", "Multiply").
//Parent-child relations.
ProcessingParent(speakers, receiver).
ProcessingParent(primitive, receiver).
ProcessingParent(localOscillator, receiver).
ProcessingParent(lowPassFilter, receiver).
ProcessingParent(gain, receiver).
ProcessingParent(usrp, receiver).
ProcessingParent(mixer, receiver).
ProcessingParent(squelch, receiver).
ProcessingParent(display, receiver).
ProcessingParent(demod, receiver).
ProcessingParent(threshold, squelch).
ProcessingParent(placeHolder, demod).
ProcessingParent(multiply, mixer).
After giving all the primitives and compounds and specifying their parent-child relations, we are now able to depict the flows. The following signal flow graph and code block show the nine flows inside the receiver compound:
//An input in Primitive
input is Input("Input", "/00000/00001/00000", primitive).
// A flow from an output in Primitve to an input in Speakers
output is Output("Output", "/00000/00001/00001", primitive).
audio0 is Input("Audio", "/00000/00000/00000", speakers).
f0 is Flow("/00000/00010", output, audio0).
// A flow from an output in Gain to an input in Speakers
out0 is Output("Out", "/00000/00004/00000", gain).
f1 is Flow("/00000/00011", out0, audio0).
// A flow from an output in WBFM Demod to an input in Gain
audio1 is Output("Audio", "/00000/00009/00000", demod).
in0 is Input("In", "/00000/00004/00001", gain).
f2 is Flow("/00000/00012", audio1, in0).
// A flow from an output in Squelch to an input in WBFM Demod
out1 is Output("Out", "/00000/00007/00000", squelch).
if0 is Input("IF", "/00000/00009/00001", demod).
f3 is Flow("/00000/00013", out1, if0).
// A flow from an output in LowPass Filter to an input in Squelch
out2 is Output("Out", "/00000/00003/00000", lowPassFilter).
in1 is Input("In", "/00000/00007/00001", squelch).
f4 is Flow("/00000/00014", out2, in1).
// A flow from an output in LowPass Filter to an input in FFT Display
ch2 is Input("Ch2", "/00000/00008/00000", display).
f5 is Flow("/00000/00015", out2, ch2).
// A flow from an output in Mixer to an input in LowPass Filter
if1 is Output("IF", "/00000/00006/00000", mixer).
in2 is Input("In", "/00000/00003/00001", lowPassFilter).
f6 is Flow("/00000/00016", if1, in2).
// A flow from an output in USRP Source to an input in Mixer
bb is Output("BB", "/00000/00005/00000", usrp).
rf is Input("RF", "/00000/00006/00001", mixer).
f7 is Flow("/00000/00017", bb, rf).
// A flow from an output in USRP Source to an input in FFT Display
ch1 is Input("Ch1", "/00000/00008/00001", display).
f8 is Flow("/00000/00018", bb, ch1).
// A flow from an output in Local Oscillator to an input in Mixer
lo0 is Output("LO", "/00000/00002/00000", localOscillator).
lo1 is Input("LO", "/00000/00006/00002", mixer).
f9 is Flow("/00000/00019", lo0, lo1).
FlowParent(f0, receiver).
FlowParent(f1, receiver).
FlowParent(f2, receiver).
FlowParent(f3, receiver).
FlowParent(f4, receiver).
FlowParent(f5, receiver).
FlowParent(f6, receiver).
FlowParent(f7, receiver).
FlowParent(f8, receiver).
FlowParent(f9, receiver).
Then the rest of the flows lives in the three compounds inside the receiver:
// Flows inside Squelch
in3 is Input("In", "/00000/00007/00000/00000", threshold).
out3 is Output("Out", "/00000/00007/00000/00001", threshold).
f10 is Flow("/00000/00007/00010", in1, in3).
f11 is Flow("/00000/00007/00011", out3, out1).
FlowParent(f10, squelch).
FlowParent(f11, squelch).
// Flows inside Mixer
a is Input("a", "/00000/00006/00000/00000", multiply).
b is Input("b", "/00000/00006/00000/00001", multiply).
y is Output("y", "/00000/00006/00000/00002", multiply).
f12 is Flow("/00000/00006/00010", rf, a).
f13 is Flow("/00000/00006/00011", lo1, b).
f14 is Flow("/00000/00006/00012", y, if1).
FlowParent(f12, mixer).
FlowParent(f13, mixer).
FlowParent(f14, mixer).
// Flows inside WBFM Demod
in4 is Input("In", "/00000/00009/00000/00000", placeHolder).
out4 is Output("Out", "/00000/00009/00000/00001", placeHolder).
f15 is Flow("/00000/00009/00010", if0, in4).
f16 is Flow("/00000/00009/00011", out4, audio1).
FlowParent(f15, demod).
FlowParent(f16, demod).
Finally, the last part of the model gives all the parameters and parameter flows:
//Parameters
ifFreq0 is Parameter("IFFreq", "/00000/00020", "Integer", "0", 1).
ifFreq1 is Parameter("IF Frequency", "/00000/00002/00020", "Integer", "0", 1).
samplingRate0 is Parameter("SamplingRate", "/00000/00021", "Integer", "0", 1).
samplingRate1 is Parameter("SamplingRate", "/00000/00002/00021", "Integer", "0", 1).
samplingRate2 is Parameter("SamplingRate", "/00000/00008/00020", "Integer", "0", 1).
samplingRate3 is Parameter("SamplingRate", "/00000/00005/00020", "Integer", "0", 1).
centeFreq0 is Parameter("CenterFreq", "/00000/00022", "Integer", "0", 1).
centeFreq1 is Parameter("CenterFreq", "/00000/00005/00021", "Integer", "0", 1).
rxGain0 is Parameter("RxGain", "/00000/00023", "Integer", "0", 1).
rxGain1 is Parameter("RxGain", "/00000/00005/00022", "Integer", "0", 1).
cutOff0 is Parameter("CutOff", "/00000/00024", "Integer", "0", 1).
cutOff1 is Parameter("CutOff", "/00000/00003/00000", "Integer", "0", 1).
audioRate0 is Parameter("AudioRate", "/00000/00025", "Integer", "0", 1).
audioRate1 is Parameter("AudioRate", "/00000/00000/00020", "Integer", "0", 1).
audioRate2 is Parameter("AudioRate", "/00000/00009/00020", "Integer", "0", 1).
moduletionIndex0 is Parameter("ModuletionIndex", "/00000/00026", "Integer", "0", 1).
moduletionIndex1 is Parameter("ModuletionIndex", "/00000/00009/00021", "Integer", "0", 1).
gain0 is Parameter("Gain", "/00000/00027", "Integer", "0", 1).
gain1 is Parameter("Gain", "/00000/00004/00020", "Integer", "0", 1).
sqLevel is Parameter("SQLevel", "/00000/00028", "Integer", "0", 1).
level0 is Parameter("Level", "/00000/00007/00020", "Integer", "0", 1).
level1 is Parameter("Level", "/00000/00007/00000/000020", "Integer", "0", 1).
//Parameter Flows
ParamFlow("Level", "00000/00007/00030", level0, level1).
ParamFlow("IFFreq", "00000/00030", ifFreq0, ifFreq1).
ParamFlow("SamplingRate", "00000/00031", samplingRate0, samplingRate1).
ParamFlow("SamplingRate", "00000/00032", samplingRate0, samplingRate2).
ParamFlow("SamplingRate", "00000/00033", samplingRate0, samplingRate3).
ParamFlow("CenterFreq", "00000/00034", centeFreq0, centeFreq1).
ParamFlow("RxGain", "00000/00035", rxGain0, rxGain1).
ParamFlow("CutOff", "00000/00036", cutOff0, cutOff1).
ParamFlow("AudioRate", "00000/00037", audioRate0, audioRate1).
ParamFlow("AudioRate", "00000/00038", audioRate0, audioRate2).
ParamFlow("ModulationIndex", "00000/00039", moduletionIndex0, moduletionIndex1).
ParamFlow("Gain", "00000/00040", gain0, gain1).
ParamFlow("Level", "00000/00041", sqLevel, level0).
}
To understand how a model contains a set of assertions, create a file called SignalFlow.4ml and copy the code in this example. A query operation tests if a property is provable on a model. Follow these steps to test if this model contains a Compound “Mixer”:
[]> load SignalFlow.4ml
[]> query FMRadio Compound("Mixer", _)
[]> ls tasks
Id | Kind | Status | Result | Started | Duration |
---|---|---|---|---|---|
0 | Query | Done | true | 7/7/2021 10:34 PM | 0.15s |
The first command line loads and compiles the file SignalFlow.4ml. The second command line starts a query operation on the model FMRadio
and tests for the property Compound("Mixer", _)
. If Compound("Mixer", _)
is provable in FMRadio
then this query operation returns true; otherwise it returns false. Starting a query spawns a new background task that may take some time to complete. The Id of the newly created task is reported (e.g. Id 0). Line 7 causes the status of all tasks to be displayed. Line 11 shows that the query completed with the result true.
Model conformance can also be checked using query operations. Run the following query to see the model FMRadio
is conformed to its domain:
[]> query FMRadio SignalFlow.conforms
Id | Kind | Status | Result | Started | Duration |
---|---|---|---|---|---|
1 | Query | Done | true | 7/7/2021 10:42 PM | 0.12s |
The result is true so FMRadio
satisfies all the conformance constraints.