The following figure shows an application supporting two channels of signal data, where one is split into two channels of lower bandwidth while the other must continue to run undisturbed. This type of dynamic reconfiguration is common in wireless applications.
In the figure, the first channel processes LTE20 data unchanged, while the middle channel is dynamically split into two LTE10 channels. The control parameters marked as carrier configuration RTP are used to split the data processing on a block boundary. When the middle channel is operating as an LTE20 channel, the 11-tap half-band kernel is bypassed. However, when the bandwidth of the middle channel is split between itself and the third channel forming two LTE10 channels, both of them need a 3-stage filter chain before the data can be mixed together. This is achieved by switching the 11-tap half-band filter back into the flow and reconfiguring the mixer to handle three streams of data instead of two.
The top-level input graph specification for the above application is shown in the following code.
class lte_reconfig : public graph {
private:
kernel demux;
kernel cf[3];
kernel interp0[3];
kernel interp1[2];
bypass bphb11;
kernel delay ;
kernel delay_byp ;
bypass bpdelay ;
kernel mixer ;
public:
input_port in;
input_port fromPS;
output_port out ;
lte_reconfig() {
// demux also handles the control
demux = kernel::create(demultiplexor);
connect<>(in, demux.in[0]);
connect< parameter >(fromPS, demux.in[1]);
runtime<ratio>(demux) = 0.1;
source(demux) = "kernels/demux.cc";
// instantiate all channel kernels
for (int i=0;i<3;i++) {
cf[i] = kernel::create(fir_89t_sym);
source(cf[i]) = "kernels/fir_89t_sym.cc";
runtime<ratio>(cf[i]) = 0.12;
}
for (int i=0;i<3;i++) {
interp0[i] = kernel::create(fir_23t_sym_hb_2i);
source(interp0[i]) = "kernels/hb23_2i.cc";
runtime<ratio>(interp0[i]) = 0.1;
}
for (int i=0;i<2;i++) {
interp1[i] = kernel::create(fir_11t_sym_hb_2i);
source(interp1[i]) = "kernels/hb11_2i.cc";
runtime<ratio>(interp1[i]) = 0.1;
}
bphb11 = bypass::create(interp1[0]);
mixer = kernel::create(mixer_dynamic);
source(mixer) = "kernels/mixer_dynamic.cc";
runtime<ratio>(mixer) = 0.4;
delay = kernel::create(sample_delay);
source(delay) = "kernels/delay.cc";
runtime<ratio>(delay) = 0.1;
delay_byp = kernel::create(sample_delay);
source(delay_byp) = "kernels/delay.cc";
runtime<ratio>(delay_byp) = 0.1;
bpdelay = bypass::create(delay_byp) ;
// Graph connections
for (int i=0; i<3; i++) {
connect<>(demux.out[i], cf[i].in[0]);
connect< parameter >(demux.inout[i], cf[i].in[1]);
}
connect< parameter >(demux.inout[3], bphb11.bp);
connect< parameter >(demux.inout[5], negate(bpdelay.bp)) ;
for (int i=0;i<3;i++) {
connect<>(cf[i].out[0], interp0[i].in[0]);
connect< parameter >(cf[i].inout[0], interp0[i].in[1]);
}
// chan0 is LTE20 and is output right away
connect<>(interp0[0].out[0], delay.in[0]);
connect<>(delay.out[0], mixer.in[0]);
// chan1 is LTE20/10 and uses bypass
connect<>(interp0[1].out[0], bphb11.in[0]);
connect< parameter >(interp0[1].inout[0], bphb11.in[1]);
connect<>(bphb11.out[0], bpdelay.in[0]);
connect<>(bpdelay.out[0], mixer.in[1]);
// chan2 is LTE10 always
connect<>(interp0[2].out[0], interp1[1].in[0]);
connect< parameter >(interp0[2].inout[0], interp1[1].in[1]);
connect<>(interp1[1].out[0], mixer.in[2]);
//Mixer
connect< parameter >(demux.inout[4], mixer.in[3]);
connect<>(mixer.out[0], out);
};
};
The bypass specification is coded as a special
encapsulator over the kernel to be bypassed. The port signature of the bypass matches the port
signature of the kernel that it encapsulates. It also receives a runtime parameter to control
the bypass: 0
for no bypass and 1
for bypass. The control can also be inverted by using the negate function as
shown.
The bypass parameter port of this graph is an ordinary scalar runtime parameter and can be driven by another kernel or by the Arm® processor using the interactive or scripted mechanisms described in Runtime Parameter Update/Read Mechanisms. This can also be connected hierarchically by embedding it into an enclosing graph.