Commit 6352d0b6 by Alan Mishchenko

Improvements to Cba data-structure.

parent af828a49
......@@ -2053,7 +2053,7 @@ void Nf_ManSetDefaultPars( Jf_Par_t * pPars )
pPars->nCutNum = 16;
pPars->nProcNum = 0;
pPars->nRounds = 5;
pPars->nRoundsEla = 0;
pPars->nRoundsEla = 1;
pPars->nRelaxRatio = 0;
pPars->nCoarseLimit = 3;
pPars->nAreaTuner = 1;
......
......@@ -34,6 +34,7 @@ static int Cba_CommandPs ( Abc_Frame_t * pAbc, int argc, char ** argv );
static int Cba_CommandPut ( Abc_Frame_t * pAbc, int argc, char ** argv );
static int Cba_CommandGet ( Abc_Frame_t * pAbc, int argc, char ** argv );
static int Cba_CommandClp ( Abc_Frame_t * pAbc, int argc, char ** argv );
static int Cba_CommandBlast ( Abc_Frame_t * pAbc, int argc, char ** argv );
static int Cba_CommandCec ( Abc_Frame_t * pAbc, int argc, char ** argv );
static int Cba_CommandTest ( Abc_Frame_t * pAbc, int argc, char ** argv );
......@@ -64,6 +65,7 @@ void Cba_Init( Abc_Frame_t * pAbc )
Cmd_CommandAdd( pAbc, "New word level", "@put", Cba_CommandPut, 0 );
Cmd_CommandAdd( pAbc, "New word level", "@get", Cba_CommandGet, 0 );
Cmd_CommandAdd( pAbc, "New word level", "@clp", Cba_CommandClp, 0 );
Cmd_CommandAdd( pAbc, "New word level", "@blast", Cba_CommandBlast, 0 );
Cmd_CommandAdd( pAbc, "New word level", "@cec", Cba_CommandCec, 0 );
Cmd_CommandAdd( pAbc, "New word level", "@test", Cba_CommandTest, 0 );
}
......@@ -101,15 +103,18 @@ int Cba_CommandRead( Abc_Frame_t * pAbc, int argc, char ** argv )
FILE * pFile;
Cba_Man_t * p = NULL;
char * pFileName = NULL;
int c, fTest = 0, fVerbose = 0;
int c, fTest = 0, fDfs = 0, fVerbose = 0;
Extra_UtilGetoptReset();
while ( ( c = Extra_UtilGetopt( argc, argv, "tvh" ) ) != EOF )
while ( ( c = Extra_UtilGetopt( argc, argv, "tdvh" ) ) != EOF )
{
switch ( c )
{
case 't':
fTest ^= 1;
break;
case 'd':
fDfs ^= 1;
break;
case 'v':
fVerbose ^= 1;
break;
......@@ -159,12 +164,19 @@ int Cba_CommandRead( Abc_Frame_t * pAbc, int argc, char ** argv )
printf( "Unrecognized input file extension.\n" );
return 0;
}
if ( fDfs )
{
Cba_Man_t * pTemp;
p = Cba_ManDup( pTemp = p, Cba_NtkCollectDfs );
Cba_ManFree( pTemp );
}
Cba_AbcUpdateMan( pAbc, p );
return 0;
usage:
Abc_Print( -2, "usage: @read [-tvh] <file_name>\n" );
Abc_Print( -2, "usage: @read [-tdvh] <file_name>\n" );
Abc_Print( -2, "\t reads hierarchical design\n" );
Abc_Print( -2, "\t-t : toggle testing the parser [default = %s]\n", fTest? "yes": "no" );
Abc_Print( -2, "\t-d : toggle computing DFS ordering [default = %s]\n", fDfs? "yes": "no" );
Abc_Print( -2, "\t-v : toggle printing verbose information [default = %s]\n", fVerbose? "yes": "no" );
Abc_Print( -2, "\t-h : print the command usage\n");
return 1;
......@@ -259,9 +271,13 @@ usage:
int Cba_CommandPs( Abc_Frame_t * pAbc, int argc, char ** argv )
{
Cba_Man_t * p = Cba_AbcGetMan(pAbc);
int c, nModules = 0, fVerbose = 0;
int nModules = 0;
int fShowMulti = 0;
int fShowAdder = 0;
int fDistrib = 0;
int c, fVerbose = 0;
Extra_UtilGetoptReset();
while ( ( c = Extra_UtilGetopt( argc, argv, "Mvh" ) ) != EOF )
while ( ( c = Extra_UtilGetopt( argc, argv, "Mmadvh" ) ) != EOF )
{
switch ( c )
{
......@@ -276,6 +292,15 @@ int Cba_CommandPs( Abc_Frame_t * pAbc, int argc, char ** argv )
if ( nModules < 0 )
goto usage;
break;
case 'm':
fShowMulti ^= 1;
break;
case 'a':
fShowAdder ^= 1;
break;
case 'd':
fDistrib ^= 1;
break;
case 'v':
fVerbose ^= 1;
break;
......@@ -290,12 +315,24 @@ int Cba_CommandPs( Abc_Frame_t * pAbc, int argc, char ** argv )
Abc_Print( 1, "Cba_CommandPs(): There is no current design.\n" );
return 0;
}
Cba_ManPrintStats( p, nModules, fVerbose );
if ( nModules )
{
Cba_ManPrintStats( p, nModules, fVerbose );
return 0;
}
Cba_NtkPrintStatsFull( Cba_ManRoot(p), fDistrib, fVerbose );
if ( fShowMulti )
Cba_NtkPrintNodes( Cba_ManRoot(p), CBA_BOX_MUL );
if ( fShowAdder )
Cba_NtkPrintNodes( Cba_ManRoot(p), CBA_BOX_ADD );
return 0;
usage:
Abc_Print( -2, "usage: @ps [-M num] [-vh]\n" );
Abc_Print( -2, "usage: @ps [-M num] [-madvh]\n" );
Abc_Print( -2, "\t prints statistics\n" );
Abc_Print( -2, "\t-M num : the number of first modules to report [default = %d]\n", nModules );
Abc_Print( -2, "\t-m : toggle printing multipliers [default = %s]\n", fShowMulti? "yes": "no" );
Abc_Print( -2, "\t-a : toggle printing adders [default = %s]\n", fShowAdder? "yes": "no" );
Abc_Print( -2, "\t-d : toggle printing distrubition [default = %s]\n", fDistrib? "yes": "no" );
Abc_Print( -2, "\t-v : toggle printing verbose information [default = %s]\n", fVerbose? "yes": "no" );
Abc_Print( -2, "\t-h : print the command usage\n");
return 1;
......@@ -316,15 +353,18 @@ int Cba_CommandPut( Abc_Frame_t * pAbc, int argc, char ** argv )
{
Cba_Man_t * p = Cba_AbcGetMan(pAbc);
Gia_Man_t * pGia = NULL;
int c, fBarBufs = 1, fVerbose = 0;
int c, fBarBufs = 1, fSeq = 0, fVerbose = 0;
Extra_UtilGetoptReset();
while ( ( c = Extra_UtilGetopt( argc, argv, "bvh" ) ) != EOF )
while ( ( c = Extra_UtilGetopt( argc, argv, "bsvh" ) ) != EOF )
{
switch ( c )
{
case 'b':
fBarBufs ^= 1;
break;
case 's':
fSeq ^= 1;
break;
case 'v':
fVerbose ^= 1;
break;
......@@ -339,7 +379,7 @@ int Cba_CommandPut( Abc_Frame_t * pAbc, int argc, char ** argv )
Abc_Print( 1, "Cba_CommandPut(): There is no current design.\n" );
return 0;
}
pGia = Cba_ManBlast( p, fBarBufs, fVerbose );
pGia = Cba_ManBlast( p, fBarBufs, fSeq, fVerbose );
if ( pGia == NULL )
{
Abc_Print( 1, "Cba_CommandPut(): Conversion to AIG has failed.\n" );
......@@ -348,9 +388,10 @@ int Cba_CommandPut( Abc_Frame_t * pAbc, int argc, char ** argv )
Abc_FrameUpdateGia( pAbc, pGia );
return 0;
usage:
Abc_Print( -2, "usage: @put [-bvh]\n" );
Abc_Print( -2, "usage: @put [-bsvh]\n" );
Abc_Print( -2, "\t extracts AIG from the hierarchical design\n" );
Abc_Print( -2, "\t-b : toggle using barrier buffers [default = %s]\n", fBarBufs? "yes": "no" );
Abc_Print( -2, "\t-s : toggle blasting sequential elements [default = %s]\n", fSeq? "yes": "no" );
Abc_Print( -2, "\t-v : toggle printing verbose information [default = %s]\n", fVerbose? "yes": "no" );
Abc_Print( -2, "\t-h : print the command usage\n");
return 1;
......@@ -479,6 +520,61 @@ usage:
SeeAlso []
******************************************************************************/
int Cba_CommandBlast( Abc_Frame_t * pAbc, int argc, char ** argv )
{
Gia_Man_t * pNew = NULL;
Cba_Man_t * p = Cba_AbcGetMan(pAbc);
int c, fSeq = 0, fVerbose = 0;
Extra_UtilGetoptReset();
while ( ( c = Extra_UtilGetopt( argc, argv, "svh" ) ) != EOF )
{
switch ( c )
{
case 's':
fSeq ^= 1;
break;
case 'v':
fVerbose ^= 1;
break;
case 'h':
goto usage;
default:
goto usage;
}
}
if ( p == NULL )
{
Abc_Print( 1, "Cba_CommandBlast(): There is no current design.\n" );
return 0;
}
pNew = Cba_ManBlast( p, 0, fSeq, fVerbose );
if ( pNew == NULL )
{
Abc_Print( 1, "Cba_CommandBlast(): Bit-blasting has failed.\n" );
return 0;
}
Abc_FrameUpdateGia( pAbc, pNew );
return 0;
usage:
Abc_Print( -2, "usage: %%blast [-svh]\n" );
Abc_Print( -2, "\t performs bit-blasting of the word-level design\n" );
Abc_Print( -2, "\t-s : toggle blasting sequential elements [default = %s]\n", fSeq? "yes": "no" );
Abc_Print( -2, "\t-v : toggle printing verbose information [default = %s]\n", fVerbose? "yes": "no" );
Abc_Print( -2, "\t-h : print the command usage\n");
return 1;
}
/**Function********************************************************************
Synopsis []
Description []
SideEffects []
SeeAlso []
******************************************************************************/
int Cba_CommandCec( Abc_Frame_t * pAbc, int argc, char ** argv )
{
Cba_Man_t * p = Cba_AbcGetMan(pAbc), * pTemp;
......@@ -536,7 +632,7 @@ int Cba_CommandCec( Abc_Frame_t * pAbc, int argc, char ** argv )
fclose( pFile );
// extract AIG from the current design
pFirst = Cba_ManBlast( p, 0, 0 );
pFirst = Cba_ManBlast( p, 0, 0, 0 );
if ( pFirst == NULL )
{
Abc_Print( -1, "Extracting AIG from the current design has failed.\n" );
......@@ -551,7 +647,7 @@ int Cba_CommandCec( Abc_Frame_t * pAbc, int argc, char ** argv )
else if ( !strcmp( Extra_FileNameExtension(pFileName), "cba" ) )
pTemp = Cba_ManReadCba( pFileName );
else assert( 0 );
pSecond = Cba_ManBlast( pTemp, 0, 0 );
pSecond = Cba_ManBlast( pTemp, 0, 0, 0 );
Cba_ManFree( pTemp );
if ( pSecond == NULL )
{
......
......@@ -128,6 +128,7 @@ struct Prs_Man_t_
Vec_Int_t vFailed;
Vec_Int_t vSucceeded;
// error handling
int nOpens; // open port counter
int fUsingTemp2; // vTemp2 is in use
int FuncNameId; // temp value
int FuncRangeId; // temp value
......@@ -226,10 +227,10 @@ static inline void Prs_ManFinalizeNtk( Prs_Man_t * p )
p->pNtk = NULL;
}
static inline int Prs_NtkNewStrId( Prs_Ntk_t * pNtk, const char * format, ... )
static inline int Prs_ManNewStrId( Prs_Man_t * p, const char * format, ... )
{
Abc_Nam_t * p = pNtk->pStrs;
Vec_Str_t * vBuf = Abc_NamBuffer( p );
Abc_Nam_t * pStrs = p->pStrs;
Vec_Str_t * vBuf = Abc_NamBuffer( pStrs );
int nAdded, nSize = 1000;
va_list args; va_start( args, format );
Vec_StrGrow( vBuf, Vec_StrSize(vBuf) + nSize );
......@@ -241,7 +242,7 @@ static inline int Prs_NtkNewStrId( Prs_Ntk_t * pNtk, const char * format, ... )
assert( nSize == nAdded );
}
va_end( args );
return Abc_NamStrFindOrAddLim( p, Vec_StrLimit(vBuf), Vec_StrLimit(vBuf) + nAdded, NULL );
return Abc_NamStrFindOrAddLim( pStrs, Vec_StrLimit(vBuf), Vec_StrLimit(vBuf) + nAdded, NULL );
}
// parsing slice/concatentation/box
......@@ -306,20 +307,22 @@ static inline char * Prs_ManLoadFile( char * pFileName, char ** ppLimit )
static inline Prs_Man_t * Prs_ManAlloc( char * pFileName )
{
Prs_Man_t * p;
char * pBuffer, * pLimit;
pBuffer = Prs_ManLoadFile( pFileName, &pLimit );
if ( pBuffer == NULL )
return NULL;
p = ABC_CALLOC( Prs_Man_t, 1 );
p->pName = pFileName;
p->pBuffer = pBuffer;
p->pLimit = pLimit;
p->pCur = pBuffer;
p->pStrs = Abc_NamStart( 1000, 24 );
p->pFuns = Abc_NamStart( 100, 24 );
p->vNtks = Vec_PtrAlloc( 100 );
p->vHash = Hash_IntManStart( 1000 );
// Hash_Int2ManInsert( p->vHash, 0, 0, 0 );
if ( pFileName )
{
char * pBuffer, * pLimit;
pBuffer = Prs_ManLoadFile( pFileName, &pLimit );
if ( pBuffer == NULL )
return NULL;
p->pName = pFileName;
p->pBuffer = pBuffer;
p->pLimit = pLimit;
p->pCur = pBuffer;
}
p->pStrs = Abc_NamStart( 1000, 24 );
p->pFuns = Abc_NamStart( 100, 24 );
p->vHash = Hash_IntManStart( 1000 );
p->vNtks = Vec_PtrAlloc( 100 );
return p;
}
......
......@@ -1449,7 +1449,7 @@ int Prs_CreateCatIn( Cba_Ntk_t * p, Prs_Ntk_t * pNtk, int Con )
int i, Sig, iObj, iFon, NameId, nBits = 0;
Vec_Int_t * vSigs = Prs_CatSignals(pNtk, Con);
// create input concatenation
iObj = Cba_ObjAlloc( p, CBA_BOX_CATIN, Vec_IntSize(vSigs), 1 );
iObj = Cba_ObjAlloc( p, CBA_BOX_CONCAT, Vec_IntSize(vSigs), 1 );
iFon = Cba_ObjFon0(p, iObj);
//sprintf( Buffer, "_icc%d_", iObj );
//NameId = Cba_NtkNewStrId( p, Buffer );
......@@ -1501,46 +1501,6 @@ int Prs_CreateRange( Cba_Ntk_t * p, int iFon, int NameId )
Cba_FonSetRangeSign( p, iFon, RangeId );
return Cba_FonRangeSize( p, iFon );
}
/*
int Prs_CreateCatOut( Cba_Ntk_t * p, int iFon, Prs_Ntk_t * pNtk, int Con )
{
int i, Sig, iObj, iFonNew, NameId, nBits = 0;
Vec_Int_t * vSigs = Prs_CatSignals(pNtk, Con); char * pSigName;
NameId = Cba_NtkNewStrId( p, "_occ%d_", iFon );
Cba_FonSetName( p, iFon, NameId );
Cba_NtkSetMap( p, NameId, iFon );
// create output concatenation
iObj = Cba_ObjAlloc( p, CBA_BOX_CATOUT, 1, Vec_IntSize(vSigs) );
Cba_ObjSetFinFon( p, iObj, 0, iFon );
// set outputs
Vec_IntForEachEntry( vSigs, Sig, i )
{
int Value = Abc_Lit2Var2(Sig);
Prs_ManType_t Type = (Prs_ManType_t)Abc_Lit2Att2( Sig );
iFonNew = Cba_ObjFon( p, iObj, Vec_IntSize(vSigs)-1-i );
if ( Type == CBA_PRS_NAME )
{
pSigName = Prs_NtkStr(pNtk, Value);
NameId = Cba_NtkNewStrId( p, pSigName );
Cba_FonSetName( p, iFonNew, NameId );
nBits += Prs_CreateRange( p, iFonNew, NameId );
}
else if ( Type == CBA_PRS_SLICE )
{
pSigName = Prs_NtkStr(pNtk, Prs_SliceName(pNtk, Value));
NameId = Cba_NtkNewStrId( p, pSigName );
Cba_FonSetName( p, iFonNew, NameId );
Prs_CreateRange( p, iFonNew, NameId );
// create slice of this concat
Prs_CreateSlice( p, iFonNew, pNtk, Prs_SliceRange(pNtk, Value) );
nBits += Cba_NtkRangeSize( p, Prs_SliceRange(pNtk, Value) );
}
else assert( 0 );
}
Cba_FonSetRange( p, iFon, Cba_NtkHashRange(p, nBits-1, 0) );
return iObj;
}
*/
void Prs_CreateSignalOut( Cba_Ntk_t * p, int iFon, Prs_Ntk_t * pNtk, int Sig )
{
int i, iFonNew, NameOut, RangeOut, NameId, RangeId, RangeSize, nBits = 0;
......@@ -1656,7 +1616,7 @@ void Prs_CreateOutConcat( Cba_Ntk_t * p, int * pSlices, int nSlices )
Prev = iFon;
}
// create new concatenation
iObj = Cba_ObjAlloc( p, CBA_BOX_CATIN, nParts, 1 );
iObj = Cba_ObjAlloc( p, CBA_BOX_CONCAT, nParts, 1 );
iFon = Cba_ObjFon0(p, iObj);
Cba_FonSetName( p, iFon, NameId );
Prs_CreateRange( p, iFon, NameId );
......@@ -1855,7 +1815,7 @@ int Prs_CreateVerilogNtk( Cba_Ntk_t * p, Prs_Ntk_t * pNtk )
{
Vec_Int_t * vBox2Obj = Vec_IntStart( Prs_NtkBoxNum(pNtk) );
Vec_Int_t * vBox; Vec_Ptr_t * vAllRams, * vRam;
int i, k, iObj, iTerm, iFon, FormId, ActId, RangeId, NameId;
int i, k, iObj, iTerm, iFon, FormId, ActId, RangeId, NameId, Type;
// map inputs
Cba_NtkCleanMap( p );
Cba_NtkForEachPi( p, iObj, i )
......@@ -1918,25 +1878,15 @@ int Prs_CreateVerilogNtk( Cba_Ntk_t * p, Prs_Ntk_t * pNtk )
{
if ( Prs_BoxIsNode(pNtk, i) ) // node
{
int Type = Prs_BoxNtk(pNtk, i);
int nSigs = Prs_BoxIONum( pNtk, i );
/*
int NameId = Abc_Lit2Var2(Vec_IntEntry(vBox, 1));
char * pName = Cba_NtkStr( p, NameId );
if ( !strcmp(pName, "E_336717") )
{
int s = 0;
}
*/
iObj = Cba_ObjAlloc( p, Type, nSigs-1, Type == CBA_BOX_ADD ? 2 : 1 );
Type = Prs_BoxNtk(pNtk, i);
iObj = Cba_ObjAlloc( p, Type, Prs_BoxIONum(pNtk, i)-1, Type == CBA_BOX_ADD ? 2 : 1 );
Prs_CreateSignalOut( p, Cba_ObjFon0(p, iObj), pNtk, Vec_IntEntry(vBox, 1) ); // node output
//Cba_ObjSetFunc( p, iObj, FuncId );
}
else // box
{
Cba_Ntk_t * pBox = NULL; int nInputs, nOutputs = 1;
char ** pOutNames = NULL, * pNtkName = Prs_NtkStr(pNtk, Prs_BoxNtk(pNtk, i));
Cba_ObjType_t Type = Prs_ManFindType( pNtkName, &nInputs, 1, &pOutNames );
Type = Prs_ManFindType( pNtkName, &nInputs, 1, &pOutNames );
if ( Type == CBA_BOX_RAMWC )
continue;
if ( Type == CBA_OBJ_BOX )
......@@ -1969,7 +1919,7 @@ int Prs_CreateVerilogNtk( Cba_Ntk_t * p, Prs_Ntk_t * pNtk )
else assert( 0 );
}
else if ( (Type == CBA_BOX_DFFRS || Type == CBA_BOX_LATCHRS) && !strncmp(pNtkName, "wide_", strlen("wide_")) && !Prs_CreateFlopSetReset(p, pNtk, vBox, NULL, NULL, NULL, NULL) )
nInputs = atoi(pNtkName+strlen(Type == CBA_BOX_DFFRS ? "wide_dffrs_" : "wide_latchrs_")), nOutputs = 1, Type = CBA_BOX_CATIN;
nInputs = atoi(pNtkName+strlen(Type == CBA_BOX_DFFRS ? "wide_dffrs_" : "wide_latchrs_")), nOutputs = 1, Type = CBA_BOX_CONCAT;
// create object
iObj = Cba_ObjAlloc( p, Type, nInputs, nOutputs );
if ( pBox ) Cba_ObjSetFunc( p, iObj, Cba_NtkId(pBox) );
......@@ -2013,16 +1963,10 @@ int Prs_CreateVerilogNtk( Cba_Ntk_t * p, Prs_Ntk_t * pNtk )
// connect objects
Prs_NtkForEachBox( pNtk, vBox, i )
{
// char * pInstName = NULL;
// if ( Prs_BoxName(pNtk, i) )
// pInstName = Prs_NtkStr(pNtk, Prs_BoxName(pNtk, i));
// if ( pNtk->iModuleName == 291 && i == 0 )
// {
// int s = 0;
// }
iObj = Vec_IntEntry( vBox2Obj, i );
if ( Prs_BoxIsNode(pNtk, i) ) // node
{
Type = Prs_BoxNtk(pNtk, i);
Vec_IntForEachEntryDoubleStart( vBox, FormId, ActId, k, 2 )
{
iFon = Prs_CreateSignalIn( p, pNtk, ActId );
......@@ -2034,7 +1978,7 @@ int Prs_CreateVerilogNtk( Cba_Ntk_t * p, Prs_Ntk_t * pNtk )
{
int nInputs = -1;
char ** pInNames = NULL, * pNtkName = Prs_NtkStr(pNtk, Prs_BoxNtk(pNtk, i));
Cba_ObjType_t Type = Prs_ManFindType( pNtkName, &nInputs, 0, &pInNames );
Type = Prs_ManFindType( pNtkName, &nInputs, 0, &pInNames );
if ( (Type == CBA_BOX_DFFRS || Type == CBA_BOX_LATCHRS) && !strncmp(pNtkName, "wide_", strlen("wide_")) )
{
int IndexSet = -1, IndexRst = -1, iBitSet = -1, iBitRst = -1;
......@@ -2048,7 +1992,7 @@ int Prs_CreateVerilogNtk( Cba_Ntk_t * p, Prs_Ntk_t * pNtk )
else
{
int w, Width = atoi( pNtkName + strlen(Type == CBA_BOX_DFFRS ? "wide_dffrs_" : "wide_latchrs_") );
assert( Cba_ObjType(p, iObj) == CBA_BOX_CATIN );
assert( Cba_ObjType(p, iObj) == CBA_BOX_CONCAT );
// prepare inputs
assert( nInputs >= 0 );
Cba_NtkCleanMap2( p );
......@@ -2149,6 +2093,14 @@ int Prs_CreateVerilogNtk( Cba_Ntk_t * p, Prs_Ntk_t * pNtk )
}
}
}
// if carry-in is not supplied, use constant 0
if ( Type == CBA_BOX_ADD && Cba_ObjFinFon(p, iObj, 0) == 0 )
Cba_ObjSetFinFon( p, iObj, 0, Cba_FonFromConst(1) );
// if set or reset are not supplied, use constant 0
if ( Type == CBA_BOX_DFFRS && Cba_ObjFinFon(p, iObj, 1) == 0 )
Cba_ObjSetFinFon( p, iObj, 1, Cba_FonFromConst(1) );
if ( Type == CBA_BOX_DFFRS && Cba_ObjFinFon(p, iObj, 2) == 0 )
Cba_ObjSetFinFon( p, iObj, 2, Cba_FonFromConst(1) );
}
Vec_IntFree( vBox2Obj );
// connect outputs
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment