/**@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. */ #include "UMTSL1FEC.h" #include "MACEngine.h" #include #include #include #include "UMTSConfig.h" #include "URRCTrCh.h" #include "URRC.h" #include "RateMatch.h" using namespace std; namespace UMTS { int gFecTestMode = 0; extern ConfigurationTable gConfig; DCHListType gActiveDCH; #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 TrCHFECDecoder::TrCHFECDecoder(TrCHFEC* wParent,FecProgInfo &fpi): TrCHFECBase(wParent,fpi), mUpstream(NULL) #if OLD_CONTROL_IF , mRunning(false), mFER(0.0F), mAssignmentTimer(10000), mReleaseTimer(10000) #endif { unsigned frameSize = gFrameLen / SF(); mD = new SoftVector(frameSize); mHDI = new SoftVector(frameSize); mDSlotIndex = 0; unsigned nrf = fpi.getNumRadioFrames();// number of radio frames per tti //mDTti = new SoftVector(frameSize * nrf); // mRM and mDTti are after rate-matching: mRM = new SoftVector(fpi.mCodedBkSz/nrf); mDTti = new SoftVector(fpi.mCodedBkSz); mDTtiIndex = 0; rateMatchComputeUlEini(fpi.mCodedBkSz/nrf,mRadioFrameSz,fpi.getTTICode(),mEini); } #if OLD_CONTROL_IF bool TrCHFECDecoder::recyclable() const { ScopedLock lock(mLock); if (mAssignmentTimer.expired()) return true; if (mReleaseTimer.expired()) return true; return false; } #endif PhCh * TrCHFECBase::getPhCh() const { assert(mParent); return mParent->mPhCh; } unsigned FecProgInfo::getNumRadioFrames() const { return TTICode2NumFrames(getTTICode()); } FecProgInfoSimple::FecProgInfoSimple(unsigned wSF,TTICodes wTTICode,unsigned wPB, unsigned wRadioFrameSz) : FecProgInfo(wSF,wTTICode,wPB,wRadioFrameSz, false) { assert(mPB == 16); // Required for the simple channels. // assume no rate matching, convolutional coding, and compute TB size. mCodedBkSz = mRadioFrameSz * getNumRadioFrames(); mTBSz = RrcDefs::R2DecodedSize(mCodedBkSz) - mPB; } // This class is used to init the FecProgInfo for a full support rach/fach/dch. // Assumes convolutional coding. // TODO: Need a different constructor for Turbo-coded. FecProgInfoInit::FecProgInfoInit(unsigned wSF,TTICodes wTTICode,unsigned wPB, unsigned wRadioFrameSz, unsigned wTBSz, bool wTurbo) : FecProgInfo(wSF,wTTICode,wPB,wRadioFrameSz, wTurbo) { assert(mPB == 24 || mPB == 16 || mPB == 12 || mPB == 8); assert(wTBSz != 0); mTBSz = wTBSz; // Determined by RRC. if (wTurbo) { mCodedBkSz = RrcDefs::TurboEncodedSize(mTBSz + mPB, &mCodeBkSz); mFillBits = RrcDefs::TurboDecodedSize(mCodedBkSz) - (mTBSz+mPB); } else { mCodedBkSz = RrcDefs::R2EncodedSize(mTBSz + mPB, &mCodeBkSz); mFillBits = RrcDefs::R2DecodedSize(mCodedBkSz) - (mTBSz+mPB); } printf("ProgInfo: %u %u %u %u %u\n",wSF,wRadioFrameSz,wTBSz,(unsigned)mCodedBkSz,(unsigned)mFillBits); } TrCHFECDecoder* TrCHFECEncoder::sibling() { assert(mParent); return mParent->decoder(); } const TrCHFECDecoder* TrCHFECEncoder::sibling() const { assert(mParent); return mParent->decoder(); } TrCHFECEncoder* TrCHFECDecoder::sibling() { assert(mParent); return mParent->encoder(); } const TrCHFECEncoder* TrCHFECDecoder::sibling() const { assert(mParent); return mParent->encoder(); } void TrCHFECEncoder::open() { mTotalBursts = 0; mPrevWriteTime = 0; mNextWriteTime = gNodeB.clock().get(); mActive = true; } void TrCHFECEncoder::close() { mActive = false; } void TrCHFECDecoder::open() { #if OLD_CONTROL_IF mActive = true; mAssignmentTimer.set(); mReleaseTimer.reset(); #else controlOpen(); #endif } void TrCHFECDecoder::close(bool hardRelease) { #if OLD_CONTROL_IF mActive = false; mReleaseTimer.set(); // TODO UMTS - If hardRelease, we need to force-expire that timer. #else controlClose(hardRelease); #endif } // Incoming frame is the result of 25.212 4.2.6 Radio Frame Segmentation. void TrCHFECEncoder::sendFrame(BitVector& frame, unsigned tfci) { //TODO: This test now fails, correctly, due to rate matching. //assert(frame.size()%gFrameSlots == 0); BitVector h = frame.alias(); // 25.212 4.2.8 TrCh Multiplexing. // (pat) We only have one TrCh, so nothing to do. // 25.212 4.2.9 Insertion of Discontinuous Transmission (DTX) Indicators. // Since we only either the max. size of possible transport format combinatiors or transmit nothing, DTX is not needed. // (pat) This adds empty bits to fill up the radio frames. // The description is complicated by various types of compressed frames that we // will never support, but it is basically just right-padding with emptiness. // 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 BitVector Yout(Ysize); if (padding == 0) { h.interleavingNP(C2, TrCHConsts::inter2Perm, Yout); } 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. BitVector Yin(Ysize); // Temporary buffer h.copyTo(Yin); memset(Yin.begin()+hsize,padval,padding); // Add the padding. Yin.interleavingNP(C2, TrCHConsts::inter2Perm, Yout); // Strip out the padding bits. char *yp = Yout.begin(), *yend = Yout.end(); for (char *cp = yp; cp < yend; cp++) { if (*cp != padval) { *yp++ = *cp; } } assert(yp == Yin.begin() + hsize); } BitVector U(Yout.head(hsize)); #if USE_OLD_FEC // This makes assumptions about RACHFEC so does not compile with the new code. if (gFecTestMode == 2) { gNodeB.mRachFec->decoder()->writeLowSide2(U); return; } #endif // 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); 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&other) const BitVector slotBits = U.segment(i*dataSlotSize,dataSlotSize); TxBitsBurst *out = new TxBitsBurst( slotBits, SF(), SpCode(), mNextWriteTime.slot(i),true ); RN_MEMLOG(TxBitsBurst,out); phch->getRadio()->writeHighSide(out); } } else { int radioFrameSize = 2*(gFrameLen / SF()); 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<mNPilot; const unsigned pi = dlslot->mPilotIndex; // Double check this against channel parameters. assert(dataSlotSize == ndata1+ndata2); BitVector radioSlotBits(radioSlotSize); for (unsigned s=0; s 0) { U.segment(dataStart,ndata1).copyToSegment(radioSlotBits,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 // TODO: Will need to do better power control later tpcField = ((1 << ntpc)-1); // * ( (s+mNextWriteTime.FN()) % 2); radioSlotBits.writeField(wp,tpcField,ntpc); #ifndef RELEASE99 radioSlotBits.writeFieldReversed(wp,tfciCode&tfciMask,ntfci); #endif if (ndata2 > 0) { U.segment(dataStart+ndata1,ndata2).copyToSegment(radioSlotBits,wp,ndata2); wp += ndata2; } radioSlotBits.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(radioSlotBits, SF(), SpCode(), mNextWriteTime.slot(s)); RN_MEMLOG(TxBitsBurst,out); phch->getRadio()->writeHighSide(out); } } mPrevWriteTime = mNextWriteTime; ++mNextWriteTime; // The ++ does not matter for BCH because the TBs have scheduled() set, which overrides mNextWriteTime. } #if 0 //void TrCHConsts::initPilotBitPatterns() //{ // // 25.211 5.3.2 Table 12: Pilot bit patterns for downlink DPCCH with Npilot = 2, 4, 8 and 16 // // // // Npilot=2 Npilot=4 Npilot=8 Npilot=16 // //Symbol 0 0 1 0 1 2 3 0 1 2 3 4 5 6 7 // // # // //Slot #0 11 11 11 11 11 11 10 11 11 11 10 11 11 11 10 // // 1 00 11 00 11 00 11 10 11 00 11 10 11 11 11 00 // // 2 01 11 01 11 01 11 01 11 01 11 01 11 10 11 00 // // 3 00 11 00 11 00 11 00 11 00 11 00 11 01 11 10 // // 4 10 11 10 11 10 11 01 11 10 11 01 11 11 11 11 // // 5 11 11 11 11 11 11 10 11 11 11 10 11 01 11 01 // // 6 11 11 11 11 11 11 00 11 11 11 00 11 10 11 11 // // 7 10 11 10 11 10 11 00 11 10 11 00 11 10 11 00 // // 8 01 11 01 11 01 11 10 11 01 11 10 11 00 11 11 // // 9 11 11 11 11 11 11 11 11 11 11 11 11 00 11 11 // // 10 01 11 01 11 01 11 01 11 01 11 01 11 11 11 10 // // 11 10 11 10 11 10 11 11 11 10 11 11 11 00 11 10 // // 12 10 11 10 11 10 11 00 11 10 11 00 11 01 11 01 // // 13 00 11 00 11 00 11 11 11 00 11 11 11 00 11 00 // // 14 00 11 00 11 00 11 11 11 00 11 11 11 10 11 01 // // // These are from columns 1,3,5,7 for nPilot==16; all the others columns are just copies. // // Table 19: Pilot Symbol Pattern for SCCPCH is identical. // static uint8_t sDlPilotBitPatternTable[4][gFrameSlots] = { // { 3, 0, 1, 0, 2, 3, 3, 2, 1, 3, 1, 2, 2, 0, 0 }, // column 1 // { 2, 2, 1, 0, 1, 2, 0, 0, 2, 3, 1, 3, 0, 3, 3 }, // column 3 // { 3, 3, 2, 1, 3, 1, 2, 2, 0, 0, 3, 0, 1, 0, 2 }, // column 5 // { 2, 0, 0, 2, 3, 1, 3, 0, 3, 3, 2, 2, 1, 0, 1 } // column 7 // }; // // for (unsigned slot = 0; slot < gFrameSlots; slot++) { // uint16_t col1 = sDlPilotBitPatternTable[0][slot]; // uint16_t col3 = sDlPilotBitPatternTable[1][slot]; // uint16_t col5 = sDlPilotBitPatternTable[2][slot]; // uint16_t col7 = sDlPilotBitPatternTable[3][slot]; // uint16_t pat; // // Npilot=2 // sDlPilotBitPattern[0][slot] = col1; // // Npilot=4 // sDlPilotBitPattern[1][slot] = pat = (3<<2)|col1; // // Npilot=8 // sDlPilotBitPattern[2][slot] = pat = (pat<<4)|(3<<2)|col3; // // Npilot=16 // sDlPilotBitPattern[3][slot] = (pat<<8)|(3<<6)|(col5<<4)|(3<<2)|col7; // } //} // // // // 25.212 4.2.11 table 7: Inter-Column permutation pattern for 2nd interleaving: //const char TrCHConsts::inter2Perm[30] = {0,20,10,5,15,25,3,13,23,8,18,28,1,11,21, // 6,16,26,4,14,24,19,9,29,12,2,7,22,27,17}; // // // (pat) 25.212 4.3.3 table 8: Magic for TFCI code encoding. // // The codes are 32 words of 10 bits each, but I am generating it // // form the original binary table in the spec (below) // // to avoid any transcription errors. //const bool TrCHConsts::reedMullerTable[32][10] = { // // i Mi,0 Mi,1 Mi,2 Mi,3 Mi,4 Mi,5 Mi,6 Mi,7 Mi,8 Mi,9 // /*0*/ { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 }, // /*1*/ { 0, 1, 0, 0, 0, 1, 1, 0, 0, 0 }, // /*2*/ { 1, 1, 0, 0, 0, 1, 0, 0, 0, 1 }, // /*3*/ { 0, 0, 1, 0, 0, 1, 1, 0, 1, 1 }, // /*4*/ { 1, 0, 1, 0, 0, 1, 0, 0, 0, 1 }, // /*5*/ { 0, 1, 1, 0, 0, 1, 0, 0, 1, 0 }, // /*6*/ { 1, 1, 1, 0, 0, 1, 0, 1, 0, 0 }, // /*7*/ { 0, 0, 0, 1, 0, 1, 0, 1, 1, 0 }, // /*8*/ { 1, 0, 0, 1, 0, 1, 1, 1, 1, 0 }, // /*9*/ { 0, 1, 0, 1, 0, 1, 1, 0, 1, 1 }, // /*10*/ { 1, 1, 0, 1, 0, 1, 0, 0, 1, 1 }, // /*11*/ { 0, 0, 1, 1, 0, 1, 0, 1, 1, 0 }, // /*12*/ { 1, 0, 1, 1, 0, 1, 0, 1, 0, 1 }, // /*13*/ { 0, 1, 1, 1, 0, 1, 1, 0, 0, 1 }, // /*14*/ { 1, 1, 1, 1, 0, 1, 1, 1, 1, 1 }, // /*15*/ { 1, 0, 0, 0, 1, 1, 1, 1, 0, 0 }, // /*16*/ { 0, 1, 0, 0, 1, 1, 1, 1, 0, 1 }, // /*17*/ { 1, 1, 0, 0, 1, 1, 1, 0, 1, 0 }, // /*18*/ { 0, 0, 1, 0, 1, 1, 0, 1, 1, 1 }, // /*19*/ { 1, 0, 1, 0, 1, 1, 0, 1, 0, 1 }, // /*20*/ { 0, 1, 1, 0, 1, 1, 0, 0, 1, 1 }, // /*21*/ { 1, 1, 1, 0, 1, 1, 0, 1, 1, 1 }, // /*22*/ { 0, 0, 0, 1, 1, 1, 0, 1, 0, 0 }, // /*23*/ { 1, 0, 0, 1, 1, 1, 1, 1, 0, 1 }, // /*24*/ { 0, 1, 0, 1, 1, 1, 1, 0, 1, 0 }, // /*25*/ { 1, 1, 0, 1, 1, 1, 1, 0, 0, 1 }, // /*26*/ { 0, 0, 1, 1, 1, 1, 0, 0, 1, 0 }, // /*27*/ { 1, 0, 1, 1, 1, 1, 1, 1, 0, 0 }, // /*28*/ { 0, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, // /*29*/ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, // /*30*/ { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 }, // /*31*/ { 0, 0, 0, 0, 1, 1, 1, 0, 0, 0 } // }; // //void TrCHConsts::initTfciCodes() //{ // // Pre-compute the tfci code for each possible tfci. // // Implements 4.3.3 verbatim. // // Yes, I realize we could turn the table sideways and do this quickly, // // but it is only done once, ever. // for (unsigned tfci = 0; tfci < sMaxTfci; tfci++) { // uint32_t result = 0; // for (unsigned i = 0; i <= 31; i++) { // unsigned bi = 0; // for (unsigned n = 0; n <= 9; n++) { // unsigned an = (tfci >> n) & 1; // a0 is the lsb of tfci. // bi += (an & reedMullerTable[i][n]); // b0 is the lsb of the result. // } // result |= ((bi&1) << i); // } // sTfciCodes[tfci] = result; // if (tfci < 4) printf("TFCI[%d] = 0x%x\n",tfci,sTfciCodes[tfci]); // } //} // //// This is the wonderfully redundant redundant C++ way to declare declare declare static members. //bool TrCHConsts::oneTimeInit = false; //uint16_t TrCHConsts::sDlPilotBitPattern[4][15]; //uint32_t TrCHConsts::sTfciCodes[sMaxTfci]; // Table for up to 8 bit tfci, plenty for us. // //TrCHConsts::TrCHConsts(TTICodes wTTImsDiv10Log2) // :mTTImsDiv10Log2(wTTImsDiv10Log2) //{ // static int inter1Columns[4] = { // 1, // TTI = 10ms // 2, // TTI = 20ms // 4, // TTI = 40ms // 8 // TTI = 80ms // }; // // // 25.212 4.2.5.2 table 4: Inter-Column permutation pattern for 1st interleaving: // static char inter1Perm[4][8] = { // {0}, // TTI = 10ms // {0, 1}, // TTI = 20ms // {0, 2, 1, 3}, // TTI = 40ms // {0, 4, 2, 6, 1, 5, 3, 7} // TTI = 80ms // }; // // // /** // static uint16_t reedMullerCode[32]; // They are 10 bits long. // for (unsigned w = 0; w < 32; w++) { // unsigned code = 0; // for (unsigned b = 0; b < 10; b++) { // code = (code << 1) | !!reedMullerTable[w][b]; // } // ReedMullerCode[w] = code; // } // **/ // // if (!oneTimeInit) { // oneTimeInit = true; // initPilotBitPatterns(); // initTfciCodes(); // } // // mInter1Columns = inter1Columns[mTTImsDiv10Log2]; // mInter1Perm = inter1Perm[mTTImsDiv10Log2]; //} // //// In uplink each slot has 2 TFCI bits which are concatenated to form 30 bits, //// from which we attempt to retrieve the original tfci. //unsigned findTfci(SoftVector &radioTfciBits, unsigned numTfcis) //{ // assert(radioTfciBits.size() == 30); // float *bits = radioTfciBits.begin(); // // unsigned bestTfci = 0; // float bestMatch = 0; // assert(numTfcis <= TrCHConsts::sMaxTfci); // for (unsigned tfci = 0; tfci < numTfcis; tfci++) { // uint32_t tfciCode = TrCHConsts::sTfciCodes[tfci]; // float thisMatch = 0; // for (unsigned b = 0; b < 30; b++) { // // The bits are transmitted in the slots LSB first, so start with LSB of tfciCode. // unsigned wantbit = tfciCode & 1; tfciCode >>= 1; // float havebit = RN_BOUND(bits[b],0.0,1.0); // if (wantbit) { // thisMatch += havebit; // } else { // thisMatch += 1.0 - havebit; // } // } // // A perfect match would be 30. // if (thisMatch > bestMatch) { // bestMatch = thisMatch; // bestTfci = tfci; // } // } // return bestTfci; // TODO: Regardless of how poor it is? //} #endif // 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); } } void TrCHFECEncoder::writeHighSide(const TransportBlock &tblock) { const BitVector a = tblock.alias(); //PhCh *phch = getPhCh(); //PhChType chType = phch->phChType(); /*if (chType==SCCPCHType) { LOG(NOTICE) << "SCCPCH TrCHFECEncoder input " << a.size() << " " << a.hexstr(); LOG(NOTICE) << tblock.size() << " " << trBkSz(); }*/ LOG(DEBUG) << "TrCHFECEncoder input " <mRadioFrameSz*this->getNumRadioFrames(); unsigned insize = this->mCodedBkSz; BitVector h(outsize); if (insize != outsize) rateMatchFunc(g,h,1); else g.copyTo(h); // not doing insertion of DTX (pat) doesnt go here? // 4.2.9 //BitVector h; // This is 3-valued, but for now, just pad with zeros. // Since we only either the max. size of possible transport format combinatiors or transmit nothing, DTX is not needed. /*if (insize == outsize) { h = frame; } else { h = BitVector(outsize); frame.copyTo(h); h.tail(frame.size()).zero(); }*/ // first interleave - 25.212, 4.2.5 BitVector q(h.size()); h.interleavingNP(inter1Columns(), inter1Perm(), q); //LOG(DEBUG) << "interleaved " <decoder()->writeLowSide3(qdebug); return; } #endif // radio frame segmentation - 25.212, 4.2.6 // inter1Columns is the same as the number of 10 ms radio frames in the TTI. const int frames = inter1Columns(); const int frameSize = q.size() / frames; assert(q.size() % frameSize == 0); if (tblock.scheduled()) mNextWriteTime = tblock.time(); for (int i = 0; i < frames; i++) { BitVector seg = q.segment(i * frameSize, frameSize); sendFrame(seg,0 /*tblock.mTfci*/); } } // (pat) I assume the incoming burst is a single-slot data burst. // 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 TrCHFECDecoder::l1WriteLowSide(const RxBitsBurst &burst) { // TODO: Enable these assertions. //assert(burst.mTfciBits[0] >= 0 && burst.mTfciBits[0] <= 1); //assert(burst.mTfciBits[1] >= 0 && burst.mTfciBits[1] <= 1); const SoftVector f = burst.alias(); // not doing rate matching SoftVector e = f.alias(); // (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 he hopes the convolutional decoder will span the data. // This assumes frame boundary is at TN=0, and TTI boundary is at FN=0 unsigned expectedTTISlotIndex = mDTtiIndex*gFrameSlots+mDSlotIndex; unsigned receivedTTISlotIndex = burst.time().TN(); unsigned numFramesTTI = TTICode2NumFrames(getTTICode()); unsigned receivedTTIIndex = ((unsigned) burst.time().FN() % numFramesTTI); receivedTTISlotIndex += (receivedTTIIndex*gFrameSlots); if (receivedTTISlotIndex <= expectedTTISlotIndex) { mDSlotIndex = burst.time().TN(); mDTtiIndex = receivedTTIIndex; expectedTTISlotIndex = receivedTTISlotIndex; } //LOG(INFO) << "time: " << burst.time() << " slot: " << mDSlotIndex << " frame " << mDTtiIndex; SoftVector fillerBurst; bool first = true; while (receivedTTISlotIndex > expectedTTISlotIndex) { if (first) { fillerBurst.resize(burst.size()); for (unsigned i = 0; i < burst.size(); i++) fillerBurst[i] = 0.5f + 0.0001*(2.0*(float) (random() & 0x01)-1.0); first = false; } float garbageTfci[2] = { 1.0, 1.0 }; writeLowSide1(fillerBurst, garbageTfci); expectedTTISlotIndex++; } writeLowSide1(e,burst.mTfciBits); } // (pat) Accumulate slots into one radio frame in mD. void TrCHFECDecoder::writeLowSide1(const SoftVector &e, const float tfcibits[2]) { //OBJLOG(INFO) << "TrCHFECDecoder input " << e.size() << " " << e; // (pat) If TTI != 10ms, we just concatenate radio frames until we have them all. // (pat) So accumulate the incoming fuzzy bits in mD until it is full. // radio frame segmentation - 25.212, 4.2.6 unsigned frameSize = gFrameLen / SF(); unsigned slotSize = frameSize/gFrameSlots; assert(e.size() == slotSize); e.copyToSegment(*mD,mDSlotIndex*slotSize); if (++mDSlotIndex < gFrameSlots) {return;} mDSlotIndex = 0; // prep for next Radio Frame. // (pat) Previous code did alot of copying things around: //if (mD->size() == frameSize) mD->resize(0); //SoftVector *tmp = mD; //mD = new SoftVector(*mD, e); //delete tmp; //if (mD->size() < frameSize) return; assert(mD->size() == frameSize); //OBJLOG(INFO) << "concatenated " << mD->size() << " " << *mD; writeLowSide2(*mD); } // The input here is one radio-frame, ie, 15 slots worth. void TrCHFECDecoder::writeLowSide2(const SoftVector &frame) { unsigned frameSize = gFrameLen / SF(); assert(frame.size() == frameSize); // 25.212 4.2.12 Physical Channel Mapping. // "In compressed mode..." Nope. // TODO: These vectors can be allocated statically at initialization time. // 25.212 4.2.11 Second Interleaving. const_cast(frame).deInterleavingNP(30, TrCHConsts::inter2Perm, *mHDI); assert(mHDI->size() == frameSize); //OBJLOG(INFO) << "2nd deinterleaved " << mHDI->size() << " " << *mHDI; // 25.212 4.2.10 Physical Channel Segmentation. // "When more than one PhCh is used..." Nope. // 25.212 4.2.8 TrCh (De-)Multiplexing. // Not yet. // 25.212 4.2.7 Rate Matching. unsigned insize = this->mRadioFrameSz; unsigned outsize = this->mCodedBkSz/this->getNumRadioFrames(); assert(mHDI->size() == insize); SoftVector *result = mHDI; if (insize != outsize) { result = mRM; rateMatchFunc(*mHDI,*mRM,this->mEini[mDTtiIndex]); } OBJLOG(INFO) << "insize: " << insize << "outsize: " << outsize; OBJLOG(INFO) << "rate-unmatched" << result->size() << " " << *result; // 25.212 4.2.6 Radio Frame Segmentation. // If not using TTI=10ms, At this point we have to reaccumulate the complete TTI data. TTICodes tticode = getTTICode(); if (tticode == TTI10ms) { writeLowSide3(*result); return; } // Accumulate one ttis worth of Radio Frames into mDTti. result->copyToSegment(*mDTti,mDTtiIndex*outsize); mDTtiIndex++; unsigned numFramesPerTti = getNumRadioFrames(); if (mDTtiIndex < numFramesPerTti) {return;} mDTtiIndex = 0; // prep for next TTI writeLowSide3(*mDTti); } // 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 TrCHFECDecoder::writeLowSide3(const SoftVector &dataTti) { //OBJLOG(INFO) << "concatenated TTI: " << dataTti.size() << " " << dataTti; // first interleave - 25.212, 4.2.5 SoftVector t(dataTti.size()); const_cast(dataTti).deInterleavingNP(inter1Columns(), inter1Perm(), t); OBJLOG(INFO) << "deinterleaved " << t.size() << " " << t; // radio frame equalization - 25.212, 4.2.4 SoftVector c = t.alias(); // 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.size() << " " << o; // 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 = 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 = isTurbo() ? ((Kienc-12)/3) : (Kienc/2 - 8); // number of unencoded bits per coded block. unsigned numFillBits = fillBits(); // number of filler bits in first coded block. assert(Kienc * Ci == c.size()); b = BitVector(Ci*Ki - numFillBits); BitVector o1(isTurbo() ? Ki : (Ki+8)); //printf("%u %u %u %u\n",Zenc,Ci,Kienc,Ki); 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); // parity - 25.212, 4.2.1 unsigned pb = getPB(); BitVector a = b.segment(0, b.size() - pb); BitVector gotParity = b.segment(b.size() - pb, pb); BitVector expectParity(pb); BitVector bWithoutParity = b.segment(0, b.size() - 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(INFO) << "parity " << expectParity << " " << gotParity; if (gFecTestMode) { std::cout<<"writeLowSide3"<macWriteLowSideTb(tb); } #if OLD_CONTROL_IF void TrCHFECDecoder::countGoodFrame() { ScopedLock lock(mLock); mAssignmentTimer.reset(); static const float a = 1.0F / ((float)mFERMemory); static const float b = 1.0F - a; mFER *= b; OBJLOG(DEBUG) <<"L1Decoder FER=" << mFER; } void TrCHFECDecoder::countBadFrame() { static const float a = 1.0F / ((float)mFERMemory); static const float b = 1.0F - a; mFER = b*mFER + a; OBJLOG(DEBUG) <<"L1Decoder FER=" << mFER; } #endif // (pat) This is used only for BCH - see macServiceLoop for FACH and DCH. //void TrCHFECEncoder::waitToSend() const void TrCHFECEncoder::l1WaitToSend() const { // Block until the NodeB clock catches up to the // mostly recently transmitted burst. //LOG(INFO) << "mPrevWriteTime: " << mPrevWriteTime << ", " << mNextWriteTime; // (pat) TODO: Add Transceiver.mTransmitLatency in here. gNodeB.clock().wait(mPrevWriteTime); //LOG(NOTICE) << "waitToSend "<generate(); } return 0; } void BCHFEC::start() { mServiceThread.start((void* (*)(void*))BCHServiceLoop, (void*)this); } void TrCHFECEncoderTurbo::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); OBJLOG(DEBUG) << "turbo " << c.str(); //c.size() << " " << c; } void TrCHFECDecoderTurbo::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); OBJLOG(DEBUG) << "turbo " << o.str(); //o.size() << " " << o; } #if USE_OLD_DCH // Create the encoder/decoders for this FEC class from the RRC programming. void DCHFEC::fecConfig(TrChConfig &config, bool turbo) { // Currently we support only one TrCh. assert(config.ul()->getNumTrCh() == 1); assert(config.dl()->getNumTrCh() == 1); // Uplink: { RrcTfs *tfs = config.ul()->getTfs(0); // Currently we do not support TFC, so only one TF can be non-empty. int ntf = tfs->getNumTF(); int numNonEmpty = 0; for (int n = 0; n < ntf; n++) { int numtb = tfs->getNumTB(n); int tbsize = tfs->getTBSize(n); if (numtb == 0 || tbsize == 0) continue; if (numNonEmpty++) {assert(0);} // There are multiple non-empty TF. assert(numtb == 1); FecProgInfoInit fpiul(this->getUlSF(),tfs->getTTICode(),tfs->getPB(), this->getUlRadioFrameSize(), tbsize, turbo); if (turbo) { TrCHFECDecoder *decoder = new TrCHFECDecoderTurbo(this,fpiul); this->setDecoder(decoder); } else { TrCHFECDecoder *decoder = new TrCHFECDecoderLowRate(this,fpiul); this->setDecoder(decoder); } } } // Downlink: { RrcTfs *tfs = config.dl()->getTfs(0); // Currently we do not support TFC, so only one TF can be non-empty. int ntf = tfs->getNumTF(); int numNonEmpty = 0; for (int n = 0; n < ntf; n++) { int numtb = tfs->getNumTB(n); int tbsize = tfs->getTBSize(n); if (numtb == 0 || tbsize == 0) continue; if (numNonEmpty++) {assert(0);} // There are multiple non-empty TF. assert(numtb == 1); FecProgInfoInit fpidl(this->getDlSF(),tfs->getTTICode(),tfs->getPB(), this->getDlRadioFrameSize(), tbsize, turbo); if (turbo) { TrCHFECEncoder *encoder = new TrCHFECEncoderTurbo(this,fpidl); this->setEncoder(encoder); } else { TrCHFECEncoder *encoder = new TrCHFECEncoderLowRate(this,fpidl); this->setEncoder(encoder); } } } //this->setDownstream(mRadio); radio pointer moved to PhCh } #endif void DCHFEC::open() { ScopedLock lock(gActiveDCH.mLock); // The DCHFEC was already allocated from the ChannelTree by the caller. assert(phChAllocated()); #if USE_OLD_DCH mEncoder->open(); mDecoder->open(); #else controlOpen(); #endif gActiveDCH.push_back((DCHFEC*)this); cout << "Opening DCH" << endl; } // TODO: Do we want a time delay here somewhere before reusing the channel? void DCHFEC::close() { printf("waiting to remove...\n"); while (gActiveDCH.inTxUse || gActiveDCH.inRxUse) usleep(1000); ScopedLock lock(gActiveDCH.mLock); gActiveDCH.remove((DCHFEC*)this); printf("removed\n"); #if USE_OLD_DCH mEncoder->close(); mDecoder->close(); #else // Somebody needs to deallocate the encoder/decoders. controlClose(); #endif mPhCh->phChClose(); // Allows reallocation in the ChannelTree. } }; // namespace // vim: ts=4 sw=4