Fields — bits, written as text¶
Every place wfmgen takes a run of bits takes it the same way: as one
Field, a short piece of text such as 0x1ACFFC1D or pn:1023:10. The
command line's --data, --fill, --sync, --acq-code and --data-code, a scene's
"spec", Python's field_bits() and C's dp_wfm_field_parse() all read it
with one parser, so a Field means the same bits on every face. This page
is the grammar; the reasoning behind it is the design's
§F.
The forms¶
| write | means |
|---|---|
1101 |
the bits themselves, left to right |
0xA5 |
hex, 4 bits a digit, most significant first |
pn:LEN:REG[:SEED[:POLY]][:fibonacci] |
LEN bits from one REG-bit LFSR. SEED 0 selects 1; POLY 0 the maximal-length one |
gold:LEN:REG:TA:SA:TB:SB |
LEN bits of a Gold code: two REG-bit registers, taps and seeds |
dotted:LEN |
1010…, LEN bits, starting high |
Any of them may end in *REPS, the same bits again REPS times.
>>> from doppler.wfm import field_bits
>>> field_bits("1101").tolist()
[1, 1, 0, 1]
>>> field_bits("0xA5").tolist()
[1, 0, 1, 0, 0, 1, 0, 1]
>>> field_bits("pn:15:4").tolist()
[1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0]
>>> field_bits("dotted:6").tolist()
[1, 0, 1, 0, 1, 0]
>>> field_bits("0101*3").tolist()
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
LEN is the number of bits that come out; REG is the width of the
register that makes them. They are different numbers: pn:15:4 is one whole
period of a 4-bit register, and pn:100:4 is the same period cycled.
A repetition is the same bits again, never a fresh draw — a preamble repeated four times is one period four times, which is what a receiver integrates over:
>>> import numpy as np
>>> np.array_equal(field_bits("pn:31:5*4"), np.tile(field_bits("pn:31:5"), 4))
True
Numbers¶
Every number — LEN, REG, SEED, POLY, the Gold taps and seeds, REPS —
is decimal, or hex after 0x, so a tap mask reads the way the literature
writes it. A leading 0 is decimal, never octal. A number is read whole:
12abc, -1, 5 and an empty pn::10 are refused, not read as far as
they go.
The limits¶
| limit | why |
|---|---|
LEN ≥ 1, REPS ≥ 1 |
a Field is never empty |
REG is 1 to 64 |
the register is a 64-bit word |
SEED, POLY, taps fit in REG |
a wider number would be masked, silently: pn:31:5:32 is the all-zero register |
a 1-bit REG needs a POLY |
one bit has no maximal-length polynomial to default to |
LEN × REPS ≤ 261120 |
a longer run is a stream, not a Field; derived here |
On the command line, *REPS belongs to --acq-code alone, because a
preamble is the one field that repeats: a sync word or a payload sent twice is
not what those flags mean, so --sync 'pn:31:5*2' is refused. Quote a Field
with a * in it, or the shell globs it.
Refused, never repaired¶
A Field outside the grammar is refused with a sentence naming the rule, and
nothing is built. A 0/1 string with a stray character is the case that
matters most: a typo that quietly shortened a sync word would still assemble,
and sync to nothing.
# Each line exits 2 and says why.
! wfmgen --type bits --data 01a1 -o x.cf32
! wfmgen --type bits --data pn:31:5:32 -o x.cf32
! wfmgen --type bits --data pn:4000000000:5 -o x.cf32
error: --data 01a1: a binary literal holds a character other than 0 or 1
error: --data pn:31:5:32: a pn SEED or POLY has a bit above its REG-bit register
error: --data pn:4000000000:5: LEN * REPS is past the Field bound of 261120 bits
One way to write each Field¶
Two spellings of one Field are the same Field, and it has one canonical
text: a literal as hex when its length is a multiple of 4 and as bits
otherwise; a generator with only what differs from its defaults; *REPS only
past one. --record writes that text, so a capture's metadata says what
produced its bits:
wfmgen --type bits --data 0101010111001010 \
--sync pn:0x1f:5:0:0:galois --acq-code '1111*2' \
--record run.json -o framed.cf32
grep -E '"(data|spec)"' run.json
The preamble, the sync word and the payload are the fields of the frame description the flags spell, so the record stores them there, as Fields.
From C¶
The same parser, and the same refusals. This is
native/examples/wfm_field_demo.c, which make test-examples-c builds and
runs; its Python twin is src/doppler/examples/wfm_field_demo.py.
/**
* wfm_field_demo.c — bits, written as text: the Field grammar from C.
*
* Every place wfmgen takes a run of bits — `--data`, `--sync`, `--acq-code`,
* `--data-code`, a scene's "spec", Python's field_bits() — reads it with ONE
* parser, dp_wfm_field_parse(). This is that parser's C face, and the twin of
* src/doppler/examples/wfm_field_demo.py.
*
* What this demonstrates, in order:
*
* 1. A Field is text in, bits out: size with a NULL buffer, then render.
* 2. Two spellings of one Field are the same Field, and it has ONE
* canonical text, which dp_wfm_field_format() writes and which parses
* back to the same field.
* 3. A repetition is the same bits again, never a fresh draw.
* 4. A Field outside the grammar is REFUSED with a sentence naming the
* rule — a stray character, a seed wider than its register, a length
* past the Field bound — and nothing is built.
*
* Every check is explicit and returns non-zero on failure; `assert()` is not
* used, because examples build Release and NDEBUG would compile the checks
* out.
*
* Build:
* make build && ./build/native/examples/wfm_field_demo
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "doppler/wfm/wfm_frame.h"
/* A Field's bits: the size, then the render. The caller frees. */
static uint8_t *
bits_of (const char *spec, size_t *n)
{
const char *why = NULL;
*n = dp_wfm_field_bits (spec, NULL, 0, &why);
if (*n == 0)
{
fprintf (stderr, " %s: %s\n", spec, why);
return NULL;
}
uint8_t *b = malloc (*n);
if (!b || dp_wfm_field_bits (spec, b, *n, NULL) != *n)
{
free (b);
return NULL;
}
return b;
}
int
main (void)
{
/* 1. Text in, bits out. */
size_t n;
uint8_t *b = bits_of ("0xA5", &n);
if (!b || n != 8)
return 1;
printf ("0xA5 ->");
for (size_t i = 0; i < n; i++)
printf (" %u", b[i]);
printf ("\n"); /* 0xA5 -> 1 0 1 0 0 1 0 1, most significant first */
free (b);
/* 2. One canonical text: the defaults and the radix are spelling. */
wfm_field_t f;
uint8_t *owned = NULL; /* a literal's bits; NULL for a generator */
if (dp_wfm_field_parse ("pn:0x1f:5:0:0:galois", &f, &owned, NULL) != 0)
return 1;
char text[64];
dp_wfm_field_format (&f, text, sizeof text);
printf ("pn:0x1f:5:0:0:galois is written %s\n", text);
if (strcmp (text, "pn:31:5") != 0)
return 1;
/* 3. *REPS repeats the SAME bits: one period, four times. */
size_t n1, n4;
uint8_t *one = bits_of ("pn:31:5", &n1);
uint8_t *four = bits_of ("pn:31:5*4", &n4);
if (!one || !four || n4 != 4 * n1)
return 1;
for (size_t r = 0; r < 4; r++)
if (memcmp (four + r * n1, one, n1) != 0)
return 1;
printf ("pn:31:5*4 is pn:31:5 four times over (%zu bits)\n", n4);
free (one);
free (four);
/* 4. Refused, never repaired: each names its rule. */
static const char *const bad[] = { "01a1", "pn:31:5:32", "pn:4000000000:5" };
for (size_t i = 0; i < sizeof bad / sizeof *bad; i++)
{
const char *why = NULL;
if (dp_wfm_field_parse (bad[i], &f, &owned, &why) == 0 || !why)
return 1;
printf ("%-16s refused: %s\n", bad[i], why);
}
return 0;
}
Related¶
- Waveforms — which flags take a Field, and framing
- Options reference — every flag and scene key
- The design, §F — why one grammar