Commit 2d90b916 by Alan Mishchenko

Improvements to the CBA package.

parent f27979fc
......@@ -300,7 +300,7 @@ void Cba_ManMarkNodesGia( Cba_Man_t * p, Gia_Man_t * pGia )
void Cba_ManRemapBarbufs( Cba_Man_t * pNew, Cba_Man_t * p )
{
Cba_Ntk_t * pNtk; int Entry, i;
assert( Vec_IntSize(&p->vBuf2RootNtk) );
//assert( Vec_IntSize(&p->vBuf2RootNtk) );
assert( !Vec_IntSize(&pNew->vBuf2RootNtk) );
Vec_IntAppend( &pNew->vBuf2RootNtk, &p->vBuf2RootNtk );
Vec_IntAppend( &pNew->vBuf2RootObj, &p->vBuf2RootObj );
......
......@@ -102,6 +102,7 @@ void Cba_ManReadCbaVecInt( Vec_Str_t * vOut, int * pPos, Vec_Int_t * p, int nSiz
void Cba_ManReadCbaNtk( Vec_Str_t * vOut, int * pPos, Cba_Ntk_t * pNtk )
{
int i, Type;
//char * pName; int iObj, NameId;
Cba_ManReadCbaVecStr( vOut, pPos, &pNtk->vType, Cba_NtkObjNumAlloc(pNtk) );
Cba_ManReadCbaVecInt( vOut, pPos, &pNtk->vFanin, 4 * Cba_NtkObjNumAlloc(pNtk) );
Cba_ManReadCbaVecInt( vOut, pPos, &pNtk->vInfo, 12 * Cba_NtkInfoNumAlloc(pNtk) );
......@@ -116,20 +117,41 @@ void Cba_ManReadCbaNtk( Vec_Str_t * vOut, int * pPos, Cba_Ntk_t * pNtk )
assert( Cba_NtkPoNum(pNtk) == Cba_NtkPoNumAlloc(pNtk) );
assert( Cba_NtkObjNum(pNtk) == Cba_NtkObjNumAlloc(pNtk) );
assert( Cba_NtkInfoNum(pNtk) == Cba_NtkInfoNumAlloc(pNtk) );
/*
// read input/output/box names
Cba_NtkForEachPiMain( pNtk, iObj, i )
{
pName = Vec_StrEntryP( vOut, Pos );
NameId = Abc_NamStrFindOrAdd( p->pStrs, pName, NULL );
Pos += strlen(pName) + 1;
}
Cba_NtkForEachPoMain( pNtk, iObj, i )
{
pName = Vec_StrEntryP( vOut, Pos );
NameId = Abc_NamStrFindOrAdd( p->pStrs, pName, NULL );
Pos += strlen(pName) + 1;
}
Cba_NtkForEachBox( pNtk, iObj )
{
pName = Vec_StrEntryP( vOut, Pos );
NameId = Abc_NamStrFindOrAdd( p->pStrs, pName, NULL );
Pos += strlen(pName) + 1;
}
*/
}
Cba_Man_t * Cba_ManReadCbaInt( Vec_Str_t * vOut )
{
Cba_Man_t * p;
Cba_Ntk_t * pNtk;
char Buffer[1000] = "#";
int i, NameId, Pos = 0, nNtks, nPrims, Num1, Num2, Num3, Num4;
int i, NameId, Pos = 0, nNtks, Num1, Num2, Num3, Num4;
while ( Buffer[0] == '#' )
if ( !CbaManReadCbaLine(vOut, &Pos, Buffer, Buffer+1000) )
return NULL;
if ( !CbaManReadCbaNameAndNums(Buffer, &nNtks, &nPrims, &Num3, &Num4) )
if ( !CbaManReadCbaNameAndNums(Buffer, &nNtks, &Num2, &Num3, &Num4) )
return NULL;
// start manager
assert( nNtks > 0 && nPrims > 0 );
assert( nNtks > 0 );
p = Cba_ManAlloc( Buffer, nNtks );
// start networks
Cba_ManForEachNtk( p, pNtk, i )
......@@ -152,16 +174,8 @@ Cba_Man_t * Cba_ManReadCbaInt( Vec_Str_t * vOut )
// read networks
Cba_ManForEachNtk( p, pNtk, i )
Cba_ManReadCbaNtk( vOut, &Pos, pNtk );
// read primitives
for ( i = 0; i < nPrims; i++ )
{
char * pName = Vec_StrEntryP( vOut, Pos );
Abc_NamStrFindOrAdd( p->pMods, pName, NULL );
Pos += strlen(pName) + 1;
}
assert( Pos == Vec_StrSize(vOut) );
assert( Cba_ManNtkNum(p) == nNtks );
assert( Cba_ManPrimNum(p) == nPrims );
assert( Pos == Vec_StrSize(vOut) );
return p;
}
Cba_Man_t * Cba_ManReadCba( char * pFileName )
......@@ -187,7 +201,7 @@ Cba_Man_t * Cba_ManReadCba( char * pFileName )
nFileSize = fread( Vec_StrArray(vOut), 1, Vec_StrSize(vOut), pFile );
assert( nFileSize == Vec_StrSize(vOut) );
fclose( pFile );
// read the library
// read the networks
p = Cba_ManReadCbaInt( vOut );
if ( p != NULL )
{
......@@ -211,18 +225,40 @@ Cba_Man_t * Cba_ManReadCba( char * pFileName )
***********************************************************************/
void Cba_ManWriteCbaNtk( Vec_Str_t * vOut, Cba_Ntk_t * pNtk )
{
//char * pName; int iObj, NameId;
Vec_StrPushBuffer( vOut, (char *)Vec_StrArray(&pNtk->vType), Cba_NtkObjNum(pNtk) );
Vec_StrPushBuffer( vOut, (char *)Vec_IntArray(&pNtk->vFanin), 4 * Cba_NtkObjNum(pNtk) );
Vec_StrPushBuffer( vOut, (char *)Vec_IntArray(&pNtk->vInfo), 12 * Cba_NtkInfoNum(pNtk) );
/*
// write input/output/box names
Cba_NtkForEachPiMain( pNtk, iObj, i )
{
pName = Cba_ObjNameStr( pNtk, iObj );
Vec_StrPrintStr( vOut, pName );
Vec_StrPush( vOut, '\0' );
}
Cba_NtkForEachPoMain( pNtk, iObj, i )
{
pName = Cba_ObjNameStr( pNtk, iObj );
Vec_StrPrintStr( vOut, pName );
Vec_StrPush( vOut, '\0' );
}
Cba_NtkForEachBox( pNtk, iObj )
{
pName = Cba_ObjNameStr( pNtk, iObj );
Vec_StrPrintStr( vOut, pName );
Vec_StrPush( vOut, '\0' );
}
*/
}
void Cba_ManWriteCbaInt( Vec_Str_t * vOut, Cba_Man_t * p )
{
char Buffer[1000];
Cba_Ntk_t * pNtk; int i, nPrims = Cba_ManPrimNum(p);
Cba_Ntk_t * pNtk; int i;
sprintf( Buffer, "# Design \"%s\" written by ABC on %s\n", Cba_ManName(p), Extra_TimeStamp() );
Vec_StrPrintStr( vOut, Buffer );
// write short info
sprintf( Buffer, "%s %d %d \n", Cba_ManName(p), Cba_ManNtkNum(p), Cba_ManPrimNum(p) );
sprintf( Buffer, "%s %d \n", Cba_ManName(p), Cba_ManNtkNum(p) );
Vec_StrPrintStr( vOut, Buffer );
Cba_ManForEachNtk( p, pNtk, i )
{
......@@ -232,12 +268,6 @@ void Cba_ManWriteCbaInt( Vec_Str_t * vOut, Cba_Man_t * p )
}
Cba_ManForEachNtk( p, pNtk, i )
Cba_ManWriteCbaNtk( vOut, pNtk );
for ( i = 0; i < nPrims; i++ )
{
char * pName = Abc_NamStr( p->pMods, Cba_ManNtkNum(p) + i );
Vec_StrPrintStr( vOut, pName );
Vec_StrPush( vOut, '\0' );
}
}
void Cba_ManWriteCba( char * pFileName, Cba_Man_t * p )
{
......
......@@ -198,21 +198,24 @@ int Cba_CommandRead( Abc_Frame_t * pAbc, int argc, char ** argv )
vDes = Prs_ManReadBlif( pFileName );
if ( vDes && Vec_PtrSize(vDes) )
p = Prs_ManBuildCba( pFileName, vDes );
Prs_ManVecFree( vDes );
if ( vDes )
Prs_ManVecFree( vDes );
}
else if ( !strcmp( Extra_FileNameExtension(pFileName), "v" ) )
{
vDes = Prs_ManReadVerilog( pFileName );
if ( vDes && Vec_PtrSize(vDes) )
p = Prs_ManBuildCba( pFileName, vDes );
Prs_ManVecFree( vDes );
if ( vDes )
Prs_ManVecFree( vDes );
}
else if ( !strcmp( Extra_FileNameExtension(pFileName), "smt" ) )
{
vDes = NULL;//Prs_ManReadSmt( pFileName );
if ( vDes && Vec_PtrSize(vDes) )
p = Prs_ManBuildCba( pFileName, vDes );
Prs_ManVecFree( vDes );
if ( vDes )
Prs_ManVecFree( vDes );
}
else if ( !strcmp( Extra_FileNameExtension(pFileName), "cba" ) )
{
......@@ -249,12 +252,16 @@ int Cba_CommandWrite( Abc_Frame_t * pAbc, int argc, char ** argv )
{
Cba_Man_t * p = Cba_AbcGetMan(pAbc);
char * pFileName = NULL;
int fUseAssign = 1;
int c, fVerbose = 0;
Extra_UtilGetoptReset();
while ( ( c = Extra_UtilGetopt( argc, argv, "vh" ) ) != EOF )
while ( ( c = Extra_UtilGetopt( argc, argv, "avh" ) ) != EOF )
{
switch ( c )
{
case 'a':
fUseAssign ^= 1;
break;
case 'v':
fVerbose ^= 1;
break;
......@@ -282,7 +289,7 @@ int Cba_CommandWrite( Abc_Frame_t * pAbc, int argc, char ** argv )
if ( !strcmp( Extra_FileNameExtension(pFileName), "blif" ) )
Cba_ManWriteBlif( pFileName, p );
else if ( !strcmp( Extra_FileNameExtension(pFileName), "v" ) )
Cba_ManWriteVerilog( pFileName, p );
Cba_ManWriteVerilog( pFileName, p, fUseAssign );
else if ( !strcmp( Extra_FileNameExtension(pFileName), "cba" ) )
Cba_ManWriteCba( pFileName, p );
else
......@@ -292,8 +299,9 @@ int Cba_CommandWrite( Abc_Frame_t * pAbc, int argc, char ** argv )
}
return 0;
usage:
Abc_Print( -2, "usage: @write [-vh]\n" );
Abc_Print( -2, "usage: @write [-avh]\n" );
Abc_Print( -2, "\t writes the design into a file in BLIF or Verilog\n" );
Abc_Print( -2, "\t-a : toggle using assign-statement for primitives [default = %s]\n", fUseAssign? "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;
......
......@@ -213,7 +213,7 @@ int Cba_ObjClpArith( Cba_Ntk_t * p, int iBox )
{
Cba_ObjType_t Type = Cba_ObjType( p, iBox );
int i, iObj = -1;
int nBis = Cba_NtkReadRangesPrim( Cba_BoxNtkName(p, iObj), &p->vArray, 0 );
int nBis = 0;//Cba_NtkReadRangesPrim( Cba_BoxNtkName(p, iObj), &p->vArray, 0 );
assert( nBis == Cba_BoxBiNum(p, iBox) );
if ( Type == CBA_BOX_ADD )
{
......
......@@ -27,6 +27,99 @@ ABC_NAMESPACE_IMPL_START
/// DECLARATIONS ///
////////////////////////////////////////////////////////////////////////
typedef struct Cba_Trip_t_ Cba_Trip_t;
struct Cba_Trip_t_
{
Cba_ObjType_t Type;
char * pName;
char * pCode;
char * pSigs[6];
};
static Cba_Trip_t s_Types[100] =
{
{ CBA_BOX_CT , "VERIFIC_PWR", "1", {"o"} },
{ CBA_BOX_CF , "VERIFIC_GND", "1", {"o"} },
{ CBA_BOX_CX , "VERIFIC_X", "1", {"o"} },
{ CBA_BOX_CZ , "VERIFIC_Z", "1", {"o"} },
{ CBA_BOX_INV , "VERIFIC_INV", "11", {"i","o"} },
{ CBA_BOX_BUF , "VERIFIC_BUF", "11", {"i","o"} },
{ CBA_BOX_AND , "VERIFIC_AND", "111", {"a0","a1","o"} },
{ CBA_BOX_NAND , "VERIFIC_NAND", "111", {"a0","a1","o"} },
{ CBA_BOX_OR , "VERIFIC_OR", "111", {"a0","a1","o"} },
{ CBA_BOX_NOR , "VERIFIC_NOR", "111", {"a0","a1","o"} },
{ CBA_BOX_XOR , "VERIFIC_XOR", "111", {"a0","a1","o"} },
{ CBA_BOX_XNOR , "VERIFIC_XNOR", "111", {"a0","a1","o"} },
{ CBA_BOX_MUX , "VERIFIC_MUX", "1111", {"c","a1","a0","o"} }, // changed order
{ (Cba_ObjType_t)-1, "VERIFIC_PULLUP", "1", {"o"} },
{ (Cba_ObjType_t)-1, "VERIFIC_PULLDOWN", "1", {"o"} },
{ CBA_BOX_TRI , "VERIFIC_TRI", "111", {"i","c","o"} },
{ CBA_BOX_LATCH , "VERIFIC_DLATCH", "11111", {"d","async_val","async_cond","gate","q"} }, // changed order
{ CBA_BOX_LATCHRS , "VERIFIC_DLATCHRS", "11111", {"d","s","r","gate","q"} }, // changed order
{ CBA_BOX_DFF , "VERIFIC_DFF", "11111", {"d","async_val","async_cond","clk","q"} }, // changed order
{ CBA_BOX_DFFRS , "VERIFIC_DFFRS", "11111", {"d","s","r","clk","q"} }, // changed order
{ (Cba_ObjType_t)-1, "VERIFIC_NMOS", "111", {"c","d","o"} },
{ (Cba_ObjType_t)-1, "VERIFIC_PMOS", "111", {"c","d","o"} },
{ (Cba_ObjType_t)-1, "VERIFIC_CMOS", "1111", {"d","nc","pc","o"} },
{ (Cba_ObjType_t)-1, "VERIFIC_TRAN", "111", {"inout1","inout2","control"} },
{ CBA_BOX_ADD , "VERIFIC_FADD", "11111", {"cin","a","b","o","cout"} },
{ (Cba_ObjType_t)-1, "VERIFIC_RCMOS", "1111", {"d","nc","pc","o"} },
{ (Cba_ObjType_t)-1, "VERIFIC_RNMOS", "111", {"c","d","o"} },
{ (Cba_ObjType_t)-1, "VERIFIC_RPMOS", "111", {"c","d","o"} },
{ (Cba_ObjType_t)-1, "VERIFIC_RTRAN", "111", {"inout1","inout2","control"} },
{ (Cba_ObjType_t)-1, "VERIFIC_HDL_ASSERTION", "1", {"condition"} },
{ CBA_BOX_ADD , "add_", "1aba1", {"cin","a","b","o","cout"} },
{ CBA_BOX_MUL , "mult_", "ab?", {"a","b","o"} }, // ? = a * b
{ CBA_BOX_DIV , "div_", "ab?", {"a","b","o"} }, // ? =
{ CBA_BOX_MOD , "mod_", "ab?", {"a","b","o"} }, // ? =
{ CBA_BOX_REM , "rem_", "ab?", {"a","b","o"} }, // ? =
{ CBA_BOX_SHIL , "shift_left_", "1aba", {"cin","a","amount","o"} },
{ CBA_BOX_SHIR , "shift_right_", "1aba", {"cin","a","amount","o"} },
{ CBA_BOX_ROTL , "rotate_left_", "aba", {"a","amount","o"} },
{ CBA_BOX_ROTR , "rotate_right_", "aba", {"a","amount","o"} },
{ CBA_BOX_RAND , "reduce_and_", "ab1", {"a","o"} },
{ CBA_BOX_ROR , "reduce_or_", "ab1", {"a","o"} },
{ CBA_BOX_RXOR , "reduce_xor_", "ab1", {"a","o"} },
{ CBA_BOX_RNAND , "reduce_nand_", "ab1", {"a","o"} },
{ CBA_BOX_RNOR , "reduce_nor_", "ab1", {"a","o"} },
{ CBA_BOX_RXNOR , "reduce_xnor_", "ab1", {"a","o"} },
{ CBA_BOX_LTHAN , "LessThan_", "1ab1", {"cin","a","b","o"} },
{ CBA_BOX_NMUX , "Mux_", "ab1", {"sel","data","o"} },
{ CBA_BOX_SEL , "Select_", "aaa", {"sel","data","o"} },
{ CBA_BOX_DEC , "Decoder_", "a?", {"a","o"} }, // ? = (1 << a)
{ CBA_BOX_EDEC , "EnabledDecoder_", "1a?", {"en","i","o"} }, // ? = (1 << a)
{ CBA_BOX_PSEL , "PrioSelect_", "1aaa", {"cin","sel","data","o"} },
{ CBA_BOX_RAM , "DualPortRam_", "1abab", {"write_enable","write_address","write_data","read_address","read_data"} },
{ CBA_BOX_RAMR , "ReadPort_", "1a1b", {"read_enable", "read_address", "RAM", "read_data" } },
{ CBA_BOX_RAMW , "WritePort_", "1ab1", {"write_enable","write_address","write_data", "RAM"} },
{ CBA_BOX_RAMWC , "ClockedWritePort_", "11ab1", {"clk","write_enable","write_address","write_data", "RAM"} },
{ CBA_BOX_LUT , "lut", "?", {"i","o"} },
{ CBA_BOX_AND , "and_", "aaa", {"a","b","o"} },
{ CBA_BOX_OR , "or_", "aaa", {"a","b","o"} },
{ CBA_BOX_XOR , "xor_", "aaa", {"a","b","o"} },
{ CBA_BOX_NAND , "nand_", "aaa", {"a","b","o"} },
{ CBA_BOX_NOR , "nor_", "aaa", {"a","b","o"} },
{ CBA_BOX_XNOR , "xnor_", "aaa", {"a","b","o"} },
{ CBA_BOX_BUF , "buf_", "aa", {"i","o"} },
{ CBA_BOX_INV , "inv_", "aa", {"i","o"} },
{ CBA_BOX_TRI , "tri_", "a1a", {"i","c","o"} },
{ CBA_BOX_SUB , "sub_", "aaa", {"a","b","o"} },
{ CBA_BOX_MIN , "unary_minus_", "aa", {"i","o"} },
{ CBA_BOX_EQU , "equal_", "aa1", {"a","b","o"} },
{ CBA_BOX_NEQU , "not_equal_", "aa1", {"a","b","o"} },
{ CBA_BOX_MUX , "mux_", "1aaa", {"cond","d1","d0","o"} }, // changed order
{ CBA_BOX_NMUX , "wide_mux_", "ab?", {"sel","data","o"} }, // ? = b / (1 << a)
{ CBA_BOX_SEL , "wide_select_", "ab?", {"sel","data","o"} }, // ? = b / a
{ CBA_BOX_DFF , "wide_dff_", "aaa1a", {"d","async_val","async_cond","clock","q"} },
{ CBA_BOX_DFFRS , "wide_dlatch_", "aaa1a", {"d","set","reset","clock","q"} },
{ CBA_BOX_LATCHRS , "wide_dffrs_", "aaa1a", {"d","set","reset","clock","q"} },
{ CBA_BOX_LATCH , "wide_dlatchrs_", "aaa1a", {"d","async_val","async_cond","clock","q"} },
{ CBA_BOX_PSEL , "wide_prio_select_", "ab??", {"sel","data","carry_in","o"} }, // ? = b / a
{ CBA_BOX_POW , "pow_", "abc", {"a","b","o"} }, // ? =
{ CBA_BOX_PENC , "PrioEncoder_", "a?", {"sel","o"} },
{ CBA_BOX_ABS , "abs", "aa", {"i","o"} }
};
////////////////////////////////////////////////////////////////////////
/// FUNCTION DEFINITIONS ///
////////////////////////////////////////////////////////////////////////
......
......@@ -462,7 +462,7 @@ Vec_Ptr_t * Cba_PtrDeriveFromCba( Cba_Man_t * p )
Cba_Ntk_t * pTemp; int i;
if ( p == NULL )
return NULL;
Cba_ManAssignInternNames( p );
Cba_ManAssignInternWordNames( p );
vDes = Vec_PtrAllocExact( 1 + Cba_ManNtkNum(p) );
Vec_PtrPush( vDes, p->pName );
Cba_ManForEachNtk( p, pTemp, i )
......
......@@ -132,7 +132,12 @@ void Cba_ManWriteBlifGate( FILE * pFile, Cba_Ntk_t * p, Mio_Gate_t * pGate, Vec_
{
int iFanin, i;
Vec_IntForEachEntry( vFanins, iFanin, i )
{
if ( Cba_ObjIsCo(p, iFanin) )
iFanin = Cba_ObjFanin(p, iFanin);
assert( Cba_ObjIsCi(p, iFanin) );
fprintf( pFile, " %s=%s", Mio_GateReadPinName(pGate, i), Cba_ObjNameStr(p, iFanin) );
}
fprintf( pFile, " %s=%s", Mio_GateReadOutName(pGate), Cba_ObjNameStr(p, iObj) );
fprintf( pFile, "\n" );
}
......@@ -140,7 +145,12 @@ void Cba_ManWriteBlifArray( FILE * pFile, Cba_Ntk_t * p, Vec_Int_t * vFanins, in
{
int iFanin, i;
Vec_IntForEachEntry( vFanins, iFanin, i )
{
if ( Cba_ObjIsCo(p, iFanin) )
iFanin = Cba_ObjFanin(p, iFanin);
assert( Cba_ObjIsCi(p, iFanin) );
fprintf( pFile, " %s", Cba_ObjNameStr(p, iFanin) );
}
if ( iObj >= 0 )
fprintf( pFile, " %s", Cba_ObjNameStr(p, iObj) );
fprintf( pFile, "\n" );
......@@ -182,7 +192,7 @@ void Cba_ManWriteBlifLines( FILE * pFile, Cba_Ntk_t * p )
{
fprintf( pFile, ".names" );
Cba_BoxForEachBi( p, i, iTerm, k )
fprintf( pFile, " %s", Cba_ObjNameStr(p, iTerm) );
fprintf( pFile, " %s", Cba_ObjNameStr(p, Cba_ObjFanin(p, iTerm)) );
Cba_BoxForEachBo( p, i, iTerm, k )
fprintf( pFile, " %s", Cba_ObjNameStr(p, iTerm) );
fprintf( pFile, "\n%s", Ptr_TypeToSop(Cba_ObjType(p, i)) );
......@@ -220,7 +230,7 @@ void Cba_ManWriteBlif( char * pFileName, Cba_Man_t * p )
return;
}
fprintf( pFile, "# Design \"%s\" written by ABC on %s\n\n", Cba_ManName(p), Extra_TimeStamp() );
Cba_ManAssignInternNames( p );
Cba_ManAssignInternWordNames( p );
Cba_ManForEachNtk( p, pNtk, i )
Cba_ManWriteBlifNtk( pFile, pNtk );
fclose( pFile );
......
......@@ -746,6 +746,39 @@ static inline int Vec_StrCountSmaller( Vec_Str_t * p, char Entry )
/**Function*************************************************************
Synopsis []
Description []
SideEffects []
SeeAlso []
***********************************************************************/
static inline int Vec_StrCountEntryLit( Vec_Str_t * p, char Entry )
{
int i, Counter = 0;
for ( i = 0; i < p->nSize; i++ )
Counter += (Abc_Lit2Var((int)p->pArray[i]) == Entry);
return Counter;
}
static inline int Vec_StrCountLargerLit( Vec_Str_t * p, char Entry )
{
int i, Counter = 0;
for ( i = 0; i < p->nSize; i++ )
Counter += (Abc_Lit2Var((int)p->pArray[i]) > Entry);
return Counter;
}
static inline int Vec_StrCountSmallerLit( Vec_Str_t * p, char Entry )
{
int i, Counter = 0;
for ( i = 0; i < p->nSize; i++ )
Counter += (Abc_Lit2Var((int)p->pArray[i]) < Entry);
return Counter;
}
/**Function*************************************************************
Synopsis [Compares two strings.]
Description []
......
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