217 lines
6.7 KiB
C
217 lines
6.7 KiB
C
|
/*
|
|||
|
|
|||
|
Module: lmic_channelshuffle.c
|
|||
|
|
|||
|
Function:
|
|||
|
Channel scheduling without replacement.
|
|||
|
|
|||
|
Copyright and License:
|
|||
|
This file copyright (C) 2021 by
|
|||
|
|
|||
|
MCCI Corporation
|
|||
|
3520 Krums Corners Road
|
|||
|
Ithaca, NY 14850
|
|||
|
|
|||
|
See accompanying LICENSE file for copyright and license information.
|
|||
|
|
|||
|
Author:
|
|||
|
Terry Moore, MCCI Corporation April 2021
|
|||
|
|
|||
|
*/
|
|||
|
|
|||
|
#include "lmic.h"
|
|||
|
#include <string.h>
|
|||
|
|
|||
|
/****************************************************************************\
|
|||
|
|
|
|||
|
| Manifest constants and local declarations.
|
|||
|
|
|
|||
|
\****************************************************************************/
|
|||
|
|
|||
|
static unsigned sidewaysSum16(const uint16_t *pMask, uint16_t nEntries);
|
|||
|
static unsigned findNthSetBit(const uint16_t *pMask, uint16_t bitnum);
|
|||
|
|
|||
|
/****************************************************************************\
|
|||
|
|
|
|||
|
| Read-only data.
|
|||
|
|
|
|||
|
\****************************************************************************/
|
|||
|
|
|||
|
/****************************************************************************\
|
|||
|
|
|
|||
|
| Variables.
|
|||
|
|
|
|||
|
\****************************************************************************/
|
|||
|
|
|||
|
/*
|
|||
|
|
|||
|
Name: LMIC_findNextChannel()
|
|||
|
|
|||
|
Function:
|
|||
|
Scan a shuffle mask, and select a channel (without replacement).
|
|||
|
|
|||
|
Definition:
|
|||
|
int LMIC_findNextChannel(
|
|||
|
uint16_t *pShuffleMask,
|
|||
|
const uint16_t *pEnableMask,
|
|||
|
uint16_t nEntries,
|
|||
|
int lastChannel
|
|||
|
);
|
|||
|
|
|||
|
Description:
|
|||
|
pShuffleMask and pEnableMask are bit vectors. Channels correspond to
|
|||
|
bits in little-endian order; entry [0] has channels 0 through 15, entry
|
|||
|
[1] channels 16 through 31, and so forth. nEntries specifies the number
|
|||
|
of entries in the mask vectors. The enable mask is 1 for a given channel
|
|||
|
if that channel is eligible for selection, 0 otherwise.
|
|||
|
|
|||
|
This routine selects channels from the shuffle mask until all entries
|
|||
|
are exhausted; it then refreshes the shuffle mask from the enable mask.
|
|||
|
|
|||
|
If it refreshes the channel mask, lastChannel is taken as a channel number
|
|||
|
that is to be avoided in the next selection. (This is to avoid back-to-back
|
|||
|
use of a channel across a refresh boundary.) Otherwise lastChannel is
|
|||
|
ignored. This avoidance can be suppresed by setting lastChannel to -1.
|
|||
|
If only one channel is enabled, lastChannel is also ignored. If lastChannel
|
|||
|
is actually disabled, lastChannel is also ignored.
|
|||
|
|
|||
|
Returns:
|
|||
|
A channel number, in 0 .. nEntries-1, or -1 if the enable mask is
|
|||
|
identically zero.
|
|||
|
|
|||
|
Notes:
|
|||
|
This routine is somewhat optimized for AVR processors, which don't have
|
|||
|
multi-bit shifts.
|
|||
|
|
|||
|
*/
|
|||
|
|
|||
|
int LMIC_findNextChannel(
|
|||
|
uint16_t *pShuffleMask,
|
|||
|
const uint16_t *pEnableMask,
|
|||
|
uint16_t nEntries,
|
|||
|
int lastChannel
|
|||
|
) {
|
|||
|
unsigned nSet16;
|
|||
|
uint16_t saveLastChannelVal;
|
|||
|
|
|||
|
// in case someone has changed the enable mask, update
|
|||
|
// the shuffle mask so there are no disable bits set.
|
|||
|
for (unsigned i = 0; i < nEntries; ++i) {
|
|||
|
pShuffleMask[i] &= pEnableMask[i];
|
|||
|
}
|
|||
|
|
|||
|
// count the set bits in the shuffle mask (with a factor of 16 for speed)
|
|||
|
nSet16 = sidewaysSum16(pShuffleMask, nEntries);
|
|||
|
|
|||
|
// if zero, copy the enable mask to the shuffle mask, and recount
|
|||
|
if (nSet16 == 0) {
|
|||
|
memcpy(pShuffleMask, pEnableMask, nEntries * sizeof(*pShuffleMask));
|
|||
|
nSet16 = sidewaysSum16(pShuffleMask, nEntries);
|
|||
|
} else {
|
|||
|
// don't try to skip the last channel becuase it can't be chosen.
|
|||
|
lastChannel = -1;
|
|||
|
}
|
|||
|
|
|||
|
// if still zero, return -1.
|
|||
|
if (nSet16 == 0) {
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
// if we have to skip a channel, and we have more than one choice, turn off
|
|||
|
// the last channel bit. Post condition: if we really clered a bit,
|
|||
|
// saveLastChannelVal will be non-zero.
|
|||
|
saveLastChannelVal = 0;
|
|||
|
if (nSet16 > 16 && lastChannel >= 0 && lastChannel <= nEntries * 16) {
|
|||
|
uint16_t const saveLastChannelMask = (1 << (lastChannel & 0xF));
|
|||
|
|
|||
|
saveLastChannelVal = pShuffleMask[lastChannel >> 4] & saveLastChannelMask;
|
|||
|
pShuffleMask[lastChannel >> 4] &= ~saveLastChannelMask;
|
|||
|
|
|||
|
// if we cleared a bit, reduce the count.
|
|||
|
if (saveLastChannelVal > 0)
|
|||
|
nSet16 -= 16;
|
|||
|
}
|
|||
|
|
|||
|
if (saveLastChannelVal == 0) {
|
|||
|
// We didn't eliminate a channel, so we don't have to worry.
|
|||
|
lastChannel = -1;
|
|||
|
}
|
|||
|
|
|||
|
// get a random number
|
|||
|
unsigned choice = os_getRndU2() % ((uint16_t)nSet16 >> 4);
|
|||
|
|
|||
|
// choose a bit based on set bit
|
|||
|
unsigned channel = findNthSetBit(pShuffleMask, choice);
|
|||
|
pShuffleMask[channel / 16] ^= (1 << (channel & 0xF));
|
|||
|
|
|||
|
// handle channel skip
|
|||
|
if (lastChannel >= 0) {
|
|||
|
pShuffleMask[lastChannel >> 4] |= saveLastChannelVal;
|
|||
|
}
|
|||
|
return channel;
|
|||
|
}
|
|||
|
|
|||
|
static unsigned sidewaysSum16(const uint16_t *pMask, uint16_t nEntries) {
|
|||
|
unsigned result;
|
|||
|
|
|||
|
result = 0;
|
|||
|
for (; nEntries > 0; --nEntries, ++pMask)
|
|||
|
{
|
|||
|
uint16_t v = *pMask;
|
|||
|
|
|||
|
// the following is an adaptation of Knuth 7.1.3 (62). To avoid
|
|||
|
// lots of shifts (slow on AVR, and code intensive) and table lookups,
|
|||
|
// we sum popc * 16, then divide by 16.
|
|||
|
|
|||
|
// sum adjacent bits, making a series of 2-bit sums
|
|||
|
v = v - ((v >> 1) & 0x5555u);
|
|||
|
v = (v & 0x3333u) + ((v >> 2) & 0x3333u);
|
|||
|
// this assumes multiplies are essentialy free;
|
|||
|
v = (v & 0xF0F0u) + ((v & 0x0F0Fu) * 16u);
|
|||
|
// Accumulate result, but note it's times 16.
|
|||
|
// AVR compiler should optimize the x8 shift.
|
|||
|
result += (v & 0xFF) + (v >> 8);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
static unsigned findNthSetBit(const uint16_t *pMask, uint16_t bitnum) {
|
|||
|
unsigned result;
|
|||
|
result = 0;
|
|||
|
bitnum = bitnum * 16;
|
|||
|
for (;; result += 16) {
|
|||
|
uint16_t m = *pMask++;
|
|||
|
if (m == 0)
|
|||
|
continue;
|
|||
|
uint16_t v = m - ((m >> 1) & 0x5555u);
|
|||
|
v = (v & 0x3333u) + ((v >> 2) & 0x3333u);
|
|||
|
// this assumes multiplies are essentialy free;
|
|||
|
v = (v & 0xF0F0u) + ((v & 0x0F0Fu) * 16u);
|
|||
|
// Accumulate result, but note it's times 16.
|
|||
|
// AVR compiler should optimize the x8 shift.
|
|||
|
v = (v & 0xFF) + (v >> 8);
|
|||
|
if (v <= bitnum)
|
|||
|
bitnum -= v;
|
|||
|
else {
|
|||
|
// the selected bit is in this word. We need to count.
|
|||
|
while (bitnum > 0) {
|
|||
|
m &= m - 1;
|
|||
|
bitnum -= 16;
|
|||
|
}
|
|||
|
// now the lsb of m is our choice.
|
|||
|
// get a mask, then use Knuth 7.1.3 (59) to find the
|
|||
|
// bit number.
|
|||
|
m &= -m;
|
|||
|
result += ((m & 0x5555u) ? 0 : 1)
|
|||
|
+ ((m & 0x3333u) ? 0 : 2)
|
|||
|
+ ((m & 0x0F0Fu) ? 0 : 4)
|
|||
|
+ ((m & 0x00FFu) ? 0 : 8)
|
|||
|
;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return result;
|
|||
|
}
|