Skip to content

File pn_core.h

FileList > doppler > pn > pn_core.h

Go to the source code of this file

PN component API. More...

  • #include "doppler/clib_common.h"
  • #include "doppler/dp_state.h"
  • #include "doppler/jm_perf.h"

Classes

Type Name
struct dp_pn_state_t

Public Types

Type Name
enum pn__core_8h_1abc6126af1d45847bc59afa0aa3216b04
PN state.

Public Functions

Type Name
dp_pn_state_t * dp_pn_create (uint64_t poly, uint64_t seed, uint32_t length, int lfsr)
Allocate and initialise a maximal-length-sequence LFSR. The register is seeded from seed and will produce a pseudo-random binary sequence with period 2^length - 1 for any primitivepoly . Both Galois and Fibonacci realizations share the same primitive polynomial and therefore the same period; they differ only in chip ordering/phase.
dp_pn_state_t * dp_pn_create_mls (uint64_t poly, uint64_t seed, uint32_t length, int lfsr)
Allocate and initialise a maximal-length-sequence LFSR. The register is seeded from seed and will produce a pseudo-random binary sequence with period 2^length - 1 for any primitivepoly . Both Galois and Fibonacci realizations share the same primitive polynomial and therefore the same period; they differ only in chip ordering/phase. Leavepoly at 0 and the maximal-length polynomial forlength is used, soPN(seed=1, length=7) is the 127-chip m-sequence.
void dp_pn_destroy (dp_pn_state_t * state)
Destroy a pn instance and release all memory. Idempotent when state is NULL; safe to call at any point in the lifecycle. After return the pointer is dangling — do not dereference it.
size_t dp_pn_generate (dp_pn_state_t * state, size_t n, uint8_t * out, size_t max_out)
Generate n chips intoout and advance the LFSR byn positions. Each element ofout is 0 or 1. Requesting more than one MLS period is valid — the sequence simply wraps around. The Python binding returns a zero-copy NumPy uint8 view over a pre-allocated buffer; copy the result before calling generate again if you need a snapshot.
size_t dp_pn_generate_max_out (dp_pn_state_t * state)
void dp_pn_get_state (const dp_pn_state_t * state, void * blob)
Serialize the LFSR register into blob .
void dp_pn_reset (dp_pn_state_t * state)
Reset PN to its post-create state. Reloads the LFSR register from the original seed so the sequence restarts from chip 0. Useful for reproducible captures without re-allocating.
int dp_pn_set_state (dp_pn_state_t * state, const void * blob)
Restore the register; DP_OK, or DP_ERR_INVALID if rejected.
size_t dp_pn_state_bytes (const dp_pn_state_t * state)
Serialized-state byte size.
JM_FORCEINLINE int pn_fits_register (uint64_t v, uint32_t n)
Non-zero when v fits a register ofn bits: no bit at or above bitn .
JM_FORCEINLINE uint64_t pn_mls_poly (uint32_t n)
Maximal-length-sequence (MLS) primitive polynomial for a register of length n , in this module's right-shift Galois convention.
JM_FORCEINLINE uint64_t pn_register_mask (uint32_t n)
The bits of an n-bit register: whatdp_pn_create() anddp_gold_create() keep of a seed, a poly or a tap set. All ones at 64.
JM_FORCEINLINE uint8_t pn_step (dp_pn_state_t * state)
Advance the LFSR one step and return the output chip (0 or 1). Both realizations output the register LSB and then shift right. Galois XORs the tap polynomial on a 1 output bit (internal feedback); Fibonacci computes the parity of all tapped positions and inserts it at the top (external feedback). Same primitive polynomial, same period. Inlined so per-sample modulators (e.g. synth's bpsk/qpsk data source) can pull chips in a tight hot loop without call overhead.

Macros

Type Name
define PN_STATE_MAGIC [**DP\_FOURCC**](dp__state_8h.md#define-dp_fourcc)('P', 'N', '\_', '\_')
define PN_STATE_VERSION 1u

Detailed Description

Lifecycle: create -> [step / steps / reset]* -> destroy

Example:

dp_pn_state_t *obj = dp_pn_create(96, 1, 7);
uint8_t y = pn_step(obj);
dp_pn_destroy(obj);

Public Types Documentation

enum pn__core_8h_1abc6126af1d45847bc59afa0aa3216b04

PN state.

enum pn__core_8h_1abc6126af1d45847bc59afa0aa3216b04 {
    PN_GALOIS = 0,
    PN_FIBONACCI = 1
};

Allocate with dp_pn_create(). LFSR realization: Galois (internal XOR) or Fibonacci (external XOR).


Public Functions Documentation

function dp_pn_create

Allocate and initialise a maximal-length-sequence LFSR. The register is seeded from seed and will produce a pseudo-random binary sequence with period 2^length - 1 for any primitivepoly . Both Galois and Fibonacci realizations share the same primitive polynomial and therefore the same period; they differ only in chip ordering/phase.

dp_pn_state_t * dp_pn_create (
    uint64_t poly,
    uint64_t seed,
    uint32_t length,
    int lfsr
) 

Parameters:

  • poly Galois feedback tap polynomial (right-shift convention). The LSB is the tap at position 0 (always 1 for a primitive poly); bit k=1 means tap at position k. Default 96 (0x60) is primitive for length=7, giving period 127. The Fibonacci taps are derived automatically so you only supply one value.
  • seed Initial LFSR register state; must be non-zero WITHIN the register, seed & pn_register_mask (length) the all-zero state is a fixed point, and a seed that masks to it (128 on 7 bits) is refused like 0. Default 1.
  • length Register width in bits, 1..64. The sequence period is 2^length - 1 for a primitive polynomial. Default 7.
  • lfsr Realization: PN_GALOIS (0, default) or PN_FIBONACCI (1).

Returns:

Heap-allocated state, or NULL on allocation failure.

Note:

Caller must call dp_pn_destroy() when done.

>>> from doppler.wfm import PN
>>> import numpy as np
>>> p = PN(poly=96, seed=1, length=7)
>>> chips = p.generate(127)
>>> chips.dtype
dtype('uint8')
>>> int(chips.sum())   # 64 ones per MLS period (2^(n-1))
64


function dp_pn_create_mls

Allocate and initialise a maximal-length-sequence LFSR. The register is seeded from seed and will produce a pseudo-random binary sequence with period 2^length - 1 for any primitivepoly . Both Galois and Fibonacci realizations share the same primitive polynomial and therefore the same period; they differ only in chip ordering/phase. Leavepoly at 0 and the maximal-length polynomial forlength is used, soPN(seed=1, length=7) is the 127-chip m-sequence.

dp_pn_state_t * dp_pn_create_mls (
    uint64_t poly,
    uint64_t seed,
    uint32_t length,
    int lfsr
) 

Parameters:

  • poly Galois feedback tap polynomial (right-shift convention). The LSB is the tap at position 0 (always 1 for a primitive poly); bit k=1 means tap at position k. 0 (the default) selects the maximal-length polynomial for length. The Fibonacci taps are derived automatically so you only supply one value.
  • seed Initial LFSR register state; must be non-zero WITHIN the register, seed & pn_register_mask (length) the all-zero state is a fixed point, and a seed that masks to it (128 on 7 bits) is refused like 0.
  • length Register width in bits, 1..64. The sequence period is 2^length - 1 for a primitive polynomial.
  • lfsr Realization: PN_GALOIS (0, default) or PN_FIBONACCI (1).

Returns:

Heap-allocated state, or NULL on a refused argument or allocation failure.

Note:

Caller must call dp_pn_destroy() when done. A width with no m-sequence (anything outside 2..64) leaves poly at 0, which dp_pn_create() takes verbatim: a register with no feedback.

>>> from doppler.wfm import PN
>>> p = PN(poly=96, seed=1, length=7)
>>> chips = p.generate(127)
>>> chips.dtype
dtype('uint8')
>>> int(chips.sum())   # 64 ones per MLS period (2^(n-1))
64
>>> int(PN(seed=1, length=7).generate(127).sum())
64


function dp_pn_destroy

Destroy a pn instance and release all memory. Idempotent when state is NULL; safe to call at any point in the lifecycle. After return the pointer is dangling — do not dereference it.

void dp_pn_destroy (
    dp_pn_state_t * state
) 

Parameters:

  • state Pointer to heap-allocated state; may be NULL (no-op).
    >>> from doppler.wfm import PN
    >>> p = PN(poly=96, seed=1, length=7)
    >>> p.destroy()   # explicit teardown; no exception
    

function dp_pn_generate

Generate n chips intoout and advance the LFSR byn positions. Each element ofout is 0 or 1. Requesting more than one MLS period is valid — the sequence simply wraps around. The Python binding returns a zero-copy NumPy uint8 view over a pre-allocated buffer; copy the result before calling generate again if you need a snapshot.

size_t dp_pn_generate (
    dp_pn_state_t * state,
    size_t n,
    uint8_t * out,
    size_t max_out
) 

Parameters:

  • state Initialised PN state returned by dp_pn_create.
  • n Number of chips to produce.
  • out Output buffer of at least n uint8 elements; each element receives 0 or 1.
  • max_out Capacity of out in elements. Emission stops there, so the return value is the number actually written.

Returns:

min(n, max_out) chips.

>>> from doppler.wfm import PN
>>> import numpy as np
>>> p = PN(poly=96, seed=1, length=7)
>>> chips = p.generate(127)
>>> chips[:8].tolist()
[1, 0, 0, 0, 0, 0, 1, 1]
>>> int(chips.sum())   # 64 ones per MLS period
64


function dp_pn_generate_max_out

size_t dp_pn_generate_max_out (
    dp_pn_state_t * state
) 

function dp_pn_get_state

Serialize the LFSR register into blob .

void dp_pn_get_state (
    const dp_pn_state_t * state,
    void * blob
) 


function dp_pn_reset

Reset PN to its post-create state. Reloads the LFSR register from the original seed so the sequence restarts from chip 0. Useful for reproducible captures without re-allocating.

void dp_pn_reset (
    dp_pn_state_t * state
) 

Parameters:

  • state Must be non-NULL.
    >>> from doppler.wfm import PN
    >>> import numpy as np
    >>> p = PN(poly=96, seed=1, length=7)
    >>> a = p.generate(8).copy()
    >>> p.reset()
    >>> np.array_equal(a, p.generate(8))
    True
    

function dp_pn_set_state

Restore the register; DP_OK, or DP_ERR_INVALID if rejected.

int dp_pn_set_state (
    dp_pn_state_t * state,
    const void * blob
) 


function dp_pn_state_bytes

Serialized-state byte size.

size_t dp_pn_state_bytes (
    const dp_pn_state_t * state
) 


function pn_fits_register

Non-zero when v fits a register ofn bits: no bit at or above bitn .

JM_FORCEINLINE int pn_fits_register (
    uint64_t v,
    uint32_t n
) 

The other half of the convention pn_mls_poly() states. dp_pn_create() and dp_gold_create() MASK a poly, a tap set or a seed to the register, silently, so a value with a bit above it is a different register from the one written: 0x40 on a 5-bit register is no feedback at all, and a seed of 32 is the all-zero register. Every caller that takes these numbers from a person refuses what this rejects, rather than letting the mask repair it the Field grammar (doppler#1624) and a source's pn_poly (doppler#1636).

pn_fits_register (0x12, 5);   // 1: x^5 + x^2 + 1's taps, inside 5 bits
pn_fits_register (0x40, 5);   // 0: bit 6, above the register
pn_fits_register (~0ull, 64); // 1: a 64-bit register holds every value

function pn_mls_poly

Maximal-length-sequence (MLS) primitive polynomial for a register of length n , in this module's right-shift Galois convention.

JM_FORCEINLINE uint64_t pn_mls_poly (
    uint32_t n
) 

The table lives here because the convention is pn's: poly is dp_pn_create()'s tap mask, and "which mask makes it maximal-length" is a fact about this LFSR, not about any caller. It is header-only so no component grows a link-line dependency for a lookup.

A zero poly is not a polynomial. dp_pn_create() takes the mask verbatim, so poly = 0 is a register with no feedback: it shifts out the seed and then emits zeros forever. Every caller that lets a user say "default" therefore resolves it as poly ? poly : pn_mls_poly (n) wfm_synth's create does, wfm_frame's PN sequence kind does, and dp_pn_create_mls() is that resolution as a constructor (the PN binding calls it). A caller that forgets gets a constant field that still looks like a field.

Returns 0 for lengths outside 2..64 (caller errors). Generated from verified primitive polynomials (period 2^n-1); the n=2..16 values are unchanged.


function pn_register_mask

The bits of an n-bit register: whatdp_pn_create() anddp_gold_create() keep of a seed, a poly or a tap set. All ones at 64.

JM_FORCEINLINE uint64_t pn_register_mask (
    uint32_t n
) 

A seed is refused when (seed & pn_register_mask (n)) == 0, not when seed == 0: 128 on a 7-bit register is non-zero and masks to the all-zero register, the fixed point the rule exists to keep out (doppler#1640). A wide seed that masks to non-zero 129 to 1 is fine, and documented.


function pn_step

Advance the LFSR one step and return the output chip (0 or 1). Both realizations output the register LSB and then shift right. Galois XORs the tap polynomial on a 1 output bit (internal feedback); Fibonacci computes the parity of all tapped positions and inserts it at the top (external feedback). Same primitive polynomial, same period. Inlined so per-sample modulators (e.g. synth's bpsk/qpsk data source) can pull chips in a tight hot loop without call overhead.

JM_FORCEINLINE uint8_t pn_step (
    dp_pn_state_t * state
) 

Parameters:

  • state Must be non-NULL.

Returns:

Output chip: 0 or 1 (register LSB before the shift).


Macro Definition Documentation

define PN_STATE_MAGIC

#define PN_STATE_MAGIC `DP_FOURCC ('P', 'N', '_', '_')`

define PN_STATE_VERSION

#define PN_STATE_VERSION `1u`


The documentation for this class was generated from the following file native/inc/doppler/pn/pn_core.h