515 lines
18 KiB
Matlab
515 lines
18 KiB
Matlab
classdef Signalgenerator
|
|
%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. M selects the mapper-ready bit
|
|
% % shape. If length is omitted, the output length is derived from
|
|
% % order, as in the old PAMsource.
|
|
% bits = Signalgenerator( ...
|
|
% "form", signalform.prms, ...
|
|
% "M", 4, ...
|
|
% "order", 7).process();
|
|
% symbols = PAMmapper(4, 0).map(bits);
|
|
%
|
|
% % PRMS for PAM-6: 5 bits map to 2 PAM-6 symbols. M = 6 returns
|
|
% % the 1-D bit vector expected by PAMmapper.
|
|
% bits = Signalgenerator( ...
|
|
% "form", signalform.prms, ...
|
|
% "M", 6, ...
|
|
% "order", 7).process();
|
|
% symbols = PAMmapper(6, 0).map(bits);
|
|
%
|
|
% % 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
|
|
M
|
|
order
|
|
randkey
|
|
skip
|
|
bruijn
|
|
|
|
end
|
|
|
|
methods (Access=public)
|
|
function obj = Signalgenerator(options)
|
|
%NAME Construct an instance of this class
|
|
% Detailed explanation goes here
|
|
|
|
arguments
|
|
options.form signalform = signalform.sine
|
|
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.M double = []
|
|
options.order double = 7
|
|
options.randkey double = 0
|
|
options.skip double = 0
|
|
options.bruijn logical = false
|
|
end
|
|
|
|
%
|
|
fn = fieldnames(options);
|
|
for n = 1:numel(fn)
|
|
try
|
|
obj.(fn{n}) = options.(fn{n});
|
|
end
|
|
end
|
|
|
|
if ~isempty(obj.M)
|
|
obj.validate_pam_format();
|
|
obj.dimension = obj.mapper_bit_dimension();
|
|
end
|
|
|
|
if isempty(obj.length)
|
|
obj.length = obj.default_length();
|
|
end
|
|
|
|
end
|
|
|
|
function signalclass_out = process(obj)
|
|
|
|
% actual processing of the signal (steps 1. - 3.)
|
|
signal = obj.build_signal();
|
|
signalclass_out = Informationsignal(signal,"fs",obj.fs);
|
|
|
|
% append to logbook
|
|
lbdesc = ['Signalgenerator: ',char(obj.form), ''];
|
|
signalclass_out = signalclass_out.logbookentry(lbdesc);
|
|
|
|
end
|
|
|
|
function signal = build_signal(obj)
|
|
%METHOD1 Summary of this method goes here
|
|
% Detailed explanation goes here
|
|
arguments(Input)
|
|
obj
|
|
end
|
|
|
|
arguments(Output)
|
|
signal double
|
|
end
|
|
|
|
switch obj.form
|
|
case signalform.sine
|
|
% Parameters
|
|
T = 1/obj.fs; % Sampling period (seconds per sample)
|
|
L = obj.length; % Length of signal (number of samples)
|
|
t = (0:L-1)*T; % Time vector
|
|
|
|
% Sine wave parameters
|
|
f = obj.fsig; % Frequency of the sine wave (Hz)
|
|
A = 1; % Amplitude of the sine wave
|
|
|
|
% 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
|
|
|
|
signal = obj.format_for_mapper(signal);
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
methods (Access=private)
|
|
% 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. Prefer M = 6 to generate the 1-D
|
|
% vector expected by PAMmapper(6, ...).map(...). The legacy
|
|
% dimension = 5 path still returns the unflattened bit matrix.
|
|
%
|
|
% Relevant Signalgenerator options:
|
|
% length Number of generated PRMS bit groups / rows.
|
|
% dimension Number of parallel bit streams per row.
|
|
% M Optional PAM format. When set, dimension is selected
|
|
% automatically and PAM-6 output is mapper-ready.
|
|
% 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 validate_pam_format(obj)
|
|
if ~ismember(obj.M, [2 4 6 8 16])
|
|
error("Signalgenerator:InvalidPAMFormat", ...
|
|
"M must be one of 2, 4, 6, 8, or 16.");
|
|
end
|
|
end
|
|
|
|
function length = default_length(obj)
|
|
switch obj.form
|
|
case {signalform.random, signalform.prms}
|
|
if obj.is_pam6_shape()
|
|
length = 2^max(0, obj.order - 2);
|
|
else
|
|
length = 2^max(0, obj.order - 1);
|
|
end
|
|
otherwise
|
|
length = 1024;
|
|
end
|
|
|
|
if obj.form == signalform.prms && length > 2^20
|
|
error("Signalgenerator:AutoLengthTooLarge", ...
|
|
"Auto-derived PRMS length would be %d samples, which is too large for an implicit default. Pass an explicit length if you really want a longer sequence.", ...
|
|
length);
|
|
end
|
|
end
|
|
|
|
function prms_order = internal_prms_order(obj)
|
|
bits_per_symbol = obj.prms_bits_per_symbol();
|
|
|
|
prms_order = max(1, floor(obj.order / bits_per_symbol));
|
|
end
|
|
|
|
function bits_per_symbol = prms_bits_per_symbol(obj)
|
|
if (~isempty(obj.M) && obj.M == 6) || (isempty(obj.M) && obj.dimension == 5)
|
|
% PAM-6 mapping in this repo consumes 5 bits for 2 symbols.
|
|
bits_per_symbol = 2.5;
|
|
elseif ~isempty(obj.M)
|
|
bits_per_symbol = log2(obj.M);
|
|
else
|
|
bits_per_symbol = obj.dimension;
|
|
end
|
|
end
|
|
|
|
function dimension = mapper_bit_dimension(obj)
|
|
if obj.M == 6
|
|
dimension = 5;
|
|
else
|
|
dimension = log2(obj.M);
|
|
end
|
|
end
|
|
|
|
function tf = is_pam6_shape(obj)
|
|
tf = (~isempty(obj.M) && obj.M == 6) || (isempty(obj.M) && obj.dimension == 5);
|
|
end
|
|
|
|
function signal = format_for_mapper(obj, signal)
|
|
if ~isempty(obj.M) && obj.M == 6
|
|
signal = reshape(signal.', [], 1);
|
|
signal = signal(1:end - mod(numel(signal), 5));
|
|
end
|
|
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
|