Zola2023-08-08T00:00:00+00:00https://www.futuresdr.org/atom.xmlFutureSDR MobiCom Demo Accepted2023-08-08T00:00:00+00:002023-08-08T00:00:00+00:00https://www.futuresdr.org/blog/mobicom-demo/<p>Our <a href="https://www.futuresdr.org/">FutureSDR</a> + <a href="https://git.esa.informatik.tu-darmstadt.de/ipec/ipec">IPEC</a> demo is accepted at <a href="https://sigmobile.org/mobicom/2023/">ACM MobiCom 2023</a>. Yay!</p>
<p>In the demo, we show the same FutureSDR receiver running on three very different platforms:</p>
<ul>
<li>a normal laptop, interfacing an Aaronia Spectran v6 SDR</li>
<li>a web browser, compiled to WebAssembly and interfacing a HackRF SDR through WebUSB</li>
<li>an AMD/Xilinx RFSoC ZCU111 evaluation board</li>
</ul>
<p>On the ZCU111, the same decoder is implemented both in software (using FutureSDR) and in hardware (using IPEC).
Since both implementations have the same structure, we can configure during runtime after which decoding stage to switch from FPGA to CPU processing.</p>
<p>With regard to FutureSDR, this highlights two important features:</p>
<ul>
<li>We show the portability of FutureSDR, having the same receiver running on three very different platforms.</li>
<li>We show that the software implementation is capable of offloading different parts of the decoding dynamically during runtime.</li>
</ul>
<p>Please checkout the paper for further information or visit our booth at the conference.</p>
<ol class="pub-list">
<li><a href="http://dx.doi.org/10.1145/3570361.3614084"><img src="https://www.bastibl.net/bib/icons/ACM-logo.gif" title="ACM" alt="" /></a> David Volz, Andreas Koch and <a class="bibauthorlink" href="https://www.bastibl.net/">Bastian Bloessl</a>, "<strong>Software-Defined Wireless Communication Systems for Heterogeneous Architectures</strong>," Proceedings of 29th Annual International Conference on Mobile Computing and Networking (MobiCom 2023), Demo Session, Madrid, Spain, October 2023.
<small>[<a href="http://dx.doi.org/10.1145/3570361.3614084">DOI</a>, <a href="https://www.bastibl.net/bib/volz2023software/volz2023software.bib">BibTeX</a>, <a href="https://www.bastibl.net/bib/volz2023software/">PDF and Details...</a>]</small></li>
</ol>
WebAssembly Tutorial2023-07-22T00:00:00+00:002023-07-22T00:00:00+00:00https://www.futuresdr.org/blog/wasm-tutorial/<p>With all the cool Software Defined Radio (SDR) WebAssembly projects popping up, it seems like 2023 is the year of SDR in the browser :-)</p>
<p>We recently worked on improving the WebAssembly support for <a href="https://www.futuresdr.org/">FutureSDR</a> and are pretty happy with the result.
It no longer requires pulling a lot of tricks to cross-compile the native driver with emscripten but enables a complete Rust workflow.</p>
<p>The current user experience is shown in a tutorial-style live coding video, where we port a native FutureSDR ZigBee receiver to WebAssembly.</p>
<div class="ratio ratio-16x9">
<iframe src="https://www.youtube.com/embed/I-g8N0CR_rk" allowfullscreen></iframe>
</div>
FutureSDR Demo at 6G Platform Germany2023-06-27T00:00:00+00:002023-06-27T00:00:00+00:00https://www.futuresdr.org/blog/futuresdr-demo-6g-platform/<p><a href="https://www.esa.informatik.tu-darmstadt.de/team/dv">David Volz</a> and I presented our demo <em>FutureSDR meets IPEC</em> at the <a href="https://www.6g-plattform.de/berlin-6g-conference/">Berlin 6G Conference</a>, a meeting of all 6G-related projects, funded by the Federal Ministry of Education and Research (BMBF).
Our demo showed how <a href="https://www.futuresdr.org/">FutureSDR</a> can be used to implement platform-independent real-time signal processing applications that can be reconfigured during runtime.</p>
<p>We had the same FutureSDR receiver running on a <a href="https://www.xilinx.com/products/boards-and-kits/zcu111.html">Xilinx RFSoC FPGA board</a>, a normal laptop with an <a href="https://aaronia.com/de/spectran-v6-rsa-2000x">Aaronia Spectran V6</a> SDR, an in the web, using a <a href="https://greatscottgadgets.com/hackrf/one/">HackRF</a>.
We, furthermore, had the same receiver implemented on the FPGA of the RFSoC, using David’s <a href="https://git.esa.informatik.tu-darmstadt.de/ipec/ipec">IPEC</a> framework for Inter-Processing Element Communication.
Since the FPGA and the CPU implementations had the same structure, we could dynamically decide where to make the cut between FPGA and CPU processing, which was reflected in the CPU load of the RFSoC’s ARM processor.</p>
<blockquote class="twitter-tweet tw-align-center"><p lang="en" dir="ltr">Happy to demonstrate our <a href="https://twitter.com/FutureSDR?ref_src=twsrc%5Etfw">@FutureSDR</a> setup, using the <a href="https://twitter.com/Aaronia_AG?ref_src=twsrc%5Etfw">@Aaronia_AG</a> Spectran V6 and the Xilinx RFSoC at the 6G Platform Germany Meeting of <a href="https://twitter.com/BMBF_Bund?ref_src=twsrc%5Etfw">@BMBF_Bund</a> <a href="https://t.co/9CTmwfFihU">pic.twitter.com/9CTmwfFihU</a></p>— Bastian Bloessl (@bastibl) <a href="https://twitter.com/bastibl/status/1673743433767190528?ref_src=twsrc%5Etfw">June 27, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
Seify Talk @ SDRA 20232023-06-24T00:00:00+00:002023-06-24T00:00:00+00:00https://www.futuresdr.org/blog/seify-talk/<p>Bastian Bloessl gave a talk about <a href="https://github.com/FutureSDR/seify">Seify</a> at the <a href="https://2023.sdra.io/pages/programme.html">Software Defined Radio Academy 2023</a>.
Seify is <a href="https://github.com/FutureSDR/FutureSDR">FutureSDR’s</a> Rust SDR hardware abstraction library that solves a similar problem to Soapy in C++ domain. </p>
<p>The preliminary talk is already on YouTube. A final version, including the live Q&A, will be available later.</p>
<div class="ratio ratio-16x9">
<iframe src="https://www.youtube.com/embed/qVDaOwnkDfM" allowfullscreen></iframe>
</div>
<p>The <a href="https://docs.google.com/presentation/d/1McroCCKE0aX01T-r-inOh8do3CY9Sgng0CCXJRMOjQQ/edit?usp=sharing">slides</a> are also available:</p>
<div class="ratio ratio-16x9">
<iframe src="https://docs.google.com/presentation/d/e/2PACX-1vSP1zwWOQ9urq0-kXzXE6GcjdxVJwDdIpl8b5PrtpRWr09at-QrR-1Qhl2jjYuLyJOfXv3-LwpuvVUm/embed?start=false&loop=false&delayms=3000" frameborder="0" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe>
</div>
Macros2022-10-03T00:00:00+00:002022-10-03T00:00:00+00:00https://www.futuresdr.org/blog/macros/<p>We finally entered the
<a href="https://doc.rust-lang.org/reference/procedural-macros.html"><code>proc_macro</code></a> game
for some advanced syntactic sugaring :-) The macros are still experimental but
already fun to use.</p>
<p>At the moment, we support two macros; one for connecting the flowgraph and one
for implementing message handlers.</p>
<h2 id="connect-macro">Connect Macro</h2>
<p>The <code>connect!</code> macro serves two purposes. It adds blocks to the flowgraph and it
allows to connect them.</p>
<p>This makes the code quite a bit cleaner. Compare, for example:</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">main</span><span>() -> Result<()> {
</span><span> </span><span style="color:#b48ead;">let mut</span><span> fg = Flowgraph::new();
</span><span>
</span><span> </span><span style="color:#b48ead;">let</span><span> src = NullSource::<</span><span style="color:#b48ead;">u8</span><span>>::new();
</span><span> </span><span style="color:#b48ead;">let</span><span> head = Head::<</span><span style="color:#b48ead;">u8</span><span>>::new(</span><span style="color:#d08770;">123</span><span>);
</span><span> </span><span style="color:#b48ead;">let</span><span> snk = NullSink::<</span><span style="color:#b48ead;">u8</span><span>>::new();
</span><span>
</span><span> </span><span style="color:#b48ead;">let</span><span> src = fg.</span><span style="color:#96b5b4;">add_block</span><span>(src);
</span><span> </span><span style="color:#b48ead;">let</span><span> head = fg.</span><span style="color:#96b5b4;">add_block</span><span>(head);
</span><span> </span><span style="color:#b48ead;">let</span><span> snk = fg.</span><span style="color:#96b5b4;">add_block</span><span>(snk);
</span><span>
</span><span> fg.</span><span style="color:#96b5b4;">connect_stream</span><span>(src, "</span><span style="color:#a3be8c;">out</span><span>", head, "</span><span style="color:#a3be8c;">in</span><span>")?;
</span><span> fg.</span><span style="color:#96b5b4;">connect_stream</span><span>(head, "</span><span style="color:#a3be8c;">out</span><span>", snk, "</span><span style="color:#a3be8c;">in</span><span>")?;
</span><span>
</span><span> Runtime::new().</span><span style="color:#96b5b4;">run</span><span>(fg)?;
</span><span>
</span><span> Ok(())
</span><span>}
</span></code></pre>
<p>with</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">main</span><span>() -> Result<()> {
</span><span> </span><span style="color:#b48ead;">let mut</span><span> fg = Flowgraph::new();
</span><span>
</span><span> </span><span style="color:#b48ead;">let</span><span> src = NullSource::<</span><span style="color:#b48ead;">u8</span><span>>::new();
</span><span> </span><span style="color:#b48ead;">let</span><span> head = Head::<</span><span style="color:#b48ead;">u8</span><span>>::new(</span><span style="color:#d08770;">123</span><span>);
</span><span> </span><span style="color:#b48ead;">let</span><span> snk = NullSink::<</span><span style="color:#b48ead;">u8</span><span>>::new();
</span><span>
</span><span> connect!(fg, src > head > snk);
</span><span>
</span><span> Runtime::new().</span><span style="color:#96b5b4;">run</span><span>(fg)?;
</span><span>
</span><span> Ok(())
</span><span>}
</span></code></pre>
<p>The macro uses <code>></code> to indicate stream connections and <code>|</code> to indicate message
connections.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span>connect!(fg, stream_source > stream_sink);
</span><span>connect!(fg, message_source | message_sink);
</span></code></pre>
<p>If the port connections are not the default <code>"in"</code> and <code>"out"</code>, they can be put
explicitly.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span>connect!(fg, src.out > snk.in);
</span></code></pre>
<p>While it is uncommon, a port might have a space in its name. This can be solved
by quoting the port name.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span>connect!(fg, src."</span><span style="color:#a3be8c;">output port</span><span>" > snk.in);
</span></code></pre>
<p>If a port has no input or output ports that have to be connected, it can just be
put on a line on its own, which just adds it to the flowgraph.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span>connect!(fg, dummy_block);
</span></code></pre>
<p>As shown in the example, blocks can also be chained.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span>connect!(fg, src > head > snk);
</span></code></pre>
<p>And, finally, more complex topologies can be set up with multiple lines.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span>connect!(fg,
</span><span> src > fwd;
</span><span> fwd > snk;
</span><span> msg_src | msg_snk;
</span><span>);
</span></code></pre>
<p>The idea and initial implementation of the <code>connect!</code> macro was by <a href="https://twitter.com/loic_fejoz">Loïc
Fejoz</a>. Thank you!</p>
<h2 id="message-handlers">Message Handlers</h2>
<p>Handlers for message ports are, at the moment, quite ugly. This is mainly due to
current limitations of Rust’s <code>async</code> functions that will hopefully be overcome
in the future.</p>
<p>Assume you want to implement a block with a handler <code>my_handler</code>.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">pub fn </span><span style="color:#8fa1b3;">new</span><span>() -> Block {
</span><span> Block::new(
</span><span> BlockMetaBuilder::new("</span><span style="color:#a3be8c;">MyBlock</span><span>").</span><span style="color:#96b5b4;">build</span><span>(),
</span><span> StreamIoBuilder::new().</span><span style="color:#96b5b4;">build</span><span>(),
</span><span> MessageIoBuilder::new()
</span><span> .</span><span style="color:#96b5b4;">add_input</span><span>("</span><span style="color:#a3be8c;">handler</span><span>", </span><span style="color:#b48ead;">Self</span><span>::my_handler)
</span><span> .</span><span style="color:#96b5b4;">build</span><span>(),
</span><span> </span><span style="color:#b48ead;">Self</span><span>,
</span><span> )
</span><span>}
</span></code></pre>
<p>Using the <code>#[message_handler]</code> attribute macro, one can implement the handler
with:</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[</span><span style="color:#bf616a;">message_handler</span><span>]
</span><span>async </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">my_handler</span><span>(
</span><span> &</span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">self</span><span>,
</span><span> </span><span style="color:#bf616a;">_mio</span><span>: &</span><span style="color:#b48ead;">mut </span><span>MessageIo<</span><span style="color:#b48ead;">Self</span><span>>,
</span><span> </span><span style="color:#bf616a;">_meta</span><span>: &</span><span style="color:#b48ead;">mut</span><span> BlockMeta,
</span><span> </span><span style="color:#bf616a;">_p</span><span>: Pmt,
</span><span>) -> Result<Pmt> {
</span><span> Ok(Pmt::Null)
</span><span>}
</span></code></pre>
<p>Which is much more what one would expect, compared to the dynamically generated
and boxed-up async block that it actually is under the hood.</p>
<p>All of this is still experimental, but it’s amazing what is possible with proc macros.</p>
<p>If you want to see a complete example, we <a href="https://github.com/FutureSDR/FutureSDR/blob/main/examples/macros/src/main.rs">added
one</a>
to the project.</p>
<p>Please give it a try and let us know what you think :-)</p>
Better Flowgraph Interaction2022-09-15T00:00:00+00:002022-09-15T00:00:00+00:00https://www.futuresdr.org/blog/better-flowgraph-interaction/<p>A <code>FlowgraphHandle</code> is the main struct to interact with a flowgraph, once it is
started and ownership is passed to the runtime. In essence, the handle wraps the
sending part of a multi-producer-single-consumer channel to send
<code>FlowgraphMessage</code>s, which define the protocol between the flowgraph and the
outside world.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">pub struct </span><span>FlowgraphHandle {
</span><span> </span><span style="color:#bf616a;">inbox</span><span>: Sender<FlowgraphMessage>,
</span><span>}
</span></code></pre>
<p>Recently, we extended the interface, allowing the user to get a
<code>FlowgraphDescritpion</code> or <code>BlockDescription</code> from the handle. This information
can be used, for example, with GUI components to plot the flowgraph topology.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">pub struct </span><span>FlowgraphDescription {
</span><span> </span><span style="color:#b48ead;">pub </span><span style="color:#bf616a;">blocks</span><span>: Vec<BlockDescription>,
</span><span> </span><span style="color:#b48ead;">pub </span><span style="color:#bf616a;">stream_edges</span><span>: Vec<(</span><span style="color:#b48ead;">usize</span><span>, </span><span style="color:#b48ead;">usize</span><span>, </span><span style="color:#b48ead;">usize</span><span>, </span><span style="color:#b48ead;">usize</span><span>)>,
</span><span> </span><span style="color:#b48ead;">pub </span><span style="color:#bf616a;">message_edges</span><span>: Vec<(</span><span style="color:#b48ead;">usize</span><span>, </span><span style="color:#b48ead;">usize</span><span>, </span><span style="color:#b48ead;">usize</span><span>, </span><span style="color:#b48ead;">usize</span><span>)>,
</span><span>}
</span></code></pre>
<p>The interaction with the flowgraph is pretty elegant. We send it a message,
asking for a <code>FlowgraphDescription</code> and provide a channel, where we <code>await</code> the
result. This is sometimes referred to as the <em>Actor Pattern</em>.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">pub</span><span> async </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">description</span><span>(&</span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">self</span><span>) -> Result<FlowgraphDescription> {
</span><span> </span><span style="color:#b48ead;">let </span><span>(tx, rx) = oneshot::channel::<FlowgraphDescription>();
</span><span> </span><span style="color:#bf616a;">self</span><span>.inbox
</span><span> .</span><span style="color:#96b5b4;">send</span><span>(FlowgraphMessage::FlowgraphDescription { tx })
</span><span> .await?;
</span><span> </span><span style="color:#b48ead;">let</span><span> d = rx.await?;
</span><span> Ok(d)
</span><span>}
</span></code></pre>
<h2 id="control-port">Control Port</h2>
<p>Apart from extending the flowgraph handle, we refactored the control port
interface of the flowgraph (i.e., the REST API) to use the <code>FlowgraphHandle</code>.
Prior to that, there was a disconnect, which resulted in code duplication. Now,
the web application server just uses the handle and exposes a web interface for
it.</p>
<p>Both <code>FlowgraphDescription</code> and <code>BlockDescription</code> are serializable structs that
are exposed at <code>localhost:1337/api/fg/</code> and <code>localhost:1337/api/block/<n>/</code>.</p>
<div class="images">
<img class="lightbox fig-100" src="/blog/better-flowgraph-interaction/fg-json.png" style="max-width: 400px" alt="Querying the new API w/ Curl.">
</div>
<p>Using the <code>FlowgraphHandle</code> with control port, the whole web server endpoint is
just:</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span>async </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">flowgraph_description</span><span>(
</span><span> Extension(</span><span style="color:#bf616a;">mut flowgraph</span><span>): Extension<FlowgraphHandle>,
</span><span>) -> Result<Json<FlowgraphDescription>, StatusCode> {
</span><span> </span><span style="color:#b48ead;">if let </span><span>Ok(d) = flowgraph.</span><span style="color:#96b5b4;">description</span><span>().await {
</span><span> Ok(Json::from(d))
</span><span> } </span><span style="color:#b48ead;">else </span><span>{
</span><span> Err(StatusCode::</span><span style="color:#d08770;">BAD_REQUEST</span><span>)
</span><span> }
</span><span>}
</span></code></pre>
<p>API endpoints like these can be used easily with tools like curl or any web library.</p>
<div class="images">
<img class="lightbox fig-100" src="/blog/better-flowgraph-interaction/curl.png" style="max-width: 700px" alt="Querying the API with Curl.">
</div>
<p>To demonstrate how this can be used, we made the control port front page a
<a href="https://mermaid-js.github.io/mermaid/#/">Mermaid</a> diagram of the flowgraph.</p>
<div class="images">
<img class="lightbox fig-100" src="/blog/better-flowgraph-interaction/fg.png" style="max-width: 700px" alt="New control port frontpage.">
</div>
<p>These concepts, in particular the Mermaid representation of the flowgraph, were
thought of and pushed forward by <a href="https://twitter.com/loic_fejoz">Loïc Fejoz</a>.
Thank you!</p>
SDRA'22 Talk Online2022-09-14T00:00:00+00:002022-09-14T00:00:00+00:00https://www.futuresdr.org/blog/sdra-talk-online/<p>The talk on FutureSDR that was presented at the <a href="https://2022.sdra.io/">Software Defined Radio Academy 2022</a> is now online.</p>
<div class="ratio ratio-16x9">
<iframe src="https://www.youtube.com/embed/E5cFj5qrJnE" allowfullscreen></iframe>
</div>
Are We WLAN Yet?2022-08-21T00:00:00+00:002022-08-21T00:00:00+00:00https://www.futuresdr.org/blog/are-we-wlan/<p>Yes! :-)</p>
<blockquote class="twitter-tweet tw-align-center"><p lang="en" dir="ltr">FutureSDR now comes with a WLAN transceiver (IEEE 802.11a/g/p) with RFtap support to monitor traffic in Wireshark. <a href="https://t.co/a95p1qlS1B">pic.twitter.com/a95p1qlS1B</a></p>— FutureSDR (@FutureSDR) <a href="https://twitter.com/FutureSDR/status/1562168250183688194?ref_src=twsrc%5Etfw">August 23, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
Monomorphized Apply-/Functional-Style Blocks2022-08-20T00:00:00+00:002022-08-20T00:00:00+00:00https://www.futuresdr.org/blog/monomorphize-apply/<p>We <a href="https://www.futuresdr.org/blog/generic-blocks/">already discussed</a> blocks that are generic over a function and showed how they are handy for rapid prototyping.
These apply- or functional-style blocks proved incredibly useful and are utilized in many <a href="https://github.com/FutureSDR/FutureSDR/tree/main/examples">examples</a>.</p>
<p>For example, a block that doubles every float is just <code>Apply::new(|i: &f32| i * 2.0)</code>.</p>
<p>However, until now, these blocks had an inherent drawback, since the function was dispatched dynamically during runtime.
To be precise, the function was a closure allocated on the heap, which resulted in a function call per item, without any chance for the compiler to optimize things.</p>
<p>From FutureSDR v0.0.22, we avoid this overhead, making the blocks generic over the function, instead of a heap-allocated closure.
This means, we move <a href="https://docs.rs/futuresdr/0.0.21/futuresdr/blocks/struct.Apply.html">from</a></p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">pub struct </span><span>Apply<A, B>
</span><span>where
</span><span> A: 'static,
</span><span> B: 'static,
</span><span>{
</span><span> </span><span style="color:#bf616a;">f</span><span>: Box<dyn FnMut(&A) -> B + Send + </span><span style="color:#b48ead;">'static</span><span>>,
</span><span>}
</span></code></pre>
<p><a href="https://docs.rs/futuresdr/0.0.22/futuresdr/blocks/struct.Apply.html">to</a></p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">pub struct </span><span>Apply<F, A, B>
</span><span>where
</span><span> F: FnMut(&A) -> B + Send + </span><span style="color:#b48ead;">'static</span><span>,
</span><span> A: Send + </span><span style="color:#b48ead;">'static</span><span>,
</span><span> B: Send + </span><span style="color:#b48ead;">'static</span><span>,
</span><span>{
</span><span> f: F,
</span><span>}
</span></code></pre>
<p>With this, the compiler generates a separate implementation for each apply-style block, i.e., it is monomorphized.
This is in contrast to the old version, which was polymorphic in the sense that there was one <code>Apply</code> implementation that could handle any function closure with a given I/O signature.</p>
<p>Switching to monomorphized blocks, the compiler can inline the function and apply optimizations.
There is, for example, no reason the compiler couldn’t vectorize the function to benefit from SIMD instructions.</p>
<p>We did a quick performance comparison for a simple operation (<code>|x: &u8| x.wrapping_add(1)</code>).
To this end, we mocked the <code>Apply</code> block and processed 100M samples.
On my machine, this took ~24ms for the monomorphized version vs ~127ms for the old, dynamically dispatched version.</p>
<script src="https://gist.github.com/bastibl/a1e68085bc0524265291e58aea23b368.js"></script>
Ulrich Rohde Award2022-06-29T00:00:00+00:002022-06-29T00:00:00+00:00https://www.futuresdr.org/blog/rohde-award/<p><a href="https://www.bastibl.net/">Bastian Bloessl</a> received the Ulrich L. Rohde Award for outstanding contributions in the field of Software Defined Radio for his work on FutureSDR.
The award was handed-over at the <a href="https://2022.sdra.io/">Software Defined Radio Academy</a>, presented by <a href="https://www.iaru-r1.org/">IARU R1</a> President <a href="https://twitter.com/sylvain_azarian">Sylvain Azarian</a> and the <a href="https://www.darc.de/home/">DARC</a>.</p>
<div class="images">
<img class="lightbox fig-50" src="/blog/rohde-award/rohde-award.png" style="max-width: 700px" alt="Rohde Award">
<img class="lightbox fig-50" src="/blog/rohde-award/darc.png" style="max-width: 700px" alt="DARC Press Release">
</div>
Sync vs Async Blocks2022-05-03T00:00:00+00:002022-05-03T00:00:00+00:00https://www.futuresdr.org/blog/sync-vs-async/<p>FutureSDR supports both sync and async blocks.
Their only difference is the <code>work()</code> function, which is either a normal or an async function.
Overall, a <code>Block</code> is an enum containing a sync/async block with a sync/async kernel, implementing <code>work()</code>.
This class structure already suggests that supporting both implementations leads to complexity, bloat, and code duplication.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">pub trait </span><span>AsyncKernel: Send {
</span><span> async </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">work</span><span>(&</span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">self</span><span>, ...) -> Result<()> {
</span><span> Ok(())
</span><span> }
</span><span> ...
</span><span>}
</span><span>
</span><span style="color:#b48ead;">pub trait </span><span>SyncKernel: Send {
</span><span> </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">work</span><span>(&</span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">self</span><span>, ...) -> Result<()> {
</span><span> Ok(())
</span><span> }
</span><span> ...
</span><span>}
</span></code></pre>
<p>Obviously, it would be possible to just use async blocks, since one is not forced to <code>.await</code> anything inside an async function, i.e., one could just make any sync function async.
The reason both implementations exist, is that async blocks implement the <code>AsyncKernel</code> trait, defining async functions.
This is an area where Rust is still in active development.
Out-of-the-box, it does not support async trait functions, which is why everybody refrains to the <a href="https://docs.rs/async-trait/latest/async_trait/">async_trait</a> crate that enables this.
The popularity of this crate shows how desperate people want this language feature.</p>
<p>The reason that it is not mainline is – according to my understanding – that there is a performance penalty to using async trait functions.
In short, one can think of an async function as a state machine with some local variables.
If the compiler knows the concrete realization, it can build complex, nested state machines during compilation.
If the compiler doesn’t know the concrete realization due to dynamic dispatch of trait functions, it has to allocate the state machine during runtime for <em>every</em> function call.</p>
<p>This sounded like a complete performance disaster – at least for <code>work()</code>, which is called over and over again.
Therefore, we added support for sync blocks, which avoid this overhead.</p>
<h2 id="performance">Performance</h2>
<p>Already back then, some quick tests suggested that the performance difference might not be that big.
So the question is, whether it is really worth having both block types.
Today, we conducted some experiments to have a closer look.</p>
<p>The code and all scripts are available <a href="https://github.com/bastibl/FutureSDR/tree/358cdafc70e60656ea19a23f900b3fcb2e6ed973/perf/sync">here</a>.
In short, the measurements consider three schedulers: a single-threaded scheduler (Smol-1), a multi-threaded scheduler (Smol-N), an optimized, multi-threaded schedulers (Flow) that polls blocks in their natural order (upstream to downstream).
We make six CPU cores available to the process and use six worker threads for the multi-threaded schedulers.
The flowgraph consists of six independent subflows, each with a source that streams 200e6 32-bit floats into the flowgraph and #Stages (x-axis) number of copy blocks, each copying a random number samples (uniformly distributed in [1; 512]) in each call to work.
We create a sync and an async version of the otherwise identical copy blocks.</p>
<div class="images">
<img class="lightbox fig-100" src="/blog/sync-vs-async/sync.svg" style="max-width: 700px" alt="Execution Time of Flowgraphs">
</div>
<p>The blocks do not do any DSP and only copy small chunks of samples.
The performance is, therefore, mainly determined by the overhead of the runtime and the potential overhead of the async block.
Yet, the differences are minor.</p>
<h2 id="conclusion">Conclusion</h2>
<p>This suggests that it is not worth supporting sync implementations.
At least not for now.
And in the future, I expect that things just get better.
There are <a href="https://smallcultfollowing.com/babysteps//blog/2022/01/07/dyn-async-traits-part-7/">ongoing discussions</a> how Rust should handle async trait functions.
Maybe a more efficient way is found, which would further improve the situation.</p>
<p>In retrospect, one could see this as a failed premature optimization.
But it is also interesting to see the effect and quantify its impact on performance.
As time permits, I will go ahead and remove sync blocks, so we get back to a more minimal runtime.</p>
Full ZigBee SDR Receiver in the Browser2022-03-02T00:00:00+00:002022-03-02T00:00:00+00:00https://www.futuresdr.org/blog/wasm-zigbee/<p>Some months ago, we showed a complete SDR waterfall plot running in the browser.
It interfaced an RTL-SDR from within the browser, using cross-compiled drivers.
In short, this requires compiling the driver to WebAssembly (Wasm) using <a href="https://emscripten.org/">Emscripten</a> and a <a href="https://github.com/luigifcruz/webusb-libusb">shim</a> that maps libusb to WebUSB calls.</p>
<p>Signal processing was implemented with FutureSDR.
It even supported <a href="https://github.com/gfx-rs/wgpu">wgpu</a> <a href="https://github.com/FutureSDR/FutureSDR/tree/master/src/runtime/buffer/wgpu">custom buffers</a> for platform-independent GPU acceleration.
Wgpu is really awesome.
It supports all major platforms, using their native backends: Linux/Android (→ Vulkan), Windows (→ DX12), macOS/iOS (→ Metal), and Wasm (→ WebGPU).</p>
<blockquote class="twitter-tweet tw-align-center"><p lang="en" dir="ltr">FutureSDR + WebUSB + WebGPU + WebGL :-) Have an RTL-SDR and Chrome Unstable, you can give this a try: <a href="https://t.co/zmj2iQ031t">https://t.co/zmj2iQ031t</a> (need to enable unsafe WebGPU flag). <a href="https://t.co/tKaxgGAFmO">pic.twitter.com/tKaxgGAFmO</a></p>— Bastian Bloessl (@bastibl) <a href="https://twitter.com/bastibl/status/1471820778022871045?ref_src=twsrc%5Etfw">December 17, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>What was missing was a real, non-trivial SDR application.
We were curious what is possible, so we developed a ZigBee receiver and cross-compiled it to Wasm.
Furthermore, since the RTL-SDR doesn’t work in the 2.4GHz band and doesn’t provide the required bandwidth, we cross-compiled the driver of the <a href="https://greatscottgadgets.com/hackrf/">HackRF</a> in a similar fashion.</p>
<p>To test the receiver, we generated ZigBee frames with <a href="https://scapy.net/">Scapy</a> and sent them using an <a href="http://shop.sysmocom.de/products/atusb">ATUSB IEEE 802.15.4 USB Adapter</a>.</p>
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#b48ead;">import </span><span>time
</span><span style="color:#b48ead;">from </span><span>scapy.all </span><span style="color:#b48ead;">import </span><span style="color:#d08770;">*
</span><span>
</span><span style="color:#65737e;"># linux: include/uapi/linux/if_ether.h
</span><span style="color:#bf616a;">ETH_P_IEEE802154 </span><span>= </span><span style="color:#d08770;">0x00f6
</span><span>
</span><span>i = </span><span style="color:#d08770;">0
</span><span style="color:#b48ead;">while </span><span style="color:#d08770;">True</span><span>:
</span><span> fcf = </span><span style="color:#bf616a;">Dot15d4FCS</span><span>()
</span><span> data = </span><span style="color:#bf616a;">Dot15d4Data</span><span>(</span><span style="color:#bf616a;">dest_panid</span><span>=</span><span style="color:#d08770;">0x47d0</span><span>, </span><span style="color:#bf616a;">dest_addr</span><span>=</span><span style="color:#d08770;">0x0000</span><span>, </span><span style="color:#bf616a;">src_panid</span><span>=</span><span style="color:#d08770;">0x47d0</span><span>, </span><span style="color:#bf616a;">src_addr</span><span>=</span><span style="color:#d08770;">0xee64</span><span>)
</span><span> frame_data = fcf/data/</span><span style="color:#b48ead;">f</span><span>"</span><span style="color:#a3be8c;">FutureSDR </span><span>{i}"
</span><span> frame_data.</span><span style="color:#bf616a;">show</span><span>()
</span><span> </span><span style="color:#bf616a;">sendp</span><span>(frame_data, </span><span style="color:#bf616a;">iface</span><span>='</span><span style="color:#a3be8c;">monitor0</span><span>', </span><span style="color:#bf616a;">type</span><span>=</span><span style="color:#bf616a;">ETH_P_IEEE802154</span><span>)
</span><span> time.</span><span style="color:#bf616a;">sleep</span><span>(</span><span style="color:#d08770;">0.4</span><span>)
</span><span> i += </span><span style="color:#d08770;">1
</span></code></pre>
<p>Turns out, this actually works and the 4Msps of the ZigBee receiver can be processed in real-time in the browser.
We <a href="https://www.fleark.de/zigbee/">host a demo</a> on our website, which is hard-coded to ZigBee channel 26 @ 2.48GHz.
The receiver is, however, also part of the <a href="https://github.com/FutureSDR/FutureSDR/tree/master/examples/zigbee">examples</a>.
It works just as well as native binary outside the browser, using <a href="https://github.com/pothosware/SoapySDR">SoapySDR</a> to interface the hardware.</p>
<p>At the moment, the FutureSDR receiver only uses one thread that is, however, separate from the HackRF RX thread, spawned by the driver.
Compiling in release mode, FutureSDR uses around 20% CPU on an Intel i7-8700K.
See the demo video here:</p>
<blockquote class="twitter-tweet tw-align-center"><p lang="en" dir="ltr">You always wanted a full SDR ZigBee receiver in a browser? Me neither. Anyway :-)<a href="https://t.co/2J6qeVdUqO">https://t.co/2J6qeVdUqO</a></p>— Bastian Bloessl (@bastibl) <a href="https://twitter.com/bastibl/status/1496399211851661313?ref_src=twsrc%5Etfw">February 23, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>And as usual, everything just works on the phone.
This is not an Android application with cross-compiled drivers.
It runs the whole ZigBee receiver in the Google Chrome browser that is shipped with my phone.
Really fascinating what is possible in the browser these days…</p>
<div class="images">
<img class="lightbox fig-100" src="/blog/wasm-zigbee/phone.jpg" style="max-width: 700px" alt="Phone Setup">
</div>
<p>If you have ideas for cool applications, feel free to reply to one of the Twitter threads :-)</p>
Slab Buffers2022-03-01T00:00:00+00:002022-03-01T00:00:00+00:00https://www.futuresdr.org/blog/red-slab/<p>Buffers are at the heart of every SDR runtime.
GNU Radio, for example, is famous for its <a href="https://www.gnuradio.org/blog/2017-01-05-buffers/">double-mapped circular buffers</a>.
In short, they use the <a href="https://en.wikipedia.org/wiki/Memory_management_unit">MMU</a> to map the same memory twice, back-to-back in the virtual address space of the process.
This arrangement allows to implement a ring buffer on-top that always presents the available read/write space as consecutive memory, similar to a C array.
The figure below shows how a buffer, consisting of physical memory areas A and B, would be mapped.</p>
<div class="images">
<img class="lightbox fig-100" src="/blog/red-slab/double-mapped-buffer.svg" style="max-width: 700px" alt="Double-Mapped Circular Buffer">
</div>
<p>Using these buffers, blocks can assume that data is always in linear, consecutive memory.
In contrast to normal circular buffers, they do not have to care about wrapping.
This simplifies DSP implementations, in particular, for algorithms that consider multiple samples to produce output (e.g., a FIR filter).
Furthermore, samples in linear memory allow using vectorized instructions (provided by SIMD extensions), which can make a big difference [1, 2].</p>
<p>Given these advantages, double-mapped circular buffers were also adopted by FutureSDR.
(There is now also a <a href="https://github.com/FutureSDR/vmcircbuffer">separate crate</a> for them, in case you want to roll your own SDR application without a framework or runtime.)
These buffers work well for Linux, Android, Windows, and macOS.
FutureSDR, however, also targets platforms that do not allow memory mapping (WebAssembly/WASM) or do not have a MMU in the, first place.</p>
<span id="continue-reading"></span><h2 id="slab">Slab</h2>
<p>For this reason, we introduced an alternate buffer implementation that works with pre-allocated, normal memory.
With custom buffers – as supported by FutureSDR – this is <a href="https://github.com/FutureSDR/FutureSDR/blob/master/src/runtime/buffer/slab.rs">easily possible</a>.
We allocate buffer memory once and keep it around for the duration of the flowgraph to avoid continuous reallocation.
This borrows ideas from a <a href="https://en.wikipedia.org/wiki/Slab_allocation">Slab allocator</a>, which is why we refer to these buffers as <em>Slab buffers</em>.</p>
<p>For every stream edge in the flowgraph, FutureSDR allocates, by default, two buffers.
This implements double-buffering, i.e., the upstream block can write into one buffer, while the downstream block reads from the other.
Everything is, however, configurable per stream connection (i.e., buffer size and number of buffers).</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span>fg.</span><span style="color:#96b5b4;">connect_stream</span><span>(src, "</span><span style="color:#a3be8c;">out</span><span>", cpy, "</span><span style="color:#a3be8c;">in</span><span>")?; </span><span style="color:#65737e;">// default buffer for architecture
</span><span>fg.</span><span style="color:#96b5b4;">connect_stream_with_type</span><span>(src, "</span><span style="color:#a3be8c;">out</span><span>", snk, "</span><span style="color:#a3be8c;">in</span><span>", Slab::new())?;
</span><span>fg.</span><span style="color:#96b5b4;">connect_stream_with_type</span><span>(src, "</span><span style="color:#a3be8c;">out</span><span>", snk, "</span><span style="color:#a3be8c;">in</span><span>", Slab::with_size(</span><span style="color:#d08770;">4096</span><span>))?;
</span><span>fg.</span><span style="color:#96b5b4;">connect_stream_with_type</span><span>(src, "</span><span style="color:#a3be8c;">out</span><span>", snk, "</span><span style="color:#a3be8c;">in</span><span>", Slab::with_buffers(</span><span style="color:#d08770;">3</span><span>))?; </span><span style="color:#65737e;">// triple-buffering
</span></code></pre>
<h2 id="red-slab">Red Slab</h2>
<p>A straightforward implementation of a Slab buffer suffers from the issue mentioned above, i.e., we have to deal with transitions between buffers.
Consider, for example, a FIR filter with 16 taps, which needs 16 samples to calculate an output sample.
What should it do, if there are only 15 samples left in a Slab buffer?
If it doesn’t consume, the flowgraph stalls forever.
So, should it copy the remaining samples to another, internal buffer and wait for the next full Slab buffer?
This leads to non-trivial logic that each block would have to implement.</p>
<p>Apart from that, the block does not even know that it reads from a Slab buffer.
And this is by design.
Since the runtime can be extended with arbitrary buffers, block implementations have to be independent from buffers.
But that would also mean that the block would always have to trigger the copying, even for the double-mapped buffers, which would not require this logic.</p>
<p>It is clear that blocks cannot solve this problem.
Instead, it has to be handled by the buffer implementation in the runtime.
For our Slab implementation, we used a little trick:
Slab buffers are, by default, not filled completely.
At the start of the buffer, there is a configurable number of reserved items that are skipped when the buffer is filled.</p>
<p>Now, when a block consumes items from the Slab buffer and leaves only up to <code>reserved_items</code>, the runtime waits for the next buffer and copies the remaining samples in its reserved area.
This is possible with rather simple logic, does not require shifting items in the target buffer, and puts samples in consecutive memory.
For our 16-tap FIR filter, we would have to reserve at least 15 items to make sure that it can always proceed.
When 15 samples are left, the buffer will copy them over in the reserved area of the following buffer.
We couldn’t do it more efficient in the block, even if it knew about the Slab buffers.</p>
<div class="images">
<img class="lightbox fig-100" src="/blog/red-slab/slab.svg" style="max-width: 700px" alt="Red Slab">
</div>
<p>Since the approach borrows some ideas from the <a href="https://en.wikipedia.org/wiki/Red_zone_(computing)">red zone</a> of a function stack, which allows using parts of the stack as scratch space, we call it <em>Red Slab</em>.</p>
<p>Note that we only talk about input buffers.
For output buffers it is much simpler.
Just switch to the next Slab buffer, even if it is not filled to the last sample.</p>
<h2 id="performance">Performance</h2>
<p>We conducted preliminary performance measurements of the different buffer implementations.
The details can be derived from the measurement scripts and flowgraphs <a href="https://github.com/FutureSDR/FutureSDR/tree/master/perf/buffer_rand">in the repository</a>.
In short, the measurements consider three schedulers: a single-threaded scheduler (Smol-1), a multi-threaded scheduler (Smol-N), an optimized, multi-threaded schedulers (Flow) that polls blocks in their natural order (upstream to downstream).
We make six CPU cores available to the process and use six worker threads for the multi-threaded schedulers.
The flowgraph consists of six independent subflows, each with a source that streams 200e6 32-bit floats into the flowgraph and #Stages (x-axis) number of copy blocks, each copying a random number samples (uniformly distributed in [1; 512]) in each call to work.
The Slab buffers have a reserved space of 128 items.</p>
<div class="images">
<img class="lightbox fig-100" src="/blog/red-slab/buffer.svg" style="max-width: 700px" alt="Execution Time of Flowgraphs">
</div>
<p>In the beginning, I found it surprising that the Slab buffers are faster than their circular counterpart.
But there are two arguments in favor of Slab:</p>
<ul>
<li>
<p>The expensive operations are the ones that have to be synchronized. For Slab, this is pushing buffers back-and-forth. But this only happens, if the buffer is full. The circular buffer, in turn, advances read/write pointers for every small chunk of samples.</p>
</li>
<li>
<p>Slab always forwards full buffers with 16k samples (minus the 128 reserved). So if a block has data, it is likely that it has many samples available. A circular buffer is always called, even if the upstream just produced a few samples. Therefore, there might be iterations through the subflowgraph, where only, say, two samples are propagated.</p>
</li>
</ul>
<p>Note, GNU Radio also got a <a href="https://wiki.gnuradio.org/index.php/CustomBuffers#Single_Mapped_Buffer_Abstraction">Slab-like buffer implementation</a> in v3.10, which they refer to as <em>single-mapped buffers</em>.
Compared to the FutureSDR implementation, there seem to be some drawbacks:</p>
<ul>
<li>GNU Radio uses only one buffer, i.e., no double-/triple-buffering.</li>
<li>Since they use only one buffer, they have to move all samples, when copying over samples from the end.</li>
</ul>
<p>Overall, it appears that this is harder to get right, requires a complex API, and is not as efficient as it could be.</p>
<p>Feel free to reach out on <a href="https://twitter.com/FutureSDR">Twitter</a> or <a href="https://discord.com/invite/vCz29eDbGP/">Discord</a>, if you have any feedback!</p>
<h2 id="references">References</h2>
<ol class="pub-list">
<li><a href="http://dx.doi.org/10.1109/RWS.2015.7129727"><img src="https://www.bastibl.net/bib/icons/IEEE-logo.gif" title="IEEE" alt="" /></a> Nathan West, Douglas Geiger and George Scheets, "<strong>Accelerating Software Radio on ARM: Adding NEON Support to VOLK</strong>," Proceedings of IEEE Radio and Wireless Symposium (RWS), San Diego, CA, jan 2015, pp. 174–176.
<small>[<a href="http://dx.doi.org/10.1109/RWS.2015.7129727">DOI</a>, <a href="https://www.bastibl.net/bib/west2015accelerating/west2015accelerating.bib">BibTeX</a>, <a href="https://www.bastibl.net/bib/west2015accelerating/">PDF and Details...</a>]</small></li>
<li>Thomas W. Rondeau, Nicholas McCarthy and Timothy O'Shea, "<strong>SIMD Programming in GNU Radio: Maintainable und User-Friendly Algorithm Optimization with VOLK</strong>," Proceedings of SDR-WInnComm 2013, Washington, DC, Jan 2013, pp. 101-110.
<small>[<a href="https://www.bastibl.net/bib/rondeau2013simd/rondeau2013simd.bib">BibTeX</a>, <a href="https://www.bastibl.net/bib/rondeau2013simd/">PDF and Details...</a>]</small></li>
</ol>
Benchmarking FutureSDR2021-11-05T00:00:00+00:002021-11-05T00:00:00+00:00https://www.futuresdr.org/blog/benchmarking/<p>The introductory video of FutureSDR already showed some quick benchmarks for the throughput of message- and stream-based flowgraphs.
What was missing (not only for FutureSDR but for SDRs in general) were latency measurements.
I, therefore, had a closer look into this issue.</p>
<p>While throughput can be measured rather easily (by piping a given amount of data through a flowgraph and measuring its execution time), latency is more tricky.
The state-of-the-art is to do I/O measurements, where the flowgraph reads samples from an SDR, processes them, and loops them back.
Using external HW (i.e., a signal generator and an oscillator), one can measure the latency.</p>
<p>The drawback of this approach is obvious.
It requires HW, a non-trivial setup, is hard to automate and integrate in CI/CD.</p>
<p>An alternative is measuring latency by logging when a sample is produced in a source and received in a sink.
The main requirement for this measurement is that the overhead must be minimal.
Otherwise, one easily measures the performance of the logging or impacts the flowgraph in a way that its behavior is no longer representative for normal execution.</p>
<span id="continue-reading"></span>
<p>There are many possible solutions (like logging to stdout, a file, or a ramdisk) or using eBPF (uprobes or USDTs).
However, they all introduce considerable overhead or require significant manual tuning and optimizations (e.g. logging to a ramdisk needs to be synchronized and should ideally use a binary format; uprobes and USDTs trigger a context switch to the kernel, etc.).</p>
<p><a href="https://lttng.org/">LTTng</a> allows probing user space applications with minimal overhead.
It uses one ring buffer per CPU to log custom events in a binary format.
Furthermore, there are tools and libraries available to evaluate the traces.</p>
<p>The main drawback of LTTng is that it is only available on Linux and requires adding tracepoints to the code.
The latter is easily possible for GNU Radio and FutureSDR by adding custom sources and sinks that provide these tracepoints.</p>
<p>The underlying idea is to define a <em>granularity</em> and issue TX/RX events when <em>granularity</em> samples are produced/consumed in the source/sink.
One then correlates the events in post-processing to calculate the latency.</p>
<p>The <code>work</code> function of the modified Null Source, for example, checks if <code>self.probe_granularity</code> samples were produced and, in case, issues a <code>tracepoints::null_rand_latency::tx</code> event that is logged by LTTng.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span>async </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">work</span><span>(&</span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">self</span><span>, </span><span style="color:#bf616a;">_io</span><span>: &</span><span style="color:#b48ead;">mut</span><span> WorkIo, </span><span style="color:#bf616a;">sio</span><span>: &</span><span style="color:#b48ead;">mut</span><span> StreamIo, </span><span style="color:#bf616a;">_mio</span><span>: &</span><span style="color:#b48ead;">mut </span><span>MessageIo<</span><span style="color:#b48ead;">Self</span><span>>, </span><span style="color:#bf616a;">_meta</span><span>: &</span><span style="color:#b48ead;">mut</span><span> BlockMeta) -> Result<()> {
</span><span> </span><span style="color:#b48ead;">let</span><span> o = sio.</span><span style="color:#96b5b4;">output</span><span>(</span><span style="color:#d08770;">0</span><span>).slice::<</span><span style="color:#b48ead;">u8</span><span>>();
</span><span> </span><span style="color:#b48ead;">unsafe </span><span>{
</span><span> ptr::write_bytes(o.</span><span style="color:#96b5b4;">as_mut_ptr</span><span>(), </span><span style="color:#d08770;">0</span><span>, o.</span><span style="color:#96b5b4;">len</span><span>());
</span><span> }
</span><span>
</span><span> </span><span style="color:#b48ead;">let</span><span> before = </span><span style="color:#bf616a;">self</span><span>.n_produced / </span><span style="color:#bf616a;">self</span><span>.probe_granularity;
</span><span> </span><span style="color:#b48ead;">let</span><span> n = o.</span><span style="color:#96b5b4;">len</span><span>() / </span><span style="color:#bf616a;">self</span><span>.item_size;
</span><span> sio.</span><span style="color:#96b5b4;">output</span><span>(</span><span style="color:#d08770;">0</span><span>).</span><span style="color:#96b5b4;">produce</span><span>(n);
</span><span> </span><span style="color:#bf616a;">self</span><span>.n_produced += n as </span><span style="color:#b48ead;">u64</span><span>;
</span><span> </span><span style="color:#b48ead;">let</span><span> after = </span><span style="color:#bf616a;">self</span><span>.n_produced / </span><span style="color:#bf616a;">self</span><span>.probe_granularity;
</span><span>
</span><span> </span><span style="color:#b48ead;">if</span><span> before != after {
</span><span> tracepoints::null_rand_latency::tx(</span><span style="color:#bf616a;">self</span><span>.id.</span><span style="color:#96b5b4;">unwrap</span><span>(), after);
</span><span> }
</span><span> Ok(())
</span><span>}
</span></code></pre>
<p>For GNU Radio, we created a Null Source in a <a href="https://github.com/bastibl/FutureSDR/blob/9996a494597cedb834740e88315b955fbde280bb/perf/null_rand_latency/null_rand_flowgraph.cpp#L22-L67">similar manner</a>.</p>
<p>I checked the overhead for FutureSDR executing a flowgraph (1) without LTTng tracepoints, (2) with disabled tracepoints, (3) with enabled tracepoints.
Adding the tracepoint and the checks if <em>granularity</em> samples were produced adds ~4% overhead.
Actual logging didn’t introduce a sizeable difference for a granularity of 32768 float samples.</p>
<p>The measurements were conducted, following the methodology described in [1].
In short, we created a CPU set for the measurements and orchestrated the process through a <a href="https://github.com/bastibl/FutureSDR/blob/9996a494597cedb834740e88315b955fbde280bb/perf/null_rand_latency/Makefile">Makefile</a>.
We allocated 3 cores with their hyper-threads to this CPU set.
On my system these were “CPUs” 0, 1, 2, 6, 7, and 8. </p>
<div class="images">
<img class="lightbox fig-100" src="/blog/benchmarking/hwloc.png" style="max-width: 700px" alt="CPU Topology">
</div>
<p>We evaluated flowgraphs with 6 parallel <em>Pipes</em> and a configurable number of <em>Stages</em>.</p>
<div class="images">
<img class="lightbox fig-100" src="/blog/benchmarking/setup.svg" style="max-width: 1200px" alt="Flowgraph Topology">
</div>
<p>The <em>Sources</em> are modified <em>Null Sources</em> followed by <em>Head</em> blocks to limit the data that is produced, allowing a graceful shutdown.
The blocks that form the <em>Stages</em> just copy floats from input to output buffers.
To avoid fixed, boring schedules, they copy only up to 512 samples at a time, with the actual value uniformly distributed between 1 and 512 samples.</p>
<p>For FutureSDR, we tested the <em>Smol</em> scheduler, which spawns one thread per CPU available to the process (in this case 6).
The tasks (corresponding to blocks) are processed by a work-stealing scheduler that is unaware of the flowgraph topology.
The <em>Flow</em> scheduler, in turn, has tasks associated to worker threads and processes them round-robin from upstream to downstream blocks, i.e., it exploits knowledge of the flowgraph topology.</p>
<p>Latency measurements with this setup provided the following results.
Note that the error bars do <em>not</em> indicate noisy measurements but plot the 5% and 95% percentile of the latency distribution.</p>
<div class="images">
<img class="lightbox fig-100" src="/blog/benchmarking/latency.svg" style="max-width: 700px" alt="Latency of Sample Flowgraph">
</div>
<p>All tools and evaluation scripts are available <a href="https://github.com/bastibl/FutureSDR/tree/7606c77a6d025a409954490b87d53a5c3adab885/perf/null_rand_latency">here</a>.
Feel free to try it on your system with different parameters.
For now, I would take these results with a grain of salt, but I believe that LTTng is a good option for latency measurements.</p>
<h2 id="references">References</h2>
<ol class="pub-list">
<li><a class="bibauthorlink" href="https://www.bastibl.net/">Bastian Bloessl</a>, Marcus Müller and Matthias Hollick, "<strong>Benchmarking and Profiling the GNU Radio Scheduler</strong>," Proceedings of 9th GNU Radio Conference (GRCon 2019), Huntsville, AL, September 2019.
<small>[<a href="https://www.bastibl.net/bib/bloessl2019benchmarking/bloessl2019benchmarking.bib">BibTeX</a>, <a href="https://www.bastibl.net/bib/bloessl2019benchmarking/">PDF and Details...</a>]</small></li>
</ol>
Generic Blocks for Rapid Prototyping2021-09-14T00:00:00+00:002021-09-14T00:00:00+00:00https://www.futuresdr.org/blog/generic-blocks/<p>FutureSDR misses basically all blocks at this stage. Fortunately, people started <a href="https://github.com/FutureSDR/FutureSDR/pull/10">contributing</a> some of them, including blocks to add or multiply a stream with a constant. This block was implemented in way so that it was generic over the arithmetic operation. Thinking a bit further about the concept, we realized that it can be extended to arbitrary operations, creating blocks that are generic over function closures.</p>
<p>Meet our new blocks: <em>Source, FiniteSource, Apply, Combine, Split</em>, and <em>Filter</em>, all of which are generic over mutable closures. This can come in handy to quickly hack something together. Let me give you some examples.</p>
<h3 id="sources">Sources</h3>
<p>Need a constant source that produces 123 as <code>u32</code>?</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">use </span><span>futuresdr::blocks::Source;
</span><span>
</span><span style="color:#b48ead;">let </span><span>_ = Source::new(|| </span><span style="color:#d08770;">123</span><span style="color:#b48ead;">u32</span><span>);
</span></code></pre>
<p>The <em>Source</em> block is generic over <code>FnMut() -> A</code>. It recognizes the output type (in this case <code>u32</code>) and creates the appropriate stream output.</p>
<p>Need a source that iterates again and again over a range or vector?</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">let mut</span><span> v = (</span><span style="color:#d08770;">0</span><span>..</span><span style="color:#d08770;">10</span><span>).</span><span style="color:#96b5b4;">cycle</span><span>();
</span><span style="color:#b48ead;">let </span><span>_ = Source::new(</span><span style="color:#b48ead;">move </span><span>|| v.</span><span style="color:#96b5b4;">next</span><span>().</span><span style="color:#96b5b4;">unwrap</span><span>());
</span></code></pre>
<span id="continue-reading"></span>
<p>Notice, how this closure is mutable (i.e., has state). One could just as well create a counter or implement a signal source that keeps the current phase as state, etc.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">let mut</span><span> i = </span><span style="color:#d08770;">0</span><span style="color:#b48ead;">u32</span><span>;
</span><span style="color:#b48ead;">let </span><span>_ = Source::new(</span><span style="color:#b48ead;">move </span><span>|| { i += </span><span style="color:#d08770;">1</span><span>; i });
</span></code></pre>
<p>Sometimes, the function signature and, hence, the data type of the output might not be obvious. In this case, we can be more explicit:</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">let mut</span><span> i = </span><span style="color:#d08770;">0</span><span style="color:#b48ead;">u32</span><span>;
</span><span style="color:#b48ead;">let </span><span>_ = Source::new(</span><span style="color:#b48ead;">move </span><span>|| -> </span><span style="color:#b48ead;">u32 </span><span>{ i += </span><span style="color:#d08770;">1</span><span>; i });
</span></code></pre>
<p>Now, what about a finite source? One could, of course, add a <em>Head</em> block after the source and terminate after a given number of items. But this might not be ideal for all use cases. So we added a <em>FiniteSource</em> that returns <code>Option<A></code> and stops once the closure returns <code>None</code>.</p>
<p>A vector source that terminates, once it outputted all items would be:</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">use </span><span>futuresdr::blocks::FiniteSource;
</span><span>
</span><span style="color:#b48ead;">let mut</span><span> v = vec![</span><span style="color:#d08770;">1</span><span>, </span><span style="color:#d08770;">2</span><span>, </span><span style="color:#d08770;">3</span><span>].</span><span style="color:#96b5b4;">into_iter</span><span>();
</span><span style="color:#b48ead;">let </span><span>_ = FiniteSource::new(</span><span style="color:#b48ead;">move </span><span>|| v.</span><span style="color:#96b5b4;">next</span><span>());
</span></code></pre>
<h3 id="apply-combine-split">Apply, Combine, Split</h3>
<p>A similar concept can be realized for simple operations on streams. Need a block that constrains an <code>f32</code> in an interval between -1 and 1?</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">use </span><span>futuresdr::blocks::Apply;
</span><span>
</span><span style="color:#b48ead;">let </span><span>_ = Apply::new(|</span><span style="color:#bf616a;">x</span><span>: &</span><span style="color:#b48ead;">f32</span><span>| x.</span><span style="color:#96b5b4;">clamp</span><span>(-</span><span style="color:#d08770;">1.0</span><span>, </span><span style="color:#d08770;">1.0</span><span>));
</span></code></pre>
<p>Need a block that adds 42 to a <code>u32</code> and returns the result as <code>f32</code>?</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">let </span><span>_ = Apply::new(|</span><span style="color:#bf616a;">x</span><span>: &</span><span style="color:#b48ead;">u32</span><span>| *x as </span><span style="color:#b48ead;">f32 </span><span>+ </span><span style="color:#d08770;">42.0</span><span>);
</span></code></pre>
<p>The <em>Apply</em> block is generic over <code>FnMut(&A) -> B</code>, i.e., any mutable closure that gets a reference to an item of type <code>A</code> in the input buffer and produces an item of type <code>B</code> that will be written to the output buffer.</p>
<p>Since input and output types can be different, we can implement a block that computes the magnitude of a complex number, for example.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">let </span><span>_ = Apply::new(|</span><span style="color:#bf616a;">x</span><span>: &Complex<</span><span style="color:#b48ead;">f32</span><span>>| x.</span><span style="color:#96b5b4;">norm</span><span>());
</span></code></pre>
<p>Note that the closure, again, is mutable and can have state. This means, we could very easily implement an IIR filter.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">let</span><span> state = </span><span style="color:#d08770;">0</span><span style="color:#b48ead;">f32</span><span>;
</span><span style="color:#b48ead;">let</span><span> alpha = </span><span style="color:#d08770;">0.1</span><span>;
</span><span style="color:#b48ead;">let </span><span>_ = Apply::new(</span><span style="color:#b48ead;">move </span><span>|x: &</span><span style="color:#b48ead;">f32</span><span>| -> </span><span style="color:#b48ead;">f32 </span><span>{ state = state * alpha + (</span><span style="color:#d08770;">1.0 </span><span>- alpha) * *x; state } );
</span></code></pre>
<p>The <em>Combine</em> and <em>Split</em> blocks are conceptually similar, just that they are for functions with two inputs and outputs, respectively.
<em>Combine</em> is generic over <em>FnMut(&A, &B) -> C</em> to implement, for example, a block that adds two streams.
<em>Split</em> is generic over <em>FnMut(&A) -> (B, C)</em> to implement, for example, a block that splits a complex number in real and imaginary parts.
Examples for these blocks can be found in the corresponding <a href="https://github.com/FutureSDR/FutureSDR/tree/master/tests">integration tests</a>.</p>
<h3 id="filter">Filter</h3>
<p>A similar concepts is used in the <em>Filter</em> block, which relaxes the fixed in–out relationship of the <em>Apply</em> block.</p>
<p>It is generic over <code>FnMut(&A) -> Option<B></code> and allows filtering the input stream. If the closure returns <code>Some(B)</code>, the value is written in the output buffer; if the closure returns <code>None</code>, nothing is written to the output buffer.</p>
<p>A stateless block that only copies even numbers would be:</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">use </span><span>futuresdr::blocks::Filter;
</span><span style="color:#b48ead;">let </span><span>_ = Filter::new(|</span><span style="color:#bf616a;">i</span><span>: &</span><span style="color:#b48ead;">u32</span><span>| -> Option<</span><span style="color:#b48ead;">u32</span><span>> {
</span><span> </span><span style="color:#b48ead;">if </span><span>*i % </span><span style="color:#d08770;">2 </span><span>== </span><span style="color:#d08770;">0 </span><span>{
</span><span> Some(*i)
</span><span> } </span><span style="color:#b48ead;">else </span><span>{
</span><span> None
</span><span> }
</span><span>});
</span></code></pre>
<p>A stateful block that only copies every other sample could look like this:</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">let mut</span><span> output = </span><span style="color:#d08770;">false</span><span>;
</span><span style="color:#b48ead;">let </span><span>_ = Filter::new(</span><span style="color:#b48ead;">move </span><span>|i: &</span><span style="color:#b48ead;">u32</span><span>| -> Option<</span><span style="color:#b48ead;">u32</span><span>> {
</span><span> output = !output;
</span><span> </span><span style="color:#b48ead;">if</span><span> output {
</span><span> Some(*i)
</span><span> } </span><span style="color:#b48ead;">else </span><span>{
</span><span> None
</span><span> }
</span><span>});
</span></code></pre>
<h3 id="conclusion">Conclusion</h3>
<p>I think these blocks are nice to quickly hack something together and can make up for a quite a few missing blocks.
Performance-wise, there might be drawbacks. The compiler would have to be really smart to figure out that it could use SIMD instructions when adding streams, for example.</p>
<p>Still, we think that these blocks show the bright side of using Rust. While it would be possible to implement similar blocks in other languages and other SDR frameworks, function closures and iterators are really fun with Rust.</p>
<p>We hope you give them a try :-)</p>
Hello World!2021-08-27T00:00:00+00:002021-08-27T00:00:00+00:00https://www.futuresdr.org/blog/hello-world/<p>We are not perfect, but we are here :-) Yay! A lot of stuff is still very much
in the flow, but we think that the project reached a state, where it might be
interesting for some.</p>
<p>After a lot of refactoring, we believe that the main components are in place and
development is more fun, working on bugs and issues that are more local, i.e.,
one doesn’t have to change bits across the whole code base to fix something :-)</p>
<p>FutureSDR implements several new concepts. We hope you have some fun
playing around with them. So happy hacking and please get in touch with us on GitHub or
Discord, if you have questions, comments, or feedback.</p>