Commit 2167d6c1 by Alan Mishchenko

Version abc70121

parent 76bcf6b2
......@@ -1166,6 +1166,10 @@ SOURCE=.\src\sat\asat\jfront.c
# End Source File
# Begin Source File
SOURCE=.\src\sat\asat\satTrace.c
# End Source File
# Begin Source File
SOURCE=.\src\sat\asat\solver.c
# End Source File
# Begin Source File
......@@ -1345,6 +1349,18 @@ SOURCE=.\src\sat\bsat\satUtil.c
SOURCE=.\src\sat\bsat\satVec.h
# End Source File
# End Group
# Begin Group "proof"
# PROP Default_Filter ""
# Begin Source File
SOURCE=.\src\sat\proof\pr.c
# End Source File
# Begin Source File
SOURCE=.\src\sat\proof\pr.h
# End Source File
# End Group
# End Group
# Begin Group "opt"
......
# global parameters
set check # checks intermediate networks
#set checkfio # prints warnings when fanins/fanouts are duplicated
#set checkread # checks new networks after reading from file
set checkread # checks new networks after reading from file
#set backup # saves backup networks retrived by "undo" and "recall"
set savesteps 1 # sets the maximum number of backup networks to save
set progressbar # display the progress bar
......
......@@ -930,6 +930,8 @@ Ivy_Store_t * Ivy_NodeFindCutsAll( Ivy_Man_t * p, Ivy_Obj_t * pObj, int nLeaves
*/
iLeaf0 = Ivy_ObjId( Ivy_ObjRealFanin(Ivy_ObjFanin0(pLeaf)) );
iLeaf1 = Ivy_ObjId( Ivy_ObjRealFanin(Ivy_ObjFanin1(pLeaf)) );
// if ( iLeaf0 == iLeaf1 ) // strange situation observed on Jan 18, 2007
// continue;
if ( !Ivy_NodeCutPrescreen( pCut, iLeaf0, iLeaf1 ) )
continue;
if ( iLeaf0 > iLeaf1 )
......
......@@ -252,7 +252,7 @@ int Ivy_FraigProve( Ivy_Man_t ** ppManAig, void * pPars )
Prove_Params_t * pParams = pPars;
Ivy_FraigParams_t Params, * pIvyParams = &Params;
Ivy_Man_t * pManAig, * pManTemp;
int RetValue, nIter, Counter, clk, timeStart = clock();
int RetValue, nIter, clk, timeStart = clock();//, Counter;
sint64 nSatConfs, nSatInspects;
// start the network and parameters
......@@ -314,12 +314,14 @@ int Ivy_FraigProve( Ivy_Man_t ** ppManAig, void * pPars )
// try rewriting
if ( pParams->fUseRewriting )
{
{ // bug in Ivy_NodeFindCutsAll() when leaves are identical!
/*
clk = clock();
Counter = (int)(pParams->nRewritingLimitStart * pow(pParams->nRewritingLimitMulti,nIter));
pManAig = Ivy_ManRwsat( pManAig, 0 );
RetValue = Ivy_FraigMiterStatus( pManAig );
Ivy_FraigMiterPrint( pManAig, "Rewriting ", clk, pParams->fVerbose );
*/
}
if ( RetValue >= 0 )
break;
......@@ -368,6 +370,15 @@ int Ivy_FraigProve( Ivy_Man_t ** ppManAig, void * pPars )
s_nInsLimitGlobal = 0;
RetValue = Ivy_FraigMiterStatus( pManAig );
Ivy_FraigMiterPrint( pManAig, "SAT solving", clk, pParams->fVerbose );
// make sure that the sover never returns "undecided" when infinite resource limits are set
if( RetValue == -1 && pParams->nTotalInspectLimit == 0 &&
pParams->nTotalBacktrackLimit == 0 )
{
extern void Prove_ParamsPrint( Prove_Params_t * pParams );
Prove_ParamsPrint( pParams );
printf("ERROR: ABC has returned \"undecided\" in spite of no limits...\n");
exit(1);
}
}
// assign the model if it was proved by rewriting (const 1 miter)
......
......@@ -570,9 +570,9 @@ extern void Abc_NtkFraigStoreClean();
/*=== abcFunc.c ==========================================================*/
extern int Abc_NtkSopToBdd( Abc_Ntk_t * pNtk );
extern DdNode * Abc_ConvertSopToBdd( DdManager * dd, char * pSop );
extern char * Abc_ConvertBddToSop( Extra_MmFlex_t * pMan, DdManager * dd, DdNode * bFuncOn, DdNode * bFuncOnDc, int nFanins, Vec_Str_t * vCube, int fMode );
extern char * Abc_ConvertBddToSop( Extra_MmFlex_t * pMan, DdManager * dd, DdNode * bFuncOn, DdNode * bFuncOnDc, int nFanins, int fAllPrimes, Vec_Str_t * vCube, int fMode );
extern int Abc_NtkBddToSop( Abc_Ntk_t * pNtk, int fDirect );
extern void Abc_NodeBddToCnf( Abc_Obj_t * pNode, Extra_MmFlex_t * pMmMan, Vec_Str_t * vCube, char ** ppSop0, char ** ppSop1 );
extern void Abc_NodeBddToCnf( Abc_Obj_t * pNode, Extra_MmFlex_t * pMmMan, Vec_Str_t * vCube, int fAllPrimes, char ** ppSop0, char ** ppSop1 );
extern int Abc_CountZddCubes( DdManager * dd, DdNode * zCover );
extern void Abc_NtkLogicMakeDirectSops( Abc_Ntk_t * pNtk );
extern int Abc_NtkSopToAig( Abc_Ntk_t * pNtk );
......@@ -726,7 +726,7 @@ extern int Abc_NtkRefactor( Abc_Ntk_t * pNtk, int nNodeSizeMax, i
extern int Abc_NtkRewrite( Abc_Ntk_t * pNtk, int fUpdateLevel, int fUseZeros, int fVerbose, int fVeryVerbose );
/*=== abcSat.c ==========================================================*/
extern int Abc_NtkMiterSat( Abc_Ntk_t * pNtk, sint64 nConfLimit, sint64 nInsLimit, int fJFront, int fVerbose, sint64 * pNumConfs, sint64 * pNumInspects );
extern solver * Abc_NtkMiterSatCreate( Abc_Ntk_t * pNtk, int fJFront );
extern solver * Abc_NtkMiterSatCreate( Abc_Ntk_t * pNtk, int fJFront, int fAllPrimes );
/*=== abcSop.c ==========================================================*/
extern char * Abc_SopRegister( Extra_MmFlex_t * pMan, char * pName );
extern char * Abc_SopStart( Extra_MmFlex_t * pMan, int nCubes, int nVars );
......
......@@ -184,7 +184,7 @@ void Abc_NtkLogicMakeDirectSops( Abc_Ntk_t * pNtk )
if ( Abc_SopIsComplement(pNode->pData) )
{
bFunc = Abc_ConvertSopToBdd( dd, pNode->pData ); Cudd_Ref( bFunc );
pNode->pData = Abc_ConvertBddToSop( pNtk->pManFunc, dd, bFunc, bFunc, Abc_ObjFaninNum(pNode), vCube, 1 );
pNode->pData = Abc_ConvertBddToSop( pNtk->pManFunc, dd, bFunc, bFunc, Abc_ObjFaninNum(pNode), 0, vCube, 1 );
Cudd_RecursiveDeref( dd, bFunc );
assert( !Abc_SopIsComplement(pNode->pData) );
}
......@@ -233,7 +233,7 @@ int Abc_NtkBddToSop( Abc_Ntk_t * pNtk, int fDirect )
{
assert( pNode->pData );
bFunc = pNode->pData;
pNode->pNext = (Abc_Obj_t *)Abc_ConvertBddToSop( pManNew, dd, bFunc, bFunc, Abc_ObjFaninNum(pNode), vCube, fMode );
pNode->pNext = (Abc_Obj_t *)Abc_ConvertBddToSop( pManNew, dd, bFunc, bFunc, Abc_ObjFaninNum(pNode), 0, vCube, fMode );
if ( pNode->pNext == NULL )
{
Extra_MmFlexStop( pManNew );
......@@ -273,7 +273,7 @@ int Abc_NtkBddToSop( Abc_Ntk_t * pNtk, int fDirect )
SeeAlso []
***********************************************************************/
char * Abc_ConvertBddToSop( Extra_MmFlex_t * pMan, DdManager * dd, DdNode * bFuncOn, DdNode * bFuncOnDc, int nFanins, Vec_Str_t * vCube, int fMode )
char * Abc_ConvertBddToSop( Extra_MmFlex_t * pMan, DdManager * dd, DdNode * bFuncOn, DdNode * bFuncOnDc, int nFanins, int fAllPrimes, Vec_Str_t * vCube, int fMode )
{
int fVerify = 0;
char * pSop;
......@@ -301,6 +301,7 @@ char * Abc_ConvertBddToSop( Extra_MmFlex_t * pMan, DdManager * dd, DdNode * bFun
if ( fMode == -1 )
{ // try both phases
assert( fAllPrimes == 0 );
// get the ZDD of the negative polarity
bCover = Cudd_zddIsop( dd, Cudd_Not(bFuncOnDc), Cudd_Not(bFuncOn), &zCover0 );
......@@ -335,20 +336,36 @@ char * Abc_ConvertBddToSop( Extra_MmFlex_t * pMan, DdManager * dd, DdNode * bFun
else if ( fMode == 0 )
{
// get the ZDD of the negative polarity
if ( fAllPrimes )
{
zCover = Extra_zddPrimes( dd, Cudd_Not(bFuncOnDc) );
Cudd_Ref( zCover );
}
else
{
bCover = Cudd_zddIsop( dd, Cudd_Not(bFuncOnDc), Cudd_Not(bFuncOn), &zCover );
Cudd_Ref( zCover );
Cudd_Ref( bCover );
Cudd_RecursiveDeref( dd, bCover );
}
nCubes = Abc_CountZddCubes( dd, zCover );
fPhase = 0;
}
else if ( fMode == 1 )
{
// get the ZDD of the positive polarity
if ( fAllPrimes )
{
zCover = Extra_zddPrimes( dd, bFuncOnDc );
Cudd_Ref( zCover );
}
else
{
bCover = Cudd_zddIsop( dd, bFuncOn, bFuncOnDc, &zCover );
Cudd_Ref( zCover );
Cudd_Ref( bCover );
Cudd_RecursiveDeref( dd, bCover );
}
nCubes = Abc_CountZddCubes( dd, zCover );
fPhase = 1;
}
......@@ -462,11 +479,11 @@ int Abc_ConvertZddToSop( DdManager * dd, DdNode * zCover, char * pSop, int nFani
SeeAlso []
***********************************************************************/
void Abc_NodeBddToCnf( Abc_Obj_t * pNode, Extra_MmFlex_t * pMmMan, Vec_Str_t * vCube, char ** ppSop0, char ** ppSop1 )
void Abc_NodeBddToCnf( Abc_Obj_t * pNode, Extra_MmFlex_t * pMmMan, Vec_Str_t * vCube, int fAllPrimes, char ** ppSop0, char ** ppSop1 )
{
assert( Abc_NtkIsBddLogic(pNode->pNtk) );
*ppSop0 = Abc_ConvertBddToSop( pMmMan, pNode->pNtk->pManFunc, pNode->pData, pNode->pData, Abc_ObjFaninNum(pNode), vCube, 0 );
*ppSop1 = Abc_ConvertBddToSop( pMmMan, pNode->pNtk->pManFunc, pNode->pData, pNode->pData, Abc_ObjFaninNum(pNode), vCube, 1 );
*ppSop0 = Abc_ConvertBddToSop( pMmMan, pNode->pNtk->pManFunc, pNode->pData, pNode->pData, Abc_ObjFaninNum(pNode), fAllPrimes, vCube, 0 );
*ppSop1 = Abc_ConvertBddToSop( pMmMan, pNode->pNtk->pManFunc, pNode->pData, pNode->pData, Abc_ObjFaninNum(pNode), fAllPrimes, vCube, 1 );
}
......
......@@ -32,6 +32,8 @@ static Abc_Obj_t * Abc_NodeFromIf_rec( Abc_Ntk_t * pNtkNew, If_Man_t * pIfMan, I
static Hop_Obj_t * Abc_NodeIfToHop( Hop_Man_t * pHopMan, If_Man_t * pIfMan, If_Obj_t * pIfObj );
static Vec_Ptr_t * Abc_NtkFindGoodOrder( Abc_Ntk_t * pNtk );
extern void Abc_NtkBddReorder( Abc_Ntk_t * pNtk, int fVerbose );
////////////////////////////////////////////////////////////////////////
/// FUNCTION DEFINITIONS ///
////////////////////////////////////////////////////////////////////////
......@@ -122,6 +124,11 @@ If_Man_t * Abc_NtkToIf( Abc_Ntk_t * pNtk, If_Par_t * pPars )
// start the mapping manager and set its parameters
pIfMan = If_ManStart( pPars );
// print warning about excessive memory usage
if ( 1.0 * Abc_NtkObjNum(pNtk) * pIfMan->nEntrySize / (1<<30) > 0.5 )
printf( "Warning: The mapper is about to allocate %.1f Gb for to represent %d cuts per node.\n",
1.0 * Abc_NtkObjNum(pNtk) * pIfMan->nEntrySize / (1<<30), pPars->nCutsMax );
// create PIs and remember them in the old nodes
Abc_AigConst1(pNtk)->pCopy = (Abc_Obj_t *)If_ManConst1( pIfMan );
Abc_NtkForEachCi( pNtk, pNode, i )
......@@ -177,7 +184,7 @@ Abc_Ntk_t * Abc_NtkFromIf( If_Man_t * pIfMan, Abc_Ntk_t * pNtk )
Vec_Int_t * vCover;
int i, nDupGates;
// create the new network
if ( pIfMan->pPars->fUseBdds )
if ( pIfMan->pPars->fUseBdds || pIfMan->pPars->fUseCnfs )
pNtkNew = Abc_NtkStartFrom( pNtk, ABC_NTK_LOGIC, ABC_FUNC_BDD );
else if ( pIfMan->pPars->fUseSops )
pNtkNew = Abc_NtkStartFrom( pNtk, ABC_NTK_LOGIC, ABC_FUNC_SOP );
......@@ -206,6 +213,11 @@ Abc_Ntk_t * Abc_NtkFromIf( If_Man_t * pIfMan, Abc_Ntk_t * pNtk )
pNodeNew = (Abc_Obj_t *)If_ObjCopy( If_ManConst1(pIfMan) );
if ( Abc_ObjFanoutNum(pNodeNew) == 0 )
Abc_NtkDeleteObj( pNodeNew );
// minimize the node
if ( pIfMan->pPars->fUseCnfs || pIfMan->pPars->fUseBdds )
Abc_NtkSweep( pNtkNew, 0 );
if ( pIfMan->pPars->fUseBdds )
Abc_NtkBddReorder( pNtkNew, 0 );
// decouple the PO driver nodes to reduce the number of levels
nDupGates = Abc_NtkLogicMakeSimpleCos( pNtkNew, 1 );
// if ( nDupGates && If_ManReadVerbose(pIfMan) )
......@@ -239,28 +251,28 @@ Abc_Obj_t * Abc_NodeFromIf_rec( Abc_Ntk_t * pNtkNew, If_Man_t * pIfMan, If_Obj_t
// create a new node
pNodeNew = Abc_NtkCreateNode( pNtkNew );
pCutBest = If_ObjCutBest( pIfObj );
if ( pIfMan->pPars->fUseCnfs )
{
If_CutForEachLeafReverse( pIfMan, pCutBest, pIfLeaf, i )
Abc_ObjAddFanin( pNodeNew, Abc_NodeFromIf_rec(pNtkNew, pIfMan, pIfLeaf, vCover) );
}
else
{
If_CutForEachLeaf( pIfMan, pCutBest, pIfLeaf, i )
Abc_ObjAddFanin( pNodeNew, Abc_NodeFromIf_rec(pNtkNew, pIfMan, pIfLeaf, vCover) );
}
// derive the function of this node
if ( pIfMan->pPars->fTruth )
{
if ( pIfMan->pPars->fUseBdds )
{
extern void Abc_NodeBddReorder( void * p, Abc_Obj_t * pNode );
// transform truth table into the BDD
pNodeNew->pData = Kit_TruthToBdd( pNtkNew->pManFunc, If_CutTruth(pCutBest), If_CutLeaveNum(pCutBest), 0 ); Cudd_Ref(pNodeNew->pData);
if ( pNodeNew->pData == Cudd_ReadLogicZero(pNtkNew->pManFunc) || pNodeNew->pData == Cudd_ReadOne(pNtkNew->pManFunc) )
{
// replace the node by a constant node
pNodeNew = pNodeNew->pData == Cudd_ReadOne(pNtkNew->pManFunc) ? Abc_NtkCreateNodeConst1(pNtkNew) : Abc_NtkCreateNodeConst0(pNtkNew);
}
else
else if ( pIfMan->pPars->fUseCnfs )
{
// make sure the node is minimum base
Abc_NodeMinimumBase( pNodeNew );
// reorder the fanins to minimize the BDD size
Abc_NodeBddReorder( pIfMan->pPars->pReoMan, pNodeNew );
}
// transform truth table into the BDD
pNodeNew->pData = Kit_TruthToBdd( pNtkNew->pManFunc, If_CutTruth(pCutBest), If_CutLeaveNum(pCutBest), 1 ); Cudd_Ref(pNodeNew->pData);
}
else if ( pIfMan->pPars->fUseSops )
{
......
......@@ -1039,7 +1039,7 @@ int Abc_NtkDemiter( Abc_Ntk_t * pNtk )
/**Function*************************************************************
Synopsis [ORs the outputs.]
Synopsis [Computes OR or AND of the POs.]
Description []
......@@ -1048,15 +1048,22 @@ int Abc_NtkDemiter( Abc_Ntk_t * pNtk )
SeeAlso []
***********************************************************************/
int Abc_NtkOrPos( Abc_Ntk_t * pNtk )
int Abc_NtkCombinePos( Abc_Ntk_t * pNtk, int fAnd )
{
Abc_Obj_t * pNode, * pMiter;
int i;
assert( Abc_NtkIsStrash(pNtk) );
assert( Abc_NtkLatchNum(pNtk) == 0 );
// OR the POs
// start the result
if ( fAnd )
pMiter = Abc_AigConst1(pNtk);
else
pMiter = Abc_ObjNot( Abc_AigConst1(pNtk) );
// perform operations on the POs
Abc_NtkForEachPo( pNtk, pNode, i )
if ( fAnd )
pMiter = Abc_AigAnd( pNtk->pManFunc, pMiter, Abc_ObjChild0(pNode) );
else
pMiter = Abc_AigOr( pNtk->pManFunc, pMiter, Abc_ObjChild0(pNode) );
// remove the POs and their names
for ( i = Abc_NtkPoNum(pNtk) - 1; i >= 0; i-- )
......
......@@ -233,7 +233,7 @@ p->timeDcs += clock() - clk;
// get the SOP of the cut
clk = clock();
pSop = Abc_ConvertBddToSop( NULL, p->dd, bNodeFunc, bNodeFunc, vFanins->nSize, p->vCube, -1 );
pSop = Abc_ConvertBddToSop( NULL, p->dd, bNodeFunc, bNodeFunc, vFanins->nSize, 0, p->vCube, -1 );
p->timeSop += clock() - clk;
// get the factored form
......
......@@ -29,6 +29,7 @@
static int Abc_NtkRenodeEvalBdd( If_Cut_t * pCut );
static int Abc_NtkRenodeEvalSop( If_Cut_t * pCut );
static int Abc_NtkRenodeEvalCnf( If_Cut_t * pCut );
static int Abc_NtkRenodeEvalAig( If_Cut_t * pCut );
static reo_man * s_pReo = NULL;
......@@ -50,7 +51,7 @@ static Vec_Int_t * s_vMemory = NULL;
SeeAlso []
***********************************************************************/
Abc_Ntk_t * Abc_NtkRenode( Abc_Ntk_t * pNtk, int nFaninMax, int nCubeMax, int fArea, int fUseBdds, int fUseSops, int fVerbose )
Abc_Ntk_t * Abc_NtkRenode( Abc_Ntk_t * pNtk, int nFaninMax, int nCubeMax, int nFlowIters, int nAreaIters, int fArea, int fUseBdds, int fUseSops, int fUseCnfs, int fVerbose )
{
extern Abc_Ntk_t * Abc_NtkIf( Abc_Ntk_t * pNtk, If_Par_t * pPars );
If_Par_t Pars, * pPars = &Pars;
......@@ -64,6 +65,8 @@ Abc_Ntk_t * Abc_NtkRenode( Abc_Ntk_t * pNtk, int nFaninMax, int nCubeMax, int fA
// user-controlable paramters
pPars->nLutSize = nFaninMax;
pPars->nCutsMax = nCubeMax;
pPars->nFlowIters = nFlowIters;
pPars->nAreaIters = nAreaIters;
pPars->DelayTarget = -1;
pPars->fPreprocess = 1;
pPars->fArea = fArea;
......@@ -81,10 +84,16 @@ Abc_Ntk_t * Abc_NtkRenode( Abc_Ntk_t * pNtk, int nFaninMax, int nCubeMax, int fA
pPars->pTimesArr = NULL;
pPars->fUseBdds = fUseBdds;
pPars->fUseSops = fUseSops;
pPars->fUseCnfs = fUseCnfs;
if ( fUseBdds )
pPars->pFuncCost = Abc_NtkRenodeEvalBdd;
else if ( fUseSops )
pPars->pFuncCost = Abc_NtkRenodeEvalSop;
else if ( fUseCnfs )
{
pPars->fArea = 1;
pPars->pFuncCost = Abc_NtkRenodeEvalCnf;
}
else
pPars->pFuncCost = Abc_NtkRenodeEvalAig;
......@@ -176,6 +185,39 @@ int Abc_NtkRenodeEvalSop( If_Cut_t * pCut )
/**Function*************************************************************
Synopsis [Computes the cost based on two ISOPs.]
Description []
SideEffects []
SeeAlso []
***********************************************************************/
int Abc_NtkRenodeEvalCnf( If_Cut_t * pCut )
{
int i, RetValue, nClauses;
for ( i = 0; i < If_CutLeaveNum(pCut); i++ )
pCut->pPerm[i] = 1;
// compute ISOP for the positive phase
RetValue = Kit_TruthIsop( If_CutTruth(pCut), If_CutLeaveNum(pCut), s_vMemory, 0 );
if ( RetValue == -1 )
return ABC_INFINITY;
assert( RetValue == 0 || RetValue == 1 );
nClauses = Vec_IntSize( s_vMemory );
// compute ISOP for the negative phase
Kit_TruthNot( If_CutTruth(pCut), If_CutTruth(pCut), If_CutLeaveNum(pCut) );
RetValue = Kit_TruthIsop( If_CutTruth(pCut), If_CutLeaveNum(pCut), s_vMemory, 0 );
Kit_TruthNot( If_CutTruth(pCut), If_CutTruth(pCut), If_CutLeaveNum(pCut) );
if ( RetValue == -1 )
return ABC_INFINITY;
assert( RetValue == 0 || RetValue == 1 );
nClauses += Vec_IntSize( s_vMemory );
return nClauses;
}
/**Function*************************************************************
Synopsis [Computes the cost based on the factored form.]
Description []
......
......@@ -24,6 +24,7 @@
/// DECLARATIONS ///
////////////////////////////////////////////////////////////////////////
static solver * Abc_NtkMiterSatCreateLogic( Abc_Ntk_t * pNtk, int fAllPrimes );
extern Vec_Int_t * Abc_NtkGetCiSatVarNums( Abc_Ntk_t * pNtk );
static nMuxes;
......@@ -53,7 +54,6 @@ int Abc_NtkMiterSat( Abc_Ntk_t * pNtk, sint64 nConfLimit, sint64 nInsLimit, int
if ( pNumInspects )
*pNumInspects = 0;
assert( Abc_NtkIsStrash(pNtk) );
assert( Abc_NtkLatchNum(pNtk) == 0 );
// if ( Abc_NtkPoNum(pNtk) > 1 )
......@@ -61,7 +61,7 @@ int Abc_NtkMiterSat( Abc_Ntk_t * pNtk, sint64 nConfLimit, sint64 nInsLimit, int
// load clauses into the solver
clk = clock();
pSat = Abc_NtkMiterSatCreate( pNtk, fJFront );
pSat = Abc_NtkMiterSatCreate( pNtk, fJFront, 0 );
if ( pSat == NULL )
return 1;
// printf( "Created SAT problem with %d variable and %d clauses. ", solver_nvars(pSat), solver_nclauses(pSat) );
......@@ -121,6 +121,9 @@ int Abc_NtkMiterSat( Abc_Ntk_t * pNtk, sint64 nConfLimit, sint64 nInsLimit, int
if ( pNumInspects )
*pNumInspects = (int)pSat->solver_stats.inspects;
Sat_SolverTraceWrite( pSat, NULL, NULL, 0 );
Sat_SolverTraceStop( pSat );
solver_delete( pSat );
return RetValue;
}
......@@ -660,14 +663,17 @@ Quits :
SeeAlso []
***********************************************************************/
solver * Abc_NtkMiterSatCreate( Abc_Ntk_t * pNtk, int fJFront )
solver * Abc_NtkMiterSatCreate( Abc_Ntk_t * pNtk, int fJFront, int fAllPrimes )
{
solver * pSat;
Abc_Obj_t * pNode;
int RetValue, i, clk = clock();
nMuxes = 0;
assert( Abc_NtkIsStrash(pNtk) || Abc_NtkIsBddLogic(pNtk) );
if ( Abc_NtkIsBddLogic(pNtk) )
return Abc_NtkMiterSatCreateLogic(pNtk, fAllPrimes);
nMuxes = 0;
pSat = solver_new();
RetValue = Abc_NtkMiterSatCreateInt( pSat, pNtk, fJFront );
Abc_NtkForEachObj( pNtk, pNode, i )
......@@ -684,6 +690,242 @@ solver * Abc_NtkMiterSatCreate( Abc_Ntk_t * pNtk, int fJFront )
}
/**Function*************************************************************
Synopsis [Adds clauses for the internal node.]
Description []
SideEffects []
SeeAlso []
***********************************************************************/
int Abc_NodeAddClauses( solver * pSat, char * pSop0, char * pSop1, Abc_Obj_t * pNode, Vec_Int_t * vVars )
{
Abc_Obj_t * pFanin;
int i, c, nFanins;
int RetValue;
char * pCube;
nFanins = Abc_ObjFaninNum( pNode );
assert( nFanins == Abc_SopGetVarNum( pSop0 ) );
// if ( nFanins == 0 )
if ( Cudd_Regular(pNode->pData) == Cudd_ReadOne(pNode->pNtk->pManFunc) )
{
vVars->nSize = 0;
// if ( Abc_SopIsConst1(pSop1) )
if ( !Cudd_IsComplement(pNode->pData) )
Vec_IntPush( vVars, toLit(pNode->Id) );
else
Vec_IntPush( vVars, neg(toLit(pNode->Id)) );
RetValue = solver_addclause( pSat, vVars->pArray, vVars->pArray + vVars->nSize );
if ( !RetValue )
{
printf( "The CNF is trivially UNSAT.\n" );
return 0;
}
return 1;
}
// add clauses for the negative phase
for ( c = 0; ; c++ )
{
// get the cube
pCube = pSop0 + c * (nFanins + 3);
if ( *pCube == 0 )
break;
// add the clause
vVars->nSize = 0;
Abc_ObjForEachFanin( pNode, pFanin, i )
{
if ( pCube[i] == '0' )
Vec_IntPush( vVars, toLit(pFanin->Id) );
else if ( pCube[i] == '1' )
Vec_IntPush( vVars, neg(toLit(pFanin->Id)) );
}
Vec_IntPush( vVars, neg(toLit(pNode->Id)) );
RetValue = solver_addclause( pSat, vVars->pArray, vVars->pArray + vVars->nSize );
if ( !RetValue )
{
printf( "The CNF is trivially UNSAT.\n" );
return 0;
}
}
// add clauses for the positive phase
for ( c = 0; ; c++ )
{
// get the cube
pCube = pSop1 + c * (nFanins + 3);
if ( *pCube == 0 )
break;
// add the clause
vVars->nSize = 0;
Abc_ObjForEachFanin( pNode, pFanin, i )
{
if ( pCube[i] == '0' )
Vec_IntPush( vVars, toLit(pFanin->Id) );
else if ( pCube[i] == '1' )
Vec_IntPush( vVars, neg(toLit(pFanin->Id)) );
}
Vec_IntPush( vVars, toLit(pNode->Id) );
RetValue = solver_addclause( pSat, vVars->pArray, vVars->pArray + vVars->nSize );
if ( !RetValue )
{
printf( "The CNF is trivially UNSAT.\n" );
return 0;
}
}
return 1;
}
/**Function*************************************************************
Synopsis [Adds clauses for the PO node.]
Description []
SideEffects []
SeeAlso []
***********************************************************************/
int Abc_NodeAddClausesTop( solver * pSat, Abc_Obj_t * pNode, Vec_Int_t * vVars )
{
Abc_Obj_t * pFanin;
int RetValue;
pFanin = Abc_ObjFanin0(pNode);
if ( Abc_ObjFaninC0(pNode) )
{
vVars->nSize = 0;
Vec_IntPush( vVars, toLit(pFanin->Id) );
Vec_IntPush( vVars, toLit(pNode->Id) );
RetValue = solver_addclause( pSat, vVars->pArray, vVars->pArray + vVars->nSize );
if ( !RetValue )
{
printf( "The CNF is trivially UNSAT.\n" );
return 0;
}
vVars->nSize = 0;
Vec_IntPush( vVars, neg(toLit(pFanin->Id)) );
Vec_IntPush( vVars, neg(toLit(pNode->Id)) );
RetValue = solver_addclause( pSat, vVars->pArray, vVars->pArray + vVars->nSize );
if ( !RetValue )
{
printf( "The CNF is trivially UNSAT.\n" );
return 0;
}
}
else
{
vVars->nSize = 0;
Vec_IntPush( vVars, neg(toLit(pFanin->Id)) );
Vec_IntPush( vVars, toLit(pNode->Id) );
RetValue = solver_addclause( pSat, vVars->pArray, vVars->pArray + vVars->nSize );
if ( !RetValue )
{
printf( "The CNF is trivially UNSAT.\n" );
return 0;
}
vVars->nSize = 0;
Vec_IntPush( vVars, toLit(pFanin->Id) );
Vec_IntPush( vVars, neg(toLit(pNode->Id)) );
RetValue = solver_addclause( pSat, vVars->pArray, vVars->pArray + vVars->nSize );
if ( !RetValue )
{
printf( "The CNF is trivially UNSAT.\n" );
return 0;
}
}
vVars->nSize = 0;
Vec_IntPush( vVars, toLit(pNode->Id) );
RetValue = solver_addclause( pSat, vVars->pArray, vVars->pArray + vVars->nSize );
if ( !RetValue )
{
printf( "The CNF is trivially UNSAT.\n" );
return 0;
}
return 1;
}
/**Function*************************************************************
Synopsis [Sets up the SAT solver.]
Description []
SideEffects []
SeeAlso []
***********************************************************************/
solver * Abc_NtkMiterSatCreateLogic( Abc_Ntk_t * pNtk, int fAllPrimes )
{
solver * pSat;
Extra_MmFlex_t * pMmFlex;
Abc_Obj_t * pNode;
Vec_Str_t * vCube;
Vec_Int_t * vVars;
char * pSop0, * pSop1;
int i;
assert( Abc_NtkIsBddLogic(pNtk) );
// transfer the IDs to the copy field
Abc_NtkForEachPi( pNtk, pNode, i )
pNode->pCopy = (void *)pNode->Id;
// start the data structures
pSat = solver_new();
pMmFlex = Extra_MmFlexStart();
vCube = Vec_StrAlloc( 100 );
vVars = Vec_IntAlloc( 100 );
Sat_SolverTraceStart( pSat, "trace.cnf" );
// add clauses for each internal nodes
Abc_NtkForEachNode( pNtk, pNode, i )
{
// derive SOPs for both phases of the node
Abc_NodeBddToCnf( pNode, pMmFlex, vCube, fAllPrimes, &pSop0, &pSop1 );
// add the clauses to the solver
if ( !Abc_NodeAddClauses( pSat, pSop0, pSop1, pNode, vVars ) )
{
solver_delete( pSat );
pSat = NULL;
goto finish;
}
}
// add clauses for each PO
Abc_NtkForEachPo( pNtk, pNode, i )
{
if ( !Abc_NodeAddClausesTop( pSat, pNode, vVars ) )
{
solver_delete( pSat );
pSat = NULL;
goto finish;
}
}
finish:
// delete
Vec_StrFree( vCube );
Vec_IntFree( vVars );
Extra_MmFlexStop( pMmFlex );
return pSat;
}
////////////////////////////////////////////////////////////////////////
/// END OF FILE ///
////////////////////////////////////////////////////////////////////////
......
......@@ -49,7 +49,7 @@ Abc_Ntk_t * Abc_NtkRestrash( Abc_Ntk_t * pNtk, bool fCleanup )
extern int timeRetime;
Abc_Ntk_t * pNtkAig;
Abc_Obj_t * pObj;
int i, nNodes, RetValue;
int i, nNodes;//, RetValue;
assert( Abc_NtkIsStrash(pNtk) );
//timeRetime = clock();
// print warning about choice nodes
......@@ -79,8 +79,8 @@ Abc_Ntk_t * Abc_NtkRestrash( Abc_Ntk_t * pNtk, bool fCleanup )
return NULL;
}
//timeRetime = clock() - timeRetime;
if ( RetValue = Abc_NtkRemoveSelfFeedLatches(pNtkAig) )
printf( "Modified %d self-feeding latches. The result will not verify.\n", RetValue );
// if ( RetValue = Abc_NtkRemoveSelfFeedLatches(pNtkAig) )
// printf( "Modified %d self-feeding latches. The result will not verify.\n", RetValue );
return pNtkAig;
}
......
......@@ -417,6 +417,12 @@ int * Abc_NtkVerifySimulatePattern( Abc_Ntk_t * pNtk, int * pModel )
pNtk = Abc_NtkStrash(pNtk, 0, 0);
fStrashed = 1;
}
/*
printf( "Counter example: " );
Abc_NtkForEachCi( pNtk, pNode, i )
printf( " %d", pModel[i] );
printf( "\n" );
*/
// increment the trav ID
Abc_NtkIncrementTravId( pNtk );
// set the CI values
......
......@@ -1212,12 +1212,17 @@ int IoCommandWriteCnf( Abc_Frame_t * pAbc, int argc, char **argv )
{
char * pFileName;
int c;
int fAllPrimes;
fAllPrimes = 0;
Extra_UtilGetoptReset();
while ( ( c = Extra_UtilGetopt( argc, argv, "h" ) ) != EOF )
while ( ( c = Extra_UtilGetopt( argc, argv, "ph" ) ) != EOF )
{
switch ( c )
{
case 'p':
fAllPrimes ^= 1;
break;
case 'h':
goto usage;
default:
......@@ -1228,13 +1233,23 @@ int IoCommandWriteCnf( Abc_Frame_t * pAbc, int argc, char **argv )
goto usage;
// get the output file name
pFileName = argv[globalUtilOptind];
// check if the feature will be used
if ( Abc_NtkIsStrash(pAbc->pNtkCur) && fAllPrimes )
{
fAllPrimes = 0;
printf( "Warning: Selected option to write all primes has no effect when deriving CNF from AIG.\n" );
}
// call the corresponding file writer
if ( fAllPrimes )
Io_WriteCnf( pAbc->pNtkCur, pFileName, 1 );
else
Io_Write( pAbc->pNtkCur, pFileName, IO_FILE_CNF );
return 0;
usage:
fprintf( pAbc->Err, "usage: write_cnf [-h] <file>\n" );
fprintf( pAbc->Err, "usage: write_cnf [-ph] <file>\n" );
fprintf( pAbc->Err, "\t write the miter cone into a CNF file\n" );
fprintf( pAbc->Err, "\t-p : toggle using all primes to enhance implicativity [default = %s]\n", fAllPrimes? "yes" : "no" );
fprintf( pAbc->Err, "\t-h : print the help massage\n" );
fprintf( pAbc->Err, "\tfile : the name of the file to write\n" );
return 1;
......
......@@ -100,7 +100,7 @@ extern void Io_WriteBlifMvNetlist( Abc_Ntk_t * pNtk, char * FileNa
/*=== abcWriteBench.c =========================================================*/
extern int Io_WriteBench( Abc_Ntk_t * pNtk, char * FileName );
/*=== abcWriteCnf.c ===========================================================*/
extern int Io_WriteCnf( Abc_Ntk_t * pNtk, char * FileName );
extern int Io_WriteCnf( Abc_Ntk_t * pNtk, char * FileName, int fAllPrimes );
/*=== abcWriteDot.c ===========================================================*/
extern void Io_WriteDot( Abc_Ntk_t * pNtk, char * FileName );
extern void Io_WriteDotNtk( Abc_Ntk_t * pNtk, Vec_Ptr_t * vNodes, Vec_Ptr_t * vNodesShow, char * pFileName, int fGateNames, int fUseReverse );
......
......@@ -220,7 +220,7 @@ void Io_Write( Abc_Ntk_t * pNtk, char * pFileName, Io_FileType_t FileType )
// write non-netlist types
if ( FileType == IO_FILE_CNF )
{
Io_WriteCnf( pNtk, pFileName );
Io_WriteCnf( pNtk, pFileName, 0 );
return;
}
if ( FileType == IO_FILE_DOT )
......
......@@ -41,14 +41,13 @@ static Abc_Ntk_t * s_pNtk = NULL;
SeeAlso []
***********************************************************************/
int Io_WriteCnf( Abc_Ntk_t * pNtk, char * pFileName )
int Io_WriteCnf( Abc_Ntk_t * pNtk, char * pFileName, int fAllPrimes )
{
solver * pSat;
if ( !Abc_NtkIsStrash(pNtk) )
{
fprintf( stdout, "Io_WriteCnf(): Currently can only process AIGs.\n" );
return 0;
}
if ( Abc_NtkIsStrash(pNtk) )
printf( "Io_WriteCnf() warning: Generating CNF by applying heuristic AIG to CNF conversion.\n" );
else
printf( "Io_WriteCnf() warning: Generating CNF by convering logic nodes into CNF clauses.\n" );
if ( Abc_NtkPoNum(pNtk) != 1 )
{
fprintf( stdout, "Io_WriteCnf(): Currently can only process the miter (the network with one PO).\n" );
......@@ -64,8 +63,11 @@ int Io_WriteCnf( Abc_Ntk_t * pNtk, char * pFileName )
fprintf( stdout, "The network has no logic nodes. No CNF file is generaled.\n" );
return 0;
}
// convert to logic BDD network
if ( Abc_NtkIsLogic(pNtk) )
Abc_NtkLogicToBdd( pNtk );
// create solver with clauses
pSat = Abc_NtkMiterSatCreate( pNtk, 0 );
pSat = Abc_NtkMiterSatCreate( pNtk, 0, fAllPrimes );
if ( pSat == NULL )
{
fprintf( stdout, "The problem is trivially UNSAT. No CNF file is generated.\n" );
......
......@@ -352,7 +352,7 @@ int Seq_NtkImplementRetimingBackward( Abc_Ntk_t * pNtk, Vec_Ptr_t * vMoves, int
printf( "The number of ANDs in the AIG = %5d.\n", Abc_NtkNodeNum(pNtkMiter) );
// transform the miter into a logic network for efficient CNF construction
// pNtkCnf = Abc_NtkRenode( pNtkMiter, 0, 100, 1, 0, 0 );
// pNtkCnf = Abc_Ntk_Renode( pNtkMiter, 0, 100, 1, 0, 0 );
// Abc_NtkDelete( pNtkMiter );
pNtkCnf = pNtkMiter;
......
......@@ -45,6 +45,8 @@ extern "C" {
#define IF_MAX_LUTSIZE 32
// the largest possible number of LUT inputs when funtionality of the LUTs are computed
#define IF_MAX_FUNC_LUTSIZE 15
// a very large number
#define IF_INFINITY 100000000
// object types
typedef enum {
......@@ -75,6 +77,8 @@ struct If_Par_t_
// user-controlable paramters
int nLutSize; // the LUT size
int nCutsMax; // the max number of cuts
int nFlowIters; // the number of iterations of area recovery
int nAreaIters; // the number of iterations of area recovery
float DelayTarget; // delay target
int fPreprocess; // preprossing
int fArea; // area-oriented mapping
......@@ -88,6 +92,7 @@ struct If_Par_t_
int fUsePerm; // use permutation (delay info)
int fUseBdds; // sets local BDDs at the nodes
int fUseSops; // sets local SOPs at the nodes
int fUseCnfs; // sets local CNFs at the nodes
int nLatches; // the number of latches in seq mapping
int fLiftLeaves; // shift the leaves for seq mapping
If_Lib_t * pLutLib; // the LUT library
......@@ -276,6 +281,8 @@ static inline float If_CutLutArea( If_Man_t * p, If_Cut_t * pCut ) { r
// iterator over the leaves of the cut
#define If_CutForEachLeaf( p, pCut, pLeaf, i ) \
for ( i = 0; (i < (int)(pCut)->nLeaves) && ((pLeaf) = If_ManObj(p, (pCut)->pLeaves[i])); i++ )
#define If_CutForEachLeafReverse( p, pCut, pLeaf, i ) \
for ( i = (int)(pCut)->nLeaves - 1; (i >= 0) && ((pLeaf) = If_ManObj(p, (pCut)->pLeaves[i])); i-- )
//#define If_CutForEachLeaf( p, pCut, pLeaf, i ) \
// for ( i = 0; (i < (int)(pCut)->nLeaves) && ((pLeaf) = If_ManObj(p, p->pPars->fLiftLeaves? (pCut)->pLeaves[i] >> 8 : (pCut)->pLeaves[i])); i++ )
// iterator over the leaves of the sequential cut
......
......@@ -42,8 +42,6 @@
int If_ManPerformMapping( If_Man_t * p )
{
If_Obj_t * pObj;
int nItersFlow = 1;
int nItersArea = 2;
int clkTotal = clock();
int RetValue, i;
......@@ -74,14 +72,14 @@ int If_ManPerformMapping( If_Man_t * p )
if ( p->pPars->fExpRed && !p->pPars->fTruth )
If_ManImproveMapping( p );
// area flow oriented mapping
for ( i = 0; i < nItersFlow; i++ )
for ( i = 0; i < p->pPars->nFlowIters; i++ )
{
If_ManPerformMappingRound( p, p->pPars->nCutsMax, 1, 1, "Flow" );
if ( p->pPars->fExpRed && !p->pPars->fTruth )
If_ManImproveMapping( p );
}
// area oriented mapping
for ( i = 0; i < nItersArea; i++ )
for ( i = 0; i < p->pPars->nAreaIters; i++ )
{
If_ManPerformMappingRound( p, p->pPars->nCutsMax, 2, 1, "Area" );
if ( p->pPars->fExpRed && !p->pPars->fTruth )
......
......@@ -86,7 +86,7 @@ void If_ObjPerformMappingAnd( If_Man_t * p, If_Obj_t * pObj, int Mode )
pObj->EstRefs = (float)((2.0 * pObj->EstRefs + pObj->nRefs) / 3.0);
}
if ( Mode && pObj->nRefs > 0 )
If_CutDeref( p, If_ObjCutBest(pObj), 100 );
If_CutDeref( p, If_ObjCutBest(pObj), IF_INFINITY );
// save the best cut as one of the candidate cuts
p->nCuts = 0;
......@@ -97,7 +97,7 @@ void If_ObjPerformMappingAnd( If_Man_t * p, If_Obj_t * pObj, int Mode )
pCut = If_ObjCutBest(pObj);
pCut->Delay = If_CutDelay( p, pCut );
assert( pCut->Delay <= pObj->Required + p->fEpsilon );
pCut->Area = (Mode == 2)? If_CutAreaDerefed( p, pCut, 100 ) : If_CutFlow( p, pCut );
pCut->Area = (Mode == 2)? If_CutAreaDerefed( p, pCut, IF_INFINITY ) : If_CutFlow( p, pCut );
// save the best cut from the previous iteration
If_CutCopy( p->ppCuts[p->nCuts++], pCut );
p->nCutsMerged++;
......@@ -135,7 +135,7 @@ void If_ObjPerformMappingAnd( If_Man_t * p, If_Obj_t * pObj, int Mode )
if ( Mode && pCut->Delay > pObj->Required + p->fEpsilon )
continue;
// compute area of the cut (this area may depend on the application specific cost)
pCut->Area = (Mode == 2)? If_CutAreaDerefed( p, pCut, 100 ) : If_CutFlow( p, pCut );
pCut->Area = (Mode == 2)? If_CutAreaDerefed( p, pCut, IF_INFINITY ) : If_CutFlow( p, pCut );
pCut->AveRefs = (Mode == 0)? (float)0.0 : If_CutAverageRefs( p, pCut );
// make sure the cut is the last one (after filtering it may not be so)
assert( pCut == p->ppCuts[iCut] );
......@@ -160,7 +160,7 @@ void If_ObjPerformMappingAnd( If_Man_t * p, If_Obj_t * pObj, int Mode )
assert( p->pPars->fSeqMap || If_ObjCutBest(pObj)->nLeaves > 1 );
// ref the selected cut
if ( Mode && pObj->nRefs > 0 )
If_CutRef( p, If_ObjCutBest(pObj), 100 );
If_CutRef( p, If_ObjCutBest(pObj), IF_INFINITY );
}
/**Function*************************************************************
......@@ -182,7 +182,7 @@ void If_ObjPerformMappingChoice( If_Man_t * p, If_Obj_t * pObj, int Mode )
assert( pObj->pEquiv != NULL );
// prepare
if ( Mode && pObj->nRefs > 0 )
If_CutDeref( p, If_ObjCutBest(pObj), 100 );
If_CutDeref( p, If_ObjCutBest(pObj), IF_INFINITY );
// prepare room for the next cut
p->nCuts = 0;
iCut = p->nCuts;
......@@ -207,7 +207,7 @@ void If_ObjPerformMappingChoice( If_Man_t * p, If_Obj_t * pObj, int Mode )
// set the phase attribute
pCut->fCompl ^= (pObj->fPhase ^ pTemp->fPhase);
// compute area of the cut (this area may depend on the application specific cost)
pCut->Area = (Mode == 2)? If_CutAreaDerefed( p, pCut, 100 ) : If_CutFlow( p, pCut );
pCut->Area = (Mode == 2)? If_CutAreaDerefed( p, pCut, IF_INFINITY ) : If_CutFlow( p, pCut );
pCut->AveRefs = (Mode == 0)? (float)0.0 : If_CutAverageRefs( p, pCut );
// make sure the cut is the last one (after filtering it may not be so)
assert( pCut == p->ppCuts[iCut] );
......@@ -237,7 +237,7 @@ void If_ObjPerformMappingChoice( If_Man_t * p, If_Obj_t * pObj, int Mode )
assert( p->pPars->fSeqMap || If_ObjCutBest(pObj)->nLeaves > 1 );
// ref the selected cut
if ( Mode && pObj->nRefs > 0 )
If_CutRef( p, If_ObjCutBest(pObj), 100 );
If_CutRef( p, If_ObjCutBest(pObj), IF_INFINITY );
}
/**Function*************************************************************
......
......@@ -156,17 +156,17 @@ void If_ManImproveNodeExpand( If_Man_t * p, If_Obj_t * pObj, int nLimit, Vec_Ptr
// get the delay
DelayOld = pCut->Delay;
// get the area
AreaBef = If_CutAreaRefed( p, pCut, 100000 );
AreaBef = If_CutAreaRefed( p, pCut, IF_INFINITY );
// if ( AreaBef == 1 )
// return;
// the cut is non-trivial
If_ManImproveNodePrepare( p, pObj, nLimit, vFront, vFrontOld, vVisited );
// iteratively modify the cut
If_CutDeref( p, pCut, 100000 );
If_CutDeref( p, pCut, IF_INFINITY );
CostBef = If_ManImproveCutCost( p, vFront );
If_ManImproveNodeFaninCompact( p, pObj, nLimit, vFront, vVisited );
CostAft = If_ManImproveCutCost( p, vFront );
If_CutRef( p, pCut, 100000 );
If_CutRef( p, pCut, IF_INFINITY );
assert( CostBef >= CostAft );
// clean up
Vec_PtrForEachEntry( vVisited, pFanin, i )
......@@ -175,11 +175,11 @@ void If_ManImproveNodeExpand( If_Man_t * p, If_Obj_t * pObj, int nLimit, Vec_Ptr
If_ManImproveNodeUpdate( p, pObj, vFront );
pCut->Delay = If_CutDelay( p, pCut );
// get the new area
AreaAft = If_CutAreaRefed( p, pCut, 100000 );
AreaAft = If_CutAreaRefed( p, pCut, IF_INFINITY );
if ( AreaAft > AreaBef || pCut->Delay > pObj->Required + p->fEpsilon )
{
If_ManImproveNodeUpdate( p, pObj, vFrontOld );
AreaAft = If_CutAreaRefed( p, pCut, 100000 );
AreaAft = If_CutAreaRefed( p, pCut, IF_INFINITY );
assert( AreaAft == AreaBef );
pCut->Delay = DelayOld;
}
......@@ -257,13 +257,13 @@ void If_ManImproveNodeUpdate( If_Man_t * p, If_Obj_t * pObj, Vec_Ptr_t * vFront
int i;
pCut = If_ObjCutBest(pObj);
// deref node's cut
If_CutDeref( p, pCut, 10000 );
If_CutDeref( p, pCut, IF_INFINITY );
// update the node's cut
pCut->nLeaves = Vec_PtrSize(vFront);
Vec_PtrForEachEntry( vFront, pFanin, i )
pCut->pLeaves[i] = pFanin->Id;
// ref the new cut
If_CutRef( p, pCut, 10000 );
If_CutRef( p, pCut, IF_INFINITY );
}
......@@ -506,9 +506,9 @@ void If_ManImproveNodeReduce( If_Man_t * p, If_Obj_t * pObj, int nLimit )
// deref the cut if the node is refed
if ( pObj->nRefs > 0 )
If_CutDeref( p, pCut, 100000 );
If_CutDeref( p, pCut, IF_INFINITY );
// get the area
AreaBef = If_CutAreaDerefed( p, pCut, 100000 );
AreaBef = If_CutAreaDerefed( p, pCut, IF_INFINITY );
// get the fanin support
if ( pFanin0->nRefs > 2 && pCut0->Delay < pObj->Required + p->fEpsilon )
// if ( pSupp0->nRefs > 0 && pSupp0->Delay < pSupp->DelayR ) // this leads to 2% worse results
......@@ -534,7 +534,7 @@ void If_ManImproveNodeReduce( If_Man_t * p, If_Obj_t * pObj, int nLimit )
if ( RetValue )
{
pCutR->Delay = If_CutDelay( p, pCutR );
AreaAft = If_CutAreaDerefed( p, pCutR, 100000 );
AreaAft = If_CutAreaDerefed( p, pCutR, IF_INFINITY );
// update the best cut
if ( AreaAft < AreaBef - p->fEpsilon && pCutR->Delay < pObj->Required + p->fEpsilon )
If_CutCopy( pCut, pCutR );
......@@ -543,7 +543,7 @@ void If_ManImproveNodeReduce( If_Man_t * p, If_Obj_t * pObj, int nLimit )
pCut->Delay = If_CutDelay( p, pCut );
// ref the cut if the node is refed
if ( pObj->nRefs > 0 )
If_CutRef( p, pCut, 100000 );
If_CutRef( p, pCut, IF_INFINITY );
}
/**Function*************************************************************
......
......@@ -213,7 +213,7 @@ int Mio_GateParseFormula( Mio_Gate_t * pGate )
Cudd_Ref( pGate->bFunc );
// derive the cover (SOP)
pGate->pSop = Abc_ConvertBddToSop( pGate->pLib->pMmFlex, dd, pGate->bFunc, pGate->bFunc, nPins, pGate->pLib->vCube, -1 );
pGate->pSop = Abc_ConvertBddToSop( pGate->pLib->pMmFlex, dd, pGate->bFunc, pGate->bFunc, nPins, 0, pGate->pLib->vCube, -1 );
return 0;
}
......
......@@ -169,6 +169,7 @@ extern int Extra_bddIsVar( DdNode * bFunc );
extern DdNode * Extra_bddCreateAnd( DdManager * dd, int nVars );
extern DdNode * Extra_bddCreateOr( DdManager * dd, int nVars );
extern DdNode * Extra_bddCreateExor( DdManager * dd, int nVars );
extern DdNode * Extra_zddPrimes( DdManager * dd, DdNode * F );
/*=== extraBddKmap.c ================================================================*/
......
......@@ -55,6 +55,8 @@ static DdNode * extraTransferPermute( DdManager * ddS, DdManager * ddD, DdNode *
static void ddSupportStep(DdNode *f, int *support);
static void ddClearFlag(DdNode *f);
static DdNode* extraZddPrimes( DdManager *dd, DdNode* F );
/**AutomaticEnd***************************************************************/
/*---------------------------------------------------------------------------*/
......@@ -890,6 +892,30 @@ DdNode * Extra_bddCreateExor( DdManager * dd, int nVars )
return bFunc;
}
/**Function********************************************************************
Synopsis [Computes the set of primes as a ZDD.]
Description []
SideEffects []
SeeAlso []
******************************************************************************/
DdNode * Extra_zddPrimes( DdManager * dd, DdNode * F )
{
DdNode *res;
do {
dd->reordered = 0;
res = extraZddPrimes(dd, F);
if ( dd->reordered == 1 )
printf("\nReordering in Extra_zddPrimes()\n");
} while (dd->reordered == 1);
return(res);
} /* end of Extra_zddPrimes */
/*---------------------------------------------------------------------------*/
/* Definition of internal functions */
......@@ -1246,6 +1272,201 @@ ddClearFlag(
} /* end of ddClearFlag */
/**Function********************************************************************
Synopsis [Composed three subcovers into one ZDD.]
Description []
SideEffects [None]
SeeAlso []
******************************************************************************/
DdNode *
extraComposeCover(
DdManager* dd, /* the manager */
DdNode* zC0, /* the pointer to the negative var cofactor */
DdNode* zC1, /* the pointer to the positive var cofactor */
DdNode* zC2, /* the pointer to the cofactor without var */
int TopVar) /* the index of the positive ZDD var */
{
DdNode * zRes, * zTemp;
/* compose with-neg-var and without-var using the neg ZDD var */
zTemp = cuddZddGetNode( dd, 2*TopVar + 1, zC0, zC2 );
if ( zTemp == NULL )
{
Cudd_RecursiveDerefZdd(dd, zC0);
Cudd_RecursiveDerefZdd(dd, zC1);
Cudd_RecursiveDerefZdd(dd, zC2);
return NULL;
}
cuddRef( zTemp );
cuddDeref( zC0 );
cuddDeref( zC2 );
/* compose with-pos-var and previous result using the pos ZDD var */
zRes = cuddZddGetNode( dd, 2*TopVar, zC1, zTemp );
if ( zRes == NULL )
{
Cudd_RecursiveDerefZdd(dd, zC1);
Cudd_RecursiveDerefZdd(dd, zTemp);
return NULL;
}
cuddDeref( zC1 );
cuddDeref( zTemp );
return zRes;
} /* extraComposeCover */
/**Function********************************************************************
Synopsis [Performs the recursive step of prime computation.]
Description []
SideEffects []
SeeAlso []
******************************************************************************/
DdNode* extraZddPrimes( DdManager *dd, DdNode* F )
{
DdNode *zRes;
if ( F == Cudd_Not( dd->one ) )
return dd->zero;
if ( F == dd->one )
return dd->one;
/* check cache */
zRes = cuddCacheLookup1Zdd(dd, extraZddPrimes, F);
if (zRes)
return(zRes);
{
/* temporary variables */
DdNode *bF01, *zP0, *zP1;
/* three components of the prime set */
DdNode *zResE, *zResP, *zResN;
int fIsComp = Cudd_IsComplement( F );
/* find cofactors of F */
DdNode * bF0 = Cudd_NotCond( Cudd_E( F ), fIsComp );
DdNode * bF1 = Cudd_NotCond( Cudd_T( F ), fIsComp );
/* find the intersection of cofactors */
bF01 = cuddBddAndRecur( dd, bF0, bF1 );
if ( bF01 == NULL ) return NULL;
cuddRef( bF01 );
/* solve the problems for cofactors */
zP0 = extraZddPrimes( dd, bF0 );
if ( zP0 == NULL )
{
Cudd_RecursiveDeref( dd, bF01 );
return NULL;
}
cuddRef( zP0 );
zP1 = extraZddPrimes( dd, bF1 );
if ( zP1 == NULL )
{
Cudd_RecursiveDeref( dd, bF01 );
Cudd_RecursiveDerefZdd( dd, zP0 );
return NULL;
}
cuddRef( zP1 );
/* check for local unateness */
if ( bF01 == bF0 ) /* unate increasing */
{
/* intersection is useless */
cuddDeref( bF01 );
/* the primes of intersection are the primes of F0 */
zResE = zP0;
/* there are no primes with negative var */
zResN = dd->zero;
cuddRef( zResN );
/* primes with positive var are primes of F1 that are not primes of F01 */
zResP = cuddZddDiff( dd, zP1, zP0 );
if ( zResP == NULL )
{
Cudd_RecursiveDerefZdd( dd, zResE );
Cudd_RecursiveDerefZdd( dd, zResN );
Cudd_RecursiveDerefZdd( dd, zP1 );
return NULL;
}
cuddRef( zResP );
Cudd_RecursiveDerefZdd( dd, zP1 );
}
else if ( bF01 == bF1 ) /* unate decreasing */
{
/* intersection is useless */
cuddDeref( bF01 );
/* the primes of intersection are the primes of F1 */
zResE = zP1;
/* there are no primes with positive var */
zResP = dd->zero;
cuddRef( zResP );
/* primes with negative var are primes of F0 that are not primes of F01 */
zResN = cuddZddDiff( dd, zP0, zP1 );
if ( zResN == NULL )
{
Cudd_RecursiveDerefZdd( dd, zResE );
Cudd_RecursiveDerefZdd( dd, zResP );
Cudd_RecursiveDerefZdd( dd, zP0 );
return NULL;
}
cuddRef( zResN );
Cudd_RecursiveDerefZdd( dd, zP0 );
}
else /* not unate */
{
/* primes without the top var are primes of F10 */
zResE = extraZddPrimes( dd, bF01 );
if ( zResE == NULL )
{
Cudd_RecursiveDerefZdd( dd, bF01 );
Cudd_RecursiveDerefZdd( dd, zP0 );
Cudd_RecursiveDerefZdd( dd, zP1 );
return NULL;
}
cuddRef( zResE );
Cudd_RecursiveDeref( dd, bF01 );
/* primes with the negative top var are those of P0 that are not in F10 */
zResN = cuddZddDiff( dd, zP0, zResE );
if ( zResN == NULL )
{
Cudd_RecursiveDerefZdd( dd, zResE );
Cudd_RecursiveDerefZdd( dd, zP0 );
Cudd_RecursiveDerefZdd( dd, zP1 );
return NULL;
}
cuddRef( zResN );
Cudd_RecursiveDerefZdd( dd, zP0 );
/* primes with the positive top var are those of P1 that are not in F10 */
zResP = cuddZddDiff( dd, zP1, zResE );
if ( zResP == NULL )
{
Cudd_RecursiveDerefZdd( dd, zResE );
Cudd_RecursiveDerefZdd( dd, zResN );
Cudd_RecursiveDerefZdd( dd, zP1 );
return NULL;
}
cuddRef( zResP );
Cudd_RecursiveDerefZdd( dd, zP1 );
}
zRes = extraComposeCover( dd, zResN, zResP, zResE, Cudd_Regular(F)->index );
if ( zRes == NULL ) return NULL;
/* insert the result into cache */
cuddCacheInsert1(dd, extraZddPrimes, F, zRes);
return zRes;
}
} /* end of extraZddPrimes */
////////////////////////////////////////////////////////////////////////
/// END OF FILE ///
////////////////////////////////////////////////////////////////////////
......
......@@ -64,7 +64,7 @@ Kit_Graph_t * Kit_SopFactor( Vec_Int_t * vCover, int fCompl, int nVars, Vec_Int_
// check for trivial functions
if ( Vec_IntSize(vCover) == 0 )
return Kit_GraphCreateConst0();
if ( Vec_IntSize(vCover) == 1 && Vec_IntEntry(vCover, 0) == (int)Kit_CubeMask(nVars) )
if ( Vec_IntSize(vCover) == 1 && Vec_IntEntry(vCover, 0) == 0 ) //(int)Kit_CubeMask(2 * nVars) )
return Kit_GraphCreateConst1();
// prepare memory manager
......
......@@ -72,6 +72,7 @@ int Kit_TruthIsop( unsigned * puTruth, int nVars, Vec_Int_t * vMemory, int fTryB
assert( Extra_TruthIsEqual( puTruth, pResult, nVars ) );
if ( pcRes->nCubes == 0 || (pcRes->nCubes == 1 && pcRes->pCubes[0] == 0) )
{
vMemory->pArray[0] = 0;
Vec_IntShrink( vMemory, pcRes->nCubes );
return 0;
}
......
......@@ -25,8 +25,6 @@
/// DECLARATIONS ///
////////////////////////////////////////////////////////////////////////
static int Res_WinVisitMffc( Res_Win_t * p );
////////////////////////////////////////////////////////////////////////
/// FUNCTION DEFINITIONS ///
////////////////////////////////////////////////////////////////////////
......
/**CFile****************************************************************
FileName [satTrace.c]
SystemName [ABC: Logic synthesis and verification system.]
PackageName [C-language MiniSat solver.]
Synopsis [Records the trace of SAT solving in the CNF form.]
Author [Alan Mishchenko]
Affiliation [UC Berkeley]
Date [Ver. 1.0. Started - June 20, 2005.]
Revision [$Id: satTrace.c,v 1.4 2005/09/16 22:55:03 casem Exp $]
***********************************************************************/
#include <stdio.h>
#include <assert.h>
#include "solver.h"
/*
The trace of SAT solving contains the original clause of the problem
along with the learned clauses derived during SAT solving.
The first line of the resulting file contains 3 numbers instead of 2:
c <num_vars> <num_all_clauses> <num_root_clauses>
*/
////////////////////////////////////////////////////////////////////////
/// DECLARATIONS ///
////////////////////////////////////////////////////////////////////////
static inline int lit_var (lit l) { return l >> 1; }
static inline int lit_sign (lit l) { return l & 1; }
static inline int lit_print(lit l) { return lit_sign(l)? -lit_var(l)-1 : lit_var(l)+1; }
////////////////////////////////////////////////////////////////////////
/// FUNCTION DEFINITIONS ///
////////////////////////////////////////////////////////////////////////
/**Function*************************************************************
Synopsis [Start the trace recording.]
Description []
SideEffects []
SeeAlso []
***********************************************************************/
void Sat_SolverTraceStart( solver * pSat, char * pName )
{
assert( pSat->pFile == NULL );
pSat->pFile = fopen( pName, "w" );
fprintf( pSat->pFile, " \n" );
pSat->nClauses = 0;
pSat->nRoots = 0;
}
/**Function*************************************************************
Synopsis [Stops the trace recording.]
Description []
SideEffects []
SeeAlso []
***********************************************************************/
void Sat_SolverTraceStop( solver * pSat )
{
if ( pSat->pFile == NULL )
return;
rewind( pSat->pFile );
fprintf( pSat->pFile, "p %d %d %d", solver_nvars(pSat), pSat->nClauses, pSat->nRoots );
fclose( pSat->pFile );
pSat->pFile = NULL;
}
/**Function*************************************************************
Synopsis [Writes one clause into the trace file.]
Description []
SideEffects []
SeeAlso []
***********************************************************************/
void Sat_SolverTraceWrite( solver * pSat, int * pBeg, int * pEnd, int fRoot )
{
if ( pSat->pFile == NULL )
return;
pSat->nClauses++;
pSat->nRoots += fRoot;
for ( ; pBeg < pEnd ; pBeg++ )
fprintf( pSat->pFile, " %d", lit_print(*pBeg) );
fprintf( pSat->pFile, " 0\n" );
}
////////////////////////////////////////////////////////////////////////
/// END OF FILE ///
////////////////////////////////////////////////////////////////////////
......@@ -20,6 +20,7 @@ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWA
// Modified to compile with MS Visual Studio 6.0 by Alan Mishchenko
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <time.h>
......@@ -505,6 +506,8 @@ static void solver_record(solver* s, veci* cls)
clause* c = (veci_size(cls) > 1) ? clause_new(s,begin,end,1) : (clause*)0;
enqueue(s,*begin,c);
Sat_SolverTraceWrite( s, begin, end, 0 );
assert(veci_size(cls) > 0);
if (c != 0) {
......@@ -948,6 +951,7 @@ static lbool solver_search(solver* s, int nof_conflicts, int nof_learnts)
solver* solver_new(void)
{
solver* s = (solver*)malloc(sizeof(solver));
memset( s, 0, sizeof(solver) );
// initialize vectors
vec_new(&s->clauses);
......@@ -1100,7 +1104,10 @@ bool solver_addclause(solver* s, lit* begin, lit* end)
if (j == begin) // empty clause
return 0;
else if (j - begin == 1) // unit clause
Sat_SolverTraceWrite( s, begin, end, 1 );
if (j - begin == 1) // unit clause
return enqueue(s,*begin,(clause*)0);
// create new clause
......
......@@ -91,6 +91,11 @@ extern void Asat_SatPrintStats( FILE * pFile, solver * p );
extern void Asat_SolverSetPrefVars( solver * s, int * pPrefVars, int nPrefVars );
extern void Asat_SolverSetFactors( solver * s, float * pFactors );
// trace recording
extern void Sat_SolverTraceStart( solver * pSat, char * pName );
extern void Sat_SolverTraceStop( solver * pSat );
extern void Sat_SolverTraceWrite( solver * pSat, int * pBeg, int * pEnd, int fRoot );
// J-frontier support
extern Asat_JMan_t * Asat_JManStart( solver * pSat, void * vCircuit );
extern void Asat_JManStop( solver * pSat );
......@@ -170,6 +175,11 @@ struct solver_t
int timeTotal;
int timeSelect;
int timeUpdate;
// trace recording
FILE * pFile;
int nClauses;
int nRoots;
};
#ifdef __cplusplus
......
......@@ -90,7 +90,7 @@ ABC_Manager ABC_InitManager()
// set default parameters for CEC
Prove_ParamsSetDefault( &mng->Params );
// set infinite resource limit for the final mitering
mng->Params.nMiteringLimitLast = ABC_INFINITY;
// mng->Params.nMiteringLimitLast = ABC_INFINITY;
return mng;
}
......
......@@ -74,6 +74,39 @@ void Prove_ParamsSetDefault( Prove_Params_t * pParams )
/**Function*************************************************************
Synopsis [Prints out the current values of CEC engine parameters.]
Description []
SideEffects []
SeeAlso []
***********************************************************************/
void Prove_ParamsPrint( Prove_Params_t * pParams )
{
printf( "CEC enging parameters:\n" );
printf( "Fraiging enabled: %s\n", pParams->fUseFraiging? "yes":"no" );
printf( "Rewriting enabled: %s\n", pParams->fUseRewriting? "yes":"no" );
printf( "BDD construction enabled: %s\n", pParams->fUseBdds? "yes":"no" );
printf( "Verbose output enabled: %s\n", pParams->fVerbose? "yes":"no" );
printf( "Solver iterations: %d\n", pParams->nItersMax );
printf( "Starting mitering limit: %d\n", pParams->nMiteringLimitStart );
printf( "Multiplicative coeficient for mitering: %.2f\n", pParams->nMiteringLimitMulti );
printf( "Starting number of rewriting iterations: %d\n", pParams->nRewritingLimitStart );
printf( "Multiplicative coeficient for rewriting: %.2f\n", pParams->nRewritingLimitMulti );
printf( "Starting number of conflicts in fraiging: %d\n", pParams->nFraigingLimitMulti );
printf( "Multiplicative coeficient for fraiging: %.2f\n", pParams->nRewritingLimitMulti );
printf( "BDD size limit for bailing out: %.2f\n", pParams->nBddSizeLimit );
printf( "BDD reordering enabled: %s\n", pParams->fBddReorder? "yes":"no" );
printf( "Last-gasp mitering limit: %d\n", pParams->nMiteringLimitLast );
printf( "Total conflict limit: %d\n", pParams->nTotalBacktrackLimit );
printf( "Total inspection limit: %d\n", pParams->nTotalInspectLimit );
printf( "Parameter dump complete.\n" );
}
/**Function*************************************************************
Synopsis [Sets the default parameters of the package.]
Description [This set of parameters is tuned for equivalence checking.]
......
/**CFile****************************************************************
FileName [pr.h]
SystemName [ABC: Logic synthesis and verification system.]
PackageName [Proof recording.]
Synopsis [External declarations.]
Author [Alan Mishchenko]
Affiliation [UC Berkeley]
Date [Ver. 1.0. Started - June 20, 2005.]
Revision [$Id: pr.h,v 1.00 2005/06/20 00:00:00 alanmi Exp $]
***********************************************************************/
#ifndef __PR_H__
#define __PR_H__
#ifdef __cplusplus
extern "C" {
#endif
#ifdef _WIN32
#define inline __inline // compatible with MS VS 6.0
#endif
////////////////////////////////////////////////////////////////////////
/// INCLUDES ///
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
/// PARAMETERS ///
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
/// BASIC TYPES ///
////////////////////////////////////////////////////////////////////////
typedef struct Pr_Man_t_ Pr_Man_t;
////////////////////////////////////////////////////////////////////////
/// MACRO DEFINITIONS ///
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
/// FUNCTION DECLARATIONS ///
////////////////////////////////////////////////////////////////////////
/*=== pr.c ==========================================================*/
#ifdef __cplusplus
}
#endif
#endif
////////////////////////////////////////////////////////////////////////
/// END OF FILE ///
////////////////////////////////////////////////////////////////////////
UC Berkeley, ABC 1.01 (compiled Jan 20 2007 16:47:34)
abc.rc: No such file or directory
Loaded "abc.rc" from the parent directory.
abc 01> test
Found last conflict after adding unit clause number 10229!
Roots = 7184. Learned = 3047. Total = 10231. Steps = 196361. Ave = 62.09. Used = 2224. Ratio = 0.73.
Runtime stats:
Reading = 0.03 sec
BCP = 0.32 sec
Trace = 0.06 sec
TOTAL = 0.43 sec
abc 01> test
Found last conflict after adding unit clause number 7676!
Roots = 6605. Learned = 1073. Total = 7678. Steps = 52402. Ave = 42.68. Used = 1011. Ratio = 0.94.
Runtime stats:
Reading = 0.01 sec
BCP = 0.02 sec
Trace = 0.02 sec
TOTAL = 0.06 sec
abc 01> test
Found last conflict after adding unit clause number 37868!
Roots = 15443. Learned = 22427. Total = 37870. Steps = 2365472. Ave = 104.79. Used = 19763. Ratio = 0.88.
Runtime stats:
Reading = 0.20 sec
BCP = 14.67 sec
Trace = 0.56 sec
TOTAL = 15.74 sec
abc 01>
abc 05> wb ibm_bmc/len25u_renc.blif
abc 05> ps
(no name) : i/o = 348/ 1 lat = 0 nd = 3648 bdd = 15522 lev = 246
abc 05> sat -v
==================================[MINISAT]===================================
| Conflicts | ORIGINAL | LEARNT | Progress |
| | Clauses Literals | Limit Clauses Literals Lit/Cl | |
==============================================================================
| 0 | 17413 54996 | 5804 0 0 0.0 | 0.000 % |
| 100 | 17413 54996 | 6384 100 606 6.1 | 0.417 % |
| 250 | 17413 54996 | 7023 250 1586 6.3 | 0.417 % |
| 476 | 17413 54996 | 7725 476 3288 6.9 | 0.417 % |
| 813 | 17413 54996 | 8498 813 7586 9.3 | 0.417 % |
| 1319 | 17403 54970 | 9347 1318 14848 11.3 | 0.442 % |
| 2078 | 17403 54970 | 10282 2076 40186 19.4 | 0.466 % |
| 3217 | 17397 54948 | 11310 3208 99402 31.0 | 0.466 % |
| 4926 | 17392 54930 | 12441 4911 131848 26.8 | 0.491 % |
| 7489 | 17392 54930 | 13686 7474 204217 27.3 | 0.491 % |
| 11336 | 17357 54829 | 15054 11310 332863 29.4 | 0.638 % |
| 17103 | 17346 54794 | 16559 9130 203029 22.2 | 0.687 % |
| 25752 | 17288 54606 | 18215 9083 176982 19.5 | 0.834 % |
| 38727 | 17266 54536 | 20037 12674 278949 22.0 | 0.883 % |
| 58188 | 17240 54453 | 22041 11905 255255 21.4 | 0.957 % |
==============================================================================
Start = 15. Conf = 79435. Dec = 130967. Prop = 24083434. Insp = 136774586.
Total runtime = 18.66 sec. Var select = 0.00 sec. Var update = 0.00 sec.
UNSATISFIABLE Time = 18.69 sec
abc 05>
abc 05> test
Found last conflict after adding unit clause number 96902!
Roots = 17469. Learned = 79435. Total = 96904. Steps = 9700042. Ave = 121.89. Used = 57072. Ratio = 0.72.
Runtime stats:
Reading = 1.26 sec
BCP = 204.99 sec
Trace = 2.85 sec
TOTAL = 209.85 sec
\ No newline at end of file
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