OpenBTS-UMTS/UMTS/UMTSL1CC.cpp

2103 lines
75 KiB
C++

/**@file L1 TrCH/PhCH FEC declarations for UMTS. */
/*
* OpenBTS provides an open source alternative to legacy telco protocols and
* traditionally complex, proprietary hardware systems.
*
* Copyright 2011, 2014 Range Networks, Inc.
*
* This software is distributed under the terms of the GNU Affero General
* Public License version 3. See the COPYING and NOTICE files in the main
* directory for licensing information.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
*/
#define UMTSL1_IMPLEMENTATION 1
#include "UMTSL1FEC.h"
#include "UMTSL1CC.h"
#include "MACEngine.h"
#include <assert.h>
#include <Configuration.h>
#include <Logger.h>
#include "UMTSConfig.h"
#include "URRCTrCh.h"
#include "URRC.h"
#include "RateMatch.h"
#include <iostream>
#include <fstream>
#define CANNEDBEACON 0
#if CANNEDBEACON
#include "cannedBeacon.h"
#endif
#define RN_ABS(x) ((x)>=0?(x):-(x)) // absolute value
#define LOGFECINFO LOG(NOTICE) // This is for the fec programming.
#define LOGDEBUG LOG(DEBUG)
#define LOG_DOWNLINK LOG(DEBUG)
#define LOG_UPLINK LOG(DEBUG)
using namespace std;
namespace UMTS {
extern ConfigurationTable gConfig;
extern int gFecTestMode;
#if SAVEME
int gFecTestMode = 0;
DCHListType gActiveDCH;
#endif
#if 0 // UNUSED
/** From 3GPP 25.211 Table 11, 15-slot formats only */
unsigned SlotFormatDnNData1[] = {
0, 0, 2, 2, // 0-3
2, 2, 2, 2, // 4-7
6, 6, 6, 6, // 8-11
12, 28, 56, 120, // 12-15
248 // 16
};
/** From 3GPP 25.211 Table 11, 15-slot formats only */
unsigned SlotFormatDnNData2[] = {
4, 2, 14, 12, // 0-3
12, 10, 8, 6, // 4-7
28, 26, 24, 22, // 8-11
48, 112, 232, 488, // 12-15
1000 // 16
};
/** From 3GPP 25.211 Table 11, 15-slot formats only */
unsigned SlotFormatDnTPC[] = {
2, 2, 2, 2, // 0-3
2, 2, 2, 2, // 4-7
2, 2, 2, 2, // 8-11
4, 4, 8, 8, // 12-15
8 // 16
};
/** From 3GPP 25.211 Table 11, 15-slot formats only */
unsigned SlotFormatDnTFCI[] = {
0, 2, 2, 2, // 0-3
0, 2, 0, 2, // 4-7
0, 2, 0, 2, // 8-11
8, 8, 8, 8, // 12-15
16 // 16
};
/** From 3GPP 25.211 Table 11, 15-slot formats only */
unsigned SlotFormatDnPilot[] = {
4, 4, 2, 4, // 0-3
4, 4, 8, 8, // 4-7
4, 4, 8, 8, // 8-11
8, 8, 16, 16, // 12-15
16 // 16
};
#endif
L1TrChProgInfo *L1FecProgInfo::getTCI() { return mInfoParent->getTCI(mCCTrChIndex); }
L1TrChProgInfo *L1FecProgInfo::getTCI() const { return mInfoParent->getTCI(mCCTrChIndex); }
unsigned L1FecProgInfo::getPB() const { return getTCI()->mPB; }
TTICodes L1FecProgInfo::getTTICode() const { return getTCI()->mTTICode; }
unsigned L1FecProgInfo::getNumRadioFrames() const { return TTICode2NumFrames(getTCI()->mTTICode); }
// A trivial TFCS can be handled by the old FEC code and the simple version MAC functions.
// It has only one TrCh and no TFC with more than one TB.
bool L1CCTrChInfo::isTrivial()
{
// We assume there is only one TrCh.
if (getNumTrCh() > 1) return false;
switch (getNumTfc()) {
case 2:
if (getFPI(0,1)->getNumTB() > 1) { return false; }
// Fall through to test tfc0
case 1:
if (getFPI(0,0)->getNumTB() > 1) { return false; }
// Fall through.
case 0:
return true; // this is a trivial TFCS
default:
return false;
}
}
L1CCTrChInfo::L1CCTrChInfo() {
for (TrChId i = 0; i < maxTrCh; i++) {
for (TfcId j = 0; j < maxTfc; j++) {
getFPI(i,j)->mInfoParent = this;
getFPI(i,j)->mCCTrChIndex = i;
}
}
}
void L1FecProgInfo::text(std::ostream &os) const {
os << LOGVARM(mTfi)<<LOGVARM(mTBSz)<<LOGVARM(mNumTB)<<LOGVARM(mCodedSz)<<LOGVARM(mCodeInBkSz)
<<LOGVARM(mCodeFillBits)<<LOGVARM(mHighSideRMSz)<<LOGVARM(mLowSideRMSz)
<<LOGVARM(mRFSegmentSize)<<LOGVARM(mRFSegmentOffset)
<<LOGVAR2("PB",getPB())<<LOGVAR2("TTICode",getTTICode());
}
void L1CCTrChInfo::text(std::ostream &os) const {
for (TrChId i = 0; i < mNumTrCh; i++) {
for (TfcId j = 0; j < mNumTfc; j++) {
os << "TFC("<<i<<","<<j<<"):";
const_cast<L1CCTrChInfo*>(this)->getFPI(i,j)->text(os); // const foo b.a.r.
os << "\n";
}
}
}
#define TESTEQL(what) (a->what == b->what) &&
void L1FecProgInfo::musteql(L1FecProgInfo *b) {
L1FecProgInfo *a = this;
if (
TESTEQL(mTBSz)
TESTEQL(mNumTB)
TESTEQL(mCodedSz)
TESTEQL(mCodeInBkSz)
TESTEQL(mCodeFillBits)
TESTEQL(mHighSideRMSz)
TESTEQL(mLowSideRMSz)
TESTEQL(mRFSegmentSize)
TESTEQL(mRFSegmentOffset) 1 ) return;
LOGDEBUG << "a:" << a->str() << endl;
LOGDEBUG << "b:" << b->str() << endl;
assert(0);
}
void L1CCTrChInfo::musteql(L1CCTrChInfo &other) {
L1CCTrChInfo *a = this, *b = &other;
assert(a->mNumTrCh == b->mNumTrCh);
assert(a->mNumTfc == b->mNumTfc);
for (TrChId i = 0; i < mNumTrCh; i++) {
for (TfcId j = 0; j < mNumTfc; j++) {
a->getFPI(i,j)->musteql(b->getFPI(i,j));
}
}
}
// Set the size for encoder/decoder channels.
// The size should be inited once and then never change, so we throw an assertion if it does.
static void initSize(BitVector &b, unsigned size)
{
if (b.size() == 0) {
b.resize(size);
} else {
assert(b.size() == size);
}
}
// Set the size for encoder/decoder channels.
// The size should be inited once and then never change, so we throw an assertion if it does.
static void initSize(SoftVector &b, unsigned size)
{
if (b.size() == 0) {
b.resize(size);
} else {
assert(b.size() == size);
}
}
// This duplicates functionality in fecComputeUlTrChSizes and fecComputeDlTrChSizes and is used for testing.
// Init the L1CCTrChInfo for a simplified full support channel: rach/fach/dch.
// For uplink: one TrCh, one TF.
// For downlink: one TrCh, one or more TB mapping to a simplified (unspecified) TFCS where each
// TFC j corresponds to j+1 TBs. For eample, if numTB == 2, the (assumed) TFCS has two entries
// for 1x or 2x TBs.
// Return true if the result is possible without puncturing.
bool L1CCTrChInfo::fecConfigForOneTrCh(bool isDownlink, unsigned wSF,TTICodes wTTICode,unsigned wPB,
unsigned wRadioFrameSz, unsigned wTBSz, unsigned wMinNumTB, unsigned wMaxNumTB, bool wTurbo)
{
bool isPunctured = false; // unused init to shut up gcc
// CCTrCh info:
mNumTfc = 0;
mNumTrCh = 1;
// TrCh info:
getTCI(0)->mPB = wPB;
getTCI(0)->mTTICode = wTTICode;
getTCI(0)->mIsTurbo = wTurbo;
int nout = wRadioFrameSz * l1GetNumRadioFrames(0); // warning: uses mTTICode set just above.
int nin = wTBSz+wPB;
// TFC info, exclusive of rate-matching:
int maxNTTIij = 0; // Something needed for rate-matching. Bogus init to shut up gcc.
TfcId tfcj = 0;
for (unsigned numTB = wMinNumTB; numTB <= wMaxNumTB; numTB++, tfcj++) {
mNumTfc++;
L1FecProgInfo *fpi = getFPI(0,tfcj);
fpi->mTBSz = wTBSz;
fpi->mNumTB = numTB;
int totalsize = nin * fpi->mNumTB;
if (wTurbo) {
fpi->mCodedSz = RrcDefs::TurboEncodedSize(totalsize,&fpi->mCodeInBkSz,&fpi->mCodeFillBits);
} else {
fpi->mCodedSz = RrcDefs::R2EncodedSize(totalsize,&fpi->mCodeInBkSz,&fpi->mCodeFillBits);
}
if (numTB == wMaxNumTB) {
// This is the largest TF.
maxNTTIij = fpi->mCodedSz;
}
// In downlink, the RFSize is a constant, so we set it to the full width even if there are no bits,
// for debugging purposes, even though the value will not be used.
// In uplink we set it to 0 if there are no bits.
fpi->mRFSegmentSize = (isDownlink || numTB) ? wRadioFrameSz : 0;
fpi->mRFSegmentOffset = 0;
fpi->mTfi = tfcj;
LOG(DEBUG) << format("tfcj=%u numTB=%u maxNTTIij=%u\n",tfcj,numTB,maxNTTIij);
}
assert((signed) l1GetLargestCodedSz(0) == maxNTTIij);
// Now that we know the coded block sizes, we can do the rate-matching calculation:
if (isDownlink) {
// Compute rate matching for one TrCh. This stuff simplies for one TrCh as follows:
// 4.2.7:
// equation 1 simplifies to: Zi,j = Ndata,j
// equation 1b simplfies to: deltaNi,j = Ndata,j - Ni,j
// Ndataj = nout;
// 4.2.7.2.1.1
// NiStar = 1.0/F * maxNTTIij;
// deltaNistar = Ndataj - Nistar; // by simplified equation 1b above.
// deltaNimax = F * deltaNistar = F * (Ndataj - maxNTTIij/F) = F * Ndataj - maxNTTIij = nout - maxNTTIij
int deltaNimax = nout - maxNTTIij;
//getTCI(0)->mDlEplus = 2 * maxNTTIij;
//getTCI(0)->mDlEminus = 2 * deltaNimax * (deltaNimax<0 ? -1 : 1);
// Lets double check:
//if (wMaxTB == 1) {
// assert(getTCI(0)->mDlEplus == 2 * nin);
// assert(getTCI(0)->mDlEminus == 2 * ((int)nout - (int)nin));
//}
// Fill in the rate match info for each TFC:
for (TfcId tfcj = 0; tfcj < mNumTfc; tfcj++) {
L1FecProgInfo *fpi = getFPI(0,tfcj);
fpi->mHighSideRMSz = fpi->mCodedSz;
// This is the last equation in 4.2.7.2.1.3
// The rate-matching params are fixed by the largest TF, so for other TFs we have to see
// how many bits pop out, and we will use DRX bit insertion to pad it out to the largest TF.
int deltaNTTIij = ceil((fabs(deltaNimax) * fpi->mHighSideRMSz) / maxNTTIij);
isPunctured = deltaNimax < 0; // No puncturing please.
fpi->mLowSideRMSz = fpi->mHighSideRMSz + (isPunctured ? (- deltaNTTIij) : deltaNTTIij);
// double-check:
if (tfcj == mNumTfc-1) {assert((int)fpi->mLowSideRMSz == nout);}
}
} else { // uplink
assert(wMaxNumTB == 1); // Way too complicated for multiple TF; use fecComputeUlTrChSizes().
assert(nin % l1GetNumRadioFrames(0) == 0);
for (TfcId tfcj = 0; tfcj < mNumTfc; tfcj++) {
// The uplink rate-matching is per-radio-frame not per-TTI.
L1FecProgInfo *fpi = getFPI(0,tfcj);
fpi->mHighSideRMSz = fpi->mCodedSz / l1GetNumRadioFrames(0);;
fpi->mLowSideRMSz = fpi->mRFSegmentSize;
if (fpi->getNumTB() == 1) {
isPunctured = fpi->mHighSideRMSz > fpi->mLowSideRMSz;
}
}
}
return !isPunctured;
}
// This duplicates functionality in fecConfigForOneTrCh and is used for testing.
// Program for super simple channels like BCH: one TrCh, one TF, no rate matching.
void L1CCTrChInfo::fecConfigTrivial(
unsigned wSF,TTICodes wTTICode,unsigned wPB, unsigned wRadioFrameSz)
{
// CCTrCh info:
mNumTfc = 1;
mNumTrCh = 1;
// TrCh info:
getTCI(0)->mPB = wPB;
getTCI(0)->mTTICode = wTTICode;
getTCI(0)->mIsTurbo = false; // Trivial fec is never turbo.
// TFC info:
L1FecProgInfo *fpi = getFPI(0,0);
// Back-compute the TB size that will just fit in the TTI.
unsigned nout = wRadioFrameSz * l1GetNumRadioFrames(0);
unsigned tbsz = RrcDefs::R2DecodedSize(nout) - wPB;
unsigned check = RrcDefs::R2EncodedSize(tbsz + wPB, &fpi->mCodeInBkSz,&fpi->mCodeFillBits);
assert(check == nout && fpi->mCodeInBkSz == tbsz+wPB && fpi->mCodeFillBits == 0);
fpi->mNumTB = 1;
fpi->mCodedSz = nout;
fpi->mTBSz = tbsz;
fpi->mRFSegmentSize = wRadioFrameSz;
fpi->mRFSegmentOffset = 0;
// No rate matching needed.
fpi->mHighSideRMSz = nout;
fpi->mLowSideRMSz = nout;
fpi->mTfi = 0;
fpi->mCCTrChIndex = 0;
assert(wPB == 16); // Required for the simple channels.
}
// Equation 1 is completely different for uplink and downlink.
// Uplink version of equation 1 is verbatim.
static void ulEquation1(
UlTrChList *ul,
int Ndataj[RrcDefs::maxTfc], // input, computed from SET1
L1CCTrChInfo *result, // input, for the number of num bits in each TrCh.
int deltaNij[RrcDefs::maxTrCh][RrcDefs::maxTfc]) // output
{
// These variables defined in 25.212 4.2.7:
// Calculate RMm x Nm,j which is the first term in the numerator
// and also used in denominator of eqn 1.
RrcTfcs *ulTfcs = ul->getTfcs();
unsigned numTrCh = ul->getNumTrCh();
int ulSumRMmxNmj[RrcDefs::maxTrCh][RrcDefs::maxTfc]; // This term is used in numerator and denominator of eqn 1.
for (TrChId trchm = 0; trchm < numTrCh; trchm++) {
RrcTfs *tfs = ul->getTfs(trchm);
for (TfcId tfcj = 0; tfcj < ulTfcs->getNumTfc(); tfcj++) {
int Fm = result->l1GetNumRadioFrames(trchm);
int Nmj = result->getFPI(trchm,tfcj)->mCodedSz / Fm;
int RMm = tfs->getRM();
ulSumRMmxNmj[trchm][tfcj] = (trchm ? ulSumRMmxNmj[trchm-1][tfcj] : 0) + RMm * Nmj;
}
}
int Zij[RrcDefs::maxTrCh][RrcDefs::maxTfc];
// Calculate Zij and deltaNij for each TrCh i as per equation 1.
for (TrChId trchi = 0; trchi < numTrCh; trchi++) {
for (TfcId tfcj = 0; tfcj < ulTfcs->getNumTfc(); tfcj++) {
int ulSumFull = ulSumRMmxNmj[numTrCh-1][tfcj];
Zij[trchi][tfcj] = ulSumFull ? floor(((double)ulSumRMmxNmj[trchi][tfcj] * Ndataj[tfcj]) / ulSumFull) : 0;
//printf("%u %u %f\n",ulSumRMmxNmj[trchi][tfcj]* Ndataj[tfcj], ulSumRMmxNmj[numTrCh-1][tfcj], ((double)ulSumRMmxNmj[trchi][tfcj] * Ndataj[tfcj]) / ulSumRMmxNmj[numTrCh-1][tfcj]);
int Fi = result->l1GetNumRadioFrames(trchi);
int Nij = result->getFPI(trchi,tfcj)->mCodedSz / Fi;
deltaNij[trchi][tfcj] = Zij[trchi][tfcj] - (trchi ? Zij[trchi-1][tfcj] : 0) - Nij;
LOGFECINFO << format("ulEquation1[%u,%u]: ulSum=%u Ndataj=%u Zij=%u Fi=%u Nij=%u deltaNij=%u\n",trchi,tfcj,
ulSumRMmxNmj[trchi][tfcj],Ndataj[tfcj],Zij[trchi][tfcj],Fi,Nij,deltaNij[trchi][tfcj]);
}
}
}
// Downlink version of equation 1 is verbatim but substituting Ni,* for Nm,j and Ndata,j is a single
// constant, because those variables do not vary with the TFC j.
static void dlEquation1(
DlTrChList *dl,
int Ndataj, // number of bits in one output radio frame; depends on PhCh and SF.
double Nistar[RrcDefs::maxTrCh], // input
double deltaNij[RrcDefs::maxTrCh]) // output
{
int numTrCh = dl->getNumTrCh();
// These variables defined in 25.212 4.2.7:
// Calculate RMm x Nm,j which is the first term in the numerator and also used in denominator of eqn 1.
double dlSumRMmxNmj[RrcDefs::maxTrCh]; // This term is used in numerator and denominator of eqn 1.
for (int trchm = 0; trchm < numTrCh; trchm++) {
//RrcTfs *tfs = dl->getTfs(trchm);
double Nmj = Nistar[trchm];
int RMm = dl->getRM(trchm);
dlSumRMmxNmj[trchm] = (trchm ? dlSumRMmxNmj[trchm-1] : 0) + RMm * Nmj;
}
// Calculate Zij and deltaNij for each TrCh i as per equation 1.
int Zij[RrcDefs::maxTrCh]; // Invariant over all TF or TFC j for TrCh i, so j is not a subscript.
for (int trchi = 0; trchi < numTrCh; trchi++) {
// For downlink, the j in eqn 1 does not matter because neither Nmj nor Ndataj vary based on j.
Zij[trchi] = floor(((double)dlSumRMmxNmj[trchi] * Ndataj) / dlSumRMmxNmj[numTrCh-1]);
double Nij = Nistar[trchi];
deltaNij[trchi] = Zij[trchi] - (trchi ? Zij[trchi-1] : 0) - Nij;
LOGFECINFO << format("dlEquation1 TrCh %u Ndataj=%u Nistar=Nij=%g dlSum=%g Zij=%u deltaNij=%g\n",
trchi,Ndataj, Nistar[trchi],dlSumRMmxNmj[trchi],Zij[trchi],deltaNij[trchi]);
}
}
#if 0 // changed to fecComputeCommon
static int computeCodedSize(RrcTfs *tfs, int numTB, int tbSz, int paritybits)
{
int totalsize = numTB * (paritybits + TBSz);
switch (ssp->mTypeOfChannelCoding) {
case Convolutional:
// Dont even check the rate - we only support 1/2.
return R2EncodedSize(totalsize);
break;
case Turbo:
return TurboEncodedSize(totalsize);
break;
default: assert(0);
}
}
#endif
// TODO: Need to check the UE capabilities:
// TODO: channel type == non-RACH is hard coded here; the difference is just that RACH can not go as fast
// ans so the SET0 and SET1 are limited to SF32.
// I quote: "For RACH if Ndata,j is not part of UE capabilities TFC j cannot be used.
static void fecComputeUlNdataj(UlTrChList *ul,
bool isRach, // RACH handled different from DCH.
L1CCTrChInfo *result,
int Ndataj[RrcDefs::maxTfc],
int SF[RrcDefs::maxTfc]) // The output result.
{
const int maxSF = 7;
// RACH supports the first four uplink spreading factors.
// Other channels support all 7 possible uplink spreading factors, but limited by the UE capabilities.
int numSF = isRach ? 4 : maxSF;
static int ulSF[maxSF] = { 256, 128, 64, 32, 16, 8, 4 };
static int ulSET0[maxSF];
for (int sfi = 0; sfi < maxSF; sfi++) {
ulSET0[sfi] = gFrameLen / ulSF[sfi];
}
int ulNumTrCh = ul->getNumTrCh();
// The min(l<=y<=I){RMy} term in the SET1 equation is a just constant. Figure it out.
int minRM = 9999999;
for (int tc = 0; tc < ulNumTrCh; tc++) {
int thisRM = ul->getTfs(tc)->getRM();
if (thisRM < minRM) { minRM = thisRM; }
}
assert(minRM != 0);
// In equation for SET1, x is the TrCh and j is the TFC.
RrcTfcs *ulTfcs = ul->getTfcs();
for (TfcId j = 0; j < ulTfcs->mNumTfc; j++) {
//RrcTfc *tfc = ulTfcs->getTfc(j);
int sumRMxNxj = 0;
for (int trchx = 0; trchx < ulNumTrCh; trchx++) { // I in equation is ulNumTrCh.
//RrcTf *tf = tfc->getTf(trchx);
int nf = ul->getTTINumFrames(trchx);
int Nxj = result->getFPI(trchx,j)->mCodedSz/nf;
//int Nxj = tf->getTfTotalSize() / nf;
unsigned RMx = ul->getTfs(trchx)->getRM();
LOGFECINFO<< format("trchx: %d %d %d %u\n",trchx,nf,Nxj,RMx);
sumRMxNxj += RMx * Nxj;
}
// Want Ndata in SET0 such that minRMyNdata - sumRMxNxj is non-negative.
// We only want the minimum value in SET1, which is NDataj, so we dont keep the rest of SET1, only Ndataj.
// In uplink, the Ndata for spreading factor sf = gFrameLen / sf;
Ndataj[j] = 0; // Impossible value, used to detect overflow.
for (int sfi = 0; sfi < numSF; sfi++) {
int Ndata = ulSET0[sfi];
LOGFECINFO<<format("sfi: %d, %d %d %d\n",sfi, minRM, Ndata, sumRMxNxj);
if (minRM * Ndata >= sumRMxNxj) {
// "If SET1 is not empty and the smallest element of SET1 requires just one PhCh then:"
// "Ndata,j = min SET1"
// And we are not worrying about the multiple PhCh case.
Ndataj[j] = Ndata;
SF[j] = ulSF[sfi];
break;
}
}
// Check for overflow.
if (Ndataj[j] == 0) {
// And now what?
assert(0);
}
}
}
static void fecComputeCommon(
TrChList *trchlist, // Either UlTrChList or DlTrChList
L1CCTrChInfo *result) // Fills in PB, TTI, numTB, TBSz and codedSz and codedBkSz.
{
//result->mChList = trchlist;
RrcTfcs *tfcs = trchlist->getTfcs();
unsigned numTrCh = trchlist->getNumTrCh();
result->mNumTfc = tfcs->getNumTfc();
//std::cout << format("trchlist=%p tfcs=%p numTfc=%d\n",trchlist,tfcs,result->mNumTfc);
result->mNumTrCh = numTrCh;
for (TrChId tcid = 0; tcid < numTrCh; tcid++) {
RrcTfs *tfs = trchlist->getTfs(tcid);
result->getTCI(tcid)->mPB = tfs->getPB();
result->getTCI(tcid)->mTTICode = tfs->getTTICode();
result->getTCI(tcid)->mIsTurbo = tfs->getTurboFlag();
RrcSemiStaticTFInfo *ssp = tfs->getSemiStatic();
int tfci = 0;
for (RrcTfc *tfc = tfcs->iterBegin(); tfc != tfcs->iterEnd(); tfc++, tfci++) {
L1FecProgInfo *fpi = result->getFPI(tcid,tfci);
RrcTf *tf = tfc->getTf(tcid);
fpi->mNumTB = tf->getNumTB();
fpi->mTBSz = tf->getTBSize();
int totalsize = fpi->mNumTB * (fpi->mTBSz + fpi->getPB());
//result->getFPI(tcid,tfci)->mCodedSz = computeCodedSize(tfs,numTB,TBSz,paritybits);
switch (ssp->mTypeOfChannelCoding) {
case RrcDefs::Convolutional:
// Dont even check the rate - we only support 1/2.
//LOGDEBUG <<format("here, tfs->pb=%u pb=%u\n",tfs->getPB(),fpi->getPB());
fpi->mCodedSz = RrcDefs::R2EncodedSize(totalsize,&fpi->mCodeInBkSz,&fpi->mCodeFillBits);
break;
case RrcDefs::Turbo:
fpi->mCodedSz = RrcDefs::TurboEncodedSize(totalsize,&fpi->mCodeInBkSz,&fpi->mCodeFillBits);
break;
default: assert(0);
}
LOGFECINFO<<format("fecComputeCommon(%d,%d) TB=%ux%u coded=%u\n",tcid,tfci,fpi->mNumTB,fpi->mTBSz,fpi->mCodedSz);
}
}
}
static void fecComputeUlTrChSizes(UlTrChList *ul, L1CCTrChInfo *result, bool isDch=true)
{
// We simplify by never using multiple PhCh. Multiple PhCh would be needed
// for higher bandwidth in the uplink direction which is not one of our requirements.
// Copy parameters from UURC.
// Compute mCodedSz.
fecComputeCommon(ul,result);
#if 0
int ulNumTrCh = ul()->getNumTrCh();
RrcTfcs *ulTfcs = ul()->getTfcs();
int tfci = 0;
for (TrChId tcid = 0; tcid < ulNumTrCh; tcid++) {
RrcTfs *tfs = ul()->getTfs
result->getTCI(tcid)->mPB = tfs->getPB();
result->getTCI(tcid)->mTTICode = tfs->getTTICode();
result->getTCI(tcid)->mIsTurbo = tfs->getTurboFlag();
RrcSemiStaticTFInfo *ssp = tfs->getSemiStatic();
for (RrcTfc *tfc = tfcs->iterBegin(); tfc != tfcs->iterEnd(); tfc++, tfci++) {
RrcTf *tf = tfc->getTf(tcid);
int numTB = result->getFPI(tcid,tfci)->mNumTB = tf->getNumTB();
int TBSz = result->getFPI(tcid,tfci)->mTBSz = tf->getTBSize();
result->getFPI(tcid,tfci)->mCodedSz = computeCodedSize(tfs,numTB,TBSz,result->getTCI(tcid)->mPB);
}
}
#endif
// 4.2.4 Radio Frame Equalisation, verbatim. Also see 4.2 figure 1. Used only in uplink.
unsigned ulNumTrCh = ul->getNumTrCh();
RrcTfcs *ulTfcs = ul->getTfcs();
for (TrChId tcid = 0; tcid < ulNumTrCh; tcid++) {
for (TfcId tfci = 0; tfci != ulTfcs->getNumTfc(); tfci++) {
L1FecProgInfo *fpi = result->getFPI(tcid,tfci);
int Ei = fpi->mCodedSz;
int Fi = ul->getTTINumFrames(tcid);
int Ni = (Ei + Fi - 1) / Fi;
//int Ti = Fi * Ni;
// Post radio frame equalisation goes into the interleaver, then radio-frame-segmentation, then rate-matching.
fpi->mHighSideRMSz = Ni;
fpi->mTfi = tfci; // This is just 1-to-1 in uplink.
}
}
// 4.2.7.1.1 Determination of SF (and number of PhCh) needed for uplink.
int Ndataj[RrcDefs::maxTfc]; // number of bits in radio-frame.
int SF[RrcDefs::maxTfc];
fecComputeUlNdataj(ul,!isDch,result,Ndataj,SF);
// 4.2.7: Compute equation 1.
int ulDeltaNij[RrcDefs::maxTrCh][RrcDefs::maxTfc];
ulEquation1(ul,Ndataj,result, ulDeltaNij);
// Copy back out to the result, and pre-compute rate-matching eini parameters.
int rfoffset = 0;
for (TrChId tcid = 0; tcid < ulNumTrCh; tcid++) {
for (TfcId tfcj = 0; tfcj != ulTfcs->getNumTfc(); tfcj++) {
L1FecProgInfo *fpi = result->getFPI(tcid,tfcj);
fpi->mLowSideRMSz = fpi->mHighSideRMSz + ulDeltaNij[tcid][tfcj];
fpi->mRFSegmentSize = fpi->mLowSideRMSz; // Number of bits in radio frame for this TrCh.
fpi->mRFSegmentOffset = rfoffset;
rfoffset += fpi->mRFSegmentSize;
fpi->mSFLog2 = round(log2(SF[tfcj]));
}
}
}
unsigned L1CCTrChInfo::l1GetLargestCodedSz(TrChId tcid)
{
unsigned maxCodedSz = 0;
for (TfcId tfci = 0; tfci != mNumTfc; tfci++) {
L1FecProgInfo *fpi = getFPI(tcid,tfci);
if (fpi->mCodedSz > maxCodedSz) {
maxCodedSz = fpi->mCodedSz;
}
}
return maxCodedSz;
}
// Downlink and uplink calculations are completely different because 1. uplink SF
// is a variable that is chosen as part of the calculation; 2. downlink data
// is expanded to fit the largest TF while uplink data is always minimized individually for each TFC.
// Figure out the number of bits send on the radio frame for each TrCh in the CCTrCh.
static void fecComputeDlTrChSizes(DlTrChList *dl,unsigned dlRadioFrameSize,L1CCTrChInfo *result)
{
// Convolutional coding:
// Lets compute the number of bits of the radio frame that will be allocated
// to each TrCh for each TFC. See 25.212 4.2.7
// ================= DOWNLINK ======================
// The downlink parameters for each TrCh vary only for the TF in the TFS for that TrCh, they do not
// differ among different TFC in the TFCS that use the same TF for a TrCh, but to make things easy
// we are going to store the info per TFC, which means entries for TrCh using the same TF are duplicated.
// Copy parameters from UURC.
// Compute mCodedSz.
int maxNTTIil[RrcDefs::maxTrCh] = {0,0,0,0};
#if 0
int dlNumTrCh = dl()->getNumTrCh();
RrcTfcs *dlTfcs = dl()->getTfcs();
for (TrChId tcid = 0; tcid < dlNumTrCh; tcid++) {
RrcTfs *tfs = dl()->getTfs
result->getTCI(tcid)->mPB = tfs->getPB();
result->getTCI(tcid)->mTTICode = tfs->getTTICode();
result->getTCI(tcid)->mIsTurbo = tfs->getTurboFlag();
RrcSemiStaticTFInfo *ssp = tfs->getSemiStatic();
int tfci = 0;
for (RrcTfc *tfc = tfcs->iterBegin(); tfc != tfcs->iterEnd(); tfc++, tfci++) {
RrcTf *tf = tfc->getTf(tcid);
int numTB = result->getFPI(tcid,tfci)->mNumTB = tf->getNumTB();
int TBSz = result->getFPI(tcid,tfci)->mTBSz = tf->getTBSize();
int codedSz = computeCodedSize(tfs,numTB,TBSz,result->getTCI(tcid)->mPB);
result->getFPI(tcid,tfci)->mCodedSz = codedSz;
result->getFPI(tcid,tfci)->mHighSideRMSz = codedSz; // Same size used for rate-matching input.
maxNTTIil[tcid] = max(maxNTTIil[tcid],codedSz);
}
}
#endif
fecComputeCommon(dl,result);
// Fill in RM high-side size, and save the max value over all TFC as MaxNTTIil per TrCh.
// In downlink, RM high side equals the coded size of all concatenated blocks.
unsigned numTrCh = dl->getNumTrCh();
RrcTfcs *dlTfcs = dl->getTfcs();
for (TrChId tcid = 0; tcid < numTrCh; tcid++) {
maxNTTIil[tcid] = result->l1GetLargestCodedSz(tcid);
#if 0
maxNTTIil[tcid] = 0;
for (TrChId tfci = 0; tfci != dlTfcs->getNumTfc(); tfci++) {
L1FecProgInfo *fpi = result->getFPI(tcif,tfcj);
if (fpi->mCodedSz > maxNTTIil[tcid]) {
result->getTCI(tcid)->?? = tcid;
maxNTTIil[tcid] = fpi->mCodedSz;
}
}
#endif
}
// Duplicate info from the TF in the TFS into the TFC in the TFCS.
//for (RrcTfc *tfc = tfcs->iterBegin(); tfc != tfcs->iterEnd(); tfc++, tfci++) {
// for (TrChId tcid = 0; tcid < dlNumTrCh; tcid++) {
// result[tcid]->perTFC[tfci] = perTFS[tcid][tfc->getTf(tcid)->mTfi];
// }
//}
// Now the stupendously complex rate matching.
// 25.212 4.2.7.2.1 Compute Ni,*
// "First an intermediate calculation variable "Ni,*" is calculated for all transport channels i:"
// Note that Ni,* is not an integer, but a multiple of 1/8 [or 1/TTINumFrames].
double Nistar[RrcDefs::maxTrCh]; // For downlink: intermediate variable with a step of 1/8.
for (TrChId trchi = 0; trchi < numTrCh; trchi++) {
RrcTfs *tfs = dl->getTfs(trchi);
int Fi = tfs->getTTINumFrames();
Nistar[trchi] = ((double)maxNTTIil[trchi]/Fi);
}
// Compute deltaNi,* from Ni,* for each TrCh i using 4.2.7 equation 1.
double deltaNistar[RrcDefs::maxTfc];
dlEquation1(dl,dlRadioFrameSize,Nistar,deltaNistar);
// Compute delta Ni,max from Ni,star using 4.2.7 equation 1.
// (pat) we ran equation one on radio frames, but we want bits per TTI, so now we have to compute back to TTIs.
int deltaNimax[RrcDefs::maxTrCh];
int rfsegmentsize[RrcDefs::maxTrCh];
for (TrChId trchi = 0; trchi < numTrCh; trchi++) {
int Fi = dl->getTTINumFrames(trchi);
// This converts deltaNistar from double (with step 1/F) back to an integral value.
deltaNimax[trchi] = round(Fi * deltaNistar[trchi]);
// The RF segment is determined by the post-rate-matched size of the largest TF.
rfsegmentsize[trchi] = maxNTTIil[trchi] + deltaNimax[trchi];
LOGFECINFO << format("fecComputeDlTrChSizes TrCh %u Nistar=%g deltaNistar=%g deltaNimax=%u RFseg=%u\n",
trchi,Nistar[trchi],deltaNistar[trchi],deltaNimax[trchi],rfsegmentsize[trchi]);
// In downlink the output size from rate matching varies for each TF, but NOT for each TFC.
// Note, these equations are equivalent to: eplus = nin; eminus = 2 * abs(nout-nin);
// where nin and nout are for the largest TF on this TrCh.
// Moved to encoder.
//result->getTCI(trchi)->mDlEplus = 2.0 * maxNTTIil[trchi];
//result->getTCI(trchi)->mDlEminus = 2.0 * RN_ABS(deltaNimax[trchi]);
}
// 4.2.7.2.1.3 Determination of the rate matching parameters (which are for the whole TTI, not per radio frame).
// (pat) The documentation is elucidated by recognizing that for downlink TrCh we always ship the
// same size vector equal to the largest TF for each TrCh, so eplus and eminus are computed from the largest TF
// for this TrCh.
// Astonishingly, you use the same rate-matching eplus and eminus for all TF regardless of the number of bits in the frame.
int rfoffset = 0;
RrcTfcs *tfcs = dl->getTfcs();
for (TrChId trchi = 0; trchi < numTrCh; trchi++) {
for (TfcId tfcj = 0; tfcj < dlTfcs->getNumTfc(); tfcj++) {
L1FecProgInfo *fpi = result->getFPI(trchi,tfcj);
int deltaNi = deltaNimax[trchi];
int Xi = fpi->mCodedSz;
// deltaNTTIl is number of bits repeated/punctured each TTI.
int deltaNTTIl = maxNTTIil[trchi] ? (int) ceil((double)RN_ABS(deltaNi) * Xi / maxNTTIil[trchi]) : 0;
LOGFECINFO << format("fecComputeDlTrChSizes(%u,%u) deltaNi=%d coded=%u deltaNTTIl=%d maxNTTIil=%d\n",trchi,tfcj,deltaNi,fpi->mCodedSz,deltaNTTIl,maxNTTIil[trchi]);
if (deltaNi < 0) { deltaNTTIl = - deltaNTTIl; } // above * sgn(delta Ni)
fpi->mHighSideRMSz = fpi->mCodedSz; // Same size used for rate-matching input.
fpi->mLowSideRMSz = (int) fpi->mHighSideRMSz + deltaNTTIl;
// In downlink these are the same for all TFC.
fpi->mRFSegmentSize = rfsegmentsize[trchi]; // Number of bits in radio frame for this TrCh.
fpi->mRFSegmentOffset = rfoffset;
// Set the TFI for each TFC.
RrcTfc *tfc = tfcs->getTfc(tfcj);
fpi->mTfi = tfc->getTfIndex(trchi);
}
rfoffset += rfsegmentsize[trchi];
}
} // fecComputeDlTrChSizes
L1TrChEncoder::L1TrChEncoder(L1CCTrCh *wParent, L1FecProgInfo *wfpi):
mParent(wParent)
/*, mFpi(wfpi)*/
{
int nin = wfpi->mInfoParent->l1GetLargestCodedSz(wfpi->mCCTrChIndex);
int nout = wfpi->mRFSegmentSize;
rateMatchComputeEplus(nin, nout, &mDlEplus, &mDlEminus);
}
L1TrChDecoder::L1TrChDecoder(L1CCTrCh* wParent,L1FecProgInfo *wfpi):
mParent(wParent),mUpstream(NULL)
{
//unsigned frameSize = gFrameLen / getSF();
unsigned nrf = wfpi->getNumRadioFrames();// number of radio frames per tti
//mDTtiBuf = new SoftVector(mRadioFrameSz * nrf);
// mRMBuf and mDTtiBuf are after rate-matching:
initSize(mRMBuf,wfpi->mHighSideRMSz);
initSize(mDTtiBuf,wfpi->mHighSideRMSz * nrf);
mDTtiIndex = 0;
//rateMatchComputeEini(wfpi->mCodedBkSz/nrf,mRadioFrameSz,wfpi->getTTICode(),mEini);
rateMatchComputeUlEini(wfpi->mHighSideRMSz,wfpi->mLowSideRMSz,wfpi->getTTICode(),mEini);
}
bool L1ControlInterface::recyclable() const
{
ScopedLock lock(mLock);
if (mAssignmentTimer.expired()) return true;
if (mReleaseTimer.expired()) return true;
return false;
}
void L1ControlInterface::controlOpen()
{
ScopedLock lock(mLock);
mActive = true;
mAssignmentTimer.set();
mReleaseTimer.reset();
}
void L1ControlInterface::controlClose(bool hardRelease) // TODO: something with hard release.
{
ScopedLock lock(mLock);
mActive = false;
mReleaseTimer.set();
}
void L1ControlInterface::countBadFrame()
{
L1FER::countBadFrame();
}
void L1ControlInterface::countGoodFrame()
{
ScopedLock lock(mLock);
L1FER::countGoodFrame();
mAssignmentTimer.reset(); // TODO: Should only reset if all TrCh are good?
}
void L1CCTrChDownlink::l1DownlinkOpen()
{
mTotalBursts = 0;
mPrevWriteTime = 0;
mNextWriteTime = gNodeB.clock().get();
}
//void L1TrCHFECDecoder::close(bool hardRelease)
//{
// mActive = false;
//}
//void L1CCTrCh::sendFrame(BitVector& frame, unsigned tfci)
//{
// //TODO: This test now fails, correctly, due to rate matching.
// //assert(frame.size()%gFrameSlots == 0);
//
// // 25.212 4.2.7 Rate Matching.
// // The RRC insures that codedBkSz < mRadioFrameSz, ie, we will never use puncturing, ever,
// // for any TF [Transport Format] and therefore there is no rate-matching in downlink;
// // DTX is used instead.
// /*unsigned insize = this->mCodeInBkSz / this->getNumRadioFrames();
// unsigned outsize = this->getRadioFrameSz();
// assert(frame.size() == insize);
// assert(insize <= outsize); // If you get this error, go fix RRC; dont mess around here.
// */
//
// // This has been moved in UMTSL1FEC.cpp
// // (pat) I had it in the wrong place.
// /*****
// unsigned outsize = this->mRadioFrameSz;
// unsigned insize = this->mCodeInBkSz/this->getNumRadioFrames();
// // (pat) 6-19-2012: Re-added this assertion, which insures no puncturing:
// assert(insize <= outsize); // If you get this error, go fix RRC; dont mess around here.
// //assert(mHDIBuf->size() == insize);
//
// if (insize != outsize) {
// rateMatchFunc<char>(frame,this->mResultFrame,1);
// //LOG(INFO) << "SCCPCH: " << insize << " " << outsize;
// }
// else {
// frame.copyTo(this->mResultFrame);
// }
// ***********/
//}
// 25.212 4.2.8 TrCh Multiplexing.
// Incoming frame is the result of 25.212 4.2.6 Radio Frame Segmentation.
// Meaning that it is sent one radio frames worth of data on just one TrCh.
void L1CCTrChDownlink::l1Multiplexer(L1FecProgInfo *fpi, BitVector& frame, unsigned intraTTIFrameNum)
{
// TODO: the multiplexor must work over all the radio frames in the TTI.
// We have to gather up an entire TTI before we can send any.
// This goes back to the MAC which may have to deal with different TrCh running at different rates?
//int offset = timestamp.FN() - mNextWriteTime;
//if (offset < 0) {
// LOG(ERR) << "Stale burst in multiplexer";
// return;
//}
// Gather up data from each trch, then send downward.
assert(frame.size() == fpi->mRFSegmentSize);
initSize(mMultiplexerBuf[intraTTIFrameNum],fpi->mRFSegmentSize);
LOG_DOWNLINK << "l1Multplexer"<<LOGVAR2("RFSegSize",fpi->mRFSegmentSize)<<LOGVAR2("RFSegOff",fpi->mRFSegmentOffset)<<LOGVAR2("FN",intraTTIFrameNum);
frame.copyToSegment(mMultiplexerBuf[intraTTIFrameNum],fpi->mRFSegmentOffset);
}
void L1CCTrChDownlink::l1SendFrame2(BitVector& frame, unsigned tfci)
{
// 25.212 4.2.9 Insertion of Discontinuous Transmission (DTX) Indicators.
// (pat) I believe the second insertion of DTX is only necessary if you are using
// multiple PhCh, and we are not.
BitVector h = frame.alias(); // You must NOT say h(frame) because it takes possession.
//LOG(DEBUG) << "here:" << h.str();
// 25.212 4.2.10 Physical Channel Segmentation.
// "When more than one PhCH is used..." OK, can stop reading right there.
// 25.212 4.2.11 Second Interleaving.
// (pat) Number of columns fixed at 30, and number of rows is the minimum that will work.
// The padding will only occur when supporting multiple TrCh, because the
// radio frame is a multiple 150 which is divisible by 30.
const unsigned C2 = 30; // Number of columns;
unsigned hsize = h.size();
unsigned rows = (hsize + (C2 - 1))/C2;
int padding = (C2 * rows) - hsize;
assert(padding >= 0);
unsigned Ysize = hsize + padding; // Y is defined in 4.2.11 as padded interleave buf
initSize(mYoutBuf,Ysize);
if (padding == 0) {
h.interleavingNP(C2, TrCHConsts::inter2Perm, mYoutBuf);
} else {
// Must pre-pad and post-strip bits.
// The post-interleave pad bits are spread all over; easiest way to get rid
// of them is to use a special marker value for the padding.
const char padval = 4; // We will pad with padbits set to this value.
initSize(mYinBuf,Ysize); // Temporary buffer
h.copyTo(mYinBuf);
memset(mYinBuf.begin()+hsize,padval,padding); // Add the padding.
mYinBuf.interleavingNP(C2, TrCHConsts::inter2Perm, mYoutBuf);
// Strip out the padding bits.
char *yp = mYoutBuf.begin(), *yend = mYoutBuf.end();
for (char *cp = yp; cp < yend; cp++) {
if (*cp != padval) { *yp++ = *cp; }
}
assert(yp == mYinBuf.begin() + hsize);
}
BitVector U(mYoutBuf.head(hsize));
//if (gFecTestMode == 2) {
// gNodeB.mRachFec->decoder()->writeLowSide2(U);
// return;
//}
// 25.212 4.2.12 Physical Channel Mapping.
// "In compressed mode..." OK, can stop reading right there.
// 25.212 4.3 Transport Format Detection Based on TFCI.
// 25.212 4.3.3 Coding of TFCI.
// (pat) We have to pre-encode the TFCI to go in the radio slots.
// This encoding is static based only on on the tfci to be sent,
// so it is done once on startup in class TrCHConsts,
// and now we just index into it with the incoming tfci.
assert(tfci < TrCHConsts::sMaxTfci);
//tfci = 0;
uint32_t tfciCode = TrCHConsts::sTfciCodes[tfci];
// (pat) Okey Dokey, finished with spec 25.212.
// Now we move on to another fine and wonderful spec:
// 25.211: "Physical channels and mapping of transport channels onto physical channels (FDD)"
PhCh *phch = getPhCh();
PhChType chType = phch->phChType();
assert(chType == DPDCHType || chType == SCCPCHType || chType == PCCPCHType);
//assert(mDownstream);
size_t dataSlotSize = U.size() / gFrameSlots;
// NOTE: PCCPCHType sends only 18 bits per slot, not 20.
if (chType == PCCPCHType) {
// The PCCPCH radio slot contains: | Tx Off | Data (always 18 bits) |
for (unsigned i=0; i<gFrameSlots; i++) {
// (pat) Before I added the BitVector copy constructor, this allocated
// a new Vector via constructor: Vector(const Vector<char>&other)
const BitVector slotBits = U.segment(i*dataSlotSize,dataSlotSize);
TxBitsBurst *out = new TxBitsBurst(
slotBits,
phch->getDlSF(), phch->getSpCode(), mNextWriteTime.slot(i),true
);
RN_MEMLOG(TxBitsBurst,out);
phch->getRadio()->writeHighSide(out);
}
} else {
int radioFrameSize = 2*(gFrameLen / phch->getDlSF());
//BitVector radioFrame(radioFrameSize);
int radioSlotSize = radioFrameSize/15;
SlotFormat *dlslot = phch->getDlSlot();
const unsigned ndata1 = dlslot->mNData1;
const unsigned ndata2 = dlslot->mNData2;
const unsigned ntpc = dlslot->mNTpc;
const unsigned ntfci = dlslot->mNTfci;
const unsigned tfciMask = (1<<ntfci)-1;
const unsigned npilot = dlslot->mNPilot;
const unsigned pi = dlslot->mPilotIndex;
// Double check this against channel parameters.
assert(dataSlotSize == ndata1+ndata2);
initSize(mRadioSlotBuf,radioSlotSize);
for (unsigned s=0; s<gFrameSlots; s++) {
const unsigned dataStart = s*dataSlotSize;
size_t wp = 0;
// 25.211 4.3.5.1: Mapping of TFCI word in normal [non-compressed] mode:
// The 32 TFCI code bits are stuck in the slots LSB first,
// wrapping around to be reused as much as needed.
switch (chType) {
case SCCPCHType:
//cout << "TFCI: " << tfci << " code: " << (tfciCode&tfciMask);
// The SCCPCH radio slot contains: | TFCI | Data | Pilot |
mRadioSlotBuf.writeFieldReversed(wp,tfciCode&tfciMask,ntfci);
U.segment(dataStart,ndata1).copyToSegment(mRadioSlotBuf,wp,ndata1);
wp += ndata1;
mRadioSlotBuf.fillField(wp, TrCHConsts::sDlPilotBitPattern[pi][s], npilot);
// There is no data2 for SCCPCH.
break;
case DPDCHType:
// The DCH radio slot contains:
// Release 4 or later: | Data1 | TPC | TFCI | Data2 | Pilot |
// Release 99 or 3: | TFCI | Data1 | TPC | Data2 | Pilot |
#ifdef RELEASE99 // defined in UMTSPhCh.cpp
mRadioSlotBuf.writeFieldReversed(wp,tfciCode&tfciMask,ntfci);
#endif
if (ndata1 > 0) {
U.segment(dataStart,ndata1).copyToSegment(mRadioSlotBuf,wp,ndata1);
wp += ndata1;
}
// Lower layers are going to fill in TPC, we hope.
// Toggle tpc bits between all ones and all zeros to keep phone at same tx power
mRadioSlotBuf.fill(0x7f,wp,ntpc); wp += ntpc;
#ifndef RELEASE99
mRadioSlotBuf.writeFieldReversed(wp,tfciCode&tfciMask,ntfci);
#endif
if (ndata2 > 0) {
U.segment(dataStart+ndata1,ndata2).copyToSegment(mRadioSlotBuf,wp,ndata2);
wp += ndata2;
}
mRadioSlotBuf.fillField(wp, TrCHConsts::sDlPilotBitPattern[pi][s], npilot);
break;
default: assert(0);
}
// rotate tfciCode by ntfci bits. It is unsigned, which helps.
tfciCode = (tfciCode>>ntfci) | (tfciCode<<(32-ntfci));
TxBitsBurst *out = new TxBitsBurst(mRadioSlotBuf, phch->getDlSF(), phch->getSpCode(), mNextWriteTime.slot(s));
if (chType==DPDCHType) out->DCH(true);
RN_MEMLOG(TxBitsBurst,out);
phch->getRadio()->writeHighSide(out);
}
}
mPrevWriteTime = mNextWriteTime;
++mNextWriteTime;
}
extern void getParity(const BitVector &in, BitVector &parity);
#if SAVEME
// parity - 25.212, 4.2.1
void getParity(const BitVector &in, BitVector &parity)
{
int L = parity.size();
if (in.size() > 0) {
uint64_t gcrc;
if (L == 24) {
gcrc = TrCHConsts::mgcrc24;
} else if (L == 16) {
gcrc = TrCHConsts::mgcrc16;
} else if (L == 12) {
gcrc = TrCHConsts::mgcrc12;
} else if (L == 8) {
gcrc = TrCHConsts::mgcrc8;
} else if (L == 0) {
// no parity bits to add
} else {
assert(0);
}
if (L != 0) {
Parity p(gcrc, L, L + in.size());
p.writeParityWord(in, parity);
parity.reverse();
parity.invert(); // undo inversion done by parity generator
}
} else {
parity.fill(0);
}
}
#endif
// Downlink entry function to L1 from MAC.
// Simplified version for BCH and maybe FACH. Send just one TB.
void L1CCTrChDownlink::l1WriteHighSide(const TransportBlock &tb)
{
assert(getNumTrCh() == 1);
assert(isTrivial()); //getNumTfc() <= 1);
int tfci = getNumTfc()-1;
L1FecProgInfo *fpi = getFPI(0,tfci);
assert(fpi->getNumTB() == 1);
TransportBlock const *blocklist[1]; // It is not a very interesting list in this case.
blocklist[0] = &tb;
if (tb.scheduled()) mNextWriteTime = tb.time();
this->mEncoders[0][tfci]->l1CrcAndTBConcatenation(fpi,blocklist);
// Now send the result to the radio.
// This function is called only for TrCh with no TFC, so it is TFC 0
//LOG(INFO) << "Pushing BCH: " << tb << " at time " << mNextWriteTime;
l1PushRadioFrames(tfci);
}
// Downlink entry function to L1 from MAC.
// Distribute the blocks in TBS out to the TrCh encoders.
void L1CCTrChDownlink::l1WriteHighSide(const MacTbs &tbs)
{
TransportBlock const *blocks[RrcDefs::maxTbPerTrCh];
RrcTfc *tfc = tbs.mTfc; // The TFC selected by MAC.
unsigned numTrCh = tfc->getNumTrCh();
bool firstblock = true;
for (TrChId tcid = 0; tcid < numTrCh; tcid++) {
// The number of Transport Blocks in the TBS must match what
// is specified in the RrcTfc.
RrcTf *tf = tfc->getTf(tcid);
unsigned numTB = tf->getNumTB();
// The number of blocks in the TBS better match the TFC.
assert(numTB == tbs.mTrChTb[tcid].mNumTb);
for (unsigned tbn = 0; tbn < numTB; tbn++) {
blocks[tbn] = tbs.getTB(tbn,tcid);
if (firstblock) {
if (blocks[tbn]->scheduled()) mNextWriteTime = blocks[tbn]->time();
firstblock = false;
}
}
//this->mPerTfc[tfc->getTfcIndex()].mEncoder[tcid]->encWriteHighSide(blocks);
//this->mPerTfc[tfc->getTfcIndex()].mEncoder[tcid]->encProcess(blocks);
L1FecProgInfo *fpi = getFPI(tcid,tfc->getTfIndex(tcid)); //tfc->getTfcsIndex());
//printf("tcid: %d, %d\n",tcid,tfc->getTfIndex(tcid));
if (tfc->getTfIndex(tcid) > 0) {
//printf("tcid: %d, %d\n",tcid,tfc->getTfIndex(tcid));
this->mEncoders[tcid][tfc->getTfIndex(tcid)]->l1CrcAndTBConcatenation(fpi,blocks);
}
}
//if (firstblock) mNextWriteTime = tbs.mTime;
// Now send the result to the radio.
if (tfc->getTfcsIndex() > 0) l1PushRadioFrames(tfc->getTfcsIndex());
}
void L1CCTrChDownlink::l1PushRadioFrames(int tfci)
{
// We are assuming TTI is the same for all TrCh.
// The MAC would need more support otherwise.
int numRF = l1GetNumRadioFrames(0);
for (int i = 0; i < numRF; i++) {
//LOG(DEBUG) << "calling l1SendFrame2"<<LOGVAR(i)<< mMultiplexerBuf[i].str();
l1SendFrame2(mMultiplexerBuf[i],tfci);
}
}
// The number of TransporBlocks passed in is an intrinsic part of the L1TrChEncoder available as getNumTB().
void L1TrChEncoder::l1CrcAndTBConcatenation(L1FecProgInfo *fpi, TransportBlock const *tblocks[RrcDefs::maxTbPerTrCh])
{
unsigned numTB = fpi->getNumTB();
unsigned tbsize = fpi->getTBSize();
unsigned paritysize = fpi->getPB();
// 24.212 4.2.2.1 Transport Block Concatenation.
// catbuf is the result of concatenation.
initSize(crcAndTBConcatenationBuf, numTB * (tbsize + paritysize));
for (unsigned tbn = 0; tbn < numTB; tbn++) {
const BitVector a = tblocks[tbn]->alias();
assert(a.size() == tbsize);
//OBJLOG(DEBUG) << "L1TrCHFECEncoder input " << a.size() << " " << a;
LOG_DOWNLINK << "L1TrCHEncoder input " <<tblocks[tbn];
// parity - 25.212, 4.2.1
unsigned start = tbn * (tbsize + paritysize);
a.copyToSegment(crcAndTBConcatenationBuf,start, tbsize);
BitVector parityOfA = crcAndTBConcatenationBuf.segment(start+tbsize, paritysize);
getParity(a, parityOfA);
}
//OBJLOG(DEBUG) << "with parity " << crcAndTBConcatenationBuf.size() << " " << crcAndTBConcatenationBuf;
l1ChannelCoding(fpi,crcAndTBConcatenationBuf);
}
void L1TrChEncoder::l1ChannelCoding(L1FecProgInfo *fpi, BitVector &catbuf)
{
BitVector c; // The result after convolutional encoding.
if (catbuf.size() == 0) { c = BitVector((size_t) 0); l1RateMatching(fpi,c); return;}
unsigned Z = getZ();
if (catbuf.size() <= Z) {
// No code block segmentation required.
BitVector o = catbuf.alias();
// 24.212 4.2.3 Channel Coding.
// convolutional coding - 25.212, 4.2.3.1
if (isTurbo()) {
c = BitVector(3 * o.size() + 12);
encode(o,c);
} else {
BitVector in(catbuf.size() + 8);
c = BitVector(2 * in.size());
o.copyTo(in);
in.fill(0, catbuf.size(), 8);
encode(in,c);
}
} else {
// 24.212 4.2.2.2 Code Block Segmentation.
// 25.212 4.2.3.3 concatenation of encoded blocks
// We just encode the blocks directly into the concatenated output c.
// This code is for convolutional coding only!!
unsigned Xi = catbuf.size(); // number of input bits
unsigned Ci = (Xi+ Z-1) / Z; // number of code blocks.
unsigned Ki = (Xi+Ci-1)/Ci; // number of bits per block.
unsigned Yi = Ci * Ki - Xi; // number of filler bits.
// Unnecessary assertion used as a warning that rate-matching
// may be required. The code below works fine, but the RRC
// does not currently take these Yi filler bits into account
// and may have botched the TB size calculation if Yi is non-zero.
// Take this assertion out if you are sure.
// (pat) 6-19-2012: Updated TrChConfig::configDchPS to take Yi into
// account and removed this assertion. We should still assert
// elsewhere that any rate-matching is downward only.
// assert(Yi == 0);
const unsigned csize = isTurbo() ? 3*Ki+12 : 2*Ki+16;
c = BitVector(Ci * csize);
BitVector in(Ki+8);
//unsigned bindex = 0;
for (unsigned r = 0; r < Ci; r++) {
if (Yi && r == 0) {
in.fill(0,0,Yi); // First block has Yi filler bits.
// Harvind changed: (pats TODO: What about the Yi filler bits in first block?)
catbuf.segment(0,Ki-Yi).copyToSegment(in,Yi);
//catbuf.segment(bindex,Ki-Yi).copyToSegment(in,Yi);
//bindex = Ki - Yi;
} else {
// Harvind changed:
catbuf.segment(r*Ki-Yi,Ki).copyToSegment(in,0);
// catbuf.segment(bindex,Ki).copyTo(in);
// bindex += Ki;
}
// 24.212 4.2.3 Channel Coding,
// convolutional coding - 25.212, 4.2.3.1
// And implicit concatenation of encoded blocks.
BitVector csegment = c.segment(r*csize,csize);
if (!isTurbo()) {
in.fill(0,Ki,8);
encode(in,csegment);
} else {
BitVector inTrunc = in.segment(0,Ki);
encode(inTrunc,csegment);
}
}
}
l1RateMatching(fpi,c);
}
void L1TrChEncoder::l1RateMatching(L1FecProgInfo *fpi, BitVector &c)
{
// 25.212 4.2.4 Radio Frame Size Equalization
// (pat) 25.212 4.2.4 and I quote:
// "Radio frame size equalisation is only performed in the UL."
// (pat) And that is because we use DTX instead of frame-size-equalisation in DL.
initSize(rateMatchingBuf,fpi->mLowSideRMSz);
// 25.212 4.2.7 Rate-matching
unsigned insize = fpi->mHighSideRMSz; // old: this->mCodeInBkSz;
unsigned outsize = fpi->mLowSideRMSz; // old: this->mRadioFrameSz*this->getNumRadioFrames();
BitVector g = rateMatchingBuf.alias();
if (insize != outsize) {
rateMatchFunc2<char>(c,g,mDlEplus,mDlEminus,1);
} else {
c.copyTo(g);
}
l1FirstDTXInsertion(fpi,g);
}
void L1TrChEncoder::l1FirstDTXInsertion(L1FecProgInfo *fpi, BitVector &g)
{
const int nframes = fpi->getNumRadioFrames();
const unsigned ttisize = fpi->mRFSegmentSize * nframes;
// 4.2.9.1 First insertion of DTX indication.
// We pad with a delta bit out to the largest TF for this TrCh.
// The appended DTX bits are not supposed to be transmitted,
// so eventually we should insert a special value that the transmitter can ignore,
// but for now just pad with zeros.
assert(g.size() <= ttisize);
initSize(firstDtxBuf,ttisize);
BitVector h = firstDtxBuf.alias();
g.copyTo(h);
//h.fillField(g.size(),0x7f,h.size() - g.size());
h.fill(0x7f,g.size(),h.size() - g.size());
LOG_DOWNLINK << "first dtx " << h.str();
// first interleave - 25.212, 4.2.5
initSize(firstInterleaveBuf,ttisize);
BitVector q = firstInterleaveBuf.alias();
h.interleavingNP(fpi->inter1Columns(), fpi->inter1Perm(), q);
LOG_DOWNLINK << "interleaved " << q.str();
//if (gFecTestMode == 1) {
// // (pat) For testing, send it back up through the decoder.
// // Jumper around the radio frame segmentation for now.
// // We are assuming it is RACH/FACH for now.
// SoftVector qdebug(q); // Convert to SoftVector.
// gNodeB.mRachFec->decoder()->writeLowSide3(qdebug);
// return;
//}
// radio frame segmentation - 25.212, 4.2.6
const unsigned frameSize = fpi->mRFSegmentSize;
assert(frameSize == q.size() / nframes);
assert(q.size() % frameSize == 0);
for (int i = 0; i < nframes; i++) {
BitVector seg = q.segment(i * frameSize, frameSize);
mParent->l1Multiplexer(fpi,seg,i);
}
}
SoftVector *L1CCTrChUplink::l1FillerBurst(unsigned size)
{
if (mFillerBurst.size() != size) {
mFillerBurst.resize(size);
for (unsigned i = 0; i < size; i++) {
mFillerBurst[i] = 0.5f + 0.0001*(2.0*(float) (random() & 0x01)-1.0);
}
}
return &mFillerBurst;
}
// (pat) The incoming burst is a single-slot data burst, one of 15 in a radio frame.
// Note that uplink separates data and control info, so the data burst is
// nothing but data - the control burst contains the pilot and tfci bits.
// This function just adds filler bursts for missing slot data.
void L1CCTrChUplink::l1WriteLowSide(const RxBitsBurst &burst)
{
LOG_UPLINK "l1WriteLowSide "<<timestr()<<" RxBitsBurst:"<<burst.str();
// TODO: Enable these assertions.
//assert(burst.mTfciBits[0] >= 0 && burst.mTfciBits[0] <= 1);
//assert(burst.mTfciBits[1] >= 0 && burst.mTfciBits[1] <= 1);
// (pat) Why are we inserting filler bursts going upstream?
// They will fail the parity check (hopefully!) and writeLowSide1 will just throw them away?
// Answer: Because the radio frame un-segmentation needs the right number of
// bursts or it will become permanently desynchronized.
// We cannot use the burst time to synchronize because incoming RACH bursts are not
// synchronized to the main radio clock; we just get 15/30 in a row starting anywhere.
// Also, maybe David hopes the convolutional decoder will span the data.
// This assumes frame boundary is at TN=0.
float garbageTfci[2] = { 0.5, 0.5 };
//unsigned receivedTTIIndex = ((unsigned) burst.time().FN() % numFramesTTI);
unsigned receivedSlotIndex = burst.time().TN();
//LOG(INFO) << "1: mReceiveTime: " << mReceiveTime << " burstTime: " << burst.time();
if (receivedSlotIndex == 0) {
// Special case: if we skipped just the last slot of previous frame, add a fillerframe and send it anyway.
if (burst.time().FN() == mReceiveTime.FN()+1 && mReceiveTime.TN() == gFrameSlots-1) {
LOG(NOTICE) << "Skipped 1 slot at "<<burst.time();
l1AccumulateSlots(l1FillerBurst(burst.size()), garbageTfci); // increments mReceiveTime.TN()
}
// Start a new radio frame.
mReceiveTime = burst.time();
mSlotSize = burst.size();
}
// Check for skipped frames.
if (burst.time().FN() != mReceiveTime.FN()) {
// Hopeless. Any slots we saved previously are useless; discard them.
LOG(NOTICE) << "Incoming skipped frames, expected="<<mReceiveTime<<" burst.time="<<burst.time();
mReceiveTime = UMTS::Time(0,1); // Set to garbage.
return;
}
if (burst.size() != mSlotSize) {
LOG(ERR) << "Incoming radio slot"<<LOGVAR2("size",burst.size())<<" but expected"<<LOGVAR(mSlotSize);
// punt.
mReceiveTime = UMTS::Time(0,1); // Set to garbage.
return;
}
// TODO: Something about skipped frames.
// Fill in skipped slots.
if (mReceiveTime.TN() < receivedSlotIndex) {
int skipped = receivedSlotIndex - mReceiveTime.TN();
LOG(NOTICE) << "Skipped "<<skipped<<" slot(s) at "<<burst.time();
}
while (mReceiveTime.TN() < receivedSlotIndex) {
l1AccumulateSlots(l1FillerBurst(burst.size()), garbageTfci); // increments mReceiveTime.TN()
}
l1AccumulateSlots(&burst,burst.mTfciBits);
//LOG(INFO) << "2: mReceiveTime: " << mReceiveTime << " burstTime: " << burst.time();
}
void L1CCTrChUplink::l1WriteLowSideFrame(const RxBitsBurst &burst, float tfci[30])
{
LOG_UPLINK "l1WriteLowSideFrame "<<timestr()<<" RxBitsBurst:"<<burst.str();
// Start a new radio frame.
mReceiveTime = burst.time();
mSlotSize = burst.size()/gFrameSlots;
// Check for skipped frames.
if (burst.time().FN() != mReceiveTime.FN()) {
// Hopeless. Any slots we saved previously are useless; discard them.
LOG(NOTICE) << "Incoming skipped frames, expected="<<mReceiveTime<<" burst.time="<<burst.time();
mReceiveTime = UMTS::Time(0,1); // Set to garbage.
return;
}
for (unsigned j = 0; j < gFrameSlots; j++) {
SoftVector burstSeg(burst.segment(j*mSlotSize,mSlotSize));
float tfciSeg[2] = {tfci[j*2],tfci[j*2+1]};
l1AccumulateSlots(&burstSeg,tfciSeg);
}
//LOG(INFO) << "2: mReceiveTime: " << mReceiveTime << " burstTime: " << burst.time();
}
// (pat) Accumulate slots into one radio frame in mDataIn and tfcibits into mTfciAccumulator.
void L1CCTrChUplink::l1AccumulateSlots(const SoftVector *e, const float tfcibits[2])
{
LOG_UPLINK << "L1TrCHFECDecoder input " << e->str();
// Uplink slot size varies, so the mDSlotAccumulator is sized for the maximum TFC.
int slotIndex = mReceiveTime.TN();
int frameIndex = mReceiveTime.FN();
// The SF and therefore the slot size can vary with each uplink TFC.
unsigned fullsize = e->size() * gFrameSlots;
if (mDSlotAccumulatorBuf.size() != fullsize) { mDSlotAccumulatorBuf.resize(fullsize); }
e->copyToSegment(mDSlotAccumulatorBuf,slotIndex * e->size());
mRawTfciAccumulator[2*slotIndex] = tfcibits[0];
mRawTfciAccumulator[2*slotIndex+1] = tfcibits[1];
mReceiveTime.incTN();
if (mReceiveTime.TN() != 0) {return;}
//OBJLOG(INFO) << "concatenated " << mDSlotAccumulatorBuf.size() << " " << mDSlotAccumulatorBuf;
unsigned tfci = findTfci(mRawTfciAccumulator,mNumTfc);
LOG(NOTICE) << "TFCI: " << tfci << " time: " << mReceiveTime;
// 25.212 4.2.12 Physical Channel Mapping.
// "In compressed mode..." Nope.
l1SecondDeinterleaving(mDSlotAccumulatorBuf, tfci, frameIndex);
}
// It is called the 2nd interleaving but in uplink it happens before the 1st interleaving.
void L1CCTrChUplink::l1SecondDeinterleaving(SoftVector &v, unsigned tfci, unsigned frameIndex)
{
// 25.212 4.2.11 Second Interleaving.
// The SF and therefore the incoming buffer size can vary with each uplink TFC.
//const_cast<SoftVector&>(frame).deInterleavingNP(30, TrCHConsts::inter2Perm, *mHDIBuf);
if (v.size() != mHDIBuf.size()) { mHDIBuf.resize(v.size()); }
v.deInterleavingNP(30, TrCHConsts::inter2Perm, mHDIBuf);
//assert(mHDIBuf.size() == frameSize);
//OBJLOG(INFO) << "2nd deinterleaved " << mHDIBuf.size() << " " << mHDIBuf;
// 25.212 4.2.10 Physical Channel Segmentation.
// "When more than one PhCh is used..." Nope.
l1Demultiplexer(mHDIBuf,tfci, frameIndex);
}
// The input here is one radio-frame, ie, 15 slots worth.
// Chop it up into TrChs using the TFs specified by tfci, and send it up to rate matching.
void L1CCTrChUplink::l1Demultiplexer(SoftVector &frame, unsigned tfci, unsigned frameIndex)
{
// 25.212 4.2.8 TrCh (De-)Multiplexing.
if (tfci >= getNumTfc()) {
// Invalid data.
// TODO what?
return;
}
unsigned loc = 0;
for (unsigned tcid = 0; tcid < getNumTrCh(); tcid++) {
//unsigned tfi = tfc->getTfIndex(tcid);
L1FecProgInfo *fpi = getFPI(tcid,tfci);
unsigned nbits = fpi->mLowSideRMSz; // Number of bits to go to this TrCh.
//printf("nbits: %d %d %u %u\n",nbits, fpi->mHighSideRMSz, loc,tfci);
if (! nbits) { continue; } // No bits for this TrCh this time.
//printf("framesize: %u %u %u %u\n",frame.size(),nbits,loc,tfci);
SoftVector tmp(frame.segment(loc,nbits));
loc += nbits;
mDecoders[tcid][tfci]->l1RateMatching(fpi,tmp, frameIndex);
}
//printf("loc: %d, frame.size: %d\n",loc,frame.size());
if (loc==0) return;
if (loc!=frame.size()) LOG(INFO) << "loc: " << loc << " " << frame.size();
//assert(loc == frame.size());
}
void L1TrChDecoder::l1RateMatching(L1FecProgInfo *fpi, SoftVector &frame, unsigned frameIndex)
{
// 25.212 4.2.7 Rate Matching.
unsigned insize = fpi->mLowSideRMSz;
assert(frame.size() == insize);
unsigned outsize = fpi->mHighSideRMSz; // this->mCodeInBkSz/this->l1GetNumRadioFrames();
mDTtiIndex = frameIndex % fpi->getNumRadioFrames();
if (insize == outsize) {
l1RadioFrameUnsegmentation(fpi,frame);
} else {
rateMatchFunc<float>(frame,mRMBuf,this->mEini[mDTtiIndex]);
OBJLOG(INFO) << "insize: " << insize << "outsize: " << outsize;
//OBJLOG(INFO) << "rate-unmatched" << mRMBuf.size() << " " << mRMBuf;
l1RadioFrameUnsegmentation(fpi,mRMBuf);
}
}
void L1TrChDecoder::l1RadioFrameUnsegmentation(L1FecProgInfo *fpi, const SoftVector&frame)
{
// 25.212 4.2.6 Radio Frame Un-Segmentation.
// If not using TTI=10ms, At this point we have to reaccumulate the complete TTI data.
TTICodes tticode = fpi->getTTICode();
if (tticode == TTI10ms) { l1FirstDeinterleave(fpi,frame); return; }
// Accumulate one ttis worth of Radio Frames into mDTtiBuf.
frame.copyToSegment(mDTtiBuf,mDTtiIndex*frame.size());
unsigned numFramesPerTti = fpi->getNumRadioFrames();
if (mDTtiIndex < numFramesPerTti - 1) {return;}
mDTtiIndex = 0; // prep for next TTI
l1FirstDeinterleave(fpi,mDTtiBuf);
}
// It is called the 1st interleaving but in uplink it happens after the 2nd interleaving.
void L1TrChDecoder::l1FirstDeinterleave(L1FecProgInfo *fpi, const SoftVector &d)
{
// first interleave - 25.212, 4.2.5
SoftVector t(d.size());
const_cast<SoftVector&>(d).deInterleavingNP(fpi->inter1Columns(), fpi->inter1Perm(), t);
//OBJLOG(INFO) << "deinterleaved " << t.size() << " " << t;
// radio frame equalization - 25.212, 4.2.4
// TODO
SoftVector c = t.alias();
l1ChannelDecoding(fpi,c);
}
// Input is post-radio-frame-segmentation, which means input is the accumulation
// of 1, 2, 4, 8 radio frames based on the TTI=10,20,40,80
void L1TrChDecoder::l1ChannelDecoding(L1FecProgInfo *fpi, const SoftVector &c)
{
// FIXME -- This stuff assumes a rate-1/2 coder.
//assert(c.size() <= 2 * getZ());
BitVector b; // The result
if (0) {
// old code does not handle concatenation/segmentation.
BitVector o(c.size() / 2);
decode(c,o);
//OBJLOG(INFO) << "unconvoluted " << o.str();
// 24.212 4.2.2 Transport Block Concatenation and Code Block Segmentation.
// concatenation - 25.212, 4.2.2.1
// segmentation - 25.212, 4.2.2.2
// nothing to do but remove 8 bits of fill
//BitVector b(o.size() - 8);
b = BitVector(o.size() - 8);
o.copyToSegment(b, 0, o.size() - 8);
} else {
// convolutional coding - 25.212, 4.2.3.1
// concatenation of encoded blocks - 25.212, 4.2.3.3
//unsigned Zenc = 2 * getZ() + 16; // encoded size of Z.
unsigned Zenc = isTurbo() ? (3*getZ()+12) : (2*getZ() + 16); // encoded size of Z.
unsigned Ci = (c.size() + Zenc-1)/Zenc; // number of coded blocks.
unsigned Kienc = c.size()/Ci; // number of encoded bits per coded block.
//unsigned Ki = Kienc/2 - 8; // number of unencoded bits per coded block.
unsigned Ki = isTurbo() ? ((Kienc-12)/3) : (Kienc/2 - 8); // number of unencoded bits per coded block
unsigned numFillBits = fpi->mCodeFillBits; // number of filler bits in first coded block.
assert(Kienc * Ci == c.size());
initSize(decodingInBuf, Ci*Ki - numFillBits);
b = decodingInBuf.alias();
//BitVector o1(Kienc/2);
initSize(decodingOutBuf, isTurbo() ? Ki : Ki+8); // pats TODO: Harvind changed, is this right?
BitVector o1 = decodingOutBuf.alias();
for (unsigned r = 0; r < Ci; r++) {
decode(c.segment(r*Kienc,Kienc),o1);
if (numFillBits && (r == 0)) { // skip first fillBits, they aren't data
o1.segmentCopyTo(b,numFillBits,Ki-numFillBits);
} else {
o1.copyToSegment(b,r*Ki-numFillBits,Ki);
}
}
}
//OBJLOG(INFO) << "de-filled " << b.size() << " " << b;
//OBJLOG(INFO) << "de-filled last 100: " << b.segment(b.size()-100,100);
l1Deconcatenation(fpi,b);
}
void L1TrChDecoder::l1Deconcatenation(L1FecProgInfo *fpi, BitVector &b)
{
// TODO
// parity - 25.212, 4.2.1
unsigned pb = fpi->getPB();
unsigned numTB = fpi->mNumTB;
unsigned tbpbSz = fpi->mTBSz + fpi->getPB();
bool frameGood = true;
for (unsigned j = 0; j < numTB; j++) {
BitVector a = b.segment(j*tbpbSz, tbpbSz - pb);
BitVector gotParity = b.segment(j*tbpbSz + tbpbSz - pb, pb);
initSize(expectParity,pb); // TODO: preallocate buffer.
BitVector bWithoutParity = b.segment(j*tbpbSz, tbpbSz - pb);
getParity(bWithoutParity, expectParity);
//bool parityOK = true;
//for (int i = 0; i < pb && parityOK; i++) {
// parityOK = expectParity[i] == gotParity[i];
//}
bool parityOK = expectParity == gotParity;
if (gotParity.sum() == 0) parityOK = false;
OBJLOG(NOTICE) << "parity OK: " << parityOK << " " << expectParity << " " << gotParity;
if (gFecTestMode) {
LOGDEBUG<<"writeLowSide3"<<LOGBV(b) <<LOGBV(gotParity)<<LOGBV(expectParity)<<LOGVAR(parityOK)<<"\n";
}
if (!parityOK) {
frameGood = false;
continue;
}
//assert(mUpstream);
const TransportBlock tb(a);
//LOG(INFO) << "Sending up tb: " << a;
if (parityOK && mUpstream)
mUpstream->macWriteLowSideTb(tb,fpi-> mCCTrChIndex);
}
if (frameGood) {
// TODO: Do we need to keep FER separately for each TrCh as well as for the CCTrCh?
//countGoodFrame();
mParent->countGoodFrame();
} else {
//countBadFrame();
mParent->countBadFrame();
}
}
void L1FER::countGoodFrame()
{
//unnecessary: ScopedLock lock(mLock);
static const float a = 1.0F / ((float)mFERMemory);
static const float b = 1.0F - a;
mFER *= b;
LOG_UPLINK <<"L1TrChDecoder FER=" << mFER;
}
void L1FER::countBadFrame()
{
static const float a = 1.0F / ((float)mFERMemory);
static const float b = 1.0F - a;
mFER = b*mFER + a;
LOG_UPLINK <<"L1TrChDecoder FER=" << mFER;
}
// (pat) This is used by BCH only - see macServiceLoop for FACH and DCH.
// (pat) Could move to BCHFEC
void L1CCTrChDownlink::l1WaitToSend() const
{
//DEBUGF("L1CCTrChDownlink::waitToSend\n");
// Block until the NodeB clock catches up to the
// mostly recently transmitted burst.
//LOG(INFO) << "mPrevWriteTime: " << mPrevWriteTime << ", " << mNextWriteTime;
unsigned fnbefore = gNodeB.clock().FN();
gNodeB.clock().wait(mPrevWriteTime);
unsigned fnafter = gNodeB.clock().FN();
int diff = FNDelta(fnafter, fnbefore);
// It is usual to wait 2 frames since BCH is TTI 20ms.
// TODO: Occasionally it waits 3 frames preceded or followed by 1 frame - why?
if (diff > 3 || diff < 0) { LOG(NOTICE) << "waitToSend waited "<<diff<<" frames"<<LOGVAR(fnbefore)<<LOGVAR(mPrevWriteTime)<<LOGVAR(fnafter); }
//DEBUGF("L1CCTrChDownlink::waitToSend finished\n");
}
void L1TrChEncoderLowRate::encode(BitVector& in, BitVector& c)
{
// convolutional coding - 25.212, 4.2.3.1
// concatenation of encoded blocks - 25.212, 4.2.3.3
in.encode(mVCoder, c);
LOG_DOWNLINK << "convoluted " << c.str();
}
void L1TrChDecoderLowRate::decode(const SoftVector& c, BitVector& o)
{
c.decode(mVCoder, o);
LOG_UPLINK << "unconvoluted " << c.str(); //<< c.size() << " " << c;
}
#if CANNEDBEACON
const TransportBlock *cannedBeaconBlocks[2048];
#endif
#if SAVEME
void BCHFEC::generate()
{
waitToSend();
#if CANNEDBEACON
const TransportBlock *tb = cannedBeaconBlocks[nextWriteTime().FN()>>1];
#else
const TransportBlock *tb = gNodeB.getTxSIB(nextWriteTime().FN());
#endif
writeHighSide(*tb);
}
void BCHFEC::start()
{
#if CANNEDBEACON
for (int i = 0; i < 2048; i++) {
BitVector tmp(62*4);
tmp.unhex(cannedBeacon[i]);
cannedBeaconBlocks[i] = new TransportBlock(tmp.head(62*4-2));
RN_MEMLOG(TransportBlock,cannedBeaconBlocks[i]);
}
#endif
mServiceThread.start((void* (*)(void*))TrCHServiceLoop, (void*)this);
}
void* TrCHServiceLoop(L1TrCHFEC* chan)
{
while (true) {
chan->generate();
}
return 0;
}
#endif
void L1TrChEncoderTurbo::encode(BitVector& in, BitVector &c)
{
// coding - 25.212, 4.2.3.1
// concatenation of encoded blocks - 25.212, 4.2.3.3
in.encode(mTCoder, c, mInterleaver);
LOG_DOWNLINK << "turbo " << c.str(); //c.size() << " " << c;
}
void L1TrChDecoderTurbo::decode(const SoftVector&c, BitVector &o)
{
// coding - 25.212, 4.2.3.1
// concatenation of encoded blocks - 25.212, 4.2.3.3
c.decode(mTCoder, o, mInterleaver);
LOG_UPLINK << "turbo " << o.str(); //o.size() << " " << o;
}
// Create the encoder/decoders for this FEC class from the RRC programming.
// The turbo flag is not used here.
void L1CCTrCh::fecConfig(TrChConfig &config)
{
#if THE_BELOW_DOESNT_WORK
// (pat) To Harvind: If you have trouble here, for testing only, you can try using this code instead;
// you will have to manually fill in the SF, PB and tbsize to exactly match what was programmed in RRC in configDchPS()
int sf = 256;
int pb = 16;
int ultbsize = 340;
int dltbsize = 340;
bool isTurbo = false;
L1CCTrChUplink::fecConfigForOneTrCh(false,sf,TTI10ms,pb,getPhCh()->getDlRadioFrameSize(),ultbsize,0,1,isTurbo);
L1CCTrChUplink::l1InstantiateUplink();
L1CCTrChDownlink::fecConfigForOneTrCh(true,sf,TTI10ms,pb,getPhCh()->getUlRadioFrameSize(),dltbsize,0,1,isTurbo);
L1CCTrChDownlink::l1InstantiateDownlink();
#endif
// Currently we support only one TrCh.
//std::cout << "fecConfig UL\n";
//config.tcdump();
fecComputeUlTrChSizes(config.ul(),static_cast<L1CCTrChUplink*>(this),mPhCh->isDch());
//std::cout << "fecConfig DL\n";
//config.tcdump();
if (!mPhCh->isRach()) fecComputeDlTrChSizes(config.dl(), getPhCh()->getDlRadioFrameSize(), static_cast<L1CCTrChDownlink*>(this));
L1CCTrChUplink::l1InstantiateUplink();
if (!mPhCh->isRach()) L1CCTrChDownlink::l1InstantiateDownlink();
}
void L1CCTrChDownlink::l1InstantiateDownlink()
{
L1CCTrCh *parent = static_cast<L1CCTrCh*>(this);
for (TrChId i = 0; i < getNumTrCh(); i++) {
// There is one encoder for each TF, not one per TFC.
for (TfcId j = 0; j < getNumTfc(); j++) {
L1FecProgInfo *fpi = getFPI(i,j);
TfIndex tfi = fpi->mTfi;
//if (fpi->mCodeInBkSz == 0) continue;
if (!this->mEncoders[i][tfi]) {
if (getTCI(i)->mIsTurbo) {
// Any one of the TFC j can be used to init the encoder for TF tfi; the sizes are the same in all.
this->mEncoders[i][tfi] = new L1TrChEncoderTurbo(parent,fpi);
} else {
this->mEncoders[i][tfi] = new L1TrChEncoderLowRate(parent,fpi);
}
}
}
}
#if 0
if (config) {
// Create trivial encoder set, assuming one TrCh, no explicit TFCS needed..
assert(getNumTrCh() == 0);
for (TfcId j = 0; j < getNumTfc(); j++) {
// Assume it is
if (turbo) {
// Any one of the TFC j can be used to init the encoder for TF tfi; the sizes are the same in all.
this->mEncoders[i][j] = new L1TrChEncoderTurbo(this,getFPI(0,j));
} else {
this->mEncoders[i][j] = new L1TrChEncoderLowRate(this,getFPI(0,j));
}
}
} else {
RrcTfcs *tfcs = config->dl()->getTfcs();
for (TrChId i = 0; i < getNumTrCh(); i++) {
// There is one encoder for each TF, not one per TFC.
// We did not bother to save the TF index for each TFC in the L1FecProgInfo because we never really need it;
// We can find the tf index using the config.
// The other place we might have needed tf index is in the incoming TBS, but that includes the tfc pointer.
for (TfcId j = 0; j < getNumTfc(); j++) {
RrcTfc *tfc = tfcs->getTfc(j);
TfIndex tfi = tfc->getTfIndex(i);
if (!this->mEncoders[i][tfi]) {
if (turbo) {
// Any one of the TFC j can be used to init the encoder for TF tfi; the sizes are the same in all.
this->mEncoders[i][tfi] = new L1TrChEncoderTurbo(this,getFPI(i,j));
} else {
this->mEncoders[i][tfi] = new L1TrChEncoderLowRate(this,getFPI(i,j));
}
}
}
}
}
#endif
}
void L1CCTrChUplink::l1InstantiateUplink()
{
L1CCTrCh *parent = static_cast<L1CCTrCh*>(this);
//initSize(mDSlotAccumulatorBuf,parent->getMaxUlRadioFrameSz());
//initSize(mHDIBuf,parent->getMaxUlRadioFrameSz());
for (TrChId i = 0; i < getNumTrCh(); i++) {
for (TfcId j = 0; j < getNumTfc(); j++) {
L1FecProgInfo *fpi = getFPI(i,j);
assert(j == fpi->mTfi);
// (pat) 12-8-2012: Harvind modified the turbocoder code to not abort if you send it a K of 0.
if (getTCI(i)->mIsTurbo) {
this->mDecoders[i][j] = new L1TrChDecoderTurbo(parent,fpi);
} else {
this->mDecoders[i][j] = new L1TrChDecoderLowRate(parent,fpi);
}
}
}
}
#if 0
void L1DCHFEC::open()
{
//ScopedLock lock(gActiveDCH.mLock);
// The DCHFEC was already allocated from the ChannelTree by the caller.
assert(phChAllocated());
// TODO: If this is a voice channel it needs to go in mDTCHPool;
gActiveDCH.push_back(this); // No one uses this yet.
LOGDEBUG << "Opening DCH" << endl;
controlOpen();
}
// TODO: Do we want a time delay here somewhere before reusing the channel?
// TODO UMTS - If hardRelease, we need to force-expire that timer.
void L1DCHFEC::close(bool hardRelease)
{
ScopedLock lock(gActiveDCH.mLock);
gActiveDCH.remove(this);
getPhCh()->phChClose(); // Allows reallocation in the ChannelTree.
// downlinkClose(); no close needed.
controlClose();
}
#endif
// Test fecConfigForOneTrCh against fecConfigTrivial.
static void testCCProgramBCH()
{
// Radio frame size is 270.
FecProgInfoSimple fpi(256,TTI20ms,16,270);
// with parity=(246+16)=262; encoded=2*(262)+16 = 540;
// PhCh is 18 bits/slot * 15 slots * 2 radio frames for TTI20ms = 540;
L1CCTrChInfo info1, info2;
info1.fecConfigTrivial(256,TTI20ms,16,270);
info2.fecConfigForOneTrCh(true,256,TTI20ms,16, 270,246,1,1,false);
info1.musteql(info2);
}
static void testCCProgramRach(int SF, TTICodes tticode,int PB)
{
TrChConfig config;
L1CCTrChInfo info1, info2;
unsigned radioFrameSize = gFrameLen / SF;
unsigned numRadioFrames = TTICode2NumFrames(tticode);
unsigned ulTotalSize = radioFrameSize * numRadioFrames;
unsigned requestedTBSize = RrcDefs::R2DecodedSize(ulTotalSize) - PB;
unsigned tbsize = quantizeRlcSize(true,requestedTBSize);
info1.fecConfigForOneTrCh(false,SF,tticode, PB, radioFrameSize,tbsize,1,1,false);
config.configRachTrCh(SF,tticode,PB,0);
fecComputeUlTrChSizes(config.ul(),&info2);
LOGDEBUG << "RACH program test:"<<LOGVAR(SF)<<LOGVAR(tticode)<<LOGVAR(PB)<<LOGVAR(radioFrameSize)<<LOGVAR(ulTotalSize)
<<LOGVAR(requestedTBSize)<<LOGVAR(tbsize) << "\n";
LOGDEBUG << "manual result:" << info1.str();
LOGDEBUG << "compute result:" << info2.str();
info1.musteql(info2);
}
static void testCCProgramFach(int SF, TTICodes tticode,int PB)
{
TrChConfig config;
L1CCTrChInfo info1, info2;
unsigned radioFrameSize = getDlRadioFrameSize(SCCPCHType, SF);
unsigned numRadioFrames = TTICode2NumFrames(tticode);
unsigned ulTotalSize = radioFrameSize * numRadioFrames;
unsigned requestedTBSize = RrcDefs::R2DecodedSize(ulTotalSize) - PB;
unsigned tbsize = quantizeRlcSize(true,requestedTBSize);
info1.fecConfigForOneTrCh(true,SF,tticode, PB, radioFrameSize,tbsize,1,1,false);
config.configFachTrCh(SF,tticode,PB,0);
fecComputeDlTrChSizes(config.dl(),radioFrameSize,&info2);
LOGDEBUG << "FACH program test:"<<LOGVAR(SF)<<LOGVAR(tticode)<<LOGVAR(PB)<<LOGVAR(radioFrameSize)<<LOGVAR(ulTotalSize)
<<LOGVAR(requestedTBSize)<<LOGVAR(tbsize) << "\n";
LOGDEBUG << "manual result:" << info1.str();
LOGDEBUG << "compute result:" << info2.str();
info1.musteql(info2);
}
static void testCCDch(unsigned sf, unsigned pb, TTICodes tticode, unsigned tbsize, bool isTurbo)
{
LOGDEBUG << "test dch"<<LOGVAR(tbsize)<<LOGVAR(sf)<<LOGVAR(pb)<<LOGVAR(tticode)<<LOGVAR(isTurbo)<<"\n";
L1CCTrChInfo info1ul, info1dl, infocul, infocdl;
unsigned dlRFSize = getDlRadioFrameSize(DPDCHType, sf);
unsigned ulRFSize = gFrameLen / sf;
DCHFEC *dch = gChannelTree.chChooseBySF(sf);
if (!dch) {
LOGDEBUG << "debug: Could not allocate DCH of sf" <<sf << "\n";
return;
}
// First test the single-TB config.
// Start with a fresh config each time:
TrChConfig *config = new TrChConfig;
config->configDchPS(dch, tticode,pb,isTurbo,tbsize,tbsize);
// test downlink:
{
fecComputeDlTrChSizes(config->dl(),dlRFSize,&infocdl);
// For fecConfigForOneTrCh, dig out the values for the last TF computed by configDchPS.
//unsigned dltbsize = tbsize ? tbsize : config->dl()->getTfs(0)->getMaxTBSize();
RrcTfs *dltfs = config->dl()->getTfs(0); // TFS for TrCh 0
unsigned dltbsize = dltfs->getTBSize(dltfs->getNumTf()-1);
unsigned dlnumtbs = dltfs->getNumTB(dltfs->getNumTf()-1);
LOGDEBUG << format("dltfs numTf=%u numTBs=%u\n",dltfs->getNumTf(),dlnumtbs);
info1dl.fecConfigForOneTrCh(true,sf,tticode, pb, dlRFSize,dltbsize,0,dlnumtbs,false);
LOGDEBUG << "info1dl:" << info1dl.str();
LOGDEBUG << "infocdl:" << infocdl.str();
if (!isTurbo) info1dl.musteql(infocdl);
}
// test uplink:
{
fecComputeUlTrChSizes(config->ul(),&infocul);
LOGDEBUG << "infocul:" << infocul.str();
//unsigned ultbsize = tbsize ? tbsize : config->ul()->getTfs(0)->getMaxTBSize();
RrcTfs *ultfs = config->ul()->getTfs(0); // TFS for TrCh 0
unsigned ultbsize = ultfs->getTBSize(ultfs->getNumTf()-1);
unsigned ulnumtbs = ultfs->getNumTB(ultfs->getNumTf()-1);
if (ulnumtbs <= 1) { // Too complicated for multiple TBs.
info1ul.fecConfigForOneTrCh(false,sf,tticode, pb, ulRFSize,ultbsize,0,ulnumtbs,false);
LOGDEBUG << "info1ul:" << info1ul.str();
// They may not be equal because fecComputeUlTrChSizes changes the SF, which is detectable
// by checking the uplink RFSegmentSize. This is most likely to happen in the smallest non-zero TFC #1.
int rf1size = info1ul.getFPI(0,1)->mRFSegmentSize;
int rfcsize = infocul.getFPI(0,1)->mRFSegmentSize;
if (rf1size == rfcsize) {
if (!isTurbo) info1ul.musteql(infocul);
} else {
LOGDEBUG << format("Not checking info1ul because SF not equal %d != %d\n",rf1size,rfcsize);
}
}
delete config;
dch->phChClose();
}
}
// Test AMR programming.
static void testCCAMR(unsigned sf)
{
LOGDEBUG << "test AMR"<<LOGVAR(sf)<<"\n";
L1CCTrChInfo infoamrul, infoamrdl;
unsigned dlRFSize = getDlRadioFrameSize(DPDCHType, sf);
//unsigned ulRFSize = gFrameLen / sf;
DCHFEC *dch = gChannelTree.chChooseBySF(sf);
if (!dch) {
LOGDEBUG << "debug: Could not allocate DCH of sf" <<sf << "\n";
return;
}
// First test the single-TB config.
// Start with a fresh config each time:
TrChConfig *config = new TrChConfig;
config->defaultConfig3TrCh();
LOGDEBUG << "AMR dl config:" << config->dl()->str() << "\n";
LOGDEBUG << "AMR ul config:" << config->ul()->str() << "\n";
// downlink:
fecComputeDlTrChSizes(config->dl(),dlRFSize,&infoamrdl);
LOGDEBUG << "infoamrdl:" << infoamrdl.str();
// uplink:
fecComputeUlTrChSizes(config->ul(),&infoamrul);
LOGDEBUG << "infoamrul:" << infoamrul.str();
}
void testCCProgramDCH(unsigned tbsize)
{
int SFs[7] = { 256, 128, 64, 32, 16, 8, 4 };
int PBs[3] = { 16, 12, 8 };
TTICodes tticode = TTI10ms;
for (unsigned iSF = 0; iSF < sizeof(SFs)/sizeof(*SFs); iSF++) {
int sf = SFs[iSF];
for (unsigned iPB = 0; iPB < sizeof(PBs)/sizeof(*PBs); iPB++) {
int pb = PBs[iPB];
for (int isTurbo = 0; isTurbo <= 1; isTurbo++) {
testCCDch(sf,pb,tticode,tbsize,isTurbo);
}
}
}
}
// Just print out a bunch of sizes.
static void printCC(std::ostream &os)
{
L1CCTrChInfo info1;
int SFs[6] = { 256, 128, 64, 32, 16, 8 };
int PBs[3] = { 16, 12, 8 };
TTICodes TTIs[2] = { TTI10ms, TTI20ms };
int TBSzs[1] = { 340 }; // Must pre-include the 4 MAC bits for logical channel multiplexing
bool isTurbo = false;
bool isDownlink = true;
for (unsigned iSF = 0; iSF < sizeof(SFs)/sizeof(*SFs); iSF++) {
int sf = SFs[iSF];
for (unsigned iPB = 0; iPB < sizeof(PBs)/sizeof(*PBs); iPB++) {
int pb = PBs[iPB];
for (unsigned iTTI = 0; iTTI < sizeof(TTIs)/sizeof(*TTIs); iTTI++) {
TTICodes tticode = TTIs[iTTI];
for (unsigned iTBSz = 0; iTBSz < sizeof(TBSzs)/sizeof(*TBSzs); iTBSz++) {
unsigned tbsize = TBSzs[iTBSz];
//unsigned radioFrameSize = getDlRadioFrameSize(SCCPCHType, sf);
unsigned radioFrameSize = getDlRadioFrameSize(DPDCHType, sf);
for (unsigned ntbs = 1; ntbs < 8; ntbs++) {
info1.fecConfigForOneTrCh(isDownlink,sf,tticode,pb,radioFrameSize,tbsize,1,ntbs,isTurbo);
os << info1.str();
}
}
}
}
}
// Must test if it results in puncturing.
}
void testCCProgramming()
{
// These are the parameters for these functions:
// fecConfigForOneTrCh(bool isDownlink, unsigned wSF,TTICodes wTTICode,unsigned wPB,
// unsigned wRadioFrameSz, unsigned wTBSz, unsigned wMaxTB, bool wTurbo)
// fecConfigTrivial(unsigned wSF,TTICodes wTTICode,unsigned wPB, unsigned wRadioFrameSz)
//std::filebuf fb;
//fb.open("l1cc.log",ios::out);
//ostream os(&fb);
ostream &os = std::cout;
if (0) printCC(os);
testCCProgramBCH();
testCCProgramRach(256,TTI10ms,16);
testCCProgramRach(256,TTI20ms,16);
testCCProgramFach(256,TTI10ms,12);
testCCProgramDCH(0);
testCCProgramDCH(340);
testCCAMR(256);
testCCAMR(128);
//fb.close();
}
}; // namespace
// vim: ts=4 sw=4