Add new signal generator method that only creates the PRBS

This commit is contained in:
2026-04-21 14:20:48 +02:00
parent 20b23dc37e
commit 0ef8129a2c
5 changed files with 975 additions and 5 deletions

View File

@@ -1,12 +1,63 @@
classdef Signalgenerator
%NAME Summary of this class goes here
% Detailed explanation goes here
%SIGNALGENERATOR Generate simple information-level test sequences.
%
% This class creates Informationsignal objects for quick simulations,
% mapper checks, and theory scripts. Use the "form" option to select
% the generated sequence type.
%
% Examples:
%
% % Sine wave
% sig = Signalgenerator( ...
% "form", signalform.sine, ...
% "length", 1024, ...
% "fs", 1000, ...
% "fsig", 50).process();
%
% % Deterministic random bit matrix, 2 bits per row
% bits = Signalgenerator( ...
% "form", signalform.random, ...
% "length", 1024, ...
% "dimension", 2, ...
% "randkey", 3).process();
%
% % PRMS bit matrix for PAM-4 mapping. If length is omitted, the
% % output length is derived from order, as in the old PAMsource.
% bits = Signalgenerator( ...
% "form", signalform.prms, ...
% "dimension", 2, ...
% "order", 7).process();
% symbols = PAMmapper(4, 0).map(bits);
%
% % PRMS for PAM-6: 5 bits map to 2 PAM-6 symbols
% bits = Signalgenerator( ...
% "form", signalform.prms, ...
% "dimension", 5, ...
% "order", 7).process();
% bitVector = reshape(bits.signal.', [], 1);
% symbols = PAMmapper(6, 0).map(bitVector);
%
% % Map, demap, and check BER
% mapper = PAMmapper(8, 0);
% bits = Signalgenerator( ...
% "form", signalform.prms, ...
% "length", 1024, ... % explicit output length override
% "dimension", 3, ...
% "order", 5).process();
% symbols = mapper.map(bits);
% rxBits = mapper.demap(symbols);
% [checkedBits, errors, ber] = calc_ber(rxBits, bits.signal);
properties(Access=public)
form
length
fs
fsig
dimension
order
randkey
skip
bruijn
end
@@ -17,9 +68,14 @@ classdef Signalgenerator
arguments
options.form signalform = signalform.sine
options.length double = 1024
options.length double = []
options.fs double = 1000 %Hz sampling
options.fsig double = 50 % Hz fundamental frex e.g. of the sine or sawtooth
options.dimension double = 1
options.order double = 7
options.randkey double = 0
options.skip double = 0
options.bruijn logical = false
end
%
@@ -30,6 +86,10 @@ classdef Signalgenerator
end
end
if isempty(obj.length)
obj.length = obj.default_length();
end
end
function signalclass_out = process(obj)
@@ -69,7 +129,12 @@ classdef Signalgenerator
% Generate sine wave
signal = A * sin(2*pi*f*t);
case signalform.noise
s = RandStream('twister','Seed',obj.randkey);
signal = randn(s, 1, obj.length);
case signalform.random
signal = obj.build_random_data();
case signalform.prms
signal = obj.build_prms_data();
end
@@ -82,6 +147,317 @@ classdef Signalgenerator
% Cant be seen from outside! So put all your functions here that can/
% shall not be called from outside
function signal = build_random_data(obj)
arguments(Input)
obj
end
s = RandStream('twister','Seed',obj.randkey);
signal = randi(s, [0 1], obj.length, obj.dimension);
end
%BUILD_PRMS_DATA Generate pseudo-random multi-level sequence bits.
%
% The output is a binary matrix with size:
% obj.length x obj.dimension
%
% Each row is one generated bit group / symbol time. Each column is
% one parallel PRMS bit stream. For conventional PAM formats this
% means:
% PAM-2: dimension = 1
% PAM-4: dimension = 2
% PAM-8: dimension = 3
%
% PAM-6 is special in this codebase: the mapper consumes 5 bits and
% maps them to two PAM-6 symbols. Use dimension = 5, then flatten
% the output before calling PAMmapper(6, ...).map(...).
%
% Relevant Signalgenerator options:
% length Number of generated PRMS bit groups / rows.
% dimension Number of parallel bit streams per row.
% order User-facing sequence order, matching the old
% PAMsource behavior. If length is omitted, it sets the
% output length. Internally it is converted to the PRMS
% order per stream.
% Default output length is 2^(order-1), except for
% PAM-6-style dimension = 5 where it is 2^(order-2).
% skip Number of PRMS symbols to advance before output. This
% is useful when different blocks should use shifted
% sections of the same deterministic sequence.
% bruijn If true, enables de Bruijn-style zero-symbol insertion
% as in the legacy MOVE-IT generator.
%
% The implementation mirrors the legacy MOVE-IT/prms_c register
% logic in plain MATLAB, so no mex build is required.
function signal = build_prms_data(obj)
arguments(Input)
obj
end
obj.validate_prms_parameters();
state.dimension = obj.dimension;
state.order = obj.internal_prms_order();
state.periodicity = state.dimension * state.order;
state.bl = obj.length;
state.bruijn = obj.bruijn;
state.bruijn_counter = -1;
state.srgtaps = Signalgenerator.srgtap_masks(state.periodicity);
mat_table = Signalgenerator.build_prms_mapping(state.dimension);
state.mat_table = mat_table * 2.^(0:state.dimension-1).';
state.reg_mask = 2^state.periodicity - 1;
state.data_mask = 2^(state.periodicity - 1);
tmp_reg = 2^49 - 1;
tmp_reg = Signalgenerator.advance_prbs_register(tmp_reg, state.srgtaps, 1, state.reg_mask);
state.reg = zeros(state.dimension, 1);
state.reg(1) = tmp_reg;
modulo_mask = 2^state.dimension - 1;
offset = (2^state.periodicity - 1) / modulo_mask;
for n = 2:state.dimension
tmp_reg = Signalgenerator.advance_prbs_register(tmp_reg, state.srgtaps, offset, state.reg_mask);
state.reg(n) = tmp_reg;
end
if obj.skip > 0
state = Signalgenerator.advance_prms_state(state, obj.skip);
end
[data_out, ~] = Signalgenerator.generate_prms_block(state, state.bl);
signal = data_out.';
end
function validate_prms_parameters(obj)
if obj.length < 1 || fix(obj.length) ~= obj.length
error("Signalgenerator:InvalidLength", ...
"PRMS length must be a positive integer.");
end
if obj.dimension < 1 || fix(obj.dimension) ~= obj.dimension
error("Signalgenerator:InvalidDimension", ...
"PRMS dimension must be a positive integer.");
end
if obj.order < 1 || fix(obj.order) ~= obj.order
error("Signalgenerator:InvalidOrder", ...
"PRMS order must be a positive integer.");
end
if obj.skip < 0 || fix(obj.skip) ~= obj.skip
error("Signalgenerator:InvalidSkip", ...
"PRMS skip must be a nonnegative integer.");
end
if obj.dimension * obj.internal_prms_order() > 48
error("Signalgenerator:UnsupportedOrder", ...
"PRMS dimension * internal PRMS order must be <= 48.");
end
end
function length = default_length(obj)
switch obj.form
case {signalform.random, signalform.prms}
if obj.dimension == 5
length = 2^max(0, obj.order - 2);
else
length = 2^max(0, obj.order - 1);
end
otherwise
length = 1024;
end
end
function prms_order = internal_prms_order(obj)
if obj.dimension == 5
bits_per_symbol = log2(6);
else
bits_per_symbol = obj.dimension;
end
prms_order = max(1, floor(obj.order / bits_per_symbol));
end
end
methods (Static, Access=private)
function state = advance_prms_state(state, number_symbols)
[~, state] = Signalgenerator.generate_prms_block(state, number_symbols);
end
function [data_out, state] = generate_prms_block(state, block_length)
data_out = zeros(state.dimension, block_length);
counter = 0;
while counter < block_length
if state.bruijn == 2
counter = counter + 1;
state.bruijn = 1;
continue;
end
for n = 1:state.dimension
prms_tmp_value = false;
for m = 1:state.dimension
prms_tmp_value = xor(prms_tmp_value, ...
state.reg(m) >= state.data_mask && ...
bitand(state.mat_table(m), 2^(n-1)) ~= 0);
end
data_out(n, counter + 1) = prms_tmp_value;
end
for n = 1:state.dimension
state.reg(n) = Signalgenerator.advance_prbs_register( ...
state.reg(n), state.srgtaps, 1, state.reg_mask);
end
counter = counter + 1;
if state.bruijn == 1
if state.bruijn_counter < 0
if any([~sum(data_out(:, counter)) state.order == 1])
state.bruijn_counter = state.bruijn_counter - 1;
if any([state.bruijn_counter == -state.order state.order == 1])
state.bruijn_counter = 2^state.periodicity - 1;
state.bruijn = 2;
end
else
state.bruijn_counter = -1;
end
else
state.bruijn_counter = state.bruijn_counter - 1;
if state.bruijn_counter == 0
state.bruijn_counter = 2^state.periodicity - 1;
state.bruijn = 2;
end
end
end
end
end
function reg = advance_prbs_register(reg, tap_masks, number_steps, reg_mask)
for idx = 1:number_steps
feedback = false;
for tap_idx = 1:numel(tap_masks)
if tap_masks(tap_idx) ~= 0
feedback = xor(feedback, bitand(reg, tap_masks(tap_idx)) ~= 0);
end
end
reg = bitand(reg * 2 + double(feedback), reg_mask);
end
end
function mat_table = build_prms_mapping(dimension)
if dimension > 2
bchpolynomial = zeros(1, dimension + 1);
bchpolynomial([1 Signalgenerator.srgtaps(dimension) + 1]) = 1;
betatable = fliplr(Signalgenerator.cyclgen_local(2^dimension - 1, bchpolynomial).');
elseif dimension == 2
betatable = [0 1; 1 0; 1 1];
else
betatable = 1;
end
binvec = 2.^(dimension-1:-1:0);
modulo_mask = 2^dimension - 1;
betatable_sort_forward = sum(repmat(binvec, 2^dimension - 1, 1) .* betatable, 2);
[~, betatable_sort_inverse] = sort(betatable_sort_forward);
betatable_sort_inverse = betatable_sort_inverse - 1;
dividend = zeros(dimension + 1, 2);
dividend([dimension - Signalgenerator.srgtaps(dimension) + 1 end], 1) = 1;
h = zeros(dimension, 2);
for digit = 1:dimension
h(digit, 1:2) = dividend(digit, 1:2);
dividend(digit, 1:2) = [0 0];
newdiv_binary = xor( ...
betatable(dividend(digit + 1, 2) + 1, :) * dividend(digit + 1, 1), ...
betatable(rem(h(digit, 2) + 1, modulo_mask) + 1, :) * h(digit, 1));
dividend(digit + 1, 1) = sum(newdiv_binary) > 0;
if (digit ~= dimension) && dividend(digit + 1, 1)
dividend(digit + 1, 2) = dividend(digit + 1, 1) * ...
betatable_sort_inverse(sum(newdiv_binary .* binvec));
else
dividend(digit + 1, 2) = 0;
end
end
mat_table = betatable(h(:, 2) + 1, :);
end
function code = cyclgen_local(columns, polynomial)
rows = log2(columns + 1);
code = zeros(rows, columns);
code(1, 1) = 1;
for s = 2:columns
code(:, s) = [0; code(1:end-1, s-1)];
if code(end, s-1)
code(:, s) = rem(code(:, s) + polynomial(1:end-1).', 2);
end
end
end
function taps = srgtaps(inx)
data = {[1] ...
[2 1] ...
[3 1] ...
[4 1] ...
[5 2] ...
[6 1] ...
[7 1] ...
[8 7 2 1] ...
[9 4] ...
[10 3] ...
[11 2] ...
[12 10 2 1] ...
[13 8 5 3] ...
[14 12 11 1] ...
[15 1] ...
[16 15 12 10] ...
[17 3] ...
[18 7] ...
[19 10 9 3] ...
[20 3] ...
[21 2] ...
[22 1] ...
[23 5] ...
[24 11 5 2] ...
[25 3] ...
[26 23 15 13] ...
[27 23 22 17] ...
[28 3] ...
[29 2] ...
[30 27 10 9] ...
[31 3] ...
[32 16 7 2] ...
[33 13] ...
[34 17 12 8] ...
[35 2] ...
[36 11] ...
[37 22 14 2] ...
[38 27 6 5] ...
[39 4] ...
[40 29 27 23] ...
[41 3] ...
[42 34 31 30] ...
[43 27 22 5] ...
[44 39 35 18] ...
[45 39 28 4] ...
[46 40 31 18] ...
[47 5] ...
[48 19 9 1]};
taps = data{inx};
end
function masks = srgtap_masks(inx)
taps = Signalgenerator.srgtaps(inx);
masks = zeros(1, 4);
masks(1:numel(taps)) = 2.^(taps - 1);
end
end
end