Can a tagged stream block be a source block?

Hi

I am implementing a complete protocol in a module where most of the MAC
layer will be placed in a tagged stream block. I intend to start out
implementing the transmit chain and then the receive chain.
The problem I have run into is that until I get to the receive chain,
the
MAC layer block will need to function as a source, but is that at all
possible based on a tagged stream block?

At the moment the block crashes (silently) in line 102 of the
gr::tagged_stream_block::general_work.
The line in question contains: if(d_n_input_items_reqd[0] == 0)

I believe the cause of the crash is that d_n_input_items_reqd is not
set
or not set to a valid value by the parse_length_tags method, since it
is
responsible for extracting the length tags on all input ports, but this
block, being configured as a source block, does not have any input
ports.

Would it be possible to overcome this by overriding the
*parse_length_tags *method
and setting d_n_input_items_reqd to something valid?
And if yes, what would be a valid value?

My implementation of calculate_output_stream_length just returns a
constant integer.

Best regards
David Marmoy

For convenience here is the relevant code from gr::tagged_stream_block:

void
tagged_stream_block::parse_length_tags(const
std::vector<std::vector<tag_t> > &tags,
gr_vector_int
&n_input_items_reqd)
{
for(unsigned i = 0; i < tags.size(); i++) {
for(unsigned k = 0; k < tags[i].size(); k++) {
if(tags[i][k].key == d_length_tag_key) {
n_input_items_reqd[i] = pmt::to_long(tags[i][k].value);
remove_item_tag(i, tags[i][k]);
}
}
}
}

int
tagged_stream_block::general_work(int noutput_items,
gr_vector_int &ninput_items,
gr_vector_const_void_star
&input_items,
gr_vector_void_star &output_items)
{
if(d_length_tag_key_str.empty()) {
return work(noutput_items, ninput_items, input_items,
output_items);
}

if(d_n_input_items_reqd[0] == 0) { // Otherwise, it's already set 

from
a previous call
std::vector<std::vector<tag_t> > tags(input_items.size(),
std::vector<tag_t>());
for(unsigned i = 0; i < input_items.size(); i++) {
get_tags_in_range(tags[i], i, nitems_read(i), nitems_read(i)+1);
}
d_n_input_items_reqd.assign(input_items.size(), -1);
parse_length_tags(tags, d_n_input_items_reqd);
}
for(unsigned i = 0; i < input_items.size(); i++) {
if(d_n_input_items_reqd[i] == -1) {
GR_LOG_FATAL(d_logger, boost::format(“Missing a required length
tag
on port %1% at item #%2%”) % i % nitems_read(i));
throw std::runtime_error(“Missing length tag.”);
}
if(d_n_input_items_reqd[i] > ninput_items[i]) {
return 0;
}
}

int min_output_size =

calculate_output_stream_length(d_n_input_items_reqd);
if(noutput_items < min_output_size) {
set_min_noutput_items(min_output_size);
return 0;
}
set_min_noutput_items(1);

// WORK CALLED HERE //
int n_produced = work(noutput_items, d_n_input_items_reqd, 

input_items,
output_items);
//////////////////////

if(n_produced == WORK_DONE) {
  return n_produced;
}
for(int i = 0; i < (int) d_n_input_items_reqd.size(); i++) {
  consume(i, d_n_input_items_reqd[i]);
}
if (n_produced > 0) {
  update_length_tags(n_produced, output_items.size());
}

d_n_input_items_reqd.assign(input_items.size(), 0);

return n_produced;

}

On 12/04/2014 02:17 PM, David Marmoy wrote:

I am implementing a complete protocol in a module where most of the MAC
layer will be placed in a tagged stream block. I intend to start out
implementing the transmit chain and then the receive chain.
The problem I have run into is that until I get to the receive chain,
the MAC layer block will need to function as a source, but is that at
all possible based on a tagged stream block?

Short answer: Erh, maybe. But not without changing the runtime.

Long answer: This a known shortcoming of tagged stream blocks. For 3.8,
we will most likely be changing the structure of those blocks, and this
will become possible.

Right now, we could update the code in tagged_stream_block to make this
happen, but it would involve more testing and branches in the work
function. It would not be a big problem to fix, though, you’re just the
first to actually request this.

Actually, it’s probably something I should fix before people start
writing tags by hand. David, until I have that ready you should change
that offending line to:

102 if(!d_n_input_items_reqd.empty() && d_n_input_items_reqd[0] ==
0) { // Otherwise, it’s already set from a previous call

parse_length_tags() will then never be called.
calculate_output_stream_length() however should return a valid length.

Cheers,
M

On 12/04/2014 04:08 PM, Martin B. wrote:

Actually, it’s probably something I should fix before people start
writing tags by hand. David, until I have that ready you should change
that offending line to:

102 if(!d_n_input_items_reqd.empty() && d_n_input_items_reqd[0] ==
0) { // Otherwise, it’s already set from a previous call

parse_length_tags() will then never be called.
calculate_output_stream_length() however should return a valid length.

This was actually simpler than expected:
https://github.com/mbr0wn/gnuradio/tree/tsb/allow_source

I also put an example in there (see the 2nd commit).

Cheers,
M

This was actually simpler than expected:
https://github.com/mbr0wn/gnuradio/tree/tsb/allow_source

I also put an example in there (see the 2nd commit).

Just to understand the implications of this change to pdu_to_tagged
stream, max input PDU size is now effectively constrained by the max
buffer size that can be handled by the scheduler and passed to work().
Correct? That may not be a limitation for many people, and I suppose it
would have surfaced before if someone tried to send a larger PDU into a
pdu_to_tagged_stream block connected to a downstream tsb-derived block.

I guess the upside is that now you only need one call to work() per PDU
and you don’t need an internal buffer and a stateful work() to push a
PDU through.

Sean

On 12/04/2014 10:23 PM, Nowlan, Sean wrote:

larger PDU into a pdu_to_tagged_stream block connected to a
downstream tsb-derived block.

Exactly. It kind of adds a new constraint, but not really, because
downstream blocks already bring this constraint with them.

I guess the upside is that now you only need one call to work() per
PDU and you don’t need an internal buffer and a stateful work() to
push a PDU through.

Not quite, unfortunately: With or without usage of TSBs, we need to spin
inside work. I gave it a rough benchmark (using htop), and both versions
of pdu_to_tagged_stream use up a lot of cycles. This would probably be
better if we blocked on popping the message Q, but that’s something we
don’t want in work functions.

The statefulness is still gone, though. If we’re spinning on work
because the Q was empty, it returns pretty much immediately. Otherwise,
the work() function is much tidier and uses less copying to intermediate
buffers.

We could probably sack all the class attributes if we popped the message
Q inside work() instead of (ab-)using calculate_output_stream_length(),
but the point of that method is to provide the parent block with the
correct output length, for which we need to read the PDU.

More importantly, we remove TSB tag handling from that block. If we go
to tagging the last item, we want no blocks to do the TSB tag handling
themselves.

M