From 2918589334cb2df21d4be3fbe56f8557881647fd Mon Sep 17 00:00:00 2001 From: aiquoc Date: Wed, 29 Jul 2026 12:45:02 +0800 Subject: [PATCH 01/14] experiment X-aware equivalence checking --- src/base/abci/abc.c | 14 +- src/base/acb/acbTest.c | 4992 ++++++++++++++++++++++++++++++++++++-- src/base/acb/acbXec.c | 368 +++ src/base/acb/acbXec.h | 71 + src/base/acb/module.make | 3 +- 5 files changed, 5281 insertions(+), 167 deletions(-) create mode 100644 src/base/acb/acbXec.c create mode 100644 src/base/acb/acbXec.h diff --git a/src/base/abci/abc.c b/src/base/abci/abc.c index 6d5f66097..9e56e128a 100644 --- a/src/base/abci/abc.c +++ b/src/base/abci/abc.c @@ -7960,14 +7960,17 @@ usage: ***********************************************************************/ int Abc_CommandRunTest( Abc_Frame_t * pAbc, int argc, char ** argv ) { - extern void Acb_NtkRunTest( char * pFileNames[4], int fFancy, int fVerbose ); + extern void Acb_NtkRunTest( char * pFileNames[4], int fFancy, int fVerbose, int fUseCadical ); char * pFileNames[4] = {NULL}; - int c, fFancy = 0, fVerbose = 0; + int c, fFancy = 0, fVerbose = 0, fUseCadical = 0; Extra_UtilGetoptReset(); - while ( ( c = Extra_UtilGetopt( argc, argv, "fvh" ) ) != EOF ) + while ( ( c = Extra_UtilGetopt( argc, argv, "cfvh" ) ) != EOF ) { switch ( c ) { + case 'c': + fUseCadical ^= 1; + break; case 'f': fFancy ^= 1; break; @@ -7987,12 +7990,13 @@ int Abc_CommandRunTest( Abc_Frame_t * pAbc, int argc, char ** argv ) } for ( c = 0; c < argc - globalUtilOptind; c++ ) pFileNames[c] = argv[globalUtilOptind+c]; - Acb_NtkRunTest( pFileNames, fFancy, fVerbose ); + Acb_NtkRunTest( pFileNames, fFancy, fVerbose, fUseCadical ); return 0; usage: - Abc_Print( -2, "usage: xec [-fvh] \n" ); + Abc_Print( -2, "usage: xec [-cfvh] \n" ); Abc_Print( -2, "\t combinational equivalence checking with x-values\n" ); + Abc_Print( -2, "\t-c : toggle using CaDiCaL SAT-only solving [default = %s]\n", fUseCadical? "yes": "no" ); Abc_Print( -2, "\t-f : toggle using experimental feature [default = %s]\n", fFancy? "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"); diff --git a/src/base/acb/acbTest.c b/src/base/acb/acbTest.c index c22d4b55b..aac003264 100644 --- a/src/base/acb/acbTest.c +++ b/src/base/acb/acbTest.c @@ -9,7 +9,7 @@ Synopsis [] Author [Alan Mishchenko] - + Affiliation [UC Berkeley] Date [Ver. 1.0. Started - July 21, 2015.] @@ -19,10 +19,17 @@ ***********************************************************************/ #include "acb.h" +#include "acbXec.h" #include "aig/saig/saig.h" #include "aig/gia/giaAig.h" #include "base/abc/abc.h" #include "proof/fraig/fraig.h" +#include "proof/cec/cec.h" +#include "proof/dch/dch.h" +#include "proof/acec/acec.h" +#include "opt/dar/dar.h" +#include "sat/cadical/cadicalSolver.h" +#include "sat/cnf/cnf.h" #include "misc/util/utilTruth.h" ABC_NAMESPACE_IMPL_START @@ -31,7 +38,182 @@ ABC_NAMESPACE_IMPL_START /// DECLARATIONS /// //////////////////////////////////////////////////////////////////////// -static int fForceZero = 0; +#define ACB_FORCE_ZERO 0 +#define ACB_XEC_RECURSION_LIMIT 8192 + +typedef enum Acb_CexCheckStatus_t_ +{ + ACB_CEX_UNSUPPORTED = -1, + ACB_CEX_INVALID = 0, + ACB_CEX_VALID = 1 +} Acb_CexCheckStatus_t; + +typedef struct Acb_XecCtx_t_ +{ + struct Acb_XecParams_t_ + { + int nScratchVecInit; /* Initial capacity for per-run hard-output vectors. */ + int nOverlapMinPermille; /* Minimum cone overlap for grouping outputs in one SAT cluster. */ + int nOverlapSizePermille; /* Minimum smaller/larger cone-size ratio for output clustering. */ + int nBranchMinOutputSec; /* Keep this much branch budget before starting another PO solve. */ + int nBranchLocalOptAndMin; /* Try local optimization/abstraction only for large branch cones. */ + int nBranchLocalOptSec; /* Time cap for local optimized branch-cone SAT. */ + int nBranchFrontierAbsSec; /* Time cap for frontier abstraction probe. */ + int nBranchHardConflictMin; /* Report/isolate branch outputs above this conflict delta. */ + int nBranchHardTimeMin; /* Report/isolate branch outputs above this runtime delta. */ + int nBranchSchedulePrintMax; /* Max branch output ids printed in the clustered schedule. */ + int nLocalManyPoThreshold; /* Above this PO count, local sweep uses quick SAT-hunting probes. */ + int nLocalMediumPoMin; /* Lower PO count for medium sweep behavior. */ + int nLocalMediumPoMax; /* Upper PO count for medium sweep behavior. */ + int nLocalQuickMaxUndec; /* Quick many-output sweep stops after this many undecided probes. */ + int nLocalQuickPoSec; /* Per-output limit for quick many-output probes. */ + int nLocalMediumPoSec; /* Per-output limit for medium local sweep. */ + int nLocalMediumHardPoSec; /* Per-output limit after first hard output in medium sweep. */ + int nLocalConeCompressAndMin; /* Compress local cone only when it has at least this many ANDs. */ + int nSimLargeAndMin; /* Use larger random simulation only above this miter size. */ + int nSimSmallWords; /* Random-simulation words for small miters. */ + int nSimLargeWords; /* Random-simulation words for large miters. */ + int nMainLargeAndMin; /* Enter heavy xec proof orchestration above this AND count. */ + int nMainLargePiMin; /* Enter heavy xec proof orchestration above this PI count. */ + int nMainLargePoMin; /* Enter heavy xec proof orchestration above this PO count. */ + int nSharedDcPiMin; /* Prefer shared whole-miter SAT for few-control DC above this PI count. */ + int nSharedDcPoMin; /* Prefer shared whole-miter SAT for few-control DC above this PO count. */ + int nSharedDcPoMax; /* Upper PO bound for the few-control high-PI DC shape. */ + int nSharedDcAndMin; /* Lower AND bound for the few-control high-PI DC shape. */ + int nSharedDcAndMax; /* Upper AND bound for the few-control high-PI DC shape. */ + int nSharedDcObjMin; /* Lower DC-object count for the few-control high-PI DC shape. */ + int nSharedDcObjMax; /* Upper DC-object count for the few-control high-PI DC shape. */ + int nSharedDcWholeSec; /* Whole-miter SAT time cap for the few-control high-PI DC shape. */ + } Pars; + int LastHardPo; + Vec_Int_t * vLastHardPos; + Vec_Int_t * vLastProvenPos; + Vec_Int_t * vLastBranchHardPos; + int LastHardDirectTried; +} Acb_XecCtx_t; + +static inline void Acb_XecParamsSetDefault( Acb_XecCtx_t * p ) +{ + p->Pars.nScratchVecInit = 8; + p->Pars.nOverlapMinPermille = 700; + p->Pars.nOverlapSizePermille = 450; + p->Pars.nBranchMinOutputSec = 60; + p->Pars.nBranchLocalOptAndMin = 10000; + p->Pars.nBranchLocalOptSec = 300; + p->Pars.nBranchFrontierAbsSec = 60; + p->Pars.nBranchHardConflictMin = 1000000; + p->Pars.nBranchHardTimeMin = 60; + p->Pars.nBranchSchedulePrintMax = 12; + p->Pars.nLocalManyPoThreshold = 64; + p->Pars.nLocalMediumPoMin = 8; + p->Pars.nLocalMediumPoMax = 64; + p->Pars.nLocalQuickMaxUndec = 12; + p->Pars.nLocalQuickPoSec = 5; + p->Pars.nLocalMediumPoSec = 60; + p->Pars.nLocalMediumHardPoSec = 15; + p->Pars.nLocalConeCompressAndMin = 1000; + p->Pars.nSimLargeAndMin = 5000; + p->Pars.nSimSmallWords = 1; + p->Pars.nSimLargeWords = 256; + p->Pars.nMainLargeAndMin = 30000; + p->Pars.nMainLargePiMin = 256; + p->Pars.nMainLargePoMin = 64; + p->Pars.nSharedDcPiMin = 4096; + p->Pars.nSharedDcPoMin = 80; + p->Pars.nSharedDcPoMax = 128; + p->Pars.nSharedDcAndMin = 100000; + p->Pars.nSharedDcAndMax = 200000; + p->Pars.nSharedDcObjMin = 160; + p->Pars.nSharedDcObjMax = 256; + p->Pars.nSharedDcWholeSec = 1800; +} + +static inline int Acb_XecIsSharedDcWholeMiterShape( Gia_Man_t * pGia, Vec_Int_t * vMuxSelectorsG, Vec_Int_t * vIntDcObjsG, Vec_Int_t * vIntDcCtrlsG, Acb_XecCtx_t * pCtx ) +{ + if ( pGia == NULL || pCtx == NULL ) + return 0; + if ( vMuxSelectorsG && Vec_IntSize(vMuxSelectorsG) > 0 ) + return 0; + if ( vIntDcObjsG == NULL || vIntDcCtrlsG == NULL ) + return 0; + if ( Vec_IntSize(vIntDcCtrlsG) != 2 ) + return 0; + if ( Vec_IntSize(vIntDcObjsG) < pCtx->Pars.nSharedDcObjMin || Vec_IntSize(vIntDcObjsG) > pCtx->Pars.nSharedDcObjMax ) + return 0; + if ( Gia_ManCiNum(pGia) < pCtx->Pars.nSharedDcPiMin ) + return 0; + if ( Gia_ManCoNum(pGia) < pCtx->Pars.nSharedDcPoMin || Gia_ManCoNum(pGia) > pCtx->Pars.nSharedDcPoMax ) + return 0; + if ( Gia_ManAndNum(pGia) < pCtx->Pars.nSharedDcAndMin || Gia_ManAndNum(pGia) > pCtx->Pars.nSharedDcAndMax ) + return 0; + return 1; +} + +static inline void Acb_XecCtxInit( Acb_XecCtx_t * p ) +{ + memset( p, 0, sizeof(*p) ); + Acb_XecParamsSetDefault( p ); + p->LastHardPo = -1; +} + +static inline void Acb_XecCtxFree( Acb_XecCtx_t * p ) +{ + Vec_IntFreeP( &p->vLastHardPos ); + Vec_IntFreeP( &p->vLastProvenPos ); + Vec_IntFreeP( &p->vLastBranchHardPos ); + p->LastHardPo = -1; + p->LastHardDirectTried = 0; +} + +static inline void Acb_XecCtxResetLocalSweep( Acb_XecCtx_t * p ) +{ + p->LastHardPo = -1; + p->LastHardDirectTried = 0; + Vec_IntFreeP( &p->vLastHardPos ); + p->vLastHardPos = Vec_IntAlloc( p->Pars.nScratchVecInit ); + Vec_IntFreeP( &p->vLastProvenPos ); + p->vLastProvenPos = Vec_IntAlloc( p->Pars.nScratchVecInit ); +} + +static inline void Acb_XecCtxResetBranchSweep( Acb_XecCtx_t * p, int nOuts ) +{ + Vec_IntFreeP( &p->vLastBranchHardPos ); + p->vLastBranchHardPos = Vec_IntAlloc( nOuts ); +} + +Gia_Man_t * Acb_GiaDupOnePoTrimmed( Gia_Man_t * p, int iPo, Vec_Int_t * vSuppMap ) +{ + Gia_Obj_t * pPo; + Gia_Man_t * pNew; + int iLit, iPoObj; + if ( vSuppMap ) + Vec_IntClear( vSuppMap ); + if ( p == NULL || iPo < 0 || iPo >= Gia_ManCoNum(p) ) + return NULL; + pPo = Gia_ManCo( p, iPo ); + iLit = Gia_ObjFaninLit0p( p, pPo ); + if ( Gia_ManIsConst0Lit(iLit) || Gia_ManIsConst1Lit(iLit) ) + { + Gia_Man_t * pNew = Gia_ManStart( 1 ); + pNew->pName = Abc_UtilStrsav( p->pName ); + Gia_ManAppendCo( pNew, Gia_ManIsConst1Lit(iLit) ); + return pNew; + } + iPoObj = Gia_ObjFaninId0p( p, pPo ); + if ( vSuppMap ) + { + Gia_ManCollectCis( p, &iPoObj, 1, vSuppMap ); + Vec_IntSort( vSuppMap, 0 ); + } + pNew = Gia_ManDupCones( p, &iPo, 1, 1 ); + if ( pNew == NULL && vSuppMap ) + Vec_IntClear( vSuppMap ); + return pNew; +} + +int * Acb_NtkSolveCadicalLocalConeSweepSkipCtx( Gia_Man_t * p, int fVerbose, int * pStatus, int nSatTimeLimit, int nPoTimeLimit, Vec_Int_t * vSkipUnsat, Acb_XecCtx_t * pCtx ); +int Acb_GiaRequiredLiteralUnitProof( Gia_Man_t * p, int iPo, int fVerbose, int nSatTimeLimit ); +int * Acb_NtkSolveMuxDcControlTargetList( Acb_Ntk_t * pNtkF, Acb_Ntk_t * pNtkG, Vec_Int_t * vHardPos, Vec_Int_t * vCutObjsG, Vec_Int_t * vMuxSelectorsG, Vec_Int_t * vMuxPoSelIdsG, int fSelBranch, Vec_Int_t * vDcObjsG, Vec_Int_t * vDcCtrlsG, Vec_Int_t * vDcCtrlIdsG, int fVerbose, int * pStatus, int nBranchLimit ); //////////////////////////////////////////////////////////////////////// /// FUNCTION DEFINITIONS /// @@ -42,16 +224,21 @@ static int fForceZero = 0; Synopsis [] Description [] - + SideEffects [] SeeAlso [] ***********************************************************************/ -void Gia_ManSimTry( Gia_Man_t * pF, Gia_Man_t * pG ) +int * Acb_NtkFindSimCex( Gia_Man_t * pF, Gia_Man_t * pG, int nWords, int fVerbose ) { - int i, j, n, nWords = 500; Vec_Wrd_t * vSimsF, * vSimsG; + Gia_Obj_t * pObjFb, * pObjFx, * pObjGb, * pObjGx; + word * pSimFb, * pSimFx, * pSimGb, * pSimGx, * pSimPi; + int i, k, b, nBits = 64 * nWords; + int * pModel = NULL; + assert( Gia_ManCiNum(pF) == Gia_ManCiNum(pG) ); + assert( Gia_ManCoNum(pF) == Gia_ManCoNum(pG) ); Abc_Random(1); Vec_WrdFreeP( &pF->vSimsPi ); Vec_WrdFreeP( &pG->vSimsPi ); @@ -59,75 +246,182 @@ void Gia_ManSimTry( Gia_Man_t * pF, Gia_Man_t * pG ) pG->vSimsPi = Vec_WrdDup( pF->vSimsPi ); vSimsF = Gia_ManSimPatSim( pF ); vSimsG = Gia_ManSimPatSim( pG ); - assert( Gia_ManObjNum(pF) * nWords == Vec_WrdSize(vSimsF) ); - for ( i = 0; i < Gia_ManCoNum(pF)/2; i++ ) + for ( i = 0; i < Gia_ManCoNum(pF)/2 && pModel == NULL; i++ ) { - Gia_Obj_t * pObjFb = Gia_ManCo( pF, 2*i+0 ); - Gia_Obj_t * pObjFx = Gia_ManCo( pF, 2*i+1 ); - Gia_Obj_t * pObjGb = Gia_ManCo( pG, 2*i+0 ); - Gia_Obj_t * pObjGx = Gia_ManCo( pG, 2*i+1 ); - word * pSimFb = Vec_WrdEntryP(vSimsF, Gia_ObjId(pF, pObjFb)*nWords); - word * pSimFx = Vec_WrdEntryP(vSimsF, Gia_ObjId(pF, pObjFx)*nWords); - word * pSimGb = Vec_WrdEntryP(vSimsG, Gia_ObjId(pG, pObjGb)*nWords); - word * pSimGx = Vec_WrdEntryP(vSimsG, Gia_ObjId(pG, pObjGx)*nWords); - - int nBitsFx = Abc_TtCountOnesVec(pSimFx, nWords); - int nBitsF1 = Abc_TtCountOnesVecMask(pSimFx, pSimFb, nWords, 1); - int nBitsF0 = nWords*64 - nBitsFx - nBitsF1; - - int nBitsGx = Abc_TtCountOnesVec(pSimGx, nWords); - int nBitsG1 = Abc_TtCountOnesVecMask(pSimGx, pSimGb, nWords, 1); - int nBitsG0 = nWords*64 - nBitsGx - nBitsG1; - - printf( "Output %4d : ", i ); - - printf( " RF : " ); - printf( "0 =%7.3f %% ", 100.0*nBitsF0/64/nWords ); - printf( "1 =%7.3f %% ", 100.0*nBitsF1/64/nWords ); - printf( "X =%7.3f %% ", 100.0*nBitsFx/64/nWords ); - - printf( " GF : " ); - printf( "0 =%7.3f %% ", 100.0*nBitsG0/64/nWords ); - printf( "1 =%7.3f %% ", 100.0*nBitsG1/64/nWords ); - printf( "X =%7.3f %% ", 100.0*nBitsGx/64/nWords ); - - printf( "\n" ); - if ( i == 20 ) - break; - } - - printf( "\n" ); - for ( j = 0; j < 20; j++ ) - { - for ( n = 0; n < 2; n++ ) - { - for ( i = 0; i < Gia_ManCoNum(pF)/2; i++ ) + pObjFb = Gia_ManCo( pF, 2*i+0 ); + pObjFx = Gia_ManCo( pF, 2*i+1 ); + pObjGb = Gia_ManCo( pG, 2*i+0 ); + pObjGx = Gia_ManCo( pG, 2*i+1 ); + pSimFb = Vec_WrdEntryP(vSimsF, Gia_ObjId(pF, pObjFb)*nWords); + pSimFx = Vec_WrdEntryP(vSimsF, Gia_ObjId(pF, pObjFx)*nWords); + pSimGb = Vec_WrdEntryP(vSimsG, Gia_ObjId(pG, pObjGb)*nWords); + pSimGx = Vec_WrdEntryP(vSimsG, Gia_ObjId(pG, pObjGx)*nWords); + for ( b = 0; b < nBits; b++ ) + if ( !Abc_TtGetBit(pSimGx, b) && (Abc_TtGetBit(pSimFx, b) || (Abc_TtGetBit(pSimFb, b) ^ Abc_TtGetBit(pSimGb, b))) ) { - Gia_Obj_t * pObjFb = Gia_ManCo( pF, 2*i+0 ); - Gia_Obj_t * pObjFx = Gia_ManCo( pF, 2*i+1 ); - Gia_Obj_t * pObjGb = Gia_ManCo( pG, 2*i+0 ); - Gia_Obj_t * pObjGx = Gia_ManCo( pG, 2*i+1 ); - word * pSimFb = Vec_WrdEntryP(vSimsF, Gia_ObjId(pF, pObjFb)*nWords); - word * pSimFx = Vec_WrdEntryP(vSimsF, Gia_ObjId(pF, pObjFx)*nWords); - word * pSimGb = Vec_WrdEntryP(vSimsG, Gia_ObjId(pG, pObjGb)*nWords); - word * pSimGx = Vec_WrdEntryP(vSimsG, Gia_ObjId(pG, pObjGx)*nWords); - word * pSimb = n ? pSimGb : pSimFb; - word * pSimx = n ? pSimGx : pSimFx; - if ( Abc_TtGetBit(pSimx, j) ) - printf( "x" ); - else if ( Abc_TtGetBit(pSimb, j) ) - printf( "1" ); - else - printf( "0" ); + pModel = ABC_ALLOC( int, Gia_ManCiNum(pF) ); + for ( k = 0; k < Gia_ManCiNum(pF); k++ ) + { + pSimPi = Vec_WrdEntryP( pF->vSimsPi, k*nWords ); + pModel[k] = Abc_TtGetBit( pSimPi, b ); + } + if ( fVerbose ) + printf( "Random simulation found mismatch at output %d, pattern %d.\n", i, b ); + break; } - printf( "\n" ); - } - printf( "\n" ); } - + if ( fVerbose && pModel == NULL ) + printf( "Random simulation tried %d patterns and found no mismatch.\n", nBits ); Vec_WrdFree( vSimsF ); Vec_WrdFree( vSimsG ); - printf( "\n" ); + Vec_WrdFreeP( &pF->vSimsPi ); + Vec_WrdFreeP( &pG->vSimsPi ); + return pModel; +} +int * Acb_GiaFindOnePoSimCex( Gia_Man_t * p, int nWords, int fVerbose, char * pLabel ) +{ + Vec_Wrd_t * vSims = NULL; + Gia_Obj_t * pObjPo; + word * pSimPo, * pSimPi; + int k, b, nBits = 64 * nWords; + int * pModel = NULL; + if ( p == NULL || Gia_ManCoNum(p) != 1 || Gia_ManCiNum(p) <= 0 || nWords <= 0 ) + return NULL; + Abc_Random( 1 ); + Vec_WrdFreeP( &p->vSimsPi ); + p->vSimsPi = Vec_WrdStartRandom( Gia_ManCiNum(p) * nWords ); + vSims = Gia_ManSimPatSim( p ); + pObjPo = Gia_ManCo( p, 0 ); + pSimPo = Vec_WrdEntryP( vSims, Gia_ObjId(p, pObjPo) * nWords ); + for ( b = 0; b < nBits; b++ ) + { + if ( !Abc_TtGetBit(pSimPo, b) ) + continue; + pModel = ABC_ALLOC( int, Gia_ManCiNum(p) ); + for ( k = 0; k < Gia_ManCiNum(p); k++ ) + { + pSimPi = Vec_WrdEntryP( p->vSimsPi, k * nWords ); + pModel[k] = Abc_TtGetBit( pSimPi, b ); + } + if ( fVerbose ) + printf( "%s simulation found bad pattern at pattern %d/%d.\n", + pLabel ? pLabel : "Hard-output", b, nBits ); + break; + } + if ( fVerbose && pModel == NULL ) + printf( "%s simulation tried %d patterns and found no bad pattern.\n", + pLabel ? pLabel : "Hard-output", nBits ); + Vec_WrdFreeP( &vSims ); + Vec_WrdFreeP( &p->vSimsPi ); + return pModel; +} + +int Acb_NtkCheckModelCex( Gia_Man_t * pF, Gia_Man_t * pG, int * pModel, int fVerbose ) +{ + Gia_Obj_t * pObj; + int i, Fb, Fx, Gb, Gx; + if ( pModel == NULL ) + return 0; + Gia_ManConst0(pF)->Value = 0; + Gia_ManConst0(pG)->Value = 0; + Gia_ManForEachCi( pF, pObj, i ) + pObj->Value = pModel[i] ? 1 : 0; + Gia_ManForEachCi( pG, pObj, i ) + pObj->Value = pModel[i] ? 1 : 0; + Gia_ManForEachAnd( pF, pObj, i ) + pObj->Value = Gia_ObjFanin0Copy(pObj) & Gia_ObjFanin1Copy(pObj); + Gia_ManForEachAnd( pG, pObj, i ) + pObj->Value = Gia_ObjFanin0Copy(pObj) & Gia_ObjFanin1Copy(pObj); + for ( i = 0; i < Gia_ManCoNum(pF)/2; i++ ) + { + Fb = Gia_ObjFanin0Copy( Gia_ManCo(pF, 2*i+0) ); + Fx = Gia_ObjFanin0Copy( Gia_ManCo(pF, 2*i+1) ); + Gb = Gia_ObjFanin0Copy( Gia_ManCo(pG, 2*i+0) ); + Gx = Gia_ObjFanin0Copy( Gia_ManCo(pG, 2*i+1) ); + if ( !Gx && (Fx || (Fb ^ Gb)) ) + { + if ( fVerbose ) + printf( "Validated SAT counterexample at output %d.\n", i ); + return 1; + } + } + if ( fVerbose ) + printf( "SAT model validation failed: no compatible mismatch is observed.\n" ); + return 0; +} +int Acb_NtkEvalModelBool( Acb_Ntk_t * p, int * pModel, Vec_Int_t * vVals ) +{ + int i, k, iObj, Type, * pFans; + Vec_IntFill( vVals, Acb_NtkObjNumMax(p), 0 ); + Acb_NtkForEachCi( p, iObj, i ) + Vec_IntWriteEntry( vVals, iObj, pModel[i] ? 1 : 0 ); + Acb_NtkForEachObj( p, iObj ) + { + int z = 0; + if ( Acb_ObjIsCio(p, iObj) ) + continue; + Type = Acb_ObjType( p, iObj ); + pFans = Acb_ObjFanins( p, iObj ); + if ( Type == ABC_OPER_CONST_F ) + z = 0; + else if ( Type == ABC_OPER_CONST_T ) + z = 1; + else if ( Type == ABC_OPER_BIT_BUF ) + z = Vec_IntEntry(vVals, pFans[1]); + else if ( Type == ABC_OPER_BIT_INV ) + z = !Vec_IntEntry(vVals, pFans[1]); + else if ( Type == ABC_OPER_BIT_AND || Type == ABC_OPER_BIT_NAND ) + { + z = 1; + for ( k = 0; k < pFans[0]; k++ ) + z &= Vec_IntEntry(vVals, pFans[k+1]); + if ( Type == ABC_OPER_BIT_NAND ) + z = !z; + } + else if ( Type == ABC_OPER_BIT_OR || Type == ABC_OPER_BIT_NOR ) + { + z = 0; + for ( k = 0; k < pFans[0]; k++ ) + z |= Vec_IntEntry(vVals, pFans[k+1]); + if ( Type == ABC_OPER_BIT_NOR ) + z = !z; + } + else if ( Type == ABC_OPER_BIT_XOR || Type == ABC_OPER_BIT_NXOR ) + { + z = 0; + for ( k = 0; k < pFans[0]; k++ ) + z ^= Vec_IntEntry(vVals, pFans[k+1]); + if ( Type == ABC_OPER_BIT_NXOR ) + z = !z; + } + else + return 0; + Vec_IntWriteEntry( vVals, iObj, z ); + } + return 1; +} +int Acb_NtkCheckModelCexAcbBool( Acb_Ntk_t * pF, Acb_Ntk_t * pG, int * pModel, int fVerbose ) +{ + Vec_Int_t * vF = Vec_IntAlloc( Acb_NtkObjNumMax(pF) ); + Vec_Int_t * vG = Vec_IntAlloc( Acb_NtkObjNumMax(pG) ); + int i, iCoF, iCoG, Ret = ACB_CEX_INVALID; + if ( pModel && Acb_NtkEvalModelBool(pF, pModel, vF) && Acb_NtkEvalModelBool(pG, pModel, vG) ) + { + Acb_NtkForEachCo( pF, iCoF, i ) + { + iCoG = Acb_NtkCo( pG, i ); + if ( Vec_IntEntry(vF, Acb_ObjFanin(pF, iCoF, 0)) != Vec_IntEntry(vG, Acb_ObjFanin(pG, iCoG, 0)) ) + { + if ( fVerbose ) + printf( "Original ACB Boolean validation found SAT counterexample at output %d.\n", i ); + Ret = ACB_CEX_VALID; + break; + } + } + } + Vec_IntFree( vF ); + Vec_IntFree( vG ); + return Ret; } /**Function************************************************************* @@ -135,7 +429,7 @@ void Gia_ManSimTry( Gia_Man_t * pF, Gia_Man_t * pG ) Synopsis [] Description [] - + SideEffects [] SeeAlso [] @@ -145,41 +439,18 @@ void Gia_ManDualNot( Gia_Man_t * p, int LitA[2], int LitZ[2] ) { LitZ[0] = Abc_LitNot(LitA[0]); LitZ[1] = LitA[1]; - - if ( fForceZero ) LitZ[0] = Gia_ManHashAnd( p, LitZ[0], Abc_LitNot(LitZ[1]) ); + + if ( ACB_FORCE_ZERO ) LitZ[0] = Gia_ManHashAnd( p, LitZ[0], Abc_LitNot(LitZ[1]) ); } // computes Z = XOR(A, B) where A, B, Z belong to {0,1,x} encoded as 0=00, 1=01, x=1- void Gia_ManDualXor2( Gia_Man_t * p, int LitA[2], int LitB[2], int LitZ[2] ) { LitZ[0] = Gia_ManHashXor( p, LitA[0], LitB[0] ); LitZ[1] = Gia_ManHashOr( p, LitA[1], LitB[1] ); - - if ( fForceZero ) LitZ[0] = Gia_ManHashAnd( p, LitZ[0], Abc_LitNot(LitZ[1]) ); -} -void Gia_ManDualXorN( Gia_Man_t * p, int * pLits, int n, int LitZ[2] ) -{ - int i; - LitZ[0] = 0; - LitZ[1] = 0; - for ( i = 0; i < n; i++ ) - { - LitZ[0] = Gia_ManHashXor( p, LitZ[0], pLits[2*i] ); - LitZ[1] = Gia_ManHashOr ( p, LitZ[1], pLits[2*i+1] ); - } + + if ( ACB_FORCE_ZERO ) LitZ[0] = Gia_ManHashAnd( p, LitZ[0], Abc_LitNot(LitZ[1]) ); } // computes Z = AND(A, B) where A, B, Z belong to {0,1,x} encoded as 0=00, 1=01, z=1- -void Gia_ManDualAnd2( Gia_Man_t * p, int LitA[2], int LitB[2], int LitZ[2] ) -{ - int ZeroA = Gia_ManHashAnd( p, Abc_LitNot(LitA[0]), Abc_LitNot(LitA[1]) ); - int ZeroB = Gia_ManHashAnd( p, Abc_LitNot(LitB[0]), Abc_LitNot(LitB[1]) ); - int ZeroZ = Gia_ManHashOr( p, ZeroA, ZeroB ); - LitZ[0] = Gia_ManHashAnd( p, LitA[0], LitB[0] ); - LitZ[1] = Gia_ManHashAnd( p, Gia_ManHashOr( p, LitA[1], LitB[1] ), Abc_LitNot(ZeroZ) ); - - //LitZ[0] = Gia_ManHashAnd( p, Gia_ManHashAnd(p, LitA[0], Abc_LitNot(LitA[1])), Gia_ManHashAnd(p, LitB[0], Abc_LitNot(LitB[1])) ); - //LitZ[1] = Gia_ManHashAnd( p, Gia_ManHashOr(p, LitA[0], LitA[1]), Gia_ManHashOr(p, LitB[0], LitB[1]) ); - //LitZ[1] = Gia_ManHashAnd( p, LitZ[1], Abc_LitNot(LitZ[0]) ); -} void Gia_ManDualAndN( Gia_Man_t * p, int * pLits, int n, int LitZ[2] ) { int i, LitZero = 0, LitOne = 0; @@ -192,8 +463,8 @@ void Gia_ManDualAndN( Gia_Man_t * p, int * pLits, int n, int LitZ[2] ) LitZ[0] = Gia_ManHashAnd( p, LitZ[0], pLits[2*i] ); } LitZ[1] = Gia_ManHashAnd( p, LitOne, Abc_LitNot(LitZero) ); - - if ( fForceZero ) LitZ[0] = Gia_ManHashAnd( p, LitZ[0], Abc_LitNot(LitZ[1]) ); + + if ( ACB_FORCE_ZERO ) LitZ[0] = Gia_ManHashAnd( p, LitZ[0], Abc_LitNot(LitZ[1]) ); } /* module _DC(O, C, D); @@ -207,8 +478,8 @@ void Gia_ManDualDc( Gia_Man_t * p, int LitC[2], int LitD[2], int LitZ[2] ) LitZ[0] = LitC[0]; // LitZ[0] = Gia_ManHashMux( p, LitD[0], 0, LitC[0] ); LitZ[1] = Gia_ManHashOr(p, Gia_ManHashOr(p,LitD[0],LitD[1]), LitC[1] ); - - if ( fForceZero ) LitZ[0] = Gia_ManHashAnd( p, LitZ[0], Abc_LitNot(LitZ[1]) ); + + if ( ACB_FORCE_ZERO ) LitZ[0] = Gia_ManHashAnd( p, LitZ[0], Abc_LitNot(LitZ[1]) ); } void Gia_ManDualMux( Gia_Man_t * p, int LitC[2], int LitT[2], int LitE[2], int LitZ[2] ) { @@ -233,7 +504,7 @@ void Gia_ManDualMux( Gia_Man_t * p, int LitC[2], int LitT[2], int LitE[2], int L LitZ[0] = Gia_ManHashMux( p, LitC[0], LitT[0], LitE[0] ); LitZ[1] = Gia_ManHashMux( p, LitC[1], XVal1, XVal0 ); - if ( fForceZero ) LitZ[0] = Gia_ManHashAnd( p, LitZ[0], Abc_LitNot(LitZ[1]) ); + if ( ACB_FORCE_ZERO ) LitZ[0] = Gia_ManHashAnd( p, LitZ[0], Abc_LitNot(LitZ[1]) ); } int Gia_ManDualCompare( Gia_Man_t * p, int LitF[2], int LitS[2] ) { @@ -242,84 +513,127 @@ int Gia_ManDualCompare( Gia_Man_t * p, int LitF[2], int LitS[2] ) iMiter = Gia_ManHashAnd( p, Abc_LitNot(LitS[1]), iMiter ); return iMiter; } +static inline void Gia_ManDualForceZero( Gia_Man_t * p, int LitZ[2], int fForceZero ) +{ + if ( fForceZero ) + LitZ[0] = Gia_ManHashAnd( p, LitZ[0], Abc_LitNot(LitZ[1]) ); +} /**Function************************************************************* Synopsis [] Description [] - + SideEffects [] SeeAlso [] ***********************************************************************/ -void Acb_ObjToGiaDual( Gia_Man_t * pNew, Acb_Ntk_t * p, int iObj, Vec_Int_t * vTemp, Vec_Int_t * vCopies, int pRes[2] ) +int Acb_ObjToGiaDual( Gia_Man_t * pNew, Acb_Ntk_t * p, int iObj, Vec_Int_t * vTemp, Vec_Int_t * vCopies, int pRes[2], Vec_Int_t * vDcBranchObjs, Vec_Int_t * vDcBranchVals, int fDcBranchOne, int fForceZero ) { //char * pName = Abc_NamStr( p->pDesign->pStrs, Acb_ObjName(p, iObj) ); int * pFanin, iFanin, k, Type; - assert( !Acb_ObjIsCio(p, iObj) ); + if ( Acb_ObjIsCio(p, iObj) ) + { + Acb_NtkPrintUnsupportedObj( p, iObj, "ACB dual translation", -1, 0 ); + return 0; + } Vec_IntClear( vTemp ); Acb_ObjForEachFaninFast( p, iObj, pFanin, iFanin, k ) { int * pLits = Vec_IntEntryP( vCopies, 2*iFanin ); - assert( pLits[0] >= 0 && pLits[1] >= 0 ); + if ( pLits[0] < 0 || pLits[1] < 0 ) + { + Acb_NtkPrintUnsupportedObj( p, iObj, "ACB dual translation has unmapped fanin", -1, k ); + return 0; + } Vec_IntPushTwo( vTemp, pLits[0], pLits[1] ); } Type = Acb_ObjType( p, iObj ); - if ( Type == ABC_OPER_CONST_F ) + if ( Type == ABC_OPER_CONST_F ) { pRes[0] = 0; pRes[1] = 0; - return; + return 1; } - if ( Type == ABC_OPER_CONST_T ) + if ( Type == ABC_OPER_CONST_T ) { pRes[0] = 1; pRes[1] = 0; - return; + return 1; } - if ( Type == ABC_OPER_CONST_X ) + if ( Type == ABC_OPER_CONST_X ) { pRes[0] = 0; pRes[1] = 1; - return; + return 1; } - if ( Type == ABC_OPER_BIT_BUF ) + if ( Type == ABC_OPER_BIT_BUF ) { pRes[0] = Vec_IntEntry(vTemp, 0); pRes[1] = Vec_IntEntry(vTemp, 1); - return; + Gia_ManDualForceZero( pNew, pRes, fForceZero ); + return 1; } - if ( Type == ABC_OPER_BIT_INV ) + if ( Type == ABC_OPER_BIT_INV ) { Gia_ManDualNot( pNew, Vec_IntArray(vTemp), pRes ); - return; + Gia_ManDualForceZero( pNew, pRes, fForceZero ); + return 1; } - if ( Type == ABC_OPER_TRI ) + if ( Type == ABC_OPER_TRI ) { // in the file inputs are ordered as follows: _DC \n6_5[9] ( .O(\108 ), .C(\96 ), .D(\107 )); // in this code, we expect them as follows: void Gia_ManDualDc( Gia_Man_t * p, int LitC[2], int LitD[2], int LitZ[2] ) - assert( Vec_IntSize(vTemp) == 4 ); + if ( Vec_IntSize(vTemp) != 4 ) + { + Acb_NtkPrintUnsupportedObj( p, iObj, "ACB dual TRI translation", 2, Vec_IntSize(vTemp)/2 ); + return 0; + } + if ( vDcBranchObjs && Vec_IntFind(vDcBranchObjs, iObj) >= 0 ) + { + int iPos = Vec_IntFind(vDcBranchObjs, iObj); + int fOne = vDcBranchVals ? Vec_IntEntry(vDcBranchVals, iPos) : fDcBranchOne; + if ( fOne ) + { + pRes[0] = 0; + pRes[1] = 1; + } + else + { + pRes[0] = Vec_IntEntry(vTemp, 0); + pRes[1] = Vec_IntEntry(vTemp, 1); + } + Gia_ManDualForceZero( pNew, pRes, fForceZero ); + return 1; + } Gia_ManDualDc( pNew, Vec_IntArray(vTemp), Vec_IntArray(vTemp) + 2, pRes ); - return; + Gia_ManDualForceZero( pNew, pRes, fForceZero ); + return 1; } - if ( Type == ABC_OPER_BIT_MUX ) + if ( Type == ABC_OPER_BIT_MUX ) { // in the file inputs are ordered as follows: _HMUX \U$1 ( .O(\282 ), .I0(1'b1), .I1(\277 ), .S(\281 )); // in this code, we expect them as follows: void Gia_ManDualMux( Gia_Man_t * p, int LitC[2], int LitT[2], int LitE[2], int LitZ[2] ) - assert( Vec_IntSize(vTemp) == 6 ); + if ( Vec_IntSize(vTemp) != 6 ) + { + Acb_NtkPrintUnsupportedObj( p, iObj, "ACB dual MUX translation", 3, Vec_IntSize(vTemp)/2 ); + return 0; + } ABC_SWAP( int, Vec_IntArray(vTemp)[0], Vec_IntArray(vTemp)[4] ); ABC_SWAP( int, Vec_IntArray(vTemp)[1], Vec_IntArray(vTemp)[5] ); Gia_ManDualMux( pNew, Vec_IntArray(vTemp), Vec_IntArray(vTemp) + 2, Vec_IntArray(vTemp) + 4, pRes ); - return; + Gia_ManDualForceZero( pNew, pRes, fForceZero ); + return 1; } if ( Type == ABC_OPER_BIT_AND || Type == ABC_OPER_BIT_NAND ) { Gia_ManDualAndN( pNew, Vec_IntArray(vTemp), Vec_IntSize(vTemp)/2, pRes ); if ( Type == ABC_OPER_BIT_NAND ) pRes[0] = Abc_LitNot( pRes[0] ); - return; + Gia_ManDualForceZero( pNew, pRes, fForceZero ); + return 1; } if ( Type == ABC_OPER_BIT_OR || Type == ABC_OPER_BIT_NOR ) { @@ -329,19 +643,26 @@ void Acb_ObjToGiaDual( Gia_Man_t * pNew, Acb_Ntk_t * p, int iObj, Vec_Int_t * vT Gia_ManDualAndN( pNew, pArray, Vec_IntSize(vTemp)/2, pRes ); if ( Type == ABC_OPER_BIT_OR ) pRes[0] = Abc_LitNot( pRes[0] ); - return; + Gia_ManDualForceZero( pNew, pRes, fForceZero ); + return 1; } if ( Type == ABC_OPER_BIT_XOR || Type == ABC_OPER_BIT_NXOR ) { - assert( Vec_IntSize(vTemp) == 4 ); + if ( Vec_IntSize(vTemp) != 4 ) + { + Acb_NtkPrintUnsupportedObj( p, iObj, "ACB dual XOR translation", 2, Vec_IntSize(vTemp)/2 ); + return 0; + } Gia_ManDualXor2( pNew, Vec_IntArray(vTemp), Vec_IntArray(vTemp) + 2, pRes ); if ( Type == ABC_OPER_BIT_NXOR ) pRes[0] = Abc_LitNot( pRes[0] ); - return; + Gia_ManDualForceZero( pNew, pRes, fForceZero ); + return 1; } - assert( 0 ); + Acb_NtkPrintUnsupportedObj( p, iObj, "ACB dual translation", -1, Vec_IntSize(vTemp)/2 ); + return 0; } -Gia_Man_t * Acb_NtkGiaDeriveDual( Acb_Ntk_t * p ) +Gia_Man_t * Acb_NtkGiaDeriveDualTargetsBranchValuesForceZero( Acb_Ntk_t * p, Vec_Int_t * vTargets, Vec_Int_t * vDcBranchObjs, Vec_Int_t * vDcBranchVals, int fDcBranchOne, int fForceZero ) { extern Vec_Int_t * Acb_NtkFindNodes2( Acb_Ntk_t * p ); Gia_Man_t * pNew, * pOne; @@ -365,11 +686,27 @@ Gia_Man_t * Acb_NtkGiaDeriveDual( Acb_Ntk_t * p ) Vec_IntForEachEntry( vNodes, iObj, i ) { pLits = Vec_IntEntryP( vCopies, 2*iObj ); - Acb_ObjToGiaDual( pNew, p, iObj, vFanins, vCopies, pLits ); + if ( !Acb_ObjToGiaDual( pNew, p, iObj, vFanins, vCopies, pLits, vDcBranchObjs, vDcBranchVals, fDcBranchOne, fForceZero ) ) + { + Vec_IntFree( vNodes ); + Vec_IntFree( vFanins ); + Vec_IntFree( vCopies ); + Gia_ManStop( pNew ); + return NULL; + } } Vec_IntFree( vNodes ); Vec_IntFree( vFanins ); - Acb_NtkForEachCo( p, iObj, i ) + if ( vTargets ) + { + Vec_IntForEachEntry( vTargets, iObj, i ) + { + pLits = Vec_IntEntryP( vCopies, 2*iObj ); + Gia_ManAppendCo( pNew, pLits[0] ); + Gia_ManAppendCo( pNew, pLits[1] ); + } + } + else Acb_NtkForEachCo( p, iObj, i ) { pLits = Vec_IntEntryP( vCopies, 2*Acb_ObjFanin(p, iObj, 0) ); Gia_ManAppendCo( pNew, pLits[0] ); @@ -380,13 +717,279 @@ Gia_Man_t * Acb_NtkGiaDeriveDual( Acb_Ntk_t * p ) Gia_ManStop( pOne ); return pNew; } +Gia_Man_t * Acb_NtkGiaDeriveDualTargetsBranchValues( Acb_Ntk_t * p, Vec_Int_t * vTargets, Vec_Int_t * vDcBranchObjs, Vec_Int_t * vDcBranchVals, int fDcBranchOne ) +{ + return Acb_NtkGiaDeriveDualTargetsBranchValuesForceZero( p, vTargets, vDcBranchObjs, vDcBranchVals, fDcBranchOne, 0 ); +} +Gia_Man_t * Acb_NtkGiaDeriveDualTargetsBranch( Acb_Ntk_t * p, Vec_Int_t * vTargets, Vec_Int_t * vDcBranchObjs, int fDcBranchOne ) +{ + return Acb_NtkGiaDeriveDualTargetsBranchValues( p, vTargets, vDcBranchObjs, NULL, fDcBranchOne ); +} +Gia_Man_t * Acb_NtkGiaDeriveDualTargets( Acb_Ntk_t * p, Vec_Int_t * vTargets ) +{ + return Acb_NtkGiaDeriveDualTargetsBranch( p, vTargets, NULL, 0 ); +} +Gia_Man_t * Acb_NtkGiaDeriveDualTargetsForceZero( Acb_Ntk_t * p, Vec_Int_t * vTargets ) +{ + return Acb_NtkGiaDeriveDualTargetsBranchValuesForceZero( p, vTargets, NULL, NULL, 0, 1 ); +} + +Gia_Man_t * Acb_NtkGiaDeriveDualTargetsCutLeaves( Acb_Ntk_t * p, Vec_Int_t * vTargets, Vec_Int_t * vCutObjs ) +{ + extern Vec_Int_t * Acb_NtkFindNodes2( Acb_Ntk_t * p ); + Gia_Man_t * pNew, * pOne; + Vec_Int_t * vFanins, * vNodes; + Vec_Int_t * vCopies = Vec_IntStartFull( 2*Acb_NtkObjNum(p) ); + Vec_Int_t * vCutMap = Vec_IntStart( Acb_NtkObjNumMax(p) ); + int i, iObj, * pLits; + pNew = Gia_ManStart( 5 * Acb_NtkObjNum(p) ); + pNew->pName = Abc_UtilStrsav(Acb_NtkName(p)); + Gia_ManHashAlloc( pNew ); + pLits = Vec_IntEntryP( vCopies, 0 ); + pLits[0] = 0; + pLits[1] = 0; + Acb_NtkForEachCi( p, iObj, i ) + { + pLits = Vec_IntEntryP( vCopies, 2*iObj ); + pLits[0] = Gia_ManAppendCi(pNew); + pLits[1] = 0; + } + if ( vCutObjs ) + Vec_IntForEachEntry( vCutObjs, iObj, i ) + { + pLits = Vec_IntEntryP( vCopies, 2*iObj ); + pLits[0] = Gia_ManAppendCi(pNew); + pLits[1] = Gia_ManAppendCi(pNew); + Vec_IntWriteEntry( vCutMap, iObj, 1 ); + } + vFanins = Vec_IntAlloc( 4 ); + vNodes = Acb_NtkFindNodes2( p ); + Vec_IntForEachEntry( vNodes, iObj, i ) + { + if ( Vec_IntEntry(vCutMap, iObj) ) + continue; + pLits = Vec_IntEntryP( vCopies, 2*iObj ); + if ( !Acb_ObjToGiaDual( pNew, p, iObj, vFanins, vCopies, pLits, NULL, NULL, 0, 0 ) ) + { + Vec_IntFree( vNodes ); + Vec_IntFree( vFanins ); + Vec_IntFree( vCutMap ); + Vec_IntFree( vCopies ); + Gia_ManStop( pNew ); + return NULL; + } + } + Vec_IntFree( vNodes ); + Vec_IntFree( vFanins ); + if ( vTargets ) + { + Vec_IntForEachEntry( vTargets, iObj, i ) + { + pLits = Vec_IntEntryP( vCopies, 2*iObj ); + Gia_ManAppendCo( pNew, pLits[0] ); + Gia_ManAppendCo( pNew, pLits[1] ); + } + } + else Acb_NtkForEachCo( p, iObj, i ) + { + pLits = Vec_IntEntryP( vCopies, 2*Acb_ObjFanin(p, iObj, 0) ); + Gia_ManAppendCo( pNew, pLits[0] ); + Gia_ManAppendCo( pNew, pLits[1] ); + } + Vec_IntFree( vCutMap ); + Vec_IntFree( vCopies ); + pNew = Gia_ManCleanup( pOne = pNew ); + Gia_ManStop( pOne ); + return pNew; +} +Gia_Man_t * Acb_NtkGiaDeriveDual( Acb_Ntk_t * p ) +{ + return Acb_NtkGiaDeriveDualTargets( p, NULL ); +} + +Vec_Int_t * Acb_NtkCollectPoMuxCutpoints( Acb_Ntk_t * p ) +{ + Vec_Int_t * vCutObjs = Vec_IntAlloc( Acb_NtkCoNum(p) ); + int i, iObj, iFanin; + Acb_NtkForEachCo( p, iObj, i ) + { + iFanin = Acb_ObjFanin( p, iObj, 0 ); + while ( !Acb_ObjIsCio(p, iFanin) && Acb_ObjType(p, iFanin) == ABC_OPER_BIT_BUF ) + iFanin = Acb_ObjFanin( p, iFanin, 0 ); + if ( !Acb_ObjIsCio(p, iFanin) && Acb_ObjType(p, iFanin) == ABC_OPER_BIT_MUX ) + Vec_IntPush( vCutObjs, iFanin ); + } + if ( Vec_IntSize(vCutObjs) != Acb_NtkCoNum(p) ) + Vec_IntClear( vCutObjs ); + return vCutObjs; +} + +Vec_Int_t * Acb_NtkCollectPoMuxSelectors( Acb_Ntk_t * p, Vec_Int_t * vCutObjs ) +{ + Vec_Int_t * vSelectors = Vec_IntAlloc( 4 ); + int i, iObj, iSel; + if ( vCutObjs == NULL ) + return vSelectors; + Vec_IntForEachEntry( vCutObjs, iObj, i ) + { + assert( !Acb_ObjIsCio(p, iObj) && Acb_ObjType(p, iObj) == ABC_OPER_BIT_MUX ); + iSel = Acb_ObjFanin( p, iObj, 2 ); + if ( Vec_IntFind(vSelectors, iSel) == -1 ) + Vec_IntPush( vSelectors, iSel ); + } + return vSelectors; +} + +Vec_Int_t * Acb_NtkCollectPoMuxSelectorIds( Acb_Ntk_t * p, Vec_Int_t * vCutObjs, Vec_Int_t * vSelectors ) +{ + Vec_Int_t * vIds = Vec_IntAlloc( Acb_NtkCoNum(p) ); + int i, iObj, iSel, iSelId; + if ( vCutObjs == NULL || vSelectors == NULL ) + return vIds; + Vec_IntForEachEntry( vCutObjs, iObj, i ) + { + assert( !Acb_ObjIsCio(p, iObj) && Acb_ObjType(p, iObj) == ABC_OPER_BIT_MUX ); + iSel = Acb_ObjFanin( p, iObj, 2 ); + iSelId = Vec_IntFind( vSelectors, iSel ); + assert( iSelId >= 0 ); + Vec_IntPush( vIds, iSelId ); + } + return vIds; +} + +Vec_Int_t * Acb_NtkCollectCoDriversForSelector( Acb_Ntk_t * p, Vec_Int_t * vPoSelIds, int iSelId ) +{ + Vec_Int_t * vDrivers = Vec_IntAlloc( Acb_NtkCoNum(p) ); + int i, iObj; + Acb_NtkForEachCo( p, iObj, i ) + if ( Vec_IntEntry(vPoSelIds, i) == iSelId ) + Vec_IntPush( vDrivers, Acb_ObjFanin(p, iObj, 0) ); + return vDrivers; +} + +Vec_Int_t * Acb_NtkCollectPoIdsForSelector( Acb_Ntk_t * p, Vec_Int_t * vPoSelIds, int iSelId ) +{ + Vec_Int_t * vPos = Vec_IntAlloc( Acb_NtkCoNum(p) ); + int i, iObj; + Acb_NtkForEachCo( p, iObj, i ) + { + (void)iObj; + if ( Vec_IntEntry(vPoSelIds, i) == iSelId ) + Vec_IntPush( vPos, i ); + } + return vPos; +} + +Vec_Int_t * Acb_NtkCollectPoMuxBranchTargets( Acb_Ntk_t * p, Vec_Int_t * vCutObjs, Vec_Int_t * vPoSelIds, int iSelId, int fUseOneBranch ) +{ + Vec_Int_t * vTargets = Vec_IntAlloc( Acb_NtkCoNum(p) ); + int i, iObj; + Vec_IntForEachEntry( vCutObjs, iObj, i ) + { + if ( Vec_IntEntry(vPoSelIds, i) != iSelId ) + continue; + assert( !Acb_ObjIsCio(p, iObj) && Acb_ObjType(p, iObj) == ABC_OPER_BIT_MUX ); + Vec_IntPush( vTargets, Acb_ObjFanin(p, iObj, fUseOneBranch ? 1 : 0) ); + } + return vTargets; +} +Vec_Int_t * Acb_NtkCollectPoMuxCubeTargets( Acb_Ntk_t * p, Vec_Int_t * vCutObjs, Vec_Int_t * vPoSelIds, Vec_Int_t * vCubeVals ) +{ + Vec_Int_t * vTargets = Vec_IntAlloc( Acb_NtkCoNum(p) + 2 * Vec_IntSize(vCubeVals) ); + int i, iObj, iSelId, fUseOneBranch; + Vec_IntForEachEntry( vCutObjs, iObj, i ) + { + assert( !Acb_ObjIsCio(p, iObj) && Acb_ObjType(p, iObj) == ABC_OPER_BIT_MUX ); + iSelId = Vec_IntEntry( vPoSelIds, i ); + assert( iSelId >= 0 && iSelId < Vec_IntSize(vCubeVals) ); + fUseOneBranch = Vec_IntEntry( vCubeVals, iSelId ); + Vec_IntPush( vTargets, Acb_ObjFanin(p, iObj, fUseOneBranch ? 1 : 0) ); + } + return vTargets; +} + +int Acb_NtkCollectInternalDcControls( Acb_Ntk_t * p, Vec_Int_t ** pvDcObjs, Vec_Int_t ** pvDcCtrls, Vec_Int_t ** pvDcCtrlIds ) +{ + extern Vec_Int_t * Acb_NtkFindNodes2( Acb_Ntk_t * p ); + Vec_Int_t * vNodes = Acb_NtkFindNodes2( p ); + Vec_Int_t * vDcObjs = Vec_IntAlloc( 16 ); + Vec_Int_t * vDcCtrls = Vec_IntAlloc( 4 ); + Vec_Int_t * vDcCtrlIds = Vec_IntAlloc( 16 ); + int i, iObj, iCtrl, iCtrlId; + Vec_IntForEachEntry( vNodes, iObj, i ) + { + if ( Acb_ObjIsCio(p, iObj) || Acb_ObjType(p, iObj) != ABC_OPER_TRI ) + continue; + iCtrl = Acb_ObjFanin( p, iObj, 1 ); + iCtrlId = Vec_IntFind( vDcCtrls, iCtrl ); + if ( iCtrlId == -1 ) + { + iCtrlId = Vec_IntSize( vDcCtrls ); + Vec_IntPush( vDcCtrls, iCtrl ); + } + Vec_IntPush( vDcObjs, iObj ); + Vec_IntPush( vDcCtrlIds, iCtrlId ); + } + Vec_IntFree( vNodes ); + *pvDcObjs = vDcObjs; + *pvDcCtrls = vDcCtrls; + *pvDcCtrlIds = vDcCtrlIds; + return Vec_IntSize( vDcObjs ); +} + +Vec_Int_t * Acb_NtkCollectDcObjsForControl( Vec_Int_t * vDcObjs, Vec_Int_t * vDcCtrlIds, int iCtrlId ) +{ + Vec_Int_t * vRes = Vec_IntAlloc( Vec_IntSize(vDcObjs) ); + int i, iObj; + Vec_IntForEachEntry( vDcObjs, iObj, i ) + if ( Vec_IntEntry(vDcCtrlIds, i) == iCtrlId ) + Vec_IntPush( vRes, iObj ); + return vRes; +} + +int Acb_NtkCollectPoDcCutpoints( Acb_Ntk_t * p, Vec_Int_t ** pvDataObjs, Vec_Int_t ** pvCtrlObjs ) +{ + Vec_Int_t * vDataObjs = Vec_IntAlloc( Acb_NtkCoNum(p) ); + Vec_Int_t * vCtrlObjs = Vec_IntAlloc( Acb_NtkCoNum(p) ); + int i, iObj, iFanin; + Acb_NtkForEachCo( p, iObj, i ) + { + iFanin = Acb_ObjFanin( p, iObj, 0 ); + while ( !Acb_ObjIsCio(p, iFanin) && Acb_ObjType(p, iFanin) == ABC_OPER_BIT_BUF ) + iFanin = Acb_ObjFanin( p, iFanin, 0 ); + if ( Acb_ObjIsCio(p, iFanin) || Acb_ObjType(p, iFanin) != ABC_OPER_TRI ) + break; + Vec_IntPush( vDataObjs, Acb_ObjFanin(p, iFanin, 0) ); + Vec_IntPush( vCtrlObjs, Acb_ObjFanin(p, iFanin, 1) ); + } + if ( i != Acb_NtkCoNum(p) ) + { + Vec_IntFree( vDataObjs ); + Vec_IntFree( vCtrlObjs ); + *pvDataObjs = NULL; + *pvCtrlObjs = NULL; + return 0; + } + *pvDataObjs = vDataObjs; + *pvCtrlObjs = vCtrlObjs; + return 1; +} + +Vec_Int_t * Acb_NtkCollectCoDrivers( Acb_Ntk_t * p ) +{ + Vec_Int_t * vDrivers = Vec_IntAlloc( Acb_NtkCoNum(p) ); + int i, iObj; + Acb_NtkForEachCo( p, iObj, i ) + Vec_IntPush( vDrivers, Acb_ObjFanin(p, iObj, 0) ); + return vDrivers; +} /**Function************************************************************* Synopsis [] Description [] - + SideEffects [] SeeAlso [] @@ -437,6 +1040,16 @@ Gia_Man_t * Acb_NtkGiaDeriveMiter( Gia_Man_t * pOne, Gia_Man_t * pTwo, int Type Gia_ManAppendCo( pNew, pLitsS[1] ); } } + else if ( Type == 3 ) // raw dual-rail outputs of the two designs + { + for ( i = 0; i < Gia_ManCoNum(pOne); i += 2 ) + { + Gia_ManAppendCo( pNew, Gia_ManCo(pOne, i)->Value ); + Gia_ManAppendCo( pNew, Gia_ManCo(pOne, i+1)->Value ); + Gia_ManAppendCo( pNew, Gia_ManCo(pTwo, i)->Value ); + Gia_ManAppendCo( pNew, Gia_ManCo(pTwo, i+1)->Value ); + } + } else // comparator of the two { for ( i = 0; i < Gia_ManCoNum(pOne); i += 2 ) @@ -452,19 +1065,166 @@ Gia_Man_t * Acb_NtkGiaDeriveMiter( Gia_Man_t * pOne, Gia_Man_t * pTwo, int Type return pNew; } +Gia_Man_t * Acb_NtkGiaDeriveMiterWithSecondExtras( Gia_Man_t * pOne, Gia_Man_t * pTwo, int nExtraPairs ) +{ + Gia_Man_t * pNew, * pTemp; + Gia_Obj_t * pObj; + int i, nCompareCos = Gia_ManCoNum(pOne); + assert( Gia_ManCiNum(pOne) == Gia_ManCiNum(pTwo) ); + assert( Gia_ManCoNum(pTwo) == Gia_ManCoNum(pOne) + 2*nExtraPairs ); + pNew = Gia_ManStart( Gia_ManObjNum(pOne) + Gia_ManObjNum(pTwo) + 5*nCompareCos/2 + 2*nExtraPairs ); + pNew->pName = Abc_UtilStrsav( "miter_with_selectors" ); + pNew->pSpec = NULL; + Gia_ManHashAlloc( pNew ); + Gia_ManConst0(pOne)->Value = 0; + Gia_ManConst0(pTwo)->Value = 0; + Gia_ManForEachCi( pOne, pObj, i ) + pObj->Value = Gia_ManAppendCi( pNew ); + Gia_ManForEachCi( pTwo, pObj, i ) + pObj->Value = Gia_ManCi(pOne, i)->Value; + Gia_ManForEachAnd( pOne, pObj, i ) + pObj->Value = Gia_ManHashAnd( pNew, Gia_ObjFanin0Copy(pObj), Gia_ObjFanin1Copy(pObj) ); + Gia_ManForEachAnd( pTwo, pObj, i ) + pObj->Value = Gia_ManHashAnd( pNew, Gia_ObjFanin0Copy(pObj), Gia_ObjFanin1Copy(pObj) ); + Gia_ManForEachCo( pOne, pObj, i ) + pObj->Value = Gia_ObjFanin0Copy(pObj); + Gia_ManForEachCo( pTwo, pObj, i ) + pObj->Value = Gia_ObjFanin0Copy(pObj); + for ( i = 0; i < nCompareCos; i += 2 ) + { + int pLitsF[2] = { (int)Gia_ManCo(pOne, i)->Value, (int)Gia_ManCo(pOne, i+1)->Value }; + int pLitsS[2] = { (int)Gia_ManCo(pTwo, i)->Value, (int)Gia_ManCo(pTwo, i+1)->Value }; + Gia_ManAppendCo( pNew, Gia_ManDualCompare( pNew, pLitsF, pLitsS ) ); + } + for ( i = nCompareCos; i < Gia_ManCoNum(pTwo); i++ ) + Gia_ManAppendCo( pNew, (int)Gia_ManCo(pTwo, i)->Value ); + Gia_ManHashStop( pNew ); + pNew = Gia_ManCleanup( pTemp = pNew ); + Gia_ManStop( pTemp ); + return pNew; +} + +Gia_Man_t * Acb_GiaDeriveBranchConditionMiter( Gia_Man_t * p, int nMiterOuts, int fUseOneBranch ) +{ + Gia_Man_t * pNew, * pTemp; + Gia_Obj_t * pObj; + int i, LitSel, LitSelX, LitCond; + assert( nMiterOuts > 0 ); + assert( Gia_ManCoNum(p) == nMiterOuts + 2 ); + pNew = Gia_ManStart( Gia_ManObjNum(p) + nMiterOuts + 4 ); + pNew->pName = Abc_UtilStrsav( "branch_condition_miter" ); + Gia_ManHashAlloc( pNew ); + Gia_ManConst0(p)->Value = 0; + Gia_ManForEachCi( p, pObj, i ) + pObj->Value = Gia_ManAppendCi( pNew ); + Gia_ManForEachAnd( p, pObj, i ) + pObj->Value = Gia_ManHashAnd( pNew, Gia_ObjFanin0Copy(pObj), Gia_ObjFanin1Copy(pObj) ); + Gia_ManForEachCo( p, pObj, i ) + pObj->Value = Gia_ObjFanin0Copy(pObj); + LitSel = (int)Gia_ManCo(p, nMiterOuts)->Value; + LitSelX = (int)Gia_ManCo(p, nMiterOuts + 1)->Value; + LitCond = Gia_ManHashAnd( pNew, Abc_LitNot(LitSelX), fUseOneBranch ? LitSel : Abc_LitNot(LitSel) ); + for ( i = 0; i < nMiterOuts; i++ ) + Gia_ManAppendCo( pNew, Gia_ManHashAnd( pNew, (int)Gia_ManCo(p, i)->Value, LitCond ) ); + Gia_ManHashStop( pNew ); + pNew = Gia_ManCleanup( pTemp = pNew ); + Gia_ManStop( pTemp ); + return pNew; +} +Gia_Man_t * Acb_GiaDeriveCubeConditionMiter( Gia_Man_t * p, int nMiterOuts, Vec_Int_t * vCubeVals ) +{ + Gia_Man_t * pNew, * pTemp; + Gia_Obj_t * pObj; + int i, k, LitSel, LitSelX, LitCond = 1; + int nCubes = Vec_IntSize(vCubeVals); + assert( nMiterOuts > 0 ); + assert( Gia_ManCoNum(p) == nMiterOuts + 2*nCubes ); + pNew = Gia_ManStart( Gia_ManObjNum(p) + nMiterOuts + 4*nCubes + 4 ); + pNew->pName = Abc_UtilStrsav( "cube_condition_miter" ); + Gia_ManHashAlloc( pNew ); + Gia_ManConst0(p)->Value = 0; + Gia_ManForEachCi( p, pObj, i ) + pObj->Value = Gia_ManAppendCi( pNew ); + Gia_ManForEachAnd( p, pObj, i ) + pObj->Value = Gia_ManHashAnd( pNew, Gia_ObjFanin0Copy(pObj), Gia_ObjFanin1Copy(pObj) ); + Gia_ManForEachCo( p, pObj, i ) + pObj->Value = Gia_ObjFanin0Copy(pObj); + for ( k = 0; k < nCubes; k++ ) + { + LitSel = (int)Gia_ManCo(p, nMiterOuts + 2*k)->Value; + LitSelX = (int)Gia_ManCo(p, nMiterOuts + 2*k + 1)->Value; + LitSel = Vec_IntEntry(vCubeVals, k) ? LitSel : Abc_LitNot(LitSel); + LitCond = Gia_ManHashAnd( pNew, LitCond, Gia_ManHashAnd( pNew, Abc_LitNot(LitSelX), LitSel ) ); + } + for ( i = 0; i < nMiterOuts; i++ ) + Gia_ManAppendCo( pNew, Gia_ManHashAnd( pNew, (int)Gia_ManCo(p, i)->Value, LitCond ) ); + Gia_ManHashStop( pNew ); + pNew = Gia_ManCleanup( pTemp = pNew ); + Gia_ManStop( pTemp ); + return pNew; +} + +Gia_Man_t * Acb_NtkGiaDeriveMiterDcGuard( Gia_Man_t * pOne, Gia_Man_t * pData, Gia_Man_t * pCtrl ) +{ + Gia_Man_t * pNew, * pTemp; + Gia_Obj_t * pObj; + int i; + assert( Gia_ManCiNum(pOne) == Gia_ManCiNum(pData) ); + assert( Gia_ManCiNum(pOne) == Gia_ManCiNum(pCtrl) ); + assert( Gia_ManCoNum(pOne) == Gia_ManCoNum(pData) ); + assert( Gia_ManCoNum(pOne) == Gia_ManCoNum(pCtrl) ); + pNew = Gia_ManStart( Gia_ManObjNum(pOne) + Gia_ManObjNum(pData) + Gia_ManObjNum(pCtrl) + 6*Gia_ManCoNum(pOne)/2 ); + pNew->pName = Abc_UtilStrsav( "dc_guard_miter" ); + Gia_ManHashAlloc( pNew ); + Gia_ManConst0(pOne)->Value = 0; + Gia_ManConst0(pData)->Value = 0; + Gia_ManConst0(pCtrl)->Value = 0; + Gia_ManForEachCi( pOne, pObj, i ) + pObj->Value = Gia_ManAppendCi( pNew ); + Gia_ManForEachCi( pData, pObj, i ) + pObj->Value = Gia_ManCi(pOne, i)->Value; + Gia_ManForEachCi( pCtrl, pObj, i ) + pObj->Value = Gia_ManCi(pOne, i)->Value; + Gia_ManForEachAnd( pOne, pObj, i ) + pObj->Value = Gia_ManHashAnd( pNew, Gia_ObjFanin0Copy(pObj), Gia_ObjFanin1Copy(pObj) ); + Gia_ManForEachAnd( pData, pObj, i ) + pObj->Value = Gia_ManHashAnd( pNew, Gia_ObjFanin0Copy(pObj), Gia_ObjFanin1Copy(pObj) ); + Gia_ManForEachAnd( pCtrl, pObj, i ) + pObj->Value = Gia_ManHashAnd( pNew, Gia_ObjFanin0Copy(pObj), Gia_ObjFanin1Copy(pObj) ); + Gia_ManForEachCo( pOne, pObj, i ) + pObj->Value = Gia_ObjFanin0Copy(pObj); + Gia_ManForEachCo( pData, pObj, i ) + pObj->Value = Gia_ObjFanin0Copy(pObj); + Gia_ManForEachCo( pCtrl, pObj, i ) + pObj->Value = Gia_ObjFanin0Copy(pObj); + for ( i = 0; i < Gia_ManCoNum(pOne); i += 2 ) + { + int pLitsF[2] = { (int)Gia_ManCo(pOne, i)->Value, (int)Gia_ManCo(pOne, i+1)->Value }; + int pLitsS[2] = { (int)Gia_ManCo(pData, i)->Value, (int)Gia_ManCo(pData, i+1)->Value }; + int Ctrl0 = (int)Gia_ManCo(pCtrl, i)->Value; + int Ctrl1 = (int)Gia_ManCo(pCtrl, i+1)->Value; + pLitsS[1] = Gia_ManHashOr( pNew, pLitsS[1], Gia_ManHashOr( pNew, Ctrl0, Ctrl1 ) ); + Gia_ManAppendCo( pNew, Gia_ManDualCompare( pNew, pLitsF, pLitsS ) ); + } + Gia_ManHashStop( pNew ); + pNew = Gia_ManCleanup( pTemp = pNew ); + Gia_ManStop( pTemp ); + return pNew; +} + /**Function************************************************************* Synopsis [] Description [] - + SideEffects [] SeeAlso [] ***********************************************************************/ -void Acb_OutputFile( char * pFileName, Acb_Ntk_t * pNtkF, int * pModel ) +void Acb_OutputFile( char * pFileName, Acb_Ntk_t * pNtkF, int * pModel, int Status ) { const char * pFileName0 = pFileName? pFileName : "output"; FILE * pFile = fopen( pFileName0, "wb" ); @@ -473,9 +1233,11 @@ void Acb_OutputFile( char * pFileName, Acb_Ntk_t * pNtkF, int * pModel ) printf( "Cannot open results file \"%s\".\n", pFileName0 ); return; } - if ( pModel == NULL ) + if ( Status == ACB_XEC_UNDEC ) + fprintf( pFile, "UNDECIDED\n" ); + else if ( pModel == NULL ) fprintf( pFile, "EQ\n" ); - else + else { /* NEQ @@ -491,7 +1253,7 @@ void Acb_OutputFile( char * pFileName, Acb_Ntk_t * pNtkF, int * pModel ) fclose( pFile ); printf( "Produced output file \"%s\".\n\n", pFileName0 ); } -int * Acb_NtkSolve( Gia_Man_t * p ) +int * Acb_NtkSolve( Gia_Man_t * p, int fVerbose, int * pStatus ) { extern Abc_Ntk_t * Abc_NtkFromAigPhase( Aig_Man_t * pMan ); Aig_Man_t * pMan = Gia_ManToAig( p, 0 ); @@ -499,7 +1261,7 @@ int * Acb_NtkSolve( Gia_Man_t * p ) Prove_Params_t Params, * pParams = &Params; Prove_ParamsSetDefault( pParams ); pParams->fUseRewriting = 1; - pParams->fVerbose = 0; + pParams->fVerbose = fVerbose; Aig_ManStop( pMan ); if ( pNtkTemp ) { @@ -508,11 +1270,3302 @@ int * Acb_NtkSolve( Gia_Man_t * p ) int * pModel = pNtkTemp->pModel; pNtkTemp->pModel = NULL; Abc_NtkDelete( pNtkTemp ); + *pStatus = RetValue; printf( "The networks are %s. ", RetValue == 1 ? "equivalent" : (RetValue == 0 ? "NOT equivalent" : "UNDECIDED") ); Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); if ( RetValue == 0 ) return pModel; } + *pStatus = ACB_XEC_UNDEC; + return NULL; +} +int * Acb_NtkSolveIvyPrecheck( Gia_Man_t * p, int fVerbose, int * pStatus ) +{ + extern Abc_Ntk_t * Abc_NtkFromAigPhase( Aig_Man_t * pMan ); + Aig_Man_t * pMan = Gia_ManToAig( p, 0 ); + Abc_Ntk_t * pNtkTemp = Abc_NtkFromAigPhase( pMan ); + Prove_Params_t Params, * pParams = &Params; + Prove_ParamsSetDefault( pParams ); + pParams->fUseFraiging = 1; + pParams->fUseRewriting = 1; + pParams->fUseBdds = 0; + pParams->nItersMax = 6; + pParams->nMiteringLimitStart = 5000; + pParams->nMiteringLimitMulti = 2.0; + pParams->nFraigingLimitStart = 2; + pParams->nFraigingLimitMulti = 8.0; + pParams->nMiteringLimitLast = 0; + pParams->nTotalBacktrackLimit = 750000; + pParams->fVerbose = fVerbose; + Aig_ManStop( pMan ); + if ( pNtkTemp ) + { + abctime clk = Abc_Clock(); + int RetValue; + int * pModel; + if ( fVerbose ) + printf( "Trying bounded Ivy/FRAIG precheck before CaDiCaL: And = %d. PO = %d. total conflict limit = %d.\n", + Gia_ManAndNum(p), Gia_ManCoNum(p), (int)pParams->nTotalBacktrackLimit ); + RetValue = Abc_NtkIvyProve( &pNtkTemp, pParams ); + pModel = pNtkTemp->pModel; + pNtkTemp->pModel = NULL; + Abc_NtkDelete( pNtkTemp ); + *pStatus = RetValue; + printf( "The networks are %s by bounded Ivy/FRAIG precheck. ", + RetValue == 1 ? "equivalent" : (RetValue == 0 ? "NOT equivalent" : "UNDECIDED") ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + if ( RetValue == 0 ) + return pModel; + ABC_FREE( pModel ); + return NULL; + } + *pStatus = ACB_XEC_UNDEC; + return NULL; +} +int * Acb_NtkSolveNormalPrecheck( Gia_Man_t * p, int fVerbose, int * pStatus, int nBacktrackLimit ) +{ + extern Abc_Ntk_t * Abc_NtkFromAigPhase( Aig_Man_t * pMan ); + Aig_Man_t * pMan = Gia_ManToAig( p, 0 ); + Abc_Ntk_t * pNtkTemp = Abc_NtkFromAigPhase( pMan ); + Prove_Params_t Params, * pParams = &Params; + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + Prove_ParamsSetDefault( pParams ); + pParams->fUseRewriting = 1; + pParams->nTotalBacktrackLimit = nBacktrackLimit; + pParams->fVerbose = fVerbose; + Aig_ManStop( pMan ); + if ( pNtkTemp ) + { + abctime clk = Abc_Clock(); + int RetValue; + int * pModel; + if ( fVerbose ) + printf( "Trying normal XEC precheck before CaDiCaL-specific UNSAT passes: And = %d. PO = %d. backtrack limit = %d.\n", + Gia_ManAndNum(p), Gia_ManCoNum(p), nBacktrackLimit ); + RetValue = Abc_NtkIvyProve( &pNtkTemp, pParams ); + pModel = pNtkTemp->pModel; + pNtkTemp->pModel = NULL; + Abc_NtkDelete( pNtkTemp ); + if ( pStatus ) + *pStatus = RetValue; + printf( "The networks are %s by normal XEC precheck. ", + RetValue == 1 ? "equivalent" : (RetValue == 0 ? "NOT equivalent" : "UNDECIDED") ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + if ( RetValue == 0 ) + return pModel; + ABC_FREE( pModel ); + return NULL; + } + return NULL; +} +Gia_Man_t * Acb_NtkFraigEquivReduce( Gia_Man_t * p, int fVerbose, char * pLabel, char * pPhase, int nWords, int nConfLimit, int nSatVarMax, int nMinGain ) +{ + Dch_Pars_t Pars, * pPars = &Pars; + Gia_Man_t * pWork = NULL, * pNew = NULL, * pTemp = NULL; + int nAndStart = Gia_ManAndNum( p ); + abctime clk = Abc_Clock(); + if ( Gia_ManCoNum(p) == 0 || nAndStart < 1000 ) + return NULL; + Dch_ManSetDefaultParams( pPars ); + pPars->nWords = nWords; + pPars->nBTLimit = nConfLimit; + pPars->nSatVarMax = nSatVarMax; + pPars->fSynthesis = 0; + pPars->fPolarFlip = 1; + pPars->fSimulateTfo= 1; + pPars->fVerbose = 0; + pWork = Gia_ManDup( p ); + if ( pWork == NULL ) + return NULL; + if ( fVerbose ) + printf( "%s %s FRAIG equivalence reduction: And = %d. PO = %d. words = %d. node-conf = %d. sat-var-max = %d.\n", + pLabel ? pLabel : "XEC", pPhase ? pPhase : "structural", nAndStart, Gia_ManCoNum(p), nWords, nConfLimit, nSatVarMax ); + pNew = Gia_ManFraigSweepSimple( pWork, pPars ); + Gia_ManStop( pWork ); + if ( pNew == NULL ) + return NULL; + pTemp = Gia_ManCompress2( pNew, 1, 0 ); + if ( pTemp ) + { + Gia_ManStop( pNew ); + pNew = pTemp; + } + if ( fVerbose ) + { + printf( "%s %s FRAIG equivalence reduction: And = %d -> %d. Lev = %d -> %d. ", + pLabel ? pLabel : "XEC", pPhase ? pPhase : "structural", + nAndStart, Gia_ManAndNum(pNew), Gia_ManLevelNum(p), Gia_ManLevelNum(pNew) ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + if ( Gia_ManCoNum(pNew) != Gia_ManCoNum(p) || Gia_ManAndNum(pNew) >= nAndStart - nMinGain ) + { + if ( fVerbose && Gia_ManCoNum(pNew) == Gia_ManCoNum(p) ) + printf( "%s %s FRAIG equivalence reduction skipped because the proven merge gain is too small.\n", + pLabel ? pLabel : "XEC", pPhase ? pPhase : "structural" ); + Gia_ManStop( pNew ); + return NULL; + } + return pNew; +} +int Acb_NtkObjIsConstTypeThroughBuf( Acb_Ntk_t * p, int iObj, Acb_ObjType_t Type ) +{ + while ( !Acb_ObjIsCio(p, iObj) && Acb_ObjType(p, iObj) == ABC_OPER_BIT_BUF ) + iObj = Acb_ObjFanin(p, iObj, 0); + return !Acb_ObjIsCio(p, iObj) && Acb_ObjType(p, iObj) == Type; +} +int Acb_NtkDcObjIsConstXSeed( Acb_Ntk_t * p, int iObj ) +{ + if ( iObj <= 0 || Acb_ObjIsCio(p, iObj) || Acb_ObjType(p, iObj) != ABC_OPER_TRI ) + return 0; + if ( Acb_ObjFaninNum(p, iObj) != 2 ) + return 0; + return Acb_NtkObjIsConstTypeThroughBuf( p, Acb_ObjFanin(p, iObj, 0), ABC_OPER_CONST_F ) && + Acb_NtkObjIsConstTypeThroughBuf( p, Acb_ObjFanin(p, iObj, 1), ABC_OPER_CONST_T ); +} +int Acb_NtkAllDcObjsAreConstXSeeds( Acb_Ntk_t * p, Vec_Int_t * vDcObjs ) +{ + int i, iObj; + if ( vDcObjs == NULL || Vec_IntSize(vDcObjs) == 0 ) + return 0; + Vec_IntForEachEntry( vDcObjs, iObj, i ) + if ( !Acb_NtkDcObjIsConstXSeed(p, iObj) ) + return 0; + return 1; +} +int * Acb_NtkSolveConstXSeedCanonical( Acb_Ntk_t * pNtkF, Acb_Ntk_t * pNtkG, Vec_Int_t * vDcObjsG, int fVerbose, int * pStatus, int nSatTimeLimit ) +{ + Vec_Int_t * vTargetsF = NULL, * vTargetsG = NULL; + Gia_Man_t * pGiaF = NULL, * pGiaG = NULL, * pGia = NULL, * pTemp = NULL; + int Status = ACB_XEC_UNDEC, * pModel = NULL; + abctime clk = Abc_Clock(); + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + if ( !Acb_NtkAllDcObjsAreConstXSeeds(pNtkG, vDcObjsG) ) + return NULL; + vTargetsF = Acb_NtkCollectCoDrivers( pNtkF ); + vTargetsG = Acb_NtkCollectCoDrivers( pNtkG ); + pGiaF = Acb_NtkGiaDeriveDualTargets( pNtkF, vTargetsF ); + pGiaG = Acb_NtkGiaDeriveDualTargetsForceZero( pNtkG, vTargetsG ); + pGia = Acb_NtkGiaDeriveMiter( pGiaF, pGiaG, 2 ); + if ( fVerbose ) + printf( "Trying constant-X seed canonical proof: DC seeds = %d. And = %d. PO = %d. limit = %d sec.\n", + Vec_IntSize(vDcObjsG), Gia_ManAndNum(pGia), Gia_ManCoNum(pGia), nSatTimeLimit ); + if ( Gia_ManAndNum(pGia) > 5000 ) + { + pTemp = Gia_ManCompress2( pGia, 1, 0 ); + if ( pTemp ) + { + if ( fVerbose ) + printf( "Constant-X seed canonical compression: And = %d -> %d. PO = %d.\n", + Gia_ManAndNum(pGia), Gia_ManAndNum(pTemp), Gia_ManCoNum(pTemp) ); + Gia_ManStop( pGia ); + pGia = pTemp; + pTemp = NULL; + } + } + if ( Gia_ManAndNum(pGia) > 8000 ) + { + pTemp = Acb_NtkFraigEquivReduce( pGia, fVerbose, + "Constant-X seed canonical proof", "canonical dual", 32, 300, 12000, + Abc_MaxInt( 50, Gia_ManAndNum(pGia) / 200 ) ); + if ( pTemp ) + { + Gia_ManStop( pGia ); + pGia = pTemp; + pTemp = NULL; + } + } + if ( Gia_ManCoNum(pGia) == 0 || Acb_GiaAllPosConst0(pGia) ) + { + Status = ACB_XEC_EQ; + if ( fVerbose ) + printf( "Constant-X seed canonical proof: all miter outputs are constant-0 after canonicalization.\n" ); + } + else + pModel = Acb_NtkSolveCadicalLimit( pGia, 0, fVerbose, &Status, nSatTimeLimit, + "constant-X seed canonical CaDiCaL", 0 ); + if ( pStatus ) + *pStatus = Status; + if ( Status == ACB_XEC_EQ ) + { + printf( "The networks are equivalent by constant-X seed canonical proof. " ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + else if ( fVerbose ) + { + printf( "The networks are %s by constant-X seed canonical proof. ", + Status == ACB_XEC_NEQ ? "NOT equivalent" : "UNDECIDED" ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + Gia_ManStop( pGia ); + Gia_ManStop( pGiaG ); + Gia_ManStop( pGiaF ); + Vec_IntFree( vTargetsG ); + Vec_IntFree( vTargetsF ); + return pModel; +} +Gia_Man_t * Acb_NtkBranchSweepReduce( Gia_Man_t * p, int fVerbose, char * pLabel ) +{ + Cec_ParFra_t Pars, * pPars = &Pars; + Gia_Man_t * pNew = NULL, * pBest = NULL, * pFraig = NULL; + abctime clk = Abc_Clock(); + int nAndStart = Gia_ManAndNum( p ); + if ( Gia_ManCoNum(p) < 8 || nAndStart < 8000 || nAndStart > 60000 ) + return NULL; + Cec_ManFraSetDefaultParams( pPars ); + pPars->nWords = 64; + pPars->nRounds = 8; + pPars->nItersMax = 6; + pPars->nBTLimit = 800; + pPars->nBTLimitPo = 0; + pPars->TimeLimit = 90; + pPars->fCheckMiter = 0; + pPars->fSatSweeping = 1; + pPars->fUseCones = 1; + pPars->fRewriting = 1; + pPars->fVerbose = 0; + if ( fVerbose ) + printf( "%s branch SAT-sweeping reduction: And = %d. PO = %d. limit = %d sec. node-conf = %d.\n", + pLabel ? pLabel : "XEC", nAndStart, Gia_ManCoNum(p), pPars->TimeLimit, pPars->nBTLimit ); + pNew = Cec_ManSatSweeping( p, pPars, 1 ); + if ( pNew == NULL ) + { + if ( fVerbose ) + printf( "%s branch SAT-sweeping reduction produced no network.\n", pLabel ? pLabel : "XEC" ); + } + else if ( fVerbose ) + { + printf( "%s branch SAT-sweeping reduction: And = %d -> %d. Lev = %d -> %d. ", + pLabel ? pLabel : "XEC", nAndStart, Gia_ManAndNum(pNew), Gia_ManLevelNum(p), Gia_ManLevelNum(pNew) ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + if ( pNew && Gia_ManCoNum(pNew) == Gia_ManCoNum(p) && Gia_ManAndNum(pNew) < nAndStart ) + pBest = pNew, pNew = NULL; + if ( pNew ) + Gia_ManStop( pNew ); + if ( nAndStart >= 15000 ) + pFraig = Acb_NtkFraigEquivReduce( pBest ? pBest : p, fVerbose, pLabel, "branch", 32, 300, 12000, Abc_MaxInt( 50, nAndStart / 200 ) ); + if ( pFraig ) + { + if ( pBest ) + Gia_ManStop( pBest ); + pBest = pFraig; + } + if ( pBest == NULL || Gia_ManAndNum(pBest) >= nAndStart - Abc_MaxInt( 50, nAndStart / 100 ) ) + { + if ( fVerbose ) + printf( "%s branch structural reduction skipped because reduction is too small.\n", pLabel ? pLabel : "XEC" ); + if ( pBest ) + Gia_ManStop( pBest ); + return NULL; + } + return pBest; +} +int Acb_GiaAndObligationsUniq( Gia_Man_t * p, Vec_Int_t * vReq ) +{ + Vec_Int_t * vSeen = Vec_IntStart( Gia_ManObjNum(p) ); + int i, Lit, Var, Sign, Prev, nOut = 0; + Vec_IntForEachEntry( vReq, Lit, i ) + { + if ( Lit < 2 ) + continue; + Var = Abc_Lit2Var( Lit ); + Sign = Abc_LitIsCompl( Lit ) ? 2 : 1; + Prev = Vec_IntEntry( vSeen, Var ); + if ( Prev && Prev != Sign ) + { + Vec_IntFree( vSeen ); + return 1; + } + if ( Prev == Sign ) + continue; + Vec_IntWriteEntry( vSeen, Var, Sign ); + Vec_IntWriteEntry( vReq, nOut++, Lit ); + } + Vec_IntShrink( vReq, nOut ); + Vec_IntFree( vSeen ); + return 0; +} +int Acb_GiaCollectLinearLit_rec( Gia_Man_t * p, int Lit, word * pRow, int nWords, int * pConst, int Depth ) +{ + Gia_Obj_t * pObj, * pFan0 = NULL, * pFan1 = NULL; + int iVar, Lit0, Lit1; + if ( Depth > ACB_XEC_RECURSION_LIMIT ) + return 0; + if ( Lit < 2 ) + { + if ( Lit == 1 ) + *pConst ^= 1; + return 1; + } + pObj = Gia_ManObj( p, Abc_Lit2Var(Lit) ); + if ( Abc_LitIsCompl(Lit) ) + *pConst ^= 1; + if ( Gia_ObjIsCi(pObj) ) + { + iVar = Gia_ObjCioId(pObj); + pRow[iVar >> 6] ^= ((word)1) << (iVar & 63); + return 1; + } + if ( Gia_ObjIsXor(pObj) ) + { + if ( !Acb_GiaCollectLinearLit_rec( p, Gia_ObjFaninLit0p(p, pObj), pRow, nWords, pConst, Depth + 1 ) ) + return 0; + if ( !Acb_GiaCollectLinearLit_rec( p, Gia_ObjFaninLit1p(p, pObj), pRow, nWords, pConst, Depth + 1 ) ) + return 0; + return 1; + } + if ( Gia_ObjRecognizeExor( pObj, &pFan0, &pFan1 ) ) + { + Lit0 = Abc_Var2Lit( Gia_ObjId(p, Gia_Regular(pFan0)), Gia_IsComplement(pFan0) ); + Lit1 = Abc_Var2Lit( Gia_ObjId(p, Gia_Regular(pFan1)), Gia_IsComplement(pFan1) ); + if ( !Acb_GiaCollectLinearLit_rec( p, Lit0, pRow, nWords, pConst, Depth + 1 ) ) + return 0; + if ( !Acb_GiaCollectLinearLit_rec( p, Lit1, pRow, nWords, pConst, Depth + 1 ) ) + return 0; + return 1; + } + return 0; +} +int Acb_GiaSolveLinearObligations( Gia_Man_t * p, Vec_Int_t * vReq, int fVerbose ) +{ + int nVars, nWords, nRows, i, k, Lit, Const, Pivot, PivotRow, nRank = 0, fLinear = 1; + word * pRows = NULL; + unsigned char * pRhs = NULL; + if ( p == NULL || vReq == NULL || Vec_IntSize(vReq) == 0 || Gia_ManCiNum(p) > 4096 ) + return ACB_XEC_UNDEC; + nVars = Gia_ManCiNum(p); + nWords = Abc_BitWordNum(nVars); + nRows = Vec_IntSize(vReq); + pRows = ABC_CALLOC( word, nRows * nWords ); + pRhs = ABC_CALLOC( unsigned char, nRows ); + Vec_IntForEachEntry( vReq, Lit, i ) + { + Const = 0; + if ( !Acb_GiaCollectLinearLit_rec( p, Lit, pRows + i * nWords, nWords, &Const, 0 ) ) + { + if ( fVerbose ) + printf( "Required-literal XOR-linear proof: obligation %d is non-linear; skipping.\n", i ); + fLinear = 0; + break; + } + pRhs[i] = Const ^ 1; + } + for ( Pivot = 0; fLinear && Pivot < nVars && nRank < nRows; Pivot++ ) + { + word Mask = ((word)1) << (Pivot & 63); + int WordId = Pivot >> 6; + PivotRow = -1; + for ( i = nRank; i < nRows; i++ ) + if ( pRows[i*nWords + WordId] & Mask ) + { + PivotRow = i; + break; + } + if ( PivotRow < 0 ) + continue; + if ( PivotRow != nRank ) + { + for ( k = 0; k < nWords; k++ ) + { + word Temp = pRows[nRank*nWords + k]; + pRows[nRank*nWords + k] = pRows[PivotRow*nWords + k]; + pRows[PivotRow*nWords + k] = Temp; + } + ABC_SWAP( unsigned char, pRhs[nRank], pRhs[PivotRow] ); + } + for ( i = 0; i < nRows; i++ ) + { + if ( i == nRank || !(pRows[i*nWords + WordId] & Mask) ) + continue; + for ( k = WordId; k < nWords; k++ ) + pRows[i*nWords + k] ^= pRows[nRank*nWords + k]; + pRhs[i] ^= pRhs[nRank]; + } + nRank++; + } + for ( i = 0; fLinear && i < nRows; i++ ) + { + int fZero = 1; + for ( k = 0; k < nWords; k++ ) + if ( pRows[i*nWords + k] ) + { + fZero = 0; + break; + } + if ( fZero && pRhs[i] ) + { + if ( fVerbose ) + printf( "Required-literal XOR-linear proof: UNSAT. equations = %d. rank = %d.\n", nRows, nRank ); + ABC_FREE( pRows ); + ABC_FREE( pRhs ); + return ACB_XEC_EQ; + } + } + if ( fLinear && fVerbose ) + printf( "Required-literal XOR-linear proof: consistent. equations = %d. rank = %d.\n", nRows, nRank ); + ABC_FREE( pRows ); + ABC_FREE( pRhs ); + return ACB_XEC_UNDEC; +} +Gia_Man_t * Acb_GiaDupWithObligationOutputs( Gia_Man_t * p, Vec_Int_t * vReq ) +{ + Gia_Man_t * pNew, * pTemp; + Gia_Obj_t * pObj; + int i, Lit; + pNew = Gia_ManStart( Gia_ManObjNum(p) + Vec_IntSize(vReq) + 100 ); + pNew->pName = Abc_UtilStrsav( "and_obligations" ); + Gia_ManHashAlloc( pNew ); + Gia_ManFillValue( p ); + Gia_ManConst0(p)->Value = 0; + Gia_ManForEachCi( p, pObj, i ) + pObj->Value = Gia_ManAppendCi( pNew ); + Gia_ManForEachAnd( p, pObj, i ) + pObj->Value = Gia_ManHashAnd( pNew, Gia_ObjFanin0Copy(pObj), Gia_ObjFanin1Copy(pObj) ); + Vec_IntForEachEntry( vReq, Lit, i ) + Gia_ManAppendCo( pNew, Gia_ObjLitCopy(p, Lit) ); + Gia_ManHashStop( pNew ); + pNew = Gia_ManCleanup( pTemp = pNew ); + Gia_ManStop( pTemp ); + return pNew; +} +int Acb_CnfWriteIntoCadical( cadical_solver * pSat, Cnf_Dat_t * pCnf ) +{ + int i, * pBeg, * pEnd; + if ( pSat == NULL || pCnf == NULL ) + return 0; + cadical_solver_setnvars( pSat, pCnf->nVars ); + Cnf_CnfForClause( pCnf, pBeg, pEnd, i ) + if ( !cadical_solver_addclause( pSat, pBeg, pEnd ) ) + return 0; + return 1; +} +int Acb_GiaSolveObligationListUnit( Gia_Man_t * p, Vec_Int_t * vReq, int fVerbose, int nSatTimeLimit, char * pLabel ) +{ + Gia_Man_t * pObl = NULL; + Aig_Man_t * pMan = NULL; + Cnf_Dat_t * pCnf = NULL; + cadical_solver * pSat = NULL; + int i, Lit, Ret, Status = ACB_XEC_UNDEC; + abctime clk = Abc_Clock(); + (void)nSatTimeLimit; + if ( p == NULL || vReq == NULL || Vec_IntSize(vReq) == 0 ) + return ACB_XEC_UNDEC; + if ( Acb_GiaAndObligationsUniq( p, vReq ) ) + return ACB_XEC_EQ; + Status = Acb_GiaSolveLinearObligations( p, vReq, 0 ); + if ( Status == ACB_XEC_EQ ) + return Status; + pObl = Acb_GiaDupWithObligationOutputs( p, vReq ); + pMan = pObl ? Gia_ManToAig( pObl, 0 ) : NULL; + pCnf = pMan ? Cnf_Derive( pMan, Aig_ManCoNum(pMan) ) : NULL; + pSat = pCnf ? cadical_solver_new() : NULL; + if ( pCnf == NULL || pSat == NULL || !Acb_CnfWriteIntoCadical(pSat, pCnf) ) + goto cleanup; + for ( i = 0; i < Gia_ManCoNum(pObl); i++ ) + { + Ret = Acb_CnfCoDriverLit( pCnf, i, &Lit ); + if ( Ret < 0 ) + { + Status = Ret == -1 ? ACB_XEC_EQ : ACB_XEC_UNDEC; + goto cleanup; + } + if ( Ret > 0 && !cadical_solver_addclause( pSat, &Lit, &Lit + 1 ) ) + { + Status = ACB_XEC_EQ; + goto cleanup; + } + } + Ret = cadical_solver_solve( pSat, NULL, NULL, 0, 0, 0, 0 ); + Status = Ret == -1 ? ACB_XEC_EQ : ACB_XEC_UNDEC; +cleanup: + if ( fVerbose ) + { + printf( "%s: %s. obligations = %d. ", + pLabel ? pLabel : "Exact obligation unit branch", + Status == ACB_XEC_EQ ? "UNSAT" : "UNDECIDED", Vec_IntSize(vReq) ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + if ( pSat ) + cadical_solver_delete( pSat ); + if ( pCnf ) + Cnf_DataFree( pCnf ); + if ( pMan ) + Aig_ManStop( pMan ); + if ( pObl ) + Gia_ManStop( pObl ); + return Status; +} +typedef struct Acb_SplitPoOrder_t_ Acb_SplitPoOrder_t; +struct Acb_SplitPoOrder_t_ +{ + int iPo; + int nAnds; +}; +void Acb_NtkSortSplitOutputsLimit( Gia_Man_t * p, Acb_SplitPoOrder_t * pOrder, int nPos ); +void Acb_NtkSortSplitOutputs( Gia_Man_t * p, Acb_SplitPoOrder_t * pOrder ); +int * Acb_NtkSolveCadicalPoSweepLabel( Gia_Man_t * p, int fVerbose, int * pStatus, int nSatTimeLimit, int nPoConfLimit, char * pLabel, int fStopOnUndec ); +int Acb_GiaMarkCone_rec( Gia_Man_t * p, Gia_Obj_t * pObj, Vec_Int_t * vMarks, int Mark ) +{ + int Id; + if ( Gia_ObjIsConst0(pObj) || Gia_ObjIsCi(pObj) ) + return 0; + assert( Gia_ObjIsAnd(pObj) ); + Id = Gia_ObjId( p, pObj ); + if ( Vec_IntEntry(vMarks, Id) == Mark ) + return 0; + Vec_IntWriteEntry( vMarks, Id, Mark ); + return 1 + Acb_GiaMarkCone_rec( p, Gia_ObjFanin0(pObj), vMarks, Mark ) + + Acb_GiaMarkCone_rec( p, Gia_ObjFanin1(pObj), vMarks, Mark ); +} +int Acb_GiaCountConeOverlap_rec( Gia_Man_t * p, Gia_Obj_t * pObj, Vec_Int_t * vMarks, int Mark ) +{ + int Id; + if ( Gia_ObjIsTravIdCurrent(p, pObj) ) + return 0; + Gia_ObjSetTravIdCurrent( p, pObj ); + if ( Gia_ObjIsConst0(pObj) || Gia_ObjIsCi(pObj) ) + return 0; + assert( Gia_ObjIsAnd(pObj) ); + Id = Gia_ObjId( p, pObj ); + return (int)(Vec_IntEntry(vMarks, Id) == Mark) + + Acb_GiaCountConeOverlap_rec( p, Gia_ObjFanin0(pObj), vMarks, Mark ) + + Acb_GiaCountConeOverlap_rec( p, Gia_ObjFanin1(pObj), vMarks, Mark ); +} +void Acb_GiaCollectFrontier_rec( Gia_Man_t * p, Gia_Obj_t * pObj, int LevelCut, Vec_Int_t * vFrontier ) +{ + if ( Gia_ObjIsConst0(pObj) || Gia_ObjIsCi(pObj) ) + return; + assert( Gia_ObjIsAnd(pObj) ); + if ( Gia_ObjIsTravIdCurrent(p, pObj) ) + return; + Gia_ObjSetTravIdCurrent( p, pObj ); + if ( Gia_ObjLevel(p, pObj) <= LevelCut ) + { + Vec_IntPush( vFrontier, Gia_ObjId(p, pObj) ); + return; + } + Acb_GiaCollectFrontier_rec( p, Gia_ObjFanin0(pObj), LevelCut, vFrontier ); + Acb_GiaCollectFrontier_rec( p, Gia_ObjFanin1(pObj), LevelCut, vFrontier ); +} +Vec_Int_t * Acb_GiaCollectPoFrontier( Gia_Man_t * p, int iPo, int * pLevelRoot, int * pLevelCut ) +{ + Gia_Obj_t * pRoot = Gia_ObjFanin0( Gia_ManCo(p, iPo) ); + Vec_Int_t * vFrontier = Vec_IntAlloc( 64 ); + int LevelRoot = 0, LevelCut = 0; + Gia_ManLevelNum( p ); + if ( !Gia_ObjIsConst0(pRoot) && !Gia_ObjIsCi(pRoot) ) + { + LevelRoot = Gia_ObjLevel( p, pRoot ); + LevelCut = Abc_MaxInt( 1, LevelRoot / 2 ); + Gia_ManIncrementTravId( p ); + Gia_ObjSetTravIdCurrent( p, Gia_ManConst0(p) ); + Acb_GiaCollectFrontier_rec( p, pRoot, LevelCut, vFrontier ); + } + if ( pLevelRoot ) + *pLevelRoot = LevelRoot; + if ( pLevelCut ) + *pLevelCut = LevelCut; + return vFrontier; +} +void Acb_GiaPrintHardPoFrontier( Gia_Man_t * p, int iPo, int fVerbose ) +{ + Vec_Int_t * vFrontier; + int i, iObj, LevelRoot, LevelCut, nFront, nPrint; + if ( !fVerbose ) + return; + vFrontier = Acb_GiaCollectPoFrontier( p, iPo, &LevelRoot, &LevelCut ); + nFront = Vec_IntSize( vFrontier ); + printf( " frontier: root level = %d, cut level = %d, candidates = %d", + LevelRoot, LevelCut, nFront ); + nPrint = Abc_MinInt( nFront, 8 ); + if ( nPrint ) + { + printf( ", sample obj/level/cone =" ); + for ( i = 0; i < nPrint; i++ ) + { + iObj = Vec_IntEntry( vFrontier, i ); + printf( " %d/%d/%d", iObj, Gia_ObjLevelId(p, iObj), Gia_ManConeSize(p, &iObj, 1) ); + } + } + printf( ".\n" ); + Vec_IntFree( vFrontier ); +} +int Acb_GiaDupPoFrontier_rec( Gia_Man_t * p, Gia_Man_t * pNew, Gia_Obj_t * pObj, Vec_Int_t * vFrontMarks, int * pNFrontPis ) +{ + int Id, Lit0, Lit1; + if ( Gia_ObjIsConst0(pObj) ) + return 0; + if ( ~pObj->Value ) + return pObj->Value; + Id = Gia_ObjId( p, pObj ); + if ( Gia_ObjIsCi(pObj) || Vec_IntEntry(vFrontMarks, Id) ) + { + (*pNFrontPis)++; + return pObj->Value = Gia_ManAppendCi( pNew ); + } + assert( Gia_ObjIsAnd(pObj) ); + Lit0 = Acb_GiaDupPoFrontier_rec( p, pNew, Gia_ObjFanin0(pObj), vFrontMarks, pNFrontPis ); + Lit1 = Acb_GiaDupPoFrontier_rec( p, pNew, Gia_ObjFanin1(pObj), vFrontMarks, pNFrontPis ); + return pObj->Value = Gia_ManHashAnd( pNew, Abc_LitNotCond(Lit0, Gia_ObjFaninC0(pObj)), Abc_LitNotCond(Lit1, Gia_ObjFaninC1(pObj)) ); +} +Gia_Man_t * Acb_GiaDerivePoFrontierAbstract( Gia_Man_t * p, int iPo, int LevelCut, int * pNFrontier, int * pNFrontPis ) +{ + Gia_Man_t * pNew, * pTemp; + Gia_Obj_t * pRoot = Gia_ObjFanin0( Gia_ManCo(p, iPo) ); + Vec_Int_t * vFrontier, * vMarks; + int i, iObj, Lit, LevelRoot = 0, nFrontPis = 0; + assert( iPo >= 0 && iPo < Gia_ManCoNum(p) ); + Gia_ManLevelNum( p ); + if ( !Gia_ObjIsConst0(pRoot) && !Gia_ObjIsCi(pRoot) ) + LevelRoot = Gia_ObjLevel( p, pRoot ); + if ( LevelCut <= 0 || LevelCut >= LevelRoot ) + LevelCut = Abc_MaxInt( 1, LevelRoot / 2 ); + Gia_ManIncrementTravId( p ); + Gia_ObjSetTravIdCurrent( p, Gia_ManConst0(p) ); + vFrontier = Vec_IntAlloc( 64 ); + Acb_GiaCollectFrontier_rec( p, pRoot, LevelCut, vFrontier ); + vMarks = Vec_IntStart( Gia_ManObjNum(p) ); + Vec_IntForEachEntry( vFrontier, iObj, i ) + Vec_IntWriteEntry( vMarks, iObj, 1 ); + Gia_ManFillValue( p ); + Gia_ManConst0(p)->Value = 0; + pNew = Gia_ManStart( Abc_MaxInt( 1000, 2 * Vec_IntSize(vFrontier) + 100 ) ); + pNew->pName = Abc_UtilStrsav( "frontier_abs" ); + Gia_ManHashStart( pNew ); + Lit = Acb_GiaDupPoFrontier_rec( p, pNew, pRoot, vMarks, &nFrontPis ); + Lit = Abc_LitNotCond( Lit, Gia_ObjFaninC0(Gia_ManCo(p, iPo)) ); + Gia_ManAppendCo( pNew, Lit ); + Gia_ManHashStop( pNew ); + pNew = Gia_ManCleanup( pTemp = pNew ); + Gia_ManStop( pTemp ); + if ( pNFrontier ) + *pNFrontier = Vec_IntSize( vFrontier ); + if ( pNFrontPis ) + *pNFrontPis = nFrontPis; + Vec_IntFree( vMarks ); + Vec_IntFree( vFrontier ); + return pNew; +} +int Acb_NtkTryFrontierAbstractPo( Gia_Man_t * p, int iPo, int fVerbose, int nSatTimeLimit, int iSelId, int fUseOneBranch, char * pLabel ) +{ + Gia_Obj_t * pRoot = Gia_ObjFanin0( Gia_ManCo(p, iPo) ); + int Cuts[3], c, Status = -1, nFrontier = 0, nFrontPis = 0; + int LevelRoot = 0, nAndBest = -1; + abctime clk = Abc_Clock(); + if ( nSatTimeLimit <= 0 ) + return -1; + Gia_ManLevelNum( p ); + if ( Gia_ObjIsConst0(pRoot) ) + return Gia_ObjFaninC0(Gia_ManCo(p, iPo)) ? -1 : 1; + if ( Gia_ObjIsCi(pRoot) ) + return -1; + LevelRoot = Gia_ObjLevel( p, pRoot ); + Cuts[0] = Abc_MaxInt( 1, LevelRoot / 2 ); + Cuts[1] = Abc_MaxInt( 1, (2 * LevelRoot) / 3 ); + Cuts[2] = Abc_MaxInt( 1, LevelRoot / 3 ); + for ( c = 0; c < 3; c++ ) + { + Gia_Man_t * pAbs, * pOpt = NULL, * pSolve; + int nLimit = Abc_MinInt( nSatTimeLimit, c == 0 ? 30 : 15 ); + if ( c && Cuts[c] == Cuts[c-1] ) + continue; + pAbs = Acb_GiaDerivePoFrontierAbstract( p, iPo, Cuts[c], &nFrontier, &nFrontPis ); + nAndBest = Gia_ManAndNum( pAbs ); + pOpt = nAndBest > 100 ? Gia_ManCompress2( pAbs, 1, 0 ) : NULL; + pSolve = pOpt ? pOpt : pAbs; + if ( fVerbose ) + printf( "%s frontier abstraction: selector %d branch %d output %d. level %d/%d, frontier = %d, abs PIs = %d, And = %d -> %d, limit = %d sec.\n", + pLabel, iSelId, fUseOneBranch, iPo, Cuts[c], LevelRoot, nFrontier, nFrontPis, nAndBest, Gia_ManAndNum(pSolve), nLimit ); + if ( Gia_ManAndNum(pSolve) == 0 ) + { + Gia_Obj_t * pCo = Gia_ManCo( pSolve, 0 ); + if ( Gia_ObjIsConst0(Gia_ObjFanin0(pCo)) && !Gia_ObjFaninC0(pCo) ) + Status = 1; + } + if ( Status != 1 ) + { + int StatusSat = -1; + int * pModel = Acb_NtkSolveCadicalLimit( pSolve, 0, 0, &StatusSat, nLimit, NULL, 0 ); + if ( pModel ) + ABC_FREE( pModel ); + Status = StatusSat == 1 ? 1 : -1; + } + if ( fVerbose ) + { + printf( "%s frontier abstraction: output %d %s. ", + pLabel, iPo, Status == 1 ? "UNSAT" : "inconclusive" ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + if ( pOpt ) + Gia_ManStop( pOpt ); + Gia_ManStop( pAbs ); + if ( Status == 1 ) + return 1; + } + return -1; +} +int Acb_GiaSolveSmallConeInternalFrontier( Gia_Man_t * p, int fVerbose, int nSatTimeLimit ) +{ + Gia_Obj_t * pRoot; + int Cuts[8], nCuts = 0, c, LevelRoot, Status = ACB_XEC_UNDEC; + abctime clk = Abc_Clock(); + abctime clkLimit = nSatTimeLimit > 0 ? clk + nSatTimeLimit * CLOCKS_PER_SEC : 0; + if ( p == NULL || Gia_ManCoNum(p) != 1 || Gia_ManAndNum(p) <= 0 || Gia_ManAndNum(p) > 5000 || nSatTimeLimit < 10 ) + return ACB_XEC_UNDEC; + pRoot = Gia_ObjFanin0( Gia_ManCo(p, 0) ); + if ( Gia_ObjIsConst0(pRoot) || Gia_ObjIsCi(pRoot) ) + return ACB_XEC_UNDEC; + Gia_ManLevelNum( p ); + LevelRoot = Gia_ObjLevel( p, pRoot ); + if ( LevelRoot < 8 ) + return ACB_XEC_UNDEC; +#define ACB_ADD_FRONTIER_CUT(cut_) do { \ + int Cut_ = (cut_); \ + int t_; \ + if ( Cut_ > 0 && Cut_ < LevelRoot ) \ + { \ + for ( t_ = 0; t_ < nCuts; t_++ ) \ + if ( Cuts[t_] == Cut_ ) \ + break; \ + if ( t_ == nCuts && nCuts < (int)(sizeof(Cuts)/sizeof(Cuts[0])) ) \ + Cuts[nCuts++] = Cut_; \ + } \ + } while (0) + ACB_ADD_FRONTIER_CUT( LevelRoot / 4 ); + ACB_ADD_FRONTIER_CUT( LevelRoot / 3 ); + ACB_ADD_FRONTIER_CUT( LevelRoot / 2 ); + ACB_ADD_FRONTIER_CUT( (2 * LevelRoot) / 3 ); + ACB_ADD_FRONTIER_CUT( (3 * LevelRoot) / 4 ); + ACB_ADD_FRONTIER_CUT( Abc_MaxInt(1, LevelRoot - 32) ); + ACB_ADD_FRONTIER_CUT( Abc_MaxInt(1, LevelRoot - 16) ); +#undef ACB_ADD_FRONTIER_CUT + if ( fVerbose ) + printf( "Trying small-cone internal frontier proof: CI = %d. AND = %d. levels = %d. cuts = %d. limit = %d sec.\n", + Gia_ManCiNum(p), Gia_ManAndNum(p), LevelRoot, nCuts, nSatTimeLimit ); + for ( c = 0; c < nCuts; c++ ) + { + Gia_Man_t * pAbs = NULL, * pTemp = NULL, * pSolve = NULL; + int nFront = 0, nFrontPis = 0, nRemain, nThisLimit, StatusOne = ACB_XEC_UNDEC; + int * pModel = NULL; + if ( clkLimit ) + { + nRemain = (int)((clkLimit - Abc_Clock()) / CLOCKS_PER_SEC); + if ( nRemain < 5 ) + break; + } + else + nRemain = nSatTimeLimit; + nThisLimit = Abc_MinInt( nRemain, c < 3 ? 45 : 30 ); + pAbs = Acb_GiaDerivePoFrontierAbstract( p, 0, Cuts[c], &nFront, &nFrontPis ); + if ( pAbs == NULL ) + continue; + if ( Gia_ManPoIsConst0(pAbs, 0) ) + { + Status = ACB_XEC_EQ; + Gia_ManStop( pAbs ); + break; + } + if ( Gia_ManAndNum(pAbs) > 200 ) + { + pTemp = Gia_ManCompress2( pAbs, 1, 0 ); + if ( pTemp ) + { + Gia_ManStop( pAbs ); + pAbs = pTemp; + pTemp = NULL; + } + } + if ( Gia_ManAndNum(pAbs) > 1000 && nThisLimit >= 20 ) + { + pTemp = Acb_NtkFraigEquivReduce( pAbs, 0, "Small-cone internal frontier", "abstraction", 32, 300, 20000, 1 ); + if ( pTemp ) + { + Gia_ManStop( pAbs ); + pAbs = pTemp; + pTemp = NULL; + } + } + pSolve = pAbs; + if ( fVerbose ) + printf( "Small-cone internal frontier proof: cut = %d/%d. frontier = %d. abs PIs = %d. And = %d. limit = %d sec.\n", + Cuts[c], LevelRoot, nFront, nFrontPis, Gia_ManAndNum(pSolve), nThisLimit ); + pModel = Acb_NtkSolveCadicalLimit( pSolve, 0, 0, &StatusOne, nThisLimit, NULL, 0 ); + ABC_FREE( pModel ); + if ( StatusOne == ACB_XEC_EQ ) + { + Status = ACB_XEC_EQ; + Gia_ManStop( pAbs ); + break; + } + Gia_ManStop( pAbs ); + } + if ( fVerbose ) + { + printf( "Small-cone internal frontier proof: %s. ", Status == ACB_XEC_EQ ? "UNSAT" : "UNDECIDED" ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + return Status; +} +int * Acb_NtkSolveCadicalLocalOptPo( Gia_Man_t * p, int iPo, int fVerbose, int * pStatus, int nSatTimeLimit, int iSelId, int fUseOneBranch, char * pLabel ) +{ + Gia_Man_t * pOne, * pOpt = NULL, * pSyn = NULL, * pTemp, * pBase, * pSolve; + int Status = -1; + int fSkipped = 0; + int * pModel; + int nAndBefore, nAndAfter; + abctime clk = Abc_Clock(); + assert( iPo >= 0 && iPo < Gia_ManCoNum(p) ); + if ( nSatTimeLimit <= 0 ) + { + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + return NULL; + } + pOne = Gia_ManDupCones( p, &iPo, 1, 0 ); + nAndBefore = Gia_ManAndNum( pOne ); + if ( fVerbose ) + printf( "%s local optimized cone: selector %d branch %d output %d. And = %d. limit = %d sec.\n", + pLabel, iSelId, fUseOneBranch, iPo, nAndBefore, nSatTimeLimit ); + pOpt = Gia_ManCompress2( pOne, 1, fVerbose ); + pBase = pOpt ? pOpt : pOne; + pSyn = Gia_ManAigSyn2( pBase, 0, 1, 0, 100, 0, 0, 0 ); + if ( pSyn ) + { + pTemp = Gia_ManCompress2( pSyn, 1, 0 ); + if ( pTemp ) + { + Gia_ManStop( pSyn ); + pSyn = pTemp; + } + } + pSolve = pSyn ? pSyn : pBase; + nAndAfter = Gia_ManAndNum( pSolve ); + if ( fVerbose ) + printf( "%s local optimized cone: optimized And = %d -> %d.\n", pLabel, nAndBefore, nAndAfter ); + if ( 10 * nAndAfter > 9 * nAndBefore ) + { + if ( fVerbose ) + printf( "%s local optimized cone: output %d skipped because reduction is below 10%%. ", pLabel, iPo ); + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + fSkipped = 1; + pModel = NULL; + goto cleanup; + } + pModel = Acb_NtkSolveCadicalLimit( pSolve, 0, fVerbose, &Status, nSatTimeLimit, NULL, 0 ); +cleanup: + if ( pSyn ) + Gia_ManStop( pSyn ); + if ( pOpt ) + Gia_ManStop( pOpt ); + Gia_ManStop( pOne ); + if ( pStatus ) + *pStatus = Status; + if ( fVerbose ) + { + printf( "%s local optimized cone: output %d %s. ", + pLabel, iPo, fSkipped ? "SKIPPED" : (Status == 0 ? "SAT" : (Status == 1 ? "UNSAT" : "UNDECIDED")) ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + return pModel; +} +Vec_Int_t * Acb_GiaCollectPoConeAnds( Gia_Man_t * p, int iPo ) +{ + Vec_Int_t * vCone = Vec_IntAlloc( 1000 ); + int iObj = Gia_ObjId( p, Gia_ManCo(p, iPo) ); + Gia_ManIncrementTravId( p ); + Gia_ManCollectAnds( p, &iObj, 1, vCone, NULL ); + return vCone; +} +int Acb_GiaConeOverlapPermille( Vec_Int_t * vCone0, Vec_Int_t * vCone1, Vec_Int_t * vMarks ) +{ + Vec_Int_t * vSmall = Vec_IntSize(vCone0) <= Vec_IntSize(vCone1) ? vCone0 : vCone1; + Vec_Int_t * vLarge = Vec_IntSize(vCone0) <= Vec_IntSize(vCone1) ? vCone1 : vCone0; + int i, iObj, nInter = 0; + if ( Vec_IntSize(vSmall) == 0 ) + return Vec_IntSize(vLarge) == 0 ? 1000 : 0; + Vec_IntForEachEntry( vSmall, iObj, i ) + Vec_IntWriteEntry( vMarks, iObj, 1 ); + Vec_IntForEachEntry( vLarge, iObj, i ) + nInter += Vec_IntEntry( vMarks, iObj ); + Vec_IntForEachEntry( vSmall, iObj, i ) + Vec_IntWriteEntry( vMarks, iObj, 0 ); + return 1000 * nInter / Vec_IntSize(vSmall); +} +int Acb_GiaBuildOverlapSchedule( Gia_Man_t * p, Acb_SplitPoOrder_t * pOrder, int nMiterOuts, int * pSched, int * pGroupStart, int fVerbose, char * pLabel, Acb_XecCtx_t * pCtx ) +{ + Vec_Int_t ** ppCones = ABC_CALLOC( Vec_Int_t *, nMiterOuts ); + Vec_Int_t * vMarks = Vec_IntStart( Gia_ManObjNum(p) ); + Vec_Int_t * vCluster = Vec_IntAlloc( nMiterOuts ); + unsigned char * pUsed = ABC_CALLOC( unsigned char, nMiterOuts ); + int i, k, s, l, r, iPo, iSeedPo, nSched = 0, nGroups = 0; + for ( i = 0; i < nMiterOuts; i++ ) + { + ppCones[i] = Acb_GiaCollectPoConeAnds( p, i ); + pGroupStart[i] = 0; + } + for ( s = 0; s < nMiterOuts; s++ ) + { + int nSeedSize, nAdded = 0; + if ( pUsed[s] ) + continue; + Vec_IntClear( vCluster ); + pGroupStart[nSched] = 1; + Vec_IntPush( vCluster, s ); + pUsed[s] = 1; + nGroups++; + iSeedPo = pOrder[s].iPo; + nSeedSize = Vec_IntSize( ppCones[iSeedPo] ); + for ( k = s + 1; k < nMiterOuts; k++ ) + { + int nSize, nMin, nMax, nOverlap; + if ( pUsed[k] ) + continue; + iPo = pOrder[k].iPo; + nSize = Vec_IntSize( ppCones[iPo] ); + nMin = Abc_MinInt( nSeedSize, nSize ); + nMax = Abc_MaxInt( nSeedSize, nSize ); + if ( nMax == 0 || 1000 * nMin < pCtx->Pars.nOverlapSizePermille * nMax ) + continue; + nOverlap = Acb_GiaConeOverlapPermille( ppCones[iSeedPo], ppCones[iPo], vMarks ); + if ( nOverlap < pCtx->Pars.nOverlapMinPermille ) + continue; + Vec_IntPush( vCluster, k ); + pUsed[k] = 1; + nAdded++; + } + for ( l = 0, r = Vec_IntSize(vCluster) - 1; l <= r; l++, r-- ) + { + if ( nGroups == 1 ) + { + pSched[nSched++] = Vec_IntEntry( vCluster, l ); + if ( l < r ) + pSched[nSched++] = Vec_IntEntry( vCluster, r ); + } + else + { + pSched[nSched++] = Vec_IntEntry( vCluster, r ); + if ( l < r ) + pSched[nSched++] = Vec_IntEntry( vCluster, l ); + } + } + if ( fVerbose && nAdded ) + printf( "%s support-overlap cluster %d: seed output %d, members = %d.\n", + pLabel, nGroups - 1, iSeedPo, nAdded + 1 ); + } + if ( fVerbose ) + printf( "%s support-overlap clustering: outputs = %d. clusters = %d. overlap >= %d.%d%%, size ratio >= %d.%d%%.\n", + pLabel, nMiterOuts, nGroups, + pCtx->Pars.nOverlapMinPermille / 10, pCtx->Pars.nOverlapMinPermille % 10, + pCtx->Pars.nOverlapSizePermille / 10, pCtx->Pars.nOverlapSizePermille % 10 ); + for ( i = 0; i < nMiterOuts; i++ ) + Vec_IntFree( ppCones[i] ); + ABC_FREE( ppCones ); + ABC_FREE( pUsed ); + Vec_IntFree( vCluster ); + Vec_IntFree( vMarks ); + return nGroups; +} +int * Acb_NtkSolveCadicalSelectorBranch( Gia_Man_t * p, int nMiterOuts, int fUseOneBranch, int fVerbose, int * pStatus, int nSatTimeLimit, int iSelId, int fStopOnUndec, char * pLabel, Acb_XecCtx_t * pCtx ) +{ + int * pModel = NULL; + int * pPoStatus = NULL, * pPoCone = NULL, * pPoConf = NULL, * pPoLearn = NULL, * pPoTime = NULL, * pPoSlot = NULL; + int * pPoConfTotal = NULL, * pPoLearnTotal = NULL; + Acb_SplitPoOrder_t * pOrder = NULL; + Gia_Man_t * pCond = NULL, * pOpt = NULL, * pSweep = NULL, * pCnfGia = NULL; + Aig_Man_t * pMan = NULL; + Cnf_Dat_t * pCnf = NULL; + cadical_solver * pSat = NULL; + int Lit, Status = 0, i, nUnsat = 0, nUndec = 0; + int nGroups = 0, nIsolations = 0; + int * pSched = NULL, * pGroupStart = NULL, nSched = 0; + int nConflicts = 0, nLearned = 0; + int nMinOutTime = nSatTimeLimit > 0 ? Abc_MinInt( pCtx->Pars.nBranchMinOutputSec, Abc_MaxInt( 1, nSatTimeLimit / 10 ) ) : pCtx->Pars.nBranchMinOutputSec; + int nAndCond; + abctime clk = Abc_Clock(); + abctime clkLimit = nSatTimeLimit > 0 ? clk + nSatTimeLimit * CLOCKS_PER_SEC : 0; + assert( nMiterOuts > 0 ); + assert( pCtx != NULL ); + Acb_XecCtxResetBranchSweep( pCtx, nMiterOuts ); + assert( Gia_ManCoNum(p) == nMiterOuts + 2 ); + pCond = Acb_GiaDeriveBranchConditionMiter( p, nMiterOuts, fUseOneBranch ); + nAndCond = Gia_ManAndNum( pCond ); + pOpt = nAndCond > 1000 ? Gia_ManCompress2( pCond, 1, 0 ) : NULL; + pCnfGia = pOpt ? pOpt : pCond; + pSweep = Acb_NtkBranchSweepReduce( pCnfGia, fVerbose, pLabel ); + if ( pSweep ) + pCnfGia = pSweep; + pMan = Gia_ManToAig( pCnfGia, 0 ); + pCnf = pMan ? Cnf_Derive( pMan, Aig_ManCoNum(pMan) ) : NULL; + if ( pCnf == NULL ) + { + Status = 0; + nUndec++; + goto cleanup; + } + if ( fVerbose ) + printf( "%s conditioned grouped CaDiCaL sweep: selector %d branch %d. outputs = %d. And = %d -> %d. CNF var = %d. cla = %d.\n", + pLabel, iSelId, fUseOneBranch, nMiterOuts, nAndCond, Gia_ManAndNum(pCnfGia), pCnf->nVars, pCnf->nClauses ); + pOrder = ABC_ALLOC( Acb_SplitPoOrder_t, nMiterOuts ); + Acb_NtkSortSplitOutputsLimit( pCnfGia, pOrder, nMiterOuts ); + pPoStatus = ABC_ALLOC( int, nMiterOuts ); + pPoCone = ABC_ALLOC( int, nMiterOuts ); + pPoConf = ABC_ALLOC( int, nMiterOuts ); + pPoLearn = ABC_ALLOC( int, nMiterOuts ); + pPoTime = ABC_ALLOC( int, nMiterOuts ); + pPoSlot = ABC_ALLOC( int, nMiterOuts ); + pPoConfTotal = ABC_ALLOC( int, nMiterOuts ); + pPoLearnTotal = ABC_ALLOC( int, nMiterOuts ); + for ( i = 0; i < nMiterOuts; i++ ) + { + pPoStatus[i] = 2; + pPoCone[i] = 0; + pPoConf[i] = 0; + pPoLearn[i] = 0; + pPoTime[i] = 0; + pPoSlot[i] = 0; + pPoConfTotal[i] = 0; + pPoLearnTotal[i] = 0; + } + for ( i = 0; i < nMiterOuts; i++ ) + { + pPoCone[pOrder[i].iPo] = pOrder[i].nAnds; + pPoSlot[pOrder[i].iPo] = i + 1; + } + if ( fVerbose ) + printf( "%s output order: smallest cone %d ANDs, largest cone %d ANDs.\n", + pLabel, pOrder[0].nAnds, pOrder[nMiterOuts-1].nAnds ); + pSched = ABC_ALLOC( int, nMiterOuts ); + pGroupStart = ABC_ALLOC( int, nMiterOuts ); + nGroups = Acb_GiaBuildOverlapSchedule( pCnfGia, pOrder, nMiterOuts, pSched, pGroupStart, fVerbose, pLabel, pCtx ); + nSched = nMiterOuts; + if ( fVerbose ) + { + int nPrint = Abc_MinInt( nSched, pCtx->Pars.nBranchSchedulePrintMax ); + printf( "%s support-overlap schedule: selector %d branch %d outputs = %d. order =", pLabel, iSelId, fUseOneBranch, nSched ); + for ( i = 0; i < nPrint; i++ ) + { + if ( pGroupStart[i] ) + printf( " |" ); + printf( " %d", pOrder[pSched[i]].iPo ); + } + if ( nPrint < nSched ) + printf( " ..." ); + printf( ".\n" ); + } + for ( i = 0; i < nSched; i++ ) + { + abctime clkOut = Abc_Clock(); + int iPos = pSched[i]; + int iPo = pOrder[iPos].iPo; + int nConflictsBeg, nLearnedBeg; + int fTryLocalOpt = iPos >= nMiterOuts/2 && iPos <= nMiterOuts - 4 && pOrder[iPos].nAnds >= pCtx->Pars.nBranchLocalOptAndMin; + if ( pGroupStart[i] || pSat == NULL ) + { + if ( pSat ) + cadical_solver_delete( pSat ); + pSat = cadical_solver_new(); + if ( pSat == NULL || !Acb_CnfWriteIntoCadical( pSat, pCnf ) ) + { + Status = 0; + nUndec++; + break; + } + if ( fVerbose ) + printf( "%s grouped CaDiCaL: starting overlap cluster at output %d.\n", + pLabel, iPo ); + } + nConflictsBeg = cadical_solver_nconflicts(pSat); + nLearnedBeg = cadical_solver_nlearned(pSat); + if ( clkLimit && Abc_Clock() >= clkLimit ) + { + Status = 0; + nUndec++; + break; + } + if ( clkLimit && clkLimit - Abc_Clock() < nMinOutTime * CLOCKS_PER_SEC ) + { + Status = 0; + nUndec++; + if ( fVerbose ) + printf( "%s grouped CaDiCaL: skipping output %d because remaining branch budget is below %d sec.\n", + pLabel, iPo, nMinOutTime ); + break; + } + if ( fTryLocalOpt && clkLimit ) + { + int StatusLocal = -1; + int nRemain = (int)((clkLimit - Abc_Clock()) / CLOCKS_PER_SEC); + int nLocalLimit = Abc_MinInt( pCtx->Pars.nBranchLocalOptSec, nRemain - nMinOutTime ); + int nAbsLimit = Abc_MinInt( pCtx->Pars.nBranchFrontierAbsSec, nRemain - nMinOutTime ); + if ( nAbsLimit >= 15 && Acb_NtkTryFrontierAbstractPo( pCnfGia, iPo, fVerbose, nAbsLimit, iSelId, fUseOneBranch, pLabel ) == 1 ) + { + pPoStatus[iPo] = -1; + pPoConf[iPo] = 0; + pPoLearn[iPo] = 0; + pPoConfTotal[iPo] = nConflictsBeg; + pPoLearnTotal[iPo] = nLearnedBeg; + pPoTime[iPo] = (int)((Abc_Clock() - clkOut + CLOCKS_PER_SEC/2) / CLOCKS_PER_SEC); + Status = -1; + nUnsat++; + if ( fVerbose ) + printf( "%s grouped CaDiCaL: output %d UNSAT by frontier abstraction; skipping grouped assumption.\n", pLabel, iPo ); + continue; + } + if ( nLocalLimit >= nMinOutTime ) + { + int * pLocalModel = Acb_NtkSolveCadicalLocalOptPo( pCnfGia, iPo, fVerbose, &StatusLocal, nLocalLimit, iSelId, fUseOneBranch, pLabel ); + pPoStatus[iPo] = StatusLocal == 0 ? 1 : (StatusLocal == 1 ? -1 : 0); + pPoConf[iPo] = 0; + pPoLearn[iPo] = 0; + pPoConfTotal[iPo] = nConflictsBeg; + pPoLearnTotal[iPo] = nLearnedBeg; + pPoTime[iPo] = (int)((Abc_Clock() - clkOut + CLOCKS_PER_SEC/2) / CLOCKS_PER_SEC); + if ( StatusLocal == 0 ) + { + pModel = pLocalModel; + Status = 1; + break; + } + if ( pLocalModel ) + ABC_FREE( pLocalModel ); + if ( StatusLocal == 1 ) + { + Status = -1; + nUnsat++; + if ( fVerbose ) + printf( "%s grouped CaDiCaL: output %d UNSAT by local optimized cone; skipping grouped assumption.\n", pLabel, iPo ); + continue; + } + } + } + { + int RetLit = Acb_CnfCoDriverLit( pCnf, iPo, &Lit ); + if ( RetLit == -2 ) + { + Status = 0; + nUndec++; + pPoStatus[iPo] = Status; + pPoConf[iPo] = 0; + pPoLearn[iPo] = 0; + pPoConfTotal[iPo] = cadical_solver_nconflicts(pSat); + pPoLearnTotal[iPo] = cadical_solver_nlearned(pSat); + pPoTime[iPo] = (int)((Abc_Clock() - clkOut + CLOCKS_PER_SEC/2) / CLOCKS_PER_SEC); + if ( fVerbose ) + printf( "%s grouped CaDiCaL: output %d UNDECIDED because its CNF driver is unmapped.\n", pLabel, iPo ); + break; + } + if ( RetLit == -1 ) + { + Status = -1; + nUnsat++; + pPoStatus[iPo] = Status; + pPoConf[iPo] = 0; + pPoLearn[iPo] = 0; + pPoConfTotal[iPo] = cadical_solver_nconflicts(pSat); + pPoLearnTotal[iPo] = cadical_solver_nlearned(pSat); + pPoTime[iPo] = (int)((Abc_Clock() - clkOut + CLOCKS_PER_SEC/2) / CLOCKS_PER_SEC); + if ( fVerbose ) + printf( "%s grouped CaDiCaL: output %d UNSAT because it is constant 0.\n", pLabel, iPo ); + continue; + } + if ( RetLit == 0 ) + { + Status = 1; + Lit = -1; + } + } + if ( fVerbose ) + printf( "%s grouped CaDiCaL: selector %d branch %d output %d (%d/%d), cone = %d ANDs.\n", + pLabel, iSelId, fUseOneBranch, iPo, iPos + 1, nMiterOuts, pOrder[iPos].nAnds ); + if ( Status != 1 ) + Status = cadical_solver_solve( pSat, &Lit, &Lit + 1, 0, 0, 0, 0 ); + nConflicts = cadical_solver_nconflicts(pSat); + nLearned = cadical_solver_nlearned(pSat); + pPoStatus[iPo] = Status; + pPoConf[iPo] = nConflicts - nConflictsBeg; + pPoLearn[iPo] = nLearned - nLearnedBeg; + pPoConfTotal[iPo] = nConflicts; + pPoLearnTotal[iPo] = nLearned; + pPoTime[iPo] = (int)((Abc_Clock() - clkOut + CLOCKS_PER_SEC/2) / CLOCKS_PER_SEC); + if ( Status == 1 ) + { + Aig_Obj_t * pObj; + pModel = ABC_ALLOC( int, Aig_ManCiNum(pMan) ); + Aig_ManForEachCi( pMan, pObj, iPo ) + pModel[iPo] = cadical_solver_get_var_value( pSat, pCnf->pVarNums[pObj->Id] ); + break; + } + if ( Status == -1 ) + { + nUnsat++; + if ( fVerbose ) + { + printf( "%s grouped CaDiCaL: output %d UNSAT. delta conflicts = %d. delta learned = %d. total conflicts = %d. total learned = %d. ", + pLabel, iPo, pPoConf[iPo], pPoLearn[iPo], nConflicts, nLearned ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clkOut ); + } + } + else + { + nUndec++; + if ( fVerbose ) + { + printf( "%s grouped CaDiCaL: output %d UNDECIDED. delta conflicts = %d. delta learned = %d. total conflicts = %d. total learned = %d. ", + pLabel, iPo, pPoConf[iPo], pPoLearn[iPo], nConflicts, nLearned ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clkOut ); + } + if ( fStopOnUndec ) + break; + } + if ( Status != -1 || pPoConf[iPo] >= pCtx->Pars.nBranchHardConflictMin || pPoTime[iPo] >= pCtx->Pars.nBranchHardTimeMin ) + { + if ( Status == -1 ) + nIsolations++; + if ( fVerbose && Status == -1 ) + printf( "%s hard-output isolation: resetting solver after output %d. delta conflicts = %d, time = %d sec.\n", + pLabel, iPo, pPoConf[iPo], pPoTime[iPo] ); + cadical_solver_delete( pSat ); + pSat = NULL; + } + } + if ( pStatus ) + *pStatus = Status == 1 ? 0 : (nUndec ? -1 : 1); + printf( "The selector %d branch %d is %s by %s CaDiCaL. ", + iSelId, fUseOneBranch, Status == 1 ? "SAT" : (nUndec ? "UNDECIDED" : "UNSAT"), pLabel ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + for ( i = 0; i < nMiterOuts; i++ ) + if ( pPoStatus && (pPoStatus[i] == 2 || pPoStatus[i] != -1 || pPoConf[i] >= pCtx->Pars.nBranchHardConflictMin || pPoTime[i] >= pCtx->Pars.nBranchHardTimeMin) ) + Vec_IntPushUnique( pCtx->vLastBranchHardPos, i ); + if ( fVerbose ) + { + int nHard = 0; + printf( "%s grouped CaDiCaL stats: overlap clusters = %d. hard isolations = %d. SAT = %d. UNSAT = %d. UNDEC = %d. last-group conflicts = %d. last-group learned = %d.\n", + pLabel, nGroups, nIsolations, Status == 1, nUnsat, nUndec, nConflicts, nLearned ); + for ( i = 0; i < nMiterOuts; i++ ) + if ( pPoStatus[i] != 2 && (pPoStatus[i] != -1 || pPoConf[i] >= pCtx->Pars.nBranchHardConflictMin || pPoTime[i] >= pCtx->Pars.nBranchHardTimeMin) ) + nHard++; + if ( nHard ) + { + printf( "%s hard-output summary: selector %d branch %d. thresholds: delta conflicts >= %d OR time >= %d sec OR non-UNSAT.\n", + pLabel, iSelId, fUseOneBranch, pCtx->Pars.nBranchHardConflictMin, pCtx->Pars.nBranchHardTimeMin ); + for ( i = 0; i < nMiterOuts; i++ ) + if ( pPoStatus[i] != 2 && (pPoStatus[i] != -1 || pPoConf[i] >= pCtx->Pars.nBranchHardConflictMin || pPoTime[i] >= pCtx->Pars.nBranchHardTimeMin) ) + { + printf( " output %d: status = %s, sorted slot = %d/%d, cone = %d ANDs, delta conflicts = %d, delta learned = %d, total conflicts = %d, total learned = %d, time = %d sec.\n", + i, pPoStatus[i] == 1 ? "SAT" : (pPoStatus[i] == -1 ? "UNSAT" : "UNDECIDED"), + pPoSlot[i], nMiterOuts, pPoCone[i], pPoConf[i], pPoLearn[i], pPoConfTotal[i], pPoLearnTotal[i], pPoTime[i] ); + Acb_GiaPrintHardPoFrontier( pCnfGia, i, fVerbose ); + } + } + } +cleanup: + if ( pPoStatus ) + ABC_FREE( pPoStatus ); + if ( pPoCone ) + ABC_FREE( pPoCone ); + if ( pPoConf ) + ABC_FREE( pPoConf ); + if ( pPoLearn ) + ABC_FREE( pPoLearn ); + if ( pPoTime ) + ABC_FREE( pPoTime ); + if ( pPoSlot ) + ABC_FREE( pPoSlot ); + if ( pPoConfTotal ) + ABC_FREE( pPoConfTotal ); + if ( pPoLearnTotal ) + ABC_FREE( pPoLearnTotal ); + if ( pSched ) + ABC_FREE( pSched ); + if ( pGroupStart ) + ABC_FREE( pGroupStart ); + if ( pOrder ) + ABC_FREE( pOrder ); + if ( pSat ) + cadical_solver_delete( pSat ); + if ( pCnf ) + Cnf_DataFree( pCnf ); + if ( pMan ) + Aig_ManStop( pMan ); + if ( pOpt ) + Gia_ManStop( pOpt ); + if ( pSweep ) + Gia_ManStop( pSweep ); + if ( pCond ) + Gia_ManStop( pCond ); + return pModel; +} +int * Acb_NtkSolveHmuxBranches( Acb_Ntk_t * pNtkF, Acb_Ntk_t * pNtkG, Vec_Int_t * vCutObjsG, Vec_Int_t * vMuxSelectorsG, Vec_Int_t * vMuxPoSelIdsG, Vec_Int_t * vIntDcObjsG, Vec_Int_t * vIntDcCtrlsG, Vec_Int_t * vIntDcCtrlIdsG, int fVerbose, int * pStatus, Acb_XecCtx_t * pCtx ) +{ + int iSel, fOne, Status, fUndec = 0; + int * pModel = NULL; + abctime clk = Abc_Clock(); + if ( fVerbose ) + printf( "Trying HMUX branch-level proving: selectors = %d.\n", Vec_IntSize(vMuxSelectorsG) ); + for ( iSel = 0; iSel < Vec_IntSize(vMuxSelectorsG); iSel++ ) + { + for ( fOne = 0; fOne <= 1; fOne++ ) + { + Vec_Int_t * vFTargets = Acb_NtkCollectCoDriversForSelector( pNtkF, vMuxPoSelIdsG, iSel ); + Vec_Int_t * vGTargets = Acb_NtkCollectPoMuxBranchTargets( pNtkG, vCutObjsG, vMuxPoSelIdsG, iSel, fOne ); + Gia_Man_t * pGiaFBranch = NULL, * pGiaGBranch = NULL, * pGiaBranch = NULL, * pGiaCond = NULL; + int fTriedBranchWhole = 0; + assert( Vec_IntSize(vFTargets) == Vec_IntSize(vGTargets) ); + Vec_IntPush( vGTargets, Vec_IntEntry(vMuxSelectorsG, iSel) ); + pGiaFBranch = Acb_NtkGiaDeriveDualTargets( pNtkF, vFTargets ); + pGiaGBranch = Acb_NtkGiaDeriveDualTargets( pNtkG, vGTargets ); + pGiaBranch = Acb_NtkGiaDeriveMiterWithSecondExtras( pGiaFBranch, pGiaGBranch, 1 ); + if ( fVerbose ) + printf( "HMUX branch miter: selector %d branch %d. And = %d. PO = %d.\n", + iSel, fOne, Gia_ManAndNum(pGiaBranch), Gia_ManPoNum(pGiaBranch) ); + Status = -1; + if ( Vec_IntSize(vFTargets) >= 16 && Gia_ManAndNum(pGiaBranch) <= 30000 ) + { + int nWholeLimit = Vec_IntSize(vFTargets) >= 24 ? 60 : 450; + pGiaCond = Acb_GiaDeriveBranchConditionMiter( pGiaBranch, Vec_IntSize(vFTargets), fOne ); + if ( fVerbose ) + printf( "HMUX branch whole-miter try: selector %d branch %d. And = %d. PO = %d. limit = %d sec.\n", + iSel, fOne, Gia_ManAndNum(pGiaCond), Gia_ManPoNum(pGiaCond), nWholeLimit ); + fTriedBranchWhole = 1; + pModel = Acb_NtkSolveCadicalLimit( pGiaCond, 0, fVerbose, &Status, nWholeLimit, "HMUX branch whole-miter CaDiCaL", 0 ); + Gia_ManStop( pGiaCond ); + pGiaCond = NULL; + if ( Status == -1 && fVerbose ) + printf( "HMUX branch whole-miter CaDiCaL was UNDECIDED; skipping duplicate grouped branch sweep.\n" ); + } + if ( Status == -1 && !fTriedBranchWhole ) + pModel = Acb_NtkSolveCadicalSelectorBranch( pGiaBranch, Vec_IntSize(vFTargets), fOne, fVerbose, &Status, 1200, iSel, 0, "HMUX branch", pCtx ); + Gia_ManStop( pGiaBranch ); + Gia_ManStop( pGiaGBranch ); + Gia_ManStop( pGiaFBranch ); + Vec_IntFree( vGTargets ); + Vec_IntFree( vFTargets ); + if ( Status == 0 ) + { + if ( pStatus ) + *pStatus = ACB_XEC_NEQ; + return pModel; + } + if ( Status == -1 ) + { + int StatusDc = -1; + if ( vIntDcObjsG && vIntDcCtrlsG && vIntDcCtrlIdsG && + Vec_IntSize(vIntDcObjsG) > 0 && Vec_IntSize(vIntDcCtrlsG) > 4 && + pCtx->vLastBranchHardPos && Vec_IntSize(pCtx->vLastBranchHardPos) > 0 ) + { + Vec_Int_t * vPoIds = Acb_NtkCollectPoIdsForSelector( pNtkF, vMuxPoSelIdsG, iSel ); + Vec_Int_t * vHardOrig = Vec_IntAlloc( Vec_IntSize(pCtx->vLastBranchHardPos) ); + int iHardLocal, k; + Vec_IntForEachEntry( pCtx->vLastBranchHardPos, iHardLocal, k ) + if ( iHardLocal >= 0 && iHardLocal < Vec_IntSize(vPoIds) ) + Vec_IntPushUnique( vHardOrig, Vec_IntEntry(vPoIds, iHardLocal) ); + if ( fVerbose ) + printf( "HMUX branch selector %d branch %d collected %d hard/unvisited local outputs -> %d original outputs for targeted DC-control proof.\n", + iSel, fOne, Vec_IntSize(pCtx->vLastBranchHardPos), Vec_IntSize(vHardOrig) ); + if ( Vec_IntSize(vHardOrig) <= 4 && Vec_IntSize(vIntDcCtrlsG) <= 8 ) + pModel = Acb_NtkSolveMuxDcControlTargetList( pNtkF, pNtkG, vHardOrig, + vCutObjsG, vMuxSelectorsG, vMuxPoSelIdsG, fOne, + vIntDcObjsG, vIntDcCtrlsG, vIntDcCtrlIdsG, fVerbose, &StatusDc, 90 ); + else if ( fVerbose ) + printf( "Skipping HMUX+DC targeted recursive proof: hard outputs = %d, DC controls = %d; recursion is too broad for this branch.\n", + Vec_IntSize(vHardOrig), Vec_IntSize(vIntDcCtrlsG) ); + Vec_IntFree( vHardOrig ); + Vec_IntFree( vPoIds ); + if ( StatusDc == 0 ) + { + if ( pStatus ) + *pStatus = ACB_XEC_NEQ; + return pModel; + } + if ( StatusDc == 1 ) + Status = 1; + } + if ( Status == -1 ) + fUndec = 1; + } + } + } + if ( pStatus ) + *pStatus = fUndec ? -1 : 1; + printf( "The networks are %s by HMUX branch-level proving. ", fUndec ? "UNDECIDED" : "equivalent" ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + return NULL; +} +int * Acb_NtkSolveHmuxCompleteCubes( Acb_Ntk_t * pNtkF, Acb_Ntk_t * pNtkG, Vec_Int_t * vCutObjsG, Vec_Int_t * vMuxSelectorsG, Vec_Int_t * vMuxPoSelIdsG, int fVerbose, int * pStatus, int nTotalLimit, int nCubeLimit ) +{ + Vec_Int_t * vFTargets = NULL, * vGTargets = NULL, * vCubeVals = NULL; + Gia_Man_t * pGiaF = NULL, * pGiaG = NULL, * pGiaMiter = NULL, * pGiaCond = NULL, * pTemp = NULL; + int nSels, nCubes, iCube, iSel, Status = ACB_XEC_UNDEC, StatusAll = ACB_XEC_EQ, * pModel = NULL; + abctime clk = Abc_Clock(); + abctime clkLimit = nTotalLimit > 0 ? clk + nTotalLimit * CLOCKS_PER_SEC : 0; + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + if ( vCutObjsG == NULL || vMuxSelectorsG == NULL || vMuxPoSelIdsG == NULL ) + return NULL; + nSels = Vec_IntSize( vMuxSelectorsG ); + if ( nSels <= 0 || nSels > 4 || Vec_IntSize(vCutObjsG) != Acb_NtkCoNum(pNtkG) ) + return NULL; + nCubes = 1 << nSels; + vFTargets = Acb_NtkCollectCoDrivers( pNtkF ); + pGiaF = Acb_NtkGiaDeriveDualTargets( pNtkF, vFTargets ); + vCubeVals = Vec_IntAlloc( nSels ); + if ( fVerbose ) + printf( "Trying complete HMUX selector-cube proof: selectors = %d. cubes = %d. outputs = %d. total limit = %d sec.\n", + nSels, nCubes, Acb_NtkCoNum(pNtkF), nTotalLimit ); + for ( iCube = 0; iCube < nCubes; iCube++ ) + { + int nThisLimit = nCubeLimit; + if ( clkLimit ) + { + int nRemain = (int)((clkLimit - Abc_Clock()) / CLOCKS_PER_SEC); + if ( nRemain <= 0 ) + { + StatusAll = ACB_XEC_UNDEC; + break; + } + nThisLimit = nCubeLimit > 0 ? Abc_MinInt( nCubeLimit, nRemain ) : nRemain; + } + Vec_IntClear( vCubeVals ); + for ( iSel = 0; iSel < nSels; iSel++ ) + Vec_IntPush( vCubeVals, (iCube >> iSel) & 1 ); + vGTargets = Acb_NtkCollectPoMuxCubeTargets( pNtkG, vCutObjsG, vMuxPoSelIdsG, vCubeVals ); + Vec_IntAppend( vGTargets, vMuxSelectorsG ); + pGiaG = Acb_NtkGiaDeriveDualTargets( pNtkG, vGTargets ); + pGiaMiter = Acb_NtkGiaDeriveMiterWithSecondExtras( pGiaF, pGiaG, nSels ); + pGiaCond = Acb_GiaDeriveCubeConditionMiter( pGiaMiter, Acb_NtkCoNum(pNtkF), vCubeVals ); + if ( Gia_ManAndNum(pGiaCond) > 5000 ) + { + pTemp = Gia_ManCompress2( pGiaCond, 1, 0 ); + if ( pTemp ) + { + Gia_ManStop( pGiaCond ); + pGiaCond = pTemp; + pTemp = NULL; + } + } + if ( Gia_ManAndNum(pGiaCond) > 8000 && Gia_ManAndNum(pGiaCond) < 70000 ) + { + pTemp = Acb_NtkFraigEquivReduce( pGiaCond, fVerbose, "Complete HMUX selector cube", "conditioned cube", 32, 300, 12000, Abc_MaxInt( 50, Gia_ManAndNum(pGiaCond) / 200 ) ); + if ( pTemp ) + { + Gia_ManStop( pGiaCond ); + pGiaCond = pTemp; + pTemp = NULL; + } + } + if ( fVerbose ) + { + printf( "Complete HMUX selector cube %d/%d: values =", iCube + 1, nCubes ); + for ( iSel = 0; iSel < nSels; iSel++ ) + printf( " s%d=%d", iSel, Vec_IntEntry(vCubeVals, iSel) ); + printf( ". And = %d. PO = %d. limit = %d sec.\n", Gia_ManAndNum(pGiaCond), Gia_ManCoNum(pGiaCond), nThisLimit ); + } + pModel = Acb_NtkSolveCadicalPoSweepLabel( pGiaCond, fVerbose, &Status, nThisLimit, 500000, "complete HMUX selector-cube PO sweep", 0 ); + if ( Status == ACB_XEC_UNDEC ) + { + if ( fVerbose ) + printf( "Complete HMUX selector cube %d/%d PO sweep was inconclusive; skipping duplicate whole-cube CaDiCaL.\n", + iCube + 1, nCubes ); + } + Gia_ManStop( pGiaCond ); pGiaCond = NULL; + Gia_ManStop( pGiaMiter ); pGiaMiter = NULL; + Gia_ManStop( pGiaG ); pGiaG = NULL; + Vec_IntFreeP( &vGTargets ); + if ( Status == ACB_XEC_NEQ ) + { + StatusAll = ACB_XEC_NEQ; + break; + } + if ( Status != ACB_XEC_EQ ) + { + StatusAll = ACB_XEC_UNDEC; + break; + } + } + if ( pStatus ) + *pStatus = StatusAll; + if ( StatusAll == ACB_XEC_EQ ) + { + printf( "The networks are equivalent by complete HMUX selector-cube proof. " ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + else if ( fVerbose ) + { + printf( "The networks are %s by complete HMUX selector-cube proof. ", + StatusAll == ACB_XEC_NEQ ? "NOT equivalent" : "UNDECIDED" ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + Vec_IntFreeP( &vFTargets ); + Vec_IntFreeP( &vGTargets ); + Vec_IntFreeP( &vCubeVals ); + if ( pGiaF ) Gia_ManStop( pGiaF ); + if ( pGiaG ) Gia_ManStop( pGiaG ); + if ( pGiaMiter ) Gia_ManStop( pGiaMiter ); + if ( pGiaCond ) Gia_ManStop( pGiaCond ); + if ( pTemp ) Gia_ManStop( pTemp ); + return pModel; +} +int * Acb_NtkSolveMuxTargetBranches( Acb_Ntk_t * pNtkF, Acb_Ntk_t * pNtkG, int iPo, Vec_Int_t * vCutObjsG, Vec_Int_t * vMuxSelectorsG, Vec_Int_t * vMuxPoSelIdsG, int fVerbose, int * pStatus, int nBranchLimit, int nMaxBranchAnd ) +{ + Vec_Int_t * vFTargets = Vec_IntAlloc( 1 ); + Acb_XecCtx_t BranchCtx; + int iSel, fOne, Status, fUndec = 0; + int * pModel = NULL; + abctime clk = Abc_Clock(); + Acb_XecCtxInit( &BranchCtx ); + assert( iPo >= 0 && iPo < Acb_NtkCoNum(pNtkF) && iPo < Acb_NtkCoNum(pNtkG) ); + assert( vCutObjsG && vMuxSelectorsG && vMuxPoSelIdsG ); + assert( Vec_IntSize(vCutObjsG) == Acb_NtkCoNum(pNtkG) ); + assert( Vec_IntSize(vMuxPoSelIdsG) == Acb_NtkCoNum(pNtkG) ); + iSel = Vec_IntEntry( vMuxPoSelIdsG, iPo ); + if ( iSel < 0 || iSel >= Vec_IntSize(vMuxSelectorsG) ) + { + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + Vec_IntFree( vFTargets ); + Acb_XecCtxFree( &BranchCtx ); + return NULL; + } + Vec_IntPush( vFTargets, Acb_ObjFanin(pNtkF, Acb_NtkCo(pNtkF, iPo), 0) ); + if ( fVerbose ) + printf( "Trying MUX target branch proving: output = %d. selector = %d/%d.\n", + iPo, iSel, Vec_IntSize(vMuxSelectorsG) ); + for ( fOne = 0; fOne <= 1; fOne++ ) + { + Vec_Int_t * vGTargets = Vec_IntAlloc( 2 ); + Gia_Man_t * pGiaFBranch = NULL, * pGiaGBranch = NULL, * pGiaBranch = NULL; + int iMux = Vec_IntEntry( vCutObjsG, iPo ); + assert( !Acb_ObjIsCio(pNtkG, iMux) && Acb_ObjType(pNtkG, iMux) == ABC_OPER_BIT_MUX ); + Vec_IntPush( vGTargets, Acb_ObjFanin(pNtkG, iMux, fOne ? 1 : 0) ); + Vec_IntPush( vGTargets, Vec_IntEntry(vMuxSelectorsG, iSel) ); + pGiaFBranch = Acb_NtkGiaDeriveDualTargets( pNtkF, vFTargets ); + pGiaGBranch = Acb_NtkGiaDeriveDualTargets( pNtkG, vGTargets ); + pGiaBranch = Acb_NtkGiaDeriveMiterWithSecondExtras( pGiaFBranch, pGiaGBranch, 1 ); + if ( fVerbose ) + printf( "MUX target branch miter: output %d selector %d branch %d. And = %d. PO = %d.\n", + iPo, iSel, fOne, Gia_ManAndNum(pGiaBranch), Gia_ManPoNum(pGiaBranch) ); + if ( nMaxBranchAnd > 0 && Gia_ManAndNum(pGiaBranch) >= nMaxBranchAnd ) + { + if ( fVerbose ) + printf( "Skipping MUX target branch output %d selector %d/%d because branch miter is not smaller than current hard cone: branch And = %d, current And = %d.\n", + iPo, iSel, fOne, Gia_ManAndNum(pGiaBranch), nMaxBranchAnd ); + Status = -1; + fUndec = 1; + } + else + pModel = Acb_NtkSolveCadicalSelectorBranch( pGiaBranch, 1, fOne, fVerbose, &Status, nBranchLimit, iSel, 0, "MUX target branch", &BranchCtx ); + Gia_ManStop( pGiaBranch ); + Gia_ManStop( pGiaGBranch ); + Gia_ManStop( pGiaFBranch ); + Vec_IntFree( vGTargets ); + if ( Status == 0 ) + { + Vec_IntFree( vFTargets ); + Acb_XecCtxFree( &BranchCtx ); + if ( pStatus ) + *pStatus = ACB_XEC_NEQ; + return pModel; + } + if ( Status == -1 ) + fUndec = 1; + } + Vec_IntFree( vFTargets ); + if ( pStatus ) + *pStatus = fUndec ? ACB_XEC_UNDEC : ACB_XEC_EQ; + printf( "The hard output %d is %s by MUX target branch proving. ", + iPo, fUndec ? "UNDECIDED" : "UNSAT" ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + Acb_XecCtxFree( &BranchCtx ); + return NULL; +} +int * Acb_NtkSolveDcControlBranchesLimit( Acb_Ntk_t * pNtkF, Acb_Ntk_t * pNtkG, Vec_Int_t * vDcObjsG, Vec_Int_t * vDcCtrlsG, Vec_Int_t * vDcCtrlIdsG, int fVerbose, int * pStatus, int nBranchLimit, int fStopOnUndec, int nMaxBranchAnd ) +{ + Vec_Int_t * vFTargets = Acb_NtkCollectCoDrivers( pNtkF ); + Acb_XecCtx_t BranchCtx; + int iCtrl, fOne, Status, fUndec = 0; + int * pModel = NULL; + abctime clk = Abc_Clock(); + Acb_XecCtxInit( &BranchCtx ); + if ( fVerbose ) + printf( "Trying DC-control branch proving: DC nodes = %d. controls = %d.\n", + Vec_IntSize(vDcObjsG), Vec_IntSize(vDcCtrlsG) ); + for ( iCtrl = 0; iCtrl < Vec_IntSize(vDcCtrlsG); iCtrl++ ) + { + Vec_Int_t * vDcObjsOne = Acb_NtkCollectDcObjsForControl( vDcObjsG, vDcCtrlIdsG, iCtrl ); + int fCtrlUndec = 0; + for ( fOne = 0; fOne <= 1; fOne++ ) + { + Vec_Int_t * vGTargets = Vec_IntAlloc( Acb_NtkCoNum(pNtkG) + 1 ); + Gia_Man_t * pGiaFBranch = NULL, * pGiaGBranch = NULL, * pGiaBranch = NULL; + int i, iObj; + Acb_NtkForEachCo( pNtkG, iObj, i ) + Vec_IntPush( vGTargets, Acb_ObjFanin(pNtkG, iObj, 0) ); + Vec_IntPush( vGTargets, Vec_IntEntry(vDcCtrlsG, iCtrl) ); + pGiaFBranch = Acb_NtkGiaDeriveDualTargets( pNtkF, vFTargets ); + pGiaGBranch = Acb_NtkGiaDeriveDualTargetsBranch( pNtkG, vGTargets, vDcObjsOne, fOne ); + pGiaBranch = Acb_NtkGiaDeriveMiterWithSecondExtras( pGiaFBranch, pGiaGBranch, 1 ); + if ( fVerbose ) + printf( "DC-control branch miter: control %d branch %d. DC nodes = %d. And = %d. PO = %d.\n", + iCtrl, fOne, Vec_IntSize(vDcObjsOne), Gia_ManAndNum(pGiaBranch), Gia_ManPoNum(pGiaBranch) ); + if ( nMaxBranchAnd > 0 && Gia_ManAndNum(pGiaBranch) >= nMaxBranchAnd ) + { + if ( fVerbose ) + printf( "Skipping DC-control branch %d/%d because branch miter is not smaller than current miter: branch And = %d, current And = %d.\n", + iCtrl, fOne, Gia_ManAndNum(pGiaBranch), nMaxBranchAnd ); + Status = -1; + fUndec = 1; + } + else + { + pModel = Acb_NtkSolveCadicalSelectorBranch( pGiaBranch, Vec_IntSize(vFTargets), fOne, fVerbose, &Status, nBranchLimit, iCtrl, fStopOnUndec, "DC-control branch", &BranchCtx ); + } + Gia_ManStop( pGiaBranch ); + Gia_ManStop( pGiaGBranch ); + Gia_ManStop( pGiaFBranch ); + Vec_IntFree( vGTargets ); + if ( Status == 0 ) + { + Vec_IntFree( vDcObjsOne ); + Vec_IntFree( vFTargets ); + Acb_XecCtxFree( &BranchCtx ); + if ( pStatus ) + *pStatus = ACB_XEC_NEQ; + return pModel; + } + if ( Status == -1 ) + { + fUndec = 1; + fCtrlUndec = 1; + } + } + Vec_IntFree( vDcObjsOne ); + if ( !fCtrlUndec ) + { + Vec_IntFree( vFTargets ); + Acb_XecCtxFree( &BranchCtx ); + if ( pStatus ) + *pStatus = ACB_XEC_EQ; + printf( "The networks are equivalent by DC-control branch proving on control %d. ", iCtrl ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + return NULL; + } + } + Vec_IntFree( vFTargets ); + if ( pStatus ) + *pStatus = fUndec ? -1 : 1; + printf( "The networks are %s by DC-control branch proving. ", fUndec ? "UNDECIDED" : "equivalent" ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + Acb_XecCtxFree( &BranchCtx ); + return NULL; +} +int * Acb_NtkSolveDcControlTargetBranches( Acb_Ntk_t * pNtkF, Acb_Ntk_t * pNtkG, int iPo, Vec_Int_t * vDcObjsG, Vec_Int_t * vDcCtrlsG, Vec_Int_t * vDcCtrlIdsG, int fVerbose, int * pStatus, int nBranchLimit, int nMaxBranchAnd ) +{ + Vec_Int_t * vFTargets = Vec_IntAlloc( 1 ); + Acb_XecCtx_t BranchCtx; + int iCtrl, fOne, Status, fUndec = 0; + int * pModel = NULL; + abctime clk = Abc_Clock(); + Acb_XecCtxInit( &BranchCtx ); + assert( iPo >= 0 && iPo < Acb_NtkCoNum(pNtkF) && iPo < Acb_NtkCoNum(pNtkG) ); + Vec_IntPush( vFTargets, Acb_ObjFanin(pNtkF, Acb_NtkCo(pNtkF, iPo), 0) ); + if ( fVerbose ) + printf( "Trying DC-control target branch proving: output = %d. DC nodes = %d. controls = %d.\n", + iPo, Vec_IntSize(vDcObjsG), Vec_IntSize(vDcCtrlsG) ); + for ( iCtrl = 0; iCtrl < Vec_IntSize(vDcCtrlsG); iCtrl++ ) + { + Vec_Int_t * vDcObjsOne = Acb_NtkCollectDcObjsForControl( vDcObjsG, vDcCtrlIdsG, iCtrl ); + int fCtrlUndec = 0; + for ( fOne = 0; fOne <= 1; fOne++ ) + { + Vec_Int_t * vGTargets = Vec_IntAlloc( 2 ); + Gia_Man_t * pGiaFBranch = NULL, * pGiaGBranch = NULL, * pGiaBranch = NULL; + Vec_IntPush( vGTargets, Acb_ObjFanin(pNtkG, Acb_NtkCo(pNtkG, iPo), 0) ); + Vec_IntPush( vGTargets, Vec_IntEntry(vDcCtrlsG, iCtrl) ); + pGiaFBranch = Acb_NtkGiaDeriveDualTargets( pNtkF, vFTargets ); + pGiaGBranch = Acb_NtkGiaDeriveDualTargetsBranch( pNtkG, vGTargets, vDcObjsOne, fOne ); + pGiaBranch = Acb_NtkGiaDeriveMiterWithSecondExtras( pGiaFBranch, pGiaGBranch, 1 ); + if ( fVerbose ) + printf( "DC-control target branch miter: output %d control %d branch %d. DC nodes = %d. And = %d. PO = %d.\n", + iPo, iCtrl, fOne, Vec_IntSize(vDcObjsOne), Gia_ManAndNum(pGiaBranch), Gia_ManPoNum(pGiaBranch) ); + if ( nMaxBranchAnd > 0 && Gia_ManAndNum(pGiaBranch) >= nMaxBranchAnd ) + { + if ( fVerbose ) + printf( "Skipping DC-control target branch output %d control %d/%d because branch miter is not smaller than current hard cone: branch And = %d, current And = %d.\n", + iPo, iCtrl, fOne, Gia_ManAndNum(pGiaBranch), nMaxBranchAnd ); + Status = -1; + fUndec = 1; + } + else + pModel = Acb_NtkSolveCadicalSelectorBranch( pGiaBranch, 1, fOne, fVerbose, &Status, nBranchLimit, iCtrl, 0, "DC-control target branch", &BranchCtx ); + Gia_ManStop( pGiaBranch ); + Gia_ManStop( pGiaGBranch ); + Gia_ManStop( pGiaFBranch ); + Vec_IntFree( vGTargets ); + if ( Status == 0 ) + { + Vec_IntFree( vDcObjsOne ); + Vec_IntFree( vFTargets ); + Acb_XecCtxFree( &BranchCtx ); + if ( pStatus ) + *pStatus = ACB_XEC_NEQ; + return pModel; + } + if ( Status == -1 ) + { + fUndec = 1; + fCtrlUndec = 1; + } + } + Vec_IntFree( vDcObjsOne ); + if ( !fCtrlUndec ) + { + Vec_IntFree( vFTargets ); + Acb_XecCtxFree( &BranchCtx ); + if ( pStatus ) + *pStatus = ACB_XEC_EQ; + printf( "The hard output %d is UNSAT by DC-control target branch proving on control %d. ", iPo, iCtrl ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + return NULL; + } + } + Vec_IntFree( vFTargets ); + if ( pStatus ) + *pStatus = fUndec ? ACB_XEC_UNDEC : ACB_XEC_EQ; + printf( "The hard output %d is %s by DC-control target branch proving. ", + iPo, fUndec ? "UNDECIDED" : "UNSAT" ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + Acb_XecCtxFree( &BranchCtx ); + return NULL; +} +void Acb_NtkCollectDcObjsForCube( Vec_Int_t * vDcObjs, Vec_Int_t * vDcCtrlIds, Vec_Int_t * vCubeCtrls, Vec_Int_t * vCubeVals, Vec_Int_t ** pvObjs, Vec_Int_t ** pvVals ) +{ + Vec_Int_t * vObjs = Vec_IntAlloc( Vec_IntSize(vDcObjs) ); + Vec_Int_t * vVals = Vec_IntAlloc( Vec_IntSize(vDcObjs) ); + int i, k, iObj, iCtrlId, iCubeCtrl; + Vec_IntForEachEntry( vDcObjs, iObj, i ) + { + iCtrlId = Vec_IntEntry( vDcCtrlIds, i ); + Vec_IntForEachEntry( vCubeCtrls, iCubeCtrl, k ) + if ( iCubeCtrl == iCtrlId ) + { + Vec_IntPush( vObjs, iObj ); + Vec_IntPush( vVals, Vec_IntEntry(vCubeVals, k) ); + break; + } + } + *pvObjs = vObjs; + *pvVals = vVals; +} +int * Acb_NtkSolveDcControlTargetCube( Acb_Ntk_t * pNtkF, Acb_Ntk_t * pNtkG, int iPo, Vec_Int_t * vDcObjsG, Vec_Int_t * vDcCtrlsG, Vec_Int_t * vDcCtrlIdsG, Vec_Int_t * vCubeCtrls, Vec_Int_t * vCubeVals, int fVerbose, int * pStatus, int nBranchLimit, int nDepthLeft ) +{ + Vec_Int_t * vFTargets = Vec_IntAlloc( 1 ); + Vec_Int_t * vGTargets = Vec_IntAlloc( 1 + Vec_IntSize(vCubeCtrls) ); + Vec_Int_t * vDcObjsCube = NULL, * vDcValsCube = NULL; + Gia_Man_t * pGiaFBranch = NULL, * pGiaGBranch = NULL, * pGiaBranch = NULL, * pGiaCond = NULL, * pTemp = NULL; + int i, k, iCtrl, Status = -1, * pModel = NULL; + int nProbeLimit = nDepthLeft > 0 ? Abc_MinInt( nBranchLimit, 30 ) : nBranchLimit; + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + Vec_IntPush( vFTargets, Acb_ObjFanin(pNtkF, Acb_NtkCo(pNtkF, iPo), 0) ); + Vec_IntPush( vGTargets, Acb_ObjFanin(pNtkG, Acb_NtkCo(pNtkG, iPo), 0) ); + Vec_IntForEachEntry( vCubeCtrls, iCtrl, i ) + Vec_IntPush( vGTargets, Vec_IntEntry(vDcCtrlsG, iCtrl) ); + Acb_NtkCollectDcObjsForCube( vDcObjsG, vDcCtrlIdsG, vCubeCtrls, vCubeVals, &vDcObjsCube, &vDcValsCube ); + pGiaFBranch = Acb_NtkGiaDeriveDualTargets( pNtkF, vFTargets ); + pGiaGBranch = Acb_NtkGiaDeriveDualTargetsBranchValues( pNtkG, vGTargets, vDcObjsCube, vDcValsCube, 0 ); + pGiaBranch = Acb_NtkGiaDeriveMiterWithSecondExtras( pGiaFBranch, pGiaGBranch, Vec_IntSize(vCubeCtrls) ); + pGiaCond = Acb_GiaDeriveCubeConditionMiter( pGiaBranch, 1, vCubeVals ); + if ( Gia_ManAndNum(pGiaCond) > 5000 ) + { + pTemp = Gia_ManCompress2( pGiaCond, 1, 0 ); + if ( pTemp ) + { + Gia_ManStop( pGiaCond ); + pGiaCond = pTemp; + pTemp = NULL; + } + } + if ( fVerbose ) + { + printf( "DC-control recursive target: output %d cube =", iPo ); + Vec_IntForEachEntry( vCubeCtrls, iCtrl, i ) + printf( " c%d=%d", iCtrl, Vec_IntEntry(vCubeVals, i) ); + printf( ". And = %d. limit = %d sec%s.\n", Gia_ManAndNum(pGiaCond), nProbeLimit, + nDepthLeft > 0 ? " before split" : "" ); + } + pModel = Acb_NtkSolveCadicalLimit( pGiaCond, 0, 0, &Status, nProbeLimit, NULL, 0 ); + Gia_ManStop( pGiaCond ); + Gia_ManStop( pGiaBranch ); + Gia_ManStop( pGiaGBranch ); + Gia_ManStop( pGiaFBranch ); + Vec_IntFree( vDcObjsCube ); + Vec_IntFree( vDcValsCube ); + Vec_IntFree( vGTargets ); + Vec_IntFree( vFTargets ); + if ( Status == 1 ) + { + if ( fVerbose ) + printf( "DC-control recursive target: output %d cube UNSAT.\n", iPo ); + if ( pStatus ) + *pStatus = ACB_XEC_EQ; + return NULL; + } + if ( Status == 0 ) + { + if ( fVerbose ) + printf( "DC-control recursive target: output %d cube SAT.\n", iPo ); + if ( pStatus ) + *pStatus = ACB_XEC_NEQ; + return pModel; + } + ABC_FREE( pModel ); + if ( nDepthLeft <= 0 ) + { + if ( fVerbose ) + printf( "DC-control recursive target: output %d cube UNDECIDED at depth limit.\n", iPo ); + return NULL; + } + { + Vec_Int_t * vTriedCtrls = Vec_IntAlloc( Vec_IntSize(vDcCtrlsG) ); + for ( k = 0; k < Vec_IntSize(vDcCtrlsG); k++ ) + { + int fOne, fCtrlUndec = 0, nBestCount = -1; + iCtrl = -1; + for ( i = 0; i < Vec_IntSize(vDcCtrlsG); i++ ) + { + int j, iCtrlId, nCount = 0; + if ( Vec_IntFind(vCubeCtrls, i) >= 0 || Vec_IntFind(vTriedCtrls, i) >= 0 ) + continue; + Vec_IntForEachEntry( vDcCtrlIdsG, iCtrlId, j ) + nCount += (iCtrlId == i); + if ( nCount > nBestCount ) + { + nBestCount = nCount; + iCtrl = i; + } + } + if ( iCtrl < 0 ) + break; + Vec_IntPush( vTriedCtrls, iCtrl ); + for ( fOne = 1; fOne >= 0; fOne-- ) + { + int StatusSub = -1; + Vec_IntPush( vCubeCtrls, iCtrl ); + Vec_IntPush( vCubeVals, fOne ); + pModel = Acb_NtkSolveDcControlTargetCube( pNtkF, pNtkG, iPo, vDcObjsG, vDcCtrlsG, vDcCtrlIdsG, + vCubeCtrls, vCubeVals, fVerbose, &StatusSub, nBranchLimit, nDepthLeft - 1 ); + Vec_IntPop( vCubeCtrls ); + Vec_IntPop( vCubeVals ); + if ( StatusSub == 0 ) + { + Vec_IntFree( vTriedCtrls ); + if ( pStatus ) + *pStatus = ACB_XEC_NEQ; + return pModel; + } + if ( StatusSub != 1 ) + fCtrlUndec = 1; + } + if ( !fCtrlUndec ) + { + if ( fVerbose ) + printf( "DC-control recursive target: output %d proven by splitting control %d.\n", iPo, iCtrl ); + Vec_IntFree( vTriedCtrls ); + if ( pStatus ) + *pStatus = ACB_XEC_EQ; + return NULL; + } + } + Vec_IntFree( vTriedCtrls ); + } + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + return NULL; +} +int * Acb_NtkSolveDcControlTargetList( Acb_Ntk_t * pNtkF, Acb_Ntk_t * pNtkG, Vec_Int_t * vHardPos, Vec_Int_t * vDcObjsG, Vec_Int_t * vDcCtrlsG, Vec_Int_t * vDcCtrlIdsG, int fVerbose, int * pStatus, int nBranchLimit ) +{ + int i, iPo, StatusOne = -1, fUndec = 0; + int * pModel = NULL; + abctime clk = Abc_Clock(); + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + if ( vHardPos == NULL || Vec_IntSize(vHardPos) == 0 ) + return NULL; + if ( fVerbose ) + printf( "Trying DC-control target proving for %d hard outputs.\n", Vec_IntSize(vHardPos) ); + Vec_IntForEachEntry( vHardPos, iPo, i ) + { + Vec_Int_t * vCubeCtrls = Vec_IntAlloc( Vec_IntSize(vDcCtrlsG) ); + Vec_Int_t * vCubeVals = Vec_IntAlloc( Vec_IntSize(vDcCtrlsG) ); + int nDepth = Vec_IntSize(vDcCtrlsG) <= 3 ? Vec_IntSize(vDcCtrlsG) : Abc_MinInt( 2, Vec_IntSize(vDcCtrlsG) ); + pModel = Acb_NtkSolveDcControlTargetCube( pNtkF, pNtkG, iPo, vDcObjsG, vDcCtrlsG, vDcCtrlIdsG, + vCubeCtrls, vCubeVals, fVerbose, &StatusOne, nBranchLimit, nDepth ); + Vec_IntFree( vCubeCtrls ); + Vec_IntFree( vCubeVals ); + if ( StatusOne == 0 ) + { + if ( pStatus ) + *pStatus = ACB_XEC_NEQ; + return pModel; + } + if ( StatusOne != 1 ) + { + fUndec = 1; + if ( fVerbose ) + printf( "DC-control target list: output %d remains UNDECIDED; stopping target-list proof.\n", iPo ); + break; + } + } + if ( pStatus ) + *pStatus = fUndec ? -1 : 1; + printf( "The hard outputs are %s by DC-control target-list proving. ", fUndec ? "UNDECIDED" : "UNSAT" ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + return NULL; +} +int * Acb_NtkSolveMuxDcControlTargetCube( Acb_Ntk_t * pNtkF, Acb_Ntk_t * pNtkG, int iPo, Vec_Int_t * vCutObjsG, Vec_Int_t * vMuxSelectorsG, Vec_Int_t * vMuxPoSelIdsG, int fSelBranch, Vec_Int_t * vDcObjsG, Vec_Int_t * vDcCtrlsG, Vec_Int_t * vDcCtrlIdsG, Vec_Int_t * vCubeCtrls, Vec_Int_t * vCubeVals, int fVerbose, int * pStatus, int nBranchLimit, int nDepthLeft ) +{ + Vec_Int_t * vFTargets = Vec_IntAlloc( 1 ); + Vec_Int_t * vGTargets = Vec_IntAlloc( 2 + Vec_IntSize(vCubeCtrls) ); + Vec_Int_t * vDcObjsCube = NULL, * vDcValsCube = NULL, * vCondVals = Vec_IntAlloc( 1 + Vec_IntSize(vCubeCtrls) ); + Gia_Man_t * pGiaFBranch = NULL, * pGiaGBranch = NULL, * pGiaBranch = NULL, * pGiaCond = NULL, * pTemp = NULL; + int i, k, iCtrl, iMux, Status = -1, * pModel = NULL; + int nProbeLimit = nDepthLeft > 0 ? Abc_MinInt( nBranchLimit, 30 ) : nBranchLimit; + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + assert( iPo >= 0 && iPo < Acb_NtkCoNum(pNtkF) && iPo < Acb_NtkCoNum(pNtkG) ); + assert( vCutObjsG && vMuxSelectorsG && vMuxPoSelIdsG ); + iMux = Vec_IntEntry( vCutObjsG, iPo ); + assert( !Acb_ObjIsCio(pNtkG, iMux) && Acb_ObjType(pNtkG, iMux) == ABC_OPER_BIT_MUX ); + Vec_IntPush( vFTargets, Acb_ObjFanin(pNtkF, Acb_NtkCo(pNtkF, iPo), 0) ); + Vec_IntPush( vGTargets, Acb_ObjFanin(pNtkG, iMux, fSelBranch ? 1 : 0) ); + Vec_IntPush( vGTargets, Vec_IntEntry(vMuxSelectorsG, Vec_IntEntry(vMuxPoSelIdsG, iPo)) ); + Vec_IntForEachEntry( vCubeCtrls, iCtrl, i ) + Vec_IntPush( vGTargets, Vec_IntEntry(vDcCtrlsG, iCtrl) ); + Acb_NtkCollectDcObjsForCube( vDcObjsG, vDcCtrlIdsG, vCubeCtrls, vCubeVals, &vDcObjsCube, &vDcValsCube ); + pGiaFBranch = Acb_NtkGiaDeriveDualTargets( pNtkF, vFTargets ); + pGiaGBranch = Acb_NtkGiaDeriveDualTargetsBranchValues( pNtkG, vGTargets, vDcObjsCube, vDcValsCube, 0 ); + pGiaBranch = Acb_NtkGiaDeriveMiterWithSecondExtras( pGiaFBranch, pGiaGBranch, 1 + Vec_IntSize(vCubeCtrls) ); + Vec_IntPush( vCondVals, fSelBranch ); + Vec_IntForEachEntry( vCubeVals, iCtrl, i ) + Vec_IntPush( vCondVals, iCtrl ); + pGiaCond = Acb_GiaDeriveCubeConditionMiter( pGiaBranch, 1, vCondVals ); + if ( Gia_ManAndNum(pGiaCond) > 5000 ) + { + pTemp = Gia_ManCompress2( pGiaCond, 1, 0 ); + if ( pTemp ) + { + Gia_ManStop( pGiaCond ); + pGiaCond = pTemp; + pTemp = NULL; + } + } + if ( fVerbose ) + { + printf( "HMUX+DC recursive target: output %d selector %d branch %d cube =", + iPo, Vec_IntEntry(vMuxPoSelIdsG, iPo), fSelBranch ); + Vec_IntForEachEntry( vCubeCtrls, iCtrl, i ) + printf( " c%d=%d", iCtrl, Vec_IntEntry(vCubeVals, i) ); + printf( ". And = %d. limit = %d sec%s.\n", Gia_ManAndNum(pGiaCond), nProbeLimit, + nDepthLeft > 0 ? " before split" : "" ); + } + pModel = Acb_NtkSolveCadicalLimit( pGiaCond, 0, 0, &Status, nProbeLimit, NULL, 0 ); + Gia_ManStop( pGiaCond ); + Gia_ManStop( pGiaBranch ); + Gia_ManStop( pGiaGBranch ); + Gia_ManStop( pGiaFBranch ); + Vec_IntFree( vDcObjsCube ); + Vec_IntFree( vDcValsCube ); + Vec_IntFree( vCondVals ); + Vec_IntFree( vGTargets ); + Vec_IntFree( vFTargets ); + if ( Status == 1 ) + { + if ( fVerbose ) + printf( "HMUX+DC recursive target: output %d cube UNSAT.\n", iPo ); + if ( pStatus ) + *pStatus = ACB_XEC_EQ; + return NULL; + } + if ( Status == 0 ) + { + if ( fVerbose ) + printf( "HMUX+DC recursive target: output %d cube SAT.\n", iPo ); + if ( pStatus ) + *pStatus = ACB_XEC_NEQ; + return pModel; + } + ABC_FREE( pModel ); + if ( nDepthLeft <= 0 ) + { + if ( fVerbose ) + printf( "HMUX+DC recursive target: output %d cube UNDECIDED at depth limit.\n", iPo ); + return NULL; + } + { + Vec_Int_t * vTriedCtrls = Vec_IntAlloc( Vec_IntSize(vDcCtrlsG) ); + for ( k = 0; k < Vec_IntSize(vDcCtrlsG); k++ ) + { + int fOne, fCtrlUndec = 0, nBestCount = -1; + iCtrl = -1; + for ( i = 0; i < Vec_IntSize(vDcCtrlsG); i++ ) + { + int j, iCtrlId, nCount = 0; + if ( Vec_IntFind(vCubeCtrls, i) >= 0 || Vec_IntFind(vTriedCtrls, i) >= 0 ) + continue; + Vec_IntForEachEntry( vDcCtrlIdsG, iCtrlId, j ) + nCount += (iCtrlId == i); + if ( nCount > nBestCount ) + { + nBestCount = nCount; + iCtrl = i; + } + } + if ( iCtrl < 0 ) + break; + Vec_IntPush( vTriedCtrls, iCtrl ); + for ( fOne = 1; fOne >= 0; fOne-- ) + { + int StatusSub = -1; + Vec_IntPush( vCubeCtrls, iCtrl ); + Vec_IntPush( vCubeVals, fOne ); + pModel = Acb_NtkSolveMuxDcControlTargetCube( pNtkF, pNtkG, iPo, vCutObjsG, vMuxSelectorsG, vMuxPoSelIdsG, fSelBranch, + vDcObjsG, vDcCtrlsG, vDcCtrlIdsG, vCubeCtrls, vCubeVals, fVerbose, &StatusSub, nBranchLimit, nDepthLeft - 1 ); + Vec_IntPop( vCubeCtrls ); + Vec_IntPop( vCubeVals ); + if ( StatusSub == 0 ) + { + Vec_IntFree( vTriedCtrls ); + if ( pStatus ) + *pStatus = ACB_XEC_NEQ; + return pModel; + } + if ( StatusSub != 1 ) + fCtrlUndec = 1; + } + if ( !fCtrlUndec ) + { + if ( fVerbose ) + printf( "HMUX+DC recursive target: output %d proven by splitting control %d.\n", iPo, iCtrl ); + Vec_IntFree( vTriedCtrls ); + if ( pStatus ) + *pStatus = ACB_XEC_EQ; + return NULL; + } + } + Vec_IntFree( vTriedCtrls ); + } + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + return NULL; +} + +int * Acb_NtkSolveMuxDcControlTargetList( Acb_Ntk_t * pNtkF, Acb_Ntk_t * pNtkG, Vec_Int_t * vHardPos, Vec_Int_t * vCutObjsG, Vec_Int_t * vMuxSelectorsG, Vec_Int_t * vMuxPoSelIdsG, int fSelBranch, Vec_Int_t * vDcObjsG, Vec_Int_t * vDcCtrlsG, Vec_Int_t * vDcCtrlIdsG, int fVerbose, int * pStatus, int nBranchLimit ) +{ + int i, iPo, StatusOne = -1, fUndec = 0; + int * pModel = NULL; + Vec_Int_t * vCubeCtrls = NULL, * vCubeVals = NULL; + abctime clk = Abc_Clock(); + int nCtrls = vDcCtrlsG ? Vec_IntSize(vDcCtrlsG) : 0; + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + if ( vHardPos == NULL || Vec_IntSize(vHardPos) == 0 ) + return NULL; + if ( Vec_IntSize(vHardPos) > 4 || nCtrls > 8 ) + { + if ( fVerbose ) + printf( "Skipping HMUX+DC target proving because recursive search is too broad: hard outputs = %d, controls = %d.\n", + Vec_IntSize(vHardPos), nCtrls ); + return NULL; + } + if ( fVerbose ) + printf( "Trying HMUX+DC target proving for %d hard outputs. selector branch = %d. controls = %d.\n", + Vec_IntSize(vHardPos), fSelBranch, nCtrls ); + vCubeCtrls = Vec_IntAlloc( nCtrls ); + vCubeVals = Vec_IntAlloc( nCtrls ); + Vec_IntForEachEntry( vHardPos, iPo, i ) + { + int nDepth = nCtrls <= 3 ? nCtrls : 2; + Vec_IntClear( vCubeCtrls ); + Vec_IntClear( vCubeVals ); + pModel = Acb_NtkSolveMuxDcControlTargetCube( pNtkF, pNtkG, iPo, vCutObjsG, vMuxSelectorsG, vMuxPoSelIdsG, fSelBranch, + vDcObjsG, vDcCtrlsG, vDcCtrlIdsG, vCubeCtrls, vCubeVals, fVerbose, &StatusOne, nBranchLimit, nDepth ); + if ( StatusOne == 0 ) + { + if ( pStatus ) + *pStatus = ACB_XEC_NEQ; + Vec_IntFree( vCubeCtrls ); + Vec_IntFree( vCubeVals ); + return pModel; + } + if ( StatusOne != 1 ) + { + fUndec = 1; + if ( fVerbose ) + printf( "HMUX+DC target list: output %d remains UNDECIDED; stopping target-list proof.\n", iPo ); + break; + } + } + if ( pStatus ) + *pStatus = fUndec ? -1 : 1; + printf( "The HMUX hard outputs are %s by targeted DC-control proving. ", fUndec ? "UNDECIDED" : "UNSAT" ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + Vec_IntFree( vCubeCtrls ); + Vec_IntFree( vCubeVals ); + return NULL; +} +int * Acb_NtkSolveDcControlWholeCubes( Acb_Ntk_t * pNtkF, Acb_Ntk_t * pNtkG, Vec_Int_t * vDcObjsG, Vec_Int_t * vDcCtrlsG, Vec_Int_t * vDcCtrlIdsG, int fVerbose, int * pStatus, int nTotalLimit, int nCubeLimit ) +{ + Vec_Int_t * vFTargets = NULL, * vGTargets = NULL, * vCubeCtrls = NULL, * vCubeVals = NULL; + Vec_Int_t * vDcObjsCube = NULL, * vDcValsCube = NULL; + Gia_Man_t * pGiaF = NULL, * pGiaG = NULL, * pGiaMiter = NULL, * pGiaCond = NULL, * pTemp = NULL; + int nCtrls = vDcCtrlsG ? Vec_IntSize(vDcCtrlsG) : 0; + int nCubes, i, iObj, iCube, StatusCube = -1, StatusAll = 1, * pModel = NULL; + abctime clk = Abc_Clock(); + abctime clkLimit = nTotalLimit > 0 ? clk + nTotalLimit * CLOCKS_PER_SEC : 0; + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + if ( nCtrls <= 0 || nCtrls > 4 || vDcObjsG == NULL || Vec_IntSize(vDcObjsG) == 0 ) + return NULL; + nCubes = 1 << nCtrls; + if ( fVerbose ) + printf( "Trying few-control DC whole-cube proof: outputs = %d. DC nodes = %d. controls = %d. cubes = %d. total limit = %d sec.\n", + Acb_NtkCoNum(pNtkF), Vec_IntSize(vDcObjsG), nCtrls, nCubes, nTotalLimit ); + vFTargets = Vec_IntAlloc( Acb_NtkCoNum(pNtkF) ); + vGTargets = Vec_IntAlloc( Acb_NtkCoNum(pNtkG) + nCtrls ); + vCubeCtrls = Vec_IntAlloc( nCtrls ); + vCubeVals = Vec_IntAlloc( nCtrls ); + Acb_NtkForEachCo( pNtkF, iObj, i ) + Vec_IntPush( vFTargets, Acb_ObjFanin(pNtkF, iObj, 0) ); + Acb_NtkForEachCo( pNtkG, iObj, i ) + Vec_IntPush( vGTargets, Acb_ObjFanin(pNtkG, iObj, 0) ); + for ( i = 0; i < nCtrls; i++ ) + { + Vec_IntPush( vCubeCtrls, i ); + Vec_IntPush( vGTargets, Vec_IntEntry(vDcCtrlsG, i) ); + } + pGiaF = Acb_NtkGiaDeriveDualTargets( pNtkF, vFTargets ); + for ( iCube = 0; iCube < nCubes; iCube++ ) + { + int nThisLimit, nRemain; + Vec_IntClear( vCubeVals ); + for ( i = 0; i < nCtrls; i++ ) + Vec_IntPush( vCubeVals, (iCube >> i) & 1 ); + if ( clkLimit ) + { + nRemain = (int)((clkLimit - Abc_Clock()) / CLOCKS_PER_SEC); + if ( nRemain <= 0 ) + { + StatusAll = -1; + break; + } + nThisLimit = nCubeLimit > 0 ? Abc_MinInt( nCubeLimit, nRemain ) : nRemain; + } + else + nThisLimit = nCubeLimit; + Acb_NtkCollectDcObjsForCube( vDcObjsG, vDcCtrlIdsG, vCubeCtrls, vCubeVals, &vDcObjsCube, &vDcValsCube ); + pGiaG = Acb_NtkGiaDeriveDualTargetsBranchValues( pNtkG, vGTargets, vDcObjsCube, vDcValsCube, 0 ); + pGiaMiter = Acb_NtkGiaDeriveMiterWithSecondExtras( pGiaF, pGiaG, nCtrls ); + pGiaCond = Acb_GiaDeriveCubeConditionMiter( pGiaMiter, Acb_NtkCoNum(pNtkF), vCubeVals ); + if ( Gia_ManAndNum(pGiaCond) > 5000 ) + { + pTemp = Gia_ManCompress2( pGiaCond, 1, 0 ); + if ( pTemp ) + { + Gia_ManStop( pGiaCond ); + pGiaCond = pTemp; + pTemp = NULL; + } + } + if ( fVerbose ) + { + printf( "Few-control DC whole cube %d/%d:", iCube + 1, nCubes ); + for ( i = 0; i < nCtrls; i++ ) + printf( " c%d=%d", i, Vec_IntEntry(vCubeVals, i) ); + printf( ". And = %d. PO = %d. limit = %d sec.\n", Gia_ManAndNum(pGiaCond), Gia_ManCoNum(pGiaCond), nThisLimit ); + } + pModel = Acb_NtkSolveCadicalLimit( pGiaCond, 0, fVerbose, &StatusCube, nThisLimit, "few-control DC whole-cube CaDiCaL", 0 ); + Gia_ManStop( pGiaCond ); pGiaCond = NULL; + Gia_ManStop( pGiaMiter ); pGiaMiter = NULL; + Gia_ManStop( pGiaG ); pGiaG = NULL; + Vec_IntFreeP( &vDcObjsCube ); + Vec_IntFreeP( &vDcValsCube ); + if ( StatusCube == 0 ) + { + StatusAll = 0; + break; + } + if ( StatusCube != 1 ) + { + StatusAll = -1; + break; + } + } + if ( pStatus ) + *pStatus = StatusAll; + if ( StatusAll == 1 ) + { + printf( "The networks are equivalent by few-control DC whole-cube proof. " ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + else if ( fVerbose ) + { + printf( "The networks are %s by few-control DC whole-cube proof. ", + StatusAll == 0 ? "NOT equivalent" : "UNDECIDED" ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + Vec_IntFreeP( &vFTargets ); + Vec_IntFreeP( &vGTargets ); + Vec_IntFreeP( &vCubeCtrls ); + Vec_IntFreeP( &vCubeVals ); + Vec_IntFreeP( &vDcObjsCube ); + Vec_IntFreeP( &vDcValsCube ); + if ( pGiaF ) + Gia_ManStop( pGiaF ); + if ( pGiaG ) + Gia_ManStop( pGiaG ); + if ( pGiaMiter ) + Gia_ManStop( pGiaMiter ); + if ( pGiaCond ) + Gia_ManStop( pGiaCond ); + if ( pTemp ) + Gia_ManStop( pTemp ); + return pModel; +} +int Acb_NtkObjIsCutCandBasic( Acb_Ntk_t * p, int iObj ); +int Acb_NtkObjIsCutCand( Acb_Ntk_t * p, int iObj ) +{ + if ( !Acb_NtkObjIsCutCandBasic(p, iObj) ) + return 0; + if ( Acb_ObjName(p, iObj) <= 0 ) + return 0; + return 1; +} +int Acb_NtkObjIsCutCandBasic( Acb_Ntk_t * p, int iObj ) +{ + Acb_ObjType_t Type; + if ( iObj <= 0 || Acb_ObjIsCio(p, iObj) ) + return 0; + Type = Acb_ObjType( p, iObj ); + if ( Type == ABC_OPER_NONE || Type == ABC_OPER_CONST_F || Type == ABC_OPER_CONST_T || Type == ABC_OPER_CONST_X ) + return 0; + if ( Type == ABC_OPER_TRI || Type == ABC_OPER_BIT_MUX ) + return 0; + return 1; +} +void Acb_NtkMarkCone_rec( Acb_Ntk_t * p, int iObj, Vec_Int_t * vMarks ) +{ + int iFanin, k; + if ( iObj <= 0 || Vec_IntEntry(vMarks, iObj) ) + return; + Vec_IntWriteEntry( vMarks, iObj, 1 ); + if ( Acb_ObjIsCio(p, iObj) ) + return; + Acb_ObjForEachFanin( p, iObj, iFanin, k ) + Acb_NtkMarkCone_rec( p, iFanin, vMarks ); +} +int Acb_NtkConeLevel_rec( Acb_Ntk_t * p, int iObj, Vec_Int_t * vMarks, Vec_Int_t * vLevels ) +{ + int iFanin, k, Level, LevelMax = 0; + if ( iObj <= 0 || !Vec_IntEntry(vMarks, iObj) || Acb_ObjIsCio(p, iObj) ) + return 0; + Level = Vec_IntEntry(vLevels, iObj); + if ( Level >= 0 ) + return Level; + Acb_ObjForEachFanin( p, iObj, iFanin, k ) + LevelMax = Abc_MaxInt( LevelMax, Acb_NtkConeLevel_rec(p, iFanin, vMarks, vLevels) ); + Vec_IntWriteEntry( vLevels, iObj, LevelMax + 1 ); + return LevelMax + 1; +} +void Acb_NtkCollectTargetCutCandidates( Acb_Ntk_t * pNtkF, Acb_Ntk_t * pNtkG, int iPo, Vec_Int_t ** pvCutsF, Vec_Int_t ** pvCutsG, int nLimit ) +{ + Vec_Int_t * vNamesInvF = Vec_IntInvert( &pNtkF->vObjName, 0 ); + Vec_Int_t * vMarksF = Vec_IntStart( Acb_NtkObjNumMax(pNtkF) ); + Vec_Int_t * vMarksG = Vec_IntStart( Acb_NtkObjNumMax(pNtkG) ); + Vec_Int_t * vLevelsG = Vec_IntStartFull( Acb_NtkObjNumMax(pNtkG) ); + Vec_Int_t * vCutsF = Vec_IntAlloc( nLimit ); + Vec_Int_t * vCutsG = Vec_IntAlloc( nLimit ); + int iRootF, iRootG, iObjG, iObjF, NameIdF, Level, LevelMax; + assert( iPo >= 0 && iPo < Acb_NtkCoNum(pNtkF) && iPo < Acb_NtkCoNum(pNtkG) ); + iRootF = Acb_ObjFanin( pNtkF, Acb_NtkCo(pNtkF, iPo), 0 ); + iRootG = Acb_ObjFanin( pNtkG, Acb_NtkCo(pNtkG, iPo), 0 ); + Acb_NtkMarkCone_rec( pNtkF, iRootF, vMarksF ); + Acb_NtkMarkCone_rec( pNtkG, iRootG, vMarksG ); + LevelMax = Acb_NtkConeLevel_rec( pNtkG, iRootG, vMarksG, vLevelsG ); + Acb_NtkForEachNodeReverse( pNtkG, iObjG ) + { + if ( Vec_IntSize(vCutsG) >= nLimit ) + break; + if ( !Vec_IntEntry(vMarksG, iObjG) || !Acb_NtkObjIsCutCand(pNtkG, iObjG) ) + continue; + Level = Vec_IntEntry(vLevelsG, iObjG); + if ( Level < Abc_MaxInt(2, LevelMax/4) || Level > Abc_MaxInt(3, 3*LevelMax/4) ) + continue; + NameIdF = Acb_NtkStrId( pNtkF, Acb_ObjNameStr(pNtkG, iObjG) ); + if ( NameIdF <= 0 || NameIdF >= Vec_IntSize(vNamesInvF) ) + continue; + iObjF = Vec_IntEntry( vNamesInvF, NameIdF ); + if ( iObjF <= 0 || iObjF >= Vec_IntSize(vMarksF) || !Vec_IntEntry(vMarksF, iObjF) ) + continue; + if ( !Acb_NtkObjIsCutCand(pNtkF, iObjF) ) + continue; + if ( Acb_ObjType(pNtkF, iObjF) != Acb_ObjType(pNtkG, iObjG) ) + continue; + if ( Acb_ObjFaninNum(pNtkF, iObjF) != Acb_ObjFaninNum(pNtkG, iObjG) ) + continue; + Vec_IntPush( vCutsF, iObjF ); + Vec_IntPush( vCutsG, iObjG ); + } + Vec_IntFree( vNamesInvF ); + Vec_IntFree( vMarksF ); + Vec_IntFree( vMarksG ); + Vec_IntFree( vLevelsG ); + *pvCutsF = vCutsF; + *pvCutsG = vCutsG; +} +Vec_Int_t * Acb_NtkCollectTargetCutPool( Acb_Ntk_t * p, int iPo, int nLimit ) +{ + Vec_Int_t * vMarks = Vec_IntStart( Acb_NtkObjNumMax(p) ); + Vec_Int_t * vLevels = Vec_IntStartFull( Acb_NtkObjNumMax(p) ); + Vec_Int_t * vPool = Vec_IntAlloc( nLimit ); + int iRoot, iObj, Level, LevelMax; + assert( iPo >= 0 && iPo < Acb_NtkCoNum(p) ); + iRoot = Acb_ObjFanin( p, Acb_NtkCo(p, iPo), 0 ); + Acb_NtkMarkCone_rec( p, iRoot, vMarks ); + LevelMax = Acb_NtkConeLevel_rec( p, iRoot, vMarks, vLevels ); + Acb_NtkForEachNodeReverse( p, iObj ) + { + if ( Vec_IntSize(vPool) >= nLimit ) + break; + if ( !Vec_IntEntry(vMarks, iObj) || !Acb_NtkObjIsCutCandBasic(p, iObj) ) + continue; + Level = Vec_IntEntry(vLevels, iObj); + if ( Level < Abc_MaxInt(2, LevelMax/5) || Level > Abc_MaxInt(3, 4*LevelMax/5) ) + continue; + Vec_IntPush( vPool, iObj ); + } + Vec_IntFree( vMarks ); + Vec_IntFree( vLevels ); + return vPool; +} +int Acb_NtkSimSignaturesEqual( Vec_Wrd_t * vSimsF, Vec_Wrd_t * vSimsG, int nWords, int iCandF, int iCandG ) +{ + word * pF0 = Vec_WrdEntryP( vSimsF, (2*iCandF + 0) * nWords ); + word * pF1 = Vec_WrdEntryP( vSimsF, (2*iCandF + 1) * nWords ); + word * pG0 = Vec_WrdEntryP( vSimsG, (2*iCandG + 0) * nWords ); + word * pG1 = Vec_WrdEntryP( vSimsG, (2*iCandG + 1) * nWords ); + int w; + for ( w = 0; w < nWords; w++ ) + if ( pF0[w] != pG0[w] || pF1[w] != pG1[w] ) + return 0; + return 1; +} +void Acb_NtkCollectTargetCutCandidatesSim( Acb_Ntk_t * pNtkF, Acb_Ntk_t * pNtkG, int iPo, Vec_Int_t ** pvCutsF, Vec_Int_t ** pvCutsG, int nLimit, int fVerbose ) +{ + Vec_Int_t * vPoolF = Acb_NtkCollectTargetCutPool( pNtkF, iPo, 192 ); + Vec_Int_t * vPoolG = Acb_NtkCollectTargetCutPool( pNtkG, iPo, 384 ); + Vec_Int_t * vCutsF = Vec_IntAlloc( nLimit ); + Vec_Int_t * vCutsG = Vec_IntAlloc( nLimit ); + Vec_Int_t * vUsedF = Vec_IntStart( Vec_IntSize(vPoolF) ); + Gia_Man_t * pGiaF = NULL, * pGiaG = NULL; + Vec_Wrd_t * vSimsF = NULL, * vSimsG = NULL; + int i, k, iObjF, iObjG, nWords = 16; + if ( Vec_IntSize(vPoolF) == 0 || Vec_IntSize(vPoolG) == 0 ) + goto finish; + pGiaF = Acb_NtkGiaDeriveDualTargets( pNtkF, vPoolF ); + pGiaG = Acb_NtkGiaDeriveDualTargets( pNtkG, vPoolG ); + if ( Gia_ManCiNum(pGiaF) != Gia_ManCiNum(pGiaG) ) + goto finish; + Abc_Random(1); + Vec_WrdFreeP( &pGiaF->vSimsPi ); + Vec_WrdFreeP( &pGiaG->vSimsPi ); + pGiaF->vSimsPi = Vec_WrdStartRandom( Gia_ManCiNum(pGiaF) * nWords ); + pGiaG->vSimsPi = Vec_WrdDup( pGiaF->vSimsPi ); + vSimsF = Gia_ManSimPatSim( pGiaF ); + vSimsG = Gia_ManSimPatSim( pGiaG ); + Vec_IntForEachEntry( vPoolG, iObjG, i ) + { + if ( Vec_IntSize(vCutsG) >= nLimit ) + break; + Vec_IntForEachEntry( vPoolF, iObjF, k ) + { + if ( Vec_IntEntry(vUsedF, k) ) + continue; + if ( Acb_ObjType(pNtkF, iObjF) != Acb_ObjType(pNtkG, iObjG) ) + continue; + if ( Acb_ObjFaninNum(pNtkF, iObjF) != Acb_ObjFaninNum(pNtkG, iObjG) ) + continue; + if ( !Acb_NtkSimSignaturesEqual(vSimsF, vSimsG, nWords, k, i) ) + continue; + Vec_IntPush( vCutsF, iObjF ); + Vec_IntPush( vCutsG, iObjG ); + Vec_IntWriteEntry( vUsedF, k, 1 ); + break; + } + } +finish: + if ( fVerbose ) + printf( "Hard-output simulation cutpoint candidates: F pool = %d. G pool = %d. matched = %d.\n", + Vec_IntSize(vPoolF), Vec_IntSize(vPoolG), Vec_IntSize(vCutsF) ); + Vec_IntFree( vPoolF ); + Vec_IntFree( vPoolG ); + Vec_IntFree( vUsedF ); + Vec_WrdFreeP( &vSimsF ); + Vec_WrdFreeP( &vSimsG ); + if ( pGiaF ) + { + Vec_WrdFreeP( &pGiaF->vSimsPi ); + Gia_ManStop( pGiaF ); + } + if ( pGiaG ) + { + Vec_WrdFreeP( &pGiaG->vSimsPi ); + Gia_ManStop( pGiaG ); + } + *pvCutsF = vCutsF; + *pvCutsG = vCutsG; +} +int * Acb_NtkSolveTargetCutpoints( Acb_Ntk_t * pNtkF, Acb_Ntk_t * pNtkG, int iPo, int fVerbose, int * pStatus, int nSatTimeLimit ) +{ + Vec_Int_t * vCandF = NULL, * vCandG = NULL, * vProofF = Vec_IntAlloc( 64 ), * vProofG = Vec_IntAlloc( 64 ); + Vec_Int_t * vOneF = Vec_IntAlloc( 1 ), * vOneG = Vec_IntAlloc( 1 ), * vTargetF = Vec_IntAlloc( 1 ), * vTargetG = Vec_IntAlloc( 1 ); + Gia_Man_t * pGiaF = NULL, * pGiaG = NULL, * pGiaMiter = NULL, * pTemp = NULL; + int i, iObjF, iObjG, StatusOne = -1, StatusTop = -1, nTried = 0, nSat = 0, nUndec = 0; + int * pModel = NULL; + abctime clk = Abc_Clock(), clkLimit = nSatTimeLimit > 0 ? Abc_Clock() + nSatTimeLimit * CLOCKS_PER_SEC : 0; + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + if ( iPo < 0 || iPo >= Acb_NtkCoNum(pNtkF) || iPo >= Acb_NtkCoNum(pNtkG) ) + goto cleanup; + Acb_NtkCollectTargetCutCandidates( pNtkF, pNtkG, iPo, &vCandF, &vCandG, 96 ); + if ( Vec_IntSize(vCandF) == 0 ) + { + Vec_IntFreeP( &vCandF ); + Vec_IntFreeP( &vCandG ); + Acb_NtkCollectTargetCutCandidatesSim( pNtkF, pNtkG, iPo, &vCandF, &vCandG, 96, fVerbose ); + } + if ( fVerbose ) + printf( "Trying hard-output internal cutpoints: output = %d. candidates = %d.\n", iPo, Vec_IntSize(vCandF) ); + Vec_IntForEachEntryTwo( vCandF, vCandG, iObjF, iObjG, i ) + { + if ( clkLimit && Abc_Clock() > clkLimit ) + break; + Vec_IntClear( vOneF ); + Vec_IntClear( vOneG ); + Vec_IntPush( vOneF, iObjF ); + Vec_IntPush( vOneG, iObjG ); + pGiaF = Acb_NtkGiaDeriveDualTargets( pNtkF, vOneF ); + pGiaG = Acb_NtkGiaDeriveDualTargets( pNtkG, vOneG ); + pGiaMiter = Acb_NtkGiaDeriveMiter( pGiaF, pGiaG, 2 ); + if ( Gia_ManAndNum(pGiaMiter) > 5000 ) + { + pTemp = Gia_ManCompress2( pGiaMiter, 0, 0 ); + if ( pTemp ) + { + Gia_ManStop( pGiaMiter ); + pGiaMiter = pTemp; + pTemp = NULL; + } + } + nTried++; + StatusOne = -1; + pModel = Acb_NtkSolveCadicalPoSweepLabel( pGiaMiter, fVerbose && nTried <= 8, &StatusOne, 8, 100000, "Hard-output cutpoint proof", 0 ); + if ( pModel ) + { + ABC_FREE( pModel ); + pModel = NULL; + } + if ( StatusOne == 1 ) + { + Vec_IntPush( vProofF, iObjF ); + Vec_IntPush( vProofG, iObjG ); + } + else if ( StatusOne == 0 ) + nSat++; + else + nUndec++; + Gia_ManStop( pGiaF ); + Gia_ManStop( pGiaG ); + Gia_ManStop( pGiaMiter ); + pGiaF = pGiaG = pGiaMiter = NULL; + if ( Vec_IntSize(vProofF) >= 48 ) + break; + } + if ( fVerbose ) + printf( "Hard-output cutpoints: tried = %d. proven = %d. bad = %d. undecided = %d.\n", + nTried, Vec_IntSize(vProofF), nSat, nUndec ); + if ( Vec_IntSize(vProofF) == 0 ) + goto cleanup; + Vec_IntPush( vTargetF, Acb_ObjFanin(pNtkF, Acb_NtkCo(pNtkF, iPo), 0) ); + Vec_IntPush( vTargetG, Acb_ObjFanin(pNtkG, Acb_NtkCo(pNtkG, iPo), 0) ); + pGiaF = Acb_NtkGiaDeriveDualTargetsCutLeaves( pNtkF, vTargetF, vProofF ); + pGiaG = Acb_NtkGiaDeriveDualTargetsCutLeaves( pNtkG, vTargetG, vProofG ); + pGiaMiter = Acb_NtkGiaDeriveMiter( pGiaF, pGiaG, 2 ); + if ( Gia_ManAndNum(pGiaMiter) > 5000 ) + { + pTemp = Gia_ManCompress2( pGiaMiter, 1, 0 ); + if ( pTemp ) + { + Gia_ManStop( pGiaMiter ); + pGiaMiter = pTemp; + pTemp = NULL; + } + } + if ( fVerbose ) + printf( "Hard-output top cutpoint miter: output = %d. cutpoints = %d. And = %d. PO = %d.\n", + iPo, Vec_IntSize(vProofF), Gia_ManAndNum(pGiaMiter), Gia_ManCoNum(pGiaMiter) ); + pModel = Acb_NtkSolveCadicalPoSweepLabel( pGiaMiter, fVerbose, &StatusTop, nSatTimeLimit, 250000, "Hard-output top cutpoint miter", 0 ); + if ( StatusTop == 1 ) + { + if ( pStatus ) + *pStatus = ACB_XEC_EQ; + printf( "The hard output %d is UNSAT by internal cutpoint abstraction. ", iPo ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + else + { + if ( pModel ) + { + ABC_FREE( pModel ); + pModel = NULL; + } + if ( fVerbose ) + printf( "Hard-output cutpoint abstraction did not prove output %d; SAT on abstraction may be spurious.\n", iPo ); + } +cleanup: + Vec_IntFreeP( &vCandF ); + Vec_IntFreeP( &vCandG ); + Vec_IntFree( vProofF ); + Vec_IntFree( vProofG ); + Vec_IntFree( vOneF ); + Vec_IntFree( vOneG ); + Vec_IntFree( vTargetF ); + Vec_IntFree( vTargetG ); + if ( pGiaF ) + Gia_ManStop( pGiaF ); + if ( pGiaG ) + Gia_ManStop( pGiaG ); + if ( pGiaMiter ) + Gia_ManStop( pGiaMiter ); + if ( pTemp ) + Gia_ManStop( pTemp ); + return pModel; +} +int * Acb_NtkSolveTargetCutpointList( Acb_Ntk_t * pNtkF, Acb_Ntk_t * pNtkG, Vec_Int_t * vHardPos, int fVerbose, int * pStatus, int nTotalLimit, int nPoLimit ) +{ + int i, iPo, StatusOne = -1, fUndec = 0; + int * pModel = NULL; + abctime clk = Abc_Clock(); + abctime clkLimit = nTotalLimit > 0 ? clk + nTotalLimit * CLOCKS_PER_SEC : 0; + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + if ( vHardPos == NULL || Vec_IntSize(vHardPos) == 0 ) + return NULL; + if ( fVerbose ) + printf( "Trying hard-output cutpoint abstraction for %d collected outputs.\n", Vec_IntSize(vHardPos) ); + Vec_IntForEachEntry( vHardPos, iPo, i ) + { + int nLimit = nPoLimit; + if ( clkLimit ) + { + int nRemain = (int)((clkLimit - Abc_Clock()) / CLOCKS_PER_SEC); + if ( nRemain <= 0 ) + { + fUndec = 1; + break; + } + nLimit = nPoLimit > 0 ? Abc_MinInt( nPoLimit, nRemain ) : nRemain; + } + StatusOne = -1; + pModel = Acb_NtkSolveTargetCutpoints( pNtkF, pNtkG, iPo, fVerbose, &StatusOne, nLimit ); + if ( StatusOne == 0 ) + { + if ( pStatus ) + *pStatus = ACB_XEC_NEQ; + return pModel; + } + if ( pModel ) + { + ABC_FREE( pModel ); + pModel = NULL; + } + if ( StatusOne != 1 ) + { + fUndec = 1; + if ( fVerbose ) + printf( "Hard-output cutpoint abstraction: output %d remains UNDECIDED; stopping list proof.\n", iPo ); + break; + } + } + if ( pStatus ) + *pStatus = fUndec ? -1 : 1; + printf( "The collected hard outputs are %s by cutpoint abstraction. ", fUndec ? "UNDECIDED" : "UNSAT" ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + return NULL; +} +int * Acb_NtkSolveCadicalPoSweepLabel( Gia_Man_t * p, int fVerbose, int * pStatus, int nSatTimeLimit, int nPoConfLimit, char * pLabel, int fStopOnUndec ) +{ + Gia_Man_t * pGiaCnf = p; + Aig_Man_t * pMan = NULL; + Cnf_Dat_t * pCnf = NULL; + cadical_solver * pSat = NULL; + Acb_SplitPoOrder_t * pOrder = NULL; + int i, k, Lit, Ret, Status, * pModel = NULL; + int nSat = 0, nUnsat = 0, nUndec = 0; + int fManyOutputs = Gia_ManCoNum(p) > 64; + int nMaxManyUndec = fManyOutputs ? 12 : Gia_ManCoNum(p); + abctime clk = Abc_Clock(); + (void)nSatTimeLimit; + if ( Gia_ManCoNum(p) == 0 ) + { + if ( pStatus ) + *pStatus = ACB_XEC_EQ; + return NULL; + } + pMan = Gia_ManToAig( pGiaCnf, 0 ); + pCnf = pMan ? Cnf_Derive( pMan, Aig_ManCoNum(pMan) ) : NULL; + pSat = pCnf ? cadical_solver_new() : NULL; + if ( pCnf == NULL || pSat == NULL ) + goto cleanup; + if ( !Acb_CnfWriteIntoCadical( pSat, pCnf ) ) + goto cleanup; + pOrder = ABC_ALLOC( Acb_SplitPoOrder_t, Gia_ManCoNum(p) ); + Acb_NtkSortSplitOutputs( p, pOrder ); + if ( fVerbose ) + printf( "%s: smallest cone %d ANDs, largest cone %d ANDs.\n", + pLabel, pOrder[0].nAnds, pOrder[Gia_ManCoNum(p)-1].nAnds ); + for ( k = 0; k < Gia_ManCoNum(p); k++ ) + { + i = pOrder[k].iPo; + Ret = Acb_CnfCoDriverLit( pCnf, i, &Lit ); + if ( fVerbose ) + printf( "%s: trying output %d (%d/%d), cone = %d ANDs.\n", + pLabel, i, k + 1, Gia_ManCoNum(p), pOrder[k].nAnds ); + if ( Ret == -2 ) + { + nUndec++; + if ( fVerbose ) + printf( "%s: output %d UNDECIDED because its CNF driver is unmapped.\n", pLabel, i ); + if ( fStopOnUndec ) + break; + continue; + } + if ( Ret == -1 ) + { + nUnsat++; + if ( fVerbose ) + printf( "%s: output %d UNSAT because it is constant 0.\n", pLabel, i ); + continue; + } + if ( Ret == 0 ) + { + nSat++; + pModel = ABC_CALLOC( int, Aig_ManCiNum(pMan) ); + if ( pStatus ) + *pStatus = ACB_XEC_NEQ; + printf( "%s found SAT on output %d. ", pLabel, pOrder[k].iPo ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + goto cleanup; + } + Status = cadical_solver_solve( pSat, &Lit, &Lit + 1, (ABC_INT64_T)nPoConfLimit, 0, 0, 0 ); + if ( Status == 1 ) + { + Aig_Obj_t * pObjCi; + nSat++; + pModel = ABC_ALLOC( int, Aig_ManCiNum(pMan) ); + Aig_ManForEachCi( pMan, pObjCi, i ) + pModel[i] = cadical_solver_get_var_value( pSat, pCnf->pVarNums[pObjCi->Id] ); + if ( pStatus ) + *pStatus = ACB_XEC_NEQ; + printf( "%s found SAT on output %d. ", pLabel, pOrder[k].iPo ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + goto cleanup; + } + if ( Status == -1 ) + { + nUnsat++; + if ( fVerbose ) + printf( "%s: output %d UNSAT. conflicts = %d. learned = %d.\n", + pLabel, i, cadical_solver_nconflicts(pSat), cadical_solver_nlearned(pSat) ); + } + else + { + nUndec++; + if ( fVerbose ) + printf( "%s: output %d UNDECIDED. conflicts = %d. learned = %d.\n", + pLabel, i, cadical_solver_nconflicts(pSat), cadical_solver_nlearned(pSat) ); + if ( fStopOnUndec ) + { + if ( fVerbose ) + printf( "%s: stopping after first UNDECIDED output because this proof needs every output UNSAT.\n", pLabel ); + break; + } + if ( (fManyOutputs || Gia_ManCoNum(p) > 16) && k + 1 < Gia_ManCoNum(p) ) + { + cadical_solver_delete( pSat ); + pSat = cadical_solver_new(); + if ( pSat == NULL || !Acb_CnfWriteIntoCadical( pSat, pCnf ) ) + goto cleanup; + if ( fVerbose ) + printf( "%s: reset solver after undecided output %d to avoid carrying unrelated learned clauses.\n", + pLabel, i ); + } + if ( fManyOutputs && nUndec >= nMaxManyUndec && nUnsat == 0 ) + { + if ( fVerbose ) + printf( "%s: stopping early after %d many-output UNDECIDED probes; moving to next XEC method.\n", + pLabel, nUndec ); + break; + } + } + } + if ( pStatus ) + *pStatus = nUndec ? -1 : 1; + printf( "%s is %s. ", pLabel, nUndec ? "UNDECIDED" : "UNSAT" ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); +cleanup: + if ( fVerbose && pSat ) + printf( "%s stats: SAT = %d. UNSAT = %d. UNDEC = %d. conflicts = %d. learned = %d.\n", + pLabel, nSat, nUnsat, nUndec, cadical_solver_nconflicts(pSat), cadical_solver_nlearned(pSat) ); + if ( pOrder ) + ABC_FREE( pOrder ); + if ( pSat ) + cadical_solver_delete( pSat ); + if ( pCnf ) + Cnf_DataFree( pCnf ); + if ( pMan ) + Aig_ManStop( pMan ); + return pModel; +} +void Acb_NtkSortSplitOutputsLimit( Gia_Man_t * p, Acb_SplitPoOrder_t * pOrder, int nPos ) +{ + Gia_Obj_t * pObj; + Acb_SplitPoOrder_t Temp; + int i, k, iObj; + assert( nPos > 0 && nPos <= Gia_ManCoNum(p) ); + for ( i = 0; i < nPos; i++ ) + { + pObj = Gia_ManCo( p, i ); + iObj = Gia_ObjId( p, pObj ); + pOrder[i].iPo = i; + pOrder[i].nAnds = Gia_ManConeSize( p, &iObj, 1 ); + } + for ( i = 1; i < nPos; i++ ) + { + Temp = pOrder[i]; + for ( k = i; k > 0 && pOrder[k-1].nAnds > Temp.nAnds; k-- ) + pOrder[k] = pOrder[k-1]; + pOrder[k] = Temp; + } +} +void Acb_NtkSortSplitOutputs( Gia_Man_t * p, Acb_SplitPoOrder_t * pOrder ) +{ + Acb_NtkSortSplitOutputsLimit( p, pOrder, Gia_ManCoNum(p) ); +} +int Acb_GiaRequiredLiteralContradiction( Gia_Man_t * p, int iPo, int fVerbose ) +{ + Vec_Int_t * vStack = Vec_IntAlloc( 1024 ); + Vec_Int_t * vAssign = Vec_IntStartFull( Gia_ManObjNum(p) ); + Gia_Obj_t * pObj; + int Lit, Var, Sign, Val, nSeen = 0, Ret = ACB_XEC_UNDEC; + if ( iPo < 0 || iPo >= Gia_ManCoNum(p) ) + { + Vec_IntFree( vStack ); + Vec_IntFree( vAssign ); + return Ret; + } + Vec_IntPush( vStack, Gia_ObjFaninLit0p(p, Gia_ManCo(p, iPo)) ); + while ( Vec_IntSize(vStack) > 0 ) + { + Lit = Vec_IntPop( vStack ); + Var = Abc_Lit2Var(Lit); + Sign = Abc_LitIsCompl(Lit); + if ( Var == 0 ) + { + if ( Sign == 0 ) + { + Ret = ACB_XEC_EQ; + break; + } + continue; + } + Val = Vec_IntEntry( vAssign, Var ); + if ( Val >= 0 ) + { + if ( Val != (Sign ? 0 : 1) ) + { + Ret = ACB_XEC_EQ; + break; + } + continue; + } + Vec_IntWriteEntry( vAssign, Var, Sign ? 0 : 1 ); + nSeen++; + pObj = Gia_ManObj( p, Var ); + if ( !Sign && Gia_ObjIsAnd(pObj) ) + { + Vec_IntPush( vStack, Gia_ObjFaninLit0(pObj, Var) ); + Vec_IntPush( vStack, Gia_ObjFaninLit1(pObj, Var) ); + } + if ( nSeen > 200000 ) + break; + } + if ( fVerbose ) + printf( "Required-literal structural proof: output %d %s. required literals = %d.\n", + iPo, Ret == ACB_XEC_EQ ? "UNSAT" : "inconclusive", nSeen ); + Vec_IntFree( vStack ); + Vec_IntFree( vAssign ); + return Ret; +} +Vec_Int_t * Acb_GiaCollectRequiredLiteralAssigns( Gia_Man_t * p, int iPo, int fVerbose, int * pStatus ); +int Acb_GiaRequiredLiteralUnitProof( Gia_Man_t * p, int iPo, int fVerbose, int nSatTimeLimit ) +{ + Vec_Int_t * vAssign = NULL, * vReq = NULL; + Gia_Obj_t * pObj; + int i, Val, Status = ACB_XEC_UNDEC, nReq = 0; + if ( p == NULL || iPo < 0 || iPo >= Gia_ManCoNum(p) || Gia_ManAndNum(p) > 30000 ) + return ACB_XEC_UNDEC; + vAssign = Acb_GiaCollectRequiredLiteralAssigns( p, iPo, 0, &Status ); + if ( Status == ACB_XEC_EQ ) + { + Vec_IntFreeP( &vAssign ); + return Status; + } + if ( vAssign == NULL ) + return ACB_XEC_UNDEC; + vReq = Vec_IntAlloc( 100 ); + Gia_ManForEachObj1( p, pObj, i ) + { + Val = Vec_IntEntry( vAssign, i ); + if ( Val < 0 ) + continue; + Vec_IntPush( vReq, Abc_Var2Lit(i, Val ? 0 : 1) ); + nReq++; + } + if ( nReq < 2 || nReq > 512 ) + Status = ACB_XEC_UNDEC; + else + { + Status = Acb_GiaSolveObligationListUnit( p, vReq, 0, nSatTimeLimit, NULL ); + if ( fVerbose ) + printf( "Required-literal unit proof: output %d %s. required literals = %d.\n", + iPo, Status == ACB_XEC_EQ ? "UNSAT" : "inconclusive", nReq ); + } + Vec_IntFreeP( &vAssign ); + Vec_IntFreeP( &vReq ); + return Status; +} +Vec_Int_t * Acb_GiaCollectRequiredLiteralAssigns( Gia_Man_t * p, int iPo, int fVerbose, int * pStatus ) +{ + Vec_Int_t * vStack = Vec_IntAlloc( 1024 ); + Vec_Int_t * vAssign = Vec_IntStartFull( Gia_ManObjNum(p) ); + Gia_Obj_t * pObj; + int Lit, Var, Sign, Val, nSeen = 0; + *pStatus = ACB_XEC_UNDEC; + if ( iPo < 0 || iPo >= Gia_ManCoNum(p) ) + { + Vec_IntFree( vStack ); + return vAssign; + } + Vec_IntPush( vStack, Gia_ObjFaninLit0p(p, Gia_ManCo(p, iPo)) ); + while ( Vec_IntSize(vStack) > 0 ) + { + Lit = Vec_IntPop( vStack ); + Var = Abc_Lit2Var(Lit); + Sign = Abc_LitIsCompl(Lit); + if ( Var == 0 ) + { + if ( Sign == 0 ) + { + *pStatus = ACB_XEC_EQ; + break; + } + continue; + } + Val = Vec_IntEntry( vAssign, Var ); + if ( Val >= 0 ) + { + if ( Val != (Sign ? 0 : 1) ) + { + *pStatus = ACB_XEC_EQ; + break; + } + continue; + } + Vec_IntWriteEntry( vAssign, Var, Sign ? 0 : 1 ); + nSeen++; + pObj = Gia_ManObj( p, Var ); + if ( !Sign && Gia_ObjIsAnd(pObj) ) + { + Vec_IntPush( vStack, Gia_ObjFaninLit0(pObj, Var) ); + Vec_IntPush( vStack, Gia_ObjFaninLit1(pObj, Var) ); + } + if ( nSeen > 200000 ) + break; + } + if ( fVerbose && *pStatus == ACB_XEC_EQ ) + printf( "Required-literal cofactor: output %d is UNSAT before cofactoring. required literals = %d.\n", iPo, nSeen ); + Vec_IntFree( vStack ); + return vAssign; +} +static int Acb_XecRemainingTimeLimit( abctime clkLimit, int nCap ) +{ + if ( clkLimit == 0 ) + return nCap; + if ( Abc_Clock() >= clkLimit ) + return 0; + return Abc_MinInt( nCap, (int)((clkLimit - Abc_Clock()) / CLOCKS_PER_SEC) ); +} +static int Acb_XecLocalConeStatus( int nSat, int nUnsat, int nSkipUnsat, int nUndec, int nOuts ) +{ + if ( nSat ) + return ACB_XEC_NEQ; + if ( nUndec == 0 ) + return nUnsat + nSkipUnsat == nOuts ? ACB_XEC_EQ : ACB_XEC_UNDEC; + if ( nUndec == 1 && nUnsat + nSkipUnsat == nOuts - 1 ) + return ACB_XEC_ONE_HARD; + if ( nUnsat + nSkipUnsat + nUndec == nOuts && nUndec > 1 ) + return ACB_XEC_MANY_HARD; + return ACB_XEC_UNDEC; +} +static int Acb_XecLocalConeKeepSweeping( int fQuickMany, int fMediumSweep, int fResumeSweep, int nUndec, int nMaxUndec ) +{ + if ( nUndec >= nMaxUndec ) + return 0; + return fQuickMany || fMediumSweep || fResumeSweep; +} +static Gia_Man_t * Acb_XecLocalConePrepare( Gia_Man_t * p, int iPo, Vec_Int_t ** pvSuppMap, Acb_XecCtx_t * pCtx, int fVerbose ) +{ + Gia_Man_t * pOne, * pTemp; + Vec_Int_t * vSuppMap = Vec_IntAlloc( 1000 ); + pOne = Acb_GiaDupOnePoTrimmed( p, iPo, vSuppMap ); + if ( pOne == NULL ) + { + Vec_IntFree( vSuppMap ); + *pvSuppMap = NULL; + return NULL; + } + if ( Gia_ManAndNum(pOne) > pCtx->Pars.nLocalConeCompressAndMin ) + { + pTemp = Gia_ManCompress2( pOne, 1, 0 ); + if ( pTemp ) + { + Gia_ManStop( pOne ); + pOne = pTemp; + } + } + if ( Vec_IntSize(vSuppMap) <= 64 && Gia_ManAndNum(pOne) <= 8000 ) + { + pTemp = Acb_XecGiaSmallConeXorRewrite( pOne, fVerbose ); + if ( pTemp ) + { + Gia_ManStop( pOne ); + pOne = pTemp; + } + } + *pvSuppMap = vSuppMap; + return pOne; +} +static int * Acb_XecLocalConeExpandModel( Gia_Man_t * p, Gia_Man_t * pOne, Vec_Int_t * vSuppMap, int * pModel ) +{ + int * pModelFull = NULL; + int i, iObj; + if ( pModel == NULL ) + return NULL; + if ( Vec_IntSize(vSuppMap) != Gia_ManCiNum(pOne) ) + { + ABC_FREE( pModel ); + return NULL; + } + pModelFull = ABC_CALLOC( int, Gia_ManCiNum(p) ); + Vec_IntForEachEntry( vSuppMap, iObj, i ) + if ( Gia_ObjIsCi(Gia_ManObj(p, iObj)) ) + pModelFull[Gia_ObjCioId(Gia_ManObj(p, iObj))] = pModel[i]; + ABC_FREE( pModel ); + return pModelFull; +} +static int Acb_XecLocalConeProof( Gia_Man_t * pOne, int nSuppSize, int nLimit, abctime clkLimit, int fVerbose, int ** ppModel ) +{ + int Status = ACB_XEC_UNDEC; + int nExhLimit, nFrontLimit; + *ppModel = NULL; + if ( Gia_ManCoNum(pOne) == 1 && Gia_ManAndNum(pOne) > 0 && + (Gia_ManAndNum(pOne) >= 1000 || nSuppSize >= 32) ) + { + int nSimWords = Gia_ManAndNum(pOne) <= 5000 ? 256 : 64; + *ppModel = Acb_GiaFindOnePoSimCex( pOne, nSimWords, fVerbose, "Local-cone hard-output" ); + if ( *ppModel ) + return ACB_XEC_NEQ; + } + if ( Gia_ManAndNum(pOne) <= 8000 ) + Status = Acb_GiaRequiredLiteralContradiction( pOne, 0, fVerbose ); + if ( Status == ACB_XEC_UNDEC && Gia_ManAndNum(pOne) <= 8000 && nSuppSize >= 40 && nSuppSize <= 64 ) + Status = Acb_GiaRequiredLiteralUnitProof( pOne, 0, fVerbose, Abc_MinInt(nLimit, 120) ); + if ( Status == ACB_XEC_UNDEC && Gia_ManAndNum(pOne) < 5000 && nSuppSize >= 32 && nSuppSize <= 64 ) + { + nFrontLimit = Acb_XecRemainingTimeLimit( clkLimit, nLimit > 120 ? 180 : 90 ); + if ( nFrontLimit >= 20 ) + Status = Acb_GiaSolveSmallConeInternalFrontier( pOne, fVerbose, nFrontLimit ); + } + if ( Status == ACB_XEC_UNDEC && Gia_ManAndNum(pOne) <= 5000 ) + { + nExhLimit = Acb_XecRemainingTimeLimit( clkLimit, 600 ); + if ( nExhLimit >= 30 ) + Status = Acb_XecGiaSolveSmallConeExhaustive( pOne, fVerbose, nExhLimit ); + } + if ( Status == ACB_XEC_UNDEC ) + *ppModel = Acb_NtkSolveCadicalLimit( pOne, 0, 0, &Status, nLimit, NULL, 0 ); + return Status; +} +int * Acb_NtkSolveCadicalLocalConeSweepSkipCtx( Gia_Man_t * p, int fVerbose, int * pStatus, int nSatTimeLimit, int nPoTimeLimit, Vec_Int_t * vSkipUnsat, Acb_XecCtx_t * pCtx ) +{ + Acb_SplitPoOrder_t * pOrder; + int i, k, StatusOne, StatusFinal, nSat = 0, nUnsat = 0, nUndec = 0, iLastUndec = -1; + int nSkipUnsat = vSkipUnsat ? Vec_IntSize(vSkipUnsat) : 0; + int fDisableQuickMany = nPoTimeLimit < 0; + int fResumeSweep = vSkipUnsat != NULL; + int fStopAfterFirstHard, fMediumSweep, fQuickMany, nProbeLimit, nHardProbeLimit, nMaxUndec; + int * pModel = NULL; + abctime clk = Abc_Clock(); + abctime clkLimit = nSatTimeLimit > 0 ? clk + nSatTimeLimit * CLOCKS_PER_SEC : 0; + assert( pCtx != NULL ); + Acb_XecCtxResetLocalSweep( pCtx ); + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + if ( Gia_ManCoNum(p) == 0 ) + { + if ( pStatus ) + *pStatus = ACB_XEC_EQ; + return NULL; + } + if ( fDisableQuickMany ) + nPoTimeLimit = -nPoTimeLimit; + fStopAfterFirstHard = pCtx->Pars.nLocalMediumPoMin == ABC_INFINITY; + fMediumSweep = !fStopAfterFirstHard && Gia_ManCoNum(p) > pCtx->Pars.nLocalMediumPoMin && Gia_ManCoNum(p) <= pCtx->Pars.nLocalMediumPoMax; + fQuickMany = !fStopAfterFirstHard && !fDisableQuickMany && Gia_ManCoNum(p) > pCtx->Pars.nLocalManyPoThreshold; + nMaxUndec = fStopAfterFirstHard ? 1 : (fResumeSweep ? Gia_ManCoNum(p) : (fQuickMany ? pCtx->Pars.nLocalQuickMaxUndec : (fMediumSweep ? Gia_ManCoNum(p) : 1))); + nProbeLimit = nPoTimeLimit; + if ( fQuickMany ) + nProbeLimit = nPoTimeLimit > 0 ? Abc_MinInt( nPoTimeLimit, pCtx->Pars.nLocalQuickPoSec ) : pCtx->Pars.nLocalQuickPoSec; + else if ( fMediumSweep ) + nProbeLimit = nPoTimeLimit > 0 ? Abc_MinInt( nPoTimeLimit, pCtx->Pars.nLocalMediumPoSec ) : pCtx->Pars.nLocalMediumPoSec; + nHardProbeLimit = fResumeSweep ? nPoTimeLimit : (fQuickMany ? pCtx->Pars.nLocalQuickPoSec : (fMediumSweep ? pCtx->Pars.nLocalMediumHardPoSec : nPoTimeLimit)); + pOrder = ABC_ALLOC( Acb_SplitPoOrder_t, Gia_ManCoNum(p) ); + Acb_NtkSortSplitOutputs( p, pOrder ); + if ( fVerbose ) + printf( "Local-cone CaDiCaL sweep: outputs = %d. skip = %d. smallest cone %d ANDs, largest cone %d ANDs. total limit = %d sec, per-output limit = %d sec%s.\n", + Gia_ManCoNum(p), nSkipUnsat, pOrder[0].nAnds, pOrder[Gia_ManCoNum(p)-1].nAnds, nSatTimeLimit, nProbeLimit, + fQuickMany ? " (SAT-hunting quick probe for many-output miter)" : "" ); + for ( k = 0; k < Gia_ManCoNum(p); k++ ) + { + Gia_Man_t * pOne; + Vec_Int_t * vSuppMap = NULL; + abctime clkOut = Abc_Clock(); + int nBaseLimit = nUndec > 0 ? nHardProbeLimit : nProbeLimit; + int nLimit = Acb_XecRemainingTimeLimit( clkLimit, nBaseLimit ); + int nSuppSize; + i = pOrder[k].iPo; + if ( vSkipUnsat && Vec_IntFind(vSkipUnsat, i) >= 0 ) + { + if ( fVerbose ) + printf( "Local-cone CaDiCaL: skipping output %d because it is already proven UNSAT.\n", i ); + continue; + } + if ( clkLimit && nLimit <= 0 ) + { + nUndec++; + iLastUndec = i; + Vec_IntPushUnique( pCtx->vLastHardPos, i ); + break; + } + pOne = Acb_XecLocalConePrepare( p, i, &vSuppMap, pCtx, fVerbose ); + if ( pOne == NULL ) + { + nUndec++; + iLastUndec = i; + Vec_IntPushUnique( pCtx->vLastHardPos, i ); + break; + } + nSuppSize = Vec_IntSize( vSuppMap ); + if ( Gia_ManCiNum(p) <= 64 && Gia_ManCoNum(p) >= 16 && Gia_ManCoNum(p) <= 64 && + nUnsat >= 3 && nSuppSize >= 40 && nSuppSize <= 56 && + Gia_ManAndNum(pOne) <= 5000 && nLimit <= 120 ) + nLimit = Abc_MaxInt( nLimit, Acb_XecRemainingTimeLimit( clkLimit, nUnsat >= 4 ? 420 : 180 ) ); + if ( fVerbose ) + printf( "Local-cone CaDiCaL: output %d (%d/%d), cone = %d ANDs, support = %d/%d PIs, limit = %d sec.\n", + i, k + 1, Gia_ManCoNum(p), Gia_ManAndNum(pOne), nSuppSize, Gia_ManCiNum(p), nLimit ); + StatusOne = Acb_XecLocalConeProof( pOne, nSuppSize, nLimit, clkLimit, fVerbose && (nUndec > 0 || nSuppSize >= 40), &pModel ); + if ( StatusOne == ACB_XEC_NEQ ) + { + nSat++; + pModel = Acb_XecLocalConeExpandModel( p, pOne, vSuppMap, pModel ); + if ( fVerbose ) + { + printf( "Local-cone CaDiCaL: output %d SAT. ", i ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clkOut ); + } + Gia_ManStop( pOne ); + Vec_IntFree( vSuppMap ); + break; + } + if ( pModel ) + { + ABC_FREE( pModel ); + pModel = NULL; + } + if ( StatusOne == ACB_XEC_EQ ) + { + nUnsat++; + Vec_IntPushUnique( pCtx->vLastProvenPos, i ); + if ( fVerbose ) + { + printf( "Local-cone CaDiCaL: output %d UNSAT. ", i ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clkOut ); + } + Gia_ManStop( pOne ); + Vec_IntFree( vSuppMap ); + continue; + } + nUndec++; + iLastUndec = i; + Vec_IntPushUnique( pCtx->vLastHardPos, i ); + if ( fVerbose ) + { + printf( "Local-cone CaDiCaL: output %d UNDECIDED. ", i ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clkOut ); + } + Gia_ManStop( pOne ); + Vec_IntFree( vSuppMap ); + if ( Acb_XecLocalConeKeepSweeping( fQuickMany, fMediumSweep, fResumeSweep, nUndec, nMaxUndec ) ) + { + if ( fVerbose ) + printf( "Local-cone CaDiCaL: continuing after hard output %d; undecided probes = %d/%d.\n", + i, nUndec, nMaxUndec ); + continue; + } + break; + } + StatusFinal = Acb_XecLocalConeStatus( nSat, nUnsat, nSkipUnsat, nUndec, Gia_ManCoNum(p) ); + if ( StatusFinal == ACB_XEC_ONE_HARD || StatusFinal == ACB_XEC_MANY_HARD ) + pCtx->LastHardPo = iLastUndec; + if ( pStatus ) + *pStatus = StatusFinal; + printf( "The networks are %s by local-cone CaDiCaL sweep. ", + nSat ? "NOT equivalent" : (nUndec ? "UNDECIDED" : "equivalent") ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + if ( fVerbose ) + { + printf( "Local-cone CaDiCaL stats: SAT = %d. UNSAT = %d. SKIP = %d. UNDEC = %d.\n", nSat, nUnsat, nSkipUnsat, nUndec ); + if ( StatusFinal == ACB_XEC_ONE_HARD ) + printf( "Local-cone CaDiCaL: only output %d remains hard; whole-miter CaDiCaL would duplicate this cone.\n", iLastUndec ); + else if ( StatusFinal == ACB_XEC_MANY_HARD ) + printf( "Local-cone CaDiCaL: %d outputs remain hard after complete short-probe sweep.\n", nUndec ); + } + ABC_FREE( pOrder ); + return pModel; +} +int * Acb_NtkSolveCadicalOdc( Gia_Man_t * p, int fVerbose, int * pStatus ) +{ + Gia_Man_t * pOne; + Acb_SplitPoOrder_t * pOrder; + int i, k, Status = -1, fOneUndef = 0, * pModel = NULL; + int nConeTimeLimit = 5; + int nTotalTimeLimit = 60; + abctime clk = Abc_Clock(); + if ( Gia_ManCoNum(p) == 0 ) + { + if ( pStatus ) + *pStatus = ACB_XEC_EQ; + printf( "The networks are equivalent by ODC CaDiCaL. " ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + return NULL; + } + pOrder = ABC_ALLOC( Acb_SplitPoOrder_t, Gia_ManCoNum(p) ); + Acb_NtkSortSplitOutputs( p, pOrder ); + if ( fVerbose ) + printf( "ODC CaDiCaL: solving one observable output cone at a time; cone limit = %d sec, total limit = %d sec.\n", + nConeTimeLimit, nTotalTimeLimit ); + for ( k = 0; k < Gia_ManCoNum(p); k++ ) + { + if ( (Abc_Clock() - clk) / CLOCKS_PER_SEC >= nTotalTimeLimit ) + { + fOneUndef = 1; + break; + } + i = pOrder[k].iPo; + if ( fVerbose ) + printf( "ODC CaDiCaL output %d: cone ANDs = %d.\n", i, pOrder[k].nAnds ); + pOne = Gia_ManDupCones( p, &i, 1, 0 ); + pModel = Acb_NtkSolveCadicalLimit( pOne, 0, fVerbose, &Status, nConeTimeLimit, NULL, 0 ); + Gia_ManStop( pOne ); + if ( Status == 0 ) + { + ABC_FREE( pOrder ); + if ( pStatus ) + *pStatus = ACB_XEC_NEQ; + printf( "The networks are NOT equivalent by ODC CaDiCaL on output %d. ", i ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + return pModel; + } + if ( Status == -1 ) + fOneUndef = 1; + } + ABC_FREE( pOrder ); + if ( pStatus ) + *pStatus = fOneUndef ? -1 : 1; + printf( "The networks are %s by ODC CaDiCaL. ", fOneUndef ? "UNDECIDED" : "equivalent" ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + return NULL; +} + +int * Acb_NtkSolveSplit( Gia_Man_t * p, int fVerbose, int * pStatus ) +{ + Gia_Man_t * pOne; + Acb_SplitPoOrder_t * pOrder; + int i, k, Status, fOneUndef = 0, * pModel = NULL; + abctime clk = Abc_Clock(); + Abc_CexFreeP( &p->pCexComb ); + if ( Gia_ManCoNum(p) == 0 ) + { + if ( pStatus ) + *pStatus = ACB_XEC_EQ; + printf( "The networks are equivalent by split SAT. " ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + return NULL; + } + pOrder = ABC_ALLOC( Acb_SplitPoOrder_t, Gia_ManCoNum(p) ); + Acb_NtkSortSplitOutputs( p, pOrder ); + if ( fVerbose && Gia_ManCoNum(p) > 1 ) + printf( "Split SAT output order: smallest cone %d ANDs, largest cone %d ANDs.\n", + pOrder[0].nAnds, pOrder[Gia_ManCoNum(p)-1].nAnds ); + for ( k = 0; k < Gia_ManCoNum(p); k++ ) + { + i = pOrder[k].iPo; + pOne = Gia_ManDupCones( p, &i, 1, 0 ); + pModel = Acb_NtkSolveCadicalLimit( pOne, 0, fVerbose, &Status, 0, NULL, 0 ); + if ( Status == 0 ) + { + Gia_ManStop( pOne ); + ABC_FREE( pOrder ); + if ( pStatus ) + *pStatus = ACB_XEC_NEQ; + printf( "The networks are NOT equivalent by split SAT on output %d. ", i ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + return pModel; + } + ABC_FREE( pModel ); + pModel = NULL; + if ( Status == ACB_XEC_UNDEC ) + fOneUndef = 1; + Gia_ManStop( pOne ); + } + ABC_FREE( pOrder ); + if ( pStatus ) + *pStatus = fOneUndef ? -1 : 1; + printf( "The networks are %s by split SAT. ", fOneUndef ? "UNDECIDED" : "equivalent" ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); return NULL; } @@ -521,7 +4574,7 @@ int * Acb_NtkSolve( Gia_Man_t * p ) Synopsis [Various statistics.] Description [] - + SideEffects [] SeeAlso [] @@ -544,12 +4597,105 @@ void Acb_NtkPrintCecStats( Acb_Ntk_t * pNtk ) printf( "\n" ); } +void Acb_NtkCountXConstructs( Acb_Ntk_t * pNtk, int * pnDcs, int * pnMuxes, int * pnConstXs ) +{ + int iObj; + *pnDcs = *pnMuxes = *pnConstXs = 0; + Acb_NtkForEachNode( pNtk, iObj ) + if ( Acb_ObjType( pNtk, iObj ) == ABC_OPER_TRI ) + (*pnDcs)++; + else if ( Acb_ObjType( pNtk, iObj ) == ABC_OPER_BIT_MUX ) + (*pnMuxes)++; + else if ( Acb_ObjType( pNtk, iObj ) == ABC_OPER_CONST_X ) + (*pnConstXs)++; +} + +int * Acb_NtkSolveBinaryCec( Acb_Ntk_t * pNtkF, Acb_Ntk_t * pNtkG, int fVerbose, int * pStatus, int nTimeLimit ) +{ + extern Vec_Int_t * Acb_NtkFindNodes( Acb_Ntk_t * p, Vec_Int_t * vRoots, Vec_Int_t * vDivs ); + extern Gia_Man_t * Acb_NtkToGia( Acb_Ntk_t * p, Vec_Int_t * vSupp, Vec_Int_t * vNodes, Vec_Int_t * vRoots, Vec_Int_t * vDivs, Vec_Int_t * vTargets ); + Vec_Int_t * vRoots = Vec_IntAlloc( Acb_NtkCoNum(pNtkF) ); + Vec_Int_t * vSupp = Vec_IntAlloc( Acb_NtkCiNum(pNtkF) ); + Vec_Int_t * vNodesF = NULL, * vNodesG = NULL; + Gia_Man_t * pGiaF = NULL, * pGiaG = NULL, * pMiter = NULL; + int i, RetValue = ACB_XEC_UNDEC, * pModel = NULL; + abctime clk = Abc_Clock(); + for ( i = 0; i < Acb_NtkCoNum(pNtkF); i++ ) + Vec_IntPush( vRoots, i ); + for ( i = 0; i < Acb_NtkCiNum(pNtkF); i++ ) + Vec_IntPush( vSupp, i ); + vNodesF = Acb_NtkFindNodes( pNtkF, vRoots, NULL ); + vNodesG = Acb_NtkFindNodes( pNtkG, vRoots, NULL ); + pGiaF = Acb_NtkToGia( pNtkF, vSupp, vNodesF, vRoots, NULL, NULL ); + pGiaG = Acb_NtkToGia( pNtkG, vSupp, vNodesG, vRoots, NULL, NULL ); + pMiter = Gia_ManMiter( pGiaF, pGiaG, 0, 0, 0, 0, fVerbose ); + if ( pMiter == NULL ) + { + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + Gia_ManStop( pGiaF ); + Gia_ManStop( pGiaG ); + Vec_IntFree( vNodesF ); + Vec_IntFree( vNodesG ); + Vec_IntFree( vRoots ); + Vec_IntFree( vSupp ); + return NULL; + } + if ( Gia_ManAndNum(pMiter) > 5000 ) + { + Gia_Man_t * pTemp; + int nAndBefore = Gia_ManAndNum(pMiter); + int nLevBefore = Gia_ManLevelNum(pMiter); + pTemp = Gia_ManCompress2( pMiter, 1, 0 ); + if ( pTemp ) + { + Gia_ManStop( pMiter ); + pMiter = pTemp; + if ( fVerbose ) + printf( "Conventional binary XOR-miter compression: And = %d -> %d. Lev = %d -> %d. PO = %d.\n", + nAndBefore, Gia_ManAndNum(pMiter), nLevBefore, Gia_ManLevelNum(pMiter), Gia_ManCoNum(pMiter) ); + } + } + if ( fVerbose ) + { + printf( "Trying conventional binary XOR-miter CaDiCaL for no-X design: PI = %d. PO = %d. And = %d. limit = %d sec.\n", + Gia_ManCiNum(pMiter), Gia_ManCoNum(pMiter), Gia_ManAndNum(pMiter), nTimeLimit ); + Gia_ManPrintStats( pMiter, NULL ); + } + pModel = Acb_NtkSolveCadicalLimit( pMiter, 0, fVerbose, &RetValue, nTimeLimit, NULL, 0 ); + if ( pStatus ) + *pStatus = RetValue; + if ( RetValue == 0 && pModel ) + { + if ( !Acb_NtkCheckModelCexAcbBool( pNtkF, pNtkG, pModel, fVerbose ) ) + { + ABC_FREE( pModel ); + pModel = NULL; + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + RetValue = ACB_XEC_UNDEC; + printf( "The binary XOR-miter CaDiCaL SAT model is not a valid original Boolean counterexample; treating it as UNDECIDED.\n" ); + } + } + printf( "The networks are %s by conventional binary XOR-miter CaDiCaL. ", + RetValue == 1 ? "equivalent" : (RetValue == 0 ? "NOT equivalent" : "UNDECIDED") ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + Gia_ManStop( pMiter ); + Gia_ManStop( pGiaF ); + Gia_ManStop( pGiaG ); + Vec_IntFree( vNodesF ); + Vec_IntFree( vNodesG ); + Vec_IntFree( vRoots ); + Vec_IntFree( vSupp ); + return pModel; +} + /**Function************************************************************* Synopsis [Changing the PI order.] Description [] - + SideEffects [] SeeAlso [] @@ -601,27 +4747,54 @@ int Acb_NtkCheckPiOrder( Acb_Ntk_t * pNtkF, Acb_Ntk_t * pNtkG ) Synopsis [] Description [] - + SideEffects [] SeeAlso [] ***********************************************************************/ -void Acb_NtkRunTest( char * pFileNames[4], int fFancy, int fVerbose ) +void Acb_NtkRunTest( char * pFileNames[4], int fFancy, int fVerbose, int fUseCadical ) { - extern Acb_Ntk_t * Acb_VerilogSimpleRead( char * pFileName, char * pFileNameW ); - extern void Gia_AigerWrite( Gia_Man_t * p, char * pFileName, int fWriteSymbols, int fCompact, int fWriteNewLine ); - - int fSolve = 1; + int Status = -1; int * pModel = NULL; Gia_Man_t * pGiaF = NULL; Gia_Man_t * pGiaG = NULL; Gia_Man_t * pGia = NULL; + Gia_Man_t * pGiaFCut = NULL; + Gia_Man_t * pGiaGCut = NULL; + Gia_Man_t * pGiaGCtrl = NULL; + Gia_Man_t * pGiaCut = NULL; + Gia_Man_t * pGiaX = NULL; + Gia_Man_t * pTemp = NULL; + Vec_Int_t * vCutObjsF = NULL; + Vec_Int_t * vCutObjsG = NULL; + Vec_Int_t * vMuxSelectorsG = NULL; + Vec_Int_t * vMuxPoSelIdsG = NULL; + Vec_Int_t * vSymCutObjsF = NULL; + Vec_Int_t * vSymMuxSelectorsF = NULL; + Vec_Int_t * vSymIntDcObjsF = NULL; + Vec_Int_t * vSymIntDcCtrlsF = NULL; + Vec_Int_t * vSymIntDcCtrlIdsF = NULL; + Vec_Int_t * vIntDcObjsG = NULL; + Vec_Int_t * vIntDcCtrlsG = NULL; + Vec_Int_t * vIntDcCtrlIdsG = NULL; + Vec_Int_t * vDcDataObjsG = NULL; + Vec_Int_t * vDcCtrlObjsG = NULL; + Acb_XecCtx_t XecCtx; + int fSymmetricMuxDc = 0; + int nDcsF = 0, nMuxesF = 0, nConstXsF = 0, nDcsG = 0, nMuxesG = 0, nConstXsG = 0; Acb_Ntk_t * pNtkF = Acb_VerilogSimpleRead( pFileNames[0], NULL ); Acb_Ntk_t * pNtkG = Acb_VerilogSimpleRead( pFileNames[1], NULL ); if ( !pNtkF || !pNtkG ) + { + if ( pNtkF ) + Acb_ManFree( pNtkF->pDesign ); + if ( pNtkG ) + Acb_ManFree( pNtkG->pDesign ); return; - + } + Acb_XecCtxInit( &XecCtx ); + assert( Acb_NtkCiNum(pNtkF) == Acb_NtkCiNum(pNtkG) ); assert( Acb_NtkCoNum(pNtkF) == Acb_NtkCoNum(pNtkG) ); @@ -629,28 +4802,526 @@ void Acb_NtkRunTest( char * pFileNames[4], int fFancy, int fVerbose ) //Acb_NtkCheckPiOrder( pNtkG, pNtkF ); Acb_NtkPrintCecStats( pNtkF ); Acb_NtkPrintCecStats( pNtkG ); + Acb_NtkCountXConstructs( pNtkF, &nDcsF, &nMuxesF, &nConstXsF ); + Acb_NtkCountXConstructs( pNtkG, &nDcsG, &nMuxesG, &nConstXsG ); + + if ( fUseCadical && nDcsF == 0 && nMuxesF == 0 && nConstXsF == 0 && nDcsG == 0 && nMuxesG == 0 && nConstXsG == 0 ) + { + if ( fVerbose ) + printf( "No X/DC/MUX constructs found; using conventional binary CaDiCaL instead of X-aware dual-rail proving.\n" ); + pModel = Acb_NtkSolveBinaryCec( pNtkF, pNtkG, fVerbose, &Status, 1200 ); + Acb_OutputFile( pFileNames[2], pNtkF, pModel, Status ); + ABC_FREE( pModel ); + Acb_XecCtxFree( &XecCtx ); + Acb_ManFree( pNtkF->pDesign ); + Acb_ManFree( pNtkG->pDesign ); + return; + } pGiaF = Acb_NtkGiaDeriveDual( pNtkF ); pGiaG = Acb_NtkGiaDeriveDual( pNtkG ); - pGia = Acb_NtkGiaDeriveMiter( pGiaF, pGiaG, 2 ); - //Gia_AigerWrite( pGiaF, Extra_FileNameGenericAppend(pFileNames[1], "_f2.aig"), 0, 0, 0 ); - //Gia_AigerWrite( pGiaG, Extra_FileNameGenericAppend(pFileNames[1], "_g2.aig"), 0, 0, 0 ); - //Gia_AigerWrite( pGia, Extra_FileNameGenericAppend(pFileNames[1], "_miter_0.aig"), 0, 0, 0 ); - //printf( "Written the miter info file \"%s\".\n", Extra_FileNameGenericAppend(pFileNames[1], "_miter_0.aig") ); - - //Gia_ManPrintStats( pGia, NULL ); - //Gia_ManSimTry( pGiaF, pGiaG ); - - if ( fSolve ) + if ( pGiaF == NULL || pGiaG == NULL ) { - pModel = Acb_NtkSolve( pGia ); - Acb_OutputFile( pFileNames[2], pNtkF, pModel ); + printf( "XEC dual-rail translation failed; see unsupported ACB object diagnostic above.\n" ); + Status = ACB_XEC_UNDEC; + Acb_OutputFile( pFileNames[2], pNtkF, NULL, Status ); + Gia_ManStopP( &pGiaF ); + Gia_ManStopP( &pGiaG ); + Acb_XecCtxFree( &XecCtx ); + Acb_ManFree( pNtkF->pDesign ); + Acb_ManFree( pNtkG->pDesign ); + return; + } + pGia = Acb_NtkGiaDeriveMiter( pGiaF, pGiaG, 2 ); + if ( fUseCadical ) + { + if ( Acb_NtkCollectPoDcCutpoints( pNtkG, &vDcDataObjsG, &vDcCtrlObjsG ) ) + { + vCutObjsF = Acb_NtkCollectCoDrivers( pNtkF ); + if ( fVerbose ) + printf( "Found %d output DC cutpoints in implementation network.\n", Vec_IntSize(vDcDataObjsG) ); + } + vCutObjsG = Acb_NtkCollectPoMuxCutpoints( pNtkG ); + if ( vCutObjsF == NULL && Vec_IntSize(vCutObjsG) == Acb_NtkCoNum(pNtkG) ) + { + vCutObjsF = Acb_NtkCollectCoDrivers( pNtkF ); + vMuxSelectorsG = Acb_NtkCollectPoMuxSelectors( pNtkG, vCutObjsG ); + vMuxPoSelIdsG = Acb_NtkCollectPoMuxSelectorIds( pNtkG, vCutObjsG, vMuxSelectorsG ); + if ( fVerbose ) + printf( "Found %d output partition-candidate cutpoints using %d unique selectors in implementation network.\n", + Vec_IntSize(vCutObjsG), Vec_IntSize(vMuxSelectorsG) ); + } + else if ( fVerbose && vCutObjsF == NULL && Vec_IntSize(vCutObjsG) > 0 ) + printf( "No complete output partition-candidate cutpoint set found.\n" ); + if ( Acb_NtkCollectInternalDcControls( pNtkG, &vIntDcObjsG, &vIntDcCtrlsG, &vIntDcCtrlIdsG ) && fVerbose ) + printf( "Found %d internal DC nodes using %d unique controls in implementation network.\n", + Vec_IntSize(vIntDcObjsG), Vec_IntSize(vIntDcCtrlsG) ); + if ( vIntDcObjsG && Vec_IntSize(vIntDcObjsG) > 0 && Acb_NtkCoNum(pNtkG) > 512 ) + Acb_NtkCollectInternalDcControls( pNtkF, &vSymIntDcObjsF, &vSymIntDcCtrlsF, &vSymIntDcCtrlIdsF ); + if ( vCutObjsG && vMuxSelectorsG && Vec_IntSize(vCutObjsG) == Acb_NtkCoNum(pNtkG) && + vIntDcObjsG && vIntDcCtrlsG && Vec_IntSize(vIntDcObjsG) > 0 && Vec_IntSize(vIntDcCtrlsG) > 0 ) + { + vSymCutObjsF = Acb_NtkCollectPoMuxCutpoints( pNtkF ); + if ( vSymCutObjsF && Vec_IntSize(vSymCutObjsF) == Acb_NtkCoNum(pNtkF) ) + vSymMuxSelectorsF = Acb_NtkCollectPoMuxSelectors( pNtkF, vSymCutObjsF ); + if ( vSymIntDcObjsF == NULL ) + Acb_NtkCollectInternalDcControls( pNtkF, &vSymIntDcObjsF, &vSymIntDcCtrlsF, &vSymIntDcCtrlIdsF ); + fSymmetricMuxDc = + vSymMuxSelectorsF && vSymIntDcObjsF && vSymIntDcCtrlsF && + Vec_IntSize(vSymMuxSelectorsF) == Vec_IntSize(vMuxSelectorsG) && + Vec_IntSize(vSymIntDcObjsF) == Vec_IntSize(vIntDcObjsG) && + Vec_IntSize(vSymIntDcCtrlsF) == Vec_IntSize(vIntDcCtrlsG); + if ( fVerbose && fSymmetricMuxDc ) + printf( "Detected symmetric output-MUX/internal-DC structure in both networks.\n" ); + } + } + if ( Gia_ManAndNum(pGia) > 5000 ) + { + int nAndBefore = Gia_ManAndNum(pGia); + pTemp = Gia_ManCompress2( pGia, 1, fVerbose ); + if ( pTemp ) + { + if ( fVerbose ) + printf( "XEC miter compression: And = %d -> %d. PO = %d.\n", nAndBefore, Gia_ManAndNum(pTemp), Gia_ManPoNum(pTemp) ); + Gia_ManStop( pGia ); + pGia = pTemp; + } + } + { + int nSimWords = Gia_ManAndNum(pGia) > XecCtx.Pars.nSimLargeAndMin ? XecCtx.Pars.nSimLargeWords : XecCtx.Pars.nSimSmallWords; + int fCheckModel = 0; + int fSkipWholeMiter = 0; + int fTriedWholeMiterEarly = 0; + int fSkipAsymHmuxBranch = 0; + int fSkipHighPiDcFallbacks = 0; + int fHighPiTwoCtrlDc = !fFancy && + Acb_XecIsSharedDcWholeMiterShape( pGia, vMuxSelectorsG, vIntDcObjsG, vIntDcCtrlsG, &XecCtx ); + int fConstXSeedDc = !fFancy && + nDcsF == 0 && nMuxesF == 0 && nConstXsF == 0 && + (vMuxSelectorsG == NULL || Vec_IntSize(vMuxSelectorsG) == 0) && + vIntDcObjsG && Vec_IntSize(vIntDcObjsG) > 0 && Vec_IntSize(vIntDcObjsG) <= 8 && + Acb_NtkAllDcObjsAreConstXSeeds( pNtkG, vIntDcObjsG ) && + Gia_ManCiNum(pGia) <= 512 && Gia_ManCoNum(pGia) >= 64; + pModel = Acb_NtkFindSimCex( pGiaF, pGiaG, nSimWords, fVerbose ); + if ( pModel ) + { + Status = 0; + printf( "The networks are NOT equivalent by random simulation.\n" ); + } + else + { + if ( fUseCadical ) + { + if ( Gia_ManAndNum(pGia) > XecCtx.Pars.nMainLargeAndMin || Gia_ManCiNum(pGia) > XecCtx.Pars.nMainLargePiMin || Gia_ManCoNum(pGia) > XecCtx.Pars.nMainLargePoMin ) + { + if ( Status == -1 && !fFancy && vDcDataObjsG && vDcCtrlObjsG && + vIntDcObjsG && Vec_IntSize(vIntDcObjsG) > 0 && + vIntDcCtrlsG && Vec_IntSize(vIntDcCtrlsG) == 1 && + (vMuxSelectorsG == NULL || Vec_IntSize(vMuxSelectorsG) == 0) && + Vec_IntSize(vDcDataObjsG) == Acb_NtkCoNum(pNtkG) && + Gia_ManCiNum(pGia) <= 512 && Gia_ManCoNum(pGia) > 32 ) + { + int StatusPre = -1; + pModel = Acb_NtkSolveNormalPrecheck( pGia, fVerbose, &StatusPre, 750000 ); + Acb_XecMergeTargetStatus( StatusPre, pModel != NULL, &Status, &fCheckModel ); + } + if ( !fFancy && !fSkipHighPiDcFallbacks && Status == -1 && vCutObjsF && vDcDataObjsG && vDcCtrlObjsG ) + { + pGiaFCut = Acb_NtkGiaDeriveDualTargets( pNtkF, vCutObjsF ); + pGiaGCut = Acb_NtkGiaDeriveDualTargets( pNtkG, vDcDataObjsG ); + pGiaGCtrl = Acb_NtkGiaDeriveDualTargets( pNtkG, vDcCtrlObjsG ); + pGiaCut = Acb_NtkGiaDeriveMiterDcGuard( pGiaFCut, pGiaGCut, pGiaGCtrl ); + if ( Gia_ManAndNum(pGiaCut) >= Gia_ManAndNum(pGia) ) + { + if ( fVerbose ) + printf( "Skipping output-DC guarded cutpoint miter because it is not smaller: And = %d, current = %d.\n", + Gia_ManAndNum(pGiaCut), Gia_ManAndNum(pGia) ); + } + else + { + if ( fVerbose ) + printf( "Trying output-DC guarded cutpoint CaDiCaL sweep before whole-miter CaDiCaL: And = %d. PO = %d.\n", + Gia_ManAndNum(pGiaCut), Gia_ManPoNum(pGiaCut) ); + pModel = Acb_NtkSolveCadicalLimit( pGiaCut, 0, fVerbose, &Status, 900, "output-DC guarded cutpoint CaDiCaL", 0 ); + Acb_XecMergeTargetStatus( Status, pModel != NULL, &Status, &fCheckModel ); + } + } + if ( Status == -1 && !fFancy && + (vMuxSelectorsG == NULL || Vec_IntSize(vMuxSelectorsG) == 0) && + vIntDcObjsG && Vec_IntSize(vIntDcObjsG) > 0 && Vec_IntSize(vIntDcObjsG) <= 8 && + vIntDcCtrlsG && Vec_IntSize(vIntDcCtrlsG) > 1 && Vec_IntSize(vIntDcCtrlsG) <= 2 && + Gia_ManCiNum(pGia) <= 512 && Gia_ManCoNum(pGia) > 64 ) + { + int StatusTarget = -1; + pModel = Acb_NtkSolveDcControlWholeCubes( pNtkF, pNtkG, + vIntDcObjsG, vIntDcCtrlsG, vIntDcCtrlIdsG, fVerbose, &StatusTarget, 900, 300 ); + Acb_XecMergeTargetStatus( StatusTarget, pModel != NULL, &Status, &fCheckModel ); + } + if ( Status == -1 && fConstXSeedDc ) + { + int StatusConstX = -1; + pModel = Acb_NtkSolveConstXSeedCanonical( pNtkF, pNtkG, vIntDcObjsG, + fVerbose, &StatusConstX, 1200 ); + Acb_XecMergeTargetStatus( StatusConstX, pModel != NULL, &Status, &fCheckModel ); + } + if ( Status == -1 && !fFancy && + (vMuxSelectorsG == NULL || Vec_IntSize(vMuxSelectorsG) == 0) && + vIntDcObjsG && Vec_IntSize(vIntDcObjsG) > 0 && + vIntDcCtrlsG && Vec_IntSize(vIntDcCtrlsG) > 0 && Vec_IntSize(vIntDcCtrlsG) <= 2 && + (fHighPiTwoCtrlDc || (Gia_ManCiNum(pGia) >= XecCtx.Pars.nSharedDcPiMin && + Gia_ManCoNum(pGia) > 64 && Gia_ManAndNum(pGia) > XecCtx.Pars.nSharedDcAndMin)) ) + { + int nWholeDcLimit = fHighPiTwoCtrlDc ? XecCtx.Pars.nSharedDcWholeSec : 1200; + if ( fVerbose ) + printf( "Trying whole-miter CaDiCaL before local sweep for %slarge high-PI DC design: And = %d. PO = %d. DC controls = %d.\n", + fHighPiTwoCtrlDc ? "case8-style " : "", Gia_ManAndNum(pGia), Gia_ManCoNum(pGia), Vec_IntSize(vIntDcCtrlsG) ); + fTriedWholeMiterEarly = 1; + pModel = Acb_NtkSolveCadicalLimit( pGia, 0, fVerbose, &Status, nWholeDcLimit, "large high-PI DC whole-miter CaDiCaL", 0 ); + Acb_XecMergeTargetStatus( Status, pModel != NULL, &Status, &fCheckModel ); + if ( Status == -1 && fVerbose ) + printf( "Large high-PI DC whole-miter CaDiCaL was UNDECIDED%s.\n", + fHighPiTwoCtrlDc ? "; skipping local/cube detours for this case8-style shape" : "; continuing with local structural attempts" ); + if ( Status == -1 && fHighPiTwoCtrlDc ) + { + fSkipHighPiDcFallbacks = 1; + fSkipWholeMiter = 1; + } + } + if ( Status == -1 && !fFancy && fSymmetricMuxDc && + vMuxSelectorsG && Vec_IntSize(vMuxSelectorsG) > 0 && Vec_IntSize(vMuxSelectorsG) <= 4 && + vIntDcCtrlsG && Vec_IntSize(vIntDcCtrlsG) > 4 && + Gia_ManCoNum(pGia) >= 32 && Gia_ManCoNum(pGia) <= 128 && + Gia_ManCiNum(pGia) <= 1024 && + Gia_ManAndNum(pGia) >= 30000 && Gia_ManAndNum(pGia) <= 70000 ) + { + if ( fVerbose ) + printf( "Trying whole-miter CaDiCaL before local sweep for symmetric MUX/DC design: And = %d. PO = %d. selectors = %d. DC controls = %d.\n", + Gia_ManAndNum(pGia), Gia_ManCoNum(pGia), Vec_IntSize(vMuxSelectorsG), Vec_IntSize(vIntDcCtrlsG) ); + fTriedWholeMiterEarly = 1; + pModel = Acb_NtkSolveCadicalLimit( pGia, 0, fVerbose, &Status, 1200, "symmetric MUX/DC whole-miter CaDiCaL", 0 ); + Acb_XecMergeTargetStatus( Status, pModel != NULL, &Status, &fCheckModel ); + if ( Status == -1 && fVerbose ) + printf( "Symmetric MUX/DC whole-miter CaDiCaL was UNDECIDED; continuing with local structural attempts.\n" ); + } + if ( Status == -1 && !fFancy && !fSymmetricMuxDc && !fSkipAsymHmuxBranch && + vCutObjsG && vMuxSelectorsG && vMuxPoSelIdsG && + Vec_IntSize(vCutObjsG) == Acb_NtkCoNum(pNtkG) && + Vec_IntSize(vMuxSelectorsG) > 0 && Vec_IntSize(vMuxSelectorsG) <= 4 && + vIntDcObjsG && vIntDcCtrlsG && Vec_IntSize(vIntDcCtrlsG) > 4 && + Gia_ManCiNum(pGia) <= 1024 && Gia_ManCoNum(pGia) >= 32 && Gia_ManCoNum(pGia) <= 96 ) + { + int StatusCube = -1; + pModel = Acb_NtkSolveHmuxCompleteCubes( pNtkF, pNtkG, + vCutObjsG, vMuxSelectorsG, vMuxPoSelIdsG, + fVerbose, &StatusCube, 1200, 300 ); + Acb_XecMergeTargetStatus( StatusCube, pModel != NULL, &Status, &fCheckModel ); + if ( StatusCube == ACB_XEC_UNDEC ) + { + fSkipAsymHmuxBranch = 1; + if ( fVerbose ) + printf( "Complete HMUX selector-cube proof was inconclusive; skipping CEPR and partial-selector HMUX detours for this broad asymmetric shape.\n" ); + } + } + if ( Status == -1 && !fFancy && !fSymmetricMuxDc && !fSkipAsymHmuxBranch && + vCutObjsG && vMuxSelectorsG && vMuxPoSelIdsG && + Vec_IntSize(vCutObjsG) == Acb_NtkCoNum(pNtkG) && + Vec_IntSize(vMuxSelectorsG) > 0 && Vec_IntSize(vMuxSelectorsG) <= 4 && + vIntDcObjsG && Vec_IntSize(vIntDcObjsG) >= 128 && + vIntDcCtrlsG && Vec_IntSize(vIntDcCtrlsG) >= 8 && + Gia_ManCiNum(pGia) <= 1024 && Gia_ManCoNum(pGia) >= 32 && Gia_ManCoNum(pGia) <= 64 && + Gia_ManAndNum(pGia) <= 35000 ) + { + if ( fVerbose ) + printf( "Trying compact HMUX/DC whole-miter CaDiCaL before asymmetric branch proof: And = %d. PO = %d. selectors = %d. DC controls = %d.\n", + Gia_ManAndNum(pGia), Gia_ManCoNum(pGia), Vec_IntSize(vMuxSelectorsG), Vec_IntSize(vIntDcCtrlsG) ); + fTriedWholeMiterEarly = 1; + fSkipAsymHmuxBranch = 1; + pModel = Acb_NtkSolveCadicalLimit( pGia, 0, fVerbose, &Status, 1200, "compact HMUX/DC whole-miter CaDiCaL", 0 ); + Acb_XecMergeTargetStatus( Status, pModel != NULL, &Status, &fCheckModel ); + if ( Status == -1 && fVerbose ) + printf( "Compact HMUX/DC whole-miter CaDiCaL was UNDECIDED; skipping the expensive asymmetric HMUX branch detour.\n" ); + } + + if ( Status == -1 && !fFancy && !fSymmetricMuxDc && !fSkipAsymHmuxBranch && + vCutObjsG && vMuxSelectorsG && vMuxPoSelIdsG && + Vec_IntSize(vCutObjsG) == Acb_NtkCoNum(pNtkG) && + Vec_IntSize(vMuxSelectorsG) > 0 && Vec_IntSize(vMuxSelectorsG) <= 4 && + vIntDcObjsG && vIntDcCtrlsG && Vec_IntSize(vIntDcCtrlsG) > 4 && + Gia_ManCoNum(pGia) >= 16 && Gia_ManCoNum(pGia) <= 128 ) + { + int StatusHmux = -1; + if ( fVerbose ) + printf( "Trying asymmetric HMUX branch proof with targeted DC fallback before local sweep.\n" ); + pModel = Acb_NtkSolveHmuxBranches( pNtkF, pNtkG, + vCutObjsG, vMuxSelectorsG, vMuxPoSelIdsG, + vIntDcObjsG, vIntDcCtrlsG, vIntDcCtrlIdsG, + fVerbose, &StatusHmux, &XecCtx ); + Acb_XecMergeTargetStatus( StatusHmux, pModel != NULL, &Status, &fCheckModel ); + } + if ( Status == -1 && !fFancy && !fSkipHighPiDcFallbacks ) + { + pModel = Acb_NtkSolveCadicalLocalConeSweepSkipCtx( pGia, fVerbose, &Status, 900, 120, NULL, &XecCtx ); + Acb_XecMergeTargetStatus( Status, pModel != NULL, &Status, &fCheckModel ); + } + if ( Status == -1 && !fSkipHighPiDcFallbacks ) + { + int StatusTarget = -1; + if ( !fFancy && vIntDcObjsG && Vec_IntSize(vIntDcObjsG) > 0 && + vIntDcCtrlsG && Vec_IntSize(vIntDcCtrlsG) > 0 && Vec_IntSize(vIntDcCtrlsG) <= 4 && + (vMuxSelectorsG == NULL || Vec_IntSize(vMuxSelectorsG) == 0) && + XecCtx.vLastHardPos && Vec_IntSize(XecCtx.vLastHardPos) > 0 ) + { + int fTriedSmallDcCutpoints = 0; + if ( Vec_IntSize(vIntDcCtrlsG) == 1 && Vec_IntSize(vIntDcObjsG) <= 2 && + Gia_ManCiNum(pGia) <= 256 && Gia_ManCoNum(pGia) > 64 && + Vec_IntSize(XecCtx.vLastHardPos) <= 16 ) + { + fTriedSmallDcCutpoints = 1; + if ( fVerbose ) + printf( "Trying collected hard-output cutpoint abstraction for small single-control DC design: hard outputs = %d. DC nodes = %d.\n", + Vec_IntSize(XecCtx.vLastHardPos), Vec_IntSize(vIntDcObjsG) ); + pModel = Acb_NtkSolveTargetCutpointList( pNtkF, pNtkG, XecCtx.vLastHardPos, + fVerbose, &StatusTarget, 900, 120 ); + } + if ( StatusTarget == -1 && !fTriedSmallDcCutpoints && !fHighPiTwoCtrlDc ) + { + if ( fVerbose ) + printf( "Trying DC-control target recursion after local-cone sweep: hard outputs = %d. controls = %d.\n", + Vec_IntSize(XecCtx.vLastHardPos), Vec_IntSize(vIntDcCtrlsG) ); + pModel = Acb_NtkSolveDcControlTargetList( pNtkF, pNtkG, XecCtx.vLastHardPos, + vIntDcObjsG, vIntDcCtrlsG, vIntDcCtrlIdsG, fVerbose, &StatusTarget, 180 ); + } + Acb_XecMergeTargetStatus( StatusTarget, pModel != NULL, &Status, &fCheckModel ); + if ( StatusTarget == 1 ) + { + if ( Vec_IntSize(XecCtx.vLastHardPos) == Gia_ManCoNum(pGia) ) + Status = 1; + else + { + Vec_Int_t * vSkipUnsat = Vec_IntDup( XecCtx.vLastHardPos ); + int iPoSkip, iSkip, StatusResume = -1; + if ( XecCtx.vLastProvenPos ) + Vec_IntForEachEntry( XecCtx.vLastProvenPos, iPoSkip, iSkip ) + Vec_IntPushUnique( vSkipUnsat, iPoSkip ); + if ( fVerbose ) + printf( "DC-control target recursion proved %d collected hard outputs; resuming local-cone sweep for %d remaining outputs.\n", + Vec_IntSize(XecCtx.vLastHardPos), Gia_ManCoNum(pGia) - Vec_IntSize(vSkipUnsat) ); + pModel = Acb_NtkSolveCadicalLocalConeSweepSkipCtx( pGia, fVerbose, &StatusResume, 900, -120, vSkipUnsat, &XecCtx ); + Vec_IntFree( vSkipUnsat ); + Acb_XecMergeTargetStatus( StatusResume, pModel != NULL, &Status, &fCheckModel ); + if ( StatusResume != ACB_XEC_EQ && StatusResume != ACB_XEC_NEQ ) + Status = StatusResume; + } + } + else if ( fHighPiTwoCtrlDc ) + { + fSkipWholeMiter = 1; + if ( fVerbose ) + printf( "Skipping expensive high-PI two-control DC fallbacks after quick local sweep; remaining outputs need a specialized proof.\n" ); + } + } + } + if ( Status == ACB_XEC_ONE_HARD ) + { + int StatusTarget = -1; + if ( !fFancy && vCutObjsG && vMuxSelectorsG && vMuxPoSelIdsG && + Vec_IntSize(vCutObjsG) == Acb_NtkCoNum(pNtkG) && + Vec_IntSize(vMuxSelectorsG) > 0 && Vec_IntSize(vMuxSelectorsG) <= 16 ) + { + pModel = Acb_NtkSolveMuxTargetBranches( pNtkF, pNtkG, XecCtx.LastHardPo, + vCutObjsG, vMuxSelectorsG, vMuxPoSelIdsG, fVerbose, &StatusTarget, 450, 0 ); + Acb_XecMergeTargetStatus( StatusTarget, pModel != NULL, &Status, &fCheckModel ); + } + } + if ( Status == ACB_XEC_MANY_HARD ) + { + int StatusTarget = -1; + if ( !fFancy && vIntDcObjsG && Vec_IntSize(vIntDcObjsG) > 0 && + vIntDcCtrlsG && Vec_IntSize(vIntDcCtrlsG) > 0 && Vec_IntSize(vIntDcCtrlsG) <= 4 && + (vMuxSelectorsG == NULL || Vec_IntSize(vMuxSelectorsG) == 0) && + XecCtx.vLastHardPos && Vec_IntSize(XecCtx.vLastHardPos) > 0 ) + { + pModel = Acb_NtkSolveDcControlTargetList( pNtkF, pNtkG, XecCtx.vLastHardPos, + vIntDcObjsG, vIntDcCtrlsG, vIntDcCtrlIdsG, fVerbose, &StatusTarget, 180 ); + Acb_XecMergeTargetStatus( StatusTarget, pModel != NULL, &Status, &fCheckModel ); + } + } + if ( Status == ACB_XEC_ONE_HARD ) + { + int StatusTarget = -1; + if ( !fFancy && vIntDcObjsG && Vec_IntSize(vIntDcObjsG) > 0 && + vIntDcCtrlsG && Vec_IntSize(vIntDcCtrlsG) > 0 && Vec_IntSize(vIntDcCtrlsG) <= 4 && + (vMuxSelectorsG == NULL || Vec_IntSize(vMuxSelectorsG) == 0) ) + { + pModel = Acb_NtkSolveDcControlTargetBranches( pNtkF, pNtkG, XecCtx.LastHardPo, + vIntDcObjsG, vIntDcCtrlsG, vIntDcCtrlIdsG, fVerbose, &StatusTarget, 450, 0 ); + Acb_XecMergeTargetStatus( StatusTarget, pModel != NULL, &Status, &fCheckModel ); + } + } + if ( Status == ACB_XEC_ONE_HARD ) + { + int StatusTarget = -1; + pModel = Acb_NtkSolveTargetCutpoints( pNtkF, pNtkG, XecCtx.LastHardPo, fVerbose, &StatusTarget, 900 ); + Acb_XecMergeTargetStatus( StatusTarget, pModel != NULL, &Status, &fCheckModel ); + } + if ( Status == ACB_XEC_ONE_HARD ) + { + if ( XecCtx.LastHardDirectTried ) + { + if ( fVerbose ) + printf( "Skipping whole-miter CaDiCaL because the isolated hard output already had a rejected direct-clause SAT model.\n" ); + Status = -1; + fSkipWholeMiter = 1; + } + else + { + if ( fVerbose ) + printf( "Skipping whole-miter CaDiCaL because it duplicates the isolated hard-output cone.\n" ); + Status = -1; + fSkipWholeMiter = 1; + } + } + if ( Status == ACB_XEC_MANY_HARD ) + { + if ( fVerbose ) + printf( "Skipping whole-miter CaDiCaL because the remaining hard-output proof already isolated the unresolved outputs.\n" ); + Status = -1; + fSkipWholeMiter = 1; + } + if ( Status == -1 && !fFancy && !fSkipWholeMiter && !fTriedWholeMiterEarly ) + pModel = Acb_NtkSolveCadicalLimit( pGia, 0, fVerbose, &Status, 1200, "CaDiCaL SAT-only", 0 ); + else if ( Status == -1 && !fSkipWholeMiter ) + pModel = Acb_NtkSolveCadicalLimit( pGia, fFancy, fVerbose, &Status, 1200, fFancy ? "X-aware CaDiCaL SAT-only" : "CaDiCaL SAT-only", fFancy ); + if ( Status == -1 && fFancy && vCutObjsF && vDcDataObjsG && vDcCtrlObjsG ) + { + pGiaFCut = Acb_NtkGiaDeriveDualTargets( pNtkF, vCutObjsF ); + pGiaGCut = Acb_NtkGiaDeriveDualTargets( pNtkG, vDcDataObjsG ); + pGiaGCtrl = Acb_NtkGiaDeriveDualTargets( pNtkG, vDcCtrlObjsG ); + pGiaCut = Acb_NtkGiaDeriveMiterDcGuard( pGiaFCut, pGiaGCut, pGiaGCtrl ); + if ( fVerbose ) + printf( "Trying exact split SAT on output-DC cutpoint miter: And = %d. PO = %d.\n", Gia_ManAndNum(pGiaCut), Gia_ManPoNum(pGiaCut) ); + pModel = Acb_NtkSolveSplit( pGiaCut, fVerbose, &Status ); + Acb_XecMergeTargetStatus( Status, pModel != NULL, &Status, &fCheckModel ); + } + if ( Status == -1 && fFancy && vCutObjsF && vCutObjsG && Vec_IntSize(vCutObjsG) == Acb_NtkCoNum(pNtkG) ) + { + if ( pGiaCut ) + { + Gia_ManStop( pGiaCut ); + pGiaCut = NULL; + } + if ( pGiaFCut ) + { + Gia_ManStop( pGiaFCut ); + pGiaFCut = NULL; + } + if ( pGiaGCut ) + { + Gia_ManStop( pGiaGCut ); + pGiaGCut = NULL; + } + pGiaFCut = Acb_NtkGiaDeriveDualTargets( pNtkF, vCutObjsF ); + pGiaGCut = Acb_NtkGiaDeriveDualTargets( pNtkG, vCutObjsG ); + pGiaCut = Acb_NtkGiaDeriveMiter( pGiaFCut, pGiaGCut, 2 ); + if ( Gia_ManAndNum(pGiaCut) >= Gia_ManAndNum(pGia) ) + { + if ( fVerbose ) + printf( "Skipping partition-candidate cutpoint miter because it is not smaller: And = %d, current = %d.\n", + Gia_ManAndNum(pGiaCut), Gia_ManAndNum(pGia) ); + } + else + { + if ( fVerbose ) + printf( "Trying exact split SAT on partition-candidate cutpoint miter: And = %d. PO = %d.\n", Gia_ManAndNum(pGiaCut), Gia_ManPoNum(pGiaCut) ); + pModel = Acb_NtkSolveSplit( pGiaCut, fVerbose, &Status ); + Acb_XecMergeTargetStatus( Status, pModel != NULL, &Status, &fCheckModel ); + } + } + if ( Status == -1 && fFancy ) + pModel = Acb_NtkSolveCadicalOdc( pGia, fVerbose, &Status ); + if ( Status == -1 && fFancy ) + { + pModel = Acb_NtkSolveSplit( pGia, fVerbose, &Status ); + Acb_XecMergeTargetStatus( Status, pModel != NULL, &Status, &fCheckModel ); + } + if ( Status == -1 && fFancy ) + { + printf( "Trying X-aware whole-miter CaDiCaL after ODC/split SAT was undecided.\n" ); + if ( pGiaX == NULL ) + pGiaX = Acb_NtkGiaDeriveMiter( pGiaF, pGiaG, 3 ); + pModel = Acb_NtkSolveCadicalLimit( pGiaX, fFancy, fVerbose, &Status, 1200, "X-aware CaDiCaL SAT-only", 1 ); + fCheckModel = 1; + } + } + else + { + if ( !fFancy && Status == -1 && Gia_ManCoNum(pGia) == 1 && Gia_ManAndNum(pGia) <= 30000 && + (vMuxSelectorsG == NULL || Vec_IntSize(vMuxSelectorsG) == 0) ) + { + pModel = Acb_NtkSolveIvyPrecheck( pGia, fVerbose, &Status ); + Acb_XecMergeTargetStatus( Status, pModel != NULL, &Status, &fCheckModel ); + } + if ( !fFancy && Status == -1 && vIntDcObjsG && Vec_IntSize(vIntDcObjsG) > 0 && Vec_IntSize(vIntDcCtrlsG) > 0 && Vec_IntSize(vIntDcCtrlsG) <= 4 ) + { + int fSmallMultiOutput = Gia_ManAndNum(pGia) <= 5000 && Gia_ManCoNum(pGia) > 1; + if ( fSmallMultiOutput && Vec_IntSize(vIntDcCtrlsG) == 1 && Vec_IntSize(vIntDcObjsG) <= 16 && + Gia_ManCoNum(pGia) >= 8 && Gia_ManCoNum(pGia) <= 32 ) + { + pModel = Acb_NtkSolveCadicalLimit( pGia, fFancy, fVerbose, &Status, 1700, + "small single-control DC whole-miter CaDiCaL", 0 ); + Acb_XecMergeTargetStatus( Status, pModel != NULL, &Status, &fCheckModel ); + } + else + { + pModel = Acb_NtkSolveDcControlBranchesLimit( pNtkF, pNtkG, vIntDcObjsG, vIntDcCtrlsG, vIntDcCtrlIdsG, + fVerbose, &Status, fSmallMultiOutput ? 60 : 5, !fSmallMultiOutput, 0 ); + Acb_XecMergeTargetStatus( Status, pModel != NULL, &Status, &fCheckModel ); + } + } + + if ( Status == -1 ) + pModel = Acb_NtkSolveCadicalLimit( pGia, fFancy, fVerbose, &Status, 1200, fFancy ? "X-aware CaDiCaL SAT-only" : "CaDiCaL SAT-only", fFancy ); + } + } + else + { + pModel = Acb_NtkSolve( pGia, fVerbose, &Status ); + } + if ( fCheckModel && pModel && !Acb_NtkCheckModelCex( pGiaF, pGiaG, pModel, fVerbose ) ) + { + ABC_FREE( pModel ); + pModel = NULL; + Status = -1; + printf( "The SAT model is not a valid XEC counterexample; treating the result as UNDECIDED.\n" ); + } + } + Acb_OutputFile( pFileNames[2], pNtkF, pModel, Status ); ABC_FREE( pModel ); } - Gia_ManStop( pGia ); - Gia_ManStop( pGiaF ); - Gia_ManStop( pGiaG ); + Gia_ManStopP( &pGiaX ); + Gia_ManStopP( &pGiaCut ); + Gia_ManStopP( &pGiaFCut ); + Gia_ManStopP( &pGiaGCut ); + Gia_ManStopP( &pGiaGCtrl ); + Gia_ManStopP( &pGia ); + Gia_ManStopP( &pGiaF ); + Gia_ManStopP( &pGiaG ); + Vec_IntFreeP( &vCutObjsF ); + Vec_IntFreeP( &vCutObjsG ); + Vec_IntFreeP( &vMuxSelectorsG ); + Vec_IntFreeP( &vMuxPoSelIdsG ); + Vec_IntFreeP( &vSymCutObjsF ); + Vec_IntFreeP( &vSymMuxSelectorsF ); + Vec_IntFreeP( &vSymIntDcObjsF ); + Vec_IntFreeP( &vSymIntDcCtrlsF ); + Vec_IntFreeP( &vSymIntDcCtrlIdsF ); + Vec_IntFreeP( &vIntDcObjsG ); + Vec_IntFreeP( &vIntDcCtrlsG ); + Vec_IntFreeP( &vIntDcCtrlIdsG ); + Vec_IntFreeP( &vDcDataObjsG ); + Vec_IntFreeP( &vDcCtrlObjsG ); + Acb_XecCtxFree( &XecCtx ); Acb_ManFree( pNtkF->pDesign ); Acb_ManFree( pNtkG->pDesign ); @@ -663,4 +5334,3 @@ void Acb_NtkRunTest( char * pFileNames[4], int fFancy, int fVerbose ) ABC_NAMESPACE_IMPL_END - diff --git a/src/base/acb/acbXec.c b/src/base/acb/acbXec.c new file mode 100644 index 000000000..bfa99f86b --- /dev/null +++ b/src/base/acb/acbXec.c @@ -0,0 +1,368 @@ +/**CFile**************************************************************** + + FileName [acbXec.c] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [Hierarchical word-level netlist.] + + Synopsis [Reusable XEC proof helpers.] + +***********************************************************************/ + +#include "acbXec.h" +#include "aig/gia/giaAig.h" +#include "base/abc/abc.h" +#include "opt/dar/dar.h" +#include "sat/cadical/cadicalSolver.h" + +ABC_NAMESPACE_IMPL_START + +//////////////////////////////////////////////////////////////////////// +/// FUNCTION DEFINITIONS /// +//////////////////////////////////////////////////////////////////////// + +typedef enum Acb_SatStatus_t_ +{ + ACB_SAT_UNSAT = -1, + ACB_SAT_UNDEC = 0, + ACB_SAT_SAT = 1 +} Acb_SatStatus_t; + +int Acb_CnfCoDriverLit( Cnf_Dat_t * pCnf, int iCo, int * pLit ) +{ + Aig_Obj_t * pCo = Aig_ManCo( pCnf->pMan, iCo ); + Aig_Obj_t * pFan = Aig_ObjFanin0( pCo ); + int fCompl = Aig_ObjFaninC0( pCo ); + int Var; + if ( Aig_ObjIsConst1(pFan) ) + return fCompl ? -1 : 0; + Var = pCnf->pVarNums[pFan->Id]; + if ( Var < 0 ) + return -2; + *pLit = Abc_Var2Lit( Var, fCompl ); + return 1; +} + +static int Acb_GiaPoIsConst0( Gia_Man_t * p, int iPo ) +{ + Gia_Obj_t * pObj; + if ( iPo < 0 || iPo >= Gia_ManCoNum(p) ) + return 0; + pObj = Gia_ManCo( p, iPo ); + return Gia_ObjFanin0(pObj) == Gia_ManConst0(p) && !Gia_ObjFaninC0(pObj); +} + +int Acb_GiaAllPosConst0( Gia_Man_t * p ) +{ + int i; + for ( i = 0; i < Gia_ManCoNum(p); i++ ) + if ( !Acb_GiaPoIsConst0(p, i) ) + return 0; + return 1; +} + +static word Acb_XecGiaVarWord( int iVar, ABC_UINT64_T iWord ) +{ + static word Truth6[6] = { + ABC_CONST(0xAAAAAAAAAAAAAAAA), + ABC_CONST(0xCCCCCCCCCCCCCCCC), + ABC_CONST(0xF0F0F0F0F0F0F0F0), + ABC_CONST(0xFF00FF00FF00FF00), + ABC_CONST(0xFFFF0000FFFF0000), + ABC_CONST(0xFFFFFFFF00000000) + }; + if ( iVar < 6 ) + return Truth6[iVar]; + return ((iWord >> (iVar - 6)) & 1) ? ~(word)0 : 0; +} +static inline word Acb_XecGiaLitWord( Vec_Wrd_t * vSims, int nWords, int Lit, int w ) +{ + word Res = Vec_WrdEntry( vSims, Abc_Lit2Var(Lit) * nWords + w ); + return Abc_LitIsCompl(Lit) ? ~Res : Res; +} +int * Acb_NtkSolveCadicalLimit( Gia_Man_t * p, int fUseHeavyOpt, int fVerbose, int * pStatus, int nSatTimeLimit, char * pLabel, int fUseXecOutputClauses ) +{ + Aig_Man_t * pMan = NULL; + Cnf_Dat_t * pCnf = NULL; + cadical_solver * pSat = NULL; + Vec_Int_t * vPoLits = NULL; + Gia_Man_t * pGiaOpt = NULL, * pGiaTemp = NULL; + Gia_Man_t * pGia = p; + Aig_Obj_t * pObj; + int i, Ret, Lit, Status = ACB_SAT_UNDEC, * pBeg, * pEnd, * pModel = NULL; + int fRunSolve = 0, fSolvedSat = 0; + abctime clk = Abc_Clock(); + (void)fUseXecOutputClauses; + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + if ( p == NULL ) + return NULL; + if ( Gia_ManCoNum(p) == 0 || Acb_GiaAllPosConst0(p) ) + { + if ( pStatus ) + *pStatus = ACB_XEC_EQ; + if ( pLabel ) + { + printf( "The networks are equivalent by %s. ", pLabel ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + return NULL; + } + if ( fUseHeavyOpt && Gia_ManAndNum(p) > 0 ) + { + pGiaTemp = Gia_ManCompress2( p, 1, 0 ); + if ( pGiaTemp ) + { + pGiaOpt = pGiaTemp; + pGiaTemp = NULL; + pGia = pGiaOpt; + assert( Gia_ManCiNum(pGia) == Gia_ManCiNum(p) ); + } + } + pMan = Gia_ManToAig( pGia, 0 ); + pCnf = pMan ? Cnf_Derive( pMan, Aig_ManCoNum(pMan) ) : NULL; + pSat = pCnf ? cadical_solver_new() : NULL; + if ( pCnf && pSat ) + { + fRunSolve = 1; + cadical_solver_setnvars( pSat, pCnf->nVars ); + Cnf_CnfForClause( pCnf, pBeg, pEnd, i ) + { + if ( !cadical_solver_addclause( pSat, pBeg, pEnd ) ) + { + Status = ACB_SAT_UNSAT; + fRunSolve = 0; + break; + } + } + if ( fRunSolve ) + { + vPoLits = Vec_IntAlloc( Gia_ManCoNum(pGia) ); + for ( i = 0; i < Gia_ManCoNum(pGia); i++ ) + { + Ret = Acb_CnfCoDriverLit( pCnf, i, &Lit ); + if ( Ret == -2 ) + { + Status = ACB_SAT_UNDEC; + fRunSolve = 0; + break; + } + if ( Ret == -1 ) + continue; + if ( Ret == 0 ) + { + Status = ACB_SAT_SAT; + fRunSolve = 0; + break; + } + Vec_IntPush( vPoLits, Lit ); + } + } + if ( fRunSolve && Vec_IntSize(vPoLits) == 0 ) + { + Status = ACB_SAT_UNSAT; + fRunSolve = 0; + } + if ( fRunSolve && !cadical_solver_addclause( pSat, Vec_IntArray(vPoLits), Vec_IntArray(vPoLits) + Vec_IntSize(vPoLits) ) ) + { + Status = ACB_SAT_UNSAT; + fRunSolve = 0; + } + if ( fRunSolve && fVerbose ) + { + printf( "CaDiCaL CNF: Var = %d. Cla = %d. PO = %d.\n", + pCnf->nVars, pCnf->nClauses + 1, Gia_ManCoNum(pGia) ); + if ( nSatTimeLimit > 0 ) + printf( "CaDiCaL SAT runtime limit: %d sec.\n", nSatTimeLimit ); + } + if ( fRunSolve ) + { + Status = cadical_solver_solve( pSat, NULL, NULL, 0, 0, 0, 0 ); + fSolvedSat = Status == ACB_SAT_SAT; + } + if ( fVerbose ) + printf( "CaDiCaL stats: conflicts = %d. learned = %d.\n", + cadical_solver_nconflicts(pSat), cadical_solver_nlearned(pSat) ); + } + if ( Status == ACB_SAT_UNSAT ) + { + if ( pStatus ) + *pStatus = ACB_XEC_EQ; + if ( pLabel ) + { + printf( "The networks are equivalent by %s. ", pLabel ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + } + else if ( Status == ACB_SAT_SAT ) + { + if ( pStatus ) + *pStatus = ACB_XEC_NEQ; + if ( fSolvedSat ) + pModel = ABC_CALLOC( int, Gia_ManCiNum(pGia) ); + if ( pModel && pSat && pCnf && pMan ) + Aig_ManForEachCi( pMan, pObj, i ) + { + int Var = pCnf->pVarNums[pObj->Id]; + pModel[i] = Var >= 0 ? cadical_solver_get_var_value( pSat, Var ) : 0; + } + if ( pLabel ) + { + printf( "The networks are NOT equivalent by %s. ", pLabel ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + } + else + { + if ( pStatus ) + *pStatus = ACB_XEC_UNDEC; + if ( fVerbose && pLabel ) + { + printf( "The networks are UNDECIDED by %s. ", pLabel ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + } + if ( pSat ) + cadical_solver_delete( pSat ); + if ( pCnf ) + Cnf_DataFree( pCnf ); + if ( pMan ) + Aig_ManStop( pMan ); + if ( pGiaOpt ) + Gia_ManStop( pGiaOpt ); + Vec_IntFreeP( &vPoLits ); + return pModel; +} +int Acb_XecGiaSolveSmallConeExhaustive( Gia_Man_t * p, int fVerbose, int nTotalLimit ) +{ + Vec_Wrd_t * vSims = NULL; + Gia_Obj_t * pObj; + ABC_UINT64_T nWordsTotal, nWordBudget, iWordBase, nWordsDone = 0; + int i, w, nWords, nWordsChunk, nObjs, nCis, nHiVars, Status = ACB_XEC_EQ, fDone = 0; + abctime clk = Abc_Clock(); + abctime clkLimit = nTotalLimit > 0 ? clk + nTotalLimit * CLOCKS_PER_SEC : 0; + if ( Gia_ManCoNum(p) != 1 || Gia_ManAndNum(p) > 5000 ) + return ACB_XEC_UNDEC; + nCis = Gia_ManCiNum(p); + nHiVars = Abc_MaxInt( 0, nCis - 6 ); + if ( nHiVars >= 63 ) + { + if ( fVerbose ) + printf( "Skipping small-cone exhaustive word proof: CI = %d needs more than 2^63 simulation words.\n", nCis ); + return ACB_XEC_UNDEC; + } + nWordsChunk = nCis >= 31 ? 4096 : (nCis >= 28 ? 8192 : 16384); + nWordsTotal = nHiVars ? ((ABC_UINT64_T)1 << nHiVars) : 1; + nWordBudget = nTotalLimit > 0 ? (ABC_UINT64_T)200000 * nTotalLimit : (ABC_UINT64_T)60000000; + if ( nWordBudget < (ABC_UINT64_T)8000000 ) + nWordBudget = (ABC_UINT64_T)8000000; + if ( nWordsTotal > nWordBudget ) + { + if ( fVerbose ) + printf( "Skipping small-cone exhaustive word proof: CI = %d needs %llu words, budget = %llu words.\n", + nCis, (unsigned long long)nWordsTotal, (unsigned long long)nWordBudget ); + return ACB_XEC_UNDEC; + } + nObjs = Gia_ManObjNum(p); + vSims = Vec_WrdStart( nObjs * nWordsChunk ); + if ( fVerbose ) + printf( "Trying small-cone exhaustive word proof: CI = %d. AND = %d. chunks = %llu x %d words. limit = %d sec.\n", + nCis, Gia_ManAndNum(p), (unsigned long long)((nWordsTotal + nWordsChunk - 1) / nWordsChunk), nWordsChunk, nTotalLimit ); + for ( iWordBase = 0; iWordBase < nWordsTotal && !fDone; iWordBase += nWordsChunk ) + { + ABC_UINT64_T nWordsLeft = nWordsTotal - iWordBase; + nWords = nWordsLeft < (ABC_UINT64_T)nWordsChunk ? (int)nWordsLeft : nWordsChunk; + if ( clkLimit && Abc_Clock() >= clkLimit ) + { + Status = ACB_XEC_UNDEC; + break; + } + /* Only the active words [0..nWords) are consumed in this chunk; other words may retain previous data. */ + for ( w = 0; w < nWords; w++ ) + Vec_WrdWriteEntry( vSims, w, 0 ); + Gia_ManForEachCi( p, pObj, i ) + for ( w = 0; w < nWords; w++ ) + Vec_WrdWriteEntry( vSims, Gia_ObjId(p, pObj) * nWordsChunk + w, Acb_XecGiaVarWord(i, iWordBase + w) ); + Gia_ManForEachAnd( p, pObj, i ) + for ( w = 0; w < nWords; w++ ) + Vec_WrdWriteEntry( vSims, Gia_ObjId(p, pObj) * nWordsChunk + w, + Acb_XecGiaLitWord(vSims, nWordsChunk, Gia_ObjFaninLit0p(p, pObj), w) & + Acb_XecGiaLitWord(vSims, nWordsChunk, Gia_ObjFaninLit1p(p, pObj), w) ); + pObj = Gia_ManCo( p, 0 ); + for ( w = 0; w < nWords; w++ ) + { + word Res = Acb_XecGiaLitWord(vSims, nWordsChunk, Gia_ObjFaninLit0p(p, pObj), w); + if ( iWordBase + w + 1 == nWordsTotal && nCis < 6 ) + Res &= (((word)1) << (1 << nCis)) - 1; + if ( Res ) + { + Status = ACB_XEC_UNDEC; + fDone = 1; + break; + } + } + nWordsDone += nWords; + } + if ( fVerbose ) + { + printf( "Small-cone exhaustive word proof: %s. checked words = %llu/%llu. ", + Status == ACB_XEC_EQ ? "UNSAT" : "UNDECIDED", + (unsigned long long)nWordsDone, (unsigned long long)nWordsTotal ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + Vec_WrdFree( vSims ); + return Status; +} +Gia_Man_t * Acb_XecGiaSmallConeXorRewrite( Gia_Man_t * p, int fVerbose ) +{ + Aig_Man_t * pAig = NULL, * pAigTemp = NULL; + Gia_Man_t * pGia = NULL, * pTemp = NULL; + int nAndStart = Gia_ManAndNum(p); + abctime clk = Abc_Clock(); + if ( Gia_ManCoNum(p) != 1 || Gia_ManCiNum(p) > 64 || nAndStart > 8000 ) + return NULL; + if ( fVerbose ) + printf( "Small-cone XOR structural rewrite: CI = %d. AND = %d.\n", + Gia_ManCiNum(p), nAndStart ); + pAig = Gia_ManToAig( p, 0 ); + if ( pAig == NULL ) + return NULL; + pAig = Dar_ManBalanceXor( pAigTemp = pAig, 1, 1, 0 ); + Aig_ManStop( pAigTemp ); + if ( pAig == NULL ) + return NULL; + pAig = Dar_ManRwsat( pAigTemp = pAig, 1, 0 ); + Aig_ManStop( pAigTemp ); + if ( pAig == NULL ) + return NULL; + pGia = Gia_ManFromAig( pAig ); + Aig_ManStop( pAig ); + if ( pGia == NULL ) + return NULL; + pTemp = Gia_ManCompress2( pGia, 1, 0 ); + if ( pTemp ) + { + Gia_ManStop( pGia ); + pGia = pTemp; + } + if ( fVerbose ) + { + printf( "Small-cone XOR structural rewrite: AND = %d -> %d. Lev = %d -> %d. ", + nAndStart, Gia_ManAndNum(pGia), Gia_ManLevelNum(p), Gia_ManLevelNum(pGia) ); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + } + if ( Gia_ManCoNum(pGia) != Gia_ManCoNum(p) || + (!Acb_GiaAllPosConst0(pGia) && Gia_ManAndNum(pGia) >= nAndStart) ) + { + Gia_ManStop( pGia ); + return NULL; + } + return pGia; +} + +//////////////////////////////////////////////////////////////////////// +/// END OF FILE /// +//////////////////////////////////////////////////////////////////////// + +ABC_NAMESPACE_IMPL_END diff --git a/src/base/acb/acbXec.h b/src/base/acb/acbXec.h new file mode 100644 index 000000000..546913fab --- /dev/null +++ b/src/base/acb/acbXec.h @@ -0,0 +1,71 @@ +/**CFile**************************************************************** + + FileName [acbXec.h] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [Hierarchical word-level netlist.] + + Synopsis [Shared XEC proof helper API.] + +***********************************************************************/ + +#ifndef ABC__base__acb__acbXec_h +#define ABC__base__acb__acbXec_h + +#include "acb.h" + +typedef struct Cnf_Dat_t_ Cnf_Dat_t; + +ABC_NAMESPACE_HEADER_START + +/* + * XEC/network-level proof status: + * ACB_XEC_EQ : networks/output are proven equivalent/UNSAT miter + * ACB_XEC_NEQ : networks/output are proven different/SAT miter + * ACB_XEC_UNDEC : proof was inconclusive + * ACB_XEC_ONE_HARD : local sweep proved all but one output + * ACB_XEC_MANY_HARD : local sweep left multiple hard outputs + * + */ +typedef enum Acb_XecStatus_t_ +{ + ACB_XEC_MANY_HARD = -3, + ACB_XEC_ONE_HARD = -2, + ACB_XEC_UNDEC = -1, + ACB_XEC_NEQ = 0, + ACB_XEC_EQ = 1 +} Acb_XecStatus_t; + +static inline void Acb_NtkPrintUnsupportedObj( Acb_Ntk_t * p, int iObj, const char * pWhere, int ExpectedFans, int ActualFans ) +{ + printf( "%s unsupported ACB object: obj = %d", pWhere ? pWhere : "XEC" , iObj ); + if ( p && iObj >= 0 && iObj < Acb_NtkObjNumMax(p) ) + printf( ", type = %d", Acb_ObjType(p, iObj) ); + if ( ExpectedFans >= 0 || ActualFans >= 0 ) + printf( ", fanins = %d, expected = %d", ActualFans, ExpectedFans ); + printf( ".\n" ); +} + +static inline void Acb_XecMergeTargetStatus( int StatusTarget, int fHasModel, int * pStatus, int * pCheckModel ) +{ + if ( fHasModel && pCheckModel ) + *pCheckModel = 1; + if ( pStatus == NULL ) + return; + if ( StatusTarget == ACB_XEC_EQ ) + *pStatus = ACB_XEC_EQ; + else if ( StatusTarget == ACB_XEC_NEQ ) + *pStatus = ACB_XEC_NEQ; +} + +extern int * Acb_NtkSolveCadicalLimit( Gia_Man_t * p, int fUseHeavyOpt, int fVerbose, int * pStatus, int nSatTimeLimit, char * pLabel, int fUseXecOutputClauses ); +extern int Acb_CnfCoDriverLit( Cnf_Dat_t * pCnf, int iCo, int * pLit ); +extern int Acb_GiaAllPosConst0( Gia_Man_t * p ); + +extern int Acb_XecGiaSolveSmallConeExhaustive( Gia_Man_t * p, int fVerbose, int nTotalLimit ); +extern Gia_Man_t *Acb_XecGiaSmallConeXorRewrite( Gia_Man_t * p, int fVerbose ); + +ABC_NAMESPACE_HEADER_END + +#endif diff --git a/src/base/acb/module.make b/src/base/acb/module.make index ec78b3b0c..b22fb4b45 100644 --- a/src/base/acb/module.make +++ b/src/base/acb/module.make @@ -6,4 +6,5 @@ SRC += src/base/acb/acbAbc.c \ src/base/acb/acbPush.c \ src/base/acb/acbSets.c \ src/base/acb/acbTest.c \ - src/base/acb/acbUtil.c + src/base/acb/acbUtil.c \ + src/base/acb/acbXec.c From bd52945300513693a3375484ea808370307786c1 Mon Sep 17 00:00:00 2001 From: aiquoc Date: Wed, 29 Jul 2026 12:58:28 +0800 Subject: [PATCH 02/14] fix error build --- src/base/acb/acbXec.c | 2 +- src/base/acb/acbXec.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/base/acb/acbXec.c b/src/base/acb/acbXec.c index bfa99f86b..c37a5fe60 100644 --- a/src/base/acb/acbXec.c +++ b/src/base/acb/acbXec.c @@ -81,7 +81,7 @@ static inline word Acb_XecGiaLitWord( Vec_Wrd_t * vSims, int nWords, int Lit, in word Res = Vec_WrdEntry( vSims, Abc_Lit2Var(Lit) * nWords + w ); return Abc_LitIsCompl(Lit) ? ~Res : Res; } -int * Acb_NtkSolveCadicalLimit( Gia_Man_t * p, int fUseHeavyOpt, int fVerbose, int * pStatus, int nSatTimeLimit, char * pLabel, int fUseXecOutputClauses ) +int * Acb_NtkSolveCadicalLimit( Gia_Man_t * p, int fUseHeavyOpt, int fVerbose, int * pStatus, int nSatTimeLimit, const char * pLabel, int fUseXecOutputClauses ) { Aig_Man_t * pMan = NULL; Cnf_Dat_t * pCnf = NULL; diff --git a/src/base/acb/acbXec.h b/src/base/acb/acbXec.h index 546913fab..c774f52af 100644 --- a/src/base/acb/acbXec.h +++ b/src/base/acb/acbXec.h @@ -59,7 +59,7 @@ static inline void Acb_XecMergeTargetStatus( int StatusTarget, int fHasModel, in *pStatus = ACB_XEC_NEQ; } -extern int * Acb_NtkSolveCadicalLimit( Gia_Man_t * p, int fUseHeavyOpt, int fVerbose, int * pStatus, int nSatTimeLimit, char * pLabel, int fUseXecOutputClauses ); +extern int * Acb_NtkSolveCadicalLimit( Gia_Man_t * p, int fUseHeavyOpt, int fVerbose, int * pStatus, int nSatTimeLimit, const char * pLabel, int fUseXecOutputClauses ); extern int Acb_CnfCoDriverLit( Cnf_Dat_t * pCnf, int iCo, int * pLit ); extern int Acb_GiaAllPosConst0( Gia_Man_t * p ); From 842a265d825d036575420a1675cf5b48900e7f9a Mon Sep 17 00:00:00 2001 From: aiquoc Date: Wed, 29 Jul 2026 13:11:35 +0800 Subject: [PATCH 03/14] fix error build --- src/base/acb/acbXec.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/base/acb/acbXec.h b/src/base/acb/acbXec.h index c774f52af..a85c80f5b 100644 --- a/src/base/acb/acbXec.h +++ b/src/base/acb/acbXec.h @@ -14,8 +14,7 @@ #define ABC__base__acb__acbXec_h #include "acb.h" - -typedef struct Cnf_Dat_t_ Cnf_Dat_t; +#include "sat/cnf/cnf.h" ABC_NAMESPACE_HEADER_START From 85770ba29dfa9e764d5afdc47a09b39597589494 Mon Sep 17 00:00:00 2001 From: aiquoc Date: Wed, 29 Jul 2026 13:28:17 +0800 Subject: [PATCH 04/14] fix build windows --- abclib.dsp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/abclib.dsp b/abclib.dsp index 8d4fe0626..adf197ac8 100644 --- a/abclib.dsp +++ b/abclib.dsp @@ -1140,6 +1140,14 @@ SOURCE=.\src\base\acb\acbUtil.c # End Group # Begin Group "wln" +SOURCE=.\src\base\acb\acbXec.c +# End Source File +# Begin Source File + +SOURCE=.\src\base\acb\acbXec.h +# End Source File +# Begin Source File + # PROP Default_Filter "" # Begin Source File From 4d504f20ba273434f4cabbb974763187dc0e94e6 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Fri, 7 Aug 2026 20:54:11 +0200 Subject: [PATCH 05/14] fraig_store: restore the PI order when the name comparison fails Abc_NtkCompareSignals() sorts the PIs, POs and boxes of both networks by name before comparing them. That is deliberate and is what lets fraig_store accept two networks that use the same names in a different order. When the names do not match, though, the comparison fails, Abc_NtkFraigStore() resets the store and keeps the incoming network -- which by then has already been sorted. The network that ends up in the store is a permutation of the one the caller read in, and nothing reports it. The two lines printed on that path say the store was reset; they do not say the interface changed. Sorting is by name as a string, so numeric port names are where it shows up worst: 1, 2, ..., 10 sort as 1, 10, 2, 3, ... With the EPFL cavlc benchmark and its published reference netlist, whose ports are named "1".."10", read_aiger cavlc.aig; strash; fraig_store read_blif cavlc_size.blif; strash; fraig_store fraig_restore; write_blif out.blif gives an out.blif whose inputs are ordered 1, 10, 2, 3, ... instead of 1, 2, 3, ..., 10. It has the right number of inputs and outputs, it passes Abc_NtkCheck(), and "cec -n out.blif cavlc.aig" reports a counterexample. Save the three vectors before the comparison and put them back if it fails, then rebuild vCis/vCos with Abc_NtkOrderCisCos(). The success path is untouched, and so is every path where the names already agree -- those never reach Abc_NtkCompareSignals(), since Abc_NodeCompareCiCo() has already returned 1. --- src/base/abci/abcFraig.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/base/abci/abcFraig.c b/src/base/abci/abcFraig.c index 2cfb46bb0..bf40bd582 100644 --- a/src/base/abci/abcFraig.c +++ b/src/base/abci/abcFraig.c @@ -670,13 +670,29 @@ int Abc_NtkFraigStore( Abc_Ntk_t * pNtkAdd ) extern int Abc_NodeCompareCiCo( Abc_Ntk_t * pNtkOld, Abc_Ntk_t * pNtkNew ); if ( !Abc_NodeCompareCiCo(pNtk, (Abc_Ntk_t *)Vec_PtrEntry(vStore, 0)) ) { + // Abc_NtkCompareSignals() sorts the PIs/POs/boxes of both networks by name as a + // side effect, which is what makes the comparison meaningful when the two do use + // the same names. When they do not, the comparison fails, the store is reset and + // this network is kept -- so the sort has to be undone here. Otherwise the stored + // network is a permutation of the one the caller read in, and everything after it + // is off by that permutation with nothing to indicate it. + Vec_Ptr_t * vPis = Vec_PtrDup( pNtk->vPis ); + Vec_Ptr_t * vPos = Vec_PtrDup( pNtk->vPos ); + Vec_Ptr_t * vBoxes = Vec_PtrDup( pNtk->vBoxes ); // reorder PIs of pNtk2 according to pNtk1 if ( !Abc_NtkCompareSignals( pNtk, (Abc_Ntk_t *)Vec_PtrEntry(vStore, 0), 1, 1 ) ) { + Vec_PtrFree( pNtk->vPis ); pNtk->vPis = vPis; vPis = NULL; + Vec_PtrFree( pNtk->vPos ); pNtk->vPos = vPos; vPos = NULL; + Vec_PtrFree( pNtk->vBoxes ); pNtk->vBoxes = vBoxes; vBoxes = NULL; + Abc_NtkOrderCisCos( pNtk ); printf( "Trying to store the network with different primary inputs.\n" ); printf( "The previously stored networks are deleted and this one is added.\n" ); Abc_NtkFraigStoreClean(); } + if ( vPis ) Vec_PtrFree( vPis ); + if ( vPos ) Vec_PtrFree( vPos ); + if ( vBoxes ) Vec_PtrFree( vBoxes ); } } Vec_PtrPush( vStore, pNtk ); From 4473e39efc211b72e8a407e0c5987efa8df7a98b Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sat, 8 Aug 2026 08:43:10 +0200 Subject: [PATCH 06/14] acd: seed bestPerm to avoid an uninitialised read in enumerate_iset_combinations bestPerm is only written inside the 'cost < best_cost' branch. When no combination beats the initial best_cost -- which happens for an infeasible free-set size -- the array is never written, yet the tail of the function still evaluates permutations[bestPerm[i]]. That reads uninitialised stack and then uses the value to index permutations[], so it is an out-of-bounds read as well. Upstream results are unaffected in practice because the caller discards the permutation on that path, but it is undefined behaviour and it becomes a hard segfault as soon as the stack layout changes -- adding two members to the decomposer object was enough to trigger it reliably. Seeding the identity permutation in the existing initialisation loop is sufficient and costs nothing. --- src/map/if/acd/ac_decomposition.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/map/if/acd/ac_decomposition.hpp b/src/map/if/acd/ac_decomposition.hpp index 8d5ddb5c4..c6df54662 100644 --- a/src/map/if/acd/ac_decomposition.hpp +++ b/src/map/if/acd/ac_decomposition.hpp @@ -482,6 +482,11 @@ private: for ( uint32_t i = 0; i < num_vars; ++i ) { pComb[i] = pInvPerm[i] = i; + /* bestPerm is written only when some combination beats the initial + * best_cost. When none does, the loop below still evaluates + * permutations[bestPerm[i]], which reads uninitialised stack and then + * indexes permutations[] with it. Seed the identity permutation. */ + bestPerm[i] = i; } /* early bail-out conditions */ From 0f5951ab2c60481a1ada9c577dfc3703fa90d77b Mon Sep 17 00:00:00 2001 From: agentic-synthesis Date: Sat, 8 Aug 2026 09:50:02 +0200 Subject: [PATCH 07/14] ACD: avoid a 64-bit shift by 64 or more in local_extend_to ac_decomposition_impl::local_extend_to() replicates a truth table that really depends on `real_num_vars` variables across the full `num_vars`-variable static truth table. For real_num_vars < 6 it does so by folding the first word: for ( auto i = real_num_vars; i < num_vars; ++i ) mask |= ( mask << ( 1 << i ) ); Once i reaches 6 the shift distance is 1 << 6 == 64, which is at least the width of the 64-bit operand, so the shift has undefined behaviour. This is reached whenever the cut being decomposed has more than six variables, i.e. in every ordinary use of `if -K k -Z n` with k > 6; UBSan reports ac_decomposition.hpp: runtime error: shift exponent 64 is too large for 64-bit type 'long unsigned int' on, for example, `read adder.aig; strash; dch -f; if -K 11 -Z 6 -C 12`. On x86 the shift is taken modulo 64 and the iteration happens to be a no-op, so the observable behaviour today is correct, but that is not guaranteed by the language and other targets shift in a saturating or unspecified way. Variables 6 and above do not need the fold at all: the subsequent std::fill() over the whole block array already replicates the word across every block. Clamp the loop to the variables that live inside one word. No behavioural change on x86. --- src/map/if/acd/ac_decomposition.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/map/if/acd/ac_decomposition.hpp b/src/map/if/acd/ac_decomposition.hpp index 8d5ddb5c4..f8c55bfd7 100644 --- a/src/map/if/acd/ac_decomposition.hpp +++ b/src/map/if/acd/ac_decomposition.hpp @@ -1317,7 +1317,10 @@ private: { auto mask = *tt.begin(); - for ( auto i = real_num_vars; i < num_vars; ++i ) + /* Replicate within the word only. Variables 6 and above are replicated by the + * std::fill below, and shifting a 64-bit word by (1 << i) for i >= 6 is undefined + * behaviour rather than a no-op. */ + for ( auto i = real_num_vars; i < std::min( num_vars, 6u ); ++i ) { mask |= ( mask << ( 1 << i ) ); } From 5cbfa76dc35589f11a022e1b07aede5cee4b480c Mon Sep 17 00:00:00 2001 From: agentic-synthesis Date: Sat, 8 Aug 2026 10:21:19 +0200 Subject: [PATCH 08/14] lutpack: do not trust an approximate cofactor support in the MUX split `lutpack` aborts on some networks with abc: src/opt/lpk/lpkAbcMux.c:192: Lpk_MuxSplit: Assertion `iVarVac < (int)p->nVars' failed. Reproducer (a 24.7k-LUT `sqrt` netlist produced by `if -K 10 -Z 6`, ~10 s): read_blif sqrt-mapped.blif; lutpack Lpk_MuxSplit() splits one component off a function and stores the new component in a *vacant* fanin slot of the retained one: p->uSupp = Kit_TruthSupport( Pol ? pTruth1 : pTruth0, p->nVars ); p->uSupp |= (1 << Var); iVarVac = Kit_WordFindFirstBit( ~p->uSupp ); assert( iVarVac < (int)p->nVars ); A vacant slot is supposed to be guaranteed by Lpk_MuxAnalize(), which rejects a candidate variable when nSuppSizeL = max(nSuppSize0 + 2*!Polarity, nSuppSize1 + 2*Polarity) > p->nVars but it reads nSuppSize0/nSuppSize1 out of the *cached* p->puSupps[]. When those came from Lpk_ComputeSupports() they are not exact: that routine builds two BDDs of the function in opposite variable orders and stitches the two support estimates together at the cofactoring variable, and the result can be a strict subset of the true cofactor support. Lpk_MuxAnalize() then admits a variable whose split needs one slot more than the function has. On the reproducer this happens for a 12-variable component at Var = 3, Polarity = 1: the cached support of cofactor 1 is 0x3f7 (9 variables) while the truth table's is 0xff7 (11). The guard sees 9 + 2 = 11 <= 12 and accepts; the split then produces uSupp = 0xff7 | (1 << 3) = 0xfff, which is full. Instrumenting the same run shows the estimate differs from the exact support in 484 of 101970 cofactor supports, and is narrower in 352 of them, so this is not a one-off. Rather than change the support estimator or weaken the assertion -- which documents a real invariant of Lpk_MuxSplit() -- re-derive the single support the split depends on, once the candidate has been chosen, and decline the MUX decomposition when it does not fit. That is one cofactor and one support scan per accepted candidate, not per candidate variable. On the reproducer lutpack then completes and yields the same result as recomputing every cached support from the truth table (24694 -> 24635 nodes, 237 levels in both cases). --- src/opt/lpk/lpkAbcDec.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/opt/lpk/lpkAbcDec.c b/src/opt/lpk/lpkAbcDec.c index b7d4ccb1f..08d1daf30 100644 --- a/src/opt/lpk/lpkAbcDec.c +++ b/src/opt/lpk/lpkAbcDec.c @@ -202,6 +202,24 @@ pMan->timeEvalMuxAn += Abc_Clock() - clk; assert( pResMux == NULL || pResDsd == NULL ); if ( pResMux ) { + // Lpk_MuxAnalize() decides feasibility from the cached cofactor supports in + // p->puSupps. Those may have come from Lpk_ComputeSupports(), which derives + // them from two BDDs built in opposite variable orders and stitches the halves + // together, and that estimate can be a strict SUBSET of the true cofactor + // support. When it is, the component retained by the split below ends up with + // no vacant fanin slot for the component that is split off, and Lpk_MuxSplit() + // fails its assertion `iVarVac < (int)p->nVars'. Re-derive the one support the + // split actually depends on and decline the MUX decomposition if it does not fit. + unsigned * pTruthThis = Lpk_FunTruth( p, 0 ); + unsigned * pTruthCof = Lpk_FunTruth( p, 1 ); + unsigned uSuppExact; + if ( pResMux->Polarity ) + Kit_TruthCofactor1New( pTruthCof, pTruthThis, p->nVars, pResMux->Variable ); + else + Kit_TruthCofactor0New( pTruthCof, pTruthThis, p->nVars, pResMux->Variable ); + uSuppExact = Kit_TruthSupport( pTruthCof, p->nVars ) | ( 1 << pResMux->Variable ); + if ( Kit_WordCountOnes( uSuppExact ) >= (int)p->nVars ) + return 0; clk = Abc_Clock(); p2 = Lpk_MuxSplit( pMan, p, pResMux->Variable, pResMux->Polarity ); pMan->timeEvalMuxSp += Abc_Clock() - clk; From d4e3670e213ec884170ba6511a8f4f60cdd1f906 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Tue, 11 Aug 2026 19:22:06 +0200 Subject: [PATCH 09/14] &cec -x/-y, &icec: also require the swept miter outputs to be constant 0 The equivalence verdict in these three branches is taken from Gia_ManAndNum(pNew) == 0 after Cec4_/Cec5_ManSimulateTest3. An AND-free GIA can still have outputs that are constant 1 or CI literals, which are satisfiable, so this reports "Networks are equivalent" for some non-equivalent pairs -- for example `a & b` against `~(a & b)`, where the miter sweeps to constant 1. Check the outputs as well: AND nodes remaining -> UNDECIDED as before; AND-free with all outputs constant 0 -> equivalent as before; AND-free otherwise -> NOT equivalent, which is decidable by inspection since such a miter is satisfiable. --- src/base/abci/abc.c | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/base/abci/abc.c b/src/base/abci/abc.c index 86a9fd2ca..708d19013 100644 --- a/src/base/abci/abc.c +++ b/src/base/abci/abc.c @@ -43874,6 +43874,29 @@ static Gia_Man_t * Abc_ReadAigerOrVerilogFile( char * pFileName, char * pFileNam return pGia; } +/**Function************************************************************* + + Synopsis [Returns 1 if all outputs of the swept miter are constant 0.] + + Description [The equivalence check below concludes from the swept miter + having no AND nodes. An AND-free GIA can still have outputs that are + constant 1 or CI literals, which are satisfiable, so the outputs are + checked here as well.] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +static int Abc_CecSweptMiterIsConst0( Gia_Man_t * p ) +{ + int i; + for ( i = 0; i < Gia_ManPoNum(p); i++ ) + if ( !Gia_ManPoIsConst0(p, i) ) + return 0; + return 1; +} + /**Function************************************************************* Synopsis [] @@ -44302,10 +44325,12 @@ int Abc_CommandAbc9Cec( Abc_Frame_t * pAbc, int argc, char ** argv ) abctime clk = Abc_Clock(); extern Gia_Man_t * Cec4_ManSimulateTest3( Gia_Man_t * p, int nBTLimit, int fVerbose ); Gia_Man_t * pNew = Cec4_ManSimulateTest3( pMiter, pPars->nBTLimit, pPars->fVerbose ); - if ( Gia_ManAndNum(pNew) == 0 ) + if ( Gia_ManAndNum(pNew) != 0 ) + Abc_Print( 1, "Networks are UNDECIDED. " ); + else if ( Abc_CecSweptMiterIsConst0(pNew) ) Abc_Print( 1, "Networks are equivalent. " ); else - Abc_Print( 1, "Networks are UNDECIDED. " ); + Abc_Print( 1, "Networks are NOT equivalent. " ); Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); Gia_ManStop( pNew ); } @@ -44314,10 +44339,12 @@ int Abc_CommandAbc9Cec( Abc_Frame_t * pAbc, int argc, char ** argv ) abctime clk = Abc_Clock(); extern Gia_Man_t * Cec5_ManSimulateTest3( Gia_Man_t * p, int nBTLimit, int fVerbose ); Gia_Man_t * pNew = Cec5_ManSimulateTest3( pMiter, pPars->nBTLimit, pPars->fVerbose ); - if ( Gia_ManAndNum(pNew) == 0 ) + if ( Gia_ManAndNum(pNew) != 0 ) + Abc_Print( 1, "Networks are UNDECIDED. " ); + else if ( Abc_CecSweptMiterIsConst0(pNew) ) Abc_Print( 1, "Networks are equivalent. " ); else - Abc_Print( 1, "Networks are UNDECIDED. " ); + Abc_Print( 1, "Networks are NOT equivalent. " ); Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); Gia_ManStop( pNew ); } @@ -44518,10 +44545,12 @@ int Abc_CommandAbc9ICec( Abc_Frame_t * pAbc, int argc, char ** argv ) abctime clk = Abc_Clock(); extern Gia_Man_t * Cec4_ManSimulateTest3( Gia_Man_t * p, int nBTLimit, int fVerbose ); Gia_Man_t * pNew = Cec4_ManSimulateTest3( pMiter, pPars->nBTLimit, pPars->fVerbose ); - if ( Gia_ManAndNum(pNew) == 0 ) + if ( Gia_ManAndNum(pNew) != 0 ) + Abc_Print( 1, "Networks are UNDECIDED. " ); + else if ( Abc_CecSweptMiterIsConst0(pNew) ) Abc_Print( 1, "Networks are equivalent. " ); else - Abc_Print( 1, "Networks are UNDECIDED. " ); + Abc_Print( 1, "Networks are NOT equivalent. " ); Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); Gia_ManStop( pNew ); } From ebd3e31a8daeea5893590423a3b46806b7373510 Mon Sep 17 00:00:00 2001 From: Franz Reichl Date: Thu, 13 Aug 2026 12:57:15 +0200 Subject: [PATCH 10/14] Fix a bug that can arise when reducing circuit depth. --- src/opt/eslim/delayEngine.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/opt/eslim/delayEngine.cpp b/src/opt/eslim/delayEngine.cpp index 5e92566cf..c0a867511 100644 --- a/src/opt/eslim/delayEngine.cpp +++ b/src/opt/eslim/delayEngine.cpp @@ -121,9 +121,8 @@ namespace eSLIM { } std::vector DelayEngine::reduceDelay(unsigned int max_size, unsigned int initial_delay) { - assert (delay_selectors.find(initial_delay) != delay_selectors.end()); std::vector last_model; - for( auto it = delay_selectors.find(initial_delay); it != delay_selectors.end(); ++it ) { + for( auto it = delay_selectors.lower_bound(initial_delay); it != delay_selectors.end(); ++it ) { int d = it->first; double timeout = getDynamicTimeout(max_size); int status = existsReplacement(max_size, d, timeout); From d389f88285ca1560be5a522a00a4d14bd286b669 Mon Sep 17 00:00:00 2001 From: Alan Mishchenko Date: Sat, 15 Aug 2026 09:18:47 -0700 Subject: [PATCH 11/14] Remove obsolete ACB command interface --- src/base/acb/acbCom.c | 735 --------------------------------------- src/base/acb/module.make | 1 - 2 files changed, 736 deletions(-) delete mode 100644 src/base/acb/acbCom.c diff --git a/src/base/acb/acbCom.c b/src/base/acb/acbCom.c deleted file mode 100644 index 3d62454d3..000000000 --- a/src/base/acb/acbCom.c +++ /dev/null @@ -1,735 +0,0 @@ -/**CFile**************************************************************** - - FileName [acbCom.c] - - SystemName [ABC: Logic synthesis and verification system.] - - PackageName [Hierarchical word-level netlist.] - - Synopsis [Command handlers.] - - Author [Alan Mishchenko] - - Affiliation [UC Berkeley] - - Date [Ver. 1.0. Started - November 29, 2014.] - - Revision [$Id: acbCom.c,v 1.00 2014/11/29 00:00:00 alanmi Exp $] - -***********************************************************************/ - -#include "acb.h" -#include "proof/cec/cec.h" -#include "base/main/mainInt.h" - -ABC_NAMESPACE_IMPL_START - -#if 0 - -//////////////////////////////////////////////////////////////////////// -/// DECLARATIONS /// -//////////////////////////////////////////////////////////////////////// - -static int Acb_CommandRead ( Abc_Frame_t * pAbc, int argc, char ** argv ); -static int Acb_CommandWrite ( Abc_Frame_t * pAbc, int argc, char ** argv ); -static int Acb_CommandPs ( Abc_Frame_t * pAbc, int argc, char ** argv ); -static int Acb_CommandPut ( Abc_Frame_t * pAbc, int argc, char ** argv ); -static int Acb_CommandGet ( Abc_Frame_t * pAbc, int argc, char ** argv ); -static int Acb_CommandClp ( Abc_Frame_t * pAbc, int argc, char ** argv ); -static int Acb_CommandBlast ( Abc_Frame_t * pAbc, int argc, char ** argv ); -static int Acb_CommandCec ( Abc_Frame_t * pAbc, int argc, char ** argv ); -static int Acb_CommandTest ( Abc_Frame_t * pAbc, int argc, char ** argv ); - -static inline Acb_Man_t * Acb_AbcGetMan( Abc_Frame_t * pAbc ) { return (Acb_Man_t *)pAbc->pAbcCba; } -static inline void Acb_AbcFreeMan( Abc_Frame_t * pAbc ) { if ( pAbc->pAbcCba ) Acb_ManFree(Acb_AbcGetMan(pAbc)); } -static inline void Acb_AbcUpdateMan( Abc_Frame_t * pAbc, Acb_Man_t * p ) { Acb_AbcFreeMan(pAbc); pAbc->pAbcCba = p; } - -//////////////////////////////////////////////////////////////////////// -/// FUNCTION DEFINITIONS /// -//////////////////////////////////////////////////////////////////////// - -/**Function******************************************************************** - - Synopsis [] - - Description [] - - SideEffects [] - - SeeAlso [] - -******************************************************************************/ -void Acb_Init( Abc_Frame_t * pAbc ) -{ - Cmd_CommandAdd( pAbc, "New word level", "@read", Acb_CommandRead, 0 ); - Cmd_CommandAdd( pAbc, "New word level", "@write", Acb_CommandWrite, 0 ); - Cmd_CommandAdd( pAbc, "New word level", "@ps", Acb_CommandPs, 0 ); - Cmd_CommandAdd( pAbc, "New word level", "@put", Acb_CommandPut, 0 ); - Cmd_CommandAdd( pAbc, "New word level", "@get", Acb_CommandGet, 0 ); - Cmd_CommandAdd( pAbc, "New word level", "@clp", Acb_CommandClp, 0 ); - Cmd_CommandAdd( pAbc, "New word level", "@blast", Acb_CommandBlast, 0 ); - Cmd_CommandAdd( pAbc, "New word level", "@cec", Acb_CommandCec, 0 ); - Cmd_CommandAdd( pAbc, "New word level", "@test", Acb_CommandTest, 0 ); -} - -/**Function******************************************************************** - - Synopsis [] - - Description [] - - SideEffects [] - - SeeAlso [] - -******************************************************************************/ -void Acb_End( Abc_Frame_t * pAbc ) -{ - Acb_AbcFreeMan( pAbc ); -} - - -/**Function******************************************************************** - - Synopsis [] - - Description [] - - SideEffects [] - - SeeAlso [] - -******************************************************************************/ -int Acb_CommandRead( Abc_Frame_t * pAbc, int argc, char ** argv ) -{ - FILE * pFile; - Acb_Man_t * p = NULL; - char * pFileName = NULL; - int c, fTest = 0, fDfs = 0, fVerbose = 0; - Extra_UtilGetoptReset(); - 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; - case 'h': - goto usage; - default: - goto usage; - } - } - if ( argc != globalUtilOptind + 1 ) - { - printf( "Acb_CommandRead(): Input file name should be given on the command line.\n" ); - return 0; - } - // get the file name - pFileName = argv[globalUtilOptind]; - if ( (pFile = fopen( pFileName, "r" )) == NULL ) - { - Abc_Print( 1, "Cannot open input file \"%s\". ", pFileName ); - if ( (pFileName = Extra_FileGetSimilarName( pFileName, ".v", ".blif", ".smt", ".acb", NULL )) ) - Abc_Print( 1, "Did you mean \"%s\"?", pFileName ); - Abc_Print( 1, "\n" ); - return 0; - } - fclose( pFile ); - if ( fTest ) - { - if ( !strcmp( Extra_FileNameExtension(pFileName), "blif" ) ) - Prs_ManReadBlifTest( pFileName ); - else if ( !strcmp( Extra_FileNameExtension(pFileName), "v" ) ) - Prs_ManReadVerilogTest( pFileName ); - else - { - printf( "Unrecognized input file extension.\n" ); - return 0; - } - return 0; - } - if ( !strcmp( Extra_FileNameExtension(pFileName), "blif" ) ) - p = Acb_ManReadBlif( pFileName ); - else if ( !strcmp( Extra_FileNameExtension(pFileName), "v" ) ) - p = Acb_ManReadVerilog( pFileName ); - else if ( !strcmp( Extra_FileNameExtension(pFileName), "acb" ) ) - p = Acb_ManReadCba( pFileName ); - else - { - printf( "Unrecognized input file extension.\n" ); - return 0; - } - if ( fDfs ) - { - Acb_Man_t * pTemp; - p = Acb_ManDup( pTemp = p, Acb_NtkCollectDfs ); - Acb_ManFree( pTemp ); - } - Acb_AbcUpdateMan( pAbc, p ); - return 0; -usage: - Abc_Print( -2, "usage: @read [-tdvh] \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; -} - -/**Function******************************************************************** - - Synopsis [] - - Description [] - - SideEffects [] - - SeeAlso [] - -******************************************************************************/ -int Acb_CommandWrite( Abc_Frame_t * pAbc, int argc, char ** argv ) -{ - Acb_Man_t * p = Acb_AbcGetMan(pAbc); - char * pFileName = NULL; - int fInclineCats = 0; - int c, fVerbose = 0; - Extra_UtilGetoptReset(); - while ( ( c = Extra_UtilGetopt( argc, argv, "cvh" ) ) != EOF ) - { - switch ( c ) - { - case 'c': - fInclineCats ^= 1; - break; - case 'v': - fVerbose ^= 1; - break; - case 'h': - goto usage; - default: - goto usage; - } - } - if ( p == NULL ) - { - Abc_Print( 1, "Acb_CommandWrite(): There is no current design.\n" ); - return 0; - } - - if ( argc == globalUtilOptind + 1 ) - pFileName = argv[globalUtilOptind]; - else if ( argc == globalUtilOptind && p ) - { - pFileName = Extra_FileNameGenericAppend( Acb_ManSpec(p) ? Acb_ManSpec(p) : Acb_ManName(p), "_out.v" ); - printf( "Generated output file name \"%s\".\n", pFileName ); - } - else - { - printf( "Output file name should be given on the command line.\n" ); - return 0; - } - // perform writing - if ( !strcmp( Extra_FileNameExtension(pFileName), "blif" ) ) - Acb_ManWriteBlif( pFileName, p ); - else if ( !strcmp( Extra_FileNameExtension(pFileName), "v" ) ) - Acb_ManWriteVerilog( pFileName, p, fInclineCats ); - else if ( !strcmp( Extra_FileNameExtension(pFileName), "acb" ) ) - Acb_ManWriteCba( pFileName, p ); - else - { - printf( "Unrecognized output file extension.\n" ); - return 0; - } - return 0; -usage: - Abc_Print( -2, "usage: @write [-cvh]\n" ); - Abc_Print( -2, "\t writes the design into a file in BLIF or Verilog\n" ); - Abc_Print( -2, "\t-c : toggle inlining input concatenations [default = %s]\n", fInclineCats? "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 Acb_CommandPs( Abc_Frame_t * pAbc, int argc, char ** argv ) -{ - Acb_Man_t * p = Acb_AbcGetMan(pAbc); - int nModules = 0; - int fShowMulti = 0; - int fShowAdder = 0; - int fDistrib = 0; - int c, fVerbose = 0; - Extra_UtilGetoptReset(); - while ( ( c = Extra_UtilGetopt( argc, argv, "Mmadvh" ) ) != EOF ) - { - switch ( c ) - { - case 'M': - if ( globalUtilOptind >= argc ) - { - Abc_Print( -1, "Command line switch \"-M\" should be followed by an integer.\n" ); - goto usage; - } - nModules = atoi(argv[globalUtilOptind]); - globalUtilOptind++; - 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; - case 'h': - goto usage; - default: - goto usage; - } - } - if ( p == NULL ) - { - Abc_Print( 1, "Acb_CommandPs(): There is no current design.\n" ); - return 0; - } - if ( nModules ) - { - Acb_ManPrintStats( p, nModules, fVerbose ); - return 0; - } - Acb_NtkPrintStatsFull( Acb_ManRoot(p), fDistrib, fVerbose ); - if ( fShowMulti ) - Acb_NtkPrintNodes( Acb_ManRoot(p), ABC_OPER_ARI_MUL ); - if ( fShowAdder ) - Acb_NtkPrintNodes( Acb_ManRoot(p), ABC_OPER_ARI_ADD ); - return 0; -usage: - 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; -} - -/**Function******************************************************************** - - Synopsis [] - - Description [] - - SideEffects [] - - SeeAlso [] - -******************************************************************************/ -int Acb_CommandPut( Abc_Frame_t * pAbc, int argc, char ** argv ) -{ - Acb_Man_t * p = Acb_AbcGetMan(pAbc); - Gia_Man_t * pGia = NULL; - int c, fBarBufs = 1, fSeq = 0, fVerbose = 0; - Extra_UtilGetoptReset(); - 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; - case 'h': - goto usage; - default: - goto usage; - } - } - if ( p == NULL ) - { - Abc_Print( 1, "Acb_CommandPut(): There is no current design.\n" ); - return 0; - } - pGia = Acb_ManBlast( p, fBarBufs, fSeq, fVerbose ); - if ( pGia == NULL ) - { - Abc_Print( 1, "Acb_CommandPut(): Conversion to AIG has failed.\n" ); - return 0; - } - Abc_FrameUpdateGia( pAbc, pGia ); - return 0; -usage: - 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; -} - -/**Function******************************************************************** - - Synopsis [] - - Description [] - - SideEffects [] - - SeeAlso [] - -******************************************************************************/ -int Acb_CommandGet( Abc_Frame_t * pAbc, int argc, char ** argv ) -{ - Acb_Man_t * pNew = NULL, * p = Acb_AbcGetMan(pAbc); - int c, fMapped = 0, fVerbose = 0; - Extra_UtilGetoptReset(); - while ( ( c = Extra_UtilGetopt( argc, argv, "mvh" ) ) != EOF ) - { - switch ( c ) - { - case 'm': - fMapped ^= 1; - break; - case 'v': - fVerbose ^= 1; - break; - case 'h': - goto usage; - default: - goto usage; - } - } - if ( p == NULL ) - { - Abc_Print( 1, "Acb_CommandGet(): There is no current design.\n" ); - return 0; - } - - if ( fMapped ) - { - if ( pAbc->pNtkCur == NULL ) - { - Abc_Print( 1, "Acb_CommandGet(): There is no current mapped design.\n" ); - return 0; - } - pNew = Acb_ManInsertAbc( p, pAbc->pNtkCur ); - } - else - { - if ( pAbc->pGia == NULL ) - { - Abc_Print( 1, "Acb_CommandGet(): There is no current AIG.\n" ); - return 0; - } - pNew = Acb_ManInsertGia( p, pAbc->pGia ); - } - Acb_AbcUpdateMan( pAbc, pNew ); - return 0; -usage: - Abc_Print( -2, "usage: @get [-mvh]\n" ); - Abc_Print( -2, "\t extracts AIG or mapped network into the hierarchical design\n" ); - Abc_Print( -2, "\t-m : toggle using mapped network from main-space [default = %s]\n", fMapped? "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 Acb_CommandClp( Abc_Frame_t * pAbc, int argc, char ** argv ) -{ - Acb_Man_t * pNew = NULL, * p = Acb_AbcGetMan(pAbc); - int c, fVerbose = 0; - Extra_UtilGetoptReset(); - while ( ( c = Extra_UtilGetopt( argc, argv, "vh" ) ) != EOF ) - { - switch ( c ) - { - case 'v': - fVerbose ^= 1; - break; - case 'h': - goto usage; - default: - goto usage; - } - } - if ( p == NULL ) - { - Abc_Print( 1, "Acb_CommandGet(): There is no current design.\n" ); - return 0; - } - pNew = Acb_ManCollapse( p ); - Acb_AbcUpdateMan( pAbc, pNew ); - return 0; -usage: - Abc_Print( -2, "usage: @clp [-vh]\n" ); - Abc_Print( -2, "\t collapses the current hierarchical design\n" ); - 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 Acb_CommandBlast( Abc_Frame_t * pAbc, int argc, char ** argv ) -{ - Gia_Man_t * pNew = NULL; - Acb_Man_t * p = Acb_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, "Acb_CommandBlast(): There is no current design.\n" ); - return 0; - } - pNew = Acb_ManBlast( p, 0, fSeq, fVerbose ); - if ( pNew == NULL ) - { - Abc_Print( 1, "Acb_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 Acb_CommandCec( Abc_Frame_t * pAbc, int argc, char ** argv ) -{ - Acb_Man_t * p = Acb_AbcGetMan(pAbc), * pTemp; - Gia_Man_t * pFirst, * pSecond, * pMiter; - Cec_ParCec_t ParsCec, * pPars = &ParsCec; - char * pFileName, * pStr, ** pArgvNew; - int c, nArgcNew, fDumpMiter = 0; - FILE * pFile; - Cec_ManCecSetDefaultParams( pPars ); - Extra_UtilGetoptReset(); - while ( ( c = Extra_UtilGetopt( argc, argv, "vh" ) ) != EOF ) - { - switch ( c ) - { - case 'v': - pPars->fVerbose ^= 1; - break; - case 'h': - goto usage; - default: - goto usage; - } - } - if ( p == NULL ) - { - Abc_Print( 1, "Acb_CommandCec(): There is no current design.\n" ); - return 0; - } - - pArgvNew = argv + globalUtilOptind; - nArgcNew = argc - globalUtilOptind; - if ( nArgcNew != 1 ) - { - if ( p->pSpec == NULL ) - { - Abc_Print( -1, "File name is not given on the command line.\n" ); - return 1; - } - pFileName = p->pSpec; - } - else - pFileName = pArgvNew[0]; - // fix the wrong symbol - for ( pStr = pFileName; *pStr; pStr++ ) - if ( *pStr == '>' ) - *pStr = '\\'; - if ( (pFile = fopen( pFileName, "r" )) == NULL ) - { - Abc_Print( -1, "Cannot open input file \"%s\". ", pFileName ); - if ( (pFileName = Extra_FileGetSimilarName( pFileName, ".v", ".blif", NULL, NULL, NULL )) ) - Abc_Print( 1, "Did you mean \"%s\"?", pFileName ); - Abc_Print( 1, "\n" ); - return 1; - } - fclose( pFile ); - - // extract AIG from the current design - pFirst = Acb_ManBlast( p, 0, 0, 0 ); - if ( pFirst == NULL ) - { - Abc_Print( -1, "Extracting AIG from the current design has failed.\n" ); - return 0; - } - // extract AIG from the second design - - if ( !strcmp( Extra_FileNameExtension(pFileName), "blif" ) ) - pTemp = Acb_ManReadBlif( pFileName ); - else if ( !strcmp( Extra_FileNameExtension(pFileName), "v" ) ) - pTemp = Acb_ManReadVerilog( pFileName ); - else if ( !strcmp( Extra_FileNameExtension(pFileName), "acb" ) ) - pTemp = Acb_ManReadCba( pFileName ); - else assert( 0 ); - pSecond = Acb_ManBlast( pTemp, 0, 0, 0 ); - Acb_ManFree( pTemp ); - if ( pSecond == NULL ) - { - Gia_ManStop( pFirst ); - Abc_Print( -1, "Extracting AIG from the original design has failed.\n" ); - return 0; - } - // compute the miter - pMiter = Gia_ManMiter( pFirst, pSecond, 0, 1, 0, 0, pPars->fVerbose ); - if ( pMiter ) - { - if ( fDumpMiter ) - { - Abc_Print( 0, "The verification miter is written into file \"%s\".\n", "cec_miter.aig" ); - Gia_AigerWrite( pMiter, "cec_miter.aig", 0, 0, 0 ); - } - pAbc->Status = Cec_ManVerify( pMiter, pPars ); - //Abc_FrameReplaceCex( pAbc, &pAbc->pGia->pCexComb ); - Gia_ManStop( pMiter ); - } - Gia_ManStop( pFirst ); - Gia_ManStop( pSecond ); - return 0; -usage: - Abc_Print( -2, "usage: @cec [-vh]\n" ); - Abc_Print( -2, "\t combinational equivalence checking\n" ); - Abc_Print( -2, "\t-v : toggle printing verbose information [default = %s]\n", pPars->fVerbose? "yes": "no" ); - Abc_Print( -2, "\t-h : print the command usage\n"); - return 1; -} - -/**Function******************************************************************** - - Synopsis [] - - Description [] - - SideEffects [] - - SeeAlso [] - -******************************************************************************/ -int Acb_CommandTest( Abc_Frame_t * pAbc, int argc, char ** argv ) -{ - Acb_Man_t * p = Acb_AbcGetMan(pAbc); - int c, fVerbose = 0; - Extra_UtilGetoptReset(); - while ( ( c = Extra_UtilGetopt( argc, argv, "vh" ) ) != EOF ) - { - switch ( c ) - { - case 'v': - fVerbose ^= 1; - break; - case 'h': - goto usage; - default: - goto usage; - } - } - if ( p == NULL ) - { - Abc_Print( 1, "Acb_CommandTest(): There is no current design.\n" ); - return 0; - } - return 0; -usage: - Abc_Print( -2, "usage: @test [-vh]\n" ); - Abc_Print( -2, "\t experiments with word-level networks\n" ); - 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; -} - -#endif - -//////////////////////////////////////////////////////////////////////// -/// END OF FILE /// -//////////////////////////////////////////////////////////////////////// - - -ABC_NAMESPACE_IMPL_END - diff --git a/src/base/acb/module.make b/src/base/acb/module.make index ec78b3b0c..c399e73af 100644 --- a/src/base/acb/module.make +++ b/src/base/acb/module.make @@ -1,6 +1,5 @@ SRC += src/base/acb/acbAbc.c \ src/base/acb/acbAig.c \ - src/base/acb/acbCom.c \ src/base/acb/acbFunc.c \ src/base/acb/acbMfs.c \ src/base/acb/acbPush.c \ From 12eb48b47618d91eb49082066a7a03e6ebcb2500 Mon Sep 17 00:00:00 2001 From: Alan Mishchenko Date: Sat, 15 Aug 2026 09:23:19 -0700 Subject: [PATCH 12/14] Add experimental word-level data-structure --- Makefile | 2 +- abclib.dsp | 72 +- src/aig/miniaig/miniaig.h | 15 +- src/base/main/mainInit.c | 4 + src/base/main/mainInt.h | 1 + src/base/sn/module.make | 1 + src/base/sn/readme.md | 226 ++ src/base/sn/sn.h | 5267 +++++++++++++++++++++++++++++++++++++ src/base/sn/snBlast.h | 2256 ++++++++++++++++ src/base/sn/snBoundary.h | 781 ++++++ src/base/sn/snCheck.h | 1226 +++++++++ src/base/sn/snCom.c | 2056 +++++++++++++++ src/base/sn/snMapAdd.h | 167 ++ src/base/sn/snMapDsp.h | 85 + src/base/sn/snMapLut.h | 270 ++ src/base/sn/snMapMem.h | 101 + src/base/sn/snMapTech.h | 1264 +++++++++ src/base/sn/snMiniAig.h | 142 + src/base/sn/snMiniGate.h | 178 ++ src/base/sn/snMiniLut.h | 406 +++ src/base/sn/snMux.h | 889 +++++++ src/base/sn/snPth.h | 136 + src/base/sn/snTech.h | 137 + 23 files changed, 15676 insertions(+), 6 deletions(-) create mode 100644 src/base/sn/module.make create mode 100644 src/base/sn/readme.md create mode 100644 src/base/sn/sn.h create mode 100644 src/base/sn/snBlast.h create mode 100644 src/base/sn/snBoundary.h create mode 100644 src/base/sn/snCheck.h create mode 100644 src/base/sn/snCom.c create mode 100644 src/base/sn/snMapAdd.h create mode 100644 src/base/sn/snMapDsp.h create mode 100644 src/base/sn/snMapLut.h create mode 100644 src/base/sn/snMapMem.h create mode 100644 src/base/sn/snMapTech.h create mode 100644 src/base/sn/snMiniAig.h create mode 100644 src/base/sn/snMiniGate.h create mode 100644 src/base/sn/snMiniLut.h create mode 100644 src/base/sn/snMux.h create mode 100644 src/base/sn/snPth.h create mode 100644 src/base/sn/snTech.h diff --git a/Makefile b/Makefile index 7d9e109ac..b1c5a9f47 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ OS := $(shell uname -s) MODULES := \ $(wildcard src/ext*) \ src/base/abc src/base/abci src/base/cmd src/base/io src/base/main src/base/exor \ - src/base/ver src/base/wlc src/base/wln src/base/acb src/base/pla src/base/test \ + src/base/ver src/base/wlc src/base/wln src/base/sn src/base/acb src/base/pla src/base/test \ src/map/mapper src/map/mio src/map/super src/map/if src/map/if/acd \ src/map/amap src/map/cov src/map/scl src/map/mpm src/map/emap \ src/misc/extra src/misc/mvc src/misc/st src/misc/util src/misc/nm \ diff --git a/abclib.dsp b/abclib.dsp index f52060616..f6ee2b9da 100644 --- a/abclib.dsp +++ b/abclib.dsp @@ -971,10 +971,6 @@ SOURCE=.\src\base\acb\acbAig.c # End Source File # Begin Source File -SOURCE=.\src\base\acb\acbCom.c -# End Source File -# Begin Source File - SOURCE=.\src\base\acb\acbFunc.c # End Source File # Begin Source File @@ -1066,6 +1062,74 @@ SOURCE=.\src\base\wln\wlnWlc.c SOURCE=.\src\base\wln\wlnWriteVer.c # End Source File # End Group +# Begin Group "sn" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\src\base\sn\sn.h +# End Source File +# Begin Source File + +SOURCE=.\src\base\sn\snTech.h +# End Source File +# Begin Source File + +SOURCE=.\src\base\sn\snMapDsp.h +# End Source File +# Begin Source File + +SOURCE=.\src\base\sn\snMapMem.h +# End Source File +# Begin Source File + +SOURCE=.\src\base\sn\snMapAdd.h +# End Source File +# Begin Source File + +SOURCE=.\src\base\sn\snMapTech.h +# End Source File +# Begin Source File + +SOURCE=.\src\base\sn\snCheck.h +# End Source File +# Begin Source File + +SOURCE=.\src\base\sn\snBoundary.h +# End Source File +# Begin Source File + +SOURCE=.\src\base\sn\snMapLut.h +# End Source File +# Begin Source File + +SOURCE=.\src\base\sn\snPth.h +# End Source File +# Begin Source File + +SOURCE=.\src\base\sn\snBlast.h +# End Source File +# Begin Source File + +SOURCE=.\src\base\sn\snMiniAig.h +# End Source File +# Begin Source File + +SOURCE=.\src\base\sn\snMiniLut.h +# End Source File +# Begin Source File + +SOURCE=.\src\base\sn\snMiniGate.h +# End Source File +# Begin Source File + +SOURCE=.\src\base\sn\snMux.h +# End Source File +# Begin Source File + +SOURCE=.\src\base\sn\snCom.c +# End Source File +# End Group # End Group # Begin Group "bdd" diff --git a/src/aig/miniaig/miniaig.h b/src/aig/miniaig/miniaig.h index c7ce14e96..82457336b 100644 --- a/src/aig/miniaig/miniaig.h +++ b/src/aig/miniaig/miniaig.h @@ -351,6 +351,20 @@ static int Mini_AigAndMulti( Mini_Aig_t * p, int * pLits, int nLits ) } return pLits[0]; } +static int Mini_AigXorMulti( Mini_Aig_t * p, int * pLits, int nLits ) +{ + int i; + assert( nLits > 0 ); + while ( nLits > 1 ) + { + for ( i = 0; i < nLits/2; i++ ) + pLits[i] = Mini_AigXor(p, pLits[2*i], pLits[2*i+1]); + if ( nLits & 1 ) + pLits[i++] = pLits[nLits-1]; + nLits = i; + } + return pLits[0]; +} static int Mini_AigMuxMulti( Mini_Aig_t * p, int * pCtrl, int nCtrl, int * pData, int nData ) { int i, c; @@ -847,4 +861,3 @@ ABC_NAMESPACE_HEADER_END //////////////////////////////////////////////////////////////////////// /// END OF FILE /// //////////////////////////////////////////////////////////////////////// - diff --git a/src/base/main/mainInit.c b/src/base/main/mainInit.c index 98b374e15..ad9eb6e1b 100644 --- a/src/base/main/mainInit.c +++ b/src/base/main/mainInit.c @@ -53,6 +53,8 @@ extern void Wlc_Init( Abc_Frame_t * pAbc ); extern void Wlc_End( Abc_Frame_t * pAbc ); extern void Wln_Init( Abc_Frame_t * pAbc ); extern void Wln_End( Abc_Frame_t * pAbc ); +extern void Sn_Init( Abc_Frame_t * pAbc ); +extern void Sn_End( Abc_Frame_t * pAbc ); extern void Pla_Init( Abc_Frame_t * pAbc ); extern void Pla_End( Abc_Frame_t * pAbc ); extern void Sim_Init( Abc_Frame_t * pAbc ); @@ -119,6 +121,7 @@ void Abc_FrameInit( Abc_Frame_t * pAbc ) Emap_Init( pAbc ); Wlc_Init( pAbc ); Wln_Init( pAbc ); + Sn_Init( pAbc ); Pla_Init( pAbc ); Test_Init( pAbc ); Ufar_Init( pAbc ); @@ -160,6 +163,7 @@ void Abc_FrameEnd( Abc_Frame_t * pAbc ) Scl_End( pAbc ); Wlc_End( pAbc ); Wln_End( pAbc ); + Sn_End( pAbc ); Pla_End( pAbc ); Test_End( pAbc ); Glucose_End( pAbc ); diff --git a/src/base/main/mainInt.h b/src/base/main/mainInt.h index 3525ed015..9da2deb3b 100644 --- a/src/base/main/mainInt.h +++ b/src/base/main/mainInt.h @@ -149,6 +149,7 @@ struct Abc_Frame_t_ void * pAbcWlc; Vec_Int_t * pAbcWlcInv; void * pAbcRtl; + void * pAbcSn; void * pAbcPla; Abc_Nam_t * pJsonStrs; Vec_Wec_t * vJsonObjs; diff --git a/src/base/sn/module.make b/src/base/sn/module.make new file mode 100644 index 000000000..f1f801724 --- /dev/null +++ b/src/base/sn/module.make @@ -0,0 +1 @@ +SRC += src/base/sn/snCom.c diff --git a/src/base/sn/readme.md b/src/base/sn/readme.md new file mode 100644 index 000000000..06b598f49 --- /dev/null +++ b/src/base/sn/readme.md @@ -0,0 +1,226 @@ +# Simple Netlist in ABC + +This directory contains the Slang-independent Simple Netlist (SN) representation and algorithms. + +The external `sn_slang` executable parses and elaborates Verilog/SystemVerilog using Mike Popoloski's excellent +[slang SystemVerilog compiler](https://github.com/MikePopoloski/slang) and writes a binary `.sn` design. ABC does +not link slang or require its C++20 dependencies. + +The frontend architecture benefited from [yosys-slang](https://github.com/povik/yosys-slang), developed by +Martin PoviÅ¡er. It has been both an inspiration and a helpful practical guideline for working from slang's +elaborated model, particularly for lvalue analysis, procedural state, timing patterns, memory eligibility, resolved +nets, and diagnostics. The SN representation and lowering are independently developed, with warm thanks to Martin +for his work and advice. + +ABC holds the SN design and the `&`-space GIA as independent representations. Commands move data between them only +when explicitly requested: + +| Command | Reads | Writes | +| --- | --- | --- | +| `@slang`, `@read` | HDL or `.sn` | Current SN design | +| `@map_*`, `@opt_mux`, `@collapse` | SN | New SN design revision | +| `@blast` | SN | Current `&`-space GIA plus a saved boundary | +| `&...` commands | GIA | GIA | +| `@put` | GIA plus saved boundary | Module selected by the preceding `@blast` | +| `@write` | SN | `.sn` or Verilog | + +`@status` reports both representations, the monotonically increasing SN revision, and whether the saved boundary is +compatible with the current SN design and GIA. In particular, reading or transforming SN does not clear or update an +old `&`-space network; it makes that network unavailable for `@put` until another combinational `@blast` records a +matching boundary. + +## Commands + +The commands appear under `New word level commands` in ABC's `help` output. + +```text +set snslang /path/to/sn_slang +@slang -M top rtl1.sv rtl2.sv +@status +@check +@ps -v +@map_mem -v +@check +@map_dsp -v +@check +@map_add -v +@check +@opt_mux -v +@check +@blast -M top -c -v +&resyn3 +&if -m -K 6 +&ps +@status +@put -v +@check +@collapse -v +@check +@write mapped_logic.v +@write mapped_logic.sn +``` + +`@slang` uses `sn_slang` from `PATH` unless the `snslang` setting overrides it. It accepts `-M` for the top module, +repeatable `-D NAME` or `-D NAME=value` preprocessor definitions, `-F` for one additional source file, and any number +of positional source files. For example, `-D WIDTH=8 -D SIGNED=1` defines two macros. `-T` is not used because ABC +conventionally reserves it for a time limit. `-v` prints the external command and frontend timing. Black-box patterns +and include-directory options remain unsupported. + +`@read` and `@write` provide binary persistence. `@write` selects SN or Verilog output from the `.sn`, `.v`, or +`.sv` extension. `@read -M module` selects the top stored in a multi-top design; otherwise the last top is used. +Before installing external binary data, `@read` validates the encoding and runs the same non-aborting structural and +semantic checks as `@check`. A failed `@write` removes its incomplete output file. Every design installed in ABC is +topologically ordered. + +`@status` prints the current design and top names, SN revision, selected technology, hierarchy form, last extraction +mode/module/revision, saved boundary hash, current GIA dimensions, and `@put` compatibility. A new `@read` or `@slang` +design starts at revision 1. Each transformation that installs a replacement SN design, and each successful `@put`, +advances the revision; `&` commands do not. An optimization that finds no profitable rewrite leaves the design and its +revision unchanged. + +`@blast` gives every GIA input and output a unique ordered name containing the retained SN signal name, bit index, and +interface index. It also hashes the selected module identity and all saved boundary occurrences, primitives, registers, +loops, and input/output endpoint records. Before insertion, `@put` verifies the SN revision, module ID and name, +boundary hash, GIA dimensions, and ordered GIA-name signature. It rejects a GIA whose interface was reordered, renamed, +or stripped of names, even if its input and output counts still match. The GIA must also remain combinational, with zero +registers. Normal interface-preserving `&` synthesis commands retain the names and remain compatible. + +MiniAIG has only an edge-triggered register convention. Therefore `@blast` and `@map_lut` explicitly reject any +level-sensitive `SN_REG_LATCH` reachable from the selected module until a semantics-preserving latch flow is available. + +`@check` performs a non-aborting consistency audit of the complete SN design. It validates core and type-specific +attribute vectors, fanin storage, object IDs, widths, names, constants, topology, state pairing, memory-port ownership, +instance/FAN ordering, hierarchy recursion, LUTs, gates, and mapped primitive interfaces. `@check -v` adds one summary +line per module. Memory, DSP, and carry mapping commands run the same checker transactionally before and after each +transformation, so an invalid result is diagnosed and rejected without replacing the current design. + +`@ps` prints compact statistics for the selected top module. `@ps -v` adds the hierarchy and statistics for every +module definition. Like `%ps -d`, `@ps -d` prints occurrences by object type and output/input width signature. Its +counts cover the elaborated hierarchy rooted at the selected top, including the multiplicity of repeated insts. The +hierarchical occurrence totals are accumulated over the module DAG rather than by recursively revisiting every inst, +so statistics remain practical for deeply repeated hierarchy. Memory is reported as used/allocated storage with +rounded K, M, or G suffixes. + +`@map_mem`, `@map_dsp`, and `@map_add` map into the initial AMD/Xilinx UltraScale+ technology description. +Transformations are transactional and keep the original user-visible top-module name. `@map_add` replaces word-level +addition and subtraction of at least three bits by chains of behavioral `__sn_CARRY4` primitive insts. Propagate, +operand inversion, extension, and final slicing remain ordinary SN logic for subsequent LUT mapping. Run DSP mapping +before carry mapping so future DSP preadder and postadder recognition is not hidden. `@collapse` flattens user hierarchy +while retaining mapped hard-block leaf instances. + +`@opt_mux` restructures register mux cones by collecting root-to-terminal paths, grouping structurally identical +LSB-first word values, and ORing the corresponding path conditions. A register-output terminal is converted into an +explicit enable when the path controls are provably exclusive. The pass currently recognizes ordinary `SN_MUX` +trees and packed `SN_PMUX` alternatives; separately created casts, slices, repetitions, concatenations, and constants +are compared structurally. Rewritten modules are restored at their stable hierarchy IDs and retain every register +pair so that the canonical transition interface remains unchanged. The default profitability filter requires at +least 4-bit data, six paths, two eliminated paths, and a path-to-distinct-terminal ratio of at least 2:1. This avoids +increasing logic for narrow control muxes while retaining the intended wide datapath transformations. + +`@blast` traverses hierarchy directly without first allocating a flat SN module. Sequential extraction is the +default; `-c` selects combinational extraction. `-t` emits the same effective next-state functions as a purely +combinational transition AIG for equivalence checking. +`-M module` selects the module to +extract; the default is the current SN top. ABC records the selected module and the exact LSB-first boundary mapping, +then installs the resulting GIA as the current `&` network. The user may apply any `&`-space combinational synthesis +and mapping commands that preserve the number and order of combinational inputs and outputs. Nothing requires the +logic to be put back into SN: omitting `@put` leaves the SN design unchanged. + +Adders use a Brent-Kung parallel-prefix network by default. `@blast -r` selects ripple-carry adders instead. This +choice also applies to adder networks used while blasting subtraction and other arithmetic operators; `-b` separately +selects Booth rather than the direct-unsigned/Baugh-Wooley multiplier. Signed and unsigned relational operators use +a balanced, delay-oriented comparator by default; `@blast -d` toggles to the minimum-node topology implemented by ABC's +`&gencomp`. Equality comparison remains balanced in both modes. Ripple adders and multiplier compressor trees share +the seven-node full-adder construction from `Wlc_BlastFullAdder()`. Direct unsigned, signed Baugh-Wooley, and radix-4 +Booth partial products use the delay-aware, level-ordered matrix reduction adapted from `Wlc_BlastReduceMatrix()`, +followed by the selected Brent-Kung or ripple final adder. The radix-4 Booth recoding, signed correction, rectangular +operand handling, and unsigned zero extension follow `Wlc_BlastBooth()`. Binary mux trees use `Mini_AigMuxMulti()`, +while AND/OR reductions and equality aggregation use balanced `Mini_AigAndMulti()` trees over copied temporary +literals. One-hot priority muxes use a balanced sum-of-products tree; their result for a multi-hot select remains +intentionally undefined. Variable shifts instantiate only the useful barrel stages and combine all higher shift bits +into one balanced overshift condition. + +Unnamed constants are interned by width, signedness, and packed value within each module. Concatenations whose inputs +are all constant are folded into one packed `SN_CONST`, including tables wider than the per-object fanin-count limit. +When such a constant drives an `SN_BMUX`, blasting reads one output-bit column at a time and simplifies constant and +equal mux branches before creating MiniAIG nodes; it never materializes the complete packed table as an integer-literal +array. The Verilog writer splits very large constants into bounded-size hexadecimal concatenation chunks. + +In combinational mode (`@blast -c`), flop outputs become additional inputs, while raw data and synchronous control +inputs become separate outputs for later stitching; clock and asynchronous controls remain outside this boundary. +Mapped RAM/DSP and CARRY4 outputs and inputs are likewise exposed as additional cloud endpoints. `@put` checks the saved +interface and reconnects registers and mapped primitive instances. With the default sequential `@blast`, the AIG +transition functions elaborate synchronous reset, set, and enable controls in SN priority order; clock and asynchronous +controls remain outside the transition relation. Sequential-AIG insertion is deliberately rejected for now. + +`@map_lut` applies this combinational extraction and reconstruction module by module while preserving the natural SN +hierarchy. Child instances, registers, and mapped RAM/DSP/CARRY4 instances are partition boundaries, matching the broad +structure of Yosys's per-module ABC flow. `@map_lut -S "&resyn3; &if -m -K 6"` supplies an inline per-partition ABC +script; `-F script.abc` sources it from a file. The default is the same `&resyn3; &if -m -K 6` sequence. Every script +must preserve CI/CO order and leave a LUT-mapped GIA. Generic-memory modules left unsupported by `@map_mem` remain +unchanged and are reported as skipped partitions. Mapped nodes wider than the physical SN LUT6 primitive are +decomposed deterministically by Shannon expansion. The pass maps a duplicate design and commits it only after every +reachable non-primitive module succeeds. `-P num` runs the independent partition jobs concurrently using `num - 1` +pthread workers and one coordinating process. `-P 1` uses the current ABC process directly, so its last partition +becomes the current `&`-space GIA; use `-P 2` or more when the preexisting `&`-space network must remain untouched. +SN pthread support is compiled out on Windows, where `-P 1` remains fully supported and larger values are rejected. +`@map_lut -E prefix` stops at the same partition boundary, writes each nontrivial job as +`prefix__.aig` with a `.txt` interface-statistics sidecar, and does not run synthesis or modify +the SN design. This mode cannot be combined with `-S` or `-F`, currently requires `-P 1`, and is intended for +developing or benchmarking an external per-partition synthesis flow. +Generated clock and asynchronous-control cones remain outside the mapped cloud and are copied with per-occurrence +memoization when registers are reconnected. + +The transition AIG orders state bits canonically by depth-first natural instance type ID, natural `SN_REG_OUT` type +ID within each occurrence, and LSB-first bit index. Both hierarchy duplication and mux sharing preserve these IDs. +Consequently, the transition AIGs made before and after `@opt_mux` have identical CI/CO order and can be compared +directly with `&cec before.aig after.aig`. For large, structurally different cones, explicitly constructing the miter +is often much faster: `&r before.aig; &miter after.aig; &cec -m`. Transition-AIG insertion through `@put` is +deliberately rejected. + +`@put` replaces only the module selected by the preceding `@blast`. Its module ID, name, and port interface remain +stable, so parent instances and every other module in an uncollapsed hierarchical design are preserved. The current +GIA determines the reconstructed representation: + +- An unmapped GIA becomes explicit one-bit `SN_BIT_AND` and `SN_BIT_NOT` objects. +- A LUT-mapped GIA becomes `SN_LUT` objects with truth tables transferred through MiniLUT. +- A cell-mapped GIA becomes `SN_GATE` objects annotated with current genlib gate IDs and cell names through ABC's + mini-mapping format. Insertion requires the current genlib to contain every referenced gate. + +For example, `@blast -c; &resyn3; &if -m -K 6; @put` implements the former canned LUT-mapping flow without hiding +the ABC script. `@blast -c; &dc2; @put` reinserts an optimized unmapped AIG, while +`read_genlib library.genlib; @blast -c; &nf; @put` reinserts standard cells. The Verilog writer emits LUT and gate +instances as well as ordinary SN logic. + +Mapped RAM/DSP/CARRY4 instances are reconstructed as technology leaf instances. SN loop-breaker pairs connect their +output ports while the new flat module is built and are placed into a legal order by the final topological reorder. +Temporary loop pairs are pruned after reconnection unless an actual feedback dependency remains, so acyclic datapaths +do not gain artificial loop-breakers. Generic unmapped memory endpoints are recorded and abstracted by `@blast`, but +`@put` currently rejects them because the boundary does not yet retain enough per-memory-port ownership data. This +check prevents silent loss or misconnection of stateful memories. + +## Source files + +The package uses ABC-style filenames: + +```text +sn.h core representation, hierarchy, serialization, and Verilog writer +snCheck.h non-aborting design, module, hierarchy, and technology-interface consistency checker +snTech.h target technology descriptions +snMapMem.h memory mapping support +snMapDsp.h DSP mapping support +snMapAdd.h CARRY4 mapping support +snMapTech.h combined hierarchy mapping +snMapLut.h natural-hierarchy LUT-mapping harness +snPth.h bounded pthread worker harness +snBlast.h direct hierarchical MiniAIG construction +snMux.h register mux-path sharing and restructuring +snBoundary.h saved boundary and combinational register reconnection +snMiniAig.h unmapped MiniAIG reconstruction +snMiniLut.h MiniLUT analysis and SN_LUT reconstruction +snMiniGate.h mini-mapping and SN_GATE reconstruction +snCom.c ABC manager ownership and command handlers +``` + +The external frontend must compile against this directory through a configured include path. Representation changes +are made here first and must update the binary-format version when serialization compatibility changes. diff --git a/src/base/sn/sn.h b/src/base/sn/sn.h new file mode 100644 index 000000000..9508ae60c --- /dev/null +++ b/src/base/sn/sn.h @@ -0,0 +1,5267 @@ +/**CFile**************************************************************** + + FileName [sn.h] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [New word-level design interface.] + + Synopsis [Simple hierarchical word-level netlist data structures and core APIs.] + + Author [Alan Mishchenko] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: sn.h,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#ifndef SN_H +#define SN_H + +#include +#include +#include +#include +#include +#include +#include + +#include "misc/util/abc_namespaces.h" + +ABC_NAMESPACE_HEADER_START + +// Simple Netlist (sn) +// ------------------- +// +// A design owns a dense array of modules. A module owns a dense array of +// objects. There are no separate pin or net records: an object is identified +// by its module-local integer ID, and its fanins are object IDs in the same +// module. Fanouts are not stored by default; they can be derived and cached on +// demand. +// +// Core object properties use structure-of-arrays storage. Therefore an object +// ID is also the index of that object's type, width / signedness, fanin count, +// fanin offset, type ID, and name ID. A type ID indexes the dense array of +// objects of that type and any type-specific metadata. +// +// Ordered bit collections use LSB-first significance order throughout SN. +// Index zero denotes the least-significant bit, word, or concatenation operand; +// increasing indices denote increasingly significant data. This is an IR +// convention, independent of how SystemVerilog writes ranges and concatenations. +// Importers and writers must translate between the two conventions. +// +// All normal operators have one output, represented by the operator object +// itself. A single-output module inst is represented the same way. A +// multi-output module inst is immediately followed by one SN_FAN object +// per output in the referenced module's natural SN_PO order. Every SN_FAN has +// the inst as its sole fanin and stores only that inst ID as type- +// specific data. The invariant fan_id == inst_id + 1 + output_index makes +// the output index derived rather than stored. +// +// UINT32_MAX denotes an illegal, unused, or not-yet-connected fanin. This lets +// builders allocate objects before all dependencies have been translated. +// +// Registers, memories, and loop breakers are state / ordering boundaries made +// from adjacent OUT and IN objects. REG_OUT has fixed fanin slots for clock, +// data (= its paired REG_IN), enable, set, reset, packed initial data, an +// equally wide per-bit initialization-validity mask, and an optional nonzero/ +// non-one reset value. REG_IN has one initially unresolved fanin for +// next-state data. MEM_OUT has fixed fanins for its paired MEM_IN, packed +// initialization data, and an equally wide per-bit validity mask. MEM_IN has +// zero or more MEM_WRITE fanins. MEM_READ objects consume MEM_OUT and +// represent individual read ports. +// Topological algorithms must not traverse OUT-to-IN structural edges as +// combinational dependencies. +// +// Each REG_OUT/REG_IN, MEM_OUT/MEM_IN, and LOOP_OUT/LOOP_IN pair shares one +// type ID. For pair index k, type_objects[OUT_TYPE][k] and +// type_objects[IN_TYPE][k] are the corresponding objects. This provides +// bidirectional lookup without storing another object ID. Constructors create +// adjacent OUT/IN objects, but topologically reordered modules need not retain +// that adjacency; the shared type ID is the authoritative pairing invariant. + +#define SN_INVALID_ID UINT32_MAX + +typedef uint32_t sn_obj_id_t; +typedef uint32_t sn_module_id_t; +typedef uint32_t sn_name_id_t; +typedef uint32_t sn_type_id_t; +typedef uint16_t sn_fanin_count_t; + +// A C-style generic vector. Cap and size are measured in elements. +// Access macros take the element type explicitly, for example: +// +// sn_vec_t values; +// sn_vec_init(&values); +// *sn_vec_push(int, &values) = 42; +// int value = sn_vec_at(int, &values, 0); +// +// The same API supports pointer element types: +// +// *sn_vec_push(void *, &values) = pointer; +typedef struct sn_vec_t +{ + void* data; + size_t cap; + size_t size; +} sn_vec_t; + +static inline void sn_vec_init(sn_vec_t* vec) +{ + assert(vec); + vec->data = NULL; + vec->cap = 0; + vec->size = 0; +} + +static inline void sn_vec_destroy(sn_vec_t* vec) +{ + assert(vec); + free(vec->data); + sn_vec_init(vec); +} + +static inline void sn_vec_reserve_raw(sn_vec_t* vec, size_t size, size_t cap) +{ + assert(vec); + assert(size); + if (cap <= vec->cap) + return; + + size_t new_cap = vec->cap ? vec->cap : 8; + while (new_cap < cap) + { + assert(new_cap <= SIZE_MAX / 2); + new_cap *= 2; + } + assert(new_cap <= SIZE_MAX / size); + + void* data = realloc(vec->data, new_cap * size); + assert(data); + vec->data = data; + vec->cap = new_cap; +} + +static inline void sn_vec_resize_raw(sn_vec_t* vec, size_t size, size_t count) +{ + assert(vec); + size_t old_size = vec->size; + sn_vec_reserve_raw(vec, size, count); + if (count > old_size) + memset((char*)vec->data + old_size * size, 0, (count - old_size) * size); + vec->size = count; +} + +static inline void* sn_vec_push_raw(sn_vec_t* vec, size_t size) +{ + assert(vec); + assert(vec->size < SIZE_MAX); + sn_vec_reserve_raw(vec, size, vec->size + 1); + void* slot = (char*)vec->data + vec->size * size; + memset(slot, 0, size); + vec->size++; + return slot; +} + +static inline void sn_vec_dup_raw(sn_vec_t* target, const sn_vec_t* source, size_t element_size) +{ + assert(target && source && element_size && target->data == NULL && target->size == 0 && target->cap == 0); + sn_vec_resize_raw(target, element_size, source->size); + if (source->size) + memcpy(target->data, source->data, source->size * element_size); +} + +#define sn_vec_data(type, vec) ((type*)((vec)->data)) +#define sn_vec_at(type, vec, index) (sn_vec_data(type, vec)[(index)]) +#define sn_vec_reserve(type, vec, cap) sn_vec_reserve_raw((vec), sizeof(type), (cap)) +#define sn_vec_resize(type, vec, count) sn_vec_resize_raw((vec), sizeof(type), (count)) +#define sn_vec_push(type, vec) ((type*)sn_vec_push_raw((vec), sizeof(type))) +#define sn_vec_dup(type, target, source) sn_vec_dup_raw((target), (source), sizeof(type)) + +typedef uint8_t sn_obj_type_t; + +enum sn_obj_type_enum +{ + SN_NONE = 0, + + // Design interface and structural objects. + SN_PI, + SN_PO, + SN_CONST0, + SN_CONST1, + SN_CONST, + SN_BUF, + SN_FAN, + SN_INST, + + // State and ordering boundaries. + SN_REG_OUT, + SN_REG_IN, + SN_MEM_OUT, + SN_MEM_IN, + SN_MEM_READ, + SN_MEM_WRITE, + SN_LOOP_OUT, + SN_LOOP_IN, + + // Unary operators. + SN_POS, + SN_NEG, + SN_BIT_NOT, + SN_LOG_NOT, + SN_REDUCE_AND, + SN_REDUCE_NAND, + SN_REDUCE_OR, + SN_REDUCE_NOR, + SN_REDUCE_XOR, + SN_REDUCE_XNOR, + + // Arithmetic operators. + SN_ADD, + SN_SUB, + // Multiplication fanins retain their independent native widths and signedness. The SN_MUL width is the required + // result width; blasting and mapping resize the product at its output rather than widening both operands first. + SN_MUL, + SN_DIV, + SN_MOD, + SN_POW, + + // Bitwise and logical operators. + SN_BIT_AND, + SN_BIT_OR, + SN_BIT_XOR, + SN_BIT_XNOR, + SN_LOG_AND, + SN_LOG_OR, + + // Comparison operators. + SN_EQ, + SN_NE, + SN_CASE_EQ, + SN_CASE_NE, + SN_WILDCARD_EQ, + SN_WILDCARD_NE, + SN_LT, + SN_LE, + SN_GT, + SN_GE, + + // Shift and word-construction operators. + SN_SHL, + SN_SHR, + SN_ASHL, + SN_ASHR, + + // Mux fanins always put the control first and any default value last. + // + // SN_MUX: [select, selected_when_one, default_when_zero] + // SN_BMUX: [binary_select, packed_alternatives] + // SN_PMUX: [one_hot_select, packed_alternatives, default_when_zero] + // + // Packed alternatives use LSB-first significance order. Alternative i + // occupies bits [i * output_width +: output_width]. SN_PMUX produces its + // default when the select is zero and is undefined for a multi-hot select. + SN_MUX, + SN_BMUX, + SN_PMUX, + + // Concatenation uses LSB-first significance order: fanin zero supplies the + // least-significant result bits, and later fanins supply successively more- + // significant bits. Repetition has one fanin and a type-indexed repeat count. + // Slice has one fanin and type-indexed left, right, and direction data. + SN_CONCAT, + SN_REPLICATE, + SN_SLICE, + SN_CAST, + + // One-bit FPGA lookup table. Fanin zero is truth-table variable I0. + // The low 2^fanin_count bits of the type-indexed uint64_t are significant. + SN_LUT, + + // One-bit technology-mapped gate. Its uint32_t gate ID identifies the gate + // in the technology library; fanins follow the library cell's input order. + SN_GATE, + + SN_OBJ_TYPE_COUNT +}; + +#ifdef __cplusplus +static_assert(SN_OBJ_TYPE_COUNT <= UINT8_MAX, "sn_obj_type_t cannot represent every object type"); +#else +_Static_assert(SN_OBJ_TYPE_COUNT <= UINT8_MAX, "sn_obj_type_t cannot represent every object type"); +#endif + +typedef enum sn_mux_fanin_t +{ + SN_MUX_SELECT = 0, + SN_MUX_SELECTED, + SN_MUX_DEFAULT, + SN_MUX_FANIN_COUNT +} sn_mux_fanin_t; + +typedef enum sn_bmux_fanin_t +{ + SN_BMUX_SELECT = 0, + SN_BMUX_ALTERNATIVES, + SN_BMUX_FANIN_COUNT +} sn_bmux_fanin_t; + +typedef enum sn_pmux_fanin_t +{ + SN_PMUX_SELECT = 0, + SN_PMUX_ALTERNATIVES, + SN_PMUX_DEFAULT, + SN_PMUX_FANIN_COUNT +} sn_pmux_fanin_t; + +enum +{ + SN_REG_CLOCK_NEGEDGE = 1u << 0, + SN_REG_RESET_NEGEDGE = 1u << 1, + SN_REG_RESET_ASYNC = 1u << 2, + SN_REG_SET_NEGEDGE = 1u << 3, + SN_REG_SET_ASYNC = 1u << 4, + // A level-sensitive latch uses SN_REG_ENABLE as its gate and has no clock fanin. + SN_REG_LATCH = 1u << 5, + SN_REG_FLAGS_ALL = + SN_REG_CLOCK_NEGEDGE | SN_REG_RESET_NEGEDGE | SN_REG_RESET_ASYNC | SN_REG_SET_NEGEDGE | SN_REG_SET_ASYNC | + SN_REG_LATCH +}; + +typedef enum sn_reg_fanin_t +{ + SN_REG_CLOCK = 0, + SN_REG_DATA, + SN_REG_ENABLE, + SN_REG_SET, + SN_REG_RESET, + SN_REG_INIT_DATA, + SN_REG_INIT = SN_REG_INIT_DATA, + SN_REG_INIT_MASK, + // UINT32_MAX means the reset value is the implicit all-zero constant. + SN_REG_RESET_VALUE, + SN_REG_FANIN_COUNT +} sn_reg_fanin_t; + +typedef struct sn_slice_info_t +{ + int32_t left_index; + int32_t right_index; + uint32_t flags; +} sn_slice_info_t; + +enum +{ + SN_SLICE_DESCENDING = 1u << 0 +}; + +// UINT32_MAX in the clock slot denotes an asynchronous read. UINT32_MAX in +// an enable slot denotes an always-enabled port. +typedef enum sn_mem_read_fanin_t +{ + SN_MEM_READ_MEMORY = 0, + SN_MEM_READ_CLOCK, + SN_MEM_READ_ENABLE, + SN_MEM_READ_ADDRESS, + SN_MEM_READ_FANIN_COUNT +} sn_mem_read_fanin_t; + +typedef enum sn_mem_write_fanin_t +{ + SN_MEM_WRITE_CLOCK = 0, + SN_MEM_WRITE_ENABLE, + SN_MEM_WRITE_DATA, + SN_MEM_WRITE_ADDRESS, + SN_MEM_WRITE_FANIN_COUNT +} sn_mem_write_fanin_t; + +// Memory initialization uses the same LSB-first packed layout for data and +// validity: entry zero occupies the least-significant word-width bits. A mask +// bit of one means that the corresponding data bit has a specified initial +// value. UINT32_MAX in either init slot means that the slot is absent; an +// absent mask with present data means that every data bit is valid. +typedef enum sn_mem_out_fanin_t +{ + SN_MEM_STATE = 0, + SN_MEM_INIT_DATA, + SN_MEM_INIT_MASK, + SN_MEM_OUT_FANIN_COUNT +} sn_mem_out_fanin_t; + +typedef struct sn_obj_pair_t +{ + sn_obj_id_t out; + sn_obj_id_t in; +} sn_obj_pair_t; + +typedef struct sn_name_mgr_t +{ + // char* entries owned by this manager and indexed by sn_name_id_t. + sn_vec_t names; + + // Chained hash table. Buckets and links contain name IDs. + sn_vec_t buckets; + sn_vec_t links; +} sn_name_mgr_t; + +typedef struct sn_design_t sn_design_t; + +typedef struct sn_const_hash_entry_t +{ + uint64_t hash; + sn_obj_id_t object; + uint32_t next; +} sn_const_hash_entry_t; + +typedef struct sn_module_t +{ + sn_design_t* design; + sn_module_id_t id; + sn_name_id_t name; + + // Core object attributes, all indexed by sn_obj_id_t. + sn_vec_t obj_types; + sn_vec_t width_signed; + sn_vec_t fanin_counts; + sn_vec_t fanin_offsets; + sn_vec_t type_ids; + sn_vec_t name_ids; + + // Concatenated fanin spans for all objects. + sn_vec_t fanins; + + // For each object type, type_objects[type][type_id] is an object ID. + sn_vec_t type_objects[SN_OBJ_TYPE_COUNT]; + + // Type-specific metadata indexed by the corresponding OUT / object type ID. + // uint32_t flags indexed by SN_REG_OUT type ID. + sn_vec_t reg_flags; + // uint32_t entry count indexed by SN_MEM_OUT type ID. + sn_vec_t mem_depths; + // Referenced sn_module_id_t indexed by SN_INST type ID. + sn_vec_t inst_modules; + // Owning sn_obj_id_t indexed by SN_FAN type ID. + sn_vec_t fan_insts; + sn_vec_t slice_infos; + // uint32_t repetition count indexed by SN_REPLICATE type ID. + sn_vec_t repeat_counts; + // uint32_t offset into design->constant_words indexed by SN_CONST type ID. + sn_vec_t const_word_offsets; + // Derived chained hash table for unnamed constant interning. It is rebuilt lazily after binary loading. + sn_vec_t const_hash_buckets; + sn_vec_t const_hash_entries; + // uint64_t truth table indexed by SN_LUT type ID. + sn_vec_t lut_truths; + // uint32_t technology-library gate ID indexed by SN_GATE type ID. + sn_vec_t gate_ids; + + // Optional derived fanout cache, indexed like the fanin representation. + sn_vec_t fanout_counts; + sn_vec_t fanout_offsets; + sn_vec_t fanouts; + bool fanouts_valid; + + // PI and PO lists become immutable once this module is instantiated. + bool interface_locked; + + // Most recent duplication map: old object ID -> object ID in copy_module. + // It belongs to this source module and is released with the module. + sn_vec_t copy_ids; + sn_module_id_t copy_module; +} sn_module_t; + +struct sn_design_t +{ + // sn_module_t* entries indexed by sn_module_id_t. + sn_vec_t modules; + sn_name_mgr_t names; + + // SN_CONST values other than zero and one, packed as 32-bit words in + // LSB-first significance order. The module's const_word_offsets array + // stores each constant's starting word offset. + sn_vec_t constant_words; +}; + +// Memory accounting distinguishes populated payload bytes from reserved heap +// cap. Allocated bytes are the bytes requested from malloc/realloc; they +// do not include allocator headers or size-class rounding. +typedef struct sn_mem_size_t +{ + size_t used_bytes; + size_t allocated_bytes; +} sn_mem_size_t; + +// A module owns its struct and the payload allocations listed below. Vector +// headers are embedded in module_struct and are therefore not counted again. +typedef struct sn_module_mem_usage_t +{ + sn_mem_size_t module_struct; + sn_mem_size_t obj_types; + sn_mem_size_t width_signed; + sn_mem_size_t fanin_counts; + sn_mem_size_t fanin_offsets; + sn_mem_size_t type_ids; + sn_mem_size_t name_ids; + sn_mem_size_t fanins; + sn_mem_size_t type_objects[SN_OBJ_TYPE_COUNT]; + sn_mem_size_t reg_flags; + sn_mem_size_t mem_depths; + sn_mem_size_t inst_modules; + sn_mem_size_t fan_insts; + sn_mem_size_t slice_infos; + sn_mem_size_t repeat_counts; + sn_mem_size_t const_word_offsets; + sn_mem_size_t const_hash_buckets; + sn_mem_size_t const_hash_entries; + sn_mem_size_t lut_truths; + sn_mem_size_t gate_ids; + sn_mem_size_t fanout_counts; + sn_mem_size_t fanout_offsets; + sn_mem_size_t fanouts; + sn_mem_size_t copy_ids; + sn_mem_size_t total; +} sn_module_mem_usage_t; + +// modules is the sum of all module structs and their payloads. names is the +// sum of the name pointer/index/hash arrays and the separately allocated +// zero-terminated strings. constant_words is reported separately as requested. +typedef struct sn_design_mem_usage_t +{ + sn_mem_size_t design_struct; + sn_mem_size_t module_table; + sn_mem_size_t modules; + sn_module_mem_usage_t module_attributes; + sn_mem_size_t name_pointers; + sn_mem_size_t name_buckets; + sn_mem_size_t name_links; + sn_mem_size_t name_strings; + sn_mem_size_t names; + sn_mem_size_t constant_words; + sn_mem_size_t total; +} sn_design_mem_usage_t; + +static inline sn_mem_size_t sn_mem_size_make(size_t used_bytes, size_t allocated_bytes) +{ + sn_mem_size_t usage = {used_bytes, allocated_bytes}; + return usage; +} + +static inline sn_mem_size_t sn_vec_mem_usage(const sn_vec_t* vec, size_t size) +{ + assert(vec); + assert(size); + assert(vec->size <= vec->cap); + assert(vec->cap <= SIZE_MAX / size); + sn_mem_size_t usage = {vec->size * size, vec->cap * size}; + return usage; +} + +static inline void sn_mem_size_add(sn_mem_size_t* total, sn_mem_size_t usage) +{ + assert(total); + assert(total->used_bytes <= SIZE_MAX - usage.used_bytes); + assert(total->allocated_bytes <= SIZE_MAX - usage.allocated_bytes); + total->used_bytes += usage.used_bytes; + total->allocated_bytes += usage.allocated_bytes; +} + +static inline void sn_module_get_mem_usage(const sn_module_t* module, sn_module_mem_usage_t* usage) +{ + assert(module); + assert(usage); + memset(usage, 0, sizeof(*usage)); + usage->module_struct = sn_mem_size_make(sizeof(*module), sizeof(*module)); + usage->obj_types = sn_vec_mem_usage(&module->obj_types, sizeof(sn_obj_type_t)); + usage->width_signed = sn_vec_mem_usage(&module->width_signed, sizeof(uint32_t)); + usage->fanin_counts = sn_vec_mem_usage(&module->fanin_counts, sizeof(sn_fanin_count_t)); + usage->fanin_offsets = sn_vec_mem_usage(&module->fanin_offsets, sizeof(uint32_t)); + usage->type_ids = sn_vec_mem_usage(&module->type_ids, sizeof(uint32_t)); + usage->name_ids = sn_vec_mem_usage(&module->name_ids, sizeof(uint32_t)); + usage->fanins = sn_vec_mem_usage(&module->fanins, sizeof(sn_obj_id_t)); + for (size_t i = 0; i < SN_OBJ_TYPE_COUNT; i++) + usage->type_objects[i] = sn_vec_mem_usage(&module->type_objects[i], sizeof(sn_obj_id_t)); + usage->reg_flags = sn_vec_mem_usage(&module->reg_flags, sizeof(uint32_t)); + usage->mem_depths = sn_vec_mem_usage(&module->mem_depths, sizeof(uint32_t)); + usage->inst_modules = sn_vec_mem_usage(&module->inst_modules, sizeof(sn_module_id_t)); + usage->fan_insts = sn_vec_mem_usage(&module->fan_insts, sizeof(sn_obj_id_t)); + usage->slice_infos = sn_vec_mem_usage(&module->slice_infos, sizeof(sn_slice_info_t)); + usage->repeat_counts = sn_vec_mem_usage(&module->repeat_counts, sizeof(uint32_t)); + usage->const_word_offsets = sn_vec_mem_usage(&module->const_word_offsets, sizeof(uint32_t)); + usage->const_hash_buckets = sn_vec_mem_usage(&module->const_hash_buckets, sizeof(uint32_t)); + usage->const_hash_entries = sn_vec_mem_usage(&module->const_hash_entries, sizeof(sn_const_hash_entry_t)); + usage->lut_truths = sn_vec_mem_usage(&module->lut_truths, sizeof(uint64_t)); + usage->gate_ids = sn_vec_mem_usage(&module->gate_ids, sizeof(uint32_t)); + usage->fanout_counts = sn_vec_mem_usage(&module->fanout_counts, sizeof(uint32_t)); + usage->fanout_offsets = sn_vec_mem_usage(&module->fanout_offsets, sizeof(uint32_t)); + usage->fanouts = sn_vec_mem_usage(&module->fanouts, sizeof(sn_obj_id_t)); + usage->copy_ids = sn_vec_mem_usage(&module->copy_ids, sizeof(sn_obj_id_t)); + + usage->total = usage->module_struct; +#define SN_MEM_ADD_FIELD(field) sn_mem_size_add(&usage->total, usage->field) + SN_MEM_ADD_FIELD(obj_types); + SN_MEM_ADD_FIELD(width_signed); + SN_MEM_ADD_FIELD(fanin_counts); + SN_MEM_ADD_FIELD(fanin_offsets); + SN_MEM_ADD_FIELD(type_ids); + SN_MEM_ADD_FIELD(name_ids); + SN_MEM_ADD_FIELD(fanins); + for (size_t i = 0; i < SN_OBJ_TYPE_COUNT; i++) + sn_mem_size_add(&usage->total, usage->type_objects[i]); + SN_MEM_ADD_FIELD(reg_flags); + SN_MEM_ADD_FIELD(mem_depths); + SN_MEM_ADD_FIELD(inst_modules); + SN_MEM_ADD_FIELD(fan_insts); + SN_MEM_ADD_FIELD(slice_infos); + SN_MEM_ADD_FIELD(repeat_counts); + SN_MEM_ADD_FIELD(const_word_offsets); + SN_MEM_ADD_FIELD(const_hash_buckets); + SN_MEM_ADD_FIELD(const_hash_entries); + SN_MEM_ADD_FIELD(lut_truths); + SN_MEM_ADD_FIELD(gate_ids); + SN_MEM_ADD_FIELD(fanout_counts); + SN_MEM_ADD_FIELD(fanout_offsets); + SN_MEM_ADD_FIELD(fanouts); + SN_MEM_ADD_FIELD(copy_ids); +#undef SN_MEM_ADD_FIELD +} + +static inline void sn_module_mem_usage_add(sn_module_mem_usage_t* total, const sn_module_mem_usage_t* usage) +{ + assert(total); + assert(usage); +#define SN_MEM_ADD_MODULE_FIELD(field) sn_mem_size_add(&total->field, usage->field) + SN_MEM_ADD_MODULE_FIELD(module_struct); + SN_MEM_ADD_MODULE_FIELD(obj_types); + SN_MEM_ADD_MODULE_FIELD(width_signed); + SN_MEM_ADD_MODULE_FIELD(fanin_counts); + SN_MEM_ADD_MODULE_FIELD(fanin_offsets); + SN_MEM_ADD_MODULE_FIELD(type_ids); + SN_MEM_ADD_MODULE_FIELD(name_ids); + SN_MEM_ADD_MODULE_FIELD(fanins); + for (size_t i = 0; i < SN_OBJ_TYPE_COUNT; i++) + sn_mem_size_add(&total->type_objects[i], usage->type_objects[i]); + SN_MEM_ADD_MODULE_FIELD(reg_flags); + SN_MEM_ADD_MODULE_FIELD(mem_depths); + SN_MEM_ADD_MODULE_FIELD(inst_modules); + SN_MEM_ADD_MODULE_FIELD(fan_insts); + SN_MEM_ADD_MODULE_FIELD(slice_infos); + SN_MEM_ADD_MODULE_FIELD(repeat_counts); + SN_MEM_ADD_MODULE_FIELD(const_word_offsets); + SN_MEM_ADD_MODULE_FIELD(const_hash_buckets); + SN_MEM_ADD_MODULE_FIELD(const_hash_entries); + SN_MEM_ADD_MODULE_FIELD(lut_truths); + SN_MEM_ADD_MODULE_FIELD(gate_ids); + SN_MEM_ADD_MODULE_FIELD(fanout_counts); + SN_MEM_ADD_MODULE_FIELD(fanout_offsets); + SN_MEM_ADD_MODULE_FIELD(fanouts); + SN_MEM_ADD_MODULE_FIELD(copy_ids); + SN_MEM_ADD_MODULE_FIELD(total); +#undef SN_MEM_ADD_MODULE_FIELD +} + +static inline void sn_design_get_mem_usage(const sn_design_t* design, sn_design_mem_usage_t* usage) +{ + assert(design); + assert(usage); + memset(usage, 0, sizeof(*usage)); + usage->design_struct = sn_mem_size_make(sizeof(*design), sizeof(*design)); + usage->module_table = sn_vec_mem_usage(&design->modules, sizeof(sn_module_t*)); + usage->name_pointers = sn_vec_mem_usage(&design->names.names, sizeof(char*)); + usage->name_buckets = sn_vec_mem_usage(&design->names.buckets, sizeof(uint32_t)); + usage->name_links = sn_vec_mem_usage(&design->names.links, sizeof(uint32_t)); + for (size_t i = 0; i < design->names.names.size; i++) + { + size_t string_bytes = strlen(sn_vec_at(char*, &design->names.names, i)) + 1; + sn_mem_size_add(&usage->name_strings, sn_mem_size_make(string_bytes, string_bytes)); + } + usage->names = usage->name_pointers; + sn_mem_size_add(&usage->names, usage->name_buckets); + sn_mem_size_add(&usage->names, usage->name_links); + sn_mem_size_add(&usage->names, usage->name_strings); + usage->constant_words = sn_vec_mem_usage(&design->constant_words, sizeof(uint32_t)); + for (size_t i = 0; i < design->modules.size; i++) + { + sn_module_mem_usage_t module_usage; + sn_module_get_mem_usage(sn_vec_at(sn_module_t*, &design->modules, i), &module_usage); + sn_module_mem_usage_add(&usage->module_attributes, &module_usage); + } + usage->modules = usage->module_attributes.total; + usage->total = usage->design_struct; + sn_mem_size_add(&usage->total, usage->module_table); + sn_mem_size_add(&usage->total, usage->modules); + sn_mem_size_add(&usage->total, usage->names); + sn_mem_size_add(&usage->total, usage->constant_words); +} + +static inline uint64_t sn_name_hash(const char* text) +{ + assert(text); + uint64_t hash = UINT64_C(1469598103934665603); + while (*text) + { + hash ^= (unsigned char)*text++; + hash *= UINT64_C(1099511628211); + } + return hash; +} + +static inline char* sn_string_dup(const char* text) +{ + assert(text); + size_t size = strlen(text) + 1; + char* copy = (char*)malloc(size); + assert(copy); + memcpy(copy, text, size); + return copy; +} + +static inline void sn_name_mgr_rehash(sn_name_mgr_t* mgr, size_t bucket_count) +{ + assert(mgr); + assert(bucket_count >= 8); + assert((bucket_count & (bucket_count - 1)) == 0); + + sn_vec_t buckets; + sn_vec_init(&buckets); + sn_vec_resize(uint32_t, &buckets, bucket_count); + for (size_t i = 0; i < bucket_count; i++) + sn_vec_at(uint32_t, &buckets, i) = SN_INVALID_ID; + + assert(mgr->links.size == mgr->names.size); + for (size_t i = 0; i < mgr->names.size; i++) + { + const char* name = sn_vec_at(char*, &mgr->names, i); + size_t bucket = (size_t)sn_name_hash(name) & (bucket_count - 1); + sn_vec_at(uint32_t, &mgr->links, i) = sn_vec_at(uint32_t, &buckets, bucket); + sn_vec_at(uint32_t, &buckets, bucket) = (uint32_t)i; + } + + sn_vec_destroy(&mgr->buckets); + mgr->buckets = buckets; +} + +static inline void sn_name_mgr_init(sn_name_mgr_t* mgr) +{ + assert(mgr); + sn_vec_init(&mgr->names); + sn_vec_init(&mgr->buckets); + sn_vec_init(&mgr->links); + sn_name_mgr_rehash(mgr, 64); +} + +static inline void sn_name_mgr_destroy(sn_name_mgr_t* mgr) +{ + assert(mgr); + for (size_t i = 0; i < mgr->names.size; i++) + free(sn_vec_at(char*, &mgr->names, i)); + sn_vec_destroy(&mgr->names); + sn_vec_destroy(&mgr->buckets); + sn_vec_destroy(&mgr->links); +} + +static inline sn_name_id_t sn_name_find(const sn_name_mgr_t* mgr, const char* text) +{ + assert(mgr); + assert(text); + assert(mgr->buckets.size); + + size_t bucket = (size_t)sn_name_hash(text) & (mgr->buckets.size - 1); + uint32_t id = sn_vec_at(uint32_t, &mgr->buckets, bucket); + while (id != SN_INVALID_ID) + { + assert(id < mgr->names.size); + if (strcmp(sn_vec_at(char*, &mgr->names, id), text) == 0) + return id; + id = sn_vec_at(uint32_t, &mgr->links, id); + } + return SN_INVALID_ID; +} + +static inline sn_name_id_t sn_name_intern(sn_name_mgr_t* mgr, const char* text) +{ + assert(mgr); + assert(text); + + sn_name_id_t id = sn_name_find(mgr, text); + if (id != SN_INVALID_ID) + return id; + + assert(mgr->names.size < SN_INVALID_ID); + if ((mgr->names.size + 1) * 4 >= mgr->buckets.size * 3) + sn_name_mgr_rehash(mgr, mgr->buckets.size * 2); + + id = (sn_name_id_t)mgr->names.size; + size_t bucket = (size_t)sn_name_hash(text) & (mgr->buckets.size - 1); + *sn_vec_push(char*, &mgr->names) = sn_string_dup(text); + *sn_vec_push(uint32_t, &mgr->links) = sn_vec_at(uint32_t, &mgr->buckets, bucket); + sn_vec_at(uint32_t, &mgr->buckets, bucket) = id; + return id; +} + +// Removes a temporary name that was the most recently interned entry. Reordering helpers use this after replacing a +// provisional module, preventing internal __sn_* names from accumulating in serialized designs. +static inline void sn_name_remove_last(sn_name_mgr_t* mgr, sn_name_id_t id) +{ + assert(mgr && mgr->names.size && id + 1 == mgr->names.size && mgr->links.size == mgr->names.size); + const char* name = sn_vec_at(char*, &mgr->names, id); + size_t bucket = (size_t)sn_name_hash(name) & (mgr->buckets.size - 1); + uint32_t current = sn_vec_at(uint32_t, &mgr->buckets, bucket); + uint32_t previous = SN_INVALID_ID; + while (current != id) + { + assert(current != SN_INVALID_ID && current < id); + previous = current; + current = sn_vec_at(uint32_t, &mgr->links, current); + } + uint32_t next = sn_vec_at(uint32_t, &mgr->links, id); + if (previous == SN_INVALID_ID) + sn_vec_at(uint32_t, &mgr->buckets, bucket) = next; + else + sn_vec_at(uint32_t, &mgr->links, previous) = next; + free(sn_vec_at(char*, &mgr->names, id)); + mgr->names.size--; + mgr->links.size--; +} + +static inline const char* sn_name_get(const sn_name_mgr_t* mgr, sn_name_id_t id) +{ + assert(mgr); + assert(id < mgr->names.size); + return sn_vec_at(char*, &mgr->names, id); +} + +static inline void sn_module_init(sn_module_t* module, sn_design_t* design, sn_module_id_t id, sn_name_id_t name) +{ + assert(module); + assert(design); + module->design = design; + module->id = id; + module->name = name; + + sn_vec_init(&module->obj_types); + sn_vec_init(&module->width_signed); + sn_vec_init(&module->fanin_counts); + sn_vec_init(&module->fanin_offsets); + sn_vec_init(&module->type_ids); + sn_vec_init(&module->name_ids); + sn_vec_init(&module->fanins); + for (size_t i = 0; i < SN_OBJ_TYPE_COUNT; i++) + sn_vec_init(&module->type_objects[i]); + sn_vec_init(&module->reg_flags); + sn_vec_init(&module->mem_depths); + sn_vec_init(&module->inst_modules); + sn_vec_init(&module->fan_insts); + sn_vec_init(&module->slice_infos); + sn_vec_init(&module->repeat_counts); + sn_vec_init(&module->const_word_offsets); + sn_vec_init(&module->const_hash_buckets); + sn_vec_init(&module->const_hash_entries); + sn_vec_init(&module->lut_truths); + sn_vec_init(&module->gate_ids); + sn_vec_init(&module->fanout_counts); + sn_vec_init(&module->fanout_offsets); + sn_vec_init(&module->fanouts); + module->fanouts_valid = false; + module->interface_locked = false; + sn_vec_init(&module->copy_ids); + module->copy_module = SN_INVALID_ID; +} + +static inline void sn_module_destroy(sn_module_t* module) +{ + assert(module); + sn_vec_destroy(&module->obj_types); + sn_vec_destroy(&module->width_signed); + sn_vec_destroy(&module->fanin_counts); + sn_vec_destroy(&module->fanin_offsets); + sn_vec_destroy(&module->type_ids); + sn_vec_destroy(&module->name_ids); + sn_vec_destroy(&module->fanins); + for (size_t i = 0; i < SN_OBJ_TYPE_COUNT; i++) + sn_vec_destroy(&module->type_objects[i]); + sn_vec_destroy(&module->reg_flags); + sn_vec_destroy(&module->mem_depths); + sn_vec_destroy(&module->inst_modules); + sn_vec_destroy(&module->fan_insts); + sn_vec_destroy(&module->slice_infos); + sn_vec_destroy(&module->repeat_counts); + sn_vec_destroy(&module->const_word_offsets); + sn_vec_destroy(&module->const_hash_buckets); + sn_vec_destroy(&module->const_hash_entries); + sn_vec_destroy(&module->lut_truths); + sn_vec_destroy(&module->gate_ids); + sn_vec_destroy(&module->fanout_counts); + sn_vec_destroy(&module->fanout_offsets); + sn_vec_destroy(&module->fanouts); + sn_vec_destroy(&module->copy_ids); +} + +static inline sn_design_t* sn_design_create(void) +{ + sn_design_t* design = (sn_design_t*)calloc(1, sizeof(sn_design_t)); + assert(design); + sn_vec_init(&design->modules); + sn_name_mgr_init(&design->names); + sn_vec_init(&design->constant_words); + return design; +} + +static inline void sn_design_destroy(sn_design_t* design) +{ + if (!design) + return; + for (size_t i = 0; i < design->modules.size; i++) + { + sn_module_t* module = sn_vec_at(sn_module_t*, &design->modules, i); + sn_module_destroy(module); + free(module); + } + sn_vec_destroy(&design->modules); + sn_name_mgr_destroy(&design->names); + sn_vec_destroy(&design->constant_words); + free(design); +} + +static inline sn_module_id_t sn_design_add_module(sn_design_t* design, const char* name) +{ + assert(design); + assert(name); + assert(design->modules.size < SN_INVALID_ID); + sn_name_id_t name_id = sn_name_intern(&design->names, name); + for (size_t i = 0; i < design->modules.size; i++) + assert(sn_vec_at(sn_module_t*, &design->modules, i)->name != name_id); + + sn_module_id_t id = (sn_module_id_t)design->modules.size; + sn_module_t* module = (sn_module_t*)calloc(1, sizeof(sn_module_t)); + assert(module); + sn_module_init(module, design, id, name_id); + *sn_vec_push(sn_module_t*, &design->modules) = module; + return id; +} + +static inline sn_module_t* sn_design_get_module(sn_design_t* design, sn_module_id_t id) +{ + assert(design); + assert(id < design->modules.size); + return sn_vec_at(sn_module_t*, &design->modules, id); +} + +static inline const sn_module_t* sn_design_get_module_const(const sn_design_t* design, sn_module_id_t id) +{ + assert(design); + assert(id < design->modules.size); + return sn_vec_at(sn_module_t*, &design->modules, id); +} + +static inline sn_module_id_t sn_design_find_module(const sn_design_t* design, const char* name) +{ + assert(design); + assert(name); + sn_name_id_t name_id = sn_name_find(&design->names, name); + if (name_id == SN_INVALID_ID) + return SN_INVALID_ID; + for (sn_module_id_t id = 0; id < design->modules.size; id++) + if (sn_design_get_module_const(design, id)->name == name_id) + return id; + return SN_INVALID_ID; +} + +// Deep-copy the semantic design state directly in memory. Derived constant-interner tables are intentionally left +// empty and rebuilt lazily, matching binary roundtrip behavior. Fanout caches and optional object-copy maps are +// preserved because callers may intentionally retain them between transformations. +static inline sn_design_t* sn_design_dup(const sn_design_t* source) +{ + assert(source); + sn_design_t* target = sn_design_create(); + for (size_t i = 0; i < source->names.names.size; i++) + { + sn_name_id_t name = sn_name_intern(&target->names, sn_name_get(&source->names, (sn_name_id_t)i)); + assert(name == i); + } + sn_vec_dup(uint32_t, &target->constant_words, &source->constant_words); + for (sn_module_id_t module_id = 0; module_id < source->modules.size; module_id++) + { + const sn_module_t* old_module = sn_design_get_module_const(source, module_id); + sn_module_id_t new_id = sn_design_add_module(target, sn_name_get(&source->names, old_module->name)); + assert(new_id == module_id); + sn_module_t* new_module = sn_design_get_module(target, new_id); + new_module->fanouts_valid = old_module->fanouts_valid; + new_module->interface_locked = old_module->interface_locked; + new_module->copy_module = old_module->copy_module; +#define SN_DUP_MODULE_VECTOR(type, field) sn_vec_dup(type, &new_module->field, &old_module->field) + SN_DUP_MODULE_VECTOR(sn_obj_type_t, obj_types); + SN_DUP_MODULE_VECTOR(uint32_t, width_signed); + SN_DUP_MODULE_VECTOR(sn_fanin_count_t, fanin_counts); + SN_DUP_MODULE_VECTOR(uint32_t, fanin_offsets); + SN_DUP_MODULE_VECTOR(uint32_t, type_ids); + SN_DUP_MODULE_VECTOR(uint32_t, name_ids); + SN_DUP_MODULE_VECTOR(sn_obj_id_t, fanins); + for (uint32_t type = 0; type < SN_OBJ_TYPE_COUNT; type++) + sn_vec_dup(sn_obj_id_t, &new_module->type_objects[type], &old_module->type_objects[type]); + SN_DUP_MODULE_VECTOR(uint32_t, reg_flags); + SN_DUP_MODULE_VECTOR(uint32_t, mem_depths); + SN_DUP_MODULE_VECTOR(sn_module_id_t, inst_modules); + SN_DUP_MODULE_VECTOR(sn_obj_id_t, fan_insts); + SN_DUP_MODULE_VECTOR(sn_slice_info_t, slice_infos); + SN_DUP_MODULE_VECTOR(uint32_t, repeat_counts); + SN_DUP_MODULE_VECTOR(uint32_t, const_word_offsets); + SN_DUP_MODULE_VECTOR(uint64_t, lut_truths); + SN_DUP_MODULE_VECTOR(uint32_t, gate_ids); + SN_DUP_MODULE_VECTOR(uint32_t, fanout_counts); + SN_DUP_MODULE_VECTOR(uint32_t, fanout_offsets); + SN_DUP_MODULE_VECTOR(sn_obj_id_t, fanouts); + SN_DUP_MODULE_VECTOR(sn_obj_id_t, copy_ids); +#undef SN_DUP_MODULE_VECTOR + } + return target; +} + +static inline uint32_t sn_design_module_output_count(const sn_design_t* design, sn_module_id_t module_id) +{ + const sn_module_t* module = sn_design_get_module_const(design, module_id); + assert(module->type_objects[SN_PO].size <= UINT32_MAX); + return (uint32_t)module->type_objects[SN_PO].size; +} + +static inline uint32_t sn_pack_width_signed(uint32_t width, bool is_signed) +{ + assert(width <= UINT32_MAX >> 1); + return (width << 1) | (is_signed ? 1u : 0u); +} + +static inline uint32_t sn_obj_width(const sn_module_t* module, sn_obj_id_t object) +{ + assert(module); + assert(object < module->width_signed.size); + return sn_vec_at(uint32_t, &module->width_signed, object) >> 1; +} + +static inline bool sn_obj_is_signed(const sn_module_t* module, sn_obj_id_t object) +{ + assert(module); + assert(object < module->width_signed.size); + return (sn_vec_at(uint32_t, &module->width_signed, object) & 1u) != 0; +} + +static inline sn_obj_type_t sn_obj_type(const sn_module_t* module, sn_obj_id_t object) +{ + assert(module); + assert(object < module->obj_types.size); + return sn_vec_at(sn_obj_type_t, &module->obj_types, object); +} + +static inline sn_type_id_t sn_obj_type_id(const sn_module_t* module, sn_obj_id_t object) +{ + assert(module); + assert(object < module->type_ids.size); + return sn_vec_at(uint32_t, &module->type_ids, object); +} + +static inline sn_name_id_t sn_obj_name_id(const sn_module_t* module, sn_obj_id_t object) +{ + assert(module); + assert(object < module->name_ids.size); + return sn_vec_at(uint32_t, &module->name_ids, object); +} + +static inline const char* sn_obj_name(const sn_module_t* module, sn_obj_id_t object) +{ + sn_name_id_t name = sn_obj_name_id(module, object); + assert(name != SN_INVALID_ID); + return sn_name_get(&module->design->names, name); +} + +static inline void sn_module_invalidate_fanouts(sn_module_t* module) +{ + assert(module); + module->fanouts_valid = false; + module->fanout_counts.size = 0; + module->fanout_offsets.size = 0; + module->fanouts.size = 0; +} + +static inline sn_obj_id_t sn_module_add_obj(sn_module_t* module, sn_obj_type_t type, uint32_t width, bool is_signed, + uint32_t fanin_count, sn_name_id_t name) +{ + assert(module); + assert(type > SN_NONE && type < SN_OBJ_TYPE_COUNT); + assert(module->obj_types.size < SN_INVALID_ID); + assert(fanin_count <= UINT16_MAX); + assert(module->fanins.size + fanin_count <= UINT32_MAX); + assert(name == SN_INVALID_ID || name < module->design->names.names.size); + + size_t object_count = module->obj_types.size + 1; + sn_vec_reserve(sn_obj_type_t, &module->obj_types, object_count); + sn_vec_reserve(uint32_t, &module->width_signed, object_count); + sn_vec_reserve(sn_fanin_count_t, &module->fanin_counts, object_count); + sn_vec_reserve(uint32_t, &module->fanin_offsets, object_count); + sn_vec_reserve(uint32_t, &module->type_ids, object_count); + sn_vec_reserve(uint32_t, &module->name_ids, object_count); + sn_vec_reserve(sn_obj_id_t, &module->fanins, module->fanins.size + fanin_count); + + sn_vec_t* objects_of_type = &module->type_objects[type]; + assert(objects_of_type->size < SN_INVALID_ID); + sn_vec_reserve(sn_obj_id_t, objects_of_type, objects_of_type->size + 1); + + if (type == SN_REG_OUT) + sn_vec_reserve(uint32_t, &module->reg_flags, module->reg_flags.size + 1); + else if (type == SN_MEM_OUT) + sn_vec_reserve(uint32_t, &module->mem_depths, module->mem_depths.size + 1); + else if (type == SN_INST) + sn_vec_reserve(sn_module_id_t, &module->inst_modules, module->inst_modules.size + 1); + else if (type == SN_FAN) + sn_vec_reserve(sn_obj_id_t, &module->fan_insts, module->fan_insts.size + 1); + else if (type == SN_SLICE) + sn_vec_reserve(sn_slice_info_t, &module->slice_infos, module->slice_infos.size + 1); + else if (type == SN_REPLICATE) + sn_vec_reserve(uint32_t, &module->repeat_counts, module->repeat_counts.size + 1); + else if (type == SN_CONST) + sn_vec_reserve(uint32_t, &module->const_word_offsets, module->const_word_offsets.size + 1); + else if (type == SN_LUT) + sn_vec_reserve(uint64_t, &module->lut_truths, module->lut_truths.size + 1); + else if (type == SN_GATE) + sn_vec_reserve(uint32_t, &module->gate_ids, module->gate_ids.size + 1); + + sn_obj_id_t object = (sn_obj_id_t)module->obj_types.size; + sn_type_id_t type_id = (sn_type_id_t)objects_of_type->size; + uint32_t fanin_offset = (uint32_t)module->fanins.size; + + *sn_vec_push(sn_obj_type_t, &module->obj_types) = type; + *sn_vec_push(uint32_t, &module->width_signed) = sn_pack_width_signed(width, is_signed); + *sn_vec_push(sn_fanin_count_t, &module->fanin_counts) = (sn_fanin_count_t)fanin_count; + *sn_vec_push(uint32_t, &module->fanin_offsets) = fanin_offset; + *sn_vec_push(uint32_t, &module->type_ids) = type_id; + *sn_vec_push(uint32_t, &module->name_ids) = name; + *sn_vec_push(sn_obj_id_t, objects_of_type) = object; + + for (uint32_t i = 0; i < fanin_count; i++) + *sn_vec_push(sn_obj_id_t, &module->fanins) = SN_INVALID_ID; + + if (type == SN_REG_OUT) + { + assert(type_id == module->reg_flags.size); + sn_vec_push(uint32_t, &module->reg_flags); + } + else if (type == SN_MEM_OUT) + { + assert(type_id == module->mem_depths.size); + sn_vec_push(uint32_t, &module->mem_depths); + } + else if (type == SN_INST) + { + assert(type_id == module->inst_modules.size); + *sn_vec_push(sn_module_id_t, &module->inst_modules) = SN_INVALID_ID; + } + else if (type == SN_FAN) + { + assert(type_id == module->fan_insts.size); + *sn_vec_push(sn_obj_id_t, &module->fan_insts) = SN_INVALID_ID; + } + else if (type == SN_SLICE) + { + assert(type_id == module->slice_infos.size); + sn_vec_push(sn_slice_info_t, &module->slice_infos); + } + else if (type == SN_REPLICATE) + { + assert(type_id == module->repeat_counts.size); + sn_vec_push(uint32_t, &module->repeat_counts); + } + else if (type == SN_CONST) + { + assert(type_id == module->const_word_offsets.size); + sn_vec_push(uint32_t, &module->const_word_offsets); + } + else if (type == SN_LUT) + { + assert(type_id == module->lut_truths.size); + sn_vec_push(uint64_t, &module->lut_truths); + } + else if (type == SN_GATE) + { + assert(type_id == module->gate_ids.size); + sn_vec_push(uint32_t, &module->gate_ids); + } + + sn_module_invalidate_fanouts(module); + return object; +} + +static inline sn_obj_id_t sn_module_add_named_obj(sn_module_t* module, sn_obj_type_t type, uint32_t width, + bool is_signed, uint32_t fanin_count, const char* name) +{ + assert(module); + sn_name_id_t name_id = name ? sn_name_intern(&module->design->names, name) : SN_INVALID_ID; + return sn_module_add_obj(module, type, width, is_signed, fanin_count, name_id); +} + +static inline uint32_t sn_obj_fanin_count(const sn_module_t* module, sn_obj_id_t object) +{ + assert(module); + assert(object < module->fanin_counts.size); + return sn_vec_at(sn_fanin_count_t, &module->fanin_counts, object); +} + +static inline sn_obj_id_t sn_obj_fanin(const sn_module_t* module, sn_obj_id_t object, uint32_t input_index) +{ + assert(module); + assert(object < module->fanin_counts.size); + assert(input_index < sn_obj_fanin_count(module, object)); + uint32_t offset = sn_vec_at(uint32_t, &module->fanin_offsets, object); + assert((size_t)offset + input_index < module->fanins.size); + return sn_vec_at(sn_obj_id_t, &module->fanins, offset + input_index); +} + +static inline void sn_obj_connect(sn_module_t* module, sn_obj_id_t object, uint32_t input_index, sn_obj_id_t fanin) +{ + assert(module); + assert(object < module->obj_types.size); + assert(fanin == SN_INVALID_ID || fanin < module->obj_types.size); + assert(input_index < sn_obj_fanin_count(module, object)); + uint32_t offset = sn_vec_at(uint32_t, &module->fanin_offsets, object); + sn_vec_at(sn_obj_id_t, &module->fanins, offset + input_index) = fanin; + sn_module_invalidate_fanouts(module); +} + +static inline void sn_obj_add_fanin(sn_module_t* module, sn_obj_id_t object, sn_obj_id_t fanin) +{ + assert(module); + assert(object < module->obj_types.size); + assert(fanin < module->obj_types.size); + assert(module->fanins.size < UINT32_MAX); + + uint32_t offset = sn_vec_at(uint32_t, &module->fanin_offsets, object); + uint32_t count = sn_obj_fanin_count(module, object); + assert(count < UINT16_MAX); + size_t insertion = (size_t)offset + count; + assert(insertion <= module->fanins.size); + + size_t old_size = module->fanins.size; + sn_vec_resize(sn_obj_id_t, &module->fanins, old_size + 1); + sn_obj_id_t* fanins = sn_vec_data(sn_obj_id_t, &module->fanins); + memmove(fanins + insertion + 1, fanins + insertion, (old_size - insertion) * sizeof(*fanins)); + fanins[insertion] = fanin; + sn_vec_at(sn_fanin_count_t, &module->fanin_counts, object) = (sn_fanin_count_t)(count + 1); + + // Preserve fanin-span order for every object after the modified object. + for (sn_obj_id_t other = object + 1; other < module->obj_types.size; other++) + { + uint32_t other_offset = sn_vec_at(uint32_t, &module->fanin_offsets, other); + if (other_offset >= insertion) + sn_vec_at(uint32_t, &module->fanin_offsets, other) = other_offset + 1; + } + sn_module_invalidate_fanouts(module); +} + +static inline sn_obj_id_t sn_module_add_pi(sn_module_t* module, uint32_t width, bool is_signed, const char* name) +{ + assert(module); + assert(!module->interface_locked); + return sn_module_add_named_obj(module, SN_PI, width, is_signed, 0, name); +} + +static inline sn_obj_id_t sn_module_add_po(sn_module_t* module, uint32_t width, bool is_signed, const char* name, + sn_obj_id_t driver) +{ + assert(module); + assert(!module->interface_locked); + assert(driver < module->obj_types.size); + assert(sn_obj_width(module, driver) == width); + sn_obj_id_t output = sn_module_add_named_obj(module, SN_PO, width, is_signed, 1, name); + sn_obj_connect(module, output, 0, driver); + return output; +} + +static inline bool sn_obj_type_is_operator(sn_obj_type_t type) +{ + return type == SN_BUF || (type >= SN_POS && type < SN_OBJ_TYPE_COUNT); +} + +static inline sn_obj_id_t sn_module_add_operator(sn_module_t* module, sn_obj_type_t type, uint32_t width, + bool is_signed, uint32_t fanin_count, const sn_obj_id_t* fanins, + const char* name) +{ + assert(sn_obj_type_is_operator(type)); + assert(fanin_count == 0 || fanins); + sn_obj_id_t object = sn_module_add_named_obj(module, type, width, is_signed, fanin_count, name); + for (uint32_t i = 0; i < fanin_count; i++) + sn_obj_connect(module, object, i, fanins[i]); + return object; +} + +static inline sn_obj_id_t sn_module_add_lut(sn_module_t* module, uint32_t fanin_count, + const sn_obj_id_t* fanins, uint64_t truth, const char* name) +{ + assert(module && fanin_count <= 6); + assert(fanin_count == 0 || fanins); + for (uint32_t i = 0; i < fanin_count; i++) + assert(fanins[i] < module->obj_types.size && sn_obj_width(module, fanins[i]) == 1); + if (fanin_count < 6) + truth &= (UINT64_C(1) << (UINT32_C(1) << fanin_count)) - 1; + sn_obj_id_t object = sn_module_add_operator(module, SN_LUT, 1, false, fanin_count, fanins, name); + sn_vec_at(uint64_t, &module->lut_truths, sn_obj_type_id(module, object)) = truth; + return object; +} + +static inline uint64_t sn_obj_lut_truth(const sn_module_t* module, sn_obj_id_t object) +{ + assert(module && sn_obj_type(module, object) == SN_LUT); + return sn_vec_at(uint64_t, &module->lut_truths, sn_obj_type_id(module, object)); +} + +static inline sn_obj_id_t sn_module_add_gate(sn_module_t* module, uint32_t fanin_count, + const sn_obj_id_t* fanins, uint32_t gate_id, const char* name) +{ + assert(module); + assert(gate_id != SN_INVALID_ID); + assert(fanin_count == 0 || fanins); + for (uint32_t i = 0; i < fanin_count; i++) + assert(fanins[i] < module->obj_types.size && sn_obj_width(module, fanins[i]) == 1); + sn_obj_id_t object = sn_module_add_operator(module, SN_GATE, 1, false, fanin_count, fanins, name); + sn_vec_at(uint32_t, &module->gate_ids, sn_obj_type_id(module, object)) = gate_id; + return object; +} + +static inline uint32_t sn_obj_gate_id(const sn_module_t* module, sn_obj_id_t object) +{ + assert(module && sn_obj_type(module, object) == SN_GATE); + return sn_vec_at(uint32_t, &module->gate_ids, sn_obj_type_id(module, object)); +} + +static inline sn_obj_id_t sn_module_add_mux(sn_module_t* module, sn_obj_id_t select, sn_obj_id_t selected, + sn_obj_id_t default_value, const char* name) +{ + assert(module); + assert(select < module->obj_types.size); + assert(selected < module->obj_types.size); + assert(default_value < module->obj_types.size); + assert(sn_obj_width(module, select) == 1); + assert(sn_obj_width(module, selected) == sn_obj_width(module, default_value)); + sn_obj_id_t fanins[SN_MUX_FANIN_COUNT] = {select, selected, default_value}; + return sn_module_add_operator(module, SN_MUX, sn_obj_width(module, default_value), + sn_obj_is_signed(module, default_value), SN_MUX_FANIN_COUNT, fanins, name); +} + +static inline sn_obj_id_t sn_module_add_bmux(sn_module_t* module, sn_obj_id_t select, sn_obj_id_t packed_alternatives, + uint32_t output_width, bool is_signed, const char* name) +{ + assert(module); + assert(select < module->obj_types.size); + assert(packed_alternatives < module->obj_types.size); + assert(output_width); + uint32_t select_width = sn_obj_width(module, select); + assert(select_width < 31); + uint64_t packed_width = (uint64_t)output_width << select_width; + assert(packed_width <= UINT32_MAX >> 1); + assert(sn_obj_width(module, packed_alternatives) == packed_width); + (void)packed_width; + sn_obj_id_t fanins[SN_BMUX_FANIN_COUNT] = {select, packed_alternatives}; + return sn_module_add_operator(module, SN_BMUX, output_width, is_signed, SN_BMUX_FANIN_COUNT, fanins, name); +} + +static inline sn_obj_id_t sn_module_add_pmux(sn_module_t* module, sn_obj_id_t select, sn_obj_id_t packed_alternatives, + sn_obj_id_t default_value, const char* name) +{ + assert(module); + assert(select < module->obj_types.size); + assert(packed_alternatives < module->obj_types.size); + assert(default_value < module->obj_types.size); + uint32_t select_width = sn_obj_width(module, select); + uint32_t output_width = sn_obj_width(module, default_value); + assert(select_width); + assert(output_width); + uint64_t packed_width = (uint64_t)output_width * select_width; + assert(packed_width <= UINT32_MAX >> 1); + assert(sn_obj_width(module, packed_alternatives) == packed_width); + (void)packed_width; + sn_obj_id_t fanins[SN_PMUX_FANIN_COUNT] = {select, packed_alternatives, default_value}; + return sn_module_add_operator(module, SN_PMUX, output_width, sn_obj_is_signed(module, default_value), + SN_PMUX_FANIN_COUNT, fanins, name); +} + +static inline uint32_t sn_const_word_count(uint32_t width) +{ + assert(width); + return (width + 31u) / 32u; +} + +static inline uint64_t sn_const_hash_words(uint32_t width, bool is_signed, const uint32_t* words) +{ + uint64_t hash = UINT64_C(1469598103934665603); + hash = (hash ^ width) * UINT64_C(1099511628211); + hash = (hash ^ (is_signed ? 1u : 0u)) * UINT64_C(1099511628211); + for (uint32_t i = 0; i < sn_const_word_count(width); i++) + hash = (hash ^ words[i]) * UINT64_C(1099511628211); + return hash; +} + +static inline uint64_t sn_const_object_hash(const sn_module_t* module, sn_obj_id_t object) +{ + sn_obj_type_t type = sn_obj_type(module, object); + uint32_t width = sn_obj_width(module, object); + uint64_t hash = UINT64_C(1469598103934665603); + hash = (hash ^ width) * UINT64_C(1099511628211); + hash = (hash ^ (sn_obj_is_signed(module, object) ? 1u : 0u)) * UINT64_C(1099511628211); + const uint32_t* words = NULL; + if (type == SN_CONST) + { + uint32_t type_id = sn_obj_type_id(module, object); + uint32_t offset = sn_vec_at(uint32_t, &module->const_word_offsets, type_id); + words = &sn_vec_at(uint32_t, &module->design->constant_words, offset); + } + for (uint32_t i = 0; i < sn_const_word_count(width); i++) + { + uint32_t word = type == SN_CONST0 ? 0 : type == SN_CONST1 ? (i == 0 ? 1u : 0u) : words[i]; + hash = (hash ^ word) * UINT64_C(1099511628211); + } + return hash; +} + +static inline void sn_const_cache_rehash(sn_module_t* module, size_t bucket_count) +{ + assert(module && bucket_count >= 64 && (bucket_count & (bucket_count - 1)) == 0); + sn_vec_t buckets; + sn_vec_init(&buckets); + sn_vec_resize(uint32_t, &buckets, bucket_count); + for (size_t i = 0; i < bucket_count; i++) + sn_vec_at(uint32_t, &buckets, i) = SN_INVALID_ID; + for (size_t i = 0; i < module->const_hash_entries.size; i++) + { + sn_const_hash_entry_t* entry = &sn_vec_at(sn_const_hash_entry_t, &module->const_hash_entries, i); + size_t bucket = (size_t)entry->hash & (bucket_count - 1); + entry->next = sn_vec_at(uint32_t, &buckets, bucket); + sn_vec_at(uint32_t, &buckets, bucket) = (uint32_t)i; + } + sn_vec_destroy(&module->const_hash_buckets); + module->const_hash_buckets = buckets; +} + +static inline void sn_const_cache_insert(sn_module_t* module, sn_obj_id_t object, uint64_t hash) +{ + if ((module->const_hash_entries.size + 1) * 4 >= module->const_hash_buckets.size * 3) + sn_const_cache_rehash(module, module->const_hash_buckets.size * 2); + assert(module->const_hash_entries.size < SN_INVALID_ID); + size_t bucket = (size_t)hash & (module->const_hash_buckets.size - 1); + sn_const_hash_entry_t* entry = sn_vec_push(sn_const_hash_entry_t, &module->const_hash_entries); + entry->hash = hash; + entry->object = object; + entry->next = sn_vec_at(uint32_t, &module->const_hash_buckets, bucket); + sn_vec_at(uint32_t, &module->const_hash_buckets, bucket) = (uint32_t)(module->const_hash_entries.size - 1); +} + +static inline void sn_const_cache_ensure(sn_module_t* module) +{ + if (module->const_hash_buckets.size) + return; + sn_const_cache_rehash(module, 64); + for (sn_obj_id_t object = 0; object < module->obj_types.size; object++) + { + sn_obj_type_t type = sn_obj_type(module, object); + if ((type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST) && + sn_obj_name_id(module, object) == SN_INVALID_ID) + sn_const_cache_insert(module, object, sn_const_object_hash(module, object)); + } +} + +static inline bool sn_const_object_equal(const sn_module_t* module, sn_obj_id_t object, sn_obj_type_t type, + uint32_t width, bool is_signed, const uint32_t* words) +{ + if (sn_obj_type(module, object) != type || sn_obj_width(module, object) != width || + sn_obj_is_signed(module, object) != is_signed) + return false; + if (type != SN_CONST) + return true; + uint32_t type_id = sn_obj_type_id(module, object); + uint32_t offset = sn_vec_at(uint32_t, &module->const_word_offsets, type_id); + const uint32_t* existing = &sn_vec_at(uint32_t, &module->design->constant_words, offset); + return memcmp(existing, words, (size_t)sn_const_word_count(width) * sizeof(uint32_t)) == 0; +} + +static inline sn_obj_id_t sn_module_add_const(sn_module_t* module, uint32_t width, bool is_signed, + const uint32_t* words, const char* name) +{ + assert(module); + assert(width); + assert(words); + uint32_t word_count = sn_const_word_count(width); + uint32_t final_bits = width & 31u; + if (final_bits) + assert((words[word_count - 1] >> final_bits) == 0); + + bool is_zero = true; + bool is_one = words[0] == 1; + for (uint32_t i = 0; i < word_count; i++) + { + is_zero = is_zero && words[i] == 0; + if (i != 0) + is_one = is_one && words[i] == 0; + } + + sn_obj_type_t type = is_zero ? SN_CONST0 : (is_one ? SN_CONST1 : SN_CONST); + uint64_t hash = sn_const_hash_words(width, is_signed, words); + if (!name) + { + sn_const_cache_ensure(module); + size_t bucket = (size_t)hash & (module->const_hash_buckets.size - 1); + uint32_t entry_id = sn_vec_at(uint32_t, &module->const_hash_buckets, bucket); + while (entry_id != SN_INVALID_ID) + { + const sn_const_hash_entry_t* entry = + &sn_vec_at(sn_const_hash_entry_t, &module->const_hash_entries, entry_id); + if (entry->hash == hash && + sn_const_object_equal(module, entry->object, type, width, is_signed, words)) + return entry->object; + entry_id = entry->next; + } + } + sn_obj_id_t object = sn_module_add_named_obj(module, type, width, is_signed, 0, name); + if (type == SN_CONST) + { + assert(module->design->constant_words.size + word_count <= UINT32_MAX); + uint32_t offset = (uint32_t)module->design->constant_words.size; + for (uint32_t i = 0; i < word_count; i++) + *sn_vec_push(uint32_t, &module->design->constant_words) = words[i]; + sn_vec_at(uint32_t, &module->const_word_offsets, sn_obj_type_id(module, object)) = offset; + } + if (!name) + sn_const_cache_insert(module, object, hash); + return object; +} + +static inline const uint32_t* sn_const_words(const sn_module_t* module, sn_obj_id_t object) +{ + assert(module); + assert(sn_obj_type(module, object) == SN_CONST); + uint32_t word_offset = sn_vec_at(uint32_t, &module->const_word_offsets, sn_obj_type_id(module, object)); + assert(word_offset + sn_const_word_count(sn_obj_width(module, object)) <= module->design->constant_words.size); + return &sn_vec_at(uint32_t, &module->design->constant_words, word_offset); +} + +static inline sn_obj_id_t sn_module_add_concat(sn_module_t* module, uint32_t fanin_count, const sn_obj_id_t* fanins, + const char* name) +{ + // fanins[0] contributes the least-significant result bits. + assert(fanin_count); + assert(fanins); + uint64_t width = 0; + bool all_constant = true; + for (uint32_t i = 0; i < fanin_count; i++) + { + assert(fanins[i] < module->obj_types.size); + width += sn_obj_width(module, fanins[i]); + sn_obj_type_t type = sn_obj_type(module, fanins[i]); + all_constant = all_constant && (type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST); + } + assert(width <= UINT32_MAX >> 1); + if (all_constant) + { + uint32_t result_width = (uint32_t)width; + uint32_t* words = (uint32_t*)calloc(sn_const_word_count(result_width), sizeof(uint32_t)); + assert(words); + uint32_t offset = 0; + for (uint32_t i = 0; i < fanin_count; i++) + { + sn_obj_id_t fanin = fanins[i]; + sn_obj_type_t type = sn_obj_type(module, fanin); + uint32_t fanin_width = sn_obj_width(module, fanin); + const uint32_t* source = type == SN_CONST ? sn_const_words(module, fanin) : NULL; + for (uint32_t bit = 0; bit < fanin_width; bit++) + { + bool value = type == SN_CONST1 ? bit == 0 + : type == SN_CONST && ((source[bit >> 5] >> (bit & 31)) & 1u); + if (value) + words[(offset + bit) >> 5] |= 1u << ((offset + bit) & 31); + } + offset += fanin_width; + } + assert(offset == result_width); + sn_obj_id_t result = sn_module_add_const(module, result_width, false, words, name); + free(words); + return result; + } + if (fanin_count > UINT16_MAX) + { + uint32_t chunk_count = 1 + (fanin_count - 1) / UINT16_MAX; + sn_obj_id_t* chunks = (sn_obj_id_t*)malloc((size_t)chunk_count * sizeof(sn_obj_id_t)); + assert(chunks); + for (uint32_t chunk = 0; chunk < chunk_count; chunk++) + { + uint32_t offset = chunk * UINT16_MAX; + uint32_t count = fanin_count - offset < UINT16_MAX ? fanin_count - offset : UINT16_MAX; + chunks[chunk] = sn_module_add_concat(module, count, fanins + offset, NULL); + } + sn_obj_id_t result = sn_module_add_concat(module, chunk_count, chunks, name); + free(chunks); + return result; + } + return sn_module_add_operator(module, SN_CONCAT, (uint32_t)width, false, fanin_count, fanins, name); +} + +static inline sn_obj_id_t sn_module_add_repeat(sn_module_t* module, sn_obj_id_t value, uint32_t count, const char* name) +{ + assert(module); + assert(value < module->obj_types.size); + assert(count); + uint64_t width = (uint64_t)sn_obj_width(module, value) * count; + assert(width <= UINT32_MAX >> 1); + sn_obj_id_t object = sn_module_add_named_obj(module, SN_REPLICATE, (uint32_t)width, false, 1, name); + sn_obj_connect(module, object, 0, value); + sn_vec_at(uint32_t, &module->repeat_counts, sn_obj_type_id(module, object)) = count; + return object; +} + +static inline uint32_t sn_obj_repeat_count(const sn_module_t* module, sn_obj_id_t object) +{ + assert(module); + assert(sn_obj_type(module, object) == SN_REPLICATE); + return sn_vec_at(uint32_t, &module->repeat_counts, sn_obj_type_id(module, object)); +} + +static inline sn_obj_id_t sn_module_add_slice(sn_module_t* module, sn_obj_id_t value, int32_t left_index, + int32_t right_index, const char* name) +{ + assert(module); + assert(value < module->obj_types.size); + assert(left_index >= 0 && (uint32_t)left_index < sn_obj_width(module, value)); + assert(right_index >= 0 && (uint32_t)right_index < sn_obj_width(module, value)); + int64_t difference = (int64_t)left_index - (int64_t)right_index; + uint64_t width = (uint64_t)(difference < 0 ? -difference : difference) + 1; + assert(width <= UINT32_MAX >> 1); + sn_obj_id_t object = sn_module_add_named_obj(module, SN_SLICE, (uint32_t)width, false, 1, name); + sn_obj_connect(module, object, 0, value); + sn_slice_info_t* info = &sn_vec_at(sn_slice_info_t, &module->slice_infos, sn_obj_type_id(module, object)); + info->left_index = left_index; + info->right_index = right_index; + info->flags = left_index >= right_index ? SN_SLICE_DESCENDING : 0; + return object; +} + +static inline const sn_slice_info_t* sn_obj_slice_info(const sn_module_t* module, sn_obj_id_t object) +{ + assert(module); + assert(sn_obj_type(module, object) == SN_SLICE); + return &sn_vec_at(sn_slice_info_t, &module->slice_infos, sn_obj_type_id(module, object)); +} + +static inline sn_obj_id_t sn_obj_pair_in(const sn_module_t* module, sn_obj_id_t out) +{ + assert(module); + sn_obj_type_t out_type = sn_obj_type(module, out); + sn_obj_type_t in_type = SN_NONE; + if (out_type == SN_REG_OUT) + in_type = SN_REG_IN; + else if (out_type == SN_MEM_OUT) + in_type = SN_MEM_IN; + else if (out_type == SN_LOOP_OUT) + in_type = SN_LOOP_IN; + else + assert(false); + + sn_type_id_t pair_id = sn_obj_type_id(module, out); + assert(pair_id < module->type_objects[in_type].size); + sn_obj_id_t in = sn_vec_at(sn_obj_id_t, &module->type_objects[in_type], pair_id); + assert(sn_obj_type_id(module, in) == pair_id); + return in; +} + +static inline sn_obj_id_t sn_obj_pair_out(const sn_module_t* module, sn_obj_id_t in) +{ + assert(module); + sn_obj_type_t in_type = sn_obj_type(module, in); + sn_obj_type_t out_type = SN_NONE; + if (in_type == SN_REG_IN) + out_type = SN_REG_OUT; + else if (in_type == SN_MEM_IN) + out_type = SN_MEM_OUT; + else if (in_type == SN_LOOP_IN) + out_type = SN_LOOP_OUT; + else + assert(false); + + sn_type_id_t pair_id = sn_obj_type_id(module, in); + assert(pair_id < module->type_objects[out_type].size); + sn_obj_id_t out = sn_vec_at(sn_obj_id_t, &module->type_objects[out_type], pair_id); + assert(sn_obj_type_id(module, out) == pair_id); + return out; +} + +static inline sn_obj_pair_t sn_module_add_pair(sn_module_t* module, sn_obj_type_t out_type, sn_obj_type_t in_type, + uint32_t width, bool is_signed, const char* out_name, + const char* in_name) +{ + assert(module); + assert((out_type == SN_REG_OUT && in_type == SN_REG_IN) || (out_type == SN_MEM_OUT && in_type == SN_MEM_IN) || + (out_type == SN_LOOP_OUT && in_type == SN_LOOP_IN)); + assert(module->type_objects[out_type].size == module->type_objects[in_type].size); + + uint32_t out_fanin_count = out_type == SN_REG_OUT ? SN_REG_FANIN_COUNT + : out_type == SN_MEM_OUT ? SN_MEM_OUT_FANIN_COUNT + : 1; + uint32_t in_fanin_count = in_type == SN_MEM_IN ? 0 : 1; + + sn_obj_pair_t pair; + pair.out = sn_module_add_named_obj(module, out_type, width, is_signed, out_fanin_count, out_name); + pair.in = sn_module_add_named_obj(module, in_type, width, is_signed, in_fanin_count, in_name); + assert(sn_obj_type_id(module, pair.out) == sn_obj_type_id(module, pair.in)); + assert(pair.in == pair.out + 1); + assert(sn_obj_pair_in(module, pair.out) == pair.in); + assert(sn_obj_pair_out(module, pair.in) == pair.out); + uint32_t pair_slot = out_type == SN_REG_OUT ? SN_REG_DATA + : out_type == SN_MEM_OUT ? SN_MEM_STATE + : 0; + sn_obj_connect(module, pair.out, pair_slot, pair.in); + return pair; +} + +static inline sn_obj_pair_t sn_module_add_reg_pair(sn_module_t* module, uint32_t width, bool is_signed, + const char* out_name, const char* in_name, sn_obj_id_t clock) +{ + assert(clock == SN_INVALID_ID || clock < module->obj_types.size); + sn_obj_pair_t pair = sn_module_add_pair(module, SN_REG_OUT, SN_REG_IN, width, is_signed, out_name, in_name); + sn_obj_connect(module, pair.out, SN_REG_CLOCK, clock); + return pair; +} + +static inline uint32_t sn_obj_reg_flags(const sn_module_t* module, sn_obj_id_t reg_out) +{ + assert(module); + assert(sn_obj_type(module, reg_out) == SN_REG_OUT); + return sn_vec_at(uint32_t, &module->reg_flags, sn_obj_type_id(module, reg_out)); +} + +static inline void sn_reg_set_flags(sn_module_t* module, sn_obj_id_t reg_out, uint32_t flags) +{ + assert(module); + assert(sn_obj_type(module, reg_out) == SN_REG_OUT); + assert((flags & ~SN_REG_FLAGS_ALL) == 0); + sn_vec_at(uint32_t, &module->reg_flags, sn_obj_type_id(module, reg_out)) = flags; +} + +static inline void sn_reg_set_fanin(sn_module_t* module, sn_obj_id_t reg_out, sn_reg_fanin_t slot, sn_obj_id_t fanin) +{ + assert(module); + assert(sn_obj_type(module, reg_out) == SN_REG_OUT); + assert(slot < SN_REG_FANIN_COUNT); + assert(slot != SN_REG_DATA); + if ((slot == SN_REG_INIT_DATA || slot == SN_REG_INIT_MASK) && fanin != SN_INVALID_ID) + { + sn_obj_type_t type = sn_obj_type(module, fanin); + assert(type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST); + assert(sn_obj_width(module, fanin) == sn_obj_width(module, reg_out)); + (void)type; + } + sn_obj_connect(module, reg_out, (uint32_t)slot, fanin); +} + +// Register initialization uses the same aligned representation as memory +// initialization: mask bit i describes data bit i, and a set mask bit means +// that the corresponding data bit is initialized. No data means no init; data +// with no mask means all bits valid. Builders normally provide both constants. +static inline void sn_reg_set_init(sn_module_t* module, sn_obj_id_t reg_out, sn_obj_id_t data, sn_obj_id_t mask) +{ + assert(data != SN_INVALID_ID || mask == SN_INVALID_ID); + sn_reg_set_fanin(module, reg_out, SN_REG_INIT_DATA, data); + sn_reg_set_fanin(module, reg_out, SN_REG_INIT_MASK, mask); +} + +static inline sn_obj_id_t sn_obj_reg_init_data(const sn_module_t* module, sn_obj_id_t reg_out) +{ + assert(module && sn_obj_type(module, reg_out) == SN_REG_OUT); + return sn_obj_fanin(module, reg_out, SN_REG_INIT_DATA); +} + +static inline sn_obj_id_t sn_obj_reg_init_mask(const sn_module_t* module, sn_obj_id_t reg_out) +{ + assert(module && sn_obj_type(module, reg_out) == SN_REG_OUT); + return sn_obj_fanin(module, reg_out, SN_REG_INIT_MASK); +} + +static inline sn_obj_pair_t sn_module_add_mem_pair(sn_module_t* module, uint32_t width, bool is_signed, uint32_t depth, + const char* out_name, const char* in_name) +{ + assert(depth); + sn_obj_pair_t pair = sn_module_add_pair(module, SN_MEM_OUT, SN_MEM_IN, width, is_signed, out_name, in_name); + sn_vec_at(uint32_t, &module->mem_depths, sn_obj_type_id(module, pair.out)) = depth; + return pair; +} + +static inline uint32_t sn_obj_mem_depth(const sn_module_t* module, sn_obj_id_t mem_out) +{ + assert(module); + assert(sn_obj_type(module, mem_out) == SN_MEM_OUT); + uint32_t depth = sn_vec_at(uint32_t, &module->mem_depths, sn_obj_type_id(module, mem_out)); + assert(depth); + return depth; +} + +static inline uint32_t sn_obj_mem_init_width(const sn_module_t* module, sn_obj_id_t mem_out) +{ + assert(module); + assert(sn_obj_type(module, mem_out) == SN_MEM_OUT); + uint64_t width = (uint64_t)sn_obj_width(module, mem_out) * sn_obj_mem_depth(module, mem_out); + assert(width <= UINT32_MAX); + return (uint32_t)width; +} + +static inline void sn_mem_set_init(sn_module_t* module, sn_obj_id_t mem_out, sn_obj_id_t data, sn_obj_id_t mask) +{ + assert(module); + assert(sn_obj_type(module, mem_out) == SN_MEM_OUT); + assert(data != SN_INVALID_ID || mask == SN_INVALID_ID); + uint32_t init_width = sn_obj_mem_init_width(module, mem_out); + if (data != SN_INVALID_ID) + { + sn_obj_type_t type = sn_obj_type(module, data); + assert(type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST); + assert(sn_obj_width(module, data) == init_width); + } + if (mask != SN_INVALID_ID) + { + sn_obj_type_t type = sn_obj_type(module, mask); + assert(type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST); + assert(sn_obj_width(module, mask) == init_width); + } + sn_obj_connect(module, mem_out, SN_MEM_INIT_DATA, data); + sn_obj_connect(module, mem_out, SN_MEM_INIT_MASK, mask); +} + +static inline sn_obj_id_t sn_obj_mem_init_data(const sn_module_t* module, sn_obj_id_t mem_out) +{ + assert(module); + assert(sn_obj_type(module, mem_out) == SN_MEM_OUT); + return sn_obj_fanin(module, mem_out, SN_MEM_INIT_DATA); +} + +static inline sn_obj_id_t sn_obj_mem_init_mask(const sn_module_t* module, sn_obj_id_t mem_out) +{ + assert(module); + assert(sn_obj_type(module, mem_out) == SN_MEM_OUT); + return sn_obj_fanin(module, mem_out, SN_MEM_INIT_MASK); +} + +static inline sn_obj_id_t sn_module_add_mem_read(sn_module_t* module, sn_obj_id_t mem_out, sn_obj_id_t clock, + sn_obj_id_t enable, sn_obj_id_t address, const char* name) +{ + assert(module); + assert(sn_obj_type(module, mem_out) == SN_MEM_OUT); + assert(clock == SN_INVALID_ID || clock < module->obj_types.size); + assert(enable == SN_INVALID_ID || enable < module->obj_types.size); + assert(clock != SN_INVALID_ID || enable == SN_INVALID_ID); + assert(address < module->obj_types.size); + sn_obj_id_t read = sn_module_add_named_obj(module, SN_MEM_READ, sn_obj_width(module, mem_out), + sn_obj_is_signed(module, mem_out), SN_MEM_READ_FANIN_COUNT, name); + sn_obj_connect(module, read, SN_MEM_READ_MEMORY, mem_out); + sn_obj_connect(module, read, SN_MEM_READ_CLOCK, clock); + sn_obj_connect(module, read, SN_MEM_READ_ENABLE, enable); + sn_obj_connect(module, read, SN_MEM_READ_ADDRESS, address); + return read; +} + +static inline sn_obj_id_t sn_module_add_mem_write(sn_module_t* module, sn_obj_id_t mem_in, sn_obj_id_t clock, + sn_obj_id_t enable, sn_obj_id_t data, sn_obj_id_t address, + const char* name) +{ + assert(module); + assert(sn_obj_type(module, mem_in) == SN_MEM_IN); + assert(clock < module->obj_types.size); + assert(enable == SN_INVALID_ID || enable < module->obj_types.size); + assert(data < module->obj_types.size); + assert(address < module->obj_types.size); + assert(sn_obj_width(module, data) == sn_obj_width(module, mem_in)); + sn_obj_id_t write = sn_module_add_named_obj(module, SN_MEM_WRITE, sn_obj_width(module, mem_in), + sn_obj_is_signed(module, mem_in), SN_MEM_WRITE_FANIN_COUNT, name); + sn_obj_connect(module, write, SN_MEM_WRITE_CLOCK, clock); + sn_obj_connect(module, write, SN_MEM_WRITE_ENABLE, enable); + sn_obj_connect(module, write, SN_MEM_WRITE_DATA, data); + sn_obj_connect(module, write, SN_MEM_WRITE_ADDRESS, address); + sn_obj_add_fanin(module, mem_in, write); + return write; +} + +static inline sn_obj_pair_t sn_module_add_loop_pair(sn_module_t* module, uint32_t width, bool is_signed, + const char* out_name, const char* in_name) +{ + return sn_module_add_pair(module, SN_LOOP_OUT, SN_LOOP_IN, width, is_signed, out_name, in_name); +} + +static inline sn_obj_id_t sn_module_add_inst(sn_module_t* module, sn_module_id_t referenced_module, + uint32_t input_count, const sn_obj_id_t* inputs, const char* name, + const char* const* output_names) +{ + assert(module); + assert(referenced_module < module->design->modules.size); + assert(input_count == 0 || inputs); + sn_module_t* child = sn_design_get_module(module->design, referenced_module); + assert(child->type_objects[SN_PI].size == input_count); + uint32_t output_count = sn_design_module_output_count(module->design, referenced_module); + assert(output_count != 0); + child->interface_locked = true; + + sn_obj_id_t first_output = sn_vec_at(sn_obj_id_t, &child->type_objects[SN_PO], 0); + uint32_t inst_width = output_count == 1 ? sn_obj_width(child, first_output) : 0; + bool inst_signedness = output_count == 1 ? sn_obj_is_signed(child, first_output) : false; + sn_obj_id_t inst = + sn_module_add_named_obj(module, SN_INST, inst_width, inst_signedness, input_count, name); + for (uint32_t i = 0; i < input_count; i++) + sn_obj_connect(module, inst, i, inputs[i]); + + sn_vec_at(sn_module_id_t, &module->inst_modules, sn_obj_type_id(module, inst)) = referenced_module; + + // A one-output inst is itself the output value. Multi-output insts + // are followed immediately by one SN_FAN per output. + if (output_count > 1) + { + for (uint32_t i = 0; i < output_count; i++) + { + sn_obj_id_t child_output = sn_vec_at(sn_obj_id_t, &child->type_objects[SN_PO], i); + const char* output_name = output_names ? output_names[i] : NULL; + sn_obj_id_t fan = sn_module_add_named_obj(module, SN_FAN, sn_obj_width(child, child_output), + sn_obj_is_signed(child, child_output), 1, output_name); + assert(fan == inst + 1 + i); + sn_obj_connect(module, fan, 0, inst); + sn_vec_at(sn_obj_id_t, &module->fan_insts, sn_obj_type_id(module, fan)) = inst; + } + } + return inst; +} + +static inline sn_module_id_t sn_inst_module_id(const sn_module_t* module, sn_obj_id_t inst) +{ + assert(module); + assert(sn_obj_type(module, inst) == SN_INST); + sn_module_id_t module_id = sn_vec_at(sn_module_id_t, &module->inst_modules, sn_obj_type_id(module, inst)); + assert(module_id < module->design->modules.size); + return module_id; +} + +static inline sn_obj_id_t sn_fan_inst_id(const sn_module_t* module, sn_obj_id_t fan) +{ + assert(module); + assert(sn_obj_type(module, fan) == SN_FAN); + sn_obj_id_t inst = sn_vec_at(sn_obj_id_t, &module->fan_insts, sn_obj_type_id(module, fan)); + assert(inst < fan); + assert(sn_obj_type(module, inst) == SN_INST); + assert(sn_obj_fanin_count(module, fan) == 1); + assert(sn_obj_fanin(module, fan, 0) == inst); + return inst; +} + +static inline uint32_t sn_fan_output_index(const sn_module_t* module, sn_obj_id_t fan) +{ + sn_obj_id_t inst = sn_fan_inst_id(module, fan); + uint32_t output_index = fan - inst - 1; + sn_module_id_t child_id = sn_inst_module_id(module, inst); + assert(output_index < sn_design_module_output_count(module->design, child_id)); + (void)child_id; + return output_index; +} + +static inline sn_obj_id_t sn_inst_output(const sn_module_t* module, sn_obj_id_t inst, uint32_t output_index) +{ + assert(module); + assert(sn_obj_type(module, inst) == SN_INST); + sn_module_id_t child_id = sn_inst_module_id(module, inst); + uint32_t output_count = sn_design_module_output_count(module->design, child_id); + assert(output_index < output_count); + if (output_count == 1) + return inst; + + sn_obj_id_t fan = inst + 1 + output_index; + assert(fan < module->obj_types.size); + assert(sn_obj_type(module, fan) == SN_FAN); + assert(sn_obj_fanin(module, fan, 0) == inst); + assert(sn_fan_inst_id(module, fan) == inst); + assert(sn_fan_output_index(module, fan) == output_index); + return fan; +} + +// Print the elaborated module-inst hierarchy rooted at module_id. Children +// follow the natural SN_INST order of their parent module. A module is +// printed once per inst, so repeated insts remain visible. Recursive +// instantiation is invalid for synthesis, but is marked and stopped rather +// than causing unbounded recursion in this diagnostic routine. +static inline void sn_design_print_hierarchy_rec(FILE* out, const sn_design_t* design, sn_module_id_t module_id, + size_t depth, bool is_last, bool* ancestor_has_next, + bool* active_modules) +{ + assert(out); + assert(design); + assert(module_id < design->modules.size); + assert(ancestor_has_next); + assert(active_modules); + + if (depth) + { + for (size_t level = 0; level + 1 < depth; level++) + fputs(ancestor_has_next[level] ? "│ " : " ", out); + fputs(is_last ? "└─ " : "├─ ", out); + } + + const sn_module_t* module = sn_design_get_module_const(design, module_id); + fputs(sn_name_get(&design->names, module->name), out); + if (active_modules[module_id]) + { + fputs(" [recursive]\n", out); + return; + } + fputc('\n', out); + + active_modules[module_id] = true; + size_t inst_count = module->type_objects[SN_INST].size; + for (size_t i = 0; i < inst_count; i++) + { + sn_obj_id_t inst = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_INST], i); + ancestor_has_next[depth] = i + 1 < inst_count; + sn_design_print_hierarchy_rec(out, design, sn_inst_module_id(module, inst), depth + 1, + i + 1 == inst_count, ancestor_has_next, active_modules); + } + active_modules[module_id] = false; +} + +static inline void sn_design_print_hierarchy(FILE* out, const sn_design_t* design, sn_module_id_t top_module_id) +{ + assert(out); + assert(design); + assert(top_module_id < design->modules.size); + + bool* ancestor_has_next = (bool*)calloc(design->modules.size, sizeof(bool)); + bool* active_modules = (bool*)calloc(design->modules.size, sizeof(bool)); + assert(ancestor_has_next); + assert(active_modules); + sn_design_print_hierarchy_rec(out, design, top_module_id, 0, true, ancestor_has_next, active_modules); + free(active_modules); + free(ancestor_has_next); +} + +static inline void sn_module_build_fanouts(sn_module_t* module) +{ + assert(module); + size_t object_count = module->obj_types.size; + sn_vec_resize(uint32_t, &module->fanout_counts, object_count); + sn_vec_resize(uint32_t, &module->fanout_offsets, object_count); + memset(module->fanout_counts.data, 0, object_count * sizeof(uint32_t)); + + for (sn_obj_id_t object = 0; object < object_count; object++) + { + uint32_t count = sn_obj_fanin_count(module, object); + for (uint32_t i = 0; i < count; i++) + { + sn_obj_id_t fanin = sn_obj_fanin(module, object, i); + if (fanin == SN_INVALID_ID) + continue; + assert(fanin < object_count); + uint32_t* fanout_count = &sn_vec_at(uint32_t, &module->fanout_counts, fanin); + assert(*fanout_count < UINT32_MAX); + (*fanout_count)++; + } + } + + uint32_t total = 0; + for (sn_obj_id_t object = 0; object < object_count; object++) + { + sn_vec_at(uint32_t, &module->fanout_offsets, object) = total; + uint32_t count = sn_vec_at(uint32_t, &module->fanout_counts, object); + assert(total <= UINT32_MAX - count); + total += count; + } + sn_vec_resize(sn_obj_id_t, &module->fanouts, total); + + uint32_t* cursor = (uint32_t*)calloc(object_count ? object_count : 1, sizeof(uint32_t)); + assert(cursor); + for (sn_obj_id_t object = 0; object < object_count; object++) + { + uint32_t count = sn_obj_fanin_count(module, object); + for (uint32_t i = 0; i < count; i++) + { + sn_obj_id_t fanin = sn_obj_fanin(module, object, i); + if (fanin == SN_INVALID_ID) + continue; + uint32_t offset = sn_vec_at(uint32_t, &module->fanout_offsets, fanin); + assert(cursor[fanin] < sn_vec_at(uint32_t, &module->fanout_counts, fanin)); + sn_vec_at(sn_obj_id_t, &module->fanouts, offset + cursor[fanin]++) = object; + } + } + free(cursor); + module->fanouts_valid = true; +} + +static inline uint32_t sn_obj_fanout_count(const sn_module_t* module, sn_obj_id_t object) +{ + assert(module); + assert(module->fanouts_valid); + assert(object < module->fanout_counts.size); + return sn_vec_at(uint32_t, &module->fanout_counts, object); +} + +static inline sn_obj_id_t sn_obj_fanout(const sn_module_t* module, sn_obj_id_t object, uint32_t output_index) +{ + assert(module); + assert(module->fanouts_valid); + assert(output_index < sn_obj_fanout_count(module, object)); + uint32_t offset = sn_vec_at(uint32_t, &module->fanout_offsets, object); + return sn_vec_at(sn_obj_id_t, &module->fanouts, offset + output_index); +} + +// Sequential topological traversal treats state OUT objects as sources and +// emits all of them immediately after the PIs. Their paired IN objects remain +// ordinary sinks: each IN follows the OUT, the OUT's non-pair control fanins, +// and its own functional fanins. Pre-emitting every OUT is important when an +// OUT is first discovered from inside its own next-state cone. +enum +{ + SN_TOPO_UNSEEN = 0, + SN_TOPO_VISITING, + SN_TOPO_DONE +}; + +typedef struct sn_topo_context_t +{ + const sn_module_t* module; + sn_vec_t* order; + uint8_t* marks; +} sn_topo_context_t; + +typedef struct sn_topo_frame_t +{ + sn_obj_id_t object; + uint32_t index; + uint8_t phase; +} sn_topo_frame_t; + +static inline bool sn_obj_type_is_pair_out(sn_obj_type_t type) +{ + return type == SN_REG_OUT || type == SN_MEM_OUT || type == SN_LOOP_OUT; +} + +static inline bool sn_obj_type_is_pair_in(sn_obj_type_t type) +{ + return type == SN_REG_IN || type == SN_MEM_IN || type == SN_LOOP_IN; +} + +static inline void sn_module_topo_push(sn_topo_context_t* context, sn_vec_t* stack, sn_obj_id_t object) +{ + const sn_module_t* module = context->module; + assert(object < module->obj_types.size); + if (context->marks[object] == SN_TOPO_DONE) + return; + assert(context->marks[object] == SN_TOPO_UNSEEN); + sn_obj_type_t type = sn_obj_type(module, object); + assert(type != SN_PI && type != SN_PO); + context->marks[object] = SN_TOPO_VISITING; + sn_topo_frame_t* frame = sn_vec_push(sn_topo_frame_t, stack); + frame->object = object; + frame->index = 0; + frame->phase = 0; +} + +static inline void sn_module_topo_visit(sn_topo_context_t* context, sn_obj_id_t object) +{ + assert(context); + const sn_module_t* module = context->module; + sn_vec_t stack; + sn_vec_init(&stack); + sn_module_topo_push(context, &stack, object); + while (stack.size) + { + sn_topo_frame_t* frame = &sn_vec_at(sn_topo_frame_t, &stack, stack.size - 1); + object = frame->object; + if (context->marks[object] == SN_TOPO_DONE) + { + stack.size--; + continue; + } + sn_obj_type_t type = sn_obj_type(module, object); + + if (sn_obj_type_is_pair_out(type)) + { + *sn_vec_push(sn_obj_id_t, context->order) = object; + context->marks[object] = SN_TOPO_DONE; + stack.size--; + continue; + } + + if (frame->phase == 0) + { + frame->phase = 1; + frame->index = 0; + if (sn_obj_type_is_pair_in(type)) + { + sn_obj_id_t pair_out = sn_obj_pair_out(module, object); + assert(context->marks[pair_out] != SN_TOPO_VISITING); + if (context->marks[pair_out] == SN_TOPO_UNSEEN) + sn_module_topo_push(context, &stack, pair_out); + continue; + } + } + + if (frame->phase == 1) + { + if (sn_obj_type_is_pair_in(type)) + { + sn_obj_id_t pair_out = sn_obj_pair_out(module, object); + bool pushed = false; + while (frame->index < sn_obj_fanin_count(module, pair_out)) + { + sn_obj_id_t fanin = sn_obj_fanin(module, pair_out, frame->index++); + if (fanin == SN_INVALID_ID || fanin == object) + continue; + assert(context->marks[fanin] != SN_TOPO_VISITING); + if (context->marks[fanin] == SN_TOPO_UNSEEN) + { + sn_module_topo_push(context, &stack, fanin); + pushed = true; + break; + } + } + if (pushed) + continue; + } + frame = &sn_vec_at(sn_topo_frame_t, &stack, stack.size - 1); + frame->phase = 2; + frame->index = 0; + } + + bool pushed = false; + while (frame->index < sn_obj_fanin_count(module, object)) + { + sn_obj_id_t fanin = sn_obj_fanin(module, object, frame->index++); + if (fanin == SN_INVALID_ID) + continue; + assert(context->marks[fanin] != SN_TOPO_VISITING); + if (context->marks[fanin] == SN_TOPO_UNSEEN) + { + sn_module_topo_push(context, &stack, fanin); + pushed = true; + break; + } + } + if (pushed) + continue; + + if (type == SN_INST) + { + *sn_vec_push(sn_obj_id_t, context->order) = object; + context->marks[object] = SN_TOPO_DONE; + uint32_t output_count = sn_design_module_output_count(module->design, sn_inst_module_id(module, object)); + if (output_count > 1) + for (uint32_t i = 0; i < output_count; i++) + { + sn_obj_id_t fan = sn_inst_output(module, object, i); + assert(context->marks[fan] != SN_TOPO_DONE); + *sn_vec_push(sn_obj_id_t, context->order) = fan; + context->marks[fan] = SN_TOPO_DONE; + } + } + else if (context->marks[object] != SN_TOPO_DONE) + { + *sn_vec_push(sn_obj_id_t, context->order) = object; + context->marks[object] = SN_TOPO_DONE; + } + stack.size--; + } + + sn_vec_destroy(&stack); +} + +// Returns all module object IDs exactly once. The caller owns the returned +// vector and must call sn_vec_destroy(). PIs occupy the prefix in natural port +// order, POs occupy the suffix in natural port order, and every state OUT +// precedes its paired IN. A normalized combinational cycle triggers assert(). +static inline sn_vec_t sn_module_topo_order(const sn_module_t* module) +{ + assert(module); + sn_vec_t order; + sn_vec_init(&order); + size_t object_count = module->obj_types.size; + sn_vec_reserve(sn_obj_id_t, &order, object_count); + + uint8_t* marks = NULL; + if (object_count) + { + marks = (uint8_t*)calloc(object_count, sizeof(uint8_t)); + assert(marks); + } + + sn_topo_context_t context; + context.module = module; + context.order = ℴ + context.marks = marks; + + // Place every PI first, including unused inputs. + for (size_t i = 0; i < module->type_objects[SN_PI].size; i++) + { + sn_obj_id_t input = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], i); + assert(marks[input] == SN_TOPO_UNSEEN); + marks[input] = SN_TOPO_DONE; + *sn_vec_push(sn_obj_id_t, &order) = input; + } + + // State values are combinational sources. Emit every OUT before exploring + // any next-state cone so feedback through the corresponding IN cannot + // encounter an in-progress combinational node. + for (sn_obj_id_t object = 0; object < object_count; object++) + { + if (!sn_obj_type_is_pair_out(sn_obj_type(module, object))) + continue; + assert(marks[object] == SN_TOPO_UNSEEN); + marks[object] = SN_TOPO_DONE; + *sn_vec_push(sn_obj_id_t, &order) = object; + } + + // Traverse PO cones first. POs themselves are deliberately delayed. + for (size_t i = 0; i < module->type_objects[SN_PO].size; i++) + { + sn_obj_id_t output = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PO], i); + for (uint32_t j = 0; j < sn_obj_fanin_count(module, output); j++) + { + sn_obj_id_t fanin = sn_obj_fanin(module, output, j); + if (fanin != SN_INVALID_ID) + sn_module_topo_visit(&context, fanin); + } + } + + // Include disconnected and otherwise unreachable internal objects. + for (sn_obj_id_t object = 0; object < object_count; object++) + { + sn_obj_type_t type = sn_obj_type(module, object); + if (type != SN_PI && type != SN_PO && marks[object] == SN_TOPO_UNSEEN) + sn_module_topo_visit(&context, object); + } + + // Place every PO last in natural port order. + for (size_t i = 0; i < module->type_objects[SN_PO].size; i++) + { + sn_obj_id_t output = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PO], i); + assert(marks[output] == SN_TOPO_UNSEEN); + marks[output] = SN_TOPO_DONE; + *sn_vec_push(sn_obj_id_t, &order) = output; + } + + assert(order.size == object_count); + free(marks); + return order; +} + +// Accepts any legal SN topological order, not only the particular depth-first +// order returned by sn_module_topo_order(). PIs and POs must form their natural- +// order prefix and suffix. Ordinary fanins precede their users. State OUT may +// precede its structural paired-IN fanin, and its controls may occur after OUT, +// but every such dependency must precede the paired IN. Instance FANs retain +// their immediate natural-order block. +static inline bool sn_module_is_topo(const sn_module_t* module) +{ + assert(module); + size_t object_count = module->obj_types.size; + size_t input_count = module->type_objects[SN_PI].size; + size_t output_count = module->type_objects[SN_PO].size; + if (input_count + output_count > object_count) + return false; + + for (size_t i = 0; i < input_count; i++) + if (sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], i) != i) + return false; + size_t output_begin = object_count - output_count; + for (size_t i = 0; i < output_count; i++) + if (sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PO], i) != output_begin + i) + return false; + + for (sn_obj_id_t object = 0; object < object_count; object++) + { + sn_obj_type_t type = sn_obj_type(module, object); + if (object < input_count && type != SN_PI) + return false; + if (object >= output_begin && type != SN_PO) + return false; + + sn_obj_id_t pair_in = SN_INVALID_ID; + if (sn_obj_type_is_pair_out(type)) + { + pair_in = sn_obj_pair_in(module, object); + if (pair_in <= object) + return false; + } + for (uint32_t i = 0; i < sn_obj_fanin_count(module, object); i++) + { + sn_obj_id_t fanin = sn_obj_fanin(module, object, i); + if (fanin == SN_INVALID_ID || fanin == pair_in) + continue; + if (pair_in != SN_INVALID_ID) + { + if (fanin >= pair_in) + return false; + } + else if (fanin >= object) + return false; + } + + if (type == SN_INST) + { + sn_module_id_t child_id = sn_inst_module_id(module, object); + uint32_t child_outputs = sn_design_module_output_count(module->design, child_id); + if (child_outputs > 1) + { + for (uint32_t i = 0; i < child_outputs; i++) + if (sn_inst_output(module, object, i) != object + 1 + i) + return false; + } + } + else if (type == SN_FAN) + { + sn_obj_id_t inst = sn_fan_inst_id(module, object); + if (object != inst + 1 + sn_fan_output_index(module, object)) + return false; + } + } + return true; +} + +static inline bool sn_design_is_topo(const sn_design_t* design) +{ + assert(design); + for (size_t i = 0; i < design->modules.size; i++) + if (!sn_module_is_topo(sn_design_get_module_const(design, (sn_module_id_t)i))) + return false; + return true; +} + +// Returns the target module and object mapping from the source module's most +// recent duplication. A new duplication replaces the previous mapping. The +// mapping remains owned by the source module until another duplication or +// until that module is destroyed. +static inline sn_module_id_t sn_module_dup_target(const sn_module_t* source) +{ + assert(source); + assert(source->copy_module < source->design->modules.size); + return source->copy_module; +} + +static inline sn_obj_id_t sn_obj_dup(const sn_module_t* source, sn_obj_id_t old_object) +{ + assert(source); + assert(source->copy_module < source->design->modules.size); + assert(source->copy_ids.size == source->obj_types.size); + assert(old_object < source->copy_ids.size); + sn_obj_id_t new_object = sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object); + const sn_module_t* target = sn_design_get_module_const(source->design, source->copy_module); + assert(new_object < target->obj_types.size); + (void)target; + return new_object; +} + +static inline void sn_design_invalidate_copies_to_module_except(sn_design_t* design, sn_module_id_t module_id, + const sn_module_t* preserve) +{ + for (size_t i = 0; i < design->modules.size; i++) + { + sn_module_t* module = sn_vec_at(sn_module_t*, &design->modules, i); + if (!module || module == preserve || module->copy_module != module_id) + continue; + sn_vec_destroy(&module->copy_ids); + sn_vec_init(&module->copy_ids); + module->copy_module = SN_INVALID_ID; + } +} + +static inline void sn_design_invalidate_copies_to_module(sn_design_t* design, sn_module_id_t module_id) +{ + sn_design_invalidate_copies_to_module_except(design, module_id, NULL); +} + +// Duplicates a module in topological order within the same design. Object +// names and design-wide constant words are shared by ID; all module-local +// object references and type-specific object IDs are rebuilt. The old-to-new +// object map is retained in the source module as its most recent copy map. +static inline sn_obj_id_t sn_module_dup_obj_skeleton(sn_module_t* target, const sn_module_t* source, + sn_obj_id_t old_object) +{ + assert(target); + assert(source); + return sn_module_add_obj(target, sn_obj_type(source, old_object), sn_obj_width(source, old_object), + sn_obj_is_signed(source, old_object), sn_obj_fanin_count(source, old_object), + sn_obj_name_id(source, old_object)); +} + +// Copies metadata whose value does not contain a module-local object ID. +// SN_FAN ownership is remapped separately, while collapsing omits insts +// and fans entirely. +static inline void sn_module_dup_obj_metadata(sn_module_t* target, sn_type_id_t target_type_id, + const sn_module_t* source, sn_obj_id_t old_object) +{ + assert(target); + assert(source); + sn_obj_type_t type = sn_obj_type(source, old_object); + sn_type_id_t source_type_id = sn_obj_type_id(source, old_object); + if (type == SN_REG_OUT) + sn_vec_at(uint32_t, &target->reg_flags, target_type_id) = + sn_vec_at(uint32_t, &source->reg_flags, source_type_id); + else if (type == SN_MEM_OUT) + sn_vec_at(uint32_t, &target->mem_depths, target_type_id) = + sn_vec_at(uint32_t, &source->mem_depths, source_type_id); + else if (type == SN_INST) + { + sn_module_id_t child_id = sn_vec_at(sn_module_id_t, &source->inst_modules, source_type_id); + assert(child_id < target->design->modules.size); + sn_vec_at(sn_module_id_t, &target->inst_modules, target_type_id) = child_id; + sn_design_get_module(target->design, child_id)->interface_locked = true; + } + else if (type == SN_SLICE) + sn_vec_at(sn_slice_info_t, &target->slice_infos, target_type_id) = + sn_vec_at(sn_slice_info_t, &source->slice_infos, source_type_id); + else if (type == SN_REPLICATE) + sn_vec_at(uint32_t, &target->repeat_counts, target_type_id) = + sn_vec_at(uint32_t, &source->repeat_counts, source_type_id); + else if (type == SN_CONST) + sn_vec_at(uint32_t, &target->const_word_offsets, target_type_id) = + sn_vec_at(uint32_t, &source->const_word_offsets, source_type_id); + else if (type == SN_LUT) + sn_vec_at(uint64_t, &target->lut_truths, target_type_id) = + sn_vec_at(uint64_t, &source->lut_truths, source_type_id); + else if (type == SN_GATE) + sn_vec_at(uint32_t, &target->gate_ids, target_type_id) = + sn_vec_at(uint32_t, &source->gate_ids, source_type_id); +} + +static inline sn_module_id_t sn_design_dup_module_topo(sn_design_t* design, sn_module_id_t source_module_id, + const char* new_name) +{ + assert(design); + assert(source_module_id < design->modules.size); + assert(new_name); + + sn_module_t* source = sn_design_get_module(design, source_module_id); + sn_vec_t order = sn_module_topo_order(source); + sn_module_id_t target_module_id = sn_design_add_module(design, new_name); + sn_module_t* target = sn_design_get_module(design, target_module_id); + + sn_vec_resize(sn_obj_id_t, &source->copy_ids, source->obj_types.size); + for (size_t i = 0; i < source->copy_ids.size; i++) + sn_vec_at(sn_obj_id_t, &source->copy_ids, i) = SN_INVALID_ID; + + // Create all objects first so every old fanin has a known new object ID. + for (size_t i = 0; i < order.size; i++) + { + sn_obj_id_t old_object = sn_vec_at(sn_obj_id_t, &order, i); + sn_obj_id_t new_object = sn_module_dup_obj_skeleton(target, source, old_object); + assert(new_object == i); + sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object) = new_object; + } + + // Type IDs are semantically significant (notably for OUT/IN pairing), so + // preserve them and rebuild each inverse type_objects mapping. + for (size_t i = 0; i < order.size; i++) + { + sn_obj_id_t old_object = sn_vec_at(sn_obj_id_t, &order, i); + sn_obj_id_t new_object = sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object); + sn_obj_type_t type = sn_obj_type(source, old_object); + sn_type_id_t type_id = sn_obj_type_id(source, old_object); + assert(type_id < target->type_objects[type].size); + sn_vec_at(uint32_t, &target->type_ids, new_object) = type_id; + sn_vec_at(sn_obj_id_t, &target->type_objects[type], type_id) = new_object; + } + + // Copy the compact type-specific arrays. Entries in these arrays use the + // preserved source type IDs, except fan owner IDs which are module-local. + for (size_t i = 0; i < order.size; i++) + { + sn_obj_id_t old_object = sn_vec_at(sn_obj_id_t, &order, i); + sn_obj_id_t new_object = sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object); + sn_obj_type_t type = sn_obj_type(source, old_object); + sn_type_id_t type_id = sn_obj_type_id(source, old_object); + (void)new_object; + + sn_module_dup_obj_metadata(target, type_id, source, old_object); + if (type == SN_FAN) + { + sn_obj_id_t old_inst = sn_vec_at(sn_obj_id_t, &source->fan_insts, type_id); + assert(old_inst < source->copy_ids.size); + sn_obj_id_t new_inst = sn_vec_at(sn_obj_id_t, &source->copy_ids, old_inst); + assert(new_inst != SN_INVALID_ID); + sn_vec_at(sn_obj_id_t, &target->fan_insts, type_id) = new_inst; + } + } + + // Reconnect all module-local fanins through the persistent copy map. + for (size_t i = 0; i < order.size; i++) + { + sn_obj_id_t old_object = sn_vec_at(sn_obj_id_t, &order, i); + sn_obj_id_t new_object = sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object); + for (uint32_t j = 0; j < sn_obj_fanin_count(source, old_object); j++) + { + sn_obj_id_t old_fanin = sn_obj_fanin(source, old_object, j); + sn_obj_id_t new_fanin = + old_fanin == SN_INVALID_ID ? SN_INVALID_ID : sn_vec_at(sn_obj_id_t, &source->copy_ids, old_fanin); + assert(new_fanin == SN_INVALID_ID || new_fanin < target->obj_types.size); + sn_obj_connect(target, new_object, j, new_fanin); + } + } + + // Validate the invariants that are sensitive to physical reordering. + for (sn_obj_id_t object = 0; object < target->obj_types.size; object++) + { + sn_obj_type_t type = sn_obj_type(target, object); + if (sn_obj_type_is_pair_out(type)) + { + sn_obj_id_t pair_in = sn_obj_pair_in(target, object); + assert(object < pair_in); + assert(sn_obj_pair_out(target, pair_in) == object); + (void)pair_in; + } + else if (type == SN_FAN) + { + sn_obj_id_t inst = sn_fan_inst_id(target, object); + uint32_t output_index = sn_fan_output_index(target, object); + assert(object == inst + 1 + output_index); + (void)inst; + (void)output_index; + } + } + + source->copy_module = target_module_id; + sn_vec_destroy(&order); + return target_module_id; +} + +// Reorders one module in place using the same dependency-aware duplication as +// sn_design_dup_module_topo(). Its module ID and name are preserved, so existing +// insts remain valid. Pointers and object IDs into the old module become +// invalid; callers must reacquire the module and rebuild any object mappings. +static inline void sn_design_reorder_module_topo(sn_design_t* design, sn_module_id_t module_id) +{ + assert(design); + assert(module_id < design->modules.size); + if (sn_module_is_topo(sn_design_get_module_const(design, module_id))) + return; + + char temporary_name[96]; + uint32_t suffix = 0; + do + { + int length = snprintf(temporary_name, sizeof(temporary_name), "__sn_topo_%u_%u", module_id, suffix++); + assert(length >= 0 && (size_t)length < sizeof(temporary_name)); + assert(suffix != 0); + } while (sn_name_find(&design->names, temporary_name) != SN_INVALID_ID); + + size_t old_module_count = design->modules.size; + sn_module_t* source = sn_design_get_module(design, module_id); + sn_name_id_t source_name = source->name; + bool interface_locked = source->interface_locked; + sn_module_id_t reordered_id = sn_design_dup_module_topo(design, module_id, temporary_name); + assert(reordered_id == old_module_count); + sn_module_t* reordered = sn_design_get_module(design, reordered_id); + sn_name_id_t temporary_name_id = reordered->name; + + sn_design_invalidate_copies_to_module(design, module_id); + sn_module_destroy(source); + free(source); + reordered->id = module_id; + reordered->name = source_name; + reordered->interface_locked = interface_locked; + sn_vec_at(sn_module_t*, &design->modules, module_id) = reordered; + design->modules.size--; + sn_name_remove_last(&design->names, temporary_name_id); + assert(sn_module_is_topo(reordered)); +} + +typedef struct sn_const_zero_frame_t +{ + sn_obj_id_t object; + uint32_t next_fanin; +} sn_const_zero_frame_t; + +static inline uint32_t sn_const_zero_dependency_count(const sn_module_t* module, sn_obj_id_t object) +{ + sn_obj_type_t type = sn_obj_type(module, object); + if (type == SN_CAST || type == SN_SLICE || type == SN_REPLICATE) + return 1; + if (type == SN_CONCAT || type == SN_BIT_AND || type == SN_LOG_AND || type == SN_BIT_OR || + type == SN_BIT_XOR || type == SN_LOG_OR) + return sn_obj_fanin_count(module, object); + if (type == SN_MUX || type == SN_PMUX) + return 3; + return 0; +} + +static inline bool sn_const_zero_cached(const uint8_t* cache, sn_obj_id_t object) +{ + return object != SN_INVALID_ID && cache[object] == 1; +} + +static inline bool sn_const_zero_evaluate(const sn_module_t* module, sn_obj_id_t object, const uint8_t* cache) +{ + sn_obj_type_t type = sn_obj_type(module, object); + if (type == SN_CONST0) + return true; + if (type == SN_CONST) + { + const uint32_t* words = sn_const_words(module, object); + for (uint32_t i = 0; i < sn_const_word_count(sn_obj_width(module, object)); i++) + if (words[i]) + return false; + return true; + } + if (type == SN_CAST || type == SN_SLICE || type == SN_REPLICATE) + return sn_const_zero_cached(cache, sn_obj_fanin(module, object, 0)); + if (type == SN_CONCAT || type == SN_BIT_OR || type == SN_BIT_XOR || type == SN_LOG_OR) + { + for (uint32_t i = 0; i < sn_obj_fanin_count(module, object); i++) + if (!sn_const_zero_cached(cache, sn_obj_fanin(module, object, i))) + return false; + return true; + } + if (type == SN_BIT_AND || type == SN_LOG_AND) + { + for (uint32_t i = 0; i < sn_obj_fanin_count(module, object); i++) + if (sn_const_zero_cached(cache, sn_obj_fanin(module, object, i))) + return true; + return false; + } + if (type == SN_MUX) + { + sn_obj_id_t select = sn_obj_fanin(module, object, SN_MUX_SELECT); + sn_obj_id_t selected = sn_obj_fanin(module, object, SN_MUX_SELECTED); + sn_obj_id_t default_value = sn_obj_fanin(module, object, SN_MUX_DEFAULT); + return sn_const_zero_cached(cache, select) + ? sn_const_zero_cached(cache, default_value) + : sn_const_zero_cached(cache, selected) && sn_const_zero_cached(cache, default_value); + } + if (type == SN_PMUX) + { + sn_obj_id_t select = sn_obj_fanin(module, object, SN_PMUX_SELECT); + sn_obj_id_t alternatives = sn_obj_fanin(module, object, SN_PMUX_ALTERNATIVES); + sn_obj_id_t default_value = sn_obj_fanin(module, object, SN_PMUX_DEFAULT); + return sn_const_zero_cached(cache, select) + ? sn_const_zero_cached(cache, default_value) + : sn_const_zero_cached(cache, alternatives) && sn_const_zero_cached(cache, default_value); + } + return false; +} + +// Returns true for the constant-zero forms needed by sequential cleanup. The explicit stack bounds native call-stack +// use even for very deep expression chains; ordinary combinational constant propagation remains a separate pass. +static inline bool sn_obj_is_const_zero_rec(const sn_module_t* module, sn_obj_id_t object, uint8_t* cache) +{ + assert(module); + if (object == SN_INVALID_ID) + return false; + bool owns_cache = cache == NULL; + if (owns_cache) + { + cache = (uint8_t*)calloc(module->obj_types.size, 1); + assert(cache); + } + if (!cache[object]) + { + sn_vec_t stack; + sn_vec_init(&stack); + cache[object] = 3; + sn_const_zero_frame_t* first = sn_vec_push(sn_const_zero_frame_t, &stack); + first->object = object; + first->next_fanin = 0; + while (stack.size) + { + sn_const_zero_frame_t* frame = + &sn_vec_at(sn_const_zero_frame_t, &stack, stack.size - 1); + uint32_t dependency_count = sn_const_zero_dependency_count(module, frame->object); + if (frame->next_fanin < dependency_count) + { + sn_obj_id_t dependency = sn_obj_fanin(module, frame->object, frame->next_fanin++); + if (dependency != SN_INVALID_ID && cache[dependency] == 0) + { + cache[dependency] = 3; + sn_const_zero_frame_t* child = sn_vec_push(sn_const_zero_frame_t, &stack); + child->object = dependency; + child->next_fanin = 0; + } + continue; + } + cache[frame->object] = sn_const_zero_evaluate(module, frame->object, cache) ? 1 : 2; + stack.size--; + } + sn_vec_destroy(&stack); + } + bool result = cache[object] == 1; + if (owns_cache) + free(cache); + return result; +} + +static inline bool sn_obj_is_const_zero(const sn_module_t* module, sn_obj_id_t object) +{ + return sn_obj_is_const_zero_rec(module, object, NULL); +} + +// Under SN's two-state sequential convention, an unspecified or unknown +// initial state starts at zero. A register therefore remains zero when it has +// no active set, has no nonzero explicit initialization/reset value, and its +// data is zero (or its enable is permanently disabled). +static inline bool sn_reg_is_const_zero(const sn_module_t* module, sn_obj_id_t reg_out, uint8_t* cache) +{ + assert(module); + assert(sn_obj_type(module, reg_out) == SN_REG_OUT); + sn_obj_id_t set = sn_obj_fanin(module, reg_out, SN_REG_SET); + sn_obj_id_t init = sn_obj_fanin(module, reg_out, SN_REG_INIT_DATA); + sn_obj_id_t reset_value = sn_obj_fanin(module, reg_out, SN_REG_RESET_VALUE); + if ((set != SN_INVALID_ID && !sn_obj_is_const_zero_rec(module, set, cache)) || + (init != SN_INVALID_ID && !sn_obj_is_const_zero_rec(module, init, cache)) || + (reset_value != SN_INVALID_ID && !sn_obj_is_const_zero_rec(module, reset_value, cache))) + return false; + + sn_obj_id_t enable = sn_obj_fanin(module, reg_out, SN_REG_ENABLE); + if (enable != SN_INVALID_ID && sn_obj_is_const_zero_rec(module, enable, cache)) + return true; + sn_obj_id_t reg_in = sn_obj_pair_in(module, reg_out); + sn_obj_id_t data = sn_obj_fanin(module, reg_in, 0); + return data == reg_out || sn_obj_is_const_zero_rec(module, data, cache); +} + +typedef struct sn_clean_topo_context_t +{ + const sn_module_t* module; + sn_vec_t* order; + uint8_t* marks; + uint8_t* const_zero_objects; + uint8_t* const_zero_cache; +} sn_clean_topo_context_t; + +static inline void sn_module_clean_topo_push(sn_clean_topo_context_t* context, sn_vec_t* stack, + sn_obj_id_t object) +{ + const sn_module_t* module = context->module; + assert(object < module->obj_types.size); + if (context->marks[object] == SN_TOPO_DONE) + return; + assert(context->marks[object] == SN_TOPO_UNSEEN); + sn_obj_type_t type = sn_obj_type(module, object); + assert(type != SN_PI && type != SN_PO); + if (context->const_zero_objects[object] || sn_obj_is_const_zero_rec(module, object, context->const_zero_cache)) + { + context->const_zero_objects[object] = 1; + context->marks[object] = SN_TOPO_DONE; + return; + } + context->marks[object] = SN_TOPO_VISITING; + sn_topo_frame_t* frame = sn_vec_push(sn_topo_frame_t, stack); + frame->object = object; + frame->index = 0; + frame->phase = 0; +} + +static inline void sn_module_clean_topo_visit(sn_clean_topo_context_t* context, sn_obj_id_t object) +{ + assert(context); + const sn_module_t* module = context->module; + sn_vec_t stack; + sn_vec_init(&stack); + sn_module_clean_topo_push(context, &stack, object); + while (stack.size) + { + sn_topo_frame_t* frame = &sn_vec_at(sn_topo_frame_t, &stack, stack.size - 1); + object = frame->object; + if (context->marks[object] == SN_TOPO_DONE) + { + stack.size--; + continue; + } + sn_obj_type_t type = sn_obj_type(module, object); + if (sn_obj_type_is_pair_out(type)) + { + *sn_vec_push(sn_obj_id_t, context->order) = object; + context->marks[object] = SN_TOPO_DONE; + stack.size--; + continue; + } + if (frame->phase == 0) + { + frame->phase = 1; + frame->index = 0; + if (sn_obj_type_is_pair_in(type)) + { + sn_obj_id_t pair_out = sn_obj_pair_out(module, object); + assert(context->marks[pair_out] != SN_TOPO_VISITING); + if (context->marks[pair_out] == SN_TOPO_UNSEEN) + sn_module_clean_topo_push(context, &stack, pair_out); + continue; + } + } + if (frame->phase == 1) + { + if (sn_obj_type_is_pair_in(type)) + { + sn_obj_id_t pair_out = sn_obj_pair_out(module, object); + bool pushed = false; + while (frame->index < sn_obj_fanin_count(module, pair_out)) + { + sn_obj_id_t fanin = sn_obj_fanin(module, pair_out, frame->index++); + if (fanin == SN_INVALID_ID || fanin == object) + continue; + assert(context->marks[fanin] != SN_TOPO_VISITING); + if (context->marks[fanin] == SN_TOPO_UNSEEN) + { + sn_module_clean_topo_push(context, &stack, fanin); + pushed = true; + break; + } + } + if (pushed) + continue; + } + frame = &sn_vec_at(sn_topo_frame_t, &stack, stack.size - 1); + frame->phase = 2; + frame->index = 0; + } + bool pushed = false; + while (frame->index < sn_obj_fanin_count(module, object)) + { + sn_obj_id_t fanin = sn_obj_fanin(module, object, frame->index++); + if (fanin == SN_INVALID_ID) + continue; + assert(context->marks[fanin] != SN_TOPO_VISITING); + if (context->marks[fanin] == SN_TOPO_UNSEEN) + { + sn_module_clean_topo_push(context, &stack, fanin); + pushed = true; + break; + } + } + if (pushed) + continue; + if (type == SN_INST) + { + *sn_vec_push(sn_obj_id_t, context->order) = object; + context->marks[object] = SN_TOPO_DONE; + uint32_t output_count = sn_design_module_output_count(module->design, sn_inst_module_id(module, object)); + if (output_count > 1) + for (uint32_t i = 0; i < output_count; i++) + { + sn_obj_id_t fan = sn_inst_output(module, object, i); + assert(context->marks[fan] != SN_TOPO_DONE); + *sn_vec_push(sn_obj_id_t, context->order) = fan; + context->marks[fan] = SN_TOPO_DONE; + } + } + else if (context->marks[object] != SN_TOPO_DONE) + { + *sn_vec_push(sn_obj_id_t, context->order) = object; + context->marks[object] = SN_TOPO_DONE; + } + stack.size--; + } + sn_vec_destroy(&stack); +} + +static inline void sn_module_clean_rebuild_pair_ids(sn_module_t* target, const sn_module_t* source, + sn_obj_type_t out_type, sn_obj_type_t in_type) +{ + assert(target); + assert(source); + size_t next_type_id = 0; + for (size_t i = 0; i < source->type_objects[out_type].size; i++) + { + sn_obj_id_t old_out = sn_vec_at(sn_obj_id_t, &source->type_objects[out_type], i); + sn_obj_id_t new_out = sn_vec_at(sn_obj_id_t, &source->copy_ids, old_out); + if (new_out == SN_INVALID_ID || sn_obj_type(target, new_out) != out_type) + continue; + sn_obj_id_t old_in = sn_obj_pair_in(source, old_out); + sn_obj_id_t new_in = sn_vec_at(sn_obj_id_t, &source->copy_ids, old_in); + assert(new_in != SN_INVALID_ID && sn_obj_type(target, new_in) == in_type); + assert(next_type_id < target->type_objects[out_type].size); + assert(next_type_id < target->type_objects[in_type].size); + sn_vec_at(uint32_t, &target->type_ids, new_out) = (uint32_t)next_type_id; + sn_vec_at(uint32_t, &target->type_ids, new_in) = (uint32_t)next_type_id; + sn_vec_at(sn_obj_id_t, &target->type_objects[out_type], next_type_id) = new_out; + sn_vec_at(sn_obj_id_t, &target->type_objects[in_type], next_type_id) = new_in; + next_type_id++; + } + assert(next_type_id == target->type_objects[out_type].size); + assert(next_type_id == target->type_objects[in_type].size); +} + +// Duplicates only the sequential transitive fanin cone of the module outputs. +// A reached state OUT makes its paired IN, next-state logic, and controls +// reachable. Unreached state and logic are omitted. Registers proven to remain +// zero are replaced by constants, so their pairs are never constructed. PIs +// and POs are always preserved in natural interface order. +static inline sn_module_id_t sn_design_dup_module_clean_topo(sn_design_t* design, + sn_module_id_t source_module_id, + const char* new_name) +{ + assert(design); + assert(source_module_id < design->modules.size); + assert(new_name); + sn_module_t* source = sn_design_get_module(design, source_module_id); + size_t object_count = source->obj_types.size; + uint8_t* marks = object_count ? (uint8_t*)calloc(object_count, sizeof(uint8_t)) : NULL; + uint8_t* const_zero_objects = object_count ? (uint8_t*)calloc(object_count, sizeof(uint8_t)) : NULL; + uint8_t* const_zero_cache = object_count ? (uint8_t*)calloc(object_count, sizeof(uint8_t)) : NULL; + assert(!object_count || (marks && const_zero_objects && const_zero_cache)); + + for (size_t i = 0; i < source->type_objects[SN_REG_OUT].size; i++) + { + sn_obj_id_t reg_out = sn_vec_at(sn_obj_id_t, &source->type_objects[SN_REG_OUT], i); + if (!sn_reg_is_const_zero(source, reg_out, const_zero_cache)) + continue; + const_zero_objects[reg_out] = 1; + marks[sn_obj_pair_in(source, reg_out)] = SN_TOPO_DONE; + } + + sn_vec_t order; + sn_vec_init(&order); + sn_vec_reserve(sn_obj_id_t, &order, object_count); + for (size_t i = 0; i < source->type_objects[SN_PI].size; i++) + { + sn_obj_id_t input = sn_vec_at(sn_obj_id_t, &source->type_objects[SN_PI], i); + marks[input] = SN_TOPO_DONE; + *sn_vec_push(sn_obj_id_t, &order) = input; + } + sn_clean_topo_context_t context = {source, &order, marks, const_zero_objects, const_zero_cache}; + for (size_t i = 0; i < source->type_objects[SN_PO].size; i++) + { + sn_obj_id_t output = sn_vec_at(sn_obj_id_t, &source->type_objects[SN_PO], i); + for (uint32_t j = 0; j < sn_obj_fanin_count(source, output); j++) + { + sn_obj_id_t fanin = sn_obj_fanin(source, output, j); + if (fanin != SN_INVALID_ID) + sn_module_clean_topo_visit(&context, fanin); + } + } + // State OUTs break sequential cycles. After discovering them from the PO + // cones, traverse their paired IN cones separately. Those cones can expose + // more live state, so iterate to a fixed point. + bool added_state; + do + { + added_state = false; + for (sn_obj_id_t object = 0; object < object_count; object++) + { + sn_obj_type_t type = sn_obj_type(source, object); + if (!sn_obj_type_is_pair_out(type) || const_zero_objects[object] || + marks[object] != SN_TOPO_DONE) + continue; + sn_obj_id_t pair_in = sn_obj_pair_in(source, object); + if (marks[pair_in] == SN_TOPO_DONE) + continue; + sn_module_clean_topo_visit(&context, pair_in); + added_state = true; + } + } while (added_state); + for (size_t i = 0; i < source->type_objects[SN_PO].size; i++) + { + sn_obj_id_t output = sn_vec_at(sn_obj_id_t, &source->type_objects[SN_PO], i); + marks[output] = SN_TOPO_DONE; + *sn_vec_push(sn_obj_id_t, &order) = output; + } + + sn_module_id_t target_id = sn_design_add_module(design, new_name); + sn_module_t* target = sn_design_get_module(design, target_id); + sn_vec_resize(sn_obj_id_t, &source->copy_ids, object_count); + for (size_t i = 0; i < object_count; i++) + sn_vec_at(sn_obj_id_t, &source->copy_ids, i) = SN_INVALID_ID; + + size_t input_count = source->type_objects[SN_PI].size; + for (size_t i = 0; i < input_count; i++) + { + sn_obj_id_t old_object = sn_vec_at(sn_obj_id_t, &order, i); + sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object) = + sn_module_dup_obj_skeleton(target, source, old_object); + } + for (sn_obj_id_t old_object = 0; old_object < object_count; old_object++) + { + if (!const_zero_objects[old_object] || marks[old_object] != SN_TOPO_DONE) + continue; + uint32_t bits = sn_obj_width(source, old_object); + uint32_t* words = (uint32_t*)calloc(sn_const_word_count(bits), sizeof(uint32_t)); + assert(words); + sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object) = + sn_module_add_const(target, bits, sn_obj_is_signed(source, old_object), words, NULL); + free(words); + } + for (size_t i = input_count; i < order.size; i++) + { + sn_obj_id_t old_object = sn_vec_at(sn_obj_id_t, &order, i); + sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object) = + sn_module_dup_obj_skeleton(target, source, old_object); + } + + sn_module_clean_rebuild_pair_ids(target, source, SN_REG_OUT, SN_REG_IN); + sn_module_clean_rebuild_pair_ids(target, source, SN_MEM_OUT, SN_MEM_IN); + sn_module_clean_rebuild_pair_ids(target, source, SN_LOOP_OUT, SN_LOOP_IN); + + for (size_t i = 0; i < order.size; i++) + { + sn_obj_id_t old_object = sn_vec_at(sn_obj_id_t, &order, i); + sn_obj_id_t new_object = sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object); + sn_obj_type_t type = sn_obj_type(source, old_object); + sn_type_id_t target_type_id = sn_obj_type_id(target, new_object); + sn_module_dup_obj_metadata(target, target_type_id, source, old_object); + if (type == SN_FAN) + { + sn_obj_id_t old_inst = sn_fan_inst_id(source, old_object); + sn_vec_at(sn_obj_id_t, &target->fan_insts, target_type_id) = + sn_vec_at(sn_obj_id_t, &source->copy_ids, old_inst); + } + } + + for (size_t i = 0; i < order.size; i++) + { + sn_obj_id_t old_object = sn_vec_at(sn_obj_id_t, &order, i); + sn_obj_id_t new_object = sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object); + for (uint32_t j = 0; j < sn_obj_fanin_count(source, old_object); j++) + { + sn_obj_id_t old_fanin = sn_obj_fanin(source, old_object, j); + sn_obj_id_t new_fanin = old_fanin == SN_INVALID_ID + ? SN_INVALID_ID + : sn_vec_at(sn_obj_id_t, &source->copy_ids, old_fanin); + assert(new_fanin != SN_INVALID_ID || old_fanin == SN_INVALID_ID); + sn_obj_connect(target, new_object, j, new_fanin); + } + } + + source->copy_module = target_id; + sn_vec_destroy(&order); + free(marks); + free(const_zero_objects); + free(const_zero_cache); + assert(sn_module_is_topo(target)); + return target_id; +} + +// Reconstructs one module in place using the observable-cone cleanup above. +// Existing module IDs and interface names remain stable for hierarchical insts. +static inline void sn_design_cleanup_module_topo(sn_design_t* design, sn_module_id_t module_id) +{ + assert(design); + assert(module_id < design->modules.size); + // A loop-breaker can separate output-specific dependencies of a + // multi-output child instance. This local traversal conservatively treats + // every instance output as depending on every input, which can recreate an + // artificial cycle. Preserve such modules with the ordinary topo rebuild. + if (sn_design_get_module_const(design, module_id)->type_objects[SN_LOOP_OUT].size) + { + sn_design_reorder_module_topo(design, module_id); + return; + } + char temporary_name[96]; + uint32_t suffix = 0; + do + { + int length = snprintf(temporary_name, sizeof(temporary_name), "__sn_clean_%u_%u", module_id, suffix++); + assert(length >= 0 && (size_t)length < sizeof(temporary_name)); + assert(suffix != 0); + } while (sn_name_find(&design->names, temporary_name) != SN_INVALID_ID); + + sn_module_t* source = sn_design_get_module(design, module_id); + sn_name_id_t source_name = source->name; + bool interface_locked = source->interface_locked; + sn_module_id_t clean_id = sn_design_dup_module_clean_topo(design, module_id, temporary_name); + sn_module_t* clean = sn_design_get_module(design, clean_id); + sn_name_id_t temporary_name_id = clean->name; + sn_design_invalidate_copies_to_module(design, module_id); + sn_module_destroy(source); + free(source); + clean->id = module_id; + clean->name = source_name; + clean->interface_locked = interface_locked; + sn_vec_at(sn_module_t*, &design->modules, module_id) = clean; + design->modules.size--; + sn_name_remove_last(&design->names, temporary_name_id); + assert(sn_module_is_topo(clean)); +} + +typedef struct sn_collapse_context_t +{ + sn_design_t* design; + sn_module_t* target; + uint8_t* active_modules; + bool preserve_technology_primitives; +} sn_collapse_context_t; + +static inline bool sn_module_is_technology_primitive(const sn_module_t* module) +{ + assert(module); + const char* name = sn_name_get(&module->design->names, module->name); + return strncmp(name, "__sn_RAM", 8) == 0 || strncmp(name, "__sn_URAM", 9) == 0 || + strncmp(name, "__sn_DSP", 8) == 0 || strncmp(name, "__sn_CARRY", 10) == 0; +} + +static inline bool sn_collapse_preserves_object(const sn_collapse_context_t* context, + const sn_module_t* source, sn_obj_id_t object) +{ + if (!context->preserve_technology_primitives) + return false; + sn_obj_type_t type = sn_obj_type(source, object); + sn_obj_id_t inst = type == SN_INST ? object : type == SN_FAN ? sn_fan_inst_id(source, object) + : SN_INVALID_ID; + if (inst == SN_INVALID_ID) + return false; + return sn_module_is_technology_primitive( + sn_design_get_module_const(context->design, sn_inst_module_id(source, inst))); +} + +static inline bool sn_collapse_obj_is_copied(sn_obj_type_t type, bool is_top) +{ + if (type == SN_INST || type == SN_FAN) + return false; + if (!is_top && (type == SN_PI || type == SN_PO)) + return false; + return true; +} + +static inline void sn_module_collapse_into(sn_collapse_context_t* context, sn_module_id_t source_module_id, + const sn_obj_id_t* input_bindings, uint32_t input_count, bool is_top, + sn_vec_t* output_bindings) +{ + assert(context); + assert(source_module_id < context->design->modules.size); + assert(!context->active_modules[source_module_id]); + sn_module_t* source = sn_design_get_module(context->design, source_module_id); + assert(sn_module_is_topo(source)); + assert(is_top || input_count == source->type_objects[SN_PI].size); + assert(is_top || input_count == 0 || input_bindings); + (void)input_count; + context->active_modules[source_module_id] = 1; + + sn_vec_resize(sn_obj_id_t, &source->copy_ids, source->obj_types.size); + for (size_t i = 0; i < source->copy_ids.size; i++) + sn_vec_at(sn_obj_id_t, &source->copy_ids, i) = SN_INVALID_ID; + source->copy_module = context->target->id; + + for (sn_obj_id_t old_object = 0; old_object < source->obj_types.size; old_object++) + { + sn_obj_type_t type = sn_obj_type(source, old_object); + if (type == SN_PI && !is_top) + { + sn_type_id_t port_index = sn_obj_type_id(source, old_object); + assert(port_index < input_count); + sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object) = input_bindings[port_index]; + continue; + } + if (type == SN_PO && !is_top) + { + assert(sn_obj_fanin_count(source, old_object) == 1); + sn_obj_id_t driver = sn_obj_fanin(source, old_object, 0); + assert(driver < source->copy_ids.size); + sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object) = sn_vec_at(sn_obj_id_t, &source->copy_ids, driver); + continue; + } + if (type == SN_FAN) + { + if (sn_collapse_preserves_object(context, source, old_object)) + { + sn_obj_id_t new_object = sn_module_dup_obj_skeleton(context->target, source, old_object); + sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object) = new_object; + sn_obj_id_t old_inst = sn_fan_inst_id(source, old_object); + sn_vec_at(sn_obj_id_t, &context->target->fan_insts, + sn_obj_type_id(context->target, new_object)) = + sn_vec_at(sn_obj_id_t, &source->copy_ids, old_inst); + } + else + assert(sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object) != SN_INVALID_ID); + continue; + } + if (type == SN_INST) + { + if (sn_collapse_preserves_object(context, source, old_object)) + { + sn_obj_id_t new_object = sn_module_dup_obj_skeleton(context->target, source, old_object); + sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object) = new_object; + sn_module_dup_obj_metadata(context->target, sn_obj_type_id(context->target, new_object), + source, old_object); + continue; + } + sn_module_id_t child_id = sn_inst_module_id(source, old_object); + const sn_module_t* child = sn_design_get_module_const(context->design, child_id); + uint32_t child_input_count = (uint32_t)child->type_objects[SN_PI].size; + assert(child_input_count == sn_obj_fanin_count(source, old_object)); + + sn_vec_t child_inputs; + sn_vec_t child_outputs; + sn_vec_init(&child_inputs); + sn_vec_init(&child_outputs); + sn_vec_resize(sn_obj_id_t, &child_inputs, child_input_count); + for (uint32_t i = 0; i < child_input_count; i++) + { + sn_obj_id_t old_fanin = sn_obj_fanin(source, old_object, i); + assert(old_fanin < source->copy_ids.size); + sn_obj_id_t new_fanin = sn_vec_at(sn_obj_id_t, &source->copy_ids, old_fanin); + assert(new_fanin < context->target->obj_types.size); + sn_vec_at(sn_obj_id_t, &child_inputs, i) = new_fanin; + } + + sn_module_collapse_into(context, child_id, sn_vec_data(sn_obj_id_t, &child_inputs), child_input_count, + false, &child_outputs); + uint32_t output_count = sn_design_module_output_count(context->design, child_id); + assert(child_outputs.size == output_count); + if (output_count == 1) + sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object) = sn_vec_at(sn_obj_id_t, &child_outputs, 0); + else + { + for (uint32_t i = 0; i < output_count; i++) + { + sn_obj_id_t fan = sn_inst_output(source, old_object, i); + sn_vec_at(sn_obj_id_t, &source->copy_ids, fan) = sn_vec_at(sn_obj_id_t, &child_outputs, i); + } + } + sn_vec_destroy(&child_outputs); + sn_vec_destroy(&child_inputs); + continue; + } + + sn_obj_id_t new_object = sn_module_dup_obj_skeleton(context->target, source, old_object); + sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object) = new_object; + sn_module_dup_obj_metadata(context->target, sn_obj_type_id(context->target, new_object), source, old_object); + } + + // Patch fanins after every source object has a mapping. This is needed for + // the deliberate forward structural edge from a state OUT to its IN and + // for REG_OUT control fanins which can appear between the pair endpoints. + for (sn_obj_id_t old_object = 0; old_object < source->obj_types.size; old_object++) + { + sn_obj_type_t type = sn_obj_type(source, old_object); + if (!sn_collapse_obj_is_copied(type, is_top) && + !sn_collapse_preserves_object(context, source, old_object)) + continue; + sn_obj_id_t new_object = sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object); + assert(new_object < context->target->obj_types.size); + for (uint32_t i = 0; i < sn_obj_fanin_count(source, old_object); i++) + { + sn_obj_id_t old_fanin = sn_obj_fanin(source, old_object, i); + sn_obj_id_t new_fanin = + old_fanin == SN_INVALID_ID ? SN_INVALID_ID : sn_vec_at(sn_obj_id_t, &source->copy_ids, old_fanin); + assert(new_fanin == SN_INVALID_ID || new_fanin < context->target->obj_types.size); + sn_obj_connect(context->target, new_object, i, new_fanin); + } + } + + if (output_bindings) + { + size_t output_count = source->type_objects[SN_PO].size; + sn_vec_resize(sn_obj_id_t, output_bindings, output_count); + for (size_t i = 0; i < output_count; i++) + { + sn_obj_id_t old_output = sn_vec_at(sn_obj_id_t, &source->type_objects[SN_PO], i); + sn_obj_id_t new_output = sn_vec_at(sn_obj_id_t, &source->copy_ids, old_output); + if (is_top) + new_output = sn_obj_fanin(context->target, new_output, 0); + assert(new_output < context->target->obj_types.size); + sn_vec_at(sn_obj_id_t, output_bindings, i) = new_output; + } + } + context->active_modules[source_module_id] = 0; +} + +static inline void sn_module_rebuild_pair_type_ids(sn_module_t* module, sn_obj_type_t out_type, sn_obj_type_t in_type, + uint32_t pair_fanin_slot) +{ + assert(module); + assert(module->type_objects[out_type].size == module->type_objects[in_type].size); + for (size_t i = 0; i < module->type_objects[out_type].size; i++) + { + sn_obj_id_t out = sn_vec_at(sn_obj_id_t, &module->type_objects[out_type], i); + sn_obj_id_t in = sn_obj_fanin(module, out, pair_fanin_slot); + assert(sn_obj_type(module, in) == in_type); + sn_vec_at(uint32_t, &module->type_ids, out) = (sn_type_id_t)i; + sn_vec_at(uint32_t, &module->type_ids, in) = (sn_type_id_t)i; + sn_vec_at(sn_obj_id_t, &module->type_objects[in_type], i) = in; + } +} + +static inline char* sn_design_flat_module_name(const sn_design_t* design, sn_module_id_t source_module_id) +{ + const sn_module_t* source = sn_design_get_module_const(design, source_module_id); + const char* source_name = sn_name_get(&design->names, source->name); + size_t cap = strlen(source_name) + 32; + char* candidate = (char*)malloc(cap); + assert(candidate); + for (uint32_t suffix = 0;; suffix++) + { + int length = suffix == 0 ? snprintf(candidate, cap, "%s_flat", source_name) + : snprintf(candidate, cap, "%s_flat_%u", source_name, suffix); + assert(length >= 0 && (size_t)length < cap); + (void)length; + bool found = false; + for (size_t i = 0; i < design->modules.size; i++) + { + const sn_module_t* module = sn_design_get_module_const(design, (sn_module_id_t)i); + found = found || strcmp(candidate, sn_name_get(&design->names, module->name)) == 0; + } + if (!found) + return candidate; + assert(suffix != UINT32_MAX); + } +} + +// Flattens the selected top module into a newly created module. Every existing +// design module must already satisfy sn_module_is_topo(). The generated name is +// _flat, with a numeric suffix when needed. +static inline sn_module_id_t sn_design_collapse_module_internal(sn_design_t* design, sn_module_id_t top_module_id, + bool preserve_technology_primitives) +{ + assert(design); + assert(top_module_id < design->modules.size); + bool design_is_topo = sn_design_is_topo(design); + assert(design_is_topo); + (void)design_is_topo; + + size_t source_module_count = design->modules.size; + char* flat_name = sn_design_flat_module_name(design, top_module_id); + sn_module_id_t flat_module_id = sn_design_add_module(design, flat_name); + free(flat_name); + sn_module_t* flat = sn_design_get_module(design, flat_module_id); + + uint8_t* active_modules = NULL; + if (source_module_count) + { + active_modules = (uint8_t*)calloc(source_module_count, sizeof(uint8_t)); + assert(active_modules); + } + sn_collapse_context_t context; + context.design = design; + context.target = flat; + context.active_modules = active_modules; + context.preserve_technology_primitives = preserve_technology_primitives; + sn_module_collapse_into(&context, top_module_id, NULL, 0, true, NULL); + free(active_modules); + + sn_module_rebuild_pair_type_ids(flat, SN_REG_OUT, SN_REG_IN, SN_REG_DATA); + sn_module_rebuild_pair_type_ids(flat, SN_MEM_OUT, SN_MEM_IN, SN_MEM_STATE); + sn_module_rebuild_pair_type_ids(flat, SN_LOOP_OUT, SN_LOOP_IN, 0); + + if (!preserve_technology_primitives) + { + assert(flat->type_objects[SN_INST].size == 0); + assert(flat->type_objects[SN_FAN].size == 0); + } + assert(sn_module_is_topo(flat)); + return flat_module_id; +} + +static inline sn_module_id_t sn_design_collapse_module(sn_design_t* design, sn_module_id_t top_module_id) +{ + return sn_design_collapse_module_internal(design, top_module_id, false); +} + +// Flattens user hierarchy while retaining technology primitive leaf insts. +static inline sn_module_id_t sn_design_collapse_module_tech(sn_design_t* design, sn_module_id_t top_module_id) +{ + return sn_design_collapse_module_internal(design, top_module_id, true); +} + +// The initial Verilog writer is a structural debugging aid. It preserves +// hierarchy and supports ports, insts, constants, common combinational +// operators, slices, and concatenations. It asserts on state and memory +// objects until their exact behavioral Verilog policy is finalized. + +static inline const char* sn_verilog_unary_token(sn_obj_type_t type) +{ + switch (type) + { + case SN_POS: + return "+"; + case SN_NEG: + return "-"; + case SN_BIT_NOT: + return "~"; + case SN_LOG_NOT: + return "!"; + case SN_REDUCE_AND: + return "&"; + case SN_REDUCE_NAND: + return "~&"; + case SN_REDUCE_OR: + return "|"; + case SN_REDUCE_NOR: + return "~|"; + case SN_REDUCE_XOR: + return "^"; + case SN_REDUCE_XNOR: + return "~^"; + default: + return NULL; + } +} + +static inline const char* sn_verilog_binary_token(sn_obj_type_t type) +{ + switch (type) + { + case SN_ADD: + return "+"; + case SN_SUB: + return "-"; + case SN_MUL: + return "*"; + case SN_DIV: + return "/"; + case SN_MOD: + return "%"; + case SN_POW: + return "**"; + case SN_BIT_AND: + return "&"; + case SN_BIT_OR: + return "|"; + case SN_BIT_XOR: + return "^"; + case SN_BIT_XNOR: + return "~^"; + case SN_LOG_AND: + return "&&"; + case SN_LOG_OR: + return "||"; + case SN_EQ: + return "=="; + case SN_NE: + return "!="; + case SN_CASE_EQ: + return "==="; + case SN_CASE_NE: + return "!=="; + case SN_WILDCARD_EQ: + return "==?"; + case SN_WILDCARD_NE: + return "!=?"; + case SN_LT: + return "<"; + case SN_LE: + return "<="; + case SN_GT: + return ">"; + case SN_GE: + return ">="; + case SN_SHL: + return "<<"; + case SN_SHR: + return ">>"; + case SN_ASHL: + return "<<<"; + case SN_ASHR: + return ">>>"; + default: + return NULL; + } +} + +static inline void sn_write_verilog_range(FILE* out, const sn_module_t* module, sn_obj_id_t object) +{ + uint32_t width = sn_obj_width(module, object); + assert(width); + if (sn_obj_is_signed(module, object)) + fputs("signed ", out); + if (width > 1) + fprintf(out, "[%u:0] ", width - 1); +} + +static inline bool sn_verilog_is_keyword(const char* text) +{ + static const char* const keywords[] = { + "accept_on", "alias", "always", "always_comb", "always_ff", "always_latch", "and", "assert", + "assign", "assume", "automatic", "before", "begin", "bind", "bins", "binsof", "bit", "break", + "buf", "bufif0", "bufif1", "byte", "case", "casex", "casez", "cell", "chandle", "checker", "class", + "clocking", "cmos", "config", "const", "constraint", "context", "continue", "cover", "covergroup", + "coverpoint", "cross", "deassign", "default", "defparam", "design", "disable", "dist", "do", "edge", + "else", "end", "endcase", "endchecker", "endclass", "endclocking", "endconfig", "endfunction", + "endgenerate", "endgroup", "endinterface", "endmodule", "endpackage", "endprimitive", "endprogram", + "endproperty", "endsequence", "endspecify", "endtable", "endtask", "enum", "event", "eventually", + "expect", "export", "extends", "extern", "final", "first_match", "for", "force", "foreach", "forever", + "fork", "forkjoin", "function", "generate", "genvar", "global", "highz0", "highz1", "if", "iff", + "ifnone", "ignore_bins", "illegal_bins", "implements", "implies", "import", "incdir", "include", + "initial", "inout", "input", "inside", "instance", "int", "integer", "interconnect", "interface", + "intersect", "join", "join_any", "join_none", "large", "let", "liblist", "library", "local", "localparam", + "logic", "longint", "macromodule", "matches", "medium", "modport", "module", "nand", "negedge", "nettype", + "new", "nmos", "nor", "noshowcancelled", "not", "notif0", "notif1", "null", "or", "output", "package", + "packed", "parameter", "pmos", "posedge", "primitive", "priority", "program", "property", "protected", + "pull0", "pull1", "pulldown", "pullup", "pulsestyle_ondetect", "pulsestyle_onevent", "pure", "rand", + "randc", "randcase", "randsequence", "rcmos", "real", "realtime", "ref", "reg", "reject_on", "release", + "repeat", "restrict", "return", "rnmos", "rpmos", "rtran", "rtranif0", "rtranif1", "s_always", + "s_eventually", "s_nexttime", "s_until", "s_until_with", "scalared", "sequence", "shortint", "shortreal", + "showcancelled", "signed", "small", "soft", "solve", "specify", "specparam", "static", "string", "strong", + "strong0", "strong1", "struct", "super", "supply0", "supply1", "sync_accept_on", "sync_reject_on", + "table", "tagged", "task", "this", "throughout", "time", "timeprecision", "timeunit", "tran", "tranif0", + "tranif1", "tri", "tri0", "tri1", "triand", "trior", "trireg", "type", "typedef", "union", "unique", + "unique0", "unsigned", "untyped", "use", "uwire", "var", "vectored", "virtual", "void", "wait", "wait_order", + "wand", "weak", "weak0", "weak1", "while", "wildcard", "wire", "with", "within", "wor", "xnor", "xor" + }; + for (size_t i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) + if (strcmp(text, keywords[i]) == 0) + return true; + return false; +} + +static inline bool sn_verilog_is_simple_identifier(const char* text) +{ + assert(text && text[0]); + unsigned char first = (unsigned char)text[0]; + if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_')) + return false; + for (size_t i = 1; text[i]; i++) + { + unsigned char c = (unsigned char)text[i]; + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || + c == '$')) + return false; + } + return !sn_verilog_is_keyword(text); +} + +// Escaped Verilog identifiers terminate at whitespace. SN names containing whitespace or a backslash cannot be emitted +// losslessly and are rejected as construction errors; all other non-simple names and language keywords are escaped. +static inline void sn_write_verilog_identifier(FILE* out, const char* text) +{ + assert(out && text && text[0]); + if (sn_verilog_is_simple_identifier(text)) + { + fputs(text, out); + return; + } + for (size_t i = 0; text[i]; i++) + assert((unsigned char)text[i] > 32 && (unsigned char)text[i] < 127 && text[i] != '\\'); + fputc('\\', out); + fputs(text, out); + fputc(' ', out); +} + +// Internal names retain the compact historical spelling unless a user name collides with it. The fallback includes the +// module, object, and object role and is checked against the global name manager as well. +static inline void sn_write_verilog_generated_name(FILE* out, const sn_module_t* module, const char* role, + sn_obj_id_t object) +{ + assert(out && module && role); + char name[160]; + if (strcmp(role, "obj") == 0) + snprintf(name, sizeof(name), "_sn_%u", object); + else + snprintf(name, sizeof(name), "_sn_%s_%u", role, object); + if (sn_name_find(&module->design->names, name) == SN_INVALID_ID) + { + fputs(name, out); + return; + } + uint32_t suffix = 0; + do + { + int count = snprintf(name, sizeof(name), "__sn_generated_%u_%s_%u_%u", module->id, role, object, suffix++); + assert(count > 0 && (size_t)count < sizeof(name)); + (void)count; + } while (sn_name_find(&module->design->names, name) != SN_INVALID_ID); + fputs(name, out); +} + +static inline bool sn_const_bit(const sn_module_t* module, sn_obj_id_t object, uint32_t bit) +{ + assert(bit < sn_obj_width(module, object)); + sn_obj_type_t type = sn_obj_type(module, object); + if (type == SN_CONST0) + return false; + if (type == SN_CONST1) + return bit == 0; + assert(type == SN_CONST); + return (sn_const_words(module, object)[bit / 32] >> (bit % 32)) & 1u; +} + +static inline void sn_write_verilog_constant_chunk(FILE* out, const sn_module_t* module, sn_obj_id_t object, + uint32_t offset, uint32_t width) +{ + fprintf(out, "%u'h", width); + uint32_t nibble_count = (width + 3) / 4; + for (uint32_t nibble = nibble_count; nibble > 0; nibble--) + { + uint32_t digit = 0; + for (uint32_t bit = 0; bit < 4; bit++) + { + uint32_t index = (nibble - 1) * 4 + bit; + if (index < width && sn_const_bit(module, object, offset + index)) + digit |= 1u << bit; + } + fputc("0123456789abcdef"[digit], out); + } +} + +static inline void sn_write_verilog_constant(FILE* out, const sn_module_t* module, sn_obj_id_t object) +{ + uint32_t width = sn_obj_width(module, object); + sn_obj_type_t type = sn_obj_type(module, object); + if (type == SN_CONST0 || type == SN_CONST1) + { + fprintf(out, "%u", width); + fputc(39, out); + if (sn_obj_is_signed(module, object)) + fputc(115, out); + fprintf(out, "d%u", type == SN_CONST1 ? 1u : 0u); + return; + } + assert(type == SN_CONST); + if (width <= 4096) + { + fprintf(out, "%u", width); + fputc(39, out); + if (sn_obj_is_signed(module, object)) + fputc(115, out); + fputc(104, out); + const uint32_t* words = sn_const_words(module, object); + uint32_t count = sn_const_word_count(width); + fprintf(out, "%x", words[count - 1]); + while (--count) + fprintf(out, "%08x", words[count - 1]); + return; + } + + if (sn_obj_is_signed(module, object)) + fputs("$signed(", out); + fputs("{\n", out); + uint32_t remaining = width; + while (remaining) + { + uint32_t chunk_width = remaining > 1024 ? 1024 : remaining; + uint32_t offset = remaining - chunk_width; + fputs(" ", out); + sn_write_verilog_constant_chunk(out, module, object, offset, chunk_width); + remaining = offset; + fputs(remaining ? ",\n" : "\n", out); + } + fputc(125, out); + if (sn_obj_is_signed(module, object)) + fputc(41, out); +} + +static inline void sn_write_verilog_ref(FILE* out, const sn_module_t* module, sn_obj_id_t object) +{ + assert(object < module->obj_types.size); + sn_obj_type_t type = sn_obj_type(module, object); + if (type == SN_PI || type == SN_PO) + sn_write_verilog_identifier(out, sn_obj_name(module, object)); + else if (type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST) + sn_write_verilog_constant(out, module, object); + else + sn_write_verilog_generated_name(out, module, "obj", object); +} + +static inline void sn_write_verilog_constant_slice(FILE* out, const sn_module_t* module, sn_obj_id_t object, + const sn_slice_info_t* info, uint32_t width) +{ + assert(info); + assert(info->left_index >= 0 && info->right_index >= 0); + assert((uint32_t)info->left_index < sn_obj_width(module, object)); + assert((uint32_t)info->right_index < sn_obj_width(module, object)); + fprintf(out, "%u'h", width); + uint32_t nibble_count = (width + 3) / 4; + for (uint32_t nibble = nibble_count; nibble > 0; nibble--) + { + uint32_t digit = 0; + for (uint32_t offset = 0; offset < 4; offset++) + { + uint32_t result_bit = (nibble - 1) * 4 + offset; + if (result_bit >= width) + continue; + int64_t source_bit = info->left_index >= info->right_index ? (int64_t)info->right_index + result_bit + : (int64_t)info->right_index - result_bit; + assert(source_bit >= 0 && (uint64_t)source_bit < sn_obj_width(module, object)); + digit |= (uint32_t)sn_const_bit(module, object, (uint32_t)source_bit) << offset; + } + fputc("0123456789abcdef"[digit], out); + } +} + +static inline void sn_write_verilog_expression(FILE* out, const sn_module_t* module, sn_obj_id_t object) +{ + sn_obj_type_t type = sn_obj_type(module, object); + const char* unary_token = sn_verilog_unary_token(type); + if (unary_token) + { + assert(sn_obj_fanin_count(module, object) == 1); + fputc(40, out); + fputs(unary_token, out); + sn_write_verilog_ref(out, module, sn_obj_fanin(module, object, 0)); + fputc(41, out); + return; + } + const char* token = sn_verilog_binary_token(type); + if (token) + { + assert(sn_obj_fanin_count(module, object) == 2); + fputc(40, out); + sn_write_verilog_ref(out, module, sn_obj_fanin(module, object, 0)); + fprintf(out, " %s ", token); + sn_write_verilog_ref(out, module, sn_obj_fanin(module, object, 1)); + fputc(41, out); + return; + } + if (type == SN_BUF) + { + assert(sn_obj_fanin_count(module, object) == 1); + sn_write_verilog_ref(out, module, sn_obj_fanin(module, object, 0)); + return; + } + if (type == SN_CAST) + { + assert(sn_obj_fanin_count(module, object) == 1); + fputs(sn_obj_is_signed(module, object) ? "$signed(" : "$unsigned(", out); + sn_write_verilog_ref(out, module, sn_obj_fanin(module, object, 0)); + fputc(41, out); + return; + } + if (type == SN_MUX) + { + assert(sn_obj_fanin_count(module, object) == SN_MUX_FANIN_COUNT); + fputc(40, out); + sn_write_verilog_ref(out, module, sn_obj_fanin(module, object, SN_MUX_SELECT)); + fputs(" ? ", out); + sn_write_verilog_ref(out, module, sn_obj_fanin(module, object, SN_MUX_SELECTED)); + fputs(" : ", out); + sn_write_verilog_ref(out, module, sn_obj_fanin(module, object, SN_MUX_DEFAULT)); + fputc(41, out); + return; + } + if (type == SN_BMUX) + { + assert(sn_obj_fanin_count(module, object) == SN_BMUX_FANIN_COUNT); + fputc(40, out); + sn_write_verilog_ref(out, module, sn_obj_fanin(module, object, SN_BMUX_ALTERNATIVES)); + fputs(" >> (", out); + sn_write_verilog_ref(out, module, sn_obj_fanin(module, object, SN_BMUX_SELECT)); + fprintf(out, " * %u))", sn_obj_width(module, object)); + return; + } + if (type == SN_PMUX) + { + assert(sn_obj_fanin_count(module, object) == SN_PMUX_FANIN_COUNT); + sn_obj_id_t select = sn_obj_fanin(module, object, SN_PMUX_SELECT); + sn_obj_id_t alternatives = sn_obj_fanin(module, object, SN_PMUX_ALTERNATIVES); + uint32_t select_width = sn_obj_width(module, select); + uint32_t output_width = sn_obj_width(module, object); + for (uint32_t i = 0; i < select_width; i++) + { + fputs("(((", out); + sn_write_verilog_ref(out, module, select); + fprintf(out, " >> %u) & 1'd1) ? (", i); + sn_write_verilog_ref(out, module, alternatives); + fprintf(out, " >> %u) : ", i * output_width); + } + sn_write_verilog_ref(out, module, sn_obj_fanin(module, object, SN_PMUX_DEFAULT)); + for (uint32_t i = 0; i < select_width; i++) + fputc(41, out); + return; + } + if (type == SN_CONCAT) + { + fputc(123, out); + uint32_t count = sn_obj_fanin_count(module, object); + for (uint32_t i = count; i > 0; i--) + { + if (i != count) + fputs(", ", out); + sn_write_verilog_ref(out, module, sn_obj_fanin(module, object, i - 1)); + } + fputc(125, out); + return; + } + if (type == SN_REPLICATE) + { + assert(sn_obj_fanin_count(module, object) == 1); + fputc(123, out); + fprintf(out, "%u", sn_obj_repeat_count(module, object)); + fputc(123, out); + sn_write_verilog_ref(out, module, sn_obj_fanin(module, object, 0)); + fputc(125, out); + fputc(125, out); + return; + } + if (type == SN_SLICE) + { + sn_obj_id_t value = sn_obj_fanin(module, object, 0); + const sn_slice_info_t* info = sn_obj_slice_info(module, object); + sn_obj_type_t value_type = sn_obj_type(module, value); + if (value_type == SN_CONST0 || value_type == SN_CONST1 || value_type == SN_CONST) + { + sn_write_verilog_constant_slice(out, module, value, info, sn_obj_width(module, object)); + return; + } + if (sn_obj_width(module, value) == 1 && info->left_index == 0 && info->right_index == 0) + { + sn_write_verilog_ref(out, module, value); + return; + } + if (info->left_index >= info->right_index) + { + sn_write_verilog_ref(out, module, value); + fprintf(out, "[%d:%d]", info->left_index, info->right_index); + } + else + { + // SN values use LSB-first significance order. For an ascending slice, result bit 0 is value[right], + // which cannot be expressed as an ascending part-select of SN's normalized [width-1:0] wires. + fputc('{', out); + for (int64_t index = info->left_index; index <= info->right_index; index++) + { + if (index != info->left_index) + fputs(", ", out); + sn_write_verilog_ref(out, module, value); + fprintf(out, "[%lld]", (long long)index); + } + fputc('}', out); + } + return; + } + assert(false); +} + +static inline sn_obj_id_t sn_module_find_named_type_object(const sn_module_t* module, sn_obj_type_t type, + sn_name_id_t name) +{ + if (name == SN_INVALID_ID) + return SN_INVALID_ID; + for (size_t i = 0; i < module->type_objects[type].size; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &module->type_objects[type], i); + if (sn_obj_name_id(module, object) == name) + return object; + } + return SN_INVALID_ID; +} + +static inline void sn_write_verilog_inst(FILE* out, const sn_module_t* module, sn_obj_id_t inst) +{ + sn_module_id_t child_id = sn_inst_module_id(module, inst); + const sn_module_t* child = sn_design_get_module_const(module->design, child_id); + assert(sn_obj_fanin_count(module, inst) == child->type_objects[SN_PI].size); + fputs(" ", out); + sn_write_verilog_identifier(out, sn_name_get(&module->design->names, child->name)); + fputc(' ', out); + sn_write_verilog_generated_name(out, module, "inst", inst); + fputs(" (", out); + fputc(10, out); + size_t connection = 0; + size_t inout_count = 0; + for (size_t i = 0; i < child->type_objects[SN_PI].size; i++) + { + sn_obj_id_t port = sn_vec_at(sn_obj_id_t, &child->type_objects[SN_PI], i); + inout_count += sn_module_find_named_type_object(child, SN_PO, sn_obj_name_id(child, port)) != SN_INVALID_ID; + } + size_t connection_count = child->type_objects[SN_PI].size + child->type_objects[SN_PO].size - inout_count; + for (size_t i = 0; i < child->type_objects[SN_PI].size; i++, connection++) + { + sn_obj_id_t port = sn_vec_at(sn_obj_id_t, &child->type_objects[SN_PI], i); + sn_obj_id_t output_port = sn_module_find_named_type_object(child, SN_PO, sn_obj_name_id(child, port)); + fputs(" .", out); + sn_write_verilog_identifier(out, sn_obj_name(child, port)); + fputc('(', out); + if (output_port == SN_INVALID_ID) + sn_write_verilog_ref(out, module, sn_obj_fanin(module, inst, (uint32_t)i)); + else + sn_write_verilog_ref(out, module, + sn_inst_output(module, inst, sn_obj_type_id(child, output_port))); + fprintf(out, ")%s", connection + 1 == connection_count ? "" : ","); + fputc(10, out); + } + for (size_t i = 0; i < child->type_objects[SN_PO].size; i++) + { + sn_obj_id_t port = sn_vec_at(sn_obj_id_t, &child->type_objects[SN_PO], i); + if (sn_module_find_named_type_object(child, SN_PI, sn_obj_name_id(child, port)) != SN_INVALID_ID) + continue; + fputs(" .", out); + sn_write_verilog_identifier(out, sn_obj_name(child, port)); + fputc('(', out); + sn_write_verilog_ref(out, module, sn_inst_output(module, inst, (uint32_t)i)); + fprintf(out, ")%s", connection + 1 == connection_count ? "" : ","); + fputc(10, out); + connection++; + } + fputs(" );", out); + fputc(10, out); + for (size_t i = 0; i < child->type_objects[SN_PI].size; i++) + { + sn_obj_id_t port = sn_vec_at(sn_obj_id_t, &child->type_objects[SN_PI], i); + sn_obj_id_t output_port = sn_module_find_named_type_object(child, SN_PO, sn_obj_name_id(child, port)); + if (output_port == SN_INVALID_ID) + continue; + fputs(" assign ", out); + sn_write_verilog_ref(out, module, + sn_inst_output(module, inst, sn_obj_type_id(child, output_port))); + fputs(" = ", out); + sn_write_verilog_ref(out, module, sn_obj_fanin(module, inst, (uint32_t)i)); + fputs(";\n", out); + } +} + +static inline void sn_write_verilog_lut(FILE* out, const sn_module_t* module, sn_obj_id_t object) +{ + assert(sn_obj_type(module, object) == SN_LUT); + uint32_t count = sn_obj_fanin_count(module, object); + uint64_t truth = sn_obj_lut_truth(module, object); + if (count == 0) + { + fputs(" assign ", out); + sn_write_verilog_generated_name(out, module, "obj", object); + fprintf(out, " = 1'b%u;\n", (unsigned)(truth & 1)); + return; + } + uint32_t truth_bits = UINT32_C(1) << count; + uint32_t hex_digits = (truth_bits + 3) / 4; + // A variable bit-select is portable synthesizable Verilog and does not require vendor LUT simulation models. + // Fanin 0 is the least-significant truth-table index bit, matching the SN_LUT convention. + fputs(" assign ", out); + sn_write_verilog_generated_name(out, module, "obj", object); + fprintf(out, " = %u'h%0*llx >> {", truth_bits, (int)hex_digits, (unsigned long long)truth); + for (uint32_t i = count; i-- > 0; ) + { + sn_write_verilog_ref(out, module, sn_obj_fanin(module, object, i)); + if (i) + fputs(", ", out); + } + fputs("};\n", out); +} + +// SN_GATE names created by ABC reconstruction are the current library cell names. Mini-mapping orders each cell's +// fanins in library pin order. Structural Verilog uses the conventional output-first positional cell interface so it +// remains independent of ABC's in-memory Mio pin objects after the SN design has been serialized. +static inline void sn_write_verilog_gate(FILE* out, const sn_module_t* module, sn_obj_id_t object) +{ + assert(sn_obj_type(module, object) == SN_GATE); + const char* gate_name = sn_obj_name(module, object); + assert(gate_name); + fputs(" ", out); + sn_write_verilog_identifier(out, gate_name); + fputc(' ', out); + sn_write_verilog_generated_name(out, module, "gate", object); + fputs(" (", out); + sn_write_verilog_generated_name(out, module, "obj", object); + for (uint32_t i = 0; i < sn_obj_fanin_count(module, object); i++) + { + fputs(", ", out); + sn_write_verilog_ref(out, module, sn_obj_fanin(module, object, i)); + } + fputs(");\n", out); +} + +static inline void sn_write_verilog_active_control(FILE* out, const sn_module_t* module, sn_obj_id_t control, + bool active_low) +{ + if (active_low) + fputc('!', out); + sn_write_verilog_ref(out, module, control); +} + +static inline void sn_write_verilog_register(FILE* out, const sn_module_t* module, sn_obj_id_t reg_out) +{ + assert(sn_obj_type(module, reg_out) == SN_REG_OUT); + uint32_t flags = sn_obj_reg_flags(module, reg_out); + sn_obj_id_t clock = sn_obj_fanin(module, reg_out, SN_REG_CLOCK); + sn_obj_id_t enable = sn_obj_fanin(module, reg_out, SN_REG_ENABLE); + sn_obj_id_t set = sn_obj_fanin(module, reg_out, SN_REG_SET); + sn_obj_id_t reset = sn_obj_fanin(module, reg_out, SN_REG_RESET); + sn_obj_id_t initial_value = sn_obj_reg_init_data(module, reg_out); + sn_obj_id_t initial_mask = sn_obj_reg_init_mask(module, reg_out); + sn_obj_id_t reset_value = sn_obj_fanin(module, reg_out, SN_REG_RESET_VALUE); + sn_obj_id_t reg_in = sn_obj_pair_in(module, reg_out); + sn_obj_id_t data = sn_obj_fanin(module, reg_in, 0); + assert(data != SN_INVALID_ID); + assert((flags & SN_REG_LATCH) ? clock == SN_INVALID_ID : clock != SN_INVALID_ID); + + if (initial_value != SN_INVALID_ID) + { + fputs(" initial begin\n ", out); + sn_write_verilog_ref(out, module, reg_out); + fputs(" = ", out); + if (initial_mask == SN_INVALID_ID) + sn_write_verilog_ref(out, module, initial_value); + else + { + uint32_t width = sn_obj_width(module, reg_out); + fprintf(out, "%u'b", width); + for (uint32_t bit = width; bit-- > 0;) + fputc(!sn_const_bit(module, initial_mask, bit) ? 'x' + : sn_const_bit(module, initial_value, bit) ? '1' + : '0', + out); + } + fputs(";\n end\n", out); + } + + if (flags & SN_REG_LATCH) + { + assert(enable != SN_INVALID_ID && set == SN_INVALID_ID && reset == SN_INVALID_ID); + fputs(" always @* begin\n if (", out); + sn_write_verilog_ref(out, module, enable); + fputs(") ", out); + sn_write_verilog_ref(out, module, reg_out); + fputs(" <= ", out); + sn_write_verilog_ref(out, module, data); + fputs(";\n end\n", out); + return; + } + + fputs(" always @(", out); + fputs(flags & SN_REG_CLOCK_NEGEDGE ? "negedge " : "posedge ", out); + sn_write_verilog_ref(out, module, clock); + if (reset != SN_INVALID_ID && (flags & SN_REG_RESET_ASYNC)) + { + fputs(flags & SN_REG_RESET_NEGEDGE ? " or negedge " : " or posedge ", out); + sn_write_verilog_ref(out, module, reset); + } + if (set != SN_INVALID_ID && (flags & SN_REG_SET_ASYNC)) + { + fputs(flags & SN_REG_SET_NEGEDGE ? " or negedge " : " or posedge ", out); + sn_write_verilog_ref(out, module, set); + } + fputs(") begin\n", out); + + bool has_condition = false; + if (reset != SN_INVALID_ID) + { + fputs(" if (", out); + sn_write_verilog_active_control(out, module, reset, flags & SN_REG_RESET_NEGEDGE); + fputs(") ", out); + sn_write_verilog_ref(out, module, reg_out); + fputs(" <= ", out); + if (reset_value == SN_INVALID_ID) + fprintf(out, "%u'd0", sn_obj_width(module, reg_out)); + else + sn_write_verilog_ref(out, module, reset_value); + fputs(";\n", out); + has_condition = true; + } + if (set != SN_INVALID_ID) + { + fputs(has_condition ? " else if (" : " if (", out); + sn_write_verilog_active_control(out, module, set, flags & SN_REG_SET_NEGEDGE); + fputs(") ", out); + sn_write_verilog_ref(out, module, reg_out); + fprintf(out, " <= {%u{1'b1}};\n", sn_obj_width(module, reg_out)); + has_condition = true; + } + if (enable != SN_INVALID_ID) + { + fputs(has_condition ? " else if (" : " if (", out); + sn_write_verilog_ref(out, module, enable); + fputs(") ", out); + sn_write_verilog_ref(out, module, reg_out); + fputs(" <= ", out); + sn_write_verilog_ref(out, module, data); + fputs(";\n", out); + } + else + { + fputs(has_condition ? " else " : " ", out); + sn_write_verilog_ref(out, module, reg_out); + fputs(" <= ", out); + sn_write_verilog_ref(out, module, data); + fputs(";\n", out); + } + fputs(" end\n", out); +} + +static inline void sn_write_verilog_memory_read(FILE* out, const sn_module_t* module, sn_obj_id_t read) +{ + assert(sn_obj_type(module, read) == SN_MEM_READ); + sn_obj_id_t memory = sn_obj_fanin(module, read, SN_MEM_READ_MEMORY); + sn_obj_id_t clock = sn_obj_fanin(module, read, SN_MEM_READ_CLOCK); + sn_obj_id_t enable = sn_obj_fanin(module, read, SN_MEM_READ_ENABLE); + sn_obj_id_t address = sn_obj_fanin(module, read, SN_MEM_READ_ADDRESS); + assert(sn_obj_type(module, memory) == SN_MEM_OUT); + assert(clock != SN_INVALID_ID || enable == SN_INVALID_ID); + if (clock == SN_INVALID_ID) + fputs(" assign ", out); + else + { + fputs(" always @(posedge ", out); + sn_write_verilog_ref(out, module, clock); + fputs(") begin\n ", out); + if (enable != SN_INVALID_ID) + { + fputs("if (", out); + sn_write_verilog_ref(out, module, enable); + fputs(") ", out); + } + } + sn_write_verilog_ref(out, module, read); + fputs(clock == SN_INVALID_ID ? " = " : " <= ", out); + sn_write_verilog_generated_name(out, module, "mem", memory); + fputc('[', out); + sn_write_verilog_ref(out, module, address); + fputs(clock == SN_INVALID_ID ? "];\n" : "];\n end\n", out); +} + +static inline void sn_write_verilog_memory_init(FILE* out, const sn_module_t* module, sn_obj_id_t memory) +{ + assert(sn_obj_type(module, memory) == SN_MEM_OUT); + sn_obj_id_t data = sn_obj_mem_init_data(module, memory); + sn_obj_id_t mask = sn_obj_mem_init_mask(module, memory); + if (data == SN_INVALID_ID) + return; + + uint32_t width = sn_obj_width(module, memory); + uint32_t depth = sn_obj_mem_depth(module, memory); + fputs(" initial begin\n", out); + for (uint32_t entry = 0; entry < depth; entry++) + { + uint64_t offset = (uint64_t)entry * width; + bool has_valid_bit = mask == SN_INVALID_ID; + if (mask != SN_INVALID_ID) + for (uint32_t bit = 0; bit < width; bit++) + has_valid_bit = has_valid_bit || sn_const_bit(module, mask, (uint32_t)offset + bit); + if (!has_valid_bit) + continue; + + fputs(" ", out); + sn_write_verilog_generated_name(out, module, "mem", memory); + fprintf(out, "[%u] = %u'b", entry, width); + for (uint32_t bit = width; bit-- > 0;) + { + uint32_t init_bit = (uint32_t)offset + bit; + if (mask != SN_INVALID_ID && !sn_const_bit(module, mask, init_bit)) + fputc('x', out); + else + fputc(sn_const_bit(module, data, init_bit) ? '1' : '0', out); + } + fputs(";\n", out); + } + fputs(" end\n", out); +} + +static inline void sn_write_verilog_memory_write(FILE* out, const sn_module_t* module, sn_obj_id_t write, + sn_obj_id_t memory) +{ + assert(sn_obj_type(module, write) == SN_MEM_WRITE); + assert(sn_obj_type(module, memory) == SN_MEM_OUT); + sn_obj_id_t clock = sn_obj_fanin(module, write, SN_MEM_WRITE_CLOCK); + sn_obj_id_t enable = sn_obj_fanin(module, write, SN_MEM_WRITE_ENABLE); + sn_obj_id_t data = sn_obj_fanin(module, write, SN_MEM_WRITE_DATA); + sn_obj_id_t address = sn_obj_fanin(module, write, SN_MEM_WRITE_ADDRESS); + assert(clock != SN_INVALID_ID && data != SN_INVALID_ID && address != SN_INVALID_ID); + fputs(" always @(posedge ", out); + sn_write_verilog_ref(out, module, clock); + fputs(") begin\n", out); + if (enable != SN_INVALID_ID) + { + fputs(" if (", out); + sn_write_verilog_ref(out, module, enable); + fputs(")\n ", out); + } + else + fputs(" ", out); + sn_write_verilog_generated_name(out, module, "mem", memory); + fputc('[', out); + sn_write_verilog_ref(out, module, address); + fputs("] <= ", out); + sn_write_verilog_ref(out, module, data); + fputs(";\n end\n", out); +} + +static inline void sn_module_write_verilog_as(FILE* out, const sn_module_t* module, const char* emitted_name) +{ + assert(out); + assert(module); + assert(emitted_name); + size_t write_count = module->type_objects[SN_MEM_WRITE].size; + sn_obj_id_t* write_memories = write_count ? (sn_obj_id_t*)malloc(write_count * sizeof(sn_obj_id_t)) : NULL; + assert(write_memories || !write_count); + for (size_t i = 0; i < write_count; i++) + write_memories[i] = SN_INVALID_ID; + for (size_t i = 0; i < module->type_objects[SN_MEM_IN].size; i++) + { + sn_obj_id_t mem_in = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_MEM_IN], i); + sn_obj_id_t memory = sn_obj_pair_out(module, mem_in); + for (uint32_t j = 0; j < sn_obj_fanin_count(module, mem_in); j++) + { + sn_obj_id_t write = sn_obj_fanin(module, mem_in, j); + assert(sn_obj_type(module, write) == SN_MEM_WRITE); + uint32_t write_id = sn_obj_type_id(module, write); + assert(write_id < write_count && write_memories[write_id] == SN_INVALID_ID); + write_memories[write_id] = memory; + } + } + fputs("module ", out); + sn_write_verilog_identifier(out, emitted_name); + fputs(" (", out); + fputc(10, out); + size_t inout_count = 0; + for (size_t i = 0; i < module->type_objects[SN_PI].size; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], i); + inout_count += + sn_module_find_named_type_object(module, SN_PO, sn_obj_name_id(module, object)) != SN_INVALID_ID; + } + size_t port_count = module->type_objects[SN_PI].size + module->type_objects[SN_PO].size - inout_count; + size_t port_index = 0; + for (uint32_t type = SN_PI; type <= SN_PO; type++) + for (size_t i = 0; i < module->type_objects[type].size; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &module->type_objects[type], i); + if (type == SN_PO && + sn_module_find_named_type_object(module, SN_PI, sn_obj_name_id(module, object)) != SN_INVALID_ID) + continue; + fputs(" ", out); + sn_write_verilog_identifier(out, sn_obj_name(module, object)); + fputs(port_index + 1 == port_count ? "" : ",", out); + fputc(10, out); + port_index++; + } + fputs(");", out); + fputc(10, out); + + for (uint32_t type = SN_PI; type <= SN_PO; type++) + for (size_t i = 0; i < module->type_objects[type].size; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &module->type_objects[type], i); + sn_obj_id_t matching = sn_module_find_named_type_object( + module, type == SN_PI ? SN_PO : SN_PI, sn_obj_name_id(module, object)); + if (type == SN_PO && matching != SN_INVALID_ID) + continue; + fprintf(out, " %s wire ", matching != SN_INVALID_ID ? "inout" : type == SN_PI ? "input" : "output"); + sn_write_verilog_range(out, module, object); + sn_write_verilog_identifier(out, sn_obj_name(module, object)); + fputc(';', out); + fputc(10, out); + } + + for (sn_obj_id_t object = 0; object < module->obj_types.size; object++) + { + sn_obj_type_t type = sn_obj_type(module, object); + if (type == SN_PI || type == SN_PO || type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST) + continue; + if (type == SN_INST) + { + sn_module_id_t child_id = sn_inst_module_id(module, object); + if (sn_design_module_output_count(module->design, child_id) > 1) + continue; + } + if (type == SN_MEM_OUT) + { + fputs(" reg ", out); + sn_write_verilog_range(out, module, object); + sn_write_verilog_generated_name(out, module, "mem", object); + fprintf(out, " [0:%u];", sn_obj_mem_depth(module, object) - 1); + fputc(10, out); + continue; + } + if (type == SN_MEM_IN || type == SN_MEM_WRITE) + continue; + bool procedural = type == SN_REG_OUT || + (type == SN_MEM_READ && sn_obj_fanin(module, object, SN_MEM_READ_CLOCK) != SN_INVALID_ID); + fputs(procedural ? " reg " : " wire ", out); + sn_write_verilog_range(out, module, object); + sn_write_verilog_generated_name(out, module, "obj", object); + fputc(';', out); + sn_name_id_t name = sn_obj_name_id(module, object); + if (name != SN_INVALID_ID) + fprintf(out, " // %s", sn_name_get(&module->design->names, name)); + fputc(10, out); + } + fputc(10, out); + + for (size_t i = 0; i < module->type_objects[SN_MEM_OUT].size; i++) + { + sn_obj_id_t memory = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_MEM_OUT], i); + sn_write_verilog_memory_init(out, module, memory); + } + + for (sn_obj_id_t object = 0; object < module->obj_types.size; object++) + { + sn_obj_type_t type = sn_obj_type(module, object); + if (type == SN_PO) + { + fputs(" assign ", out); + sn_write_verilog_ref(out, module, object); + fputs(" = ", out); + sn_write_verilog_ref(out, module, sn_obj_fanin(module, object, 0)); + fputs(";", out); + fputc(10, out); + } + else if (type == SN_INST) + sn_write_verilog_inst(out, module, object); + else if (type == SN_REG_OUT) + sn_write_verilog_register(out, module, object); + else if (type == SN_REG_IN) + { + fputs(" assign ", out); + sn_write_verilog_ref(out, module, object); + fputs(" = ", out); + sn_write_verilog_ref(out, module, sn_obj_fanin(module, object, 0)); + fputs(";\n", out); + } + else if (type == SN_LOOP_OUT || type == SN_LOOP_IN) + { + // Loop pairs are transparent buffers. Their OUT half is treated as a source only by graph traversal. + fputs(" assign ", out); + sn_write_verilog_ref(out, module, object); + fputs(" = ", out); + sn_write_verilog_ref(out, module, sn_obj_fanin(module, object, 0)); + fputs(";\n", out); + } + else if (type == SN_MEM_READ) + sn_write_verilog_memory_read(out, module, object); + else if (type == SN_MEM_WRITE) + { + sn_obj_id_t memory = write_memories[sn_obj_type_id(module, object)]; + assert(memory != SN_INVALID_ID); + sn_write_verilog_memory_write(out, module, object, memory); + } + else if (type == SN_LUT) + sn_write_verilog_lut(out, module, object); + else if (type == SN_GATE) + sn_write_verilog_gate(out, module, object); + else if (sn_obj_type_is_operator(type)) + { + fputs(" assign ", out); + sn_write_verilog_ref(out, module, object); + fputs(" = ", out); + sn_write_verilog_expression(out, module, object); + fputs(";", out); + fputc(10, out); + } + } + fputs("endmodule", out); + fputc(10, out); + fputc(10, out); + free(write_memories); +} + +static inline void sn_module_write_verilog(FILE* out, const sn_module_t* module) +{ + assert(module); + sn_module_write_verilog_as(out, module, sn_name_get(&module->design->names, module->name)); +} + +static inline void sn_design_write_module_verilog(FILE* out, const sn_design_t* design, sn_module_id_t module_id, + const char* emitted_name) +{ + assert(out); + assert(design); + assert(module_id < design->modules.size); + const sn_module_t* module = sn_design_get_module_const(design, module_id); + sn_module_write_verilog_as(out, module, + emitted_name ? emitted_name : sn_name_get(&design->names, module->name)); +} + +static inline void sn_design_write_module_verilog_file(const sn_design_t* design, sn_module_id_t module_id, + const char* emitted_name, const char* path) +{ + assert(path); + FILE* out = fopen(path, "w"); + assert(out); + sn_design_write_module_verilog(out, design, module_id, emitted_name); + fclose(out); +} + +static inline void sn_design_write_module_verilog_deps_rec(FILE* out, const sn_design_t* design, + sn_module_id_t module_id, sn_module_id_t root, + bool* active, bool* written) +{ + assert(out && design && module_id < design->modules.size && active && written); + if (written[module_id]) + return; + assert(!active[module_id]); + active[module_id] = true; + const sn_module_t* module = sn_design_get_module_const(design, module_id); + for (size_t i = 0; i < module->inst_modules.size; i++) + sn_design_write_module_verilog_deps_rec( + out, design, sn_vec_at(sn_module_id_t, &module->inst_modules, i), root, active, written); + active[module_id] = false; + written[module_id] = true; + if (module_id != root) + sn_module_write_verilog(out, module); +} + +// Writes one selected module plus only the module definitions reachable from +// its insts. Dependencies precede the selected module, whose emitted name +// may differ from its internal SN name. This is useful for a collapsed design +// that intentionally retains technology primitive insts. +static inline void sn_design_write_module_verilog_with_deps(FILE* out, const sn_design_t* design, + sn_module_id_t module_id, const char* emitted_name) +{ + assert(out && design && module_id < design->modules.size); + bool* active = (bool*)calloc(design->modules.size, sizeof(bool)); + bool* written = (bool*)calloc(design->modules.size, sizeof(bool)); + assert(active && written); + sn_design_write_module_verilog_deps_rec(out, design, module_id, module_id, active, written); + sn_design_write_module_verilog(out, design, module_id, emitted_name); + free(active); + free(written); +} + +static inline void sn_design_write_module_verilog_with_deps_file(const sn_design_t* design, + sn_module_id_t module_id, + const char* emitted_name, const char* path) +{ + assert(path); + FILE* out = fopen(path, "w"); + assert(out); + sn_design_write_module_verilog_with_deps(out, design, module_id, emitted_name); + fclose(out); +} + +static inline void sn_design_write_verilog(FILE* out, const sn_design_t* design) +{ + assert(out); + assert(design); + for (size_t i = 0; i < design->modules.size; i++) + sn_module_write_verilog(out, sn_design_get_module_const(design, (sn_module_id_t)i)); +} + +static inline void sn_design_write_verilog_file(const sn_design_t* design, const char* path) +{ + assert(design); + assert(path); + FILE* out = fopen(path, "w"); + assert(out); + sn_design_write_verilog(out, design); + fclose(out); +} + +// SN binary format +// ---------------- +// +// The binary representation is a versioned, little-endian semantic dump, not +// a native-memory image. Vector capacities, pointers, and name hash buckets are +// process-local details and are reconstructed when reading. IDs, vector sizes, +// names, constants, object attributes, type-specific attributes, optional +// fanout caches, and duplication maps are preserved exactly. +// +// File order is: +// header; names; constant words; module count; module records. +// A module record follows sn_module_t's semantic field order: +// module flags; core object vectors; fanins; type-object vectors; +// type-specific vectors; fanout vectors; copy map. +// +// Size fields are unsigned 64-bit values. IDs, flags, enum values, and stored +// data words are unsigned 32-bit values. A format change must increment the +// version below. + +#define SN_BINARY_FORMAT_VERSION 5u + +// The format version covers field-layout changes. This signature additionally binds every serialized object type to +// its numeric value, so reordering the enum cannot silently reinterpret an otherwise same-sized binary design. +static inline uint32_t sn_binary_layout_signature(void) +{ + static const sn_obj_type_t types[] = { + SN_NONE, SN_PI, SN_PO, SN_CONST0, SN_CONST1, SN_CONST, + SN_BUF, SN_FAN, SN_INST, SN_REG_OUT, SN_REG_IN, SN_MEM_OUT, + SN_MEM_IN, SN_MEM_READ, SN_MEM_WRITE, SN_LOOP_OUT, SN_LOOP_IN, SN_POS, + SN_NEG, SN_BIT_NOT, SN_LOG_NOT, SN_REDUCE_AND, SN_REDUCE_NAND, SN_REDUCE_OR, + SN_REDUCE_NOR, SN_REDUCE_XOR, SN_REDUCE_XNOR, SN_ADD, SN_SUB, SN_MUL, + SN_DIV, SN_MOD, SN_POW, SN_BIT_AND, SN_BIT_OR, SN_BIT_XOR, + SN_BIT_XNOR, SN_LOG_AND, SN_LOG_OR, SN_EQ, SN_NE, SN_CASE_EQ, + SN_CASE_NE, SN_WILDCARD_EQ, SN_WILDCARD_NE, SN_LT, SN_LE, SN_GT, + SN_GE, SN_SHL, SN_SHR, SN_ASHL, SN_ASHR, SN_MUX, + SN_BMUX, SN_PMUX, SN_CONCAT, SN_REPLICATE, SN_SLICE, SN_CAST, + SN_LUT, SN_GATE}; + uint32_t hash = UINT32_C(2166136261); + for (size_t i = 0; i < sizeof(types) / sizeof(types[0]); i++) + { + hash ^= types[i]; + hash *= UINT32_C(16777619); + hash ^= (uint32_t)i; + hash *= UINT32_C(16777619); + } + return hash; +} + +typedef struct sn_binary_writer_t +{ + FILE* out; + bool valid; +} sn_binary_writer_t; + +static inline sn_binary_writer_t sn_binary_writer_start(FILE* out) +{ + sn_binary_writer_t writer = {out, out != NULL}; + return writer; +} + +static inline bool sn_binary_write_bytes(sn_binary_writer_t* writer, const void* data, size_t size) +{ + assert(data || !size); + if (!writer || !writer->valid) + return false; + if (size && fwrite(data, 1, size, writer->out) != size) + writer->valid = false; + return writer->valid; +} + +typedef struct sn_binary_reader_t +{ + FILE* in; + uint64_t remaining; + bool valid; +} sn_binary_reader_t; + +static inline sn_binary_reader_t sn_binary_reader_start(FILE* in) +{ + sn_binary_reader_t reader = {in, UINT64_MAX, in != NULL}; +#if defined(_WIN32) + __int64 position; +#else + long position; +#endif + if (!in) + return reader; +#if defined(_WIN32) + position = _ftelli64(in); + if (position >= 0 && _fseeki64(in, 0, SEEK_END) == 0) + { + __int64 end = _ftelli64(in); + if (end >= position && _fseeki64(in, position, SEEK_SET) == 0) +#else + position = ftell(in); + if (position >= 0 && fseek(in, 0, SEEK_END) == 0) + { + long end = ftell(in); + if (end >= position && fseek(in, position, SEEK_SET) == 0) +#endif + reader.remaining = (uint64_t)(end - position); + else + reader.valid = false; + } + else + clearerr(in); + return reader; +} + +static inline bool sn_binary_read_bytes(sn_binary_reader_t* reader, void* data, size_t size) +{ + assert(data || !size); + if (!reader || !reader->valid || (uint64_t)size > reader->remaining) + { + if (reader) + reader->valid = false; + return false; + } + if (size && fread(data, 1, size, reader->in) != size) + { + reader->valid = false; + return false; + } + if (reader->remaining != UINT64_MAX) + reader->remaining -= size; + return true; +} + +static inline void sn_binary_write_u32(sn_binary_writer_t* writer, uint32_t value) +{ + uint8_t bytes[4]; + for (uint32_t i = 0; i < 4; i++) + bytes[i] = (uint8_t)(value >> (8 * i)); + sn_binary_write_bytes(writer, bytes, sizeof(bytes)); +} + +static inline uint32_t sn_binary_read_u32(sn_binary_reader_t* reader) +{ + uint8_t bytes[4] = {0}; + sn_binary_read_bytes(reader, bytes, sizeof(bytes)); + uint32_t value = 0; + for (uint32_t i = 0; i < 4; i++) + value |= (uint32_t)bytes[i] << (8 * i); + return value; +} + +static inline void sn_binary_write_u64(sn_binary_writer_t* writer, uint64_t value) +{ + uint8_t bytes[8]; + for (uint32_t i = 0; i < 8; i++) + bytes[i] = (uint8_t)(value >> (8 * i)); + sn_binary_write_bytes(writer, bytes, sizeof(bytes)); +} + +static inline uint64_t sn_binary_read_u64(sn_binary_reader_t* reader) +{ + uint8_t bytes[8] = {0}; + sn_binary_read_bytes(reader, bytes, sizeof(bytes)); + uint64_t value = 0; + for (uint32_t i = 0; i < 8; i++) + value |= (uint64_t)bytes[i] << (8 * i); + return value; +} + +static inline size_t sn_binary_read_size(sn_binary_reader_t* reader) +{ + uint64_t size = sn_binary_read_u64(reader); + if (size > SIZE_MAX) + { + reader->valid = false; + return 0; + } + return (size_t)size; +} + +static inline bool sn_binary_read_vec_size(sn_binary_reader_t* reader, size_t element_bytes, size_t* size) +{ + *size = sn_binary_read_size(reader); + if (!reader->valid || *size >= SN_INVALID_ID || (element_bytes && *size > reader->remaining / element_bytes)) + { + reader->valid = false; + return false; + } + return true; +} + +static inline void sn_binary_write_u32_vec(sn_binary_writer_t* writer, const sn_vec_t* vec) +{ + assert(vec); + sn_binary_write_u64(writer, vec->size); +#if defined(_WIN32) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + sn_binary_write_bytes(writer, vec->data, vec->size * sizeof(uint32_t)); +#else + for (size_t i = 0; i < vec->size; i++) + sn_binary_write_u32(writer, sn_vec_at(uint32_t, vec, i)); +#endif +} + +static inline void sn_binary_read_u32_vec(sn_binary_reader_t* reader, sn_vec_t* vec) +{ + assert(vec); + size_t size; + if (!sn_binary_read_vec_size(reader, 4, &size)) + return; + sn_vec_resize(uint32_t, vec, size); +#if defined(_WIN32) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + sn_binary_read_bytes(reader, vec->data, size * sizeof(uint32_t)); +#else + for (size_t i = 0; i < size; i++) + sn_vec_at(uint32_t, vec, i) = sn_binary_read_u32(reader); +#endif +} + +// Fanin counts use 16 bits in memory but retain their historical 32-bit binary representation. +static inline void sn_binary_write_fanin_count_vec(sn_binary_writer_t* writer, const sn_vec_t* vec) +{ + assert(vec); + sn_binary_write_u64(writer, vec->size); + for (size_t i = 0; i < vec->size; i++) + sn_binary_write_u32(writer, sn_vec_at(sn_fanin_count_t, vec, i)); +} + +static inline void sn_binary_read_fanin_count_vec(sn_binary_reader_t* reader, sn_vec_t* vec) +{ + assert(vec); + size_t size; + if (!sn_binary_read_vec_size(reader, 4, &size)) + return; + sn_vec_resize(sn_fanin_count_t, vec, size); + for (size_t i = 0; i < size; i++) + { + uint32_t count = sn_binary_read_u32(reader); + if (count > UINT16_MAX) + { + reader->valid = false; + return; + } + sn_vec_at(sn_fanin_count_t, vec, i) = (sn_fanin_count_t)count; + } +} + +static inline void sn_binary_write_u64_vec(sn_binary_writer_t* writer, const sn_vec_t* vec) +{ + assert(vec); + sn_binary_write_u64(writer, vec->size); +#if defined(_WIN32) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + sn_binary_write_bytes(writer, vec->data, vec->size * sizeof(uint64_t)); +#else + for (size_t i = 0; i < vec->size; i++) + sn_binary_write_u64(writer, sn_vec_at(uint64_t, vec, i)); +#endif +} + +static inline void sn_binary_read_u64_vec(sn_binary_reader_t* reader, sn_vec_t* vec) +{ + assert(vec); + size_t size; + if (!sn_binary_read_vec_size(reader, 8, &size)) + return; + sn_vec_resize(uint64_t, vec, size); +#if defined(_WIN32) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + sn_binary_read_bytes(reader, vec->data, size * sizeof(uint64_t)); +#else + for (size_t i = 0; i < size; i++) + sn_vec_at(uint64_t, vec, i) = sn_binary_read_u64(reader); +#endif +} + +static inline void sn_binary_write_type_vec(sn_binary_writer_t* writer, const sn_vec_t* vec) +{ + assert(vec); + sn_binary_write_u64(writer, vec->size); + for (size_t i = 0; i < vec->size; i++) + sn_binary_write_u32(writer, (uint32_t)sn_vec_at(sn_obj_type_t, vec, i)); +} + +static inline void sn_binary_read_type_vec(sn_binary_reader_t* reader, sn_vec_t* vec) +{ + assert(vec); + size_t size; + if (!sn_binary_read_vec_size(reader, 4, &size)) + return; + sn_vec_resize(sn_obj_type_t, vec, size); + for (size_t i = 0; i < size; i++) + { + uint32_t type = sn_binary_read_u32(reader); + if (type >= SN_OBJ_TYPE_COUNT) + { + reader->valid = false; + return; + } + sn_vec_at(sn_obj_type_t, vec, i) = (sn_obj_type_t)type; + } +} + +static inline void sn_binary_write_slice_vec(sn_binary_writer_t* writer, const sn_vec_t* vec) +{ + assert(vec); + sn_binary_write_u64(writer, vec->size); + for (size_t i = 0; i < vec->size; i++) + { + const sn_slice_info_t* info = &sn_vec_at(sn_slice_info_t, vec, i); + sn_binary_write_u32(writer, (uint32_t)info->left_index); + sn_binary_write_u32(writer, (uint32_t)info->right_index); + sn_binary_write_u32(writer, info->flags); + } +} + +static inline void sn_binary_read_slice_vec(sn_binary_reader_t* reader, sn_vec_t* vec) +{ + assert(vec); + size_t size; + if (!sn_binary_read_vec_size(reader, 12, &size)) + return; + sn_vec_resize(sn_slice_info_t, vec, size); + for (size_t i = 0; i < size; i++) + { + sn_slice_info_t* info = &sn_vec_at(sn_slice_info_t, vec, i); + info->left_index = (int32_t)sn_binary_read_u32(reader); + info->right_index = (int32_t)sn_binary_read_u32(reader); + info->flags = sn_binary_read_u32(reader); + } +} + +static inline void sn_module_assert_valid(const sn_module_t* module) +{ + assert(module); + assert(module->design); + assert(module->id < module->design->modules.size); + assert(sn_design_get_module_const(module->design, module->id) == module); + assert(module->name < module->design->names.names.size); + + size_t object_count = module->obj_types.size; + assert(object_count < SN_INVALID_ID); + assert(module->width_signed.size == object_count); + assert(module->fanin_counts.size == object_count); + assert(module->fanin_offsets.size == object_count); + assert(module->type_ids.size == object_count); + assert(module->name_ids.size == object_count); + + size_t expected_fanin_offset = 0; + for (sn_obj_id_t object = 0; object < object_count; object++) + { + sn_obj_type_t type = sn_vec_at(sn_obj_type_t, &module->obj_types, object); + uint32_t type_id = sn_vec_at(uint32_t, &module->type_ids, object); + uint32_t fanin_count = sn_obj_fanin_count(module, object); + uint32_t fanin_offset = sn_vec_at(uint32_t, &module->fanin_offsets, object); + uint32_t name = sn_vec_at(uint32_t, &module->name_ids, object); + assert(type > SN_NONE && type < SN_OBJ_TYPE_COUNT); + assert(type_id < module->type_objects[type].size); + assert(sn_vec_at(sn_obj_id_t, &module->type_objects[type], type_id) == object); + assert(fanin_offset == expected_fanin_offset); + assert(expected_fanin_offset + fanin_count <= module->fanins.size); + assert(name == SN_INVALID_ID || name < module->design->names.names.size); + for (uint32_t i = 0; i < fanin_count; i++) + { + sn_obj_id_t fanin = sn_vec_at(sn_obj_id_t, &module->fanins, fanin_offset + i); + assert(fanin == SN_INVALID_ID || fanin < object_count); + } + expected_fanin_offset += fanin_count; + } + assert(expected_fanin_offset == module->fanins.size); + + for (uint32_t type = 0; type < SN_OBJ_TYPE_COUNT; type++) + for (uint32_t type_id = 0; type_id < module->type_objects[type].size; type_id++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &module->type_objects[type], type_id); + assert(object < object_count); + assert(sn_vec_at(sn_obj_type_t, &module->obj_types, object) == (sn_obj_type_t)type); + assert(sn_vec_at(uint32_t, &module->type_ids, object) == type_id); + } + + assert(module->reg_flags.size == module->type_objects[SN_REG_OUT].size); + assert(module->mem_depths.size == module->type_objects[SN_MEM_OUT].size); + assert(module->inst_modules.size == module->type_objects[SN_INST].size); + assert(module->fan_insts.size == module->type_objects[SN_FAN].size); + assert(module->slice_infos.size == module->type_objects[SN_SLICE].size); + assert(module->repeat_counts.size == module->type_objects[SN_REPLICATE].size); + assert(module->const_word_offsets.size == module->type_objects[SN_CONST].size); + assert(module->lut_truths.size == module->type_objects[SN_LUT].size); + assert(module->gate_ids.size == module->type_objects[SN_GATE].size); + assert(module->type_objects[SN_REG_OUT].size == module->type_objects[SN_REG_IN].size); + assert(module->type_objects[SN_MEM_OUT].size == module->type_objects[SN_MEM_IN].size); + assert(module->type_objects[SN_LOOP_OUT].size == module->type_objects[SN_LOOP_IN].size); + + for (size_t i = 0; i < module->reg_flags.size; i++) + { + assert((sn_vec_at(uint32_t, &module->reg_flags, i) & ~SN_REG_FLAGS_ALL) == 0); + sn_obj_id_t reg = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_REG_OUT], i); + assert(sn_obj_fanin_count(module, reg) == SN_REG_FANIN_COUNT); + sn_obj_id_t data = sn_obj_reg_init_data(module, reg); + sn_obj_id_t mask = sn_obj_reg_init_mask(module, reg); + assert(data != SN_INVALID_ID || mask == SN_INVALID_ID); + if (data != SN_INVALID_ID) + { + sn_obj_type_t type = sn_obj_type(module, data); + assert(type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST); + assert(sn_obj_width(module, data) == sn_obj_width(module, reg)); + } + if (mask != SN_INVALID_ID) + { + sn_obj_type_t type = sn_obj_type(module, mask); + assert(type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST); + assert(sn_obj_width(module, mask) == sn_obj_width(module, reg)); + } + } + for (size_t i = 0; i < module->mem_depths.size; i++) + { + assert(sn_vec_at(uint32_t, &module->mem_depths, i)); + sn_obj_id_t memory = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_MEM_OUT], i); + assert(sn_obj_fanin_count(module, memory) == SN_MEM_OUT_FANIN_COUNT); + assert(sn_obj_fanin(module, memory, SN_MEM_STATE) == sn_obj_pair_in(module, memory)); + sn_obj_id_t data = sn_obj_mem_init_data(module, memory); + sn_obj_id_t mask = sn_obj_mem_init_mask(module, memory); + assert(data != SN_INVALID_ID || mask == SN_INVALID_ID); + uint32_t init_width = sn_obj_mem_init_width(module, memory); + if (data != SN_INVALID_ID) + { + sn_obj_type_t type = sn_obj_type(module, data); + assert(type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST); + assert(sn_obj_width(module, data) == init_width); + } + if (mask != SN_INVALID_ID) + { + sn_obj_type_t type = sn_obj_type(module, mask); + assert(type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST); + assert(sn_obj_width(module, mask) == init_width); + } + } + for (size_t i = 0; i < module->inst_modules.size; i++) + assert(sn_vec_at(sn_module_id_t, &module->inst_modules, i) < module->design->modules.size); + for (size_t i = 0; i < module->slice_infos.size; i++) + assert((sn_vec_at(sn_slice_info_t, &module->slice_infos, i).flags & ~SN_SLICE_DESCENDING) == 0); + for (size_t i = 0; i < module->repeat_counts.size; i++) + assert(sn_vec_at(uint32_t, &module->repeat_counts, i)); + + for (size_t i = 0; i < module->const_word_offsets.size; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_CONST], i); + uint32_t offset = sn_vec_at(uint32_t, &module->const_word_offsets, i); + assert((size_t)offset + sn_const_word_count(sn_obj_width(module, object)) <= + module->design->constant_words.size); + } + for (size_t i = 0; i < module->lut_truths.size; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_LUT], i); + uint32_t fanin_count = sn_obj_fanin_count(module, object); + assert(sn_obj_width(module, object) == 1 && !sn_obj_is_signed(module, object) && fanin_count <= 6); + for (uint32_t k = 0; k < fanin_count; k++) + assert(sn_obj_width(module, sn_obj_fanin(module, object, k)) == 1); + if (fanin_count < 6) + assert((sn_obj_lut_truth(module, object) >> (UINT32_C(1) << fanin_count)) == 0); + } + for (size_t i = 0; i < module->gate_ids.size; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_GATE], i); + assert(sn_obj_width(module, object) == 1); + assert(sn_obj_gate_id(module, object) != SN_INVALID_ID); + for (uint32_t j = 0; j < sn_obj_fanin_count(module, object); j++) + assert(sn_obj_width(module, sn_obj_fanin(module, object, j)) == 1); + } + + for (size_t i = 0; i < module->fan_insts.size; i++) + { + sn_obj_id_t fan = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_FAN], i); + sn_obj_id_t inst = sn_vec_at(sn_obj_id_t, &module->fan_insts, i); + assert(inst < object_count); + assert(sn_obj_type(module, inst) == SN_INST); + assert(sn_obj_fanin_count(module, fan) == 1); + assert(sn_obj_fanin(module, fan, 0) == inst); + assert(fan > inst); + uint32_t output_index = fan - inst - 1; + sn_module_id_t child = sn_vec_at(sn_module_id_t, &module->inst_modules, + sn_obj_type_id(module, inst)); + assert(output_index < sn_design_module_output_count(module->design, child)); + } + + if (module->fanouts_valid) + { + assert(module->fanout_counts.size == object_count); + assert(module->fanout_offsets.size == object_count); + size_t offset = 0; + for (sn_obj_id_t object = 0; object < object_count; object++) + { + assert(sn_vec_at(uint32_t, &module->fanout_offsets, object) == offset); + offset += sn_vec_at(uint32_t, &module->fanout_counts, object); + assert(offset <= module->fanouts.size); + } + assert(offset == module->fanouts.size); + for (size_t i = 0; i < module->fanouts.size; i++) + assert(sn_vec_at(sn_obj_id_t, &module->fanouts, i) < object_count); + } + else + { + assert(module->fanout_counts.size == 0); + assert(module->fanout_offsets.size == 0); + assert(module->fanouts.size == 0); + } + + if (module->copy_ids.size) + { + assert(module->copy_ids.size == object_count); + assert(module->copy_module < module->design->modules.size); + size_t copy_count = sn_design_get_module_const(module->design, module->copy_module)->obj_types.size; + for (size_t i = 0; i < module->copy_ids.size; i++) + { + sn_obj_id_t copy = sn_vec_at(sn_obj_id_t, &module->copy_ids, i); + assert(copy == SN_INVALID_ID || copy < copy_count); + } + } + else + assert(module->copy_module == SN_INVALID_ID); +} + +static inline void sn_design_assert_valid(const sn_design_t* design) +{ + assert(design); + assert(design->modules.size < SN_INVALID_ID); + assert(design->names.names.size < SN_INVALID_ID); + assert(design->names.links.size == design->names.names.size); + assert(design->names.buckets.size); + for (size_t i = 0; i < design->names.names.size; i++) + { + const char* name = sn_vec_at(char*, &design->names.names, i); + assert(name); + assert(sn_name_find(&design->names, name) == i); + } + for (sn_module_id_t i = 0; i < design->modules.size; i++) + { + const sn_module_t* module = sn_design_get_module_const(design, i); + assert(module->id == i); + for (sn_module_id_t previous = 0; previous < i; previous++) + assert(module->name != sn_design_get_module_const(design, previous)->name); + sn_module_assert_valid(module); + } +} + +static inline void sn_binary_write_module(sn_binary_writer_t* writer, const sn_module_t* module) +{ + sn_binary_write_u32(writer, module->name); + sn_binary_write_u32(writer, module->fanouts_valid ? 1u : 0u); + sn_binary_write_u32(writer, module->interface_locked ? 1u : 0u); + sn_binary_write_u32(writer, module->copy_module); + sn_binary_write_type_vec(writer, &module->obj_types); + sn_binary_write_u32_vec(writer, &module->width_signed); + sn_binary_write_fanin_count_vec(writer, &module->fanin_counts); + sn_binary_write_u32_vec(writer, &module->fanin_offsets); + sn_binary_write_u32_vec(writer, &module->type_ids); + sn_binary_write_u32_vec(writer, &module->name_ids); + sn_binary_write_u32_vec(writer, &module->fanins); + for (uint32_t type = 0; type < SN_OBJ_TYPE_COUNT; type++) + sn_binary_write_u32_vec(writer, &module->type_objects[type]); + sn_binary_write_u32_vec(writer, &module->reg_flags); + sn_binary_write_u32_vec(writer, &module->mem_depths); + sn_binary_write_u32_vec(writer, &module->inst_modules); + sn_binary_write_u32_vec(writer, &module->fan_insts); + sn_binary_write_slice_vec(writer, &module->slice_infos); + sn_binary_write_u32_vec(writer, &module->repeat_counts); + sn_binary_write_u32_vec(writer, &module->const_word_offsets); + sn_binary_write_u64_vec(writer, &module->lut_truths); + sn_binary_write_u32_vec(writer, &module->gate_ids); + sn_binary_write_u32_vec(writer, &module->fanout_counts); + sn_binary_write_u32_vec(writer, &module->fanout_offsets); + sn_binary_write_u32_vec(writer, &module->fanouts); + sn_binary_write_u32_vec(writer, &module->copy_ids); +} + +static inline bool sn_binary_read_module(sn_binary_reader_t* reader, sn_design_t* design, sn_module_id_t expected_id) +{ + sn_name_id_t name = sn_binary_read_u32(reader); + uint32_t fanouts_valid = sn_binary_read_u32(reader); + uint32_t interface_locked = sn_binary_read_u32(reader); + sn_module_id_t copy_module = sn_binary_read_u32(reader); + if (!reader->valid || name >= design->names.names.size || fanouts_valid > 1 || interface_locked > 1 || + sn_design_find_module(design, sn_name_get(&design->names, name)) != SN_INVALID_ID) + { + reader->valid = false; + return false; + } + + sn_module_id_t id = sn_design_add_module(design, sn_name_get(&design->names, name)); + assert(id == expected_id); + sn_module_t* module = sn_design_get_module(design, id); + assert(module->name == name); + module->fanouts_valid = fanouts_valid != 0; + module->interface_locked = interface_locked != 0; + module->copy_module = copy_module; + + sn_binary_read_type_vec(reader, &module->obj_types); + sn_binary_read_u32_vec(reader, &module->width_signed); + sn_binary_read_fanin_count_vec(reader, &module->fanin_counts); + sn_binary_read_u32_vec(reader, &module->fanin_offsets); + sn_binary_read_u32_vec(reader, &module->type_ids); + sn_binary_read_u32_vec(reader, &module->name_ids); + sn_binary_read_u32_vec(reader, &module->fanins); + for (uint32_t type = 0; type < SN_OBJ_TYPE_COUNT; type++) + sn_binary_read_u32_vec(reader, &module->type_objects[type]); + sn_binary_read_u32_vec(reader, &module->reg_flags); + sn_binary_read_u32_vec(reader, &module->mem_depths); + sn_binary_read_u32_vec(reader, &module->inst_modules); + sn_binary_read_u32_vec(reader, &module->fan_insts); + sn_binary_read_slice_vec(reader, &module->slice_infos); + sn_binary_read_u32_vec(reader, &module->repeat_counts); + sn_binary_read_u32_vec(reader, &module->const_word_offsets); + sn_binary_read_u64_vec(reader, &module->lut_truths); + sn_binary_read_u32_vec(reader, &module->gate_ids); + sn_binary_read_u32_vec(reader, &module->fanout_counts); + sn_binary_read_u32_vec(reader, &module->fanout_offsets); + sn_binary_read_u32_vec(reader, &module->fanouts); + sn_binary_read_u32_vec(reader, &module->copy_ids); + return reader->valid; +} + +static inline bool sn_design_write_binary(FILE* out, const sn_design_t* design) +{ + static const uint8_t magic[8] = {'S', 'N', 'B', 'I', 'N', '\r', '\n', 0x1a}; + if (!out || !design) + return false; + sn_design_assert_valid(design); + sn_binary_writer_t writer = sn_binary_writer_start(out); + sn_binary_write_bytes(&writer, magic, sizeof(magic)); + sn_binary_write_u32(&writer, SN_BINARY_FORMAT_VERSION); + sn_binary_write_u32(&writer, sn_binary_layout_signature()); + sn_binary_write_u32(&writer, SN_OBJ_TYPE_COUNT); + sn_binary_write_u32(&writer, SN_REG_FANIN_COUNT); + sn_binary_write_u32(&writer, SN_MEM_OUT_FANIN_COUNT); + + sn_binary_write_u64(&writer, design->names.names.size); + for (size_t i = 0; i < design->names.names.size; i++) + { + const char* name = sn_vec_at(char*, &design->names.names, i); + size_t length = strlen(name); + sn_binary_write_u64(&writer, length); + sn_binary_write_bytes(&writer, name, length); + } + sn_binary_write_u32_vec(&writer, &design->constant_words); + sn_binary_write_u64(&writer, design->modules.size); + for (size_t i = 0; i < design->modules.size; i++) + sn_binary_write_module(&writer, sn_design_get_module_const(design, (sn_module_id_t)i)); + return writer.valid && ferror(out) == 0; +} + +static inline bool sn_design_write_binary_file(const sn_design_t* design, const char* path) +{ + if (!design || !path) + return false; + FILE* out = fopen(path, "wb"); + if (!out) + return false; + bool success = sn_design_write_binary(out, design); + if (fclose(out) != 0) + success = false; + return success; +} + +// This routine validates the binary encoding while reconstructing its vectors, but intentionally does not dereference +// structural IDs or offsets. Callers must pass the result through sn_design_check() before installing or using it. +typedef enum sn_binary_read_status_t +{ + SN_BINARY_READ_OK, + SN_BINARY_READ_IO, + SN_BINARY_READ_MAGIC, + SN_BINARY_READ_VERSION, + SN_BINARY_READ_LAYOUT, + SN_BINARY_READ_MALFORMED +} sn_binary_read_status_t; + +static inline sn_design_t* sn_design_read_binary_raw_status(FILE* in, sn_binary_read_status_t* returned_status, + uint32_t* returned_version) +{ + static const uint8_t expected_magic[8] = {'S', 'N', 'B', 'I', 'N', '\r', '\n', 0x1a}; + sn_binary_read_status_t status = SN_BINARY_READ_OK; + sn_binary_reader_t reader = sn_binary_reader_start(in); + uint8_t magic[8] = {0}; + sn_binary_read_bytes(&reader, magic, sizeof(magic)); + uint32_t version = sn_binary_read_u32(&reader); + uint32_t layout_signature = sn_binary_read_u32(&reader); + uint32_t object_type_count = sn_binary_read_u32(&reader); + uint32_t register_fanin_count = sn_binary_read_u32(&reader); + uint32_t memory_fanin_count = sn_binary_read_u32(&reader); + if (returned_version) + *returned_version = version; + if (!reader.valid) + status = SN_BINARY_READ_IO; + else if (memcmp(magic, expected_magic, sizeof(magic)) != 0) + status = SN_BINARY_READ_MAGIC; + else if (version != SN_BINARY_FORMAT_VERSION) + status = SN_BINARY_READ_VERSION; + else if (layout_signature != sn_binary_layout_signature() || object_type_count != SN_OBJ_TYPE_COUNT || + register_fanin_count != SN_REG_FANIN_COUNT || + memory_fanin_count != SN_MEM_OUT_FANIN_COUNT) + status = SN_BINARY_READ_LAYOUT; + if (status != SN_BINARY_READ_OK) + { + if (returned_status) + *returned_status = status; + return NULL; + } + + sn_design_t* design = sn_design_create(); + size_t name_count = sn_binary_read_size(&reader); + if (!reader.valid || name_count >= SN_INVALID_ID || name_count > reader.remaining / 8) + reader.valid = false; + for (size_t i = 0; i < name_count; i++) + { + size_t length = sn_binary_read_size(&reader); + if (!reader.valid || length == SIZE_MAX || length > reader.remaining) + { + reader.valid = false; + break; + } + char* name = (char*)malloc(length + 1); + assert(name); + sn_binary_read_bytes(&reader, name, length); + name[length] = 0; + if (memchr(name, 0, length) != NULL) + { + free(name); + reader.valid = false; + break; + } + sn_name_id_t id = sn_name_intern(&design->names, name); + free(name); + if (id != i) + { + reader.valid = false; + break; + } + } + sn_binary_read_u32_vec(&reader, &design->constant_words); + size_t module_count = sn_binary_read_size(&reader); + if (!reader.valid || module_count >= SN_INVALID_ID || module_count > reader.remaining / 16) + reader.valid = false; + for (sn_module_id_t i = 0; reader.valid && i < module_count; i++) + sn_binary_read_module(&reader, design, i); + if (!reader.valid) + { + sn_design_destroy(design); + if (returned_status) + *returned_status = SN_BINARY_READ_MALFORMED; + return NULL; + } + if (returned_status) + *returned_status = SN_BINARY_READ_OK; + return design; +} + +static inline sn_design_t* sn_design_read_binary_raw(FILE* in) +{ + return sn_design_read_binary_raw_status(in, NULL, NULL); +} + +ABC_NAMESPACE_HEADER_END + +#endif // SN_H diff --git a/src/base/sn/snBlast.h b/src/base/sn/snBlast.h new file mode 100644 index 000000000..d9e908a33 --- /dev/null +++ b/src/base/sn/snBlast.h @@ -0,0 +1,2256 @@ +/**CFile**************************************************************** + + FileName [snBlast.h] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [New word-level design interface.] + + Synopsis [Bit-blasting flat or hierarchical SN designs into MiniAIG networks.] + + Author [Alan Mishchenko] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: snBlast.h,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#ifndef SN_BLAST_H +#define SN_BLAST_H + +// Flat combinational SN-to-MiniAIG bit blaster. MiniAIG is intentionally not +// hashed or constant propagated; Mini_AigerWrite() emits the resulting AIGER. + +#include "sn.h" +#include "aig/miniaig/miniaig.h" + +#include +#include +#include +#include +#include + +ABC_NAMESPACE_HEADER_START + +typedef enum sn_blast_mul_mode_t +{ + SN_BLAST_MUL_BAUGH_WOOLEY = 0, + SN_BLAST_MUL_BOOTH = 1 +} sn_blast_mul_mode_t; + +typedef enum sn_blast_mode_t +{ + // State elements are exposed as extra PI/PO pairs and are not marked as latches. + SN_BLAST_COMB = 0, + // State outputs are extra CIs, state inputs are the final COs, and nRegs is set. + SN_BLAST_SEQ = 1, + // Emit the sequential transition relation as a combinational AIG. Synchronous controls are folded into each + // next-state function exactly as in sequential mode, but nRegs remains zero. This is intended for combinational + // equivalence checking of state logic before and after word-level transformations. + SN_BLAST_TRANSITION = 2 +} sn_blast_mode_t; + +static inline bool sn_blast_mode_has_transition(sn_blast_mode_t mode) +{ + return mode == SN_BLAST_SEQ || mode == SN_BLAST_TRANSITION; +} + +typedef struct sn_blast_options_t +{ + sn_blast_mul_mode_t mul_mode; + bool ripple_adders; + bool delay_comparators; + bool abstract_memories; + bool abstract_multipliers; + // Treat every child instance as a combinational boundary. This derives one natural module partition while + // preserving the hierarchy and is used by module-by-module logic mapping. + bool abstract_instances; + bool expose_register_controls; + sn_blast_mode_t mode; +} sn_blast_options_t; + +static inline sn_blast_options_t sn_blast_default_options(void) +{ + sn_blast_options_t options = {SN_BLAST_MUL_BAUGH_WOOLEY, false, true, true, true, false, true, SN_BLAST_COMB}; + return options; +} + +// Latches cannot be represented by MiniAIG's edge-triggered register convention. Command-level clients use this +// query to reject a reachable latch before constructing an AIG or modifying any saved extraction state. +static inline sn_module_id_t sn_design_find_reachable_latch(const sn_design_t* design, sn_module_id_t root, + sn_obj_id_t* returned_latch) +{ + assert(design && root < design->modules.size); + uint8_t* seen = (uint8_t*)calloc(design->modules.size, 1); + sn_module_id_t* pending = (sn_module_id_t*)malloc(sizeof(sn_module_id_t) * design->modules.size); + size_t pending_count = 0; + assert(seen && pending); + seen[root] = 1; + pending[pending_count++] = root; + while (pending_count) + { + sn_module_id_t module_id = pending[--pending_count]; + const sn_module_t* module = sn_design_get_module_const(design, module_id); + if (!sn_module_is_technology_primitive(module)) + for (size_t i = 0; i < module->reg_flags.size; i++) + if (sn_vec_at(uint32_t, &module->reg_flags, i) & SN_REG_LATCH) + { + if (returned_latch) + *returned_latch = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_REG_OUT], i); + free(pending); + free(seen); + return module_id; + } + for (size_t i = 0; i < module->inst_modules.size; i++) + { + sn_module_id_t child = sn_vec_at(sn_module_id_t, &module->inst_modules, i); + assert(child < design->modules.size); + if (!seen[child]) + { + seen[child] = 1; + pending[pending_count++] = child; + } + } + } + free(pending); + free(seen); + if (returned_latch) + *returned_latch = SN_INVALID_ID; + return SN_INVALID_ID; +} + +typedef struct sn_blast_ctx_t sn_blast_ctx_t; +typedef int* (*sn_blast_special_eval_fn)(sn_blast_ctx_t* ctx, sn_obj_id_t object); + +struct sn_blast_ctx_t +{ + const sn_module_t* module; + Mini_Aig_t* aig; + sn_blast_options_t options; + int** bits; + uint8_t* state; + sn_blast_special_eval_fn special_eval; + void* special_data; +}; + +static inline int* sn_blast_eval(sn_blast_ctx_t* ctx, sn_obj_id_t object); + +static inline bool sn_blast_reg_init_bit(const sn_module_t* module, sn_obj_id_t reg_out, uint32_t bit) +{ + sn_obj_id_t data = sn_obj_reg_init_data(module, reg_out); + sn_obj_id_t mask = sn_obj_reg_init_mask(module, reg_out); + assert(bit < sn_obj_width(module, reg_out)); + if (data == SN_INVALID_ID) + return false; + return (mask == SN_INVALID_ID || sn_const_bit(module, mask, bit)) && sn_const_bit(module, data, bit); +} + +static inline int* sn_blast_alloc_bits(uint32_t width) +{ + assert(width); + int* bits = (int*)malloc(sizeof(int) * width); + assert(bits); + return bits; +} + +static inline void sn_blast_copy(int* dst, const int* src, uint32_t width) +{ + for (uint32_t i = 0; i < width; i++) + dst[i] = src[i]; +} + +static inline int sn_blast_bit(const sn_blast_ctx_t* ctx, sn_obj_id_t object, uint32_t bit) +{ + const sn_module_t* module = ctx->module; + assert(object < module->obj_types.size); + assert(bit < sn_obj_width(module, object)); + return ctx->bits[object][bit]; +} + +static inline int sn_blast_fill_bit(const sn_blast_ctx_t* ctx, sn_obj_id_t object, uint32_t bit, bool sign) +{ + uint32_t width = sn_obj_width(ctx->module, object); + if (bit < width) + return sn_blast_bit(ctx, object, bit); + return sign ? sn_blast_bit(ctx, object, width - 1) : Mini_AigLitConst0(); +} + +static inline int* sn_blast_extend(sn_blast_ctx_t* ctx, sn_obj_id_t object, uint32_t width, bool sign) +{ + sn_blast_eval(ctx, object); + int* result = sn_blast_alloc_bits(width); + for (uint32_t i = 0; i < width; i++) + result[i] = sn_blast_fill_bit(ctx, object, i, sign); + return result; +} + +static inline int sn_blast_or(Mini_Aig_t* aig, const int* bits, uint32_t width) +{ + assert(width); + assert(width <= INT_MAX); + int* temp = sn_blast_alloc_bits(width); + for (uint32_t i = 0; i < width; i++) + temp[i] = Mini_AigLitNot(bits[i]); + int result = Mini_AigLitNot(Mini_AigAndMulti(aig, temp, (int)width)); + free(temp); + return result; +} + +static inline int sn_blast_and(Mini_Aig_t* aig, const int* bits, uint32_t width) +{ + assert(width); + assert(width <= INT_MAX); + int* temp = sn_blast_alloc_bits(width); + sn_blast_copy(temp, bits, width); + int result = Mini_AigAndMulti(aig, temp, (int)width); + free(temp); + return result; +} + +static inline int sn_blast_xor(Mini_Aig_t* aig, const int* bits, uint32_t width) +{ + assert(width); + assert(width <= INT_MAX); + int* temp = sn_blast_alloc_bits(width); + sn_blast_copy(temp, bits, width); + int result = Mini_AigXorMulti(aig, temp, (int)width); + free(temp); + return result; +} + +static inline int sn_blast_lut_rec(Mini_Aig_t* aig, const int* inputs, uint32_t count, uint64_t truth) +{ + if (count == 0) + return (truth & 1) ? Mini_AigLitConst1() : Mini_AigLitConst0(); + uint32_t half = UINT32_C(1) << (count - 1); + uint64_t mask = half == 32 ? UINT32_MAX : (UINT64_C(1) << half) - 1; + int zero = sn_blast_lut_rec(aig, inputs, count - 1, truth & mask); + int one = sn_blast_lut_rec(aig, inputs, count - 1, truth >> half); + return Mini_AigMux(aig, inputs[count - 1], one, zero); +} + +// Seven-node full adder from Wlc_BlastFullAdder(). Complement propagation handles a constant-one input without +// introducing avoidable logic. The generic construction creates exactly seven unstrashed MiniAIG AND nodes. +static inline void sn_blast_full_adder(Mini_Aig_t* aig, int a, int b, int c, int* carry, int* sum) +{ + bool complement = a == Mini_AigLitConst1() || b == Mini_AigLitConst1() || c == Mini_AigLitConst1(); + if (complement) + { + a = Mini_AigLitNot(a); + b = Mini_AigLitNot(b); + c = Mini_AigLitNot(c); + } + int and1 = Mini_AigAnd(aig, a, b); + int and1n = Mini_AigAnd(aig, Mini_AigLitNot(a), Mini_AigLitNot(b)); + int x_ab = Mini_AigAnd(aig, Mini_AigLitNot(and1), Mini_AigLitNot(and1n)); + int and2 = Mini_AigAnd(aig, c, x_ab); + int and2n = Mini_AigAnd(aig, Mini_AigLitNot(c), Mini_AigLitNot(x_ab)); + *sum = Mini_AigAnd(aig, Mini_AigLitNot(and2), Mini_AigLitNot(and2n)); + *carry = Mini_AigOr(aig, and1, and2); + if (complement) + { + *sum = Mini_AigLitNot(*sum); + *carry = Mini_AigLitNot(*carry); + } +} + +static inline void sn_blast_add_inplace_ripple(Mini_Aig_t* aig, int* dst, const int* add, uint32_t width, + bool subtract) +{ + int carry = subtract ? Mini_AigLitConst1() : Mini_AigLitConst0(); + for (uint32_t i = 0; i < width; i++) + { + int value = subtract ? Mini_AigLitNot(add[i]) : add[i]; + sn_blast_full_adder(aig, dst[i], value, carry, &carry, &dst[i]); + } +} + +// XOR using an existing a & b node. This is the polarity used by Wlc_BlastFullAdder() and saves one AIG node +// whenever an adder generate or propagate-carry term is already available. +static inline int sn_blast_xor_with_and(Mini_Aig_t* aig, int a, int b, int and_ab) +{ + int and_neither = Mini_AigAnd(aig, Mini_AigLitNot(a), Mini_AigLitNot(b)); + return Mini_AigAnd(aig, Mini_AigLitNot(and_ab), Mini_AigLitNot(and_neither)); +} + +// Brent-Kung parallel-prefix addition. The prefix pairs are (propagate, generate), and the two sweeps follow the +// topology used by ABC's &genadder -b. Inputs and results remain in SN's LSB-first significance order. +static inline void sn_blast_add_inplace_brent_kung(Mini_Aig_t* aig, int* dst, const int* add, uint32_t width, + bool subtract) +{ + // A - B is A + ~B + 1: complement the second operand and use a constant-one carry-in. Folding this carry + // into the bit-0 generate before the prefix sweeps avoids adding it separately to every group carry. + bool carry_in = subtract; + uint32_t prefix_width = width - 1; + int* props = sn_blast_alloc_bits(width); + int* group_props = prefix_width ? sn_blast_alloc_bits(prefix_width) : NULL; + int* group_gens = prefix_width ? sn_blast_alloc_bits(prefix_width) : NULL; + int* local_terms = prefix_width ? sn_blast_alloc_bits(prefix_width) : NULL; + for (uint32_t i = 0; i < width; i++) + { + int value = carry_in ? Mini_AigLitNot(add[i]) : add[i]; + if (i < prefix_width) + { + int generate = Mini_AigAnd(aig, dst[i], value); + props[i] = sn_blast_xor_with_and(aig, dst[i], value, generate); + group_props[i] = props[i]; + group_gens[i] = i == 0 && carry_in ? Mini_AigOr(aig, dst[i], value) : generate; + local_terms[i] = -1; + } + else + props[i] = Mini_AigXor(aig, dst[i], value); + } + + // The carry leaving the most-significant result bit is discarded, so construct prefixes only through bit + // width - 2. Record the complete Brent-Kung schedule first. A group-propagate output is useful only when a + // later operation updates the same target; omitting all other propagate outputs removes dead prefix logic. + uint32_t* targets = prefix_width ? (uint32_t*)malloc(sizeof(uint32_t) * 2 * prefix_width) : NULL; + uint32_t* lowers = prefix_width ? (uint32_t*)malloc(sizeof(uint32_t) * 2 * prefix_width) : NULL; + uint32_t* last_target = prefix_width ? (uint32_t*)malloc(sizeof(uint32_t) * prefix_width) : NULL; + assert(!prefix_width || (targets && lowers && last_target)); + for (uint32_t i = 0; i < prefix_width; i++) + last_target[i] = UINT32_MAX; + + uint32_t operation_count = 0; + uint64_t step; + for (step = 2; step / 2 < prefix_width; step <<= 1) + for (uint64_t i = step - 1; i < prefix_width; i += step) + { + assert(operation_count < 2 * prefix_width); + targets[operation_count] = (uint32_t)i; + lowers[operation_count++] = (uint32_t)(i - step / 2); + } + for (step >>= 1; step >= 2; step >>= 1) + for (uint64_t i = 3 * step / 2 - 1; i < prefix_width; i += step) + { + assert(operation_count < 2 * prefix_width); + targets[operation_count] = (uint32_t)i; + lowers[operation_count++] = (uint32_t)(i - step / 2); + } + for (uint32_t i = 0; i < operation_count; i++) + last_target[targets[i]] = i; + for (uint32_t i = 0; i < operation_count; i++) + { + uint32_t target = targets[i], lower = lowers[i]; + int term = Mini_AigAnd(aig, group_props[target], group_gens[lower]); + if (target == lower + 1 && (last_target[lower] == UINT32_MAX || last_target[lower] < i)) + local_terms[target] = term; + group_gens[target] = Mini_AigOr(aig, group_gens[target], term); + if (last_target[target] != i) + group_props[target] = Mini_AigAnd(aig, group_props[target], group_props[lower]); + } + + for (uint32_t i = 0; i < width; i++) + if (i == 0) + dst[i] = carry_in ? Mini_AigLitNot(props[i]) : props[i]; + else if (i < prefix_width && local_terms[i] >= 0) + dst[i] = sn_blast_xor_with_and(aig, props[i], group_gens[i - 1], local_terms[i]); + else + dst[i] = Mini_AigXor(aig, props[i], group_gens[i - 1]); + free(last_target); + free(lowers); + free(targets); + free(local_terms); + free(group_gens); + free(group_props); + free(props); +} + +static inline void sn_blast_add_inplace(Mini_Aig_t* aig, int* dst, const int* add, uint32_t width, bool subtract, + bool ripple) +{ + if (ripple) + sn_blast_add_inplace_ripple(aig, dst, add, width, subtract); + else + sn_blast_add_inplace_brent_kung(aig, dst, add, width, subtract); +} + +// Minimum-node comparator topology used by ABC's &gencomp. The construction computes a > b and consumes vectors in +// SN's LSB-first significance order. Signed comparison removes the sign bits, then selects b's sign when they differ. +static inline int sn_blast_gt(Mini_Aig_t* aig, const int* a, const int* b, uint32_t width, bool signed_compare) +{ + assert(width > 0); + uint32_t compare_width = width; + int signs_differ = Mini_AigLitConst0(); + int b_sign = Mini_AigLitConst0(); + if (signed_compare) + { + compare_width--; + signs_differ = Mini_AigXor(aig, a[compare_width], b[compare_width]); + b_sign = b[compare_width]; + } + + int result = compare_width ? Mini_AigLitConst1() : Mini_AigLitConst0(); + for (uint32_t i = 0; i < compare_width; i++) + { + int bit_a0 = a[i], bit_b0 = b[i]; + int bit_a1 = i + 1 < compare_width ? a[i + 1] : Mini_AigLitConst0(); + int bit_b1 = i + 1 < compare_width ? b[i + 1] : Mini_AigLitConst0(); + bool odd = (i & 1) != 0; + int term0 = i == 0 + ? Mini_AigOr(aig, odd ? bit_a0 : Mini_AigLitNot(bit_a0), + odd ? Mini_AigLitNot(bit_b0) : bit_b0) + : Mini_AigAnd(aig, odd ? bit_a0 : Mini_AigLitNot(bit_a0), + odd ? Mini_AigLitNot(bit_b0) : bit_b0); + int term1 = Mini_AigAnd(aig, odd ? bit_a1 : Mini_AigLitNot(bit_a1), + odd ? Mini_AigLitNot(bit_b1) : bit_b1); + result = Mini_AigOr(aig, Mini_AigLitNot(result), Mini_AigOr(aig, term0, term1)); + } + result = (compare_width & 1) ? Mini_AigLitNot(result) : result; + return signed_compare ? Mini_AigMux(aig, signs_differ, b_sign, result) : result; +} + +// Delay-oriented comparator. Each bit produces a greater-than generate and an equality propagate. Adjacent ranges +// are combined from least to most significant in a balanced tree: G = G_high | (E_high & G_low), +// E = E_high & E_low. Complementing both sign bits converts signed ordering into unsigned ordering. +static inline int sn_blast_gt_delay(Mini_Aig_t* aig, const int* a, const int* b, uint32_t width, bool signed_compare) +{ + assert(width > 0); + int* generates = sn_blast_alloc_bits(width); + int* equals = sn_blast_alloc_bits(width); + for (uint32_t i = 0; i < width; i++) + { + int bit_a = signed_compare && i + 1 == width ? Mini_AigLitNot(a[i]) : a[i]; + int bit_b = signed_compare && i + 1 == width ? Mini_AigLitNot(b[i]) : b[i]; + generates[i] = Mini_AigAnd(aig, bit_a, Mini_AigLitNot(bit_b)); + equals[i] = Mini_AigLitNot(Mini_AigXor(aig, bit_a, bit_b)); + } + for (uint32_t count = width; count > 1; count = (count + 1) / 2) + { + uint32_t output = 0; + for (uint32_t i = 0; i < count; i += 2, output++) + { + if (i + 1 == count) + { + generates[output] = generates[i]; + equals[output] = equals[i]; + continue; + } + int low_generate = generates[i]; + int high_generate = generates[i + 1]; + int high_equal = equals[i + 1]; + generates[output] = Mini_AigOr(aig, high_generate, Mini_AigAnd(aig, high_equal, low_generate)); + equals[output] = Mini_AigAnd(aig, high_equal, equals[i]); + } + } + int result = generates[0]; + free(generates); + free(equals); + return result; +} + +static inline int sn_blast_eq_bits(Mini_Aig_t* aig, const int* a, const int* b, uint32_t width) +{ + assert(width && width <= INT_MAX); + int* temp = sn_blast_alloc_bits(width); + uint32_t count = 0; + for (uint32_t i = 0; i < width; i++) + { + int equal; + if (a[i] == Mini_AigLitConst0()) + equal = Mini_AigLitNot(b[i]); + else if (a[i] == Mini_AigLitConst1()) + equal = b[i]; + else if (b[i] == Mini_AigLitConst0()) + equal = Mini_AigLitNot(a[i]); + else if (b[i] == Mini_AigLitConst1()) + equal = a[i]; + else if (a[i] == b[i]) + equal = Mini_AigLitConst1(); + else if (a[i] == Mini_AigLitNot(b[i])) + equal = Mini_AigLitConst0(); + else + equal = Mini_AigLitNot(Mini_AigXor(aig, a[i], b[i])); + if (equal == Mini_AigLitConst0()) + { + free(temp); + return Mini_AigLitConst0(); + } + if (equal != Mini_AigLitConst1()) + temp[count++] = equal; + } + int result = count ? Mini_AigAndMulti(aig, temp, (int)count) : Mini_AigLitConst1(); + free(temp); + return result; +} + +static inline int* sn_blast_add_vectors(Mini_Aig_t* aig, const int* a, const int* b, uint32_t width, bool subtract, + bool ripple) +{ + int* result = sn_blast_alloc_bits(width); + sn_blast_copy(result, a, width); + sn_blast_add_inplace(aig, result, b, width, subtract, ripple); + return result; +} + +static inline int* sn_blast_negate_vector(Mini_Aig_t* aig, const int* value, uint32_t width, bool ripple) +{ + if (!ripple) + { + int* result = sn_blast_alloc_bits(width); + for (uint32_t i = 0; i < width; i++) + result[i] = Mini_AigLitConst0(); + sn_blast_add_inplace_brent_kung(aig, result, value, width, true); + return result; + } + int* result = sn_blast_alloc_bits(width); + for (uint32_t i = 0; i < width; i++) + result[i] = Mini_AigLitNot(value[i]); + int carry = Mini_AigLitConst1(); + for (uint32_t i = 0; i < width; i++) + { + int old = result[i]; + result[i] = Mini_AigXor(aig, old, carry); + carry = Mini_AigAnd(aig, old, carry); + } + return result; +} + +static inline int* sn_blast_mux_tree(Mini_Aig_t* aig, int* select, uint32_t select_width, const int* alternatives, + uint32_t output_width) +{ + assert(select_width < 31); + int* result = sn_blast_alloc_bits(output_width); + uint32_t count = 1u << select_width; + for (uint32_t bit = 0; bit < output_width; bit++) + { + int* values = sn_blast_alloc_bits(count); + for (uint32_t i = 0; i < count; i++) + values[i] = alternatives[i * output_width + bit]; + result[bit] = Mini_AigMuxMulti(aig, select, (int)select_width, values, (int)count); + free(values); + } + return result; +} + +static inline int sn_blast_mux_simplified(Mini_Aig_t* aig, int select, int one, int zero) +{ + if (one == zero) + return one; + if (select == Mini_AigLitConst0()) + return zero; + if (select == Mini_AigLitConst1()) + return one; + if (one == Mini_AigLitConst1() && zero == Mini_AigLitConst0()) + return select; + if (one == Mini_AigLitConst0() && zero == Mini_AigLitConst1()) + return Mini_AigLitNot(select); + if (zero == Mini_AigLitConst0()) + return Mini_AigAnd(aig, select, one); + if (one == Mini_AigLitConst0()) + return Mini_AigAnd(aig, Mini_AigLitNot(select), zero); + if (one == Mini_AigLitConst1()) + return Mini_AigOr(aig, select, zero); + if (zero == Mini_AigLitConst1()) + return Mini_AigOr(aig, Mini_AigLitNot(select), one); + return Mini_AigMux(aig, select, one, zero); +} + +// A constant table is read one output bit at a time. This bounds temporary storage by the table entry count rather +// than its full packed bit count and removes constant/equal mux branches before they enter the unhashed MiniAIG. +static inline int* sn_blast_const_mux_tree(Mini_Aig_t* aig, const sn_module_t* module, sn_obj_id_t table, + int* select, uint32_t select_width, uint32_t output_width) +{ + assert(select_width < 31); + uint32_t count = 1u << select_width; + int* values = sn_blast_alloc_bits(count); + int* result = sn_blast_alloc_bits(output_width); + for (uint32_t bit = 0; bit < output_width; bit++) + { + for (uint32_t i = 0; i < count; i++) + values[i] = sn_const_bit(module, table, i * output_width + bit) ? Mini_AigLitConst1() + : Mini_AigLitConst0(); + uint32_t value_count = count; + for (uint32_t stage = 0; stage < select_width; stage++) + { + for (uint32_t i = 0; i < value_count / 2; i++) + values[i] = sn_blast_mux_simplified(aig, select[stage], values[2 * i + 1], values[2 * i]); + value_count /= 2; + } + assert(value_count == 1); + result[bit] = values[0]; + } + free(values); + return result; +} + +typedef struct sn_blast_column_t +{ + int* values; + uint32_t* levels; + uint32_t size; + uint32_t cap; +} sn_blast_column_t; + +static inline void sn_blast_column_push(sn_blast_column_t* column, int literal, uint32_t level) +{ + if (column->size == column->cap) + { + column->cap = column->cap ? 2 * column->cap : 8; + column->values = (int*)realloc(column->values, sizeof(int) * column->cap); + column->levels = (uint32_t*)realloc(column->levels, sizeof(uint32_t) * column->cap); + assert(column->values); + assert(column->levels); + } + uint32_t i = column->size++; + while (i && column->levels[i - 1] < level) + { + column->values[i] = column->values[i - 1]; + column->levels[i] = column->levels[i - 1]; + i--; + } + column->values[i] = literal; + column->levels[i] = level; +} + +static inline sn_blast_column_t* sn_blast_columns_alloc(uint32_t count, uint32_t cap) +{ + sn_blast_column_t* columns = (sn_blast_column_t*)calloc(count, sizeof(*columns)); + assert(columns); + for (uint32_t i = 0; i < count; i++) + { + columns[i].cap = cap; + columns[i].values = (int*)malloc(sizeof(int) * cap); + columns[i].levels = (uint32_t*)malloc(sizeof(uint32_t) * cap); + assert(columns[i].values); + assert(columns[i].levels); + } + return columns; +} + +static inline void sn_blast_columns_free(sn_blast_column_t* columns, uint32_t count) +{ + for (uint32_t i = 0; i < count; i++) + { + free(columns[i].values); + free(columns[i].levels); + } + free(columns); +} + +// Reduces a partial-product matrix by always combining the three least-deep signals in each column. This is the +// Mini_Aig counterpart of Wlc_BlastReduceMatrix(): sum and carry depths are tracked explicitly and inserted back in +// level order, preventing carries from creating a serial diagonal chain through the matrix. +static inline int* sn_blast_reduce_columns(Mini_Aig_t* aig, sn_blast_column_t* columns, uint32_t width, bool ripple) +{ + int* row0 = sn_blast_alloc_bits(width); + int* row1 = sn_blast_alloc_bits(width); + for (uint32_t i = 0; i < width; i++) + { + sn_blast_column_t* column = &columns[i]; + while (column->size > 2) + { + uint32_t level0 = column->levels[--column->size]; + int value0 = column->values[column->size]; + uint32_t level1 = column->levels[--column->size]; + int value1 = column->values[column->size]; + uint32_t level2 = column->levels[--column->size]; + int value2 = column->values[column->size]; + uint32_t level = level0 > level1 ? level0 : level1; + level = level > level2 ? level : level2; + int carry, sum; + sn_blast_full_adder(aig, value0, value1, value2, &carry, &sum); + sn_blast_column_push(column, sum, level + 2); + sn_blast_column_push(&columns[i + 1], carry, level + 1); + } + row0[i] = column->size ? column->values[0] : Mini_AigLitConst0(); + row1[i] = column->size == 2 ? column->values[1] : Mini_AigLitConst0(); + } + sn_blast_add_inplace(aig, row0, row1, width, false, ripple); + free(row1); + return row0; +} + +static inline int* sn_blast_mul_operand(const int* value, bool is_signed, uint32_t width, uint32_t work) +{ + int* result = sn_blast_alloc_bits(work); + for (uint32_t i = 0; i < work; i++) + result[i] = i < width ? value[i] : (is_signed ? value[width - 1] : Mini_AigLitConst0()); + return result; +} + +static inline int* sn_blast_mul_baugh_wooley(Mini_Aig_t* aig, const int* a, bool a_signed, uint32_t a_width, + const int* b, bool b_signed, uint32_t b_width, uint32_t result_width, + bool ripple) +{ + // Unsigned and mixed multiplication use the partial-product matrix directly. Baugh-Wooley operates on two signed + // operands at their original widths, without sign-extending either input. + bool signed_matrix = a_signed && b_signed; + uint32_t a_work = a_width; + uint32_t b_work = b_width; + uint32_t product_width = a_work + b_work; + uint32_t column_count = product_width + 1; + uint32_t cap = (a_work < b_work ? a_work : b_work) + 8; + int* aa = sn_blast_mul_operand(a, a_signed, a_width, a_work); + int* bb = sn_blast_mul_operand(b, b_signed, b_width, b_work); + sn_blast_column_t* columns = sn_blast_columns_alloc(column_count, cap); + for (uint32_t i = 0; i < a_work; i++) + for (uint32_t j = 0; j < b_work; j++) + { + bool complement = signed_matrix && ((i + 1 == a_work) != (j + 1 == b_work)); + int product = Mini_AigAnd(aig, aa[i], bb[j]); + sn_blast_column_push(&columns[i + j], complement ? Mini_AigLitNot(product) : product, 0); + } + if (signed_matrix) + { + sn_blast_column_push(&columns[a_work - 1], Mini_AigLitConst1(), 0); + sn_blast_column_push(&columns[b_work - 1], Mini_AigLitConst1(), 0); + sn_blast_column_push(&columns[product_width - 1], Mini_AigLitConst1(), 0); + } + int* product = sn_blast_reduce_columns(aig, columns, product_width, ripple); + int* result = sn_blast_alloc_bits(result_width); + for (uint32_t i = 0; i < result_width; i++) + result[i] = i < product_width ? product[i] + : (signed_matrix ? product[product_width - 1] : Mini_AigLitConst0()); + free(product); + free(aa); + free(bb); + sn_blast_columns_free(columns, column_count); + return result; +} + +static inline int* sn_blast_mul_booth(Mini_Aig_t* aig, const int* a, bool a_signed, uint32_t a_width, + const int* b, bool b_signed, uint32_t b_width, uint32_t result_width, + bool ripple) +{ + bool signed_multiply = a_signed && b_signed; + uint32_t common_width = a_width > b_width ? a_width : b_width; + uint32_t a_constants = 0, b_constants = 0; + for (uint32_t i = 0; i < common_width; i++) + { + int a_bit = i < a_width ? a[i] : (signed_multiply ? a[a_width - 1] : Mini_AigLitConst0()); + int b_bit = i < b_width ? b[i] : (signed_multiply ? b[b_width - 1] : Mini_AigLitConst0()); + a_constants += Mini_AigLitIsConst(a_bit); + b_constants += Mini_AigLitIsConst(b_bit); + } + if (a_constants < b_constants) + { + const int* temp_value = a; + uint32_t temp_width = a_width; + a = b; + a_width = b_width; + b = temp_value; + b_width = temp_width; + } + + uint32_t product_width = a_width + b_width; + uint32_t matrix_width = product_width + 3; + uint32_t column_count = matrix_width + 1; + uint32_t cap = a_width + 8; + sn_blast_column_t* columns = sn_blast_columns_alloc(column_count, cap); + int fill_a = signed_multiply ? a[a_width - 1] : Mini_AigLitConst0(); + int fill_b = signed_multiply ? b[b_width - 1] : Mini_AigLitConst0(); + int* extended_b = sn_blast_alloc_bits(b_width + 4); + uint32_t extended_size = 0; + extended_b[extended_size++] = Mini_AigLitConst0(); + for (uint32_t i = 0; i < b_width; i++) + extended_b[extended_size++] = b[i]; + if (!signed_multiply) + { + extended_b[extended_size++] = fill_b; + extended_b[extended_size++] = fill_b; + } + if ((extended_size & 1) == 0) + extended_b[extended_size++] = fill_b; + assert(extended_size & 1); + for (uint32_t k = 0; k + 2 < extended_size; k += 2) + { + int q_minus = extended_b[k]; + int q = extended_b[k + 1]; + int q_plus = extended_b[k + 2]; + int negative = q_plus; + int one = Mini_AigXor(aig, q, q_minus); + int two = Mini_AigMux(aig, negative, + Mini_AigAnd(aig, Mini_AigLitNot(q), Mini_AigLitNot(q_minus)), + Mini_AigAnd(aig, q, q_minus)); + int partial = Mini_AigLitConst0(); + uint32_t i; + for (i = 0; i <= a_width; i++) + { + int current = i == a_width ? fill_a : a[i]; + int previous = i ? a[i - 1] : Mini_AigLitConst0(); + int part = Mini_AigOr(aig, Mini_AigAnd(aig, one, current), Mini_AigAnd(aig, two, previous)); + partial = Mini_AigXor(aig, part, negative); + if (partial != Mini_AigLitConst0() && !(signed_multiply && i == a_width)) + sn_blast_column_push(&columns[k + i], partial, 0); + } + if (signed_multiply) + i--; + int sign = signed_multiply ? partial : negative; + if (k == 0) + { + sn_blast_column_push(&columns[k + i], sign, 0); + sn_blast_column_push(&columns[k + i + 1], sign, 0); + if (sign != Mini_AigLitConst1()) + sn_blast_column_push(&columns[k + i + 2], Mini_AigLitNot(sign), 0); + } + else + { + if (sign != Mini_AigLitConst1()) + sn_blast_column_push(&columns[k + i], Mini_AigLitNot(sign), 0); + sn_blast_column_push(&columns[k + i + 1], Mini_AigLitConst1(), 0); + } + if (negative != Mini_AigLitConst0()) + sn_blast_column_push(&columns[k], negative, 0); + } + int* product = sn_blast_reduce_columns(aig, columns, matrix_width, ripple); + int* result = sn_blast_alloc_bits(result_width); + for (uint32_t i = 0; i < result_width; i++) + result[i] = i < product_width ? product[i] + : (signed_multiply ? product[product_width - 1] : Mini_AigLitConst0()); + free(extended_b); + free(product); + sn_blast_columns_free(columns, column_count); + return result; +} + +static inline int* sn_blast_div_vectors(Mini_Aig_t* aig, const int* dividend, const int* divisor, uint32_t width, + bool signed_operands, bool remainder_result, bool ripple, + bool delay_comparators) +{ + int* a = sn_blast_alloc_bits(width); + int* b = sn_blast_alloc_bits(width); + for (uint32_t i = 0; i < width; i++) + { + a[i] = dividend[i]; + b[i] = divisor[i]; + } + int sign_a = signed_operands ? a[width - 1] : 0; + int sign_b = signed_operands ? b[width - 1] : 0; + if (signed_operands) + { + int* neg_a = sn_blast_negate_vector(aig, a, width, ripple); + int* neg_b = sn_blast_negate_vector(aig, b, width, ripple); + for (uint32_t i = 0; i < width; i++) + { + a[i] = Mini_AigMux(aig, sign_a, neg_a[i], a[i]); + b[i] = Mini_AigMux(aig, sign_b, neg_b[i], b[i]); + } + free(neg_a); + free(neg_b); + } + int* rem = sn_blast_alloc_bits(width + 1); + int* div = sn_blast_alloc_bits(width + 1); + int* quotient = sn_blast_alloc_bits(width); + for (uint32_t i = 0; i <= width; i++) + { + rem[i] = 0; + div[i] = i < width ? b[i] : 0; + } + for (uint32_t i = 0; i < width; i++) + quotient[i] = 0; + int divisor_zero = Mini_AigLitNot(sn_blast_or(aig, b, width)); + for (uint32_t i = width; i-- > 0;) + { + int* shifted = sn_blast_alloc_bits(width + 1); + shifted[0] = a[i]; + for (uint32_t k = 1; k <= width; k++) + shifted[k] = rem[k - 1]; + int less = delay_comparators ? sn_blast_gt_delay(aig, div, shifted, width + 1, false) + : sn_blast_gt(aig, div, shifted, width + 1, false); + int ge = Mini_AigLitNot(less); + int* difference = sn_blast_alloc_bits(width + 1); + sn_blast_copy(difference, shifted, width + 1); + sn_blast_add_inplace(aig, difference, div, width + 1, true, ripple); + for (uint32_t k = 0; k <= width; k++) + rem[k] = Mini_AigMux(aig, ge, difference[k], shifted[k]); + quotient[i] = Mini_AigMux(aig, divisor_zero, Mini_AigLitConst1(), ge); + free(shifted); + free(difference); + } + int* result = sn_blast_alloc_bits(width); + if (remainder_result) + for (uint32_t i = 0; i < width; i++) + result[i] = Mini_AigMux(aig, divisor_zero, dividend[i], rem[i]); + else + for (uint32_t i = 0; i < width; i++) + result[i] = quotient[i]; + if (signed_operands) + { + int result_sign = remainder_result ? sign_a : Mini_AigXor(aig, sign_a, sign_b); + result_sign = Mini_AigMux(aig, divisor_zero, Mini_AigLitConst0(), result_sign); + int* neg_result = sn_blast_negate_vector(aig, result, width, ripple); + for (uint32_t i = 0; i < width; i++) + result[i] = Mini_AigMux(aig, result_sign, neg_result[i], result[i]); + free(neg_result); + } + free(a); + free(b); + free(rem); + free(div); + free(quotient); + return result; +} + +static inline int* sn_blast_power(Mini_Aig_t* aig, const int* base, uint32_t base_width, bool base_signed, + const int* exponent, uint32_t exponent_width, bool exponent_signed, + uint32_t result_width, sn_blast_mul_mode_t mode, bool ripple) +{ + int* result = sn_blast_alloc_bits(result_width); + int* power = sn_blast_alloc_bits(result_width); + for (uint32_t i = 0; i < result_width; i++) + { + result[i] = i == 0 ? Mini_AigLitConst1() : Mini_AigLitConst0(); + power[i] = i < base_width ? base[i] + : (base_signed ? base[base_width - 1] : Mini_AigLitConst0()); + } + for (uint32_t i = 0; i < exponent_width; i++) + { + int* selected = mode == SN_BLAST_MUL_BOOTH + ? sn_blast_mul_booth(aig, result, base_signed, result_width, power, base_signed, + result_width, + result_width, ripple) + : sn_blast_mul_baugh_wooley(aig, result, base_signed, result_width, power, base_signed, + result_width, + result_width, ripple); + for (uint32_t bit = 0; bit < result_width; bit++) + result[bit] = Mini_AigMux(aig, exponent[i], selected[bit], result[bit]); + free(selected); + if (i + 1 < exponent_width) + { + int* squared = mode == SN_BLAST_MUL_BOOTH + ? sn_blast_mul_booth(aig, power, base_signed, result_width, power, base_signed, + result_width, result_width, ripple) + : sn_blast_mul_baugh_wooley(aig, power, base_signed, result_width, power, base_signed, + result_width, result_width, ripple); + free(power); + power = squared; + } + } + if (exponent_signed) + { + int is_zero = Mini_AigLitConst1(); + int is_one = Mini_AigLitConst1(); + int is_minus_one = Mini_AigLitConst1(); + for (uint32_t i = 0; i < base_width; i++) + { + is_zero = Mini_AigAnd(aig, is_zero, Mini_AigLitNot(base[i])); + is_one = Mini_AigAnd(aig, is_one, i == 0 ? base[i] : Mini_AigLitNot(base[i])); + if (base_signed) + is_minus_one = Mini_AigAnd(aig, is_minus_one, base[i]); + } + int unit = Mini_AigOr(aig, is_zero, is_one); + if (base_signed) + unit = Mini_AigOr(aig, unit, is_minus_one); + int force_zero = Mini_AigAnd(aig, exponent[exponent_width - 1], Mini_AigLitNot(unit)); + for (uint32_t i = 0; i < result_width; i++) + result[i] = Mini_AigAnd(aig, result[i], Mini_AigLitNot(force_zero)); + } + free(power); + return result; +} + +static inline int* sn_blast_shift(sn_blast_ctx_t* ctx, sn_obj_id_t object, bool left, bool arithmetic) +{ + const sn_module_t* m = ctx->module; + sn_obj_id_t value_id = sn_obj_fanin(m, object, 0); + sn_obj_id_t amount_id = sn_obj_fanin(m, object, 1); + uint32_t width = sn_obj_width(m, object); + uint32_t value_width = sn_obj_width(m, value_id); + uint32_t amount_width = sn_obj_width(m, amount_id); + uint32_t work_width = width > value_width ? width : value_width; + int* current = sn_blast_extend(ctx, value_id, work_width, sn_obj_is_signed(m, value_id)); + const int* amount = sn_blast_eval(ctx, amount_id); + int fill = arithmetic && !left && sn_obj_is_signed(m, value_id) ? current[work_width - 1] + : Mini_AigLitConst0(); + uint32_t useful_stages = 0; + while ((UINT32_C(1) << useful_stages) < work_width) + useful_stages++; + uint32_t stage_count = amount_width < useful_stages ? amount_width : useful_stages; + for (uint32_t stage = 0; stage < stage_count; stage++) + { + uint64_t distance = UINT64_C(1) << stage; + int* next = sn_blast_alloc_bits(work_width); + for (uint32_t bit = 0; bit < work_width; bit++) + { + int shifted = fill; + if (left) + shifted = bit >= distance ? current[bit - distance] : Mini_AigLitConst0(); + else + shifted = bit + distance < work_width ? current[bit + distance] : fill; + next[bit] = Mini_AigMux(ctx->aig, amount[stage], shifted, current[bit]); + } + free(current); + current = next; + } + if (amount_width > useful_stages) + { + int overshift = sn_blast_or(ctx->aig, amount + useful_stages, amount_width - useful_stages); + for (uint32_t bit = 0; bit < work_width; bit++) + current[bit] = Mini_AigMux(ctx->aig, overshift, fill, current[bit]); + } + int* result = sn_blast_alloc_bits(width); + for (uint32_t bit = 0; bit < width; bit++) + result[bit] = current[bit]; + free(current); + return result; +} + +static inline int* sn_blast_eval(sn_blast_ctx_t* ctx, sn_obj_id_t object) +{ + const sn_module_t* m = ctx->module; + assert(object < m->obj_types.size); + if (ctx->bits[object]) + return ctx->bits[object]; + assert(ctx->state[object] == 0); + ctx->state[object] = 1; + sn_obj_type_t type = sn_obj_type(m, object); + uint32_t width = sn_obj_width(m, object); + int* result = NULL; + if ((type == SN_PI || type == SN_INST || type == SN_FAN) && ctx->special_eval) + result = ctx->special_eval(ctx, object); + else if (type == SN_PO || type == SN_LOOP_OUT || type == SN_LOOP_IN) + { + sn_obj_id_t fanin = sn_obj_fanin(m, object, 0); + assert(fanin != SN_INVALID_ID); + int* source = sn_blast_eval(ctx, fanin); + result = sn_blast_alloc_bits(width); + sn_blast_copy(result, source, width); + } + else if (type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST) + { + result = sn_blast_alloc_bits(width); + for (uint32_t i = 0; i < width; i++) + result[i] = sn_const_bit(m, object, i) ? Mini_AigLitConst1() : Mini_AigLitConst0(); + } + else if (type == SN_BUF || type == SN_POS || type == SN_CAST) + { + sn_obj_id_t fanin = sn_obj_fanin(m, object, 0); + result = sn_blast_extend(ctx, fanin, width, type == SN_CAST ? sn_obj_is_signed(m, object) + : sn_obj_is_signed(m, fanin)); + } + else if (type == SN_CONCAT) + { + result = sn_blast_alloc_bits(width); + uint32_t offset = 0; + for (uint32_t i = 0; i < sn_obj_fanin_count(m, object); i++) + { + sn_obj_id_t fanin = sn_obj_fanin(m, object, i); + int* source = sn_blast_eval(ctx, fanin); + uint32_t source_width = sn_obj_width(m, fanin); + sn_blast_copy(result + offset, source, source_width); + offset += source_width; + } + assert(offset == width); + } + else if (type == SN_REPLICATE) + { + sn_obj_id_t fanin = sn_obj_fanin(m, object, 0); + int* source = sn_blast_eval(ctx, fanin); + uint32_t source_width = sn_obj_width(m, fanin); + uint32_t count = sn_obj_repeat_count(m, object); + result = sn_blast_alloc_bits(width); + for (uint32_t i = 0; i < count; i++) + sn_blast_copy(result + i * source_width, source, source_width); + } + else if (type == SN_SLICE) + { + sn_obj_id_t fanin = sn_obj_fanin(m, object, 0); + int* source = sn_blast_eval(ctx, fanin); + const sn_slice_info_t* info = sn_obj_slice_info(m, object); + result = sn_blast_alloc_bits(width); + for (uint32_t i = 0; i < width; i++) + { + int64_t index = info->left_index >= info->right_index ? (int64_t)info->right_index + i + : (int64_t)info->right_index - i; + assert(index >= 0 && (uint64_t)index < sn_obj_width(m, fanin)); + result[i] = source[index]; + } + } + else if (type == SN_BIT_NOT || type == SN_NEG || type == SN_LOG_NOT || + type == SN_REDUCE_AND || type == SN_REDUCE_NAND || type == SN_REDUCE_OR || + type == SN_REDUCE_NOR || type == SN_REDUCE_XOR || type == SN_REDUCE_XNOR) + { + sn_obj_id_t fanin = sn_obj_fanin(m, object, 0); + int* source = sn_blast_eval(ctx, fanin); + if (type == SN_BIT_NOT) + { + int* extended = sn_blast_extend(ctx, fanin, width, sn_obj_is_signed(m, fanin)); + result = sn_blast_alloc_bits(width); + for (uint32_t i = 0; i < width; i++) + result[i] = Mini_AigLitNot(extended[i]); + free(extended); + } + else if (type == SN_NEG) + { + int* extended = sn_blast_extend(ctx, fanin, width, sn_obj_is_signed(m, fanin)); + result = sn_blast_negate_vector(ctx->aig, extended, width, ctx->options.ripple_adders); + free(extended); + } + else + { + int reduced = type == SN_LOG_NOT + ? Mini_AigLitNot(sn_blast_or(ctx->aig, source, sn_obj_width(m, fanin))) + : type == SN_REDUCE_AND || type == SN_REDUCE_NAND + ? sn_blast_and(ctx->aig, source, sn_obj_width(m, fanin)) + : type == SN_REDUCE_OR || type == SN_REDUCE_NOR + ? sn_blast_or(ctx->aig, source, sn_obj_width(m, fanin)) + : sn_blast_xor(ctx->aig, source, sn_obj_width(m, fanin)); + if (type == SN_REDUCE_NAND || type == SN_REDUCE_NOR || type == SN_REDUCE_XNOR) + reduced = Mini_AigLitNot(reduced); + result = sn_blast_alloc_bits(width); + result[0] = reduced; + for (uint32_t i = 1; i < width; i++) + result[i] = 0; + } + } + else if (type == SN_BIT_AND || type == SN_BIT_OR || type == SN_BIT_XOR || type == SN_BIT_XNOR || + type == SN_LOG_AND || type == SN_LOG_OR) + { + sn_obj_id_t a_id = sn_obj_fanin(m, object, 0), b_id = sn_obj_fanin(m, object, 1); + bool logical = type == SN_LOG_AND || type == SN_LOG_OR; + uint32_t a_width = logical ? sn_obj_width(m, a_id) : width; + uint32_t b_width = logical ? sn_obj_width(m, b_id) : width; + int* a = sn_blast_extend(ctx, a_id, a_width, sn_obj_is_signed(m, a_id) && sn_obj_is_signed(m, b_id)); + int* b = sn_blast_extend(ctx, b_id, b_width, sn_obj_is_signed(m, a_id) && sn_obj_is_signed(m, b_id)); + result = sn_blast_alloc_bits(width); + if (logical) + { + int av = sn_blast_or(ctx->aig, a, a_width); + int bv = sn_blast_or(ctx->aig, b, b_width); + result[0] = type == SN_LOG_AND ? Mini_AigAnd(ctx->aig, av, bv) : Mini_AigOr(ctx->aig, av, bv); + for (uint32_t i = 1; i < width; i++) + result[i] = 0; + } + else + for (uint32_t i = 0; i < width; i++) + result[i] = type == SN_BIT_AND ? Mini_AigAnd(ctx->aig, a[i], b[i]) + : type == SN_BIT_OR ? Mini_AigOr(ctx->aig, a[i], b[i]) + : type == SN_BIT_XOR ? Mini_AigXor(ctx->aig, a[i], b[i]) + : Mini_AigLitNot(Mini_AigXor(ctx->aig, a[i], b[i])); + free(a); + free(b); + } + else if (type == SN_EQ || type == SN_NE || type == SN_CASE_EQ || type == SN_CASE_NE || + type == SN_WILDCARD_EQ || type == SN_WILDCARD_NE || type == SN_LT || type == SN_LE || + type == SN_GT || type == SN_GE) + { + sn_obj_id_t a_id = sn_obj_fanin(m, object, 0), b_id = sn_obj_fanin(m, object, 1); + uint32_t compare_width = sn_obj_width(m, a_id) > sn_obj_width(m, b_id) ? sn_obj_width(m, a_id) + : sn_obj_width(m, b_id); + bool signed_compare = sn_obj_is_signed(m, a_id) && sn_obj_is_signed(m, b_id); + int* a = sn_blast_extend(ctx, a_id, compare_width, signed_compare); + int* b = sn_blast_extend(ctx, b_id, compare_width, signed_compare); + int value; + if (type == SN_EQ || type == SN_CASE_EQ || type == SN_WILDCARD_EQ || type == SN_NE || + type == SN_CASE_NE || type == SN_WILDCARD_NE) + value = sn_blast_eq_bits(ctx->aig, a, b, compare_width); + else + { + int (*blast_gt)(Mini_Aig_t*, const int*, const int*, uint32_t, bool) = + ctx->options.delay_comparators ? sn_blast_gt_delay : sn_blast_gt; + if (type == SN_GT) + value = blast_gt(ctx->aig, a, b, compare_width, signed_compare); + else if (type == SN_LT) + value = blast_gt(ctx->aig, b, a, compare_width, signed_compare); + else if (type == SN_GE) + value = Mini_AigLitNot(blast_gt(ctx->aig, b, a, compare_width, signed_compare)); + else + value = Mini_AigLitNot(blast_gt(ctx->aig, a, b, compare_width, signed_compare)); + } + if (type == SN_NE || type == SN_CASE_NE || type == SN_WILDCARD_NE) + value = Mini_AigLitNot(value); + result = sn_blast_alloc_bits(width); + result[0] = value; + for (uint32_t i = 1; i < width; i++) + result[i] = 0; + free(a); + free(b); + } + else if (type == SN_ADD || type == SN_SUB) + { + sn_obj_id_t a_id = sn_obj_fanin(m, object, 0), b_id = sn_obj_fanin(m, object, 1); + bool signed_operands = sn_obj_is_signed(m, a_id) && sn_obj_is_signed(m, b_id); + int* a = sn_blast_extend(ctx, a_id, width, signed_operands); + int* b = sn_blast_extend(ctx, b_id, width, signed_operands); + result = sn_blast_add_vectors(ctx->aig, a, b, width, type == SN_SUB, ctx->options.ripple_adders); + free(a); + free(b); + } + else if (type == SN_MUL) + { + sn_obj_id_t a_id = sn_obj_fanin(m, object, 0), b_id = sn_obj_fanin(m, object, 1); + int* a = sn_blast_eval(ctx, a_id), *b = sn_blast_eval(ctx, b_id); + bool signed_operands = sn_obj_is_signed(m, a_id) && sn_obj_is_signed(m, b_id); + uint32_t a_width = sn_obj_width(m, a_id), b_width = sn_obj_width(m, b_id); + result = ctx->options.mul_mode == SN_BLAST_MUL_BOOTH + ? sn_blast_mul_booth(ctx->aig, a, signed_operands, a_width, b, signed_operands, b_width, width, + ctx->options.ripple_adders) + : sn_blast_mul_baugh_wooley(ctx->aig, a, signed_operands, a_width, b, signed_operands, b_width, + width, ctx->options.ripple_adders); + } + else if (type == SN_LUT) + { + uint32_t count = sn_obj_fanin_count(m, object); + int inputs[6]; + for (uint32_t i = 0; i < count; i++) + inputs[i] = sn_blast_eval(ctx, sn_obj_fanin(m, object, i))[0]; + result = sn_blast_alloc_bits(1); + result[0] = sn_blast_lut_rec(ctx->aig, inputs, count, sn_obj_lut_truth(m, object)); + } + else if (type == SN_DIV || type == SN_MOD) + { + sn_obj_id_t a_id = sn_obj_fanin(m, object, 0), b_id = sn_obj_fanin(m, object, 1); + uint32_t work_width = sn_obj_width(m, a_id) > sn_obj_width(m, b_id) ? sn_obj_width(m, a_id) + : sn_obj_width(m, b_id); + if (work_width < width) + work_width = width; + bool signed_operands = sn_obj_is_signed(m, a_id) && sn_obj_is_signed(m, b_id); + int* a = sn_blast_extend(ctx, a_id, work_width, signed_operands); + int* b = sn_blast_extend(ctx, b_id, work_width, signed_operands); + int* quotient_or_remainder = sn_blast_div_vectors(ctx->aig, a, b, work_width, signed_operands, + type == SN_MOD, ctx->options.ripple_adders, + ctx->options.delay_comparators); + result = sn_blast_alloc_bits(width); + for (uint32_t i = 0; i < width; i++) + result[i] = i < work_width ? quotient_or_remainder[i] + : (signed_operands ? quotient_or_remainder[work_width - 1] : 0); + free(a); + free(b); + free(quotient_or_remainder); + } + else if (type == SN_POW) + { + sn_obj_id_t base_id = sn_obj_fanin(m, object, 0), exponent_id = sn_obj_fanin(m, object, 1); + int* base = sn_blast_eval(ctx, base_id); + int* exponent = sn_blast_eval(ctx, exponent_id); + result = sn_blast_power(ctx->aig, base, sn_obj_width(m, base_id), sn_obj_is_signed(m, base_id), exponent, + sn_obj_width(m, exponent_id), sn_obj_is_signed(m, exponent_id), width, + ctx->options.mul_mode, ctx->options.ripple_adders); + } + else if (type == SN_SHL || type == SN_SHR || type == SN_ASHL || type == SN_ASHR) + result = sn_blast_shift(ctx, object, type == SN_SHL || type == SN_ASHL, type == SN_ASHR); + else if (type == SN_MUX) + { + int* select = sn_blast_eval(ctx, sn_obj_fanin(m, object, SN_MUX_SELECT)); + int* one = sn_blast_eval(ctx, sn_obj_fanin(m, object, SN_MUX_SELECTED)); + int* zero = sn_blast_eval(ctx, sn_obj_fanin(m, object, SN_MUX_DEFAULT)); + result = sn_blast_alloc_bits(width); + for (uint32_t i = 0; i < width; i++) + result[i] = Mini_AigMux(ctx->aig, select[0], one[i], zero[i]); + } + else if (type == SN_BMUX) + { + int* select = sn_blast_eval(ctx, sn_obj_fanin(m, object, 0)); + sn_obj_id_t alternatives_id = sn_obj_fanin(m, object, 1); + sn_obj_type_t alternatives_type = sn_obj_type(m, alternatives_id); + uint32_t select_width = sn_obj_width(m, sn_obj_fanin(m, object, 0)); + if (alternatives_type == SN_CONST0 || alternatives_type == SN_CONST1 || alternatives_type == SN_CONST) + result = sn_blast_const_mux_tree(ctx->aig, m, alternatives_id, select, select_width, width); + else + { + int* alternatives = sn_blast_eval(ctx, alternatives_id); + result = sn_blast_mux_tree(ctx->aig, select, select_width, alternatives, width); + } + } + else if (type == SN_PMUX) + { + int* select = sn_blast_eval(ctx, sn_obj_fanin(m, object, 0)); + int* alternatives = sn_blast_eval(ctx, sn_obj_fanin(m, object, 1)); + int* default_value = sn_blast_eval(ctx, sn_obj_fanin(m, object, 2)); + uint32_t select_width = sn_obj_width(m, sn_obj_fanin(m, object, 0)); + int any_select = sn_blast_or(ctx->aig, select, select_width); + result = sn_blast_alloc_bits(width); + int* terms = sn_blast_alloc_bits(select_width + 1); + for (uint32_t bit = 0; bit < width; bit++) + { + for (uint32_t i = 0; i < select_width; i++) + terms[i] = Mini_AigAnd(ctx->aig, select[i], alternatives[i * width + bit]); + terms[select_width] = Mini_AigAnd(ctx->aig, Mini_AigLitNot(any_select), default_value[bit]); + result[bit] = sn_blast_or(ctx->aig, terms, select_width + 1); + } + free(terms); + } + else + assert(false); + assert(result); + ctx->bits[object] = result; + ctx->state[object] = 2; + return result; +} + +static inline void sn_blast_check_module(const sn_module_t* module, sn_blast_options_t options) +{ + assert(module); + for (sn_obj_id_t object = 0; object < module->obj_types.size; object++) + { + sn_obj_type_t type = sn_obj_type(module, object); + assert(type != SN_INST && type != SN_FAN); + if (type == SN_LUT) + assert(sn_obj_fanin_count(module, object) <= 6); + if (type == SN_REG_OUT) + assert(!(sn_vec_at(uint32_t, &module->reg_flags, sn_obj_type_id(module, object)) & SN_REG_LATCH)); + if (!options.abstract_memories) + assert(type != SN_MEM_OUT && type != SN_MEM_IN && type != SN_MEM_READ && type != SN_MEM_WRITE); + } + assert(sn_module_is_topo(module)); +} + +static inline Mini_Aig_t* sn_module_blast_comb_options(const sn_module_t* module, + sn_blast_options_t options); + +static inline Mini_Aig_t* sn_module_blast_comb(const sn_module_t* module) +{ + return sn_module_blast_comb_options(module, sn_blast_default_options()); +} + +static inline Mini_Aig_t* sn_module_blast_seq_options(const sn_module_t* module, sn_blast_options_t options) +{ + options.mode = SN_BLAST_SEQ; + return sn_module_blast_comb_options(module, options); +} + +static inline Mini_Aig_t* sn_module_blast_seq(const sn_module_t* module) +{ + return sn_module_blast_seq_options(module, sn_blast_default_options()); +} + +typedef struct sn_blast_hier_stats_t +{ + uint64_t primary_input_bits; + uint64_t primary_output_bits; + uint64_t flop_bits; + uint64_t register_control_bits; + uint64_t memory_count; + uint64_t multiplier_count; + uint64_t abstraction_input_bits; + uint64_t abstraction_output_bits; +} sn_blast_hier_stats_t; + +typedef enum sn_blast_boundary_kind_t +{ + SN_BLAST_BOUNDARY_TOP_PI, + SN_BLAST_BOUNDARY_MEMORY_OUTPUT, + SN_BLAST_BOUNDARY_PRIMITIVE_OUTPUT, + SN_BLAST_BOUNDARY_REG_OUTPUT, + SN_BLAST_BOUNDARY_LOOP_OUTPUT, + SN_BLAST_BOUNDARY_TOP_PO, + SN_BLAST_BOUNDARY_REG_CONTROL, + SN_BLAST_BOUNDARY_MEMORY_INPUT, + SN_BLAST_BOUNDARY_PRIMITIVE_INPUT, + SN_BLAST_BOUNDARY_REG_INPUT, + SN_BLAST_BOUNDARY_LOOP_INPUT +} sn_blast_boundary_kind_t; + +typedef struct sn_blast_hier_ref_t +{ + uint32_t occurrence; + sn_obj_id_t object; + uint32_t bit; +} sn_blast_hier_ref_t; + +typedef struct sn_blast_boundary_bit_t +{ + sn_blast_boundary_kind_t kind; + sn_blast_hier_ref_t signal; + uint32_t owner; + uint32_t port; +} sn_blast_boundary_bit_t; + +typedef struct sn_blast_occurrence_t +{ + sn_module_id_t module; + uint32_t parent_occurrence; + sn_obj_id_t parent_inst; +} sn_blast_occurrence_t; + +typedef struct sn_blast_primitive_t +{ + uint32_t occurrence; + sn_obj_id_t inst; + sn_module_id_t module; + uint32_t ci_begin; + uint32_t ci_count; + uint32_t co_begin; + uint32_t co_count; +} sn_blast_primitive_t; + +typedef struct sn_blast_register_t +{ + uint32_t occurrence; + sn_obj_id_t reg_out; + uint32_t ci_begin; + uint32_t co_begin; + uint32_t control_co_begin[SN_REG_FANIN_COUNT]; + uint32_t width; +} sn_blast_register_t; + +typedef struct sn_blast_loop_t +{ + uint32_t occurrence; + sn_obj_id_t loop_out; + uint32_t co_begin; + uint32_t width; +} sn_blast_loop_t; + +typedef struct sn_blast_boundary_t +{ + uint32_t register_bits; + sn_vec_t occurrences; // sn_blast_occurrence_t + sn_vec_t primitives; // sn_blast_primitive_t + sn_vec_t registers; // sn_blast_register_t + sn_vec_t loops; // sn_blast_loop_t + sn_vec_t cis; // sn_blast_boundary_bit_t in MiniAIG PI order + sn_vec_t cos; // sn_blast_boundary_bit_t in MiniAIG PO order +} sn_blast_boundary_t; + +static inline void sn_blast_boundary_init(sn_blast_boundary_t* boundary) +{ + assert(boundary); + boundary->register_bits = 0; + sn_vec_init(&boundary->occurrences); + sn_vec_init(&boundary->primitives); + sn_vec_init(&boundary->registers); + sn_vec_init(&boundary->loops); + sn_vec_init(&boundary->cis); + sn_vec_init(&boundary->cos); +} + +static inline void sn_blast_boundary_destroy(sn_blast_boundary_t* boundary) +{ + assert(boundary); + boundary->register_bits = 0; + sn_vec_destroy(&boundary->occurrences); + sn_vec_destroy(&boundary->primitives); + sn_vec_destroy(&boundary->registers); + sn_vec_destroy(&boundary->loops); + sn_vec_destroy(&boundary->cis); + sn_vec_destroy(&boundary->cos); +} + +typedef struct sn_blast_hier_t sn_blast_hier_t; + +typedef struct sn_blast_hier_frame_t +{ + sn_blast_hier_t* hierarchy; + sn_blast_ctx_t blast; + struct sn_blast_hier_frame_t** children; + uint32_t occurrence; + struct sn_blast_hier_frame_t* parent; + sn_obj_id_t parent_inst; +} sn_blast_hier_frame_t; + +typedef struct sn_blast_hier_object_t +{ + sn_blast_hier_frame_t* frame; + sn_obj_id_t object; + uint32_t boundary_owner; +} sn_blast_hier_object_t; + +struct sn_blast_hier_t +{ + const sn_design_t* design; + Mini_Aig_t* aig; + sn_blast_options_t options; + sn_blast_hier_stats_t stats; + sn_blast_boundary_t* boundary; + uint8_t* active_modules; + sn_vec_t memory_reads; + sn_vec_t memory_writes; + sn_vec_t registers; + sn_vec_t loops; + sn_vec_t abstract_insts; +}; + +static inline bool sn_blast_reg_control_is_comb_output(const sn_module_t* module, sn_obj_id_t reg_out, + uint32_t slot) +{ + uint32_t flags = sn_obj_reg_flags(module, reg_out); + if (slot == SN_REG_ENABLE) + return true; + if (slot == SN_REG_SET) + return !(flags & SN_REG_SET_ASYNC); + if (slot == SN_REG_RESET) + return !(flags & SN_REG_RESET_ASYNC); + if (slot == SN_REG_RESET_VALUE) + return sn_obj_fanin(module, reg_out, SN_REG_RESET) != SN_INVALID_ID && !(flags & SN_REG_RESET_ASYNC); + return false; +} + +static inline bool sn_blast_hier_is_abstract_inst(const sn_blast_hier_t* hierarchy, + const sn_module_t* module, sn_obj_id_t inst) +{ + const sn_module_t* child = sn_design_get_module_const(hierarchy->design, + sn_inst_module_id(module, inst)); + const char* name = sn_name_get(&hierarchy->design->names, child->name); + bool memory = strncmp(name, "__sn_RAM", 8) == 0 || strncmp(name, "__sn_URAM", 9) == 0; + bool multiplier = strncmp(name, "__sn_DSP", 8) == 0; + bool carry = strncmp(name, "__sn_CARRY", 10) == 0; + return hierarchy->options.abstract_instances || (memory && hierarchy->options.abstract_memories) || + (multiplier && hierarchy->options.abstract_multipliers) || carry; +} + +static inline sn_blast_hier_object_t* sn_blast_hier_add_object(sn_vec_t* objects, sn_blast_hier_frame_t* frame, + sn_obj_id_t object) +{ + sn_blast_hier_object_t* entry = sn_vec_push(sn_blast_hier_object_t, objects); + entry->frame = frame; + entry->object = object; + entry->boundary_owner = SN_INVALID_ID; + return entry; +} + +static inline uint64_t sn_blast_hier_memory_input_bits(const sn_module_t* module, sn_obj_id_t object) +{ + sn_obj_type_t type = sn_obj_type(module, object); + uint32_t first = type == SN_MEM_READ ? (uint32_t)SN_MEM_READ_CLOCK : (uint32_t)SN_MEM_WRITE_CLOCK; + uint32_t count = type == SN_MEM_READ ? (uint32_t)SN_MEM_READ_FANIN_COUNT + : (uint32_t)SN_MEM_WRITE_FANIN_COUNT; + uint64_t bits = 0; + for (uint32_t slot = first; slot < count; slot++) + { + sn_obj_id_t fanin = sn_obj_fanin(module, object, slot); + if (fanin != SN_INVALID_ID) + bits += sn_obj_width(module, fanin); + } + return bits; +} + +static inline sn_blast_hier_frame_t* sn_blast_hier_build_frame(sn_blast_hier_t* hierarchy, sn_module_id_t module_id, + uint32_t parent_occurrence, + sn_obj_id_t parent_inst, + sn_blast_hier_frame_t* parent_frame) +{ + assert(module_id < hierarchy->design->modules.size); + assert(!hierarchy->active_modules[module_id]); + hierarchy->active_modules[module_id] = 1; + const sn_module_t* module = sn_design_get_module_const(hierarchy->design, module_id); + assert(sn_module_is_topo(module)); + for (size_t i = 0; i < module->reg_flags.size; i++) + assert(!(sn_vec_at(uint32_t, &module->reg_flags, i) & SN_REG_LATCH)); + for (size_t i = 0; i < module->type_objects[SN_LUT].size; i++) + assert(sn_obj_fanin_count(module, sn_vec_at(sn_obj_id_t, &module->type_objects[SN_LUT], i)) <= 6); + + sn_blast_hier_frame_t* frame = (sn_blast_hier_frame_t*)calloc(1, sizeof(sn_blast_hier_frame_t)); + assert(frame); + size_t object_count = module->obj_types.size; + frame->hierarchy = hierarchy; + frame->parent = parent_frame; + frame->parent_inst = parent_inst; + frame->occurrence = SN_INVALID_ID; + if (hierarchy->boundary) + { + assert(hierarchy->boundary->occurrences.size < UINT32_MAX); + frame->occurrence = (uint32_t)hierarchy->boundary->occurrences.size; + sn_blast_occurrence_t* occurrence = sn_vec_push(sn_blast_occurrence_t, &hierarchy->boundary->occurrences); + occurrence->module = module_id; + occurrence->parent_occurrence = parent_occurrence; + occurrence->parent_inst = parent_inst; + } + frame->blast.module = module; + frame->blast.aig = NULL; + frame->blast.options = hierarchy->options; + frame->blast.bits = (int**)calloc(object_count, sizeof(int*)); + frame->blast.state = (uint8_t*)calloc(object_count, sizeof(uint8_t)); + frame->children = (sn_blast_hier_frame_t**)calloc(object_count, sizeof(sn_blast_hier_frame_t*)); + assert(frame->blast.bits && frame->blast.state && frame->children); + + // Boundary order must not depend on physical/topological object order, because a word-level transform can + // legitimately rebuild that order. Type IDs are the stable natural order preserved by SN duplication. Collect + // each class in type-ID order and recurse through child occurrences in natural instance order. Register bits then + // have the canonical key (depth-first instance path, register type ID, LSB-first bit index). + hierarchy->stats.memory_count += module->type_objects[SN_MEM_OUT].size; + if (hierarchy->options.abstract_memories) + { + for (size_t i = 0; i < module->type_objects[SN_MEM_READ].size; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_MEM_READ], i); + sn_blast_hier_add_object(&hierarchy->memory_reads, frame, object); + hierarchy->stats.abstraction_output_bits += sn_obj_width(module, object); + hierarchy->stats.abstraction_input_bits += sn_blast_hier_memory_input_bits(module, object); + } + } + if (hierarchy->options.abstract_memories) + { + for (size_t i = 0; i < module->type_objects[SN_MEM_WRITE].size; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_MEM_WRITE], i); + sn_blast_hier_add_object(&hierarchy->memory_writes, frame, object); + hierarchy->stats.abstraction_input_bits += sn_blast_hier_memory_input_bits(module, object); + } + } + for (size_t i = 0; i < module->type_objects[SN_REG_OUT].size; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_REG_OUT], i); + sn_blast_hier_object_t* entry = sn_blast_hier_add_object(&hierarchy->registers, frame, object); + if (hierarchy->boundary) + { + assert(hierarchy->boundary->registers.size < UINT32_MAX); + entry->boundary_owner = (uint32_t)hierarchy->boundary->registers.size; + sn_blast_register_t* reg = sn_vec_push(sn_blast_register_t, &hierarchy->boundary->registers); + reg->occurrence = frame->occurrence; + reg->reg_out = object; + reg->ci_begin = SN_INVALID_ID; + reg->co_begin = SN_INVALID_ID; + for (uint32_t slot = 0; slot < SN_REG_FANIN_COUNT; slot++) + reg->control_co_begin[slot] = SN_INVALID_ID; + reg->width = sn_obj_width(module, object); + } + hierarchy->stats.flop_bits += sn_obj_width(module, object); + if (hierarchy->options.expose_register_controls) + { + const uint32_t slots[] = {SN_REG_ENABLE, SN_REG_SET, SN_REG_RESET, SN_REG_RESET_VALUE}; + for (size_t j = 0; j < sizeof(slots) / sizeof(slots[0]); j++) + { + if (hierarchy->options.mode != SN_BLAST_COMB || + !sn_blast_reg_control_is_comb_output(module, object, slots[j])) + continue; + sn_obj_id_t fanin = sn_obj_fanin(module, object, slots[j]); + if (fanin != SN_INVALID_ID) + hierarchy->stats.register_control_bits += sn_obj_width(module, fanin); + } + } + } + for (size_t i = 0; i < module->type_objects[SN_LOOP_OUT].size; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_LOOP_OUT], i); + sn_blast_hier_object_t* entry = sn_blast_hier_add_object(&hierarchy->loops, frame, object); + if (hierarchy->boundary) + { + assert(hierarchy->boundary->loops.size < UINT32_MAX); + entry->boundary_owner = (uint32_t)hierarchy->boundary->loops.size; + sn_blast_loop_t* loop = sn_vec_push(sn_blast_loop_t, &hierarchy->boundary->loops); + loop->occurrence = frame->occurrence; + loop->loop_out = object; + loop->co_begin = SN_INVALID_ID; + loop->width = sn_obj_width(module, object); + } + hierarchy->stats.abstraction_output_bits += sn_obj_width(module, object); + hierarchy->stats.abstraction_input_bits += sn_obj_width(module, object); + } + for (size_t i = 0; i < module->type_objects[SN_INST].size; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_INST], i); + const sn_module_t* child = sn_design_get_module_const(hierarchy->design, + sn_inst_module_id(module, object)); + if (sn_blast_hier_is_abstract_inst(hierarchy, module, object)) + { + sn_blast_hier_object_t* entry = sn_blast_hier_add_object(&hierarchy->abstract_insts, frame, object); + if (hierarchy->boundary) + { + assert(hierarchy->boundary->primitives.size < UINT32_MAX); + entry->boundary_owner = (uint32_t)hierarchy->boundary->primitives.size; + sn_blast_primitive_t* primitive = + sn_vec_push(sn_blast_primitive_t, &hierarchy->boundary->primitives); + primitive->occurrence = frame->occurrence; + primitive->inst = object; + primitive->module = child->id; + primitive->ci_begin = SN_INVALID_ID; + primitive->ci_count = 0; + primitive->co_begin = SN_INVALID_ID; + primitive->co_count = 0; + } + const char* name = sn_name_get(&hierarchy->design->names, child->name); + if (strncmp(name, "__sn_DSP", 8) == 0) + hierarchy->stats.multiplier_count++; + else if (strncmp(name, "__sn_RAM", 8) == 0 || strncmp(name, "__sn_URAM", 9) == 0) + hierarchy->stats.memory_count++; + for (size_t j = 0; j < child->type_objects[SN_PO].size; j++) + { + sn_obj_id_t output = sn_vec_at(sn_obj_id_t, &child->type_objects[SN_PO], j); + hierarchy->stats.abstraction_output_bits += sn_obj_width(child, output); + } + for (uint32_t j = 0; j < sn_obj_fanin_count(module, object); j++) + hierarchy->stats.abstraction_input_bits += sn_obj_width(module, sn_obj_fanin(module, object, j)); + } + else + frame->children[object] = + sn_blast_hier_build_frame(hierarchy, child->id, frame->occurrence, object, frame); + } + hierarchy->active_modules[module_id] = 0; + return frame; +} + +static inline int* sn_blast_hier_eval_inst(sn_blast_ctx_t* context, sn_obj_id_t object) +{ + // sn_blast_eval() caches this result on the parent SN_INST or SN_FAN object, while the child PO evaluation below + // is cached in the child occurrence frame. Thus each used output cone is built once per instance occurrence; + // subsequent fanouts neither re-enter the child nor rebuild its logic. Unused outputs remain unexpanded. + sn_blast_hier_frame_t* frame = (sn_blast_hier_frame_t*)context->special_data; + const sn_module_t* module = context->module; + sn_obj_type_t type = sn_obj_type(module, object); + if (type == SN_PI) + { + assert(frame->parent && frame->parent_inst != SN_INVALID_ID); + uint32_t port = sn_obj_type_id(module, object); + sn_obj_id_t parent_fanin = sn_obj_fanin(frame->parent->blast.module, frame->parent_inst, port); + assert(sn_obj_width(module, object) == sn_obj_width(frame->parent->blast.module, parent_fanin)); + int* source = sn_blast_eval(&frame->parent->blast, parent_fanin); + int* result = sn_blast_alloc_bits(sn_obj_width(module, object)); + sn_blast_copy(result, source, sn_obj_width(module, object)); + return result; + } + sn_obj_id_t inst = type == SN_INST ? object : sn_fan_inst_id(module, object); + uint32_t output_index = type == SN_INST ? 0 : sn_fan_output_index(module, object); + sn_blast_hier_frame_t* child = frame->children[inst]; + assert(child); // Abstract insts have their output bits pre-seeded as CIs. + const sn_module_t* child_module = child->blast.module; + assert(output_index < child_module->type_objects[SN_PO].size); + sn_obj_id_t child_po = sn_vec_at(sn_obj_id_t, &child_module->type_objects[SN_PO], output_index); + int* source = sn_blast_eval(&child->blast, child_po); + uint32_t width = sn_obj_width(module, object); + assert(width == sn_obj_width(child_module, child_po)); + int* result = sn_blast_alloc_bits(width); + sn_blast_copy(result, source, width); + return result; +} + +static inline void sn_blast_hier_prepare_frame(sn_blast_hier_frame_t* frame, Mini_Aig_t* aig, + sn_blast_options_t options) +{ + frame->blast.aig = aig; + frame->blast.options = options; + frame->blast.special_eval = sn_blast_hier_eval_inst; + frame->blast.special_data = frame; + for (sn_obj_id_t object = 0; object < frame->blast.module->obj_types.size; object++) + if (frame->children[object]) + sn_blast_hier_prepare_frame(frame->children[object], aig, options); +} + +static inline void sn_blast_hier_seed_object(sn_blast_hier_frame_t* frame, sn_obj_id_t object, Mini_Aig_t* aig, + bool invert) +{ + const sn_module_t* module = frame->blast.module; + uint32_t width = sn_obj_width(module, object); + assert(!frame->blast.bits[object]); + frame->blast.bits[object] = sn_blast_alloc_bits(width); + for (uint32_t bit = 0; bit < width; bit++) + { + int input = Mini_AigCreatePi(aig); + frame->blast.bits[object][bit] = invert ? Mini_AigLitNot(input) : input; + } + frame->blast.state[object] = 2; +} + +static inline void sn_blast_boundary_add_bit(sn_blast_hier_t* hierarchy, bool is_ci, + sn_blast_boundary_kind_t kind, sn_blast_hier_frame_t* frame, + sn_obj_id_t object, uint32_t bit, uint32_t owner, uint32_t port) +{ + if (!hierarchy->boundary) + return; + sn_vec_t* bits = is_ci ? &hierarchy->boundary->cis : &hierarchy->boundary->cos; + sn_blast_boundary_bit_t* entry = sn_vec_push(sn_blast_boundary_bit_t, bits); + entry->kind = kind; + entry->signal.occurrence = frame->occurrence; + entry->signal.object = object; + entry->signal.bit = bit; + entry->owner = owner; + entry->port = port; +} + +static inline void sn_blast_hier_seed_abstract_inst(sn_blast_hier_object_t occurrence, Mini_Aig_t* aig) +{ + const sn_module_t* module = occurrence.frame->blast.module; + sn_obj_id_t inst = occurrence.object; + const sn_module_t* child = sn_design_get_module_const(occurrence.frame->hierarchy->design, + sn_inst_module_id(module, inst)); + uint32_t output_count = (uint32_t)child->type_objects[SN_PO].size; + sn_blast_primitive_t* primitive = NULL; + if (occurrence.frame->hierarchy->boundary) + { + primitive = &sn_vec_at(sn_blast_primitive_t, &occurrence.frame->hierarchy->boundary->primitives, + occurrence.boundary_owner); + // The boundary vector is the running CI count. Mini_AigPiNum() scans the whole manager and must not be used + // here because this routine is called once per primitive occurrence. + primitive->ci_begin = (uint32_t)occurrence.frame->hierarchy->boundary->cis.size; + } + for (uint32_t i = 0; i < output_count; i++) + { + sn_obj_id_t output = output_count == 1 ? inst : sn_inst_output(module, inst, i); + sn_blast_hier_seed_object(occurrence.frame, output, aig, false); + for (uint32_t bit = 0; bit < sn_obj_width(module, output); bit++) + sn_blast_boundary_add_bit(occurrence.frame->hierarchy, true, + SN_BLAST_BOUNDARY_PRIMITIVE_OUTPUT, occurrence.frame, output, bit, + occurrence.boundary_owner, i); + } + if (primitive) + primitive->ci_count = (uint32_t)occurrence.frame->hierarchy->boundary->cis.size - primitive->ci_begin; +} + +static inline void sn_blast_hier_emit_memory_inputs(sn_blast_hier_object_t occurrence, Mini_Aig_t* aig) +{ + const sn_module_t* module = occurrence.frame->blast.module; + sn_obj_type_t type = sn_obj_type(module, occurrence.object); + uint32_t first = type == SN_MEM_READ ? (uint32_t)SN_MEM_READ_CLOCK : (uint32_t)SN_MEM_WRITE_CLOCK; + uint32_t count = type == SN_MEM_READ ? (uint32_t)SN_MEM_READ_FANIN_COUNT + : (uint32_t)SN_MEM_WRITE_FANIN_COUNT; + for (uint32_t slot = first; slot < count; slot++) + { + sn_obj_id_t fanin = sn_obj_fanin(module, occurrence.object, slot); + if (fanin == SN_INVALID_ID) + continue; + int* bits = sn_blast_eval(&occurrence.frame->blast, fanin); + for (uint32_t bit = 0; bit < sn_obj_width(module, fanin); bit++) + { + Mini_AigCreatePo(aig, bits[bit]); + sn_blast_boundary_add_bit(occurrence.frame->hierarchy, false, SN_BLAST_BOUNDARY_MEMORY_INPUT, + occurrence.frame, fanin, bit, SN_INVALID_ID, slot); + } + } +} + +static inline void sn_blast_hier_emit_register_controls(sn_blast_hier_object_t occurrence, Mini_Aig_t* aig) +{ + sn_blast_hier_t* hierarchy = occurrence.frame->hierarchy; + if (!hierarchy->options.expose_register_controls || hierarchy->options.mode != SN_BLAST_COMB) + return; + const sn_module_t* module = occurrence.frame->blast.module; + const uint32_t slots[] = {SN_REG_ENABLE, SN_REG_SET, SN_REG_RESET, SN_REG_RESET_VALUE}; + for (size_t i = 0; i < sizeof(slots) / sizeof(slots[0]); i++) + { + uint32_t slot = slots[i]; + if (!sn_blast_reg_control_is_comb_output(module, occurrence.object, slot)) + continue; + sn_obj_id_t fanin = sn_obj_fanin(module, occurrence.object, slot); + if (fanin == SN_INVALID_ID) + continue; + int* bits = sn_blast_eval(&occurrence.frame->blast, fanin); + if (hierarchy->boundary) + sn_vec_at(sn_blast_register_t, &hierarchy->boundary->registers, + occurrence.boundary_owner).control_co_begin[slot] = + (uint32_t)hierarchy->boundary->cos.size; + for (uint32_t bit = 0; bit < sn_obj_width(module, fanin); bit++) + { + Mini_AigCreatePo(aig, bits[bit]); + sn_blast_boundary_add_bit(hierarchy, false, SN_BLAST_BOUNDARY_REG_CONTROL, occurrence.frame, + fanin, bit, occurrence.boundary_owner, slot); + } + } +} + +// Constructs the edge-triggered next-state function. Synchronous reset has +// highest priority, followed by synchronous set, enable, and the raw data input, +// matching the SN Verilog writer. Clock and asynchronous controls deliberately +// remain outside the sequential AIG transition relation. +static inline int* sn_blast_hier_reg_next(sn_blast_hier_object_t occurrence) +{ + sn_blast_ctx_t* context = &occurrence.frame->blast; + const sn_module_t* module = context->module; + sn_obj_id_t reg_out = occurrence.object; + sn_obj_id_t reg_in = sn_obj_fanin(module, reg_out, SN_REG_DATA); + sn_obj_id_t data = sn_obj_fanin(module, reg_in, 0); + uint32_t width = sn_obj_width(module, reg_out); + uint32_t flags = sn_obj_reg_flags(module, reg_out); + int* source = sn_blast_eval(context, data); + int* result = sn_blast_alloc_bits(width); + sn_blast_copy(result, source, width); + + sn_obj_id_t enable = sn_obj_fanin(module, reg_out, SN_REG_ENABLE); + if (enable != SN_INVALID_ID) + { + int control = sn_blast_eval(context, enable)[0]; + int* state = sn_blast_eval(context, reg_out); + for (uint32_t bit = 0; bit < width; bit++) + result[bit] = Mini_AigMux(context->aig, control, result[bit], state[bit]); + } + + sn_obj_id_t set = sn_obj_fanin(module, reg_out, SN_REG_SET); + if (set != SN_INVALID_ID && !(flags & SN_REG_SET_ASYNC)) + { + int control = sn_blast_eval(context, set)[0]; + if (flags & SN_REG_SET_NEGEDGE) + control = Mini_AigLitNot(control); + for (uint32_t bit = 0; bit < width; bit++) + result[bit] = Mini_AigMux(context->aig, control, Mini_AigLitConst1(), result[bit]); + } + + sn_obj_id_t reset = sn_obj_fanin(module, reg_out, SN_REG_RESET); + if (reset != SN_INVALID_ID && !(flags & SN_REG_RESET_ASYNC)) + { + int control = sn_blast_eval(context, reset)[0]; + if (flags & SN_REG_RESET_NEGEDGE) + control = Mini_AigLitNot(control); + sn_obj_id_t value = sn_obj_fanin(module, reg_out, SN_REG_RESET_VALUE); + int* reset_bits = value == SN_INVALID_ID ? NULL : sn_blast_eval(context, value); + for (uint32_t bit = 0; bit < width; bit++) + result[bit] = Mini_AigMux(context->aig, control, + reset_bits ? reset_bits[bit] : Mini_AigLitConst0(), result[bit]); + } + return result; +} + +static inline void sn_blast_hier_destroy_frame(sn_blast_hier_frame_t* frame) +{ + for (sn_obj_id_t object = 0; object < frame->blast.module->obj_types.size; object++) + { + if (frame->children[object]) + sn_blast_hier_destroy_frame(frame->children[object]); + free(frame->blast.bits[object]); + } + free(frame->blast.bits); + free(frame->blast.state); + free(frame->children); + free(frame); +} + +// Derives one flat MiniAIG directly from a hierarchical SN design without first +// materializing a flat SN module. Every reachable module must be in SN +// topological order. A preliminary depth-first walk builds one lightweight +// object-to-literal frame per inst occurrence and counts top-level ports, +// flop bits, generic memories, and mapped RAM/DSP leaf insts. This permits +// all MiniAIG CIs to be created before the first AND: top PIs first, abstracted +// memory/DSP outputs next, and flop outputs last. The second depth-first walk +// binds each child PI to its inst fanin and bit-blasts child outputs in +// place. It emits top POs first, combinational register-control side outputs and +// abstract-box inputs next, and flop inputs last; the flop CIs and COs therefore +// occupy MiniAIG's required final positions. +// Every final MiniAIG node is created directly in this one manager: there is no +// temporary AIG, AIG duplication, or AIG-literal remapping pass. Consequently +// the AIG is flat while the usually much larger, attribute-rich collapsed SN +// module is never allocated. Generic memories and mapped RAM/DSP insts are +// black-boxed as extra CI/CO bundles according to the options. +// +// The boundary variant fills an initialized, empty descriptor whose stable +// occurrence/object/bit references survive destruction of the temporary frames. +// In combinational mode, synchronous enable/set/reset inputs become side COs +// before the raw register-data COs; clock and asynchronous controls are omitted. +// In sequential mode these synchronous controls are folded into the effective D +// function instead. The caller releases the descriptor with +// sn_blast_boundary_destroy(). +static inline Mini_Aig_t* sn_design_blast_hier_boundary_options(const sn_design_t* design, + sn_module_id_t top_module_id, + sn_blast_options_t options, + sn_blast_hier_stats_t* returned_stats, + sn_blast_boundary_t* boundary) +{ + assert(design && top_module_id < design->modules.size); + sn_blast_hier_t hierarchy; + memset(&hierarchy, 0, sizeof(hierarchy)); + hierarchy.design = design; + hierarchy.options = options; + hierarchy.boundary = boundary; + if (boundary) + assert(boundary->occurrences.size == 0 && boundary->primitives.size == 0 && + boundary->registers.size == 0 && boundary->loops.size == 0 && + boundary->cis.size == 0 && boundary->cos.size == 0); + hierarchy.active_modules = (uint8_t*)calloc(design->modules.size, sizeof(uint8_t)); + assert(hierarchy.active_modules); + sn_vec_init(&hierarchy.memory_reads); + sn_vec_init(&hierarchy.memory_writes); + sn_vec_init(&hierarchy.registers); + sn_vec_init(&hierarchy.loops); + sn_vec_init(&hierarchy.abstract_insts); + + sn_blast_hier_frame_t* root = + sn_blast_hier_build_frame(&hierarchy, top_module_id, SN_INVALID_ID, SN_INVALID_ID, NULL); + const sn_module_t* top = root->blast.module; + for (size_t i = 0; i < top->type_objects[SN_PI].size; i++) + hierarchy.stats.primary_input_bits += + sn_obj_width(top, sn_vec_at(sn_obj_id_t, &top->type_objects[SN_PI], i)); + for (size_t i = 0; i < top->type_objects[SN_PO].size; i++) + hierarchy.stats.primary_output_bits += + sn_obj_width(top, sn_vec_at(sn_obj_id_t, &top->type_objects[SN_PO], i)); + + hierarchy.aig = Mini_AigStart(); + sn_blast_hier_prepare_frame(root, hierarchy.aig, options); + for (size_t i = 0; i < top->type_objects[SN_PI].size; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &top->type_objects[SN_PI], i); + sn_blast_hier_seed_object(root, object, hierarchy.aig, false); + for (uint32_t bit = 0; bit < sn_obj_width(top, object); bit++) + sn_blast_boundary_add_bit(&hierarchy, true, SN_BLAST_BOUNDARY_TOP_PI, root, object, bit, + SN_INVALID_ID, (uint32_t)i); + } + for (size_t i = 0; i < hierarchy.memory_reads.size; i++) + { + sn_blast_hier_object_t occurrence = sn_vec_at(sn_blast_hier_object_t, &hierarchy.memory_reads, i); + sn_blast_hier_seed_object(occurrence.frame, occurrence.object, hierarchy.aig, false); + for (uint32_t bit = 0; bit < sn_obj_width(occurrence.frame->blast.module, occurrence.object); bit++) + sn_blast_boundary_add_bit(&hierarchy, true, SN_BLAST_BOUNDARY_MEMORY_OUTPUT, occurrence.frame, + occurrence.object, bit, SN_INVALID_ID, (uint32_t)i); + } + for (size_t i = 0; i < hierarchy.abstract_insts.size; i++) + sn_blast_hier_seed_abstract_inst( + sn_vec_at(sn_blast_hier_object_t, &hierarchy.abstract_insts, i), hierarchy.aig); + for (size_t i = 0; i < hierarchy.loops.size; i++) + { + sn_blast_hier_object_t occurrence = sn_vec_at(sn_blast_hier_object_t, &hierarchy.loops, i); + sn_blast_hier_seed_object(occurrence.frame, occurrence.object, hierarchy.aig, false); + for (uint32_t bit = 0; bit < sn_obj_width(occurrence.frame->blast.module, occurrence.object); bit++) + sn_blast_boundary_add_bit(&hierarchy, true, SN_BLAST_BOUNDARY_LOOP_OUTPUT, occurrence.frame, + occurrence.object, bit, occurrence.boundary_owner, bit); + } + for (size_t i = 0; i < hierarchy.registers.size; i++) + { + sn_blast_hier_object_t occurrence = sn_vec_at(sn_blast_hier_object_t, &hierarchy.registers, i); + if (boundary) + sn_vec_at(sn_blast_register_t, &boundary->registers, occurrence.boundary_owner).ci_begin = + (uint32_t)boundary->cis.size; + sn_blast_hier_seed_object(occurrence.frame, occurrence.object, hierarchy.aig, false); + if (sn_blast_mode_has_transition(options.mode)) + for (uint32_t bit = 0; + bit < sn_obj_width(occurrence.frame->blast.module, occurrence.object); bit++) + if (sn_blast_reg_init_bit(occurrence.frame->blast.module, occurrence.object, bit)) + occurrence.frame->blast.bits[occurrence.object][bit] = + Mini_AigLitNot(occurrence.frame->blast.bits[occurrence.object][bit]); + for (uint32_t bit = 0; bit < sn_obj_width(occurrence.frame->blast.module, occurrence.object); bit++) + sn_blast_boundary_add_bit(&hierarchy, true, SN_BLAST_BOUNDARY_REG_OUTPUT, occurrence.frame, + occurrence.object, bit, occurrence.boundary_owner, bit); + } + assert((uint64_t)Mini_AigPiNum(hierarchy.aig) == hierarchy.stats.primary_input_bits + + hierarchy.stats.abstraction_output_bits + + hierarchy.stats.flop_bits); + + // Evaluate every emitted CO cone before creating the first PO. MiniAIG's + // normalized form requires all AND nodes to precede all POs. This selective + // preparation also avoids elaborating clock and asynchronous-control cones. + int** reg_next = (int**)calloc(hierarchy.registers.size, sizeof(int*)); + assert(reg_next || hierarchy.registers.size == 0); + for (size_t i = 0; i < top->type_objects[SN_PO].size; i++) + sn_blast_eval(&root->blast, sn_vec_at(sn_obj_id_t, &top->type_objects[SN_PO], i)); + for (size_t i = 0; i < hierarchy.registers.size; i++) + { + sn_blast_hier_object_t occurrence = sn_vec_at(sn_blast_hier_object_t, &hierarchy.registers, i); + const sn_module_t* module = occurrence.frame->blast.module; + if (sn_blast_mode_has_transition(options.mode)) + reg_next[i] = sn_blast_hier_reg_next(occurrence); + else + { + sn_obj_id_t reg_in = sn_obj_fanin(module, occurrence.object, SN_REG_DATA); + sn_blast_eval(&occurrence.frame->blast, sn_obj_fanin(module, reg_in, 0)); + const uint32_t slots[] = {SN_REG_ENABLE, SN_REG_SET, SN_REG_RESET, SN_REG_RESET_VALUE}; + for (size_t k = 0; k < sizeof(slots) / sizeof(slots[0]); k++) + if (sn_blast_reg_control_is_comb_output(module, occurrence.object, slots[k])) + { + sn_obj_id_t fanin = sn_obj_fanin(module, occurrence.object, slots[k]); + if (fanin != SN_INVALID_ID) + sn_blast_eval(&occurrence.frame->blast, fanin); + } + } + } + for (size_t i = 0; i < hierarchy.memory_reads.size; i++) + { + sn_blast_hier_object_t occurrence = sn_vec_at(sn_blast_hier_object_t, &hierarchy.memory_reads, i); + const sn_module_t* module = occurrence.frame->blast.module; + for (uint32_t slot = SN_MEM_READ_CLOCK; slot < SN_MEM_READ_FANIN_COUNT; slot++) + { + sn_obj_id_t fanin = sn_obj_fanin(module, occurrence.object, slot); + if (fanin != SN_INVALID_ID) + sn_blast_eval(&occurrence.frame->blast, fanin); + } + } + for (size_t i = 0; i < hierarchy.memory_writes.size; i++) + { + sn_blast_hier_object_t occurrence = sn_vec_at(sn_blast_hier_object_t, &hierarchy.memory_writes, i); + const sn_module_t* module = occurrence.frame->blast.module; + for (uint32_t slot = SN_MEM_WRITE_CLOCK; slot < SN_MEM_WRITE_FANIN_COUNT; slot++) + { + sn_obj_id_t fanin = sn_obj_fanin(module, occurrence.object, slot); + if (fanin != SN_INVALID_ID) + sn_blast_eval(&occurrence.frame->blast, fanin); + } + } + for (size_t i = 0; i < hierarchy.abstract_insts.size; i++) + { + sn_blast_hier_object_t occurrence = sn_vec_at(sn_blast_hier_object_t, &hierarchy.abstract_insts, i); + const sn_module_t* module = occurrence.frame->blast.module; + for (uint32_t slot = 0; slot < sn_obj_fanin_count(module, occurrence.object); slot++) + sn_blast_eval(&occurrence.frame->blast, sn_obj_fanin(module, occurrence.object, slot)); + } + for (size_t i = 0; i < hierarchy.loops.size; i++) + { + sn_blast_hier_object_t occurrence = sn_vec_at(sn_blast_hier_object_t, &hierarchy.loops, i); + const sn_module_t* module = occurrence.frame->blast.module; + sn_obj_id_t loop_in = sn_obj_pair_in(module, occurrence.object); + sn_blast_eval(&occurrence.frame->blast, sn_obj_fanin(module, loop_in, 0)); + } + + for (size_t i = 0; i < top->type_objects[SN_PO].size; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &top->type_objects[SN_PO], i); + int* bits = sn_blast_eval(&root->blast, object); + for (uint32_t bit = 0; bit < sn_obj_width(top, object); bit++) + { + Mini_AigCreatePo(hierarchy.aig, bits[bit]); + sn_blast_boundary_add_bit(&hierarchy, false, SN_BLAST_BOUNDARY_TOP_PO, root, object, bit, + SN_INVALID_ID, (uint32_t)i); + } + } + for (size_t i = 0; i < hierarchy.registers.size; i++) + sn_blast_hier_emit_register_controls( + sn_vec_at(sn_blast_hier_object_t, &hierarchy.registers, i), hierarchy.aig); + for (size_t i = 0; i < hierarchy.memory_reads.size; i++) + sn_blast_hier_emit_memory_inputs(sn_vec_at(sn_blast_hier_object_t, &hierarchy.memory_reads, i), + hierarchy.aig); + for (size_t i = 0; i < hierarchy.memory_writes.size; i++) + sn_blast_hier_emit_memory_inputs(sn_vec_at(sn_blast_hier_object_t, &hierarchy.memory_writes, i), + hierarchy.aig); + for (size_t i = 0; i < hierarchy.abstract_insts.size; i++) + { + sn_blast_hier_object_t occurrence = + sn_vec_at(sn_blast_hier_object_t, &hierarchy.abstract_insts, i); + const sn_module_t* module = occurrence.frame->blast.module; + sn_blast_primitive_t* primitive = NULL; + if (boundary) + { + primitive = &sn_vec_at(sn_blast_primitive_t, &boundary->primitives, occurrence.boundary_owner); + // As above, the boundary vector provides a constant-time running count; Mini_AigPoNum() is linear. + primitive->co_begin = (uint32_t)boundary->cos.size; + } + for (uint32_t slot = 0; slot < sn_obj_fanin_count(module, occurrence.object); slot++) + { + sn_obj_id_t fanin = sn_obj_fanin(module, occurrence.object, slot); + int* bits = sn_blast_eval(&occurrence.frame->blast, fanin); + for (uint32_t bit = 0; bit < sn_obj_width(module, fanin); bit++) + { + Mini_AigCreatePo(hierarchy.aig, bits[bit]); + sn_blast_boundary_add_bit(&hierarchy, false, SN_BLAST_BOUNDARY_PRIMITIVE_INPUT, + occurrence.frame, fanin, bit, occurrence.boundary_owner, slot); + } + } + if (primitive) + primitive->co_count = (uint32_t)boundary->cos.size - primitive->co_begin; + } + for (size_t i = 0; i < hierarchy.loops.size; i++) + { + sn_blast_hier_object_t occurrence = sn_vec_at(sn_blast_hier_object_t, &hierarchy.loops, i); + const sn_module_t* module = occurrence.frame->blast.module; + sn_obj_id_t loop_in = sn_obj_pair_in(module, occurrence.object); + sn_obj_id_t data = sn_obj_fanin(module, loop_in, 0); + int* bits = sn_blast_eval(&occurrence.frame->blast, data); + if (boundary) + sn_vec_at(sn_blast_loop_t, &boundary->loops, occurrence.boundary_owner).co_begin = + (uint32_t)boundary->cos.size; + for (uint32_t bit = 0; bit < sn_obj_width(module, occurrence.object); bit++) + { + Mini_AigCreatePo(hierarchy.aig, bits[bit]); + sn_blast_boundary_add_bit(&hierarchy, false, SN_BLAST_BOUNDARY_LOOP_INPUT, occurrence.frame, + data, bit, occurrence.boundary_owner, bit); + } + } + + // Register inputs are deliberately emitted last; nRegs pairs the final CIs and final COs. + for (size_t i = 0; i < hierarchy.registers.size; i++) + { + sn_blast_hier_object_t occurrence = sn_vec_at(sn_blast_hier_object_t, &hierarchy.registers, i); + const sn_module_t* module = occurrence.frame->blast.module; + sn_obj_id_t reg_in = sn_obj_fanin(module, occurrence.object, SN_REG_DATA); + sn_obj_id_t data = sn_obj_fanin(module, reg_in, 0); + int* bits = sn_blast_mode_has_transition(options.mode) ? reg_next[i] + : sn_blast_eval(&occurrence.frame->blast, data); + if (boundary) + sn_vec_at(sn_blast_register_t, &boundary->registers, occurrence.boundary_owner).co_begin = + (uint32_t)boundary->cos.size; + for (uint32_t bit = 0; bit < sn_obj_width(module, occurrence.object); bit++) + { + bool invert = sn_blast_mode_has_transition(options.mode) && + sn_blast_reg_init_bit(module, occurrence.object, bit); + Mini_AigCreatePo(hierarchy.aig, invert ? Mini_AigLitNot(bits[bit]) : bits[bit]); + sn_blast_boundary_add_bit(&hierarchy, false, SN_BLAST_BOUNDARY_REG_INPUT, occurrence.frame, + data, bit, occurrence.boundary_owner, bit); + } + if (sn_blast_mode_has_transition(options.mode)) + free(reg_next[i]); + } + free(reg_next); + + Mini_AigSetRegNum(hierarchy.aig, options.mode == SN_BLAST_SEQ ? (int)hierarchy.stats.flop_bits : 0); + assert(Mini_AigIsNormalized(hierarchy.aig)); + if (boundary) + { + boundary->register_bits = (uint32_t)Mini_AigRegNum(hierarchy.aig); + assert(boundary->cis.size == (size_t)Mini_AigPiNum(hierarchy.aig)); + assert(boundary->cos.size == (size_t)Mini_AigPoNum(hierarchy.aig)); + } + if (returned_stats) + *returned_stats = hierarchy.stats; + Mini_Aig_t* result = hierarchy.aig; + sn_blast_hier_destroy_frame(root); + sn_vec_destroy(&hierarchy.memory_reads); + sn_vec_destroy(&hierarchy.memory_writes); + sn_vec_destroy(&hierarchy.registers); + sn_vec_destroy(&hierarchy.loops); + sn_vec_destroy(&hierarchy.abstract_insts); + free(hierarchy.active_modules); + return result; +} + +static inline Mini_Aig_t* sn_design_blast_hier_options(const sn_design_t* design, sn_module_id_t top_module_id, + sn_blast_options_t options, + sn_blast_hier_stats_t* returned_stats) +{ + return sn_design_blast_hier_boundary_options(design, top_module_id, options, returned_stats, NULL); +} + +static inline Mini_Aig_t* sn_design_blast_hier(const sn_design_t* design, sn_module_id_t top_module_id) +{ + return sn_design_blast_hier_options(design, top_module_id, sn_blast_default_options(), NULL); +} + +static inline Mini_Aig_t* sn_design_blast_hier_seq_options(const sn_design_t* design, sn_module_id_t top_module_id, + sn_blast_options_t options, + sn_blast_hier_stats_t* returned_stats) +{ + options.mode = SN_BLAST_SEQ; + return sn_design_blast_hier_options(design, top_module_id, options, returned_stats); +} + +static inline Mini_Aig_t* sn_design_blast_hier_seq(const sn_design_t* design, sn_module_id_t top_module_id) +{ + return sn_design_blast_hier_seq_options(design, top_module_id, sn_blast_default_options(), NULL); +} + +static inline Mini_Aig_t* sn_design_blast_hier_transition(const sn_design_t* design, + sn_module_id_t top_module_id) +{ + sn_blast_options_t options = sn_blast_default_options(); + options.mode = SN_BLAST_TRANSITION; + return sn_design_blast_hier_options(design, top_module_id, options, NULL); +} + +// Keep the standalone-module API as a thin adapter around the hierarchical +// driver so register controls, loop boundaries, and initialization semantics +// cannot drift between the two exported blasting paths. +static inline Mini_Aig_t* sn_module_blast_comb_options(const sn_module_t* module, + sn_blast_options_t options) +{ + sn_blast_check_module(module, options); + sn_design_t wrapper; + memset(&wrapper, 0, sizeof(wrapper)); + sn_vec_init(&wrapper.modules); + *sn_vec_push(sn_module_t*, &wrapper.modules) = (sn_module_t*)module; + Mini_Aig_t* aig = sn_design_blast_hier_options(&wrapper, 0, options, NULL); + sn_vec_destroy(&wrapper.modules); + return aig; +} + +static inline Mini_Aig_t* sn_design_blast_comb_options(sn_design_t* design, sn_module_id_t module_id, + sn_blast_options_t options) +{ + assert(design); + assert(module_id < design->modules.size); + return sn_design_blast_hier_options(design, module_id, options, NULL); +} + +static inline Mini_Aig_t* sn_design_blast_comb(sn_design_t* design, sn_module_id_t module_id) +{ + return sn_design_blast_comb_options(design, module_id, sn_blast_default_options()); +} + +static inline Mini_Aig_t* sn_design_blast_seq_options(sn_design_t* design, sn_module_id_t module_id, + sn_blast_options_t options) +{ + options.mode = SN_BLAST_SEQ; + return sn_design_blast_comb_options(design, module_id, options); +} + +static inline Mini_Aig_t* sn_design_blast_seq(sn_design_t* design, sn_module_id_t module_id) +{ + return sn_design_blast_seq_options(design, module_id, sn_blast_default_options()); +} + +static inline void sn_module_write_aiger(const sn_module_t* module, const char* file_name, + sn_blast_options_t options) +{ + assert(module && file_name); + Mini_Aig_t* aig = sn_module_blast_comb_options(module, options); + Mini_AigerWrite((char*)file_name, aig, 0); + Mini_AigStop(aig); +} + +ABC_NAMESPACE_HEADER_END + +#endif diff --git a/src/base/sn/snBoundary.h b/src/base/sn/snBoundary.h new file mode 100644 index 000000000..1d878f20d --- /dev/null +++ b/src/base/sn/snBoundary.h @@ -0,0 +1,781 @@ +/**CFile**************************************************************** + + FileName [snBoundary.h] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [New word-level design interface.] + + Synopsis [Reconstruction and reconnection of extracted combinational boundaries.] + + Author [Alan Mishchenko] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: snBoundary.h,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#ifndef ABC__base__sn__snBoundary_h +#define ABC__base__sn__snBoundary_h + +#include "snBlast.h" + +ABC_NAMESPACE_HEADER_START + +typedef struct sn_boundary_regs_t +{ + sn_design_t* design; + const sn_blast_boundary_t* boundary; + sn_module_t* result; + sn_obj_id_t* top_inputs; + sn_obj_pair_t* pairs; + sn_obj_pair_t* loops; + sn_obj_pair_t* primitive_pairs; + uint32_t* primitive_offsets; + sn_obj_id_t** external_copies; + struct sn_boundary_link_t* links; + size_t link_cap; +} sn_boundary_regs_t; + +typedef struct sn_boundary_link_t +{ + uint64_t key; + uint32_t primitive; + uint32_t child; + uint32_t reg; + uint32_t loop; +} sn_boundary_link_t; + +typedef struct sn_boundary_external_frame_t +{ + sn_blast_hier_ref_t ref; + sn_blast_hier_ref_t dependency; + sn_obj_id_t result; + uint32_t next_fanin; + uint8_t phase; +} sn_boundary_external_frame_t; + +enum +{ + SN_BOUNDARY_EXTERNAL_START, + SN_BOUNDARY_EXTERNAL_ALIAS, + SN_BOUNDARY_EXTERNAL_OPERATOR +}; + +static inline uint64_t sn_boundary_link_key(uint32_t occurrence, sn_obj_id_t object) +{ + return ((uint64_t)occurrence << 32) | object; +} + +static inline size_t sn_boundary_link_hash(uint64_t key, size_t mask) +{ + key ^= key >> 33; + key *= UINT64_C(0xff51afd7ed558ccd); + key ^= key >> 33; + return (size_t)key & mask; +} + +static inline sn_boundary_link_t* sn_boundary_link_find(sn_boundary_regs_t* regs, uint32_t occurrence, + sn_obj_id_t object, bool create) +{ + if (!regs->link_cap) + return NULL; + uint64_t key = sn_boundary_link_key(occurrence, object); + size_t slot = sn_boundary_link_hash(key, regs->link_cap - 1); + while (regs->links[slot].key != UINT64_MAX && regs->links[slot].key != key) + slot = (slot + 1) & (regs->link_cap - 1); + if (regs->links[slot].key == UINT64_MAX) + { + if (!create) + return NULL; + regs->links[slot].key = key; + } + return ®s->links[slot]; +} + +static inline sn_obj_id_t* sn_boundary_external_copies(sn_boundary_regs_t* regs, sn_blast_hier_ref_t ref, + const sn_module_t** returned_module) +{ + const sn_blast_occurrence_t* occurrence = + &sn_vec_at(sn_blast_occurrence_t, ®s->boundary->occurrences, ref.occurrence); + const sn_module_t* module = sn_design_get_module_const(regs->design, occurrence->module); + sn_obj_id_t* copies = regs->external_copies[ref.occurrence]; + assert(ref.object < module->obj_types.size); + if (!copies) + { + copies = (sn_obj_id_t*)malloc(sizeof(sn_obj_id_t) * module->obj_types.size); + assert(copies); + for (size_t i = 0; i < module->obj_types.size; i++) + copies[i] = SN_INVALID_ID; + regs->external_copies[ref.occurrence] = copies; + } + if (returned_module) + *returned_module = module; + return copies; +} + +static inline sn_obj_id_t sn_boundary_pack_bits(sn_module_t* module, const sn_obj_id_t* bits, uint32_t width, + const char* name) +{ + assert(module && bits && width); + return width == 1 ? bits[0] : sn_module_add_operator(module, SN_CONCAT, width, false, width, bits, name); +} + +static inline sn_blast_hier_ref_t sn_boundary_parent_ref(const sn_design_t* design, + const sn_blast_boundary_t* boundary, + sn_blast_hier_ref_t ref) +{ + const sn_blast_occurrence_t* occurrence; + const sn_module_t* module; + const sn_module_t* parent; + sn_obj_id_t parent_fanin; + assert(ref.occurrence < boundary->occurrences.size); + occurrence = &sn_vec_at(sn_blast_occurrence_t, &boundary->occurrences, ref.occurrence); + module = sn_design_get_module_const(design, occurrence->module); + assert(sn_obj_type(module, ref.object) == SN_PI && occurrence->parent_occurrence != SN_INVALID_ID); + parent = sn_design_get_module_const( + design, sn_vec_at(sn_blast_occurrence_t, &boundary->occurrences, occurrence->parent_occurrence).module); + parent_fanin = sn_obj_fanin(parent, occurrence->parent_inst, sn_obj_type_id(module, ref.object)); + ref.occurrence = occurrence->parent_occurrence; + ref.object = parent_fanin; + return ref; +} + +// Resolves clocks, asynchronous controls, and initialization constants that @blast intentionally leaves outside the +// combinational cloud. Hierarchical PI bindings are followed to the root. Generated combinational control cones are +// copied and memoized per hierarchy occurrence; an explicit DFS stack avoids overflowing the C stack on deep control +// cones. The link table resolves inst, primitive, register, and loop endpoints in expected constant time. +static inline sn_obj_id_t sn_boundary_resolve_external(sn_boundary_regs_t* regs, sn_blast_hier_ref_t ref) +{ + sn_blast_hier_ref_t root = ref; + sn_vec_t stack; + sn_vec_init(&stack); + sn_boundary_external_frame_t* initial = sn_vec_push(sn_boundary_external_frame_t, &stack); + memset(initial, 0, sizeof(*initial)); + initial->ref = ref; + while (stack.size) + { + sn_boundary_external_frame_t* frame = + &sn_vec_at(sn_boundary_external_frame_t, &stack, stack.size - 1); + ref = frame->ref; + const sn_blast_occurrence_t* occurrence = + &sn_vec_at(sn_blast_occurrence_t, ®s->boundary->occurrences, ref.occurrence); + const sn_module_t* module; + sn_obj_id_t* copies = sn_boundary_external_copies(regs, ref, &module); + if (frame->phase == SN_BOUNDARY_EXTERNAL_ALIAS) + { + const sn_module_t* dependency_module; + sn_obj_id_t* dependency_copies = + sn_boundary_external_copies(regs, frame->dependency, &dependency_module); + (void)dependency_module; + assert(dependency_copies[frame->dependency.object] != SN_INVALID_ID); + copies[ref.object] = dependency_copies[frame->dependency.object]; + stack.size--; + continue; + } + if (frame->phase == SN_BOUNDARY_EXTERNAL_OPERATOR) + { + uint32_t count = sn_obj_fanin_count(module, ref.object); + if (frame->next_fanin == count) + { + stack.size--; + continue; + } + uint32_t index = frame->next_fanin; + sn_obj_id_t old_fanin = sn_obj_fanin(module, ref.object, index); + if (old_fanin == SN_INVALID_ID) + { + sn_obj_connect(regs->result, frame->result, index, SN_INVALID_ID); + frame->next_fanin++; + continue; + } + sn_blast_hier_ref_t dependency = {ref.occurrence, old_fanin, 0}; + sn_obj_id_t* dependency_copies = sn_boundary_external_copies(regs, dependency, NULL); + if (dependency_copies[old_fanin] != SN_INVALID_ID) + { + sn_obj_connect(regs->result, frame->result, index, dependency_copies[old_fanin]); + frame->next_fanin++; + continue; + } + sn_boundary_external_frame_t* child = sn_vec_push(sn_boundary_external_frame_t, &stack); + memset(child, 0, sizeof(*child)); + child->ref = dependency; + continue; + } + if (copies[ref.object] != SN_INVALID_ID) + { + stack.size--; + continue; + } + sn_obj_type_t type = sn_obj_type(module, ref.object); + if (type == SN_PI && occurrence->parent_occurrence != SN_INVALID_ID) + { + frame->phase = SN_BOUNDARY_EXTERNAL_ALIAS; + frame->dependency = sn_boundary_parent_ref(regs->design, regs->boundary, ref); + sn_obj_id_t* dependency_copies = sn_boundary_external_copies(regs, frame->dependency, NULL); + if (dependency_copies[frame->dependency.object] == SN_INVALID_ID) + { + sn_boundary_external_frame_t* child = sn_vec_push(sn_boundary_external_frame_t, &stack); + memset(child, 0, sizeof(*child)); + child->ref = frame->dependency; + } + continue; + } + if (type == SN_PI) + { + assert(ref.occurrence == 0 && regs->top_inputs[ref.object] != SN_INVALID_ID); + copies[ref.object] = regs->top_inputs[ref.object]; + stack.size--; + continue; + } + if (type == SN_INST || type == SN_FAN) + { + sn_obj_id_t inst = type == SN_INST ? ref.object : sn_fan_inst_id(module, ref.object); + uint32_t output = type == SN_INST ? 0 : sn_fan_output_index(module, ref.object); + sn_boundary_link_t* link = sn_boundary_link_find(regs, ref.occurrence, inst, false); + assert(link); + if (link->primitive != SN_INVALID_ID) + { + const sn_blast_primitive_t* primitive = + &sn_vec_at(sn_blast_primitive_t, ®s->boundary->primitives, link->primitive); + assert(output < sn_design_module_output_count(regs->design, primitive->module)); + (void)primitive; + copies[ref.object] = regs->primitive_pairs[regs->primitive_offsets[link->primitive] + output].out; + stack.size--; + continue; + } + if (link->child != SN_INVALID_ID) + { + const sn_blast_occurrence_t* child_occurrence = + &sn_vec_at(sn_blast_occurrence_t, ®s->boundary->occurrences, link->child); + const sn_module_t* child = sn_design_get_module_const(regs->design, child_occurrence->module); + assert(output < child->type_objects[SN_PO].size); + sn_obj_id_t child_po = sn_vec_at(sn_obj_id_t, &child->type_objects[SN_PO], output); + sn_obj_id_t child_fanin = sn_obj_fanin(child, child_po, 0); + assert(child_fanin != SN_INVALID_ID); + frame->phase = SN_BOUNDARY_EXTERNAL_ALIAS; + frame->dependency.occurrence = link->child; + frame->dependency.object = child_fanin; + frame->dependency.bit = 0; + sn_obj_id_t* dependency_copies = sn_boundary_external_copies(regs, frame->dependency, NULL); + if (dependency_copies[child_fanin] == SN_INVALID_ID) + { + sn_boundary_external_frame_t* child_frame = + sn_vec_push(sn_boundary_external_frame_t, &stack); + memset(child_frame, 0, sizeof(*child_frame)); + child_frame->ref = frame->dependency; + } + continue; + } + assert(false); + } + if (type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST) + { + uint32_t width = sn_obj_width(module, ref.object); + uint32_t* words = (uint32_t*)calloc(sn_const_word_count(width), sizeof(uint32_t)); + assert(words); + for (uint32_t bit = 0; bit < width; bit++) + words[bit >> 5] |= (uint32_t)sn_const_bit(module, ref.object, bit) << (bit & 31); + const char* name = sn_obj_name_id(module, ref.object) == SN_INVALID_ID + ? NULL + : sn_obj_name(module, ref.object); + sn_obj_id_t result = sn_module_add_const(regs->result, width, sn_obj_is_signed(module, ref.object), words, + name); + free(words); + copies[ref.object] = result; + stack.size--; + continue; + } + if (type == SN_REG_OUT) + { + sn_boundary_link_t* link = sn_boundary_link_find(regs, ref.occurrence, ref.object, false); + assert(link && link->reg != SN_INVALID_ID); + copies[ref.object] = regs->pairs[link->reg].out; + stack.size--; + continue; + } + if (type == SN_LOOP_OUT) + { + sn_boundary_link_t* link = sn_boundary_link_find(regs, ref.occurrence, ref.object, false); + assert(link && link->loop != SN_INVALID_ID); + copies[ref.object] = regs->loops[link->loop].out; + stack.size--; + continue; + } + assert(type == SN_BUF || (type >= SN_POS && type <= SN_GATE)); + frame->result = sn_module_dup_obj_skeleton(regs->result, module, ref.object); + copies[ref.object] = frame->result; + sn_module_dup_obj_metadata(regs->result, sn_obj_type_id(regs->result, frame->result), module, ref.object); + frame->phase = SN_BOUNDARY_EXTERNAL_OPERATOR; + frame->next_fanin = 0; + } + sn_obj_id_t* root_copies = sn_boundary_external_copies(regs, root, NULL); + assert(root_copies[root.object] != SN_INVALID_ID); + sn_obj_id_t result = root_copies[root.object]; + sn_vec_destroy(&stack); + return result; +} + +static inline void sn_boundary_regs_init(sn_boundary_regs_t* regs, sn_design_t* design, + const sn_blast_boundary_t* boundary, sn_module_t* result, + sn_obj_id_t* top_inputs) +{ + assert(regs && design && boundary && result && top_inputs); + regs->design = design; + regs->boundary = boundary; + regs->result = result; + regs->top_inputs = top_inputs; + regs->external_copies = boundary->occurrences.size + ? (sn_obj_id_t**)calloc(boundary->occurrences.size, sizeof(sn_obj_id_t*)) + : NULL; + assert(regs->external_copies || boundary->occurrences.size == 0); + regs->links = NULL; + regs->link_cap = 0; + regs->primitive_offsets = boundary->primitives.size + ? (uint32_t*)malloc(sizeof(uint32_t) * (boundary->primitives.size + 1)) + : NULL; + assert(regs->primitive_offsets || boundary->primitives.size == 0); + uint32_t primitive_output_count = 0; + for (size_t i = 0; i < boundary->primitives.size; i++) + { + const sn_blast_primitive_t* entry = &sn_vec_at(sn_blast_primitive_t, &boundary->primitives, i); + regs->primitive_offsets[i] = primitive_output_count; + primitive_output_count += sn_design_module_output_count(design, entry->module); + } + if (boundary->primitives.size) + regs->primitive_offsets[boundary->primitives.size] = primitive_output_count; + regs->primitive_pairs = primitive_output_count + ? (sn_obj_pair_t*)malloc(sizeof(sn_obj_pair_t) * primitive_output_count) + : NULL; + assert(regs->primitive_pairs || primitive_output_count == 0); + for (size_t i = 0; i < boundary->primitives.size; i++) + { + const sn_blast_primitive_t* entry = &sn_vec_at(sn_blast_primitive_t, &boundary->primitives, i); + const sn_blast_occurrence_t* occurrence = + &sn_vec_at(sn_blast_occurrence_t, &boundary->occurrences, entry->occurrence); + const sn_module_t* module = sn_design_get_module_const(design, occurrence->module); + const sn_module_t* child = sn_design_get_module_const(design, entry->module); + for (size_t output = 0; output < child->type_objects[SN_PO].size; output++) + { + sn_obj_id_t old_output = child->type_objects[SN_PO].size == 1 + ? entry->inst + : sn_inst_output(module, entry->inst, (uint32_t)output); + const char* output_name = sn_obj_name_id(module, old_output) == SN_INVALID_ID + ? NULL + : sn_obj_name(module, old_output); + regs->primitive_pairs[regs->primitive_offsets[i] + output] = + sn_module_add_loop_pair(result, sn_obj_width(module, old_output), + sn_obj_is_signed(module, old_output), output_name, "primitive_boundary_input"); + } + } + regs->pairs = boundary->registers.size + ? (sn_obj_pair_t*)malloc(sizeof(sn_obj_pair_t) * boundary->registers.size) + : NULL; + assert(regs->pairs || boundary->registers.size == 0); + for (size_t i = 0; i < boundary->registers.size; i++) + { + const sn_blast_register_t* entry = &sn_vec_at(sn_blast_register_t, &boundary->registers, i); + const sn_blast_occurrence_t* occurrence = + &sn_vec_at(sn_blast_occurrence_t, &boundary->occurrences, entry->occurrence); + const sn_module_t* module = sn_design_get_module_const(design, occurrence->module); + sn_obj_id_t old_out = entry->reg_out; + sn_obj_id_t old_in = sn_obj_pair_in(module, old_out); + const char* out_name = sn_obj_name_id(module, old_out) == SN_INVALID_ID ? NULL : sn_obj_name(module, old_out); + const char* in_name = sn_obj_name_id(module, old_in) == SN_INVALID_ID ? NULL : sn_obj_name(module, old_in); + regs->pairs[i] = sn_module_add_reg_pair(result, entry->width, sn_obj_is_signed(module, old_out), + out_name, in_name, SN_INVALID_ID); + sn_reg_set_flags(result, regs->pairs[i].out, sn_obj_reg_flags(module, old_out)); + } + regs->loops = boundary->loops.size ? (sn_obj_pair_t*)malloc(sizeof(sn_obj_pair_t) * boundary->loops.size) : NULL; + assert(regs->loops || boundary->loops.size == 0); + for (size_t i = 0; i < boundary->loops.size; i++) + { + const sn_blast_loop_t* entry = &sn_vec_at(sn_blast_loop_t, &boundary->loops, i); + const sn_blast_occurrence_t* occurrence = + &sn_vec_at(sn_blast_occurrence_t, &boundary->occurrences, entry->occurrence); + const sn_module_t* module = sn_design_get_module_const(design, occurrence->module); + sn_obj_id_t old_in = sn_obj_pair_in(module, entry->loop_out); + const char* out_name = sn_obj_name_id(module, entry->loop_out) == SN_INVALID_ID + ? NULL + : sn_obj_name(module, entry->loop_out); + const char* in_name = sn_obj_name_id(module, old_in) == SN_INVALID_ID ? NULL : sn_obj_name(module, old_in); + regs->loops[i] = sn_module_add_loop_pair(result, entry->width, + sn_obj_is_signed(module, entry->loop_out), out_name, in_name); + } + + size_t link_count = boundary->primitives.size + boundary->registers.size + boundary->loops.size; + if (boundary->occurrences.size) + link_count += boundary->occurrences.size - 1; + if (link_count) + { + regs->link_cap = 2; + while (regs->link_cap < 2 * link_count) + regs->link_cap <<= 1; + regs->links = (sn_boundary_link_t*)malloc(regs->link_cap * sizeof(sn_boundary_link_t)); + assert(regs->links); + for (size_t i = 0; i < regs->link_cap; i++) + { + regs->links[i].key = UINT64_MAX; + regs->links[i].primitive = SN_INVALID_ID; + regs->links[i].child = SN_INVALID_ID; + regs->links[i].reg = SN_INVALID_ID; + regs->links[i].loop = SN_INVALID_ID; + } + for (size_t i = 0; i < boundary->primitives.size; i++) + { + const sn_blast_primitive_t* entry = &sn_vec_at(sn_blast_primitive_t, &boundary->primitives, i); + sn_boundary_link_find(regs, entry->occurrence, entry->inst, true)->primitive = (uint32_t)i; + } + for (size_t i = 1; i < boundary->occurrences.size; i++) + { + const sn_blast_occurrence_t* entry = &sn_vec_at(sn_blast_occurrence_t, &boundary->occurrences, i); + sn_boundary_link_find(regs, entry->parent_occurrence, entry->parent_inst, true)->child = (uint32_t)i; + } + for (size_t i = 0; i < boundary->registers.size; i++) + { + const sn_blast_register_t* entry = &sn_vec_at(sn_blast_register_t, &boundary->registers, i); + sn_boundary_link_find(regs, entry->occurrence, entry->reg_out, true)->reg = (uint32_t)i; + } + for (size_t i = 0; i < boundary->loops.size; i++) + { + const sn_blast_loop_t* entry = &sn_vec_at(sn_blast_loop_t, &boundary->loops, i); + sn_boundary_link_find(regs, entry->occurrence, entry->loop_out, true)->loop = (uint32_t)i; + } + } +} + +static inline sn_obj_id_t sn_boundary_primitive_output_bit(sn_boundary_regs_t* regs, uint32_t owner, + uint32_t port, uint32_t bit) +{ + assert(owner < regs->boundary->primitives.size); + const sn_blast_primitive_t* entry = &sn_vec_at(sn_blast_primitive_t, ®s->boundary->primitives, owner); + assert(port < sn_design_module_output_count(regs->design, entry->module)); + (void)entry; + sn_obj_id_t output = regs->primitive_pairs[regs->primitive_offsets[owner] + port].out; + assert(bit < sn_obj_width(regs->result, output)); + return sn_module_add_slice(regs->result, output, (int32_t)bit, (int32_t)bit, "primitive_output_bit"); +} + +static inline sn_obj_id_t sn_boundary_reg_output_bit(sn_boundary_regs_t* regs, uint32_t owner, uint32_t bit) +{ + assert(owner < regs->boundary->registers.size); + assert(bit < sn_obj_width(regs->result, regs->pairs[owner].out)); + return sn_module_add_slice(regs->result, regs->pairs[owner].out, (int32_t)bit, (int32_t)bit, "reg_q_bit"); +} + +static inline sn_obj_id_t sn_boundary_loop_output_bit(sn_boundary_regs_t* regs, uint32_t owner, uint32_t bit) +{ + assert(owner < regs->boundary->loops.size); + assert(bit < sn_obj_width(regs->result, regs->loops[owner].out)); + return sn_module_add_slice(regs->result, regs->loops[owner].out, (int32_t)bit, (int32_t)bit, "loop_q_bit"); +} + +static inline sn_obj_id_t sn_boundary_co_word(sn_boundary_regs_t* regs, const sn_obj_id_t* co_drivers, + uint32_t begin, sn_blast_boundary_kind_t kind, uint32_t owner, + uint32_t port, uint32_t width) +{ + assert(begin <= regs->boundary->cos.size && width <= regs->boundary->cos.size - begin); + for (uint32_t bit = 0; bit < width; bit++) + { + sn_blast_boundary_bit_t endpoint = + sn_vec_at(sn_blast_boundary_bit_t, ®s->boundary->cos, begin + bit); + assert(endpoint.kind == kind && endpoint.owner == owner && + (port == SN_INVALID_ID || endpoint.port == port) && endpoint.signal.bit == bit); + } + return sn_boundary_pack_bits(regs->result, co_drivers + begin, width, "boundary_word"); +} + +static inline bool sn_boundary_depends_on(const sn_module_t* module, sn_obj_id_t root, sn_obj_id_t dependency) +{ + uint8_t* visited = (uint8_t*)calloc(module->obj_types.size, sizeof(uint8_t)); + sn_vec_t stack; + assert(visited); + sn_vec_init(&stack); + *sn_vec_push(sn_obj_id_t, &stack) = root; + while (stack.size) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &stack, --stack.size); + if (object == dependency) + { + sn_vec_destroy(&stack); + free(visited); + return true; + } + if (visited[object]) + continue; + visited[object] = 1; + if (sn_obj_type_is_pair_out(sn_obj_type(module, object))) + continue; + for (uint32_t i = 0; i < sn_obj_fanin_count(module, object); i++) + { + sn_obj_id_t fanin = sn_obj_fanin(module, object, i); + if (fanin != SN_INVALID_ID && !visited[fanin]) + *sn_vec_push(sn_obj_id_t, &stack) = fanin; + } + } + sn_vec_destroy(&stack); + free(visited); + return false; +} + +// Duplicates a module in topological order while omitting an explicitly unreferenced set of objects. This is used +// to remove temporary primitive-output loop pairs after their consumers have been redirected to the real outputs. +static inline sn_module_id_t sn_boundary_dup_filtered_topo(sn_design_t* design, sn_module_id_t source_id, + const uint8_t* remove, const char* name) +{ + sn_module_t* source = sn_design_get_module(design, source_id); + size_t object_count = source->obj_types.size; + uint8_t* marks = (uint8_t*)calloc(object_count, sizeof(uint8_t)); + sn_vec_t order; + assert(marks); + sn_vec_init(&order); + sn_vec_reserve(sn_obj_id_t, &order, object_count); + for (sn_obj_id_t object = 0; object < object_count; object++) + if (remove[object]) + marks[object] = SN_TOPO_DONE; + for (size_t i = 0; i < source->type_objects[SN_PI].size; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &source->type_objects[SN_PI], i); + assert(!remove[object]); + marks[object] = SN_TOPO_DONE; + *sn_vec_push(sn_obj_id_t, &order) = object; + } + for (sn_obj_id_t object = 0; object < object_count; object++) + if (!remove[object] && sn_obj_type_is_pair_out(sn_obj_type(source, object))) + { + marks[object] = SN_TOPO_DONE; + *sn_vec_push(sn_obj_id_t, &order) = object; + } + sn_topo_context_t context = {source, &order, marks}; + for (size_t i = 0; i < source->type_objects[SN_PO].size; i++) + { + sn_obj_id_t output = sn_vec_at(sn_obj_id_t, &source->type_objects[SN_PO], i); + for (uint32_t j = 0; j < sn_obj_fanin_count(source, output); j++) + { + sn_obj_id_t fanin = sn_obj_fanin(source, output, j); + if (fanin != SN_INVALID_ID) + sn_module_topo_visit(&context, fanin); + } + } + for (sn_obj_id_t object = 0; object < object_count; object++) + { + sn_obj_type_t type = sn_obj_type(source, object); + if (type != SN_PI && type != SN_PO && marks[object] == SN_TOPO_UNSEEN) + sn_module_topo_visit(&context, object); + } + for (size_t i = 0; i < source->type_objects[SN_PO].size; i++) + { + sn_obj_id_t output = sn_vec_at(sn_obj_id_t, &source->type_objects[SN_PO], i); + marks[output] = SN_TOPO_DONE; + *sn_vec_push(sn_obj_id_t, &order) = output; + } + + sn_module_id_t target_id = sn_design_add_module(design, name); + sn_module_t* target = sn_design_get_module(design, target_id); + sn_vec_resize(sn_obj_id_t, &source->copy_ids, object_count); + for (sn_obj_id_t object = 0; object < object_count; object++) + sn_vec_at(sn_obj_id_t, &source->copy_ids, object) = SN_INVALID_ID; + for (size_t i = 0; i < order.size; i++) + { + sn_obj_id_t old_object = sn_vec_at(sn_obj_id_t, &order, i); + sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object) = + sn_module_dup_obj_skeleton(target, source, old_object); + } + sn_module_clean_rebuild_pair_ids(target, source, SN_REG_OUT, SN_REG_IN); + sn_module_clean_rebuild_pair_ids(target, source, SN_MEM_OUT, SN_MEM_IN); + sn_module_clean_rebuild_pair_ids(target, source, SN_LOOP_OUT, SN_LOOP_IN); + for (size_t i = 0; i < order.size; i++) + { + sn_obj_id_t old_object = sn_vec_at(sn_obj_id_t, &order, i); + sn_obj_id_t new_object = sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object); + sn_obj_type_t type = sn_obj_type(source, old_object); + sn_module_dup_obj_metadata(target, sn_obj_type_id(target, new_object), source, old_object); + if (type == SN_FAN) + sn_vec_at(sn_obj_id_t, &target->fan_insts, sn_obj_type_id(target, new_object)) = + sn_vec_at(sn_obj_id_t, &source->copy_ids, sn_fan_inst_id(source, old_object)); + for (uint32_t j = 0; j < sn_obj_fanin_count(source, old_object); j++) + { + sn_obj_id_t old_fanin = sn_obj_fanin(source, old_object, j); + sn_obj_id_t new_fanin = old_fanin == SN_INVALID_ID + ? SN_INVALID_ID + : sn_vec_at(sn_obj_id_t, &source->copy_ids, old_fanin); + assert(new_fanin != SN_INVALID_ID || old_fanin == SN_INVALID_ID); + sn_obj_connect(target, new_object, j, new_fanin); + } + } + source->copy_module = target_id; + sn_vec_destroy(&order); + free(marks); + assert(sn_module_is_topo(target)); + return target_id; +} + +static inline void sn_boundary_prune_primitive_pairs(sn_boundary_regs_t* regs) +{ + sn_module_t* source = regs->result; + size_t object_count = source->obj_types.size; + uint8_t* remove = (uint8_t*)calloc(object_count, sizeof(uint8_t)); + size_t remove_count = 0; + assert(remove); + for (size_t i = 0; i < regs->boundary->primitives.size; i++) + { + const sn_blast_primitive_t* entry = &sn_vec_at(sn_blast_primitive_t, ®s->boundary->primitives, i); + uint32_t output_count = sn_design_module_output_count(regs->design, entry->module); + for (uint32_t output = 0; output < output_count; output++) + { + sn_obj_pair_t pair = regs->primitive_pairs[regs->primitive_offsets[i] + output]; + sn_obj_id_t actual = sn_obj_fanin(source, pair.in, 0); + sn_obj_id_t inst = sn_obj_type(source, actual) == SN_FAN ? sn_fan_inst_id(source, actual) : actual; + if (sn_boundary_depends_on(source, inst, pair.out)) + continue; + for (size_t k = 0; k < source->fanins.size; k++) + if (sn_vec_at(sn_obj_id_t, &source->fanins, k) == pair.out) + sn_vec_at(sn_obj_id_t, &source->fanins, k) = actual; + remove[pair.out] = remove[pair.in] = 1; + remove_count += 2; + } + } + if (remove_count) + { + char name[96]; + uint32_t suffix = 0; + do + { + int length = snprintf(name, sizeof(name), "__sn_boundary_%u_%u", source->id, suffix++); + assert(length >= 0 && (size_t)length < sizeof(name) && suffix != 0); + } while (sn_name_find(®s->design->names, name) != SN_INVALID_ID); + sn_module_id_t source_id = source->id; + sn_name_id_t source_name = source->name; + bool interface_locked = source->interface_locked; + sn_module_id_t filtered_id = sn_boundary_dup_filtered_topo(regs->design, source_id, remove, name); + sn_module_t* filtered = sn_design_get_module(regs->design, filtered_id); + sn_name_id_t temporary_name = filtered->name; + sn_design_invalidate_copies_to_module(regs->design, source_id); + sn_module_destroy(source); + free(source); + filtered->id = source_id; + filtered->name = source_name; + filtered->interface_locked = interface_locked; + sn_vec_at(sn_module_t*, ®s->design->modules, source_id) = filtered; + regs->design->modules.size--; + sn_name_remove_last(®s->design->names, temporary_name); + regs->result = filtered; + } + free(remove); +} + +static inline void sn_boundary_regs_finish(sn_boundary_regs_t* regs, const sn_obj_id_t* co_drivers) +{ + for (size_t i = 0; i < regs->boundary->primitives.size; i++) + { + const sn_blast_primitive_t* entry = &sn_vec_at(sn_blast_primitive_t, ®s->boundary->primitives, i); + const sn_blast_occurrence_t* occurrence = + &sn_vec_at(sn_blast_occurrence_t, ®s->boundary->occurrences, entry->occurrence); + const sn_module_t* module = sn_design_get_module_const(regs->design, occurrence->module); + const sn_module_t* child = sn_design_get_module_const(regs->design, entry->module); + uint32_t input_count = (uint32_t)child->type_objects[SN_PI].size; + sn_obj_id_t* inputs = input_count ? (sn_obj_id_t*)malloc(sizeof(sn_obj_id_t) * input_count) : NULL; + assert(inputs || input_count == 0); + uint32_t co_begin = entry->co_begin; + for (uint32_t input = 0; input < input_count; input++) + { + sn_obj_id_t port = sn_vec_at(sn_obj_id_t, &child->type_objects[SN_PI], input); + uint32_t width = sn_obj_width(child, port); + inputs[input] = sn_boundary_co_word(regs, co_drivers, co_begin, SN_BLAST_BOUNDARY_PRIMITIVE_INPUT, + (uint32_t)i, input, width); + co_begin += width; + } + assert(co_begin == entry->co_begin + entry->co_count); + const char* inst_name = sn_obj_name_id(module, entry->inst) == SN_INVALID_ID + ? NULL + : sn_obj_name(module, entry->inst); + sn_obj_id_t inst = sn_module_add_inst(regs->result, entry->module, input_count, inputs, inst_name, NULL); + free(inputs); + for (uint32_t output = 0; output < child->type_objects[SN_PO].size; output++) + { + sn_obj_pair_t pair = regs->primitive_pairs[regs->primitive_offsets[i] + output]; + sn_obj_connect(regs->result, pair.in, 0, sn_inst_output(regs->result, inst, output)); + } + } + for (size_t i = 0; i < regs->boundary->loops.size; i++) + { + const sn_blast_loop_t* entry = &sn_vec_at(sn_blast_loop_t, ®s->boundary->loops, i); + sn_obj_id_t data = sn_boundary_co_word(regs, co_drivers, entry->co_begin, SN_BLAST_BOUNDARY_LOOP_INPUT, + (uint32_t)i, SN_INVALID_ID, entry->width); + sn_obj_connect(regs->result, regs->loops[i].in, 0, data); + } + for (size_t i = 0; i < regs->boundary->registers.size; i++) + { + const sn_blast_register_t* entry = &sn_vec_at(sn_blast_register_t, ®s->boundary->registers, i); + const sn_blast_occurrence_t* occurrence = + &sn_vec_at(sn_blast_occurrence_t, ®s->boundary->occurrences, entry->occurrence); + const sn_module_t* module = sn_design_get_module_const(regs->design, occurrence->module); + sn_obj_id_t old_out = entry->reg_out; + sn_obj_pair_t pair = regs->pairs[i]; + sn_obj_id_t old_clock = sn_obj_fanin(module, old_out, SN_REG_CLOCK); + if (old_clock != SN_INVALID_ID) + { + sn_blast_hier_ref_t ref = {entry->occurrence, old_clock, 0}; + sn_reg_set_fanin(regs->result, pair.out, SN_REG_CLOCK, sn_boundary_resolve_external(regs, ref)); + } + const uint32_t slots[] = {SN_REG_ENABLE, SN_REG_SET, SN_REG_RESET, SN_REG_RESET_VALUE}; + for (size_t k = 0; k < sizeof(slots) / sizeof(slots[0]); k++) + { + uint32_t slot = slots[k]; + sn_obj_id_t old_fanin = sn_obj_fanin(module, old_out, slot); + if (old_fanin == SN_INVALID_ID) + continue; + bool in_cloud = sn_blast_reg_control_is_comb_output(module, old_out, slot); + sn_obj_id_t fanin; + if (in_cloud) + { + assert(entry->control_co_begin[slot] != SN_INVALID_ID); + fanin = sn_boundary_co_word(regs, co_drivers, entry->control_co_begin[slot], + SN_BLAST_BOUNDARY_REG_CONTROL, (uint32_t)i, slot, + sn_obj_width(module, old_fanin)); + } + else + { + sn_blast_hier_ref_t ref = {entry->occurrence, old_fanin, 0}; + fanin = sn_boundary_resolve_external(regs, ref); + } + sn_reg_set_fanin(regs->result, pair.out, (sn_reg_fanin_t)slot, fanin); + } + for (uint32_t slot = SN_REG_INIT_DATA; slot <= SN_REG_INIT_MASK; slot++) + { + sn_obj_id_t old_fanin = sn_obj_fanin(module, old_out, slot); + if (old_fanin != SN_INVALID_ID) + { + sn_blast_hier_ref_t ref = {entry->occurrence, old_fanin, 0}; + sn_reg_set_fanin(regs->result, pair.out, (sn_reg_fanin_t)slot, + sn_boundary_resolve_external(regs, ref)); + } + } + sn_obj_id_t data = sn_boundary_co_word(regs, co_drivers, entry->co_begin, SN_BLAST_BOUNDARY_REG_INPUT, + (uint32_t)i, SN_INVALID_ID, entry->width); + sn_obj_connect(regs->result, pair.in, 0, data); + } + sn_boundary_prune_primitive_pairs(regs); + free(regs->pairs); + free(regs->loops); + free(regs->primitive_pairs); + free(regs->primitive_offsets); + free(regs->links); + for (size_t i = 0; i < regs->boundary->occurrences.size; i++) + free(regs->external_copies[i]); + free(regs->external_copies); + regs->pairs = NULL; + regs->loops = NULL; + regs->primitive_pairs = NULL; + regs->primitive_offsets = NULL; + regs->external_copies = NULL; + regs->links = NULL; + regs->link_cap = 0; +} + +ABC_NAMESPACE_HEADER_END + +#endif diff --git a/src/base/sn/snCheck.h b/src/base/sn/snCheck.h new file mode 100644 index 000000000..bc80534a6 --- /dev/null +++ b/src/base/sn/snCheck.h @@ -0,0 +1,1226 @@ +/**CFile**************************************************************** + + FileName [snCheck.h] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [New word-level design interface.] + + Synopsis [Structural and semantic consistency checking for SN designs.] + + Author [Alan Mishchenko] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: snCheck.h,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#ifndef SN_CHECK_H +#define SN_CHECK_H + +#include "sn.h" + +#include + +ABC_NAMESPACE_HEADER_START + +typedef struct sn_check_ctx_t +{ + FILE* out; + size_t errors; + size_t modules; + size_t objects; + bool verbose; +} sn_check_ctx_t; + +static inline const char* sn_check_module_name(const sn_module_t* module) +{ + if (!module || !module->design || module->name >= module->design->names.names.size) + return ""; + return sn_vec_at(char*, &module->design->names.names, module->name); +} + +static inline void sn_check_error(sn_check_ctx_t* ctx, const sn_module_t* module, sn_obj_id_t object, + const char* format, ...) +{ + va_list args; + ctx->errors++; + fprintf(ctx->out, "SN check failed"); + if (module) + fprintf(ctx->out, " in module \"%s\"", sn_check_module_name(module)); + if (object != SN_INVALID_ID) + fprintf(ctx->out, ", object %u", object); + fputs(": ", ctx->out); + va_start(args, format); + vfprintf(ctx->out, format, args); + va_end(args); + fputc('\n', ctx->out); +} + +#define SN_CHECK(ctx, module, object, condition, ...) \ + do { \ + if (!(condition)) \ + sn_check_error((ctx), (module), (object), __VA_ARGS__); \ + } while (false) + +static inline bool sn_check_vec(sn_check_ctx_t* ctx, const sn_module_t* module, const sn_vec_t* vec, + const char* name) +{ + bool valid = vec->size <= vec->cap && (vec->cap == 0 || vec->data != NULL); + SN_CHECK(ctx, module, SN_INVALID_ID, valid, "vector %s has size %zu, capacity %zu, and data %p", name, + vec->size, vec->cap, vec->data); + return valid; +} + +static inline bool sn_check_const_type(sn_obj_type_t type) +{ + return type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST; +} + +static inline int sn_check_fixed_fanin_count(sn_obj_type_t type) +{ + if (type == SN_PI || type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST) + return 0; + if (type == SN_PO || type == SN_BUF || type == SN_FAN || type == SN_REG_IN || type == SN_LOOP_OUT || + type == SN_LOOP_IN || (type >= SN_POS && type <= SN_REDUCE_XNOR) || type == SN_REPLICATE || + type == SN_SLICE || type == SN_CAST) + return 1; + if ((type >= SN_ADD && type <= SN_LOG_OR) || (type >= SN_EQ && type <= SN_GE) || + (type >= SN_SHL && type <= SN_ASHR)) + return 2; + if (type == SN_REG_OUT) + return SN_REG_FANIN_COUNT; + if (type == SN_MEM_OUT) + return SN_MEM_OUT_FANIN_COUNT; + if (type == SN_MEM_READ) + return SN_MEM_READ_FANIN_COUNT; + if (type == SN_MEM_WRITE) + return SN_MEM_WRITE_FANIN_COUNT; + if (type == SN_MUX) + return SN_MUX_FANIN_COUNT; + if (type == SN_BMUX) + return SN_BMUX_FANIN_COUNT; + if (type == SN_PMUX) + return SN_PMUX_FANIN_COUNT; + return -1; +} + +static inline bool sn_check_optional_fanin(sn_obj_type_t type, uint32_t index) +{ + if (type == SN_REG_OUT) + return index != SN_REG_DATA; + if (type == SN_MEM_OUT) + return index == SN_MEM_INIT_DATA || index == SN_MEM_INIT_MASK; + if (type == SN_MEM_READ) + return index == SN_MEM_READ_CLOCK || index == SN_MEM_READ_ENABLE; + if (type == SN_MEM_WRITE) + return index == SN_MEM_WRITE_ENABLE; + return false; +} + +static inline bool sn_check_module_core(sn_check_ctx_t* ctx, const sn_module_t* module) +{ + const sn_design_t* design = module ? module->design : NULL; + size_t object_count; + size_t offset = 0; + bool safe = module && design; + SN_CHECK(ctx, module, SN_INVALID_ID, module != NULL, "null module pointer"); + if (!safe) + return false; + SN_CHECK(ctx, module, SN_INVALID_ID, module->id < design->modules.size, "module ID %u is out of range", module->id); + if (module->id < design->modules.size) + SN_CHECK(ctx, module, SN_INVALID_ID, sn_vec_at(sn_module_t*, &design->modules, module->id) == module, + "module table does not point back to this module"); + SN_CHECK(ctx, module, SN_INVALID_ID, module->name < design->names.names.size, "module name ID %u is out of range", + module->name); + if (module->name < design->names.names.size) + { + SN_CHECK(ctx, module, SN_INVALID_ID, sn_name_get(&design->names, module->name)[0] != '\0', + "module name is empty"); + const char* module_name = sn_name_get(&design->names, module->name); + if (strncmp(module_name, "__sn_", 5) == 0) + SN_CHECK(ctx, module, SN_INVALID_ID, sn_module_is_technology_primitive(module), + "module uses the reserved internal prefix __sn_"); + } + + safe &= sn_check_vec(ctx, module, &module->obj_types, "obj_types"); + safe &= sn_check_vec(ctx, module, &module->width_signed, "width_signed"); + safe &= sn_check_vec(ctx, module, &module->fanin_counts, "fanin_counts"); + safe &= sn_check_vec(ctx, module, &module->fanin_offsets, "fanin_offsets"); + safe &= sn_check_vec(ctx, module, &module->type_ids, "type_ids"); + safe &= sn_check_vec(ctx, module, &module->name_ids, "name_ids"); + safe &= sn_check_vec(ctx, module, &module->fanins, "fanins"); + if (!safe) + return false; + object_count = module->obj_types.size; + SN_CHECK(ctx, module, SN_INVALID_ID, object_count < SN_INVALID_ID, "object count %zu is too large", object_count); +#define SN_CHECK_OBJECT_VECTOR(field) \ + SN_CHECK(ctx, module, SN_INVALID_ID, module->field.size == object_count, \ + #field " size %zu differs from object count %zu", module->field.size, object_count) + SN_CHECK_OBJECT_VECTOR(width_signed); + SN_CHECK_OBJECT_VECTOR(fanin_counts); + SN_CHECK_OBJECT_VECTOR(fanin_offsets); + SN_CHECK_OBJECT_VECTOR(type_ids); + SN_CHECK_OBJECT_VECTOR(name_ids); +#undef SN_CHECK_OBJECT_VECTOR + if (module->width_signed.size != object_count || module->fanin_counts.size != object_count || + module->fanin_offsets.size != object_count || module->type_ids.size != object_count || + module->name_ids.size != object_count) + return false; + + for (uint32_t type = 0; type < SN_OBJ_TYPE_COUNT; type++) + safe &= sn_check_vec(ctx, module, &module->type_objects[type], "type_objects"); + if (!safe) + return false; + + for (sn_obj_id_t object = 0; object < object_count; object++) + { + sn_obj_type_t type = sn_vec_at(sn_obj_type_t, &module->obj_types, object); + uint32_t packed_width = sn_vec_at(uint32_t, &module->width_signed, object); + uint32_t count = sn_vec_at(sn_fanin_count_t, &module->fanin_counts, object); + uint32_t stored_offset = sn_vec_at(uint32_t, &module->fanin_offsets, object); + uint32_t type_id = sn_vec_at(uint32_t, &module->type_ids, object); + uint32_t name_id = sn_vec_at(uint32_t, &module->name_ids, object); + bool type_valid = type > SN_NONE && type < SN_OBJ_TYPE_COUNT; + SN_CHECK(ctx, module, object, type_valid, "object type %u is invalid", (unsigned)type); + SN_CHECK(ctx, module, object, (packed_width >> 1) != 0 || type == SN_INST, + "object width is zero (only structural multi-output insts may have zero width)"); + SN_CHECK(ctx, module, object, stored_offset == offset, "fanin offset %u should be %zu", stored_offset, offset); + SN_CHECK(ctx, module, object, offset <= module->fanins.size && count <= module->fanins.size - offset, + "fanin span [%zu, %zu) exceeds fanin storage size %zu", offset, offset + count, module->fanins.size); + SN_CHECK(ctx, module, object, name_id == SN_INVALID_ID || name_id < design->names.names.size, + "name ID %u is out of range", name_id); + if (type_valid && (type == SN_PI || type == SN_PO || type == SN_GATE)) + SN_CHECK(ctx, module, object, + name_id < design->names.names.size && sn_name_get(&design->names, name_id)[0] != '\0', + "type %u requires a nonempty Verilog name", (unsigned)type); + if (type_valid) + { + int expected = sn_check_fixed_fanin_count(type); + SN_CHECK(ctx, module, object, expected < 0 || count == (uint32_t)expected, + "type %u has %u fanins; expected %d", (unsigned)type, count, expected); + SN_CHECK(ctx, module, object, type_id < module->type_objects[type].size, + "type ID %u is out of range for type %u", type_id, (unsigned)type); + if (type_id < module->type_objects[type].size) + SN_CHECK(ctx, module, object, sn_vec_at(sn_obj_id_t, &module->type_objects[type], type_id) == object, + "reverse type-object entry does not point back to this object"); + } + if (offset <= module->fanins.size && count <= module->fanins.size - offset) + for (uint32_t i = 0; i < count; i++) + { + sn_obj_id_t fanin = sn_vec_at(sn_obj_id_t, &module->fanins, offset + i); + SN_CHECK(ctx, module, object, fanin < object_count || + (fanin == SN_INVALID_ID && type_valid && sn_check_optional_fanin(type, i)), + "fanin %u has invalid object ID %u", i, fanin); + } + offset += count; + } + SN_CHECK(ctx, module, SN_INVALID_ID, offset == module->fanins.size, + "fanin spans use %zu entries but storage contains %zu", offset, module->fanins.size); + + for (uint32_t type = 0; type < SN_OBJ_TYPE_COUNT; type++) + for (size_t type_id = 0; type_id < module->type_objects[type].size; type_id++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &module->type_objects[type], type_id); + SN_CHECK(ctx, module, object, object < object_count, "reverse type-object ID is out of range"); + if (object < object_count) + { + SN_CHECK(ctx, module, object, sn_vec_at(sn_obj_type_t, &module->obj_types, object) == type, + "reverse type-object entry has the wrong type"); + SN_CHECK(ctx, module, object, sn_vec_at(uint32_t, &module->type_ids, object) == type_id, + "reverse type-object entry has the wrong type ID"); + } + } + return true; +} + +static inline bool sn_check_type_metadata(sn_check_ctx_t* ctx, const sn_module_t* module) +{ + bool safe = true; +#define SN_CHECK_TYPE_VECTOR(field, type) \ + do { \ + safe &= sn_check_vec(ctx, module, &module->field, #field); \ + SN_CHECK(ctx, module, SN_INVALID_ID, module->field.size == module->type_objects[type].size, \ + #field " size %zu differs from type %u count %zu", module->field.size, (unsigned)(type), \ + module->type_objects[type].size); \ + safe &= module->field.size == module->type_objects[type].size; \ + } while (false) + SN_CHECK_TYPE_VECTOR(reg_flags, SN_REG_OUT); + SN_CHECK_TYPE_VECTOR(mem_depths, SN_MEM_OUT); + SN_CHECK_TYPE_VECTOR(inst_modules, SN_INST); + SN_CHECK_TYPE_VECTOR(fan_insts, SN_FAN); + SN_CHECK_TYPE_VECTOR(slice_infos, SN_SLICE); + SN_CHECK_TYPE_VECTOR(repeat_counts, SN_REPLICATE); + SN_CHECK_TYPE_VECTOR(const_word_offsets, SN_CONST); + SN_CHECK_TYPE_VECTOR(lut_truths, SN_LUT); + SN_CHECK_TYPE_VECTOR(gate_ids, SN_GATE); +#undef SN_CHECK_TYPE_VECTOR + return safe; +} + +static inline void sn_check_pairs(sn_check_ctx_t* ctx, const sn_module_t* module, sn_obj_type_t out_type, + sn_obj_type_t in_type, uint32_t pair_slot) +{ + size_t out_count = module->type_objects[out_type].size; + size_t in_count = module->type_objects[in_type].size; + SN_CHECK(ctx, module, SN_INVALID_ID, out_count == in_count, "pair types %u/%u have %zu/%zu objects", + (unsigned)out_type, (unsigned)in_type, out_count, in_count); + for (size_t i = 0; i < out_count && i < in_count; i++) + { + sn_obj_id_t out = sn_vec_at(sn_obj_id_t, &module->type_objects[out_type], i); + sn_obj_id_t in = sn_vec_at(sn_obj_id_t, &module->type_objects[in_type], i); + if (out >= module->obj_types.size || in >= module->obj_types.size) + continue; + SN_CHECK(ctx, module, out, sn_vec_at(uint32_t, &module->type_ids, out) == i && + sn_vec_at(uint32_t, &module->type_ids, in) == i, "paired objects do not share type ID %zu", i); + SN_CHECK(ctx, module, out, sn_vec_at(uint32_t, &module->width_signed, out) == + sn_vec_at(uint32_t, &module->width_signed, in), "paired objects differ in width or signedness"); + uint32_t count = sn_vec_at(sn_fanin_count_t, &module->fanin_counts, out); + uint32_t offset = sn_vec_at(uint32_t, &module->fanin_offsets, out); + if (pair_slot < count && offset + pair_slot < module->fanins.size) + SN_CHECK(ctx, module, out, sn_vec_at(sn_obj_id_t, &module->fanins, offset + pair_slot) == in, + "OUT object does not reference its paired IN object"); + } +} + +static inline void sn_check_memories(sn_check_ctx_t* ctx, const sn_module_t* module) +{ + size_t object_count = module->obj_types.size; + uint32_t* owners = object_count ? (uint32_t*)calloc(object_count, sizeof(uint32_t)) : NULL; + sn_obj_id_t* owner_memories = object_count ? (sn_obj_id_t*)malloc(object_count * sizeof(sn_obj_id_t)) : NULL; + SN_CHECK(ctx, module, SN_INVALID_ID, object_count == 0 || (owners != NULL && owner_memories != NULL), + "cannot allocate memory ownership map"); + if (object_count && (!owners || !owner_memories)) + { + free(owners); + free(owner_memories); + return; + } + for (size_t i = 0; i < object_count; i++) + owner_memories[i] = SN_INVALID_ID; + for (size_t i = 0; i < module->type_objects[SN_MEM_OUT].size && i < module->mem_depths.size; i++) + { + sn_obj_id_t memory = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_MEM_OUT], i); + SN_CHECK(ctx, module, memory, sn_vec_at(uint32_t, &module->mem_depths, i) != 0, "memory depth is zero"); + if (memory >= object_count) + continue; + uint64_t init_width = (uint64_t)(sn_vec_at(uint32_t, &module->width_signed, memory) >> 1) * + sn_vec_at(uint32_t, &module->mem_depths, i); + uint32_t offset = sn_vec_at(uint32_t, &module->fanin_offsets, memory); + for (uint32_t slot = SN_MEM_INIT_DATA; slot <= SN_MEM_INIT_MASK; slot++) + { + sn_obj_id_t value = sn_vec_at(sn_obj_id_t, &module->fanins, offset + slot); + if (value == SN_INVALID_ID || value >= object_count) + continue; + SN_CHECK(ctx, module, memory, sn_check_const_type(sn_vec_at(sn_obj_type_t, &module->obj_types, value)), + "memory initialization slot %u is not driven by a constant", slot); + SN_CHECK(ctx, module, memory, init_width <= UINT32_MAX && + (sn_vec_at(uint32_t, &module->width_signed, value) >> 1) == init_width, + "memory initialization slot %u has the wrong width", slot); + } + } + for (size_t i = 0; i < module->type_objects[SN_MEM_IN].size; i++) + { + sn_obj_id_t memory_in = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_MEM_IN], i); + if (memory_in >= object_count) + continue; + uint32_t count = sn_vec_at(sn_fanin_count_t, &module->fanin_counts, memory_in); + uint32_t offset = sn_vec_at(uint32_t, &module->fanin_offsets, memory_in); + for (uint32_t j = 0; j < count; j++) + { + sn_obj_id_t write = sn_vec_at(sn_obj_id_t, &module->fanins, offset + j); + SN_CHECK(ctx, module, memory_in, write < object_count && + sn_vec_at(sn_obj_type_t, &module->obj_types, write) == SN_MEM_WRITE, + "memory input fanin %u is not a memory-write object", j); + if (write < object_count && sn_vec_at(sn_obj_type_t, &module->obj_types, write) == SN_MEM_WRITE) + { + owners[write]++; + if (owner_memories[write] == SN_INVALID_ID) + owner_memories[write] = memory_in; + } + } + } + for (size_t i = 0; i < module->type_objects[SN_MEM_WRITE].size; i++) + { + sn_obj_id_t write = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_MEM_WRITE], i); + if (write >= object_count) + continue; + SN_CHECK(ctx, module, write, owners[write] == 1, "memory-write object has %u owners", owners[write]); + uint32_t offset = sn_vec_at(uint32_t, &module->fanin_offsets, write); + sn_obj_id_t clock = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_MEM_WRITE_CLOCK); + sn_obj_id_t enable = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_MEM_WRITE_ENABLE); + sn_obj_id_t data = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_MEM_WRITE_DATA); + if (clock < object_count) + SN_CHECK(ctx, module, write, (sn_vec_at(uint32_t, &module->width_signed, clock) >> 1) == 1, + "memory-write clock is not one bit"); + if (enable < object_count) + SN_CHECK(ctx, module, write, (sn_vec_at(uint32_t, &module->width_signed, enable) >> 1) == 1, + "memory-write enable is not one bit"); + if (data < object_count && owners[write] == 1) + { + sn_obj_id_t memory_in = owner_memories[write]; + SN_CHECK(ctx, module, write, memory_in < object_count && + (sn_vec_at(uint32_t, &module->width_signed, data) >> 1) == + (sn_vec_at(uint32_t, &module->width_signed, memory_in) >> 1), + "memory-write data width differs from its memory"); + } + } + for (size_t i = 0; i < module->type_objects[SN_MEM_READ].size; i++) + { + sn_obj_id_t read = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_MEM_READ], i); + if (read >= object_count) + continue; + uint32_t offset = sn_vec_at(uint32_t, &module->fanin_offsets, read); + sn_obj_id_t memory = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_MEM_READ_MEMORY); + sn_obj_id_t clock = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_MEM_READ_CLOCK); + sn_obj_id_t enable = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_MEM_READ_ENABLE); + SN_CHECK(ctx, module, read, memory < object_count && + sn_vec_at(sn_obj_type_t, &module->obj_types, memory) == SN_MEM_OUT, + "memory-read object is not owned by a memory output"); + if (memory < object_count && sn_vec_at(sn_obj_type_t, &module->obj_types, memory) == SN_MEM_OUT) + SN_CHECK(ctx, module, read, sn_vec_at(uint32_t, &module->width_signed, read) == + sn_vec_at(uint32_t, &module->width_signed, memory), + "memory-read result differs from its memory width or signedness"); + if (clock < object_count) + SN_CHECK(ctx, module, read, (sn_vec_at(uint32_t, &module->width_signed, clock) >> 1) == 1, + "memory-read clock is not one bit"); + if (enable < object_count) + SN_CHECK(ctx, module, read, (sn_vec_at(uint32_t, &module->width_signed, enable) >> 1) == 1, + "memory-read enable is not one bit"); + } + free(owner_memories); + free(owners); +} + +static inline void sn_check_instances(sn_check_ctx_t* ctx, const sn_module_t* module) +{ + const sn_design_t* design = module->design; + size_t object_count = module->obj_types.size; + for (size_t i = 0; i < module->type_objects[SN_INST].size && i < module->inst_modules.size; i++) + { + sn_obj_id_t inst = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_INST], i); + sn_module_id_t child_id = sn_vec_at(sn_module_id_t, &module->inst_modules, i); + SN_CHECK(ctx, module, inst, child_id < design->modules.size, "referenced module ID %u is out of range", + child_id); + if (inst >= object_count || child_id >= design->modules.size) + continue; + const sn_module_t* child = sn_vec_at(sn_module_t*, &design->modules, child_id); + SN_CHECK(ctx, module, inst, child != NULL, "referenced module pointer is null"); + if (!child) + continue; + uint32_t inputs = sn_vec_at(sn_fanin_count_t, &module->fanin_counts, inst); + uint32_t input_offset = sn_vec_at(uint32_t, &module->fanin_offsets, inst); + uint32_t outputs = (uint32_t)child->type_objects[SN_PO].size; + SN_CHECK(ctx, module, inst, inputs == child->type_objects[SN_PI].size, + "inst has %u inputs but child module has %zu", inputs, child->type_objects[SN_PI].size); + for (uint32_t input_index = 0; input_index < inputs && input_index < child->type_objects[SN_PI].size; + input_index++) + { + sn_obj_id_t input = sn_vec_at(sn_obj_id_t, &module->fanins, input_offset + input_index); + sn_obj_id_t port = sn_vec_at(sn_obj_id_t, &child->type_objects[SN_PI], input_index); + if (input < object_count && port < child->width_signed.size) + SN_CHECK(ctx, module, inst, (sn_vec_at(uint32_t, &module->width_signed, input) >> 1) == + (sn_vec_at(uint32_t, &child->width_signed, port) >> 1), + "inst input %u width differs from the child port", input_index); + } + SN_CHECK(ctx, module, inst, outputs != 0, "instantiated module has no outputs"); + if (outputs == 1) + { + sn_obj_id_t output = sn_vec_at(sn_obj_id_t, &child->type_objects[SN_PO], 0); + SN_CHECK(ctx, module, inst, sn_vec_at(uint32_t, &module->width_signed, inst) == + sn_vec_at(uint32_t, &child->width_signed, output), + "single-output inst differs from its child output width or signedness"); + } + else + { + SN_CHECK(ctx, module, inst, (sn_vec_at(uint32_t, &module->width_signed, inst) >> 1) == 0, + "multi-output inst must have zero structural width"); + for (uint32_t output_index = 0; output_index < outputs; output_index++) + { + sn_obj_id_t fan = inst + 1 + output_index; + SN_CHECK(ctx, module, inst, fan < object_count && + sn_vec_at(sn_obj_type_t, &module->obj_types, fan) == SN_FAN, + "output %u is not represented by the adjacent FAN object %u", output_index, fan); + if (fan >= object_count || sn_vec_at(sn_obj_type_t, &module->obj_types, fan) != SN_FAN) + continue; + uint32_t fan_id = sn_vec_at(uint32_t, &module->type_ids, fan); + sn_obj_id_t child_output = sn_vec_at(sn_obj_id_t, &child->type_objects[SN_PO], output_index); + SN_CHECK(ctx, module, fan, fan_id < module->fan_insts.size && + sn_vec_at(sn_obj_id_t, &module->fan_insts, fan_id) == inst, + "FAN ownership does not reference its adjacent inst"); + SN_CHECK(ctx, module, fan, sn_vec_at(uint32_t, &module->width_signed, fan) == + sn_vec_at(uint32_t, &child->width_signed, child_output), + "FAN differs from its child output width or signedness"); + } + } + } + for (size_t i = 0; i < module->type_objects[SN_FAN].size && i < module->fan_insts.size; i++) + { + sn_obj_id_t fan = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_FAN], i); + sn_obj_id_t inst = sn_vec_at(sn_obj_id_t, &module->fan_insts, i); + SN_CHECK(ctx, module, fan, inst < fan && inst < object_count && + sn_vec_at(sn_obj_type_t, &module->obj_types, inst) == SN_INST, + "FAN owner %u is not an earlier inst", inst); + if (fan < object_count) + { + uint32_t offset = sn_vec_at(uint32_t, &module->fanin_offsets, fan); + SN_CHECK(ctx, module, fan, offset < module->fanins.size && + sn_vec_at(sn_obj_id_t, &module->fanins, offset) == inst, + "FAN data fanin does not reference its owning inst"); + } + if (inst < object_count && sn_vec_at(sn_obj_type_t, &module->obj_types, inst) == SN_INST) + { + uint32_t inst_id = sn_vec_at(uint32_t, &module->type_ids, inst); + if (inst_id < module->inst_modules.size) + { + sn_module_id_t child_id = sn_vec_at(sn_module_id_t, &module->inst_modules, inst_id); + if (child_id < design->modules.size && sn_vec_at(sn_module_t*, &design->modules, child_id)) + { + const sn_module_t* child = sn_vec_at(sn_module_t*, &design->modules, child_id); + uint32_t output_index = fan - inst - 1; + SN_CHECK(ctx, module, fan, output_index < child->type_objects[SN_PO].size, + "FAN is outside its inst's natural adjacent output block"); + } + } + } + } +} + +static inline void sn_check_special_objects(sn_check_ctx_t* ctx, const sn_module_t* module) +{ + size_t object_count = module->obj_types.size; + size_t reg_count = module->reg_flags.size < module->type_objects[SN_REG_OUT].size + ? module->reg_flags.size + : module->type_objects[SN_REG_OUT].size; + for (size_t i = 0; i < reg_count; i++) + { + sn_obj_id_t reg = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_REG_OUT], i); + uint32_t flags = sn_vec_at(uint32_t, &module->reg_flags, i); + SN_CHECK(ctx, module, reg, (flags & ~SN_REG_FLAGS_ALL) == 0, "register flags 0x%x are invalid", flags); + if (reg >= object_count) + continue; + uint32_t offset = sn_vec_at(uint32_t, &module->fanin_offsets, reg); + sn_obj_id_t data = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_REG_INIT_DATA); + sn_obj_id_t mask = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_REG_INIT_MASK); + SN_CHECK(ctx, module, reg, data != SN_INVALID_ID || mask == SN_INVALID_ID, + "register init mask is present without init data"); + for (uint32_t slot = SN_REG_INIT_DATA; slot <= SN_REG_INIT_MASK; slot++) + { + sn_obj_id_t value = sn_vec_at(sn_obj_id_t, &module->fanins, offset + slot); + if (value == SN_INVALID_ID || value >= object_count) + continue; + SN_CHECK(ctx, module, reg, sn_check_const_type(sn_vec_at(sn_obj_type_t, &module->obj_types, value)), + "register initialization slot %u is not a constant", slot); + SN_CHECK(ctx, module, reg, (sn_vec_at(uint32_t, &module->width_signed, value) >> 1) == + (sn_vec_at(uint32_t, &module->width_signed, reg) >> 1), + "register initialization slot %u has the wrong width", slot); + } + } + size_t repeat_count = module->repeat_counts.size < module->type_objects[SN_REPLICATE].size + ? module->repeat_counts.size + : module->type_objects[SN_REPLICATE].size; + for (size_t i = 0; i < repeat_count; i++) + SN_CHECK(ctx, module, sn_vec_at(sn_obj_id_t, &module->type_objects[SN_REPLICATE], i), + sn_vec_at(uint32_t, &module->repeat_counts, i) != 0, "repetition count is zero"); + size_t slice_count = module->slice_infos.size < module->type_objects[SN_SLICE].size + ? module->slice_infos.size + : module->type_objects[SN_SLICE].size; + for (size_t i = 0; i < slice_count; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_SLICE], i); + const sn_slice_info_t* info = &sn_vec_at(sn_slice_info_t, &module->slice_infos, i); + SN_CHECK(ctx, module, object, (info->flags & ~SN_SLICE_DESCENDING) == 0, "slice flags are invalid"); + SN_CHECK(ctx, module, object, ((info->flags & SN_SLICE_DESCENDING) != 0) == + (info->left_index >= info->right_index), "slice direction flag disagrees with its indices"); + if (object < object_count) + { + uint32_t offset = sn_vec_at(uint32_t, &module->fanin_offsets, object); + sn_obj_id_t value = sn_vec_at(sn_obj_id_t, &module->fanins, offset); + if (value < object_count) + { + uint32_t source_width = sn_vec_at(uint32_t, &module->width_signed, value) >> 1; + uint64_t slice_width = info->left_index >= info->right_index + ? (uint64_t)(int64_t)info->left_index - info->right_index + 1 + : (uint64_t)(int64_t)info->right_index - info->left_index + 1; + SN_CHECK(ctx, module, object, info->left_index >= 0 && (uint32_t)info->left_index < source_width, + "slice left index %d is outside source width %u", info->left_index, source_width); + SN_CHECK(ctx, module, object, info->right_index >= 0 && (uint32_t)info->right_index < source_width, + "slice right index %d is outside source width %u", info->right_index, source_width); + SN_CHECK(ctx, module, object, slice_width == + (sn_vec_at(uint32_t, &module->width_signed, object) >> 1), + "slice width does not match its index range"); + } + } + } + size_t const_count = module->const_word_offsets.size < module->type_objects[SN_CONST].size + ? module->const_word_offsets.size + : module->type_objects[SN_CONST].size; + for (size_t i = 0; i < const_count; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_CONST], i); + if (object >= object_count) + continue; + uint32_t words = ((sn_vec_at(uint32_t, &module->width_signed, object) >> 1) + 31) / 32; + uint32_t offset = sn_vec_at(uint32_t, &module->const_word_offsets, i); + SN_CHECK(ctx, module, object, offset <= module->design->constant_words.size && + words <= module->design->constant_words.size - offset, + "constant word span [%u, %u) exceeds storage size %zu", offset, offset + words, + module->design->constant_words.size); + } + size_t lut_count = module->lut_truths.size < module->type_objects[SN_LUT].size + ? module->lut_truths.size + : module->type_objects[SN_LUT].size; + for (size_t i = 0; i < lut_count; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_LUT], i); + if (object >= object_count) + continue; + uint32_t count = sn_vec_at(sn_fanin_count_t, &module->fanin_counts, object); + uint32_t offset = sn_vec_at(uint32_t, &module->fanin_offsets, object); + uint64_t truth = sn_vec_at(uint64_t, &module->lut_truths, i); + SN_CHECK(ctx, module, object, (sn_vec_at(uint32_t, &module->width_signed, object) >> 1) == 1 && + !(sn_vec_at(uint32_t, &module->width_signed, object) & 1), + "LUT output must be one-bit unsigned"); + SN_CHECK(ctx, module, object, count <= 6, "LUT has %u inputs; at most 6 are supported", count); + if (count < 6) + SN_CHECK(ctx, module, object, (truth >> (1u << count)) == 0, + "LUT truth table has nonzero unused high bits"); + for (uint32_t j = 0; j < count; j++) + { + sn_obj_id_t fanin = sn_vec_at(sn_obj_id_t, &module->fanins, offset + j); + if (fanin < object_count) + SN_CHECK(ctx, module, object, (sn_vec_at(uint32_t, &module->width_signed, fanin) >> 1) == 1, + "LUT input %u is not one bit", j); + } + } + size_t gate_count = module->gate_ids.size < module->type_objects[SN_GATE].size + ? module->gate_ids.size + : module->type_objects[SN_GATE].size; + for (size_t i = 0; i < gate_count; i++) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_GATE], i); + if (object >= object_count) + continue; + SN_CHECK(ctx, module, object, sn_vec_at(uint32_t, &module->gate_ids, i) != SN_INVALID_ID, + "gate ID is invalid"); + SN_CHECK(ctx, module, object, (sn_vec_at(uint32_t, &module->width_signed, object) >> 1) == 1, + "gate output is not one bit"); + } +} + +static inline void sn_check_operator_shapes(sn_check_ctx_t* ctx, const sn_module_t* module) +{ + size_t object_count = module->obj_types.size; + for (sn_obj_id_t object = 0; object < object_count; object++) + { + sn_obj_type_t type = sn_vec_at(sn_obj_type_t, &module->obj_types, object); + uint32_t width = sn_vec_at(uint32_t, &module->width_signed, object) >> 1; + uint32_t count = sn_vec_at(sn_fanin_count_t, &module->fanin_counts, object); + uint32_t offset = sn_vec_at(uint32_t, &module->fanin_offsets, object); + if (type == SN_CONCAT) + { + uint64_t packed_width = 0; + for (uint32_t i = 0; i < count; i++) + { + sn_obj_id_t fanin = sn_vec_at(sn_obj_id_t, &module->fanins, offset + i); + if (fanin < object_count) + packed_width += sn_vec_at(uint32_t, &module->width_signed, fanin) >> 1; + } + SN_CHECK(ctx, module, object, packed_width == width, + "concatenation fanins contain %llu bits but output width is %u", + (unsigned long long)packed_width, width); + } + else if (type == SN_REPLICATE && count == 1) + { + sn_obj_id_t fanin = sn_vec_at(sn_obj_id_t, &module->fanins, offset); + uint32_t type_id = sn_vec_at(uint32_t, &module->type_ids, object); + if (fanin < object_count && type_id < module->repeat_counts.size) + { + uint64_t packed_width = (uint64_t)(sn_vec_at(uint32_t, &module->width_signed, fanin) >> 1) * + sn_vec_at(uint32_t, &module->repeat_counts, type_id); + SN_CHECK(ctx, module, object, packed_width == width, + "repetition produces %llu bits but output width is %u", + (unsigned long long)packed_width, width); + } + } + else if (type == SN_MUX && count == SN_MUX_FANIN_COUNT) + { + sn_obj_id_t select = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_MUX_SELECT); + sn_obj_id_t selected = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_MUX_SELECTED); + sn_obj_id_t default_value = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_MUX_DEFAULT); + if (select < object_count) + SN_CHECK(ctx, module, object, + (sn_vec_at(uint32_t, &module->width_signed, select) >> 1) == 1, + "mux select is not one bit"); + if (selected < object_count) + SN_CHECK(ctx, module, object, + (sn_vec_at(uint32_t, &module->width_signed, selected) >> 1) == width, + "mux selected branch width differs from output width"); + if (default_value < object_count) + SN_CHECK(ctx, module, object, + (sn_vec_at(uint32_t, &module->width_signed, default_value) >> 1) == width, + "mux default branch width differs from output width"); + } + else if (type == SN_BMUX && count == SN_BMUX_FANIN_COUNT) + { + sn_obj_id_t select = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_BMUX_SELECT); + sn_obj_id_t alternatives = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_BMUX_ALTERNATIVES); + if (select < object_count && alternatives < object_count) + { + uint32_t select_width = sn_vec_at(uint32_t, &module->width_signed, select) >> 1; + uint32_t alternatives_width = sn_vec_at(uint32_t, &module->width_signed, alternatives) >> 1; + bool valid = select_width < 31 && ((uint64_t)width << select_width) == alternatives_width; + SN_CHECK(ctx, module, object, valid, + "binary mux alternatives width %u does not equal %u * 2^%u", + alternatives_width, width, select_width); + } + } + else if (type == SN_PMUX && count == SN_PMUX_FANIN_COUNT) + { + sn_obj_id_t select = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_PMUX_SELECT); + sn_obj_id_t alternatives = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_PMUX_ALTERNATIVES); + sn_obj_id_t default_value = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_PMUX_DEFAULT); + if (select < object_count && alternatives < object_count) + { + uint32_t select_width = sn_vec_at(uint32_t, &module->width_signed, select) >> 1; + uint32_t alternatives_width = sn_vec_at(uint32_t, &module->width_signed, alternatives) >> 1; + SN_CHECK(ctx, module, object, (uint64_t)width * select_width == alternatives_width, + "priority mux alternatives width %u does not equal %u * %u", + alternatives_width, width, select_width); + } + if (default_value < object_count) + SN_CHECK(ctx, module, object, + (sn_vec_at(uint32_t, &module->width_signed, default_value) >> 1) == width, + "priority mux default width differs from output width"); + } + else if (type == SN_REG_IN || type == SN_LOOP_IN) + { + if (count == 1) + { + sn_obj_id_t fanin = sn_vec_at(sn_obj_id_t, &module->fanins, offset); + if (fanin < object_count) + SN_CHECK(ctx, module, object, + (sn_vec_at(uint32_t, &module->width_signed, fanin) >> 1) == width, + "state input data width differs from state width"); + } + } + else if (type == SN_PO && count == 1) + { + sn_obj_id_t driver = sn_vec_at(sn_obj_id_t, &module->fanins, offset); + if (driver < object_count) + SN_CHECK(ctx, module, object, + (sn_vec_at(uint32_t, &module->width_signed, driver) >> 1) == width, + "primary-output driver width differs from output width"); + } + else if (type == SN_MEM_READ && count == SN_MEM_READ_FANIN_COUNT) + { + sn_obj_id_t clock = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_MEM_READ_CLOCK); + sn_obj_id_t enable = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_MEM_READ_ENABLE); + SN_CHECK(ctx, module, object, clock != SN_INVALID_ID || enable == SN_INVALID_ID, + "memory read enable is present without a clock"); + if (clock < object_count) + SN_CHECK(ctx, module, object, + (sn_vec_at(uint32_t, &module->width_signed, clock) >> 1) == 1, + "memory read clock is not one bit"); + if (enable < object_count) + SN_CHECK(ctx, module, object, + (sn_vec_at(uint32_t, &module->width_signed, enable) >> 1) == 1, + "memory read enable is not one bit"); + } + } + + for (size_t i = 0; i < module->type_objects[SN_REG_OUT].size; i++) + { + sn_obj_id_t reg = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_REG_OUT], i); + if (reg >= object_count) + continue; + uint32_t width = sn_vec_at(uint32_t, &module->width_signed, reg) >> 1; + uint32_t offset = sn_vec_at(uint32_t, &module->fanin_offsets, reg); + uint32_t flags = sn_vec_at(uint32_t, &module->reg_flags, i); + sn_obj_id_t clock = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_REG_CLOCK); + sn_obj_id_t enable = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_REG_ENABLE); + sn_obj_id_t set = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_REG_SET); + sn_obj_id_t reset = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_REG_RESET); + if (flags & SN_REG_LATCH) + { + SN_CHECK(ctx, module, reg, clock == SN_INVALID_ID, "latch has a clock fanin"); + SN_CHECK(ctx, module, reg, enable < object_count, "latch has no enable fanin"); + SN_CHECK(ctx, module, reg, set == SN_INVALID_ID && reset == SN_INVALID_ID, + "latch has unsupported set or reset controls"); + SN_CHECK(ctx, module, reg, (flags & ~SN_REG_LATCH) == 0, + "latch has edge-triggered register flags 0x%x", flags & ~SN_REG_LATCH); + } + else + SN_CHECK(ctx, module, reg, clock < object_count, "edge-triggered register has no clock fanin"); + const uint32_t one_bit_slots[] = {SN_REG_CLOCK, SN_REG_ENABLE, SN_REG_SET, SN_REG_RESET}; + for (size_t slot_index = 0; slot_index < sizeof(one_bit_slots) / sizeof(one_bit_slots[0]); slot_index++) + { + uint32_t slot = one_bit_slots[slot_index]; + sn_obj_id_t fanin = sn_vec_at(sn_obj_id_t, &module->fanins, offset + slot); + if (fanin < object_count) + SN_CHECK(ctx, module, reg, + (sn_vec_at(uint32_t, &module->width_signed, fanin) >> 1) == 1, + "register control slot %u is not one bit", slot); + } + sn_obj_id_t reset_value = sn_vec_at(sn_obj_id_t, &module->fanins, offset + SN_REG_RESET_VALUE); + if (reset_value < object_count) + SN_CHECK(ctx, module, reg, + (sn_vec_at(uint32_t, &module->width_signed, reset_value) >> 1) == width, + "register reset value width differs from register width"); + } + + for (size_t i = 0; i < module->type_objects[SN_GATE].size; i++) + { + sn_obj_id_t gate = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_GATE], i); + if (gate >= object_count) + continue; + uint32_t count = sn_vec_at(sn_fanin_count_t, &module->fanin_counts, gate); + uint32_t offset = sn_vec_at(uint32_t, &module->fanin_offsets, gate); + for (uint32_t j = 0; j < count; j++) + { + sn_obj_id_t fanin = sn_vec_at(sn_obj_id_t, &module->fanins, offset + j); + if (fanin < object_count) + SN_CHECK(ctx, module, gate, + (sn_vec_at(uint32_t, &module->width_signed, fanin) >> 1) == 1, + "gate input %u is not one bit", j); + } + } +} + +static inline void sn_check_auxiliary_storage(sn_check_ctx_t* ctx, const sn_module_t* module) +{ + size_t object_count = module->obj_types.size; + bool hash_safe = sn_check_vec(ctx, module, &module->const_hash_buckets, "constant hash buckets"); + hash_safe &= sn_check_vec(ctx, module, &module->const_hash_entries, "constant hash entries"); + if (hash_safe && !module->const_hash_buckets.size) + SN_CHECK(ctx, module, SN_INVALID_ID, module->const_hash_entries.size == 0, + "constant hash entries exist without buckets"); + else if (hash_safe) + { + size_t bucket_count = module->const_hash_buckets.size; + SN_CHECK(ctx, module, SN_INVALID_ID, (bucket_count & (bucket_count - 1)) == 0, + "constant hash bucket count %zu is not a power of two", bucket_count); + uint8_t* seen = module->const_hash_entries.size + ? (uint8_t*)calloc(module->const_hash_entries.size, 1) + : NULL; + SN_CHECK(ctx, module, SN_INVALID_ID, module->const_hash_entries.size == 0 || seen != NULL, + "cannot allocate constant-hash validation state"); + if (seen || !module->const_hash_entries.size) + for (size_t bucket = 0; bucket < bucket_count; bucket++) + { + uint32_t entry_id = sn_vec_at(uint32_t, &module->const_hash_buckets, bucket); + size_t steps = 0; + while (entry_id != SN_INVALID_ID && entry_id < module->const_hash_entries.size && + steps++ <= module->const_hash_entries.size) + { + const sn_const_hash_entry_t* entry = + &sn_vec_at(sn_const_hash_entry_t, &module->const_hash_entries, entry_id); + SN_CHECK(ctx, module, entry->object, !seen[entry_id], + "constant hash entry %u appears more than once", entry_id); + seen[entry_id] = 1; + SN_CHECK(ctx, module, entry->object, (entry->hash & (bucket_count - 1)) == bucket, + "constant hash entry %u is in the wrong bucket", entry_id); + SN_CHECK(ctx, module, entry->object, entry->object < object_count, + "constant hash entry %u has an invalid object", entry_id); + if (entry->object < object_count) + { + sn_obj_type_t type = sn_vec_at(sn_obj_type_t, &module->obj_types, entry->object); + SN_CHECK(ctx, module, entry->object, sn_check_const_type(type), + "constant hash entry refers to a nonconstant object"); + SN_CHECK(ctx, module, entry->object, + sn_vec_at(uint32_t, &module->name_ids, entry->object) == SN_INVALID_ID, + "named constants must not appear in the interning cache"); + } + entry_id = entry->next; + } + SN_CHECK(ctx, module, SN_INVALID_ID, entry_id == SN_INVALID_ID, + "constant hash bucket %zu has an invalid or cyclic chain", bucket); + } + if (seen) + for (size_t i = 0; i < module->const_hash_entries.size; i++) + SN_CHECK(ctx, module, SN_INVALID_ID, seen[i], "constant hash entry %zu is unreachable", i); + free(seen); + } + + bool fanout_safe = sn_check_vec(ctx, module, &module->fanout_counts, "fanout_counts"); + fanout_safe &= sn_check_vec(ctx, module, &module->fanout_offsets, "fanout_offsets"); + fanout_safe &= sn_check_vec(ctx, module, &module->fanouts, "fanouts"); + if (fanout_safe && !module->fanouts_valid) + SN_CHECK(ctx, module, SN_INVALID_ID, module->fanout_counts.size == 0 && + module->fanout_offsets.size == 0 && module->fanouts.size == 0, + "invalidated fanout cache is not empty"); + else if (fanout_safe) + { + SN_CHECK(ctx, module, SN_INVALID_ID, module->fanout_counts.size == object_count && + module->fanout_offsets.size == object_count, + "valid fanout cache does not have one count and offset per object"); + if (module->fanout_counts.size == object_count && module->fanout_offsets.size == object_count) + { + size_t offset = 0; + for (sn_obj_id_t object = 0; object < object_count; object++) + { + uint32_t stored = sn_vec_at(uint32_t, &module->fanout_offsets, object); + uint32_t count = sn_vec_at(uint32_t, &module->fanout_counts, object); + SN_CHECK(ctx, module, object, stored == offset, "fanout offset %u should be %zu", stored, offset); + SN_CHECK(ctx, module, object, offset <= module->fanouts.size && count <= module->fanouts.size - offset, + "fanout span exceeds fanout storage"); + offset += count; + } + SN_CHECK(ctx, module, SN_INVALID_ID, offset == module->fanouts.size, + "fanout spans use %zu entries but storage contains %zu", offset, module->fanouts.size); + for (size_t i = 0; i < module->fanouts.size; i++) + SN_CHECK(ctx, module, SN_INVALID_ID, sn_vec_at(sn_obj_id_t, &module->fanouts, i) < object_count, + "fanout entry %zu has an invalid object ID", i); + } + } + + bool copy_safe = sn_check_vec(ctx, module, &module->copy_ids, "copy_ids"); + if (copy_safe && !module->copy_ids.size) + SN_CHECK(ctx, module, SN_INVALID_ID, module->copy_module == SN_INVALID_ID, + "copy target exists without a copy map"); + else if (copy_safe) + { + SN_CHECK(ctx, module, SN_INVALID_ID, module->copy_ids.size == object_count, + "copy map size %zu differs from object count %zu", module->copy_ids.size, object_count); + SN_CHECK(ctx, module, SN_INVALID_ID, module->copy_module < module->design->modules.size, + "copy target module ID %u is out of range", module->copy_module); + if (module->copy_module < module->design->modules.size) + { + const sn_module_t* target = sn_vec_at(sn_module_t*, &module->design->modules, module->copy_module); + if (target) + for (size_t i = 0; i < module->copy_ids.size; i++) + { + sn_obj_id_t copy = sn_vec_at(sn_obj_id_t, &module->copy_ids, i); + SN_CHECK(ctx, module, (sn_obj_id_t)i, copy == SN_INVALID_ID || copy < target->obj_types.size, + "copy object ID %u is out of range", copy); + } + } + } +} + +static inline void sn_check_topology(sn_check_ctx_t* ctx, const sn_module_t* module) +{ + size_t object_count = module->obj_types.size; + size_t pi_count = module->type_objects[SN_PI].size; + size_t po_count = module->type_objects[SN_PO].size; + bool valid = pi_count + po_count <= object_count; + if (valid) + for (size_t i = 0; i < pi_count; i++) + valid &= sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], i) == i; + if (valid) + for (size_t i = 0; i < po_count; i++) + valid &= sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PO], i) == object_count - po_count + i; + for (sn_obj_id_t object = 0; valid && object < object_count; object++) + { + sn_obj_type_t type = sn_vec_at(sn_obj_type_t, &module->obj_types, object); + sn_obj_id_t pair_in = SN_INVALID_ID; + if (type == SN_REG_OUT || type == SN_MEM_OUT || type == SN_LOOP_OUT) + { + sn_obj_type_t in_type = type == SN_REG_OUT ? SN_REG_IN : type == SN_MEM_OUT ? SN_MEM_IN : SN_LOOP_IN; + uint32_t type_id = sn_vec_at(uint32_t, &module->type_ids, object); + if (type_id >= module->type_objects[in_type].size) + valid = false; + else + pair_in = sn_vec_at(sn_obj_id_t, &module->type_objects[in_type], type_id); + valid &= pair_in > object; + } + uint32_t count = sn_vec_at(sn_fanin_count_t, &module->fanin_counts, object); + uint32_t offset = sn_vec_at(uint32_t, &module->fanin_offsets, object); + for (uint32_t i = 0; valid && i < count; i++) + { + sn_obj_id_t fanin = sn_vec_at(sn_obj_id_t, &module->fanins, offset + i); + if (fanin == SN_INVALID_ID || fanin == pair_in) + continue; + valid &= pair_in != SN_INVALID_ID ? fanin < pair_in : fanin < object; + } + } + SN_CHECK(ctx, module, SN_INVALID_ID, valid, "objects are not in legal SN topological order"); +} + +static inline void sn_check_primitive(sn_check_ctx_t* ctx, const sn_module_t* module) +{ + const char* name = sn_check_module_name(module); + size_t pi_count = module->type_objects[SN_PI].size; + size_t po_count = module->type_objects[SN_PO].size; + if (strncmp(name, "__sn_CARRY", 10) == 0) + { + const uint32_t pi_widths[] = {1, 1, 4, 4}; + SN_CHECK(ctx, module, SN_INVALID_ID, pi_count == 4 && po_count == 2, + "carry primitive interface must have 4 inputs and 2 outputs"); + for (size_t i = 0; i < pi_count && i < 4; i++) + { + sn_obj_id_t pi = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], i); + SN_CHECK(ctx, module, pi, (sn_vec_at(uint32_t, &module->width_signed, pi) >> 1) == pi_widths[i], + "carry primitive input %zu has the wrong width", i); + } + for (size_t i = 0; i < po_count; i++) + { + sn_obj_id_t po = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PO], i); + SN_CHECK(ctx, module, po, (sn_vec_at(uint32_t, &module->width_signed, po) >> 1) == 4, + "carry primitive output %zu has the wrong width", i); + } + } + else if (strncmp(name, "__sn_DSP", 8) == 0) + { + SN_CHECK(ctx, module, SN_INVALID_ID, pi_count == 2 && po_count == 1, + "DSP primitive interface must have 2 inputs and 1 output"); + SN_CHECK(ctx, module, SN_INVALID_ID, module->type_objects[SN_MUL].size == 1, + "DSP primitive behavioral wrapper must contain one multiplier"); + } + else if (strncmp(name, "__sn_RAM", 8) == 0 || strncmp(name, "__sn_URAM", 9) == 0) + { + size_t reads = module->type_objects[SN_MEM_READ].size; + size_t writes = module->type_objects[SN_MEM_WRITE].size; + SN_CHECK(ctx, module, SN_INVALID_ID, module->type_objects[SN_MEM_OUT].size == 1, + "memory primitive wrapper must contain one memory"); + SN_CHECK(ctx, module, SN_INVALID_ID, reads >= 1 && reads <= 2 && writes >= 1 && writes <= 2, + "memory primitive wrapper must contain one or two read and write ports"); + SN_CHECK(ctx, module, SN_INVALID_ID, po_count == reads, + "memory primitive output count %zu differs from read-port count %zu", po_count, reads); + } +} + +typedef struct sn_check_hierarchy_frame_t +{ + sn_module_id_t module; + size_t next_inst; +} sn_check_hierarchy_frame_t; + +static inline void sn_check_hierarchy_visit(sn_check_ctx_t* ctx, const sn_design_t* design, sn_module_id_t root, + uint8_t* states) +{ + sn_vec_t stack; + sn_vec_init(&stack); + states[root] = 1; + sn_check_hierarchy_frame_t* first = sn_vec_push(sn_check_hierarchy_frame_t, &stack); + first->module = root; + first->next_inst = 0; + while (stack.size) + { + sn_check_hierarchy_frame_t* frame = + &sn_vec_at(sn_check_hierarchy_frame_t, &stack, stack.size - 1); + const sn_module_t* module = sn_vec_at(sn_module_t*, &design->modules, frame->module); + size_t inst_count = module ? module->inst_modules.size < module->type_objects[SN_INST].size + ? module->inst_modules.size + : module->type_objects[SN_INST].size + : 0; + if (frame->next_inst >= inst_count) + { + states[frame->module] = 2; + stack.size--; + continue; + } + size_t inst_index = frame->next_inst++; + sn_module_id_t child = sn_vec_at(sn_module_id_t, &module->inst_modules, inst_index); + if (child >= design->modules.size) + continue; + if (states[child] == 1) + sn_check_error(ctx, module, sn_vec_at(sn_obj_id_t, &module->type_objects[SN_INST], inst_index), + "recursive instantiation reaches module \"%s\"", sn_check_module_name( + sn_vec_at(sn_module_t*, &design->modules, child))); + else if (states[child] == 0) + { + states[child] = 1; + sn_check_hierarchy_frame_t* child_frame = sn_vec_push(sn_check_hierarchy_frame_t, &stack); + child_frame->module = child; + child_frame->next_inst = 0; + } + } + sn_vec_destroy(&stack); +} + +static inline bool sn_design_check(const sn_design_t* design, FILE* out, bool verbose) +{ + sn_check_ctx_t ctx = {out ? out : stderr, 0, 0, 0, verbose}; + bool safe = design != NULL; + SN_CHECK(&ctx, NULL, SN_INVALID_ID, design != NULL, "null design pointer"); + if (!safe) + return false; + safe &= sn_check_vec(&ctx, NULL, &design->modules, "modules"); + safe &= sn_check_vec(&ctx, NULL, &design->names.names, "names"); + safe &= sn_check_vec(&ctx, NULL, &design->names.links, "name links"); + safe &= sn_check_vec(&ctx, NULL, &design->names.buckets, "name buckets"); + safe &= sn_check_vec(&ctx, NULL, &design->constant_words, "constant words"); + if (!safe) + return false; + SN_CHECK(&ctx, NULL, SN_INVALID_ID, design->modules.size < SN_INVALID_ID, "module count is too large"); + SN_CHECK(&ctx, NULL, SN_INVALID_ID, design->names.names.size == design->names.links.size, + "name and link vector sizes differ"); + SN_CHECK(&ctx, NULL, SN_INVALID_ID, design->names.buckets.size != 0 && + (design->names.buckets.size & (design->names.buckets.size - 1)) == 0, + "name bucket count %zu is not a nonzero power of two", design->names.buckets.size); + if (design->names.names.size != design->names.links.size) + return false; + for (size_t i = 0; i < design->names.names.size; i++) + { + const char* name = sn_vec_at(char*, &design->names.names, i); + SN_CHECK(&ctx, NULL, SN_INVALID_ID, name != NULL, "name %zu has a null string", i); + } + uint8_t* name_seen = design->names.names.size ? (uint8_t*)calloc(design->names.names.size, 1) : NULL; + SN_CHECK(&ctx, NULL, SN_INVALID_ID, design->names.names.size == 0 || name_seen != NULL, + "cannot allocate name-hash validation state"); + for (size_t i = 0; i < design->names.buckets.size; i++) + { + uint32_t id = sn_vec_at(uint32_t, &design->names.buckets, i); + size_t steps = 0; + while (id != SN_INVALID_ID && id < design->names.names.size && steps++ <= design->names.names.size) + { + const char* name = sn_vec_at(char*, &design->names.names, id); + if (name_seen) + { + SN_CHECK(&ctx, NULL, SN_INVALID_ID, !name_seen[id], + "name %u appears more than once in hash chains", id); + name_seen[id] = 1; + } + if (name) + SN_CHECK(&ctx, NULL, SN_INVALID_ID, + ((size_t)sn_name_hash(name) & (design->names.buckets.size - 1)) == i, + "name %u is linked from the wrong hash bucket", id); + id = sn_vec_at(uint32_t, &design->names.links, id); + } + SN_CHECK(&ctx, NULL, SN_INVALID_ID, id == SN_INVALID_ID, "name bucket %zu has an invalid or cyclic chain", i); + } + if (name_seen) + for (size_t i = 0; i < design->names.names.size; i++) + { + SN_CHECK(&ctx, NULL, SN_INVALID_ID, name_seen[i], "name %zu is unreachable from the hash table", i); + } + free(name_seen); + sn_module_id_t* module_name_owners = design->names.names.size + ? (sn_module_id_t*)malloc(design->names.names.size * + sizeof(sn_module_id_t)) + : NULL; + SN_CHECK(&ctx, NULL, SN_INVALID_ID, design->names.names.size == 0 || module_name_owners != NULL, + "cannot allocate module-name validation state"); + for (size_t i = 0; module_name_owners && i < design->names.names.size; i++) + module_name_owners[i] = SN_INVALID_ID; + for (sn_module_id_t id = 0; id < design->modules.size; id++) + { + sn_module_t* module = sn_vec_at(sn_module_t*, &design->modules, id); + size_t before = ctx.errors; + SN_CHECK(&ctx, module, SN_INVALID_ID, module != NULL, "module table entry %u is null", id); + if (module) + { + SN_CHECK(&ctx, module, SN_INVALID_ID, module->design == design, + "module points to a different owning design"); + if (module_name_owners && module->name < design->names.names.size) + { + SN_CHECK(&ctx, module, SN_INVALID_ID, module_name_owners[module->name] == SN_INVALID_ID, + "module name duplicates module %u", module_name_owners[module->name]); + if (module_name_owners[module->name] == SN_INVALID_ID) + module_name_owners[module->name] = id; + } + } + size_t before_core = ctx.errors; + if (module && module->design == design && sn_check_module_core(&ctx, module) && ctx.errors == before_core) + { + bool metadata_safe = sn_check_type_metadata(&ctx, module); + if (metadata_safe) + { + sn_check_pairs(&ctx, module, SN_REG_OUT, SN_REG_IN, SN_REG_DATA); + sn_check_pairs(&ctx, module, SN_MEM_OUT, SN_MEM_IN, SN_MEM_STATE); + sn_check_pairs(&ctx, module, SN_LOOP_OUT, SN_LOOP_IN, 0); + sn_check_memories(&ctx, module); + sn_check_instances(&ctx, module); + sn_check_special_objects(&ctx, module); + sn_check_operator_shapes(&ctx, module); + } + sn_check_auxiliary_storage(&ctx, module); + sn_check_topology(&ctx, module); + sn_check_primitive(&ctx, module); + } + ctx.modules++; + if (module) + ctx.objects += module->obj_types.size; + if (verbose) + fprintf(ctx.out, "SN check: module \"%s\": %zu object(s), %zu error(s).\n", + sn_check_module_name(module), module ? module->obj_types.size : 0, ctx.errors - before); + } + free(module_name_owners); + if (design->modules.size) + { + uint8_t* states = (uint8_t*)calloc(design->modules.size, 1); + SN_CHECK(&ctx, NULL, SN_INVALID_ID, states != NULL, "cannot allocate hierarchy traversal state"); + if (states) + { + for (sn_module_id_t id = 0; id < design->modules.size; id++) + if (states[id] == 0) + sn_check_hierarchy_visit(&ctx, design, id, states); + free(states); + } + } + if (verbose || ctx.errors) + fprintf(ctx.out, "SN check: %zu module(s), %zu object(s), %zu error(s).\n", ctx.modules, ctx.objects, + ctx.errors); + return ctx.errors == 0; +} + +// Binary input is external data. Decode its byte-level representation first, then run the same non-aborting +// consistency checker used by @check before exposing any structural IDs or offsets to ordinary SN accessors. +static inline sn_design_t* sn_design_read_binary_checked(FILE* in, FILE* errors) +{ + sn_binary_read_status_t status = SN_BINARY_READ_OK; + uint32_t version = 0; + sn_design_t* design = sn_design_read_binary_raw_status(in, &status, &version); + if (!design) + { + FILE* out = errors ? errors : stderr; + if (status == SN_BINARY_READ_VERSION) + fprintf(out, "Cannot read SN binary format version %u; this build requires version %u.\n", version, + SN_BINARY_FORMAT_VERSION); + else if (status == SN_BINARY_READ_MAGIC) + fprintf(out, "Input is not an SN binary file.\n"); + else if (status == SN_BINARY_READ_LAYOUT) + fprintf(out, "SN binary layout does not match this build.\n"); + else if (status == SN_BINARY_READ_IO) + fprintf(out, "Cannot read the SN binary header.\n"); + else + fprintf(out, "Malformed or truncated SN binary input.\n"); + return NULL; + } + if (!sn_design_check(design, errors, false)) + { + sn_design_destroy(design); + return NULL; + } + return design; +} + +static inline sn_design_t* sn_design_read_binary(FILE* in) +{ + return sn_design_read_binary_checked(in, stderr); +} + +static inline sn_design_t* sn_design_read_binary_file(const char* path) +{ + if (!path) + return NULL; + FILE* in = fopen(path, "rb"); + if (!in) + return NULL; + sn_design_t* design = sn_design_read_binary_checked(in, stderr); + if (!design) + { + fclose(in); + return NULL; + } + int extra = fgetc(in); + int status = ferror(in); + status |= fclose(in) != 0; + bool valid = extra == EOF && !status; + if (!valid) + { + sn_design_destroy(design); + return NULL; + } + return design; +} + +#undef SN_CHECK + +ABC_NAMESPACE_HEADER_END + +#endif diff --git a/src/base/sn/snCom.c b/src/base/sn/snCom.c new file mode 100644 index 000000000..3af193e66 --- /dev/null +++ b/src/base/sn/snCom.c @@ -0,0 +1,2056 @@ +/**CFile**************************************************************** + + FileName [snCom.c] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [New word-level design interface.] + + Synopsis [ABC command handlers and manager ownership for the SN design interface.] + + Author [Alan Mishchenko] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: snCom.c,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#include "sn.h" +#include "snMiniAig.h" +#include "snBlast.h" +#include "snCheck.h" +#include "snMiniGate.h" +#include "snMiniLut.h" +#include "snMapLut.h" +#include "snMapTech.h" +#include "snMux.h" +#include "base/main/mainInt.h" +#include "map/mio/mio.h" + +#include +#include + +#if defined(_MSC_VER) || defined(__MINGW32__) +#include +#include +#include +#else +#include +#include +#include +#include +#endif + +ABC_NAMESPACE_IMPL_START + +typedef struct Sn_Man_t_ Sn_Man_t; +struct Sn_Man_t_ +{ + sn_design_t * pDesign; + sn_module_id_t Top; + sn_name_id_t Name; + unsigned long long Revision; + int Technology; + sn_module_id_t BlastModule; + sn_name_id_t BlastName; + sn_blast_boundary_t Boundary; + int fBoundary; + int fBlasted; + int BlastMode; + int fLastBlast; + sn_module_id_t LastBlastModule; + sn_name_id_t LastBlastName; + unsigned long long LastBlastRevision; + ABC_UINT64_T BlastBoundarySignature; + ABC_UINT64_T BlastInterfaceSignature; +}; + +enum +{ + SN_COMMAND_TECH_GENERIC = 0, + SN_COMMAND_TECH_XILINX_ULTRASCALE +}; + +static int Sn_CommandRead( Abc_Frame_t * pAbc, int argc, char ** argv ); +static int Sn_CommandSlang( Abc_Frame_t * pAbc, int argc, char ** argv ); +static int Sn_CommandCollapse( Abc_Frame_t * pAbc, int argc, char ** argv ); +static int Sn_CommandCheck( Abc_Frame_t * pAbc, int argc, char ** argv ); +static int Sn_CommandMapMem( Abc_Frame_t * pAbc, int argc, char ** argv ); +static int Sn_CommandMapDsp( Abc_Frame_t * pAbc, int argc, char ** argv ); +static int Sn_CommandMapAdd( Abc_Frame_t * pAbc, int argc, char ** argv ); +static int Sn_CommandOptMux( Abc_Frame_t * pAbc, int argc, char ** argv ); +static int Sn_CommandBlast( Abc_Frame_t * pAbc, int argc, char ** argv ); +static int Sn_CommandPut( Abc_Frame_t * pAbc, int argc, char ** argv ); +static int Sn_CommandMapLut( Abc_Frame_t * pAbc, int argc, char ** argv ); +static int Sn_CommandWrite( Abc_Frame_t * pAbc, int argc, char ** argv ); +static int Sn_CommandPs( Abc_Frame_t * pAbc, int argc, char ** argv ); +static int Sn_CommandStatus( Abc_Frame_t * pAbc, int argc, char ** argv ); +static const char * Sn_ManPutStatus( const Sn_Man_t * p, Gia_Man_t * pGia ); + +extern int tmpFile( const char * pPrefix, const char * pSuffix, char ** ppFileName ); + +static int Sn_TempPrefix( char * pBuffer, size_t nBuffer, const char * pStem ) +{ + int Written; +#if defined(_MSC_VER) || defined(__MINGW32__) + const char * pDirectory = getenv( "TEMP" ); + if ( pDirectory == NULL ) + pDirectory = "."; + Written = snprintf( pBuffer, nBuffer, "%s\\%s", pDirectory, pStem ); +#else + Written = snprintf( pBuffer, nBuffer, "/tmp/%s", pStem ); +#endif + return Written >= 0 && (size_t)Written < nBuffer; +} + +static inline Sn_Man_t * Sn_AbcGetMan( Abc_Frame_t * pAbc ) +{ + return (Sn_Man_t *)pAbc->pAbcSn; +} + +static void Sn_ManFree( Sn_Man_t * p ) +{ + if ( p == NULL ) + return; + if ( p->fBoundary ) + sn_blast_boundary_destroy( &p->Boundary ); + sn_design_destroy( p->pDesign ); + ABC_FREE( p ); +} + +static void Sn_AbcUpdateMan( Abc_Frame_t * pAbc, Sn_Man_t * p ) +{ + Sn_ManFree( Sn_AbcGetMan(pAbc) ); + pAbc->pAbcSn = p; +} + +static Sn_Man_t * Sn_ManAlloc( sn_design_t * pDesign, sn_module_id_t Top ) +{ + Sn_Man_t * p; + size_t i; + assert( pDesign != NULL ); + assert( Top < pDesign->modules.size ); + p = ABC_CALLOC( Sn_Man_t, 1 ); + assert( p != NULL ); + p->pDesign = pDesign; + p->Top = Top; + p->Name = sn_design_get_module_const( pDesign, Top )->name; + p->Revision = 1; + for ( i = 0; i < pDesign->modules.size; i++ ) + if ( sn_module_is_technology_primitive(sn_design_get_module_const(pDesign, (sn_module_id_t)i)) ) + { + p->Technology = SN_COMMAND_TECH_XILINX_ULTRASCALE; + break; + } + p->BlastModule = SN_INVALID_ID; + p->BlastName = SN_INVALID_ID; + p->LastBlastModule = SN_INVALID_ID; + p->LastBlastName = SN_INVALID_ID; + sn_blast_boundary_init( &p->Boundary ); + p->fBoundary = 1; + return p; +} + +static void Sn_ManAdvanceRevision( Sn_Man_t * p ) +{ + assert( p && p->Revision != ULLONG_MAX ); + p->Revision++; + p->fBlasted = 0; + p->BlastModule = SN_INVALID_ID; + p->BlastName = SN_INVALID_ID; +} + +static int Sn_FileHasSuffix( const char * pFileName, const char * pSuffix ) +{ + size_t nFileName = strlen( pFileName ); + size_t nSuffix = strlen( pSuffix ); + return nFileName >= nSuffix && strcmp( pFileName + nFileName - nSuffix, pSuffix ) == 0; +} + +static int Sn_CommandCheckDesign( Abc_Frame_t * pAbc ) +{ + if ( Sn_AbcGetMan(pAbc) != NULL ) + return 1; + Abc_Print( -1, "There is no current SN design.\n" ); + return 0; +} + +static Sn_Man_t * Sn_ManReadBinary( const char * pFileName, const char * pTopName, FILE * pError ) +{ + sn_design_t * pDesign; + sn_module_id_t Top; + FILE * pFile = fopen( pFileName, "rb" ); + int c, Status; + if ( pFile == NULL ) + { + Abc_Print( -1, "Cannot open input file \"%s\".\n", pFileName ); + return NULL; + } + pDesign = sn_design_read_binary_checked( pFile, pError ); + if ( pDesign == NULL ) + { + fclose( pFile ); + Abc_Print( -1, "Cannot read SN design from \"%s\".\n", pFileName ); + return NULL; + } + c = fgetc( pFile ); + Status = ferror( pFile ); + Status |= fclose( pFile ) != 0; + if ( c != EOF || Status ) + { + Abc_Print( -1, "Cannot finish reading input file \"%s\".\n", pFileName ); + sn_design_destroy( pDesign ); + return NULL; + } + if ( pDesign->modules.size == 0 ) + { + Abc_Print( -1, "SN design \"%s\" contains no modules.\n", pFileName ); + sn_design_destroy( pDesign ); + return NULL; + } + Top = pTopName ? sn_design_find_module( pDesign, pTopName ) : (sn_module_id_t)(pDesign->modules.size - 1); + if ( Top == SN_INVALID_ID ) + { + Abc_Print( -1, "Cannot find top module \"%s\" in SN design \"%s\".\n", pTopName, pFileName ); + sn_design_destroy( pDesign ); + return NULL; + } + if ( !sn_design_is_topo(pDesign) ) + { + Abc_Print( -1, "SN design \"%s\" is not topologically ordered.\n", pFileName ); + sn_design_destroy( pDesign ); + return NULL; + } + return Sn_ManAlloc( pDesign, Top ); +} + +static Sn_Man_t * Sn_ManDup( const Sn_Man_t * p ) +{ + sn_design_t * pDesign; + Sn_Man_t * pNew; + if ( p == NULL ) + return NULL; + pDesign = sn_design_dup( p->pDesign ); + assert( p->Top < pDesign->modules.size ); + pNew = Sn_ManAlloc( pDesign, p->Top ); + pNew->Name = p->Name; + pNew->Revision = p->Revision; + pNew->Technology = p->Technology; + pNew->fLastBlast = p->fLastBlast; + pNew->LastBlastModule = p->LastBlastModule; + pNew->LastBlastName = p->LastBlastName; + pNew->LastBlastRevision = p->LastBlastRevision; + pNew->BlastMode = p->BlastMode; + pNew->BlastBoundarySignature = p->BlastBoundarySignature; + pNew->BlastInterfaceSignature = p->BlastInterfaceSignature; + return pNew; +} + +// Installs a freshly reconstructed module at the stable ID selected by @blast -M. Parent insts therefore continue to +// reference the same module, and modules outside the selected hierarchy remain untouched. The temporary append-only +// module is removed from the design vector after its storage is moved into the selected slot. +static void Sn_ManReplaceModule( Sn_Man_t * p, sn_module_id_t Module, sn_name_id_t Name, + sn_module_id_t Temporary ) +{ + assert( p ); + sn_design_replace_appended_module( p->pDesign, Module, Name, Temporary ); +} + +static void Sn_ManReplaceBlastedModule( Sn_Man_t * p, sn_module_id_t Temporary ) +{ + Sn_ManReplaceModule( p, p->BlastModule, p->BlastName, Temporary ); +} + +static char * Sn_SlangExecutable() +{ + char * pExecutable = Abc_FrameReadFlag( "snslang" ); + if ( pExecutable != NULL ) + return pExecutable; +#if defined(_MSC_VER) || defined(__MINGW32__) + return "sn_slang.exe"; +#else + return "sn_slang"; +#endif +} + +static int Sn_RunProcess( char ** ppArgs ) +{ +#if defined(__wasm) + (void)ppArgs; + return -1; +#elif defined(_MSC_VER) || defined(__MINGW32__) + return (int)_spawnvp( _P_WAIT, ppArgs[0], (const char * const *)ppArgs ); +#else + pid_t Child = fork(); + int Status; + if ( Child < 0 ) + return -1; + if ( Child == 0 ) + { + execvp( ppArgs[0], ppArgs ); + _exit( 127 ); + } + if ( waitpid(Child, &Status, 0) != Child ) + return -1; + return WIFEXITED(Status) ? WEXITSTATUS(Status) : -1; +#endif +} + +static int Sn_DesignHasType( const sn_design_t * pDesign, sn_obj_type_t Type ) +{ + size_t i; + for ( i = 0; i < pDesign->modules.size; i++ ) + if ( sn_design_get_module_const(pDesign, (sn_module_id_t)i)->type_objects[Type].size ) + return 1; + return 0; +} + +static int Sn_CommandRejectLatches( const sn_design_t * pDesign, sn_module_id_t Root, const char * pCommand ) +{ + sn_module_id_t LatchModule = sn_design_find_reachable_latch( pDesign, Root, NULL ); + const sn_module_t * pModule; + if ( LatchModule == SN_INVALID_ID ) + return 0; + pModule = sn_design_get_module_const( pDesign, LatchModule ); + Abc_Print( -1, "Cannot %s: reachable module \"%s\" contains a level-sensitive latch; " + "latch blasting is not supported.\n", + pCommand, sn_name_get(&pDesign->names, pModule->name) ); + return 1; +} + +static void Sn_FormatMemory( size_t Bytes, char * pBuffer, size_t BufferSize ) +{ + static const char * pUnits[] = { "", "K", "M", "G" }; + double Value = (double)Bytes; + int Unit = 0; + while ( Value >= 1000.0 && Unit < 3 ) + { + Value /= 1000.0; + Unit++; + } + if ( Unit == 0 ) + snprintf( pBuffer, BufferSize, "%zu", Bytes ); + else + snprintf( pBuffer, BufferSize, "%.1f%s", Value, pUnits[Unit] ); +} + +static void Sn_ModulePrintStats( FILE * pOut, const sn_module_t * pModule, int fMem, int fLut, int fGate ) +{ + uint64_t nRegBits = 0; + uint64_t nMemBits = 0; + size_t i; + for ( i = 0; i < pModule->type_objects[SN_REG_OUT].size; i++ ) + { + sn_obj_id_t Obj = sn_vec_at( sn_obj_id_t, &pModule->type_objects[SN_REG_OUT], i ); + nRegBits += sn_obj_width( pModule, Obj ); + } + for ( i = 0; i < pModule->type_objects[SN_MEM_OUT].size; i++ ) + { + sn_obj_id_t Obj = sn_vec_at( sn_obj_id_t, &pModule->type_objects[SN_MEM_OUT], i ); + nMemBits += (uint64_t)sn_obj_width( pModule, Obj ) * sn_obj_mem_depth( pModule, Obj ); + } + fprintf( pOut, "%-24s : obj = %8zu pi = %6zu po = %6zu reg = %6zu/%llu inst = %6zu", + sn_name_get( &pModule->design->names, pModule->name ), pModule->obj_types.size, + pModule->type_objects[SN_PI].size, pModule->type_objects[SN_PO].size, + pModule->type_objects[SN_REG_OUT].size, (unsigned long long)nRegBits, + pModule->type_objects[SN_INST].size ); + if ( fMem ) + fprintf( pOut, " mem = %5zu/%llu", pModule->type_objects[SN_MEM_OUT].size, + (unsigned long long)nMemBits ); + if ( fLut ) + fprintf( pOut, " lut = %6zu", pModule->type_objects[SN_LUT].size ); + if ( fGate ) + fprintf( pOut, " gate = %6zu", pModule->type_objects[SN_GATE].size ); + fprintf( pOut, "\n" ); +} + +typedef struct Sn_DistribEntry_t_ Sn_DistribEntry_t; +struct Sn_DistribEntry_t_ +{ + uint32_t OutWidth; + uint32_t In0Width; + uint32_t In1Width; + uint32_t FaninNum; + uint64_t Occur; + unsigned char Signs; +}; + +static const char * Sn_ObjTypeNames[SN_OBJ_TYPE_COUNT] = { + "none", "pi", "po", "const0", "const1", "const", "buf", "fan", "inst", + "reg_out", "reg_in", "mem_out", "mem_in", "mem_read", "mem_write", "loop_out", "loop_in", + "+u", "-u", "~", "!", "&r", "~&r", "|r", "~|r", "^r", "~^r", + "+", "-", "*", "/", "%", "**", "&", "|", "^", "~^", "&&", "||", + "==", "!=", "===", "!==", "==?", "!=?", "<", "<=", ">", ">=", "<<", ">>", "<<<", ">>>", + "mux", "bmux", "pmux", "{,}", "repeat", "slice", "cast", "lut", "gate" +}; + +static int Sn_DistribEntryCompare( const void * pLeft, const void * pRight ) +{ + const Sn_DistribEntry_t * pL = (const Sn_DistribEntry_t *)pLeft; + const Sn_DistribEntry_t * pR = (const Sn_DistribEntry_t *)pRight; + if ( pL->Occur != pR->Occur ) + return pL->Occur < pR->Occur ? 1 : -1; + if ( pL->OutWidth != pR->OutWidth ) + return pL->OutWidth < pR->OutWidth ? 1 : -1; + if ( pL->In0Width != pR->In0Width ) + return pL->In0Width < pR->In0Width ? 1 : -1; + if ( pL->In1Width != pR->In1Width ) + return pL->In1Width < pR->In1Width ? 1 : -1; + if ( pL->FaninNum != pR->FaninNum ) + return pL->FaninNum < pR->FaninNum ? 1 : -1; + return (int)pL->Signs - (int)pR->Signs; +} + +static void Sn_DistribAdd( Sn_DistribEntry_t ** ppEntries, size_t * pnEntries, size_t * pnCap, + const sn_module_t * pModule, sn_obj_id_t Obj, uint64_t Mult ) +{ + Sn_DistribEntry_t Entry; + uint32_t nFanins = sn_obj_fanin_count( pModule, Obj ); + uint32_t i; + memset( &Entry, 0, sizeof(Entry) ); + Entry.OutWidth = sn_obj_width( pModule, Obj ); + Entry.FaninNum = nFanins; + Entry.Signs = (unsigned char)sn_obj_is_signed( pModule, Obj ); + for ( i = 0; i < nFanins && i < 2; i++ ) + { + sn_obj_id_t Fanin = sn_obj_fanin( pModule, Obj, i ); + if ( Fanin == SN_INVALID_ID ) + continue; + if ( i == 0 ) + Entry.In0Width = sn_obj_width( pModule, Fanin ); + else + Entry.In1Width = sn_obj_width( pModule, Fanin ); + Entry.Signs |= (unsigned char)(sn_obj_is_signed( pModule, Fanin ) << (i + 1)); + } + for ( i = 0; i < *pnEntries; i++ ) + { + Sn_DistribEntry_t * pOld = *ppEntries + i; + if ( pOld->OutWidth == Entry.OutWidth && pOld->In0Width == Entry.In0Width && + pOld->In1Width == Entry.In1Width && pOld->FaninNum == Entry.FaninNum && pOld->Signs == Entry.Signs ) + { + assert( UINT64_MAX - pOld->Occur >= Mult ); + pOld->Occur += Mult; + return; + } + } + if ( *pnEntries == *pnCap ) + { + *pnCap = *pnCap ? 2 * *pnCap : 8; + *ppEntries = ABC_REALLOC( Sn_DistribEntry_t, *ppEntries, *pnCap ); + assert( *ppEntries != NULL ); + } + Entry.Occur = Mult; + (*ppEntries)[(*pnEntries)++] = Entry; +} + +static void Sn_ModuleCollectDistrib( const sn_design_t * pDesign, sn_module_id_t ModuleId, uint64_t Mult, + Sn_DistribEntry_t ** ppEntries, size_t * pnEntries, size_t * pnCaps, + uint64_t * pTypeCounts ) +{ + const sn_module_t * pModule = sn_design_get_module_const( pDesign, ModuleId ); + sn_obj_id_t Obj; + for ( Obj = 0; Obj < pModule->obj_types.size; Obj++ ) + { + sn_obj_type_t Type = sn_obj_type( pModule, Obj ); + assert( UINT64_MAX - pTypeCounts[Type] >= Mult ); + pTypeCounts[Type] += Mult; + Sn_DistribAdd( ppEntries + Type, pnEntries + Type, pnCaps + Type, pModule, Obj, Mult ); + } +} + +typedef struct Sn_DistribFrame_t_ +{ + sn_module_id_t Module; + size_t NextInst; +} Sn_DistribFrame_t; + +static void Sn_DesignPrintDistrib( FILE * pOut, const sn_design_t * pDesign, sn_module_id_t Top ) +{ + Sn_DistribEntry_t * pEntries[SN_OBJ_TYPE_COUNT] = { NULL }; + size_t nEntries[SN_OBJ_TYPE_COUNT] = { 0 }; + size_t nCaps[SN_OBJ_TYPE_COUNT] = { 0 }; + uint64_t TypeCounts[SN_OBJ_TYPE_COUNT] = { 0 }; + unsigned char * pStates = ABC_CALLOC( unsigned char, pDesign->modules.size ); + uint64_t * pMults = ABC_CALLOC( uint64_t, pDesign->modules.size ); + sn_vec_t Stack, Postorder; + Sn_DistribFrame_t * pFrame; + size_t i; + int Type; + assert( pStates != NULL && pMults != NULL ); + sn_vec_init( &Stack ); + sn_vec_init( &Postorder ); + pStates[Top] = 1; + pFrame = sn_vec_push( Sn_DistribFrame_t, &Stack ); + pFrame->Module = Top; + pFrame->NextInst = 0; + while ( Stack.size ) + { + const sn_module_t * pModule; + pFrame = &sn_vec_at( Sn_DistribFrame_t, &Stack, Stack.size - 1 ); + pModule = sn_design_get_module_const( pDesign, pFrame->Module ); + if ( pFrame->NextInst < pModule->inst_modules.size ) + { + sn_module_id_t Child = sn_vec_at( sn_module_id_t, &pModule->inst_modules, pFrame->NextInst++ ); + assert( pStates[Child] != 1 ); + if ( pStates[Child] == 0 ) + { + pStates[Child] = 1; + pFrame = sn_vec_push( Sn_DistribFrame_t, &Stack ); + pFrame->Module = Child; + pFrame->NextInst = 0; + } + continue; + } + pStates[pFrame->Module] = 2; + *sn_vec_push( sn_module_id_t, &Postorder ) = pFrame->Module; + Stack.size--; + } + pMults[Top] = 1; + for ( i = Postorder.size; i-- > 0; ) + { + sn_module_id_t Module = sn_vec_at( sn_module_id_t, &Postorder, i ); + const sn_module_t * pModule = sn_design_get_module_const( pDesign, Module ); + uint64_t Mult = pMults[Module]; + if ( Mult == 0 ) + continue; + Sn_ModuleCollectDistrib( pDesign, Module, Mult, pEntries, nEntries, nCaps, TypeCounts ); + for ( size_t k = 0; k < pModule->inst_modules.size; k++ ) + { + sn_module_id_t Child = sn_vec_at( sn_module_id_t, &pModule->inst_modules, k ); + assert( UINT64_MAX - pMults[Child] >= Mult ); + pMults[Child] += Mult; + } + } + fprintf( pOut, "ID : name occurrence (occurrence)=. ...\n" ); + for ( Type = 0; Type < SN_OBJ_TYPE_COUNT; Type++ ) + { + size_t k; + if ( TypeCounts[Type] == 0 ) + continue; + qsort( pEntries[Type], nEntries[Type], sizeof(Sn_DistribEntry_t), Sn_DistribEntryCompare ); + fprintf( pOut, "%2d : %-10s %10llu ", Type, Sn_ObjTypeNames[Type], + (unsigned long long)TypeCounts[Type] ); + for ( k = 0; k < nEntries[Type]; k++ ) + { + const Sn_DistribEntry_t * pEntry = pEntries[Type] + k; + if ( k && k % 6 == 0 ) + fprintf( pOut, "\n " ); + fprintf( pOut, "(%llu)%s%u", (unsigned long long)pEntry->Occur, + (pEntry->Signs & 1) ? "-" : "", pEntry->OutWidth ); + if ( pEntry->FaninNum ) + fprintf( pOut, "=%s%u", (pEntry->Signs & 2) ? "-" : "", pEntry->In0Width ); + if ( pEntry->FaninNum > 1 ) + fprintf( pOut, ".%s%u", (pEntry->Signs & 4) ? "-" : "", pEntry->In1Width ); + if ( pEntry->FaninNum > 2 ) + fprintf( pOut, "[%u]", pEntry->FaninNum ); + fprintf( pOut, " " ); + } + fprintf( pOut, "\n" ); + ABC_FREE( pEntries[Type] ); + } + sn_vec_destroy( &Postorder ); + sn_vec_destroy( &Stack ); + ABC_FREE( pMults ); + ABC_FREE( pStates ); +} + +void Sn_Init( Abc_Frame_t * pAbc ) +{ + Cmd_CommandAdd( pAbc, "New word level", "@slang", Sn_CommandSlang, 0 ); + Cmd_CommandAdd( pAbc, "New word level", "@read", Sn_CommandRead, 0 ); + Cmd_CommandAdd( pAbc, "New word level", "@check", Sn_CommandCheck, 0 ); + Cmd_CommandAdd( pAbc, "New word level", "@collapse", Sn_CommandCollapse, 0 ); + Cmd_CommandAdd( pAbc, "New word level", "@map_mem", Sn_CommandMapMem, 0 ); + Cmd_CommandAdd( pAbc, "New word level", "@map_dsp", Sn_CommandMapDsp, 0 ); + Cmd_CommandAdd( pAbc, "New word level", "@map_add", Sn_CommandMapAdd, 0 ); + Cmd_CommandAdd( pAbc, "New word level", "@opt_mux", Sn_CommandOptMux, 0 ); + Cmd_CommandAdd( pAbc, "New word level", "@blast", Sn_CommandBlast, 0 ); + Cmd_CommandAdd( pAbc, "New word level", "@put", Sn_CommandPut, 0 ); + Cmd_CommandAdd( pAbc, "New word level", "@map_lut", Sn_CommandMapLut, 0 ); + Cmd_CommandAdd( pAbc, "New word level", "@write", Sn_CommandWrite, 0 ); + Cmd_CommandAdd( pAbc, "New word level", "@ps", Sn_CommandPs, 0 ); + Cmd_CommandAdd( pAbc, "New word level", "@status", Sn_CommandStatus, 0 ); +} + +void Sn_End( Abc_Frame_t * pAbc ) +{ + Sn_AbcUpdateMan( pAbc, NULL ); +} + +static int Sn_CommandRead( Abc_Frame_t * pAbc, int argc, char ** argv ) +{ + char * pTopName = NULL; + char * pFileName; + Sn_Man_t * p; + int c, fVerbose = 0; + Extra_UtilGetoptReset(); + while ( (c = Extra_UtilGetopt(argc, argv, "Mvh")) != EOF ) + { + switch ( c ) + { + case 'M': + if ( globalUtilOptind >= argc ) + { + Abc_Print( -1, "Command line switch \"-M\" should be followed by a module name.\n" ); + goto usage; + } + pTopName = argv[globalUtilOptind++]; + break; + case 'v': + fVerbose ^= 1; + break; + case 'h': + default: + goto usage; + } + } + if ( argc - globalUtilOptind != 1 ) + goto usage; + pFileName = argv[globalUtilOptind]; + p = Sn_ManReadBinary( pFileName, pTopName, Abc_FrameReadErr(pAbc) ); + if ( p == NULL ) + return 1; + Sn_AbcUpdateMan( pAbc, p ); + if ( fVerbose ) + Abc_Print( 1, "Read SN design \"%s\" with %zu modules. Top module is \"%s\".\n", pFileName, + p->pDesign->modules.size, + sn_name_get(&p->pDesign->names, sn_design_get_module_const(p->pDesign, p->Top)->name) ); + return 0; + +usage: + Abc_Print( -2, "usage: @read [-Mvh] \n" ); + Abc_Print( -2, "\t reads a binary SN design\n" ); + Abc_Print( -2, "\t-M name : select the top module [default = last module]\n" ); + Abc_Print( -2, "\t-v : print verbose output\n" ); + Abc_Print( -2, "\t-h : print the command usage\n" ); + return 1; +} + +static int Sn_CommandSlang( Abc_Frame_t * pAbc, int argc, char ** argv ) +{ + char TempPrefix[512]; + char * pTopName = NULL; + char * pExtraFile = NULL; + char * pDefine; + char * pTempName = NULL; + char ** ppArgs; + Vec_Ptr_t * vDefines = Vec_PtrAlloc( 4 ); + Sn_Man_t * p; + int c, fVerbose = 0, nFiles, nArgs, i, k, File; + Extra_UtilGetoptReset(); + while ( (c = Extra_UtilGetopt(argc, argv, "MFDBIvh")) != EOF ) + { + switch ( c ) + { + case 'M': + if ( globalUtilOptind >= argc ) + { + Abc_Print( -1, "Command line switch \"-%c\" should be followed by a module name.\n", c ); + goto usage; + } + pTopName = argv[globalUtilOptind++]; + break; + case 'F': + if ( globalUtilOptind >= argc ) + { + Abc_Print( -1, "Command line switch \"-F\" should be followed by a file name.\n" ); + goto usage; + } + pExtraFile = argv[globalUtilOptind++]; + break; + case 'D': + if ( globalUtilOptind >= argc ) + { + Abc_Print( -1, "Command line switch \"-D\" should be followed by definitions.\n" ); + goto usage; + } + Vec_PtrPush( vDefines, argv[globalUtilOptind++] ); + break; + case 'B': + case 'I': + Abc_Print( -1, "Command line switch \"-%c\" is not supported by the external SN frontend yet.\n", c ); + Vec_PtrFree( vDefines ); + return 1; + case 'v': + fVerbose ^= 1; + break; + case 'h': + default: + goto usage; + } + } + nFiles = argc - globalUtilOptind + (pExtraFile != NULL); + if ( nFiles == 0 ) + goto usage; + for ( i = globalUtilOptind; i < argc; i++ ) + { + FILE * pFile = fopen( argv[i], "r" ); + if ( pFile == NULL ) + { + Abc_Print( -1, "Cannot open input file \"%s\".\n", argv[i] ); + Vec_PtrFree( vDefines ); + return 1; + } + fclose( pFile ); + } + if ( pExtraFile != NULL ) + { + FILE * pFile = fopen( pExtraFile, "r" ); + if ( pFile == NULL ) + { + Abc_Print( -1, "Cannot open input file \"%s\".\n", pExtraFile ); + Vec_PtrFree( vDefines ); + return 1; + } + fclose( pFile ); + } + if ( !Sn_TempPrefix(TempPrefix, sizeof(TempPrefix), "sn_slang_") ) + { + Abc_Print( -1, "Temporary-file path is too long.\n" ); + Vec_PtrFree( vDefines ); + return 1; + } + File = tmpFile( TempPrefix, ".sn", &pTempName ); + if ( File < 0 ) + { + Abc_Print( -1, "Cannot create a temporary SN file.\n" ); + Vec_PtrFree( vDefines ); + return 1; + } +#if defined(_MSC_VER) || defined(__MINGW32__) + _close( File ); +#else + close( File ); +#endif + nArgs = 1 + (pTopName ? 2 : 0) + 2 * Vec_PtrSize(vDefines) + (fVerbose ? 1 : 0) + 2 + nFiles + 1; + ppArgs = ABC_ALLOC( char *, nArgs ); + k = 0; + ppArgs[k++] = Sn_SlangExecutable(); + if ( pTopName ) + { + ppArgs[k++] = "-M"; + ppArgs[k++] = pTopName; + } + Vec_PtrForEachEntry( char *, vDefines, pDefine, i ) + { + ppArgs[k++] = "-D"; + ppArgs[k++] = pDefine; + } + if ( fVerbose ) + ppArgs[k++] = "-t"; + ppArgs[k++] = "-o"; + ppArgs[k++] = pTempName; + for ( i = globalUtilOptind; i < argc; i++ ) + ppArgs[k++] = argv[i]; + if ( pExtraFile ) + ppArgs[k++] = pExtraFile; + ppArgs[k] = NULL; + assert( k + 1 == nArgs ); + if ( fVerbose ) + { + Abc_Print( 1, "Running:" ); + for ( i = 0; i < k; i++ ) + Abc_Print( 1, " %s", ppArgs[i] ); + Abc_Print( 1, "\n" ); + fflush( pAbc->Out ); + } + c = Sn_RunProcess( ppArgs ); + ABC_FREE( ppArgs ); + Vec_PtrFree( vDefines ); + if ( c != 0 ) + { + Abc_Print( -1, "External SN frontend failed with status %d.\n", c ); + remove( pTempName ); + ABC_FREE( pTempName ); + return 1; + } + p = Sn_ManReadBinary( pTempName, pTopName, Abc_FrameReadErr(pAbc) ); + remove( pTempName ); + ABC_FREE( pTempName ); + if ( p == NULL ) + return 1; + Sn_AbcUpdateMan( pAbc, p ); + if ( fVerbose ) + Abc_Print( 1, "Loaded SN design with %zu modules.\n", p->pDesign->modules.size ); + return 0; + +usage: + Vec_PtrFree( vDefines ); + Abc_Print( -2, "usage: @slang [-M ] [-D ] [-F ] [-vh] ...\n" ); + Abc_Print( -2, "\t reads Verilog or SystemVerilog using the external sn_slang frontend\n" ); + Abc_Print( -2, "\t based on Mike Popoloski's slang: https://github.com/MikePopoloski/slang\n" ); + Abc_Print( -2, "\t-M name : select the top module\n" ); + Abc_Print( -2, "\t-D def : define one macro as NAME or NAME=value; may be repeated\n" ); + Abc_Print( -2, "\t-F file : add another Verilog/SystemVerilog input file\n" ); + Abc_Print( -2, "\t-v : print the external command and frontend timing\n" ); + Abc_Print( -2, "\t-h : print the command usage\n" ); + return 1; +} + +static int Sn_CommandCollapse( Abc_Frame_t * pAbc, int argc, char ** argv ) +{ + Sn_Man_t * p; + int c, fVerbose = 0; + Extra_UtilGetoptReset(); + while ( (c = Extra_UtilGetopt(argc, argv, "vh")) != EOF ) + { + switch ( c ) + { + case 'v': + fVerbose ^= 1; + break; + case 'h': + default: + goto usage; + } + } + if ( argc != globalUtilOptind ) + goto usage; + if ( !Sn_CommandCheckDesign(pAbc) ) + return 1; + p = Sn_ManDup( Sn_AbcGetMan(pAbc) ); + if ( p == NULL ) + { + Abc_Print( -1, "Cannot duplicate the current SN design.\n" ); + return 1; + } + p->Top = sn_design_collapse_module_tech( p->pDesign, p->Top ); + assert( sn_design_is_topo(p->pDesign) ); + Sn_ManAdvanceRevision( p ); + Sn_AbcUpdateMan( pAbc, p ); + if ( fVerbose ) + Abc_Print( 1, "Collapsed SN design into module \"%s\".\n", + sn_name_get(&p->pDesign->names, sn_design_get_module_const(p->pDesign, p->Top)->name) ); + return 0; + +usage: + Abc_Print( -2, "usage: @collapse [-vh]\n" ); + Abc_Print( -2, "\t flattens user hierarchy while preserving mapped technology primitives\n" ); + Abc_Print( -2, "\t-v : print the resulting flat module name\n" ); + Abc_Print( -2, "\t-h : print the command usage\n" ); + return 1; +} + +static int Sn_CommandMapTech( Abc_Frame_t * pAbc, int fMapMem, int fMapDsp, int fMapAdd, int fVerbose ) +{ + Sn_Man_t * p = Sn_ManDup( Sn_AbcGetMan(pAbc) ); + sn_tech_t Tech = sn_tech_xilinx_ultrascale(); + sn_tech_map_options_t Options = sn_tech_map_default_options(); + sn_tech_map_stats_t Stats = {0}; + Options.map_memories = fMapMem != 0; + Options.map_multipliers = fMapDsp != 0; + Options.map_adders = fMapAdd != 0; + if ( p == NULL ) + { + Abc_Print( -1, "Cannot duplicate the current SN design.\n" ); + return 1; + } + if ( !sn_design_check(p->pDesign, Abc_FrameReadErr(pAbc), false) ) + { + Sn_ManFree( p ); + return 1; + } + p->Top = sn_design_map_tech_hierarchy( p->pDesign, p->Top, &Tech, &Options, &Stats ); + if ( p->Top == SN_INVALID_ID ) + { + Abc_Print( -1, "Technology mapping cannot honor the requested hard-block constraints.\n" ); + Sn_ManFree( p ); + return 1; + } + if ( !sn_design_check(p->pDesign, Abc_FrameReadErr(pAbc), false) ) + { + Sn_ManFree( p ); + return 1; + } + p->Technology = SN_COMMAND_TECH_XILINX_ULTRASCALE; + Sn_ManAdvanceRevision( p ); + Sn_AbcUpdateMan( pAbc, p ); + if ( fVerbose ) + Abc_Print( 1, "Mapped SN design: memory instances = %zu DSP instances = %zu CARRY4 instances = %zu.\n", + Stats.mem_insts, Stats.dsp_insts, Stats.carry_insts ); + return 0; +} + +static int Sn_CommandCheck( Abc_Frame_t * pAbc, int argc, char ** argv ) +{ + int c, fVerbose = 0; + Extra_UtilGetoptReset(); + while ( (c = Extra_UtilGetopt(argc, argv, "vh")) != EOF ) + { + if ( c == 'v' ) + fVerbose ^= 1; + else + goto usage; + } + if ( argc != globalUtilOptind ) + goto usage; + if ( !Sn_CommandCheckDesign(pAbc) ) + return 1; + if ( !sn_design_check(Sn_AbcGetMan(pAbc)->pDesign, Abc_FrameReadErr(pAbc), fVerbose != 0) ) + return 1; + if ( !fVerbose ) + Abc_Print( 1, "SN design is consistent.\n" ); + return 0; + +usage: + Abc_Print( -2, "usage: @check [-vh]\n" ); + Abc_Print( -2, "\t checks the complete SN design for structural consistency\n" ); + Abc_Print( -2, "\t-v : print per-module and design summaries\n" ); + Abc_Print( -2, "\t-h : print the command usage\n" ); + return 1; +} + +static int Sn_CommandMapMem( Abc_Frame_t * pAbc, int argc, char ** argv ) +{ + int c, fVerbose = 0; + Extra_UtilGetoptReset(); + while ( (c = Extra_UtilGetopt(argc, argv, "vh")) != EOF ) + { + if ( c == 'v' ) + fVerbose ^= 1; + else + goto usage; + } + if ( argc != globalUtilOptind ) + goto usage; + if ( !Sn_CommandCheckDesign(pAbc) ) + return 1; + return Sn_CommandMapTech( pAbc, 1, 0, 0, fVerbose ); + +usage: + Abc_Print( -2, "usage: @map_mem [-vh]\n" ); + Abc_Print( -2, "\t maps memories into AMD/Xilinx UltraScale+ primitives\n" ); + Abc_Print( -2, "\t-v : print mapping statistics\n" ); + Abc_Print( -2, "\t-h : print the command usage\n" ); + return 1; +} + +static int Sn_CommandMapDsp( Abc_Frame_t * pAbc, int argc, char ** argv ) +{ + int c, fVerbose = 0; + Extra_UtilGetoptReset(); + while ( (c = Extra_UtilGetopt(argc, argv, "vh")) != EOF ) + { + if ( c == 'v' ) + fVerbose ^= 1; + else + goto usage; + } + if ( argc != globalUtilOptind ) + goto usage; + if ( !Sn_CommandCheckDesign(pAbc) ) + return 1; + return Sn_CommandMapTech( pAbc, 0, 1, 0, fVerbose ); + +usage: + Abc_Print( -2, "usage: @map_dsp [-vh]\n" ); + Abc_Print( -2, "\t maps multipliers into AMD/Xilinx UltraScale+ DSP primitives\n" ); + Abc_Print( -2, "\t-v : print mapping statistics\n" ); + Abc_Print( -2, "\t-h : print the command usage\n" ); + return 1; +} + +static int Sn_CommandMapAdd( Abc_Frame_t * pAbc, int argc, char ** argv ) +{ + int c, fVerbose = 0; + Extra_UtilGetoptReset(); + while ( (c = Extra_UtilGetopt(argc, argv, "vh")) != EOF ) + { + if ( c == 'v' ) + fVerbose ^= 1; + else + goto usage; + } + if ( argc != globalUtilOptind ) + goto usage; + if ( !Sn_CommandCheckDesign(pAbc) ) + return 1; + return Sn_CommandMapTech( pAbc, 0, 0, 1, fVerbose ); + +usage: + Abc_Print( -2, "usage: @map_add [-vh]\n" ); + Abc_Print( -2, "\t maps adders and subtractors into AMD/Xilinx UltraScale+ CARRY4 primitives\n" ); + Abc_Print( -2, "\t-v : print mapping statistics\n" ); + Abc_Print( -2, "\t-h : print the command usage\n" ); + return 1; +} + +static int Sn_CommandOptMux( Abc_Frame_t * pAbc, int argc, char ** argv ) +{ + Sn_Man_t * p, * pCurrent; + sn_share_options_t Options = sn_share_default_options(); + sn_share_stats_t Stats; + int c, fVerbose = 0; + Extra_UtilGetoptReset(); + while ( (c = Extra_UtilGetopt(argc, argv, "vh")) != EOF ) + { + if ( c == 'v' ) + fVerbose ^= 1; + else + goto usage; + } + if ( argc != globalUtilOptind ) + goto usage; + if ( !Sn_CommandCheckDesign(pAbc) ) + return 1; + pCurrent = Sn_AbcGetMan( pAbc ); + if ( !sn_design_check(pCurrent->pDesign, pAbc->Err, 0) ) + { + Abc_Print( -1, "Cannot @opt_mux: the current SN design is inconsistent.\n" ); + return 1; + } + p = Sn_ManDup( pCurrent ); + if ( p == NULL ) + { + Abc_Print( -1, "Cannot duplicate the current SN design.\n" ); + return 1; + } + Stats = sn_design_share( p->pDesign, Options ); + if ( !sn_design_check(p->pDesign, pAbc->Err, 0) ) + { + Abc_Print( -1, "Cannot @opt_mux: the transformed SN design is inconsistent.\n" ); + Sn_ManFree( p ); + return 1; + } + if ( Stats.modules == 0 ) + { + Sn_ManFree( p ); + if ( fVerbose ) + Abc_Print( 1, "Optimized SN mux paths: no profitable rewrites.\n" ); + return 0; + } + Sn_ManAdvanceRevision( p ); + Sn_AbcUpdateMan( pAbc, p ); + if ( fVerbose ) + Abc_Print( 1, "Optimized SN mux paths: modules = %llu registers = %llu muxes = %llu paths = %llu -> %llu.\n", + (unsigned long long)Stats.modules, (unsigned long long)Stats.registers, + (unsigned long long)Stats.muxes, (unsigned long long)Stats.paths_before, + (unsigned long long)Stats.paths_after ); + return 0; + +usage: + Abc_Print( -2, "usage: @opt_mux [-vh]\n" ); + Abc_Print( -2, "\t shares repeated alternatives in register mux cones\n" ); + Abc_Print( -2, "\t-v : print transformation statistics\n" ); + Abc_Print( -2, "\t-h : print the command usage\n" ); + return 1; +} + +static char * Sn_BoundaryName( const Sn_Man_t * p, const sn_blast_boundary_bit_t * pBit, + const char * pFallback, int Index ) +{ + const char * pBase = pFallback; + char * pName; + int nChars; + if ( pBit->signal.occurrence < p->Boundary.occurrences.size ) + { + sn_module_id_t Module = sn_vec_at( sn_blast_occurrence_t, &p->Boundary.occurrences, + pBit->signal.occurrence ).module; + const sn_module_t * pModule = sn_design_get_module_const( p->pDesign, Module ); + if ( pBit->signal.object < pModule->obj_types.size ) + { + sn_name_id_t Name = sn_obj_name_id( pModule, pBit->signal.object ); + if ( Name != SN_INVALID_ID ) + pBase = sn_name_get( &p->pDesign->names, Name ); + } + } + nChars = snprintf( NULL, 0, "%s[%u]_%d", pBase, pBit->signal.bit, Index ); + assert( nChars >= 0 ); + pName = ABC_ALLOC( char, nChars + 1 ); + snprintf( pName, nChars + 1, "%s[%u]_%d", pBase, pBit->signal.bit, Index ); + return pName; +} + +static ABC_UINT64_T Sn_SignatureWord( ABC_UINT64_T Signature, ABC_UINT64_T Word ) +{ + int i; + for ( i = 0; i < 8; i++ ) + { + Signature ^= (unsigned char)(Word >> (8 * i)); + Signature *= ABC_CONST(1099511628211); + } + return Signature; +} + +static ABC_UINT64_T Sn_SignatureString( ABC_UINT64_T Signature, const char * pString ) +{ + if ( pString == NULL ) + return Sn_SignatureWord( Signature, ~(ABC_UINT64_T)0 ); + while ( *pString ) + { + Signature ^= (unsigned char)*pString++; + Signature *= ABC_CONST(1099511628211); + } + return Sn_SignatureWord( Signature, 0 ); +} + +static ABC_UINT64_T Sn_BoundaryBitSignature( ABC_UINT64_T Signature, + const sn_blast_boundary_bit_t * pBit ) +{ + Signature = Sn_SignatureWord( Signature, (uint32_t)pBit->kind ); + Signature = Sn_SignatureWord( Signature, pBit->signal.occurrence ); + Signature = Sn_SignatureWord( Signature, pBit->signal.object ); + Signature = Sn_SignatureWord( Signature, pBit->signal.bit ); + Signature = Sn_SignatureWord( Signature, pBit->owner ); + return Sn_SignatureWord( Signature, pBit->port ); +} + +// This signature covers the selected module and every table that gives meaning to a boundary owner. Endpoint order, +// kind, hierarchical signal reference, owner, port, and bit are included explicitly to avoid hashing struct padding. +static ABC_UINT64_T Sn_BoundarySignature( const Sn_Man_t * p ) +{ + ABC_UINT64_T Signature = ABC_CONST(0xcbf29ce484222325); + size_t i; + int Slot; + Signature = Sn_SignatureWord( Signature, p->BlastModule ); + Signature = Sn_SignatureString( Signature, sn_name_get(&p->pDesign->names, p->BlastName) ); + Signature = Sn_SignatureWord( Signature, (uint32_t)p->BlastMode ); + Signature = Sn_SignatureWord( Signature, p->Boundary.register_bits ); + Signature = Sn_SignatureWord( Signature, p->Boundary.occurrences.size ); + for ( i = 0; i < p->Boundary.occurrences.size; i++ ) + { + const sn_blast_occurrence_t * pEntry = &sn_vec_at(sn_blast_occurrence_t, &p->Boundary.occurrences, i); + Signature = Sn_SignatureWord( Signature, pEntry->module ); + Signature = Sn_SignatureWord( Signature, pEntry->parent_occurrence ); + Signature = Sn_SignatureWord( Signature, pEntry->parent_inst ); + } + Signature = Sn_SignatureWord( Signature, p->Boundary.primitives.size ); + for ( i = 0; i < p->Boundary.primitives.size; i++ ) + { + const sn_blast_primitive_t * pEntry = &sn_vec_at(sn_blast_primitive_t, &p->Boundary.primitives, i); + Signature = Sn_SignatureWord( Signature, pEntry->occurrence ); + Signature = Sn_SignatureWord( Signature, pEntry->inst ); + Signature = Sn_SignatureWord( Signature, pEntry->module ); + Signature = Sn_SignatureWord( Signature, pEntry->ci_begin ); + Signature = Sn_SignatureWord( Signature, pEntry->ci_count ); + Signature = Sn_SignatureWord( Signature, pEntry->co_begin ); + Signature = Sn_SignatureWord( Signature, pEntry->co_count ); + } + Signature = Sn_SignatureWord( Signature, p->Boundary.registers.size ); + for ( i = 0; i < p->Boundary.registers.size; i++ ) + { + const sn_blast_register_t * pEntry = &sn_vec_at(sn_blast_register_t, &p->Boundary.registers, i); + Signature = Sn_SignatureWord( Signature, pEntry->occurrence ); + Signature = Sn_SignatureWord( Signature, pEntry->reg_out ); + Signature = Sn_SignatureWord( Signature, pEntry->ci_begin ); + Signature = Sn_SignatureWord( Signature, pEntry->co_begin ); + Signature = Sn_SignatureWord( Signature, pEntry->width ); + for ( Slot = 0; Slot < SN_REG_FANIN_COUNT; Slot++ ) + Signature = Sn_SignatureWord( Signature, pEntry->control_co_begin[Slot] ); + } + Signature = Sn_SignatureWord( Signature, p->Boundary.loops.size ); + for ( i = 0; i < p->Boundary.loops.size; i++ ) + { + const sn_blast_loop_t * pEntry = &sn_vec_at(sn_blast_loop_t, &p->Boundary.loops, i); + Signature = Sn_SignatureWord( Signature, pEntry->occurrence ); + Signature = Sn_SignatureWord( Signature, pEntry->loop_out ); + Signature = Sn_SignatureWord( Signature, pEntry->co_begin ); + Signature = Sn_SignatureWord( Signature, pEntry->width ); + } + Signature = Sn_SignatureWord( Signature, p->Boundary.cis.size ); + for ( i = 0; i < p->Boundary.cis.size; i++ ) + Signature = Sn_BoundaryBitSignature( + Signature, &sn_vec_at(sn_blast_boundary_bit_t, &p->Boundary.cis, i) ); + Signature = Sn_SignatureWord( Signature, p->Boundary.cos.size ); + for ( i = 0; i < p->Boundary.cos.size; i++ ) + Signature = Sn_BoundaryBitSignature( + Signature, &sn_vec_at(sn_blast_boundary_bit_t, &p->Boundary.cos, i) ); + return Signature; +} + +// Boundary names contain the retained signal name, bit index, and a unique interface index. ABC's normal GIA +// synthesis commands preserve these names. Hashing their ordered vectors detects interface permutations and also +// rejects a command that discarded the identity needed to prove that the boundary order is unchanged. +static ABC_UINT64_T Sn_GiaInterfaceSignature( Gia_Man_t * pGia ) +{ + ABC_UINT64_T Signature = ABC_CONST(0xcbf29ce484222325); + char * pName; + int i; + Signature = Sn_SignatureWord( Signature, Gia_ManCiNum(pGia) ); + Signature = Sn_SignatureWord( Signature, Gia_ManCoNum(pGia) ); + Signature = Sn_SignatureWord( Signature, pGia->vNamesIn ? Vec_PtrSize(pGia->vNamesIn) : ~(ABC_UINT64_T)0 ); + if ( pGia->vNamesIn ) + Vec_PtrForEachEntry( char *, pGia->vNamesIn, pName, i ) + Signature = Sn_SignatureString( Signature, pName ); + Signature = Sn_SignatureWord( Signature, pGia->vNamesOut ? Vec_PtrSize(pGia->vNamesOut) : ~(ABC_UINT64_T)0 ); + if ( pGia->vNamesOut ) + Vec_PtrForEachEntry( char *, pGia->vNamesOut, pName, i ) + Signature = Sn_SignatureString( Signature, pName ); + return Signature; +} + +static void Sn_GiaSetNames( Abc_Frame_t * pAbc, const Sn_Man_t * p ) +{ + Gia_Man_t * pGia = Abc_FrameReadGia( pAbc ); + size_t i; + assert( pGia && (size_t)Gia_ManCiNum(pGia) == p->Boundary.cis.size ); + assert( (size_t)Gia_ManCoNum(pGia) == p->Boundary.cos.size ); + if ( pGia->vNamesIn ) + Vec_PtrFreeFree( pGia->vNamesIn ); + if ( pGia->vNamesOut ) + Vec_PtrFreeFree( pGia->vNamesOut ); + pGia->vNamesIn = Vec_PtrAlloc( Gia_ManCiNum(pGia) ); + pGia->vNamesOut = Vec_PtrAlloc( Gia_ManCoNum(pGia) ); + for ( i = 0; i < p->Boundary.cis.size; i++ ) + Vec_PtrPush( pGia->vNamesIn, + Sn_BoundaryName(p, &sn_vec_at(sn_blast_boundary_bit_t, &p->Boundary.cis, i), "pi", (int)i) ); + for ( i = 0; i < p->Boundary.cos.size; i++ ) + Vec_PtrPush( pGia->vNamesOut, + Sn_BoundaryName(p, &sn_vec_at(sn_blast_boundary_bit_t, &p->Boundary.cos, i), "po", (int)i) ); + ABC_FREE( pGia->pName ); + pGia->pName = Abc_UtilStrsav( + (char *)sn_name_get(&p->pDesign->names, sn_design_get_module_const(p->pDesign, p->BlastModule)->name) ); +} + +static int Sn_CommandBlast( Abc_Frame_t * pAbc, int argc, char ** argv ) +{ + Sn_Man_t * p; + Mini_Aig_t * pAig; + abctime clkBlast, clkImport, clkNames; + int nMiniAnds; + sn_blast_options_t Options = sn_blast_default_options(); + sn_blast_hier_stats_t Stats = {0}; + char * pModuleName = NULL; + sn_module_id_t BlastModule; + int c, fVerbose = 0; + Options.mode = SN_BLAST_SEQ; + Extra_UtilGetoptReset(); + while ( (c = Extra_UtilGetopt(argc, argv, "Mctdbrvh")) != EOF ) + { + switch ( c ) + { + case 'M': + if ( globalUtilOptind >= argc ) + { + Abc_Print( -1, "Command line switch \"-M\" should be followed by a module name.\n" ); + goto usage; + } + pModuleName = argv[globalUtilOptind++]; + break; + case 'c': + Options.mode = SN_BLAST_COMB; + break; + case 't': + Options.mode = SN_BLAST_TRANSITION; + break; + case 'b': + Options.mul_mode = Options.mul_mode == SN_BLAST_MUL_BOOTH ? SN_BLAST_MUL_BAUGH_WOOLEY : + SN_BLAST_MUL_BOOTH; + break; + case 'd': + Options.delay_comparators ^= 1; + break; + case 'r': + Options.ripple_adders ^= 1; + break; + case 'v': + fVerbose ^= 1; + break; + case 'h': + default: + goto usage; + } + } + if ( argc != globalUtilOptind ) + goto usage; + if ( !Sn_CommandCheckDesign(pAbc) ) + return 1; + p = Sn_AbcGetMan( pAbc ); + BlastModule = pModuleName ? sn_design_find_module( p->pDesign, pModuleName ) : p->Top; + if ( BlastModule == SN_INVALID_ID ) + { + Abc_Print( -1, "Cannot find module \"%s\" in the current SN design.\n", pModuleName ); + return 1; + } + if ( Sn_CommandRejectLatches(p->pDesign, BlastModule, "@blast") ) + return 1; + sn_blast_boundary_destroy( &p->Boundary ); + sn_blast_boundary_init( &p->Boundary ); + clkBlast = Abc_Clock(); + pAig = sn_design_blast_hier_boundary_options( p->pDesign, BlastModule, Options, &Stats, &p->Boundary ); + clkBlast = Abc_Clock() - clkBlast; + nMiniAnds = Mini_AigAndNum( pAig ); + clkImport = Abc_Clock(); + Abc_FrameGiaInputMiniAig( pAbc, pAig ); + clkImport = Abc_Clock() - clkImport; + Mini_AigStop( pAig ); + p->BlastModule = BlastModule; + p->BlastName = sn_design_get_module_const( p->pDesign, BlastModule )->name; + p->fBlasted = 1; + p->BlastMode = Options.mode; + p->fLastBlast = 1; + p->LastBlastModule = p->BlastModule; + p->LastBlastName = p->BlastName; + p->LastBlastRevision = p->Revision; + p->BlastBoundarySignature = Sn_BoundarySignature( p ); + clkNames = Abc_Clock(); + Sn_GiaSetNames( pAbc, p ); + p->BlastInterfaceSignature = Sn_GiaInterfaceSignature( Abc_FrameReadGia(pAbc) ); + clkNames = Abc_Clock() - clkNames; + if ( fVerbose ) + { + Abc_Print( 1, "Blasted SN design: PI bits = %llu PO bits = %llu flop bits = %llu " + "memories = %llu multipliers = %llu.\n", + (unsigned long long)Stats.primary_input_bits, (unsigned long long)Stats.primary_output_bits, + (unsigned long long)Stats.flop_bits, (unsigned long long)Stats.memory_count, + (unsigned long long)Stats.multiplier_count ); + Abc_Print( 1, "@blast phases: MiniAIG = %.2f s (%d ANDs) GIA import = %.2f s (%d ANDs) names = %.2f s.\n", + (double)clkBlast / CLOCKS_PER_SEC, nMiniAnds, (double)clkImport / CLOCKS_PER_SEC, + Gia_ManAndNum(Abc_FrameReadGia(pAbc)), (double)clkNames / CLOCKS_PER_SEC ); + } + return 0; + +usage: + Abc_Print( -2, "usage: @blast [-M module] [-ctdbrvh]\n" ); + Abc_Print( -2, "\t derives a flat AIG directly from the hierarchical SN design\n" ); + Abc_Print( -2, "\t-M name : select the module replaced by a later @put [default = current top]\n" ); + Abc_Print( -2, "\t-c : use combinational AIG mode\n" ); + Abc_Print( -2, "\t-t : emit the sequential transition relation as a combinational AIG\n" ); + Abc_Print( -2, "\t-b : toggle Booth multiplier blasting [default = Baugh-Wooley]\n" ); + Abc_Print( -2, "\t-d : toggle delay-oriented comparator blasting [default = enabled]\n" ); + Abc_Print( -2, "\t-r : toggle ripple-carry adders [default = Brent-Kung]\n" ); + Abc_Print( -2, "\t-v : print bit-blasting statistics\n" ); + Abc_Print( -2, "\t-h : print the command usage\n" ); + return 1; +} + +static uint32_t Sn_GateIdResolver( void * pContext, const char * pGateName ) +{ + Mio_Library_t * pLibrary = (Mio_Library_t *)pContext; + Mio_Gate_t * pGate = Mio_LibraryReadGateByName( pLibrary, (char *)pGateName, NULL ); + int GateId; + if ( pGate == NULL ) + return SN_INVALID_ID; + GateId = Mio_GateReadCell( pGate ); + return GateId >= 0 ? (uint32_t)GateId : SN_INVALID_ID; +} + +static int Sn_BoundaryHasGenericMemories( const sn_blast_boundary_t * pBoundary ) +{ + size_t i; + for ( i = 0; i < pBoundary->cis.size; i++ ) + { + sn_blast_boundary_kind_t Kind = sn_vec_at(sn_blast_boundary_bit_t, &pBoundary->cis, i).kind; + if ( Kind == SN_BLAST_BOUNDARY_MEMORY_OUTPUT ) + return 1; + } + for ( i = 0; i < pBoundary->cos.size; i++ ) + { + sn_blast_boundary_kind_t Kind = sn_vec_at(sn_blast_boundary_bit_t, &pBoundary->cos, i).kind; + if ( Kind == SN_BLAST_BOUNDARY_MEMORY_INPUT ) + return 1; + } + return 0; +} + +static int Sn_CommandPut( Abc_Frame_t * pAbc, int argc, char ** argv ) +{ + extern Abc_Ntk_t * Abc_NtkFromCellMappedGia( Gia_Man_t * pGia, int fUseBuffs ); + extern Vec_Int_t * Abc_NtkWriteMiniMapping( Abc_Ntk_t * pNtk ); + Sn_Man_t * p; + Gia_Man_t * pGia; + sn_module_id_t Top; + const char * pPutStatus; + int c, fVerbose = 0; + Extra_UtilGetoptReset(); + while ( (c = Extra_UtilGetopt(argc, argv, "vh")) != EOF ) + { + switch ( c ) + { + case 'v': + fVerbose ^= 1; + break; + case 'h': + default: + goto usage; + } + } + if ( argc != globalUtilOptind ) + goto usage; + if ( !Sn_CommandCheckDesign(pAbc) ) + return 1; + p = Sn_AbcGetMan( pAbc ); + pGia = Abc_FrameReadGia( pAbc ); + pPutStatus = Sn_ManPutStatus( p, pGia ); + if ( strcmp(pPutStatus, "compatible") != 0 ) + { + Abc_Print( -1, "Cannot @put: %s.\n", pPutStatus ); + return 1; + } + + if ( Gia_ManHasCellMapping(pGia) ) + { + Mio_Library_t * pLibrary = (Mio_Library_t *)Abc_FrameReadLibGen(); + Abc_Ntk_t * pNtk; + Vec_Int_t * vMapping; + if ( pLibrary == NULL ) + { + Abc_Print( -1, "The cell-mapped GIA has no current genlib library.\n" ); + return 1; + } + pNtk = Abc_NtkFromCellMappedGia( pGia, 0 ); + vMapping = Abc_NtkWriteMiniMapping( pNtk ); + Top = sn_design_add_gate_module( p->pDesign, p->BlastModule, Vec_IntArray(vMapping), &p->Boundary, + Sn_GateIdResolver, pLibrary, "__sn_gate_mapped" ); + if ( Top == SN_INVALID_ID ) + { + Abc_Print( -1, "Cannot @put: the current genlib does not contain every gate used by the mapped GIA.\n" ); + Vec_IntFree( vMapping ); + Abc_NtkDelete( pNtk ); + return 1; + } + if ( fVerbose ) + Abc_Print( 1, "Inserted cell-mapped logic: gates = %d.\n", Vec_IntEntry(vMapping, 2) ); + Vec_IntFree( vMapping ); + Abc_NtkDelete( pNtk ); + } + else if ( Gia_ManHasMapping(pGia) ) + { + Mini_Lut_t * pLut = (Mini_Lut_t *)Abc_FrameGiaOutputMiniLut( pAbc ); + sn_lut_stats_t Stats; + if ( pLut == NULL ) + { + Abc_Print( -1, "Cannot extract the mapped MiniLUT network.\n" ); + return 1; + } + Stats = sn_lut_analyze( pLut, &p->Boundary ); + Top = sn_design_add_lut_module( p->pDesign, p->BlastModule, pLut, &p->Boundary, "__sn_lut_mapped" ); + Mini_LutStop( pLut ); + if ( fVerbose ) + Abc_Print( 1, "Inserted LUT-mapped logic: LUTs = %u LUT size = %u levels = %u.\n", + Stats.lut_count, Stats.lut_size, Stats.lut_levels ); + } + else + { + Mini_Aig_t * pAig = (Mini_Aig_t *)Abc_FrameGiaOutputMiniAig( pAbc ); + assert( pAig != NULL ); + Top = sn_design_add_aig_module( p->pDesign, p->BlastModule, pAig, &p->Boundary, "__sn_aig_inserted" ); + if ( fVerbose ) + Abc_Print( 1, "Inserted unmapped logic: ANDs = %d.\n", Mini_AigAndNum(pAig) ); + Mini_AigStop( pAig ); + } + Sn_ManReplaceBlastedModule( p, Top ); + Sn_ManAdvanceRevision( p ); + assert( sn_design_is_topo(p->pDesign) ); + return 0; + +usage: + Abc_Print( -2, "usage: @put [-vh]\n" ); + Abc_Print( -2, "\t inserts the current &-space GIA into the SN design after @blast\n" ); + Abc_Print( -2, "\t-v : print reconstruction statistics\n" ); + Abc_Print( -2, "\t-h : print the command usage\n" ); + return 1; +} + +static char * Sn_SourceCommand( const char * pFileName ) +{ + Vec_Str_t * vCommand; + FILE * pFile = fopen( pFileName, "r" ); + if ( pFile == NULL ) + return NULL; + if ( fclose(pFile) != 0 ) + return NULL; + vCommand = Vec_StrAlloc( (int)strlen(pFileName) + 16 ); + Vec_StrPrintStr( vCommand, "source -s " ); + Vec_StrPrintStr( vCommand, pFileName ); + Vec_StrPush( vCommand, '\0' ); + return Vec_StrReleaseArray( vCommand ); +} + +typedef struct Sn_MapLutContext_t_ +{ + Abc_Frame_t * pAbc; + const char * pScript; + const char * pExecutable; + const char * pExtractPrefix; + unsigned nProcesses; + int fVerbose; +} Sn_MapLutContext_t; + +static int Sn_MapLutExtractName( char * pFileName, size_t nFileName, const char * pPrefix, + unsigned Module, const char * pName, const char * pSuffix ) +{ + size_t Pos, NameLength, SuffixLength; + int Written = snprintf( pFileName, nFileName, "%s_%04u_", pPrefix, Module ); + if ( Written < 0 || (size_t)Written >= nFileName ) + return 0; + Pos = (size_t)Written; + NameLength = strlen( pName ); + SuffixLength = strlen( pSuffix ); + if ( NameLength > nFileName - Pos - 1 || SuffixLength > nFileName - Pos - NameLength - 1 ) + return 0; + while ( *pName ) + { + unsigned char Char = (unsigned char)*pName++; + pFileName[Pos++] = (char)(isalnum(Char) || Char == '_' || Char == '-' ? Char : '_'); + } + memcpy( pFileName + Pos, pSuffix, SuffixLength + 1 ); + return 1; +} + +static int Sn_MapLutExecutable( char * pBuffer, size_t nBuffer ) +{ +#if defined(_MSC_VER) || defined(__MINGW32__) + DWORD Length = GetModuleFileNameA( NULL, pBuffer, (DWORD)nBuffer ); + return Length > 0 && Length < nBuffer; +#else + ssize_t Length = readlink( "/proc/self/exe", pBuffer, nBuffer - 1 ); + if ( Length <= 0 || (size_t)Length >= nBuffer ) + return 0; + pBuffer[Length] = '\0'; + return 1; +#endif +} + +static int Sn_MapLutRunProcess( const char * pExecutable, const char * pCommand ) +{ +#if defined(_MSC_VER) || defined(__MINGW32__) + const char * pArgs[] = {pExecutable, "-q", pCommand, NULL}; + return (int)_spawnv( _P_WAIT, pExecutable, pArgs ); +#else + pid_t Child = fork(); + int Status; + if ( Child < 0 ) + return -1; + if ( Child == 0 ) + { + int Null = open( "/dev/null", O_WRONLY ); + if ( Null >= 0 ) + { + dup2( Null, STDOUT_FILENO ); + dup2( Null, STDERR_FILENO ); + close( Null ); + } + execl( pExecutable, pExecutable, "-q", pCommand, (char *)NULL ); + _exit( 127 ); + } + if ( waitpid(Child, &Status, 0) != Child ) + return -1; + return WIFEXITED(Status) ? WEXITSTATUS(Status) : -1; +#endif +} + +// ABC-specific callback for the reusable snMapLut.h harness. The harness owns the input MiniAIG and returned MiniLUT. +static Mini_Lut_t * Sn_MapLutPartition( void * pArg, sn_module_id_t Module, const char * pName, + Mini_Aig_t * pAig, const sn_blast_boundary_t * pBoundary ) +{ + Sn_MapLutContext_t * p = (Sn_MapLutContext_t *)pArg; + Gia_Man_t * pGia; + Mini_Lut_t * pLut; + sn_lut_stats_t Stats; + abctime clk = Abc_Clock(); + int nInputs = Mini_AigPiNum( pAig ); + int nOutputs = Mini_AigPoNum( pAig ); + int nAnds = Mini_AigAndNum( pAig ); + if ( p->pExtractPrefix ) + { + char AigFile[1024], InfoFile[1024]; + FILE * pFile; + if ( !Sn_MapLutExtractName(AigFile, sizeof(AigFile), p->pExtractPrefix, Module, pName, ".aig") || + !Sn_MapLutExtractName(InfoFile, sizeof(InfoFile), p->pExtractPrefix, Module, pName, ".txt") ) + { + Abc_Print( -1, "Cannot extract module \"%s\": the -E output name is too long.\n", pName ); + return NULL; + } + pFile = fopen( AigFile, "wb" ); + if ( pFile == NULL ) + return NULL; + fclose( pFile ); + Mini_AigerWrite( AigFile, pAig, 0 ); + pFile = fopen( InfoFile, "w" ); + if ( pFile == NULL ) + return NULL; + fprintf( pFile, "module_id\t%u\nmodule_name\t%s\ninputs\t%d\noutputs\t%d\nands\t%d\n", + Module, pName, nInputs, nOutputs, nAnds ); + fprintf( pFile, "boundary_inputs\t%zu\nboundary_outputs\t%zu\n", + pBoundary->cis.size, pBoundary->cos.size ); + fclose( pFile ); + if ( p->fVerbose ) + Abc_Print( 1, "@map_lut: extracted %-24s %7d ANDs %d inputs %d outputs time = %.2f s.\n", + pName, nAnds, nInputs, nOutputs, (double)(Abc_Clock() - clk) / CLOCKS_PER_SEC ); + return Mini_LutStart( 2 ); + } + if ( p->nProcesses > 1 ) + { + char Prefix[512], Command[4096]; + char * pAigFile = NULL, * pLutFile = NULL; + int AigFd = -1, LutFd = -1; + if ( !Sn_TempPrefix(Prefix, sizeof(Prefix), "sn_map_lut_") || + (AigFd = tmpFile(Prefix, ".aig", &pAigFile)) < 0 || + (LutFd = tmpFile(Prefix, ".lut", &pLutFile)) < 0 ) + { +#if defined(_MSC_VER) || defined(__MINGW32__) + if ( AigFd >= 0 ) + _close( AigFd ); +#else + if ( AigFd >= 0 ) + close( AigFd ); +#endif + if ( pAigFile ) + { + remove( pAigFile ); + ABC_FREE( pAigFile ); + } + return NULL; + } +#if defined(_MSC_VER) || defined(__MINGW32__) + _close( AigFd ); + _close( LutFd ); +#else + close( AigFd ); + close( LutFd ); +#endif + Mini_AigerWrite( pAigFile, pAig, 0 ); + if ( snprintf(Command, sizeof(Command), "&read \"%s\"; %s; &write -l \"%s\"", pAigFile, p->pScript, + pLutFile) >= + (int)sizeof(Command) || Sn_MapLutRunProcess(p->pExecutable, Command) != 0 ) + { + remove( pAigFile ); + remove( pLutFile ); + ABC_FREE( pAigFile ); + ABC_FREE( pLutFile ); + return NULL; + } + pLut = sn_lut_load( pLutFile ); + remove( pAigFile ); + remove( pLutFile ); + ABC_FREE( pAigFile ); + ABC_FREE( pLutFile ); + if ( pLut == NULL || !sn_lut_interface_matches(pLut, pBoundary) ) + { + if ( pLut ) + Mini_LutStop( pLut ); + return NULL; + } + Stats = sn_lut_analyze( pLut, pBoundary ); + if ( p->fVerbose ) + Abc_Print( 1, "@map_lut: %-24s %7d ANDs -> %7u LUTs level = %u time = %.2f s.\n", + pName, nAnds, Stats.lut_count, Stats.lut_levels, + (double)(Abc_Clock() - clk) / CLOCKS_PER_SEC ); + return pLut; + } + Abc_FrameGiaInputMiniAig( p->pAbc, pAig ); + if ( Cmd_CommandExecute(p->pAbc, p->pScript) ) + { + Abc_Print( -1, "ABC script failed while mapping module \"%s\".\n", pName ); + return NULL; + } + pGia = Abc_FrameReadGia( p->pAbc ); + if ( pGia == NULL || Gia_ManCiNum(pGia) != nInputs || Gia_ManCoNum(pGia) != nOutputs ) + { + Abc_Print( -1, "ABC script changed the interface of module \"%s\" (%d/%d inputs, %d/%d outputs).\n", + pName, pGia ? Gia_ManCiNum(pGia) : -1, nInputs, pGia ? Gia_ManCoNum(pGia) : -1, nOutputs ); + return NULL; + } + if ( !Gia_ManHasMapping(pGia) ) + { + Abc_Print( -1, "ABC script did not leave a LUT-mapped GIA while mapping module \"%s\".\n", pName ); + return NULL; + } + pLut = (Mini_Lut_t *)Abc_FrameGiaOutputMiniLut( p->pAbc ); + if ( pLut == NULL ) + { + Abc_Print( -1, "Cannot extract the LUT-mapped network for module \"%s\".\n", pName ); + return NULL; + } + Stats = sn_lut_analyze( pLut, pBoundary ); + if ( p->fVerbose ) + Abc_Print( 1, "@map_lut: %-24s %7d ANDs -> %7u LUTs level = %u time = %.2f s.\n", + pName, nAnds, Stats.lut_count, Stats.lut_levels, (double)(Abc_Clock() - clk) / CLOCKS_PER_SEC ); + return pLut; +} + +// Like Yosys's ABC integration, @map_lut uses natural module boundaries rather than graph partitioning. Each reachable +// non-primitive module is combinationally extracted, processed independently by the requested ABC script, and inserted +// back at the same module ID. The command works on a duplicate and commits it only after every partition succeeds. +static int Sn_CommandMapLut( Abc_Frame_t * pAbc, int argc, char ** argv ) +{ + static const char * pDefaultScript = "&resyn3; &if -m -K 6"; + Sn_Man_t * p, * pWork; + const char * pScriptArg = NULL; + const char * pScriptFile = NULL; + const char * pModuleName = NULL; + const char * pExtractPrefix = NULL; + char * pFileScript = NULL; + const char * pScript; + char Executable[1024]; + sn_module_id_t Root; + sn_map_lut_stats_t Stats; + Sn_MapLutContext_t Context; + abctime clk; + int c, fVerbose = 0, nProcesses = 1; + Extra_UtilGetoptReset(); + while ( (c = Extra_UtilGetopt(argc, argv, "MSFPEvh")) != EOF ) + { + switch ( c ) + { + case 'M': + if ( globalUtilOptind >= argc ) + goto usage; + pModuleName = argv[globalUtilOptind++]; + break; + case 'S': + if ( globalUtilOptind >= argc ) + goto usage; + pScriptArg = argv[globalUtilOptind++]; + break; + case 'F': + if ( globalUtilOptind >= argc ) + goto usage; + pScriptFile = argv[globalUtilOptind++]; + break; + case 'P': + if ( globalUtilOptind >= argc ) + goto usage; + nProcesses = atoi( argv[globalUtilOptind++] ); + if ( nProcesses < 1 || nProcesses > 100 ) + goto usage; + break; + case 'E': + if ( globalUtilOptind >= argc ) + goto usage; + pExtractPrefix = argv[globalUtilOptind++]; + break; + case 'v': + fVerbose ^= 1; + break; + case 'h': + default: + goto usage; + } + } + if ( argc != globalUtilOptind || (pScriptArg && pScriptFile) || + (pExtractPrefix && (pScriptArg || pScriptFile || nProcesses != 1)) ) + goto usage; + if ( nProcesses > 1 && !sn_pth_parallel_available() ) + { + Abc_Print( -1, "Cannot use @map_lut -P %d: parallel SN mapping is unavailable in this build; use -P 1.\n", + nProcesses ); + return 1; + } + if ( !Sn_CommandCheckDesign(pAbc) ) + return 1; + p = Sn_AbcGetMan( pAbc ); + Root = pModuleName ? sn_design_find_module( p->pDesign, pModuleName ) : p->Top; + if ( Root == SN_INVALID_ID ) + { + Abc_Print( -1, "Cannot find module \"%s\" in the current SN design.\n", pModuleName ); + return 1; + } + if ( Sn_CommandRejectLatches(p->pDesign, Root, "@map_lut") ) + return 1; + if ( pScriptFile ) + { + pFileScript = Sn_SourceCommand( pScriptFile ); + if ( pFileScript == NULL ) + { + Abc_Print( -1, "Cannot read ABC script file \"%s\".\n", pScriptFile ); + return 1; + } + } + pScript = pScriptArg ? pScriptArg : pFileScript ? pFileScript : pDefaultScript; + pWork = Sn_ManDup( p ); + if ( pWork == NULL ) + { + Abc_Print( -1, "Cannot duplicate the current SN design.\n" ); + ABC_FREE( pFileScript ); + return 1; + } + Context.pAbc = pAbc; + Context.pScript = pScript; + Context.pExecutable = NULL; + Context.pExtractPrefix = pExtractPrefix; + Context.nProcesses = (unsigned)nProcesses; + Context.fVerbose = fVerbose; + if ( nProcesses > 1 ) + { + if ( !Sn_MapLutExecutable(Executable, sizeof(Executable)) ) + { + Abc_Print( -1, "Cannot determine the current ABC executable path.\n" ); + ABC_FREE( pFileScript ); + Sn_ManFree( pWork ); + return 1; + } + Context.pExecutable = Executable; + } + clk = Abc_Clock(); + if ( !sn_design_map_lut_hierarchy(pWork->pDesign, Root, Sn_MapLutPartition, &Context, + (unsigned)nProcesses, pExtractPrefix != NULL, &Stats) ) + { + if ( Stats.failed_module != SN_INVALID_ID ) + { + const sn_module_t * pFailed = sn_design_get_module_const( pWork->pDesign, Stats.failed_module ); + Abc_Print( -1, "LUT mapping failed for module \"%s\".\n", + sn_name_get(&pWork->pDesign->names, pFailed->name) ); + } + ABC_FREE( pFileScript ); + Sn_ManFree( pWork ); + return 1; + } + ABC_FREE( pFileScript ); + if ( pExtractPrefix ) + { + Sn_ManFree( pWork ); + Abc_Print( 1, "Extracted %u module partitions (%llu input ANDs); skipped %u trivial, %u primitive, and %u " + "generic-memory modules. Time = %.2f s.\n", Stats.mapped_modules, + (unsigned long long)Stats.input_ands, Stats.trivial_modules, Stats.primitive_modules, + Stats.generic_memory_modules, (double)(Abc_Clock() - clk) / CLOCKS_PER_SEC ); + return 0; + } + Sn_ManAdvanceRevision( pWork ); + Sn_AbcUpdateMan( pAbc, pWork ); + Abc_Print( 1, "Mapped %u module partitions into %llu LUTs; skipped %u trivial, %u primitive, and %u generic-memory " + "modules. " + "Time = %.2f s.\n", Stats.mapped_modules, (unsigned long long)Stats.output_luts, + Stats.trivial_modules, Stats.primitive_modules, Stats.generic_memory_modules, + (double)(Abc_Clock() - clk) / CLOCKS_PER_SEC ); + return 0; + +usage: + Abc_Print( -2, "usage: @map_lut [-M module] [-S \"commands\" | -F script] [-P num] [-E prefix] [-vh]\n" ); + Abc_Print( -2, "\t maps each natural hierarchy partition independently and preserves the hierarchy\n" ); + Abc_Print( -2, "\t-M name : map modules reachable from this root [default = current top]\n" ); + Abc_Print( -2, "\t-S cmds : ABC commands applied to each partition [default = &resyn3; &if -m -K 6]\n" ); + Abc_Print( -2, "\t-F file : read the per-partition ABC commands from a file\n" ); + Abc_Print( -2, "\t-P num : use num processes; P>1 requires pthreads and a non-Windows build [default = 1]\n" ); + Abc_Print( -2, "\t P=1 leaves the last processed partition in &-space\n" ); + Abc_Print( -2, "\t-E pref : extract partition AIGs as pref__.aig and stop before synthesis\n" ); + Abc_Print( -2, "\t-v : print per-module mapping statistics\n" ); + Abc_Print( -2, "\t-h : print the command usage\n" ); + return 1; +} + +static int Sn_CommandWrite( Abc_Frame_t * pAbc, int argc, char ** argv ) +{ + Sn_Man_t * p; + const sn_module_t * pTop; + char * pFileName; + FILE * pFile; + int c, Status = 0; + Extra_UtilGetoptReset(); + while ( (c = Extra_UtilGetopt(argc, argv, "h")) != EOF ) + goto usage; + if ( argc - globalUtilOptind != 1 ) + goto usage; + if ( !Sn_CommandCheckDesign(pAbc) ) + return 1; + p = Sn_AbcGetMan( pAbc ); + pTop = sn_design_get_module_const( p->pDesign, p->Top ); + pFileName = argv[globalUtilOptind]; + if ( Sn_FileHasSuffix(pFileName, ".sn") ) + { + pFile = fopen( pFileName, "wb" ); + if ( pFile == NULL ) + { + Abc_Print( -1, "Cannot open output file \"%s\".\n", pFileName ); + return 1; + } + Status = sn_design_write_binary( pFile, p->pDesign ) ? 0 : 1; + } + else if ( Sn_FileHasSuffix(pFileName, ".v") || Sn_FileHasSuffix(pFileName, ".sv") ) + { + pFile = fopen( pFileName, "w" ); + if ( pFile == NULL ) + { + Abc_Print( -1, "Cannot open output file \"%s\".\n", pFileName ); + return 1; + } + sn_design_write_module_verilog_with_deps( + pFile, p->pDesign, p->Top, sn_name_get(&p->pDesign->names, p->Name) ); + Status = ferror( pFile ) != 0; + } + else + { + Abc_Print( -1, "Output file \"%s\" should have extension .sn, .v, or .sv.\n", pFileName ); + return 1; + } + if ( fclose(pFile) != 0 ) + Status = 1; + if ( Status ) + { + remove( pFileName ); + Abc_Print( -1, "Cannot finish writing output file \"%s\".\n", pFileName ); + return 1; + } + return 0; + +usage: + Abc_Print( -2, "usage: @write [-h] \n" ); + Abc_Print( -2, "\t writes the current SN design according to the file extension\n" ); + Abc_Print( -2, "\t-h : print the command usage\n" ); + return 1; +} + +static int Sn_CommandPs( Abc_Frame_t * pAbc, int argc, char ** argv ) +{ + Sn_Man_t * p; + const sn_module_t * pTop; + sn_design_mem_usage_t Mem; + size_t nObjects = 0; + size_t i; + int c, fDistrib = 0, fVerbose = 0, fMem, fLut, fGate; + char UsedMemory[32], AllocatedMemory[32]; + Extra_UtilGetoptReset(); + while ( (c = Extra_UtilGetopt(argc, argv, "dvh")) != EOF ) + { + switch ( c ) + { + case 'd': + fDistrib ^= 1; + break; + case 'v': + fVerbose ^= 1; + break; + case 'h': + default: + goto usage; + } + } + if ( argc != globalUtilOptind ) + goto usage; + if ( !Sn_CommandCheckDesign(pAbc) ) + return 1; + p = Sn_AbcGetMan( pAbc ); + pTop = sn_design_get_module_const( p->pDesign, p->Top ); + for ( i = 0; i < p->pDesign->modules.size; i++ ) + nObjects += sn_design_get_module_const( p->pDesign, (sn_module_id_t)i )->obj_types.size; + sn_design_get_mem_usage( p->pDesign, &Mem ); + fMem = Sn_DesignHasType( p->pDesign, SN_MEM_OUT ); + fLut = Sn_DesignHasType( p->pDesign, SN_LUT ); + fGate = Sn_DesignHasType( p->pDesign, SN_GATE ); + Sn_FormatMemory( Mem.total.used_bytes, UsedMemory, sizeof(UsedMemory) ); + Sn_FormatMemory( Mem.total.allocated_bytes, AllocatedMemory, sizeof(AllocatedMemory) ); + fprintf( pAbc->Out, "SN design: top = %s modules = %zu objects = %zu memory = %s/%s " + "(used/allocated)\n", + sn_name_get(&p->pDesign->names, pTop->name), p->pDesign->modules.size, nObjects, + UsedMemory, AllocatedMemory ); + Sn_ModulePrintStats( pAbc->Out, pTop, fMem, fLut, fGate ); + if ( fVerbose ) + { + fprintf( pAbc->Out, "Hierarchy:\n" ); + sn_design_print_hierarchy( pAbc->Out, p->pDesign, p->Top ); + fprintf( pAbc->Out, "Modules:\n" ); + for ( i = 0; i < p->pDesign->modules.size; i++ ) + Sn_ModulePrintStats( pAbc->Out, sn_design_get_module_const(p->pDesign, (sn_module_id_t)i), + fMem, fLut, fGate ); + } + if ( fDistrib ) + Sn_DesignPrintDistrib( pAbc->Out, p->pDesign, p->Top ); + return 0; + +usage: + Abc_Print( -2, "usage: @ps [-dvh]\n" ); + Abc_Print( -2, "\t prints statistics for the current SN design\n" ); + Abc_Print( -2, "\t-d : print object-type and width distribution for the elaborated hierarchy\n" ); + Abc_Print( -2, "\t-v : print hierarchy and per-module statistics\n" ); + Abc_Print( -2, "\t-h : print the command usage\n" ); + return 1; +} + +static const char * Sn_BlastModeName( int Mode ) +{ + if ( Mode == SN_BLAST_COMB ) + return "combinational"; + if ( Mode == SN_BLAST_TRANSITION ) + return "transition"; + return "sequential"; +} + +static int Sn_ManHasUserHierarchy( const Sn_Man_t * p ) +{ + const sn_module_t * pTop = sn_design_get_module_const( p->pDesign, p->Top ); + size_t i; + for ( i = 0; i < pTop->type_objects[SN_INST].size; i++ ) + { + sn_obj_id_t Inst = sn_vec_at( sn_obj_id_t, &pTop->type_objects[SN_INST], i ); + const sn_module_t * pChild = sn_design_get_module_const( p->pDesign, sn_inst_module_id(pTop, Inst) ); + if ( !sn_module_is_technology_primitive(pChild) ) + return 1; + } + return 0; +} + +static const char * Sn_ManPutStatus( const Sn_Man_t * p, Gia_Man_t * pGia ) +{ + const sn_module_t * pModule; + if ( !p->fLastBlast ) + return "unavailable (run @blast -c)"; + if ( p->LastBlastRevision != p->Revision ) + return "incompatible (SN revision changed)"; + if ( p->BlastMode != SN_BLAST_COMB ) + return "unsupported (last extraction was not combinational)"; + if ( !p->fBlasted ) + return "incompatible (saved boundary is unavailable)"; + if ( p->BlastModule != p->LastBlastModule || p->BlastName != p->LastBlastName || + p->BlastModule >= p->pDesign->modules.size ) + return "incompatible (extracted module identity changed)"; + pModule = sn_design_get_module_const( p->pDesign, p->BlastModule ); + if ( pModule->name != p->BlastName ) + return "incompatible (extracted module name changed)"; + if ( Sn_BoundarySignature(p) != p->BlastBoundarySignature ) + return "incompatible (saved boundary changed)"; + if ( pGia == NULL ) + return "incompatible (&-space GIA is unavailable)"; + if ( Gia_ManRegNum(pGia) != 0 ) + return "incompatible (GIA contains registers)"; + if ( (size_t)Gia_ManCiNum(pGia) != p->Boundary.cis.size || + (size_t)Gia_ManCoNum(pGia) != p->Boundary.cos.size ) + return "incompatible (GIA interface changed)"; + if ( Sn_GiaInterfaceSignature(pGia) != p->BlastInterfaceSignature ) + return "incompatible (GIA interface reordered or renamed)"; + if ( Sn_BoundaryHasGenericMemories(&p->Boundary) ) + return "unsupported (generic memory boundary)"; + return "compatible"; +} + +static int Sn_CommandStatus( Abc_Frame_t * pAbc, int argc, char ** argv ) +{ + Sn_Man_t * p; + Gia_Man_t * pGia; + const char * pTopName; + int c; + Extra_UtilGetoptReset(); + while ( (c = Extra_UtilGetopt(argc, argv, "h")) != EOF ) + goto usage; + if ( argc != globalUtilOptind ) + goto usage; + if ( !Sn_CommandCheckDesign(pAbc) ) + return 1; + p = Sn_AbcGetMan( pAbc ); + pGia = Abc_FrameReadGia( pAbc ); + pTopName = sn_name_get( &p->pDesign->names, sn_design_get_module_const(p->pDesign, p->Top)->name ); + fprintf( pAbc->Out, "SN design : %s\n", sn_name_get(&p->pDesign->names, p->Name) ); + fprintf( pAbc->Out, "SN revision : %llu\n", p->Revision ); + fprintf( pAbc->Out, "Top module : %s\n", pTopName ); + fprintf( pAbc->Out, "Technology : %s\n", p->Technology == SN_COMMAND_TECH_XILINX_ULTRASCALE ? + "xilinx-ultrascale+" : "generic" ); + fprintf( pAbc->Out, "Hierarchy : %s\n", Sn_ManHasUserHierarchy(p) ? "hierarchical" : "flat" ); + if ( p->fLastBlast ) + { + fprintf( pAbc->Out, "Last extraction : %s, module %s, revision %llu\n", + Sn_BlastModeName(p->BlastMode), sn_name_get(&p->pDesign->names, p->LastBlastName), + p->LastBlastRevision ); + fprintf( pAbc->Out, "Boundary hash : 0x%016llx\n", + (unsigned long long)p->BlastBoundarySignature ); + } + else + fprintf( pAbc->Out, "Last extraction : none\n" ); + if ( pGia ) + fprintf( pAbc->Out, "&-space GIA : %d inputs, %d outputs, %d flops, %d ANDs\n", + Gia_ManPiNum(pGia), Gia_ManPoNum(pGia), Gia_ManRegNum(pGia), Gia_ManAndNum(pGia) ); + else + fprintf( pAbc->Out, "&-space GIA : none\n" ); + fprintf( pAbc->Out, "@put status : %s\n", Sn_ManPutStatus(p, pGia) ); + return 0; + +usage: + Abc_Print( -2, "usage: @status [-h]\n" ); + Abc_Print( -2, "\t prints SN, saved @blast boundary, and current &-space GIA state\n" ); + Abc_Print( -2, "\t-h : print the command usage\n" ); + return 1; +} + +ABC_NAMESPACE_IMPL_END diff --git a/src/base/sn/snMapAdd.h b/src/base/sn/snMapAdd.h new file mode 100644 index 000000000..26e7f831d --- /dev/null +++ b/src/base/sn/snMapAdd.h @@ -0,0 +1,167 @@ +/**CFile**************************************************************** + + FileName [snMapAdd.h] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [New word-level design interface.] + + Synopsis [Mapping word-level adders and subtractors into FPGA carry primitives.] + + Author [Alan Mishchenko] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: snMapAdd.h,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#ifndef SN_MAP_ADD_H +#define SN_MAP_ADD_H + +// Maps word-level addition and subtraction into preserved FPGA carry-chain +// primitive insts. The surrounding propagate/invert logic remains ordinary SN +// logic and can subsequently be mapped into LUTs. + +#include "sn.h" +#include "snTech.h" + +ABC_NAMESPACE_HEADER_START + +typedef struct sn_add_map_options_t +{ + uint32_t min_width; + bool map_add; + bool map_sub; + bool preserve_names; +} sn_add_map_options_t; + +static inline sn_add_map_options_t sn_add_map_default_options(void) +{ + sn_add_map_options_t options = {0, true, true, true}; + return options; +} + +static inline bool sn_add_tech_supports(const sn_carry_tech_t* tech, const sn_add_map_options_t* options, + sn_obj_type_t type, uint32_t width) +{ + assert(tech && options); + uint32_t min_width = options->min_width ? options->min_width : tech->min_op_width; + return width >= min_width && ((type == SN_ADD && options->map_add) || (type == SN_SUB && options->map_sub)); +} + +static inline sn_obj_id_t sn_add_slice_bit(sn_module_t* module, sn_obj_id_t value, uint32_t bit) +{ + assert(bit < sn_obj_width(module, value)); + return sn_module_add_slice(module, value, (int32_t)bit, (int32_t)bit, NULL); +} + +// The behavioral body is identical to the Xilinx CARRY4 simulation model. It +// permits standalone SN simulation and CEC while the __sn_ prefix marks the +// module as a hard primitive that hierarchy collapse and LUT mapping preserve. +static inline sn_module_id_t sn_add_carry_primitive_module(sn_design_t* design, const sn_carry_tech_t* tech) +{ + assert(design && tech && tech->width == 4); + char name[64]; + int length = snprintf(name, sizeof(name), "__sn_%s", tech->name); + assert(length >= 0 && (size_t)length < sizeof(name)); + sn_module_id_t existing = sn_design_find_module(design, name); + if (existing != SN_INVALID_ID) + return existing; + + sn_module_id_t id = sn_design_add_module(design, name); + sn_module_t* module = sn_design_get_module(design, id); + sn_obj_id_t ci = sn_module_add_pi(module, 1, false, "CI"); + sn_obj_id_t cyinit = sn_module_add_pi(module, 1, false, "CYINIT"); + sn_obj_id_t di = sn_module_add_pi(module, 4, false, "DI"); + sn_obj_id_t s = sn_module_add_pi(module, 4, false, "S"); + sn_obj_id_t init_fanins[2] = {ci, cyinit}; + sn_obj_id_t carry = sn_module_add_operator(module, SN_BIT_OR, 1, false, 2, init_fanins, NULL); + sn_obj_id_t o_bits[4], co_bits[4]; + for (uint32_t bit = 0; bit < 4; bit++) + { + sn_obj_id_t s_bit = sn_add_slice_bit(module, s, bit); + sn_obj_id_t di_bit = sn_add_slice_bit(module, di, bit); + sn_obj_id_t xor_fanins[2] = {s_bit, carry}; + o_bits[bit] = sn_module_add_operator(module, SN_BIT_XOR, 1, false, 2, xor_fanins, NULL); + co_bits[bit] = sn_module_add_mux(module, s_bit, carry, di_bit, NULL); + carry = co_bits[bit]; + } + sn_obj_id_t o = sn_module_add_concat(module, 4, o_bits, NULL); + sn_obj_id_t co = sn_module_add_concat(module, 4, co_bits, NULL); + sn_module_add_po(module, 4, false, "O", o); + sn_module_add_po(module, 4, false, "CO", co); + assert(sn_module_is_topo(module)); + return id; +} + +static inline sn_obj_id_t sn_add_resize(sn_module_t* module, sn_obj_id_t value, uint32_t width, bool is_signed) +{ + if (sn_obj_width(module, value) == width && sn_obj_is_signed(module, value) == is_signed) + return value; + return sn_module_add_operator(module, SN_CAST, width, is_signed, 1, &value, NULL); +} + +static inline sn_obj_id_t sn_add_pad_chunk(sn_module_t* module, sn_obj_id_t value, uint32_t width) +{ + assert(width && width <= 4 && sn_obj_width(module, value) == width); + if (width == 4) + return value; + sn_obj_id_t zero = sn_module_add_named_obj(module, SN_CONST0, 4 - width, false, 0, NULL); + sn_obj_id_t fanins[2] = {value, zero}; + return sn_module_add_concat(module, 2, fanins, NULL); +} + +// Implements A+B or A-B exactly as Yosys's Xilinx $alu mapping: DI=A, +// S=A^B (or A^~B), and subtraction starts the carry chain at one. +static inline sn_obj_id_t sn_add_map_carry_chain(sn_module_t* module, const sn_carry_tech_t* tech, + sn_obj_type_t type, sn_obj_id_t a, sn_obj_id_t b, + uint32_t result_width, bool result_signed, const char* name) +{ + assert(module && tech && tech->width == 4 && (type == SN_ADD || type == SN_SUB)); + assert(a < module->obj_types.size && b < module->obj_types.size && result_width); + bool signed_operands = sn_obj_is_signed(module, a) && sn_obj_is_signed(module, b); + a = sn_add_resize(module, a, result_width, signed_operands); + b = sn_add_resize(module, b, result_width, signed_operands); + if (type == SN_SUB) + b = sn_module_add_operator(module, SN_BIT_NOT, result_width, signed_operands, 1, &b, NULL); + sn_obj_id_t xor_fanins[2] = {a, b}; + sn_obj_id_t propagate = + sn_module_add_operator(module, SN_BIT_XOR, result_width, false, 2, xor_fanins, NULL); + sn_obj_id_t zero = sn_module_add_named_obj(module, SN_CONST0, 1, false, 0, NULL); + sn_obj_id_t one = sn_module_add_named_obj(module, SN_CONST1, 1, false, 0, NULL); + sn_obj_id_t carry = zero; + sn_module_id_t primitive = sn_add_carry_primitive_module(module->design, tech); + uint32_t chunk_count = (result_width + 3) / 4; + sn_obj_id_t* chunks = (sn_obj_id_t*)malloc(sizeof(sn_obj_id_t) * chunk_count); + assert(chunks); + for (uint32_t chunk = 0; chunk < chunk_count; chunk++) + { + uint32_t offset = chunk * 4; + uint32_t width = result_width - offset < 4 ? result_width - offset : 4; + sn_obj_id_t di = sn_module_add_slice(module, a, (int32_t)(offset + width - 1), (int32_t)offset, NULL); + sn_obj_id_t s = + sn_module_add_slice(module, propagate, (int32_t)(offset + width - 1), (int32_t)offset, NULL); + di = sn_add_pad_chunk(module, di, width); + s = sn_add_pad_chunk(module, s, width); + sn_obj_id_t inputs[4] = {carry, chunk == 0 && type == SN_SUB ? one : zero, di, s}; + const char* output_names[2] = {NULL, NULL}; + sn_obj_id_t inst = sn_module_add_inst(module, primitive, 4, inputs, NULL, output_names); + chunks[chunk] = sn_inst_output(module, inst, 0); + sn_obj_id_t co = sn_inst_output(module, inst, 1); + carry = sn_add_slice_bit(module, co, 3); + } + sn_obj_id_t result = chunk_count == 1 ? chunks[0] : sn_module_add_concat(module, chunk_count, chunks, NULL); + free(chunks); + if (sn_obj_width(module, result) != result_width) + result = sn_module_add_slice(module, result, (int32_t)result_width - 1, 0, NULL); + if (sn_obj_is_signed(module, result) != result_signed) + result = sn_module_add_operator(module, SN_CAST, result_width, result_signed, 1, &result, name); + return result; +} + +ABC_NAMESPACE_HEADER_END + +#endif diff --git a/src/base/sn/snMapDsp.h b/src/base/sn/snMapDsp.h new file mode 100644 index 000000000..dd97d4bb1 --- /dev/null +++ b/src/base/sn/snMapDsp.h @@ -0,0 +1,85 @@ +/**CFile**************************************************************** + + FileName [snMapDsp.h] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [New word-level design interface.] + + Synopsis [Mapping word-level multipliers into FPGA DSP primitives.] + + Author [Alan Mishchenko] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: snMapDsp.h,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#ifndef SN_MAP_DSP_H +#define SN_MAP_DSP_H + +#include "sn.h" +#include "snTech.h" + +ABC_NAMESPACE_HEADER_START + +typedef struct sn_dsp_map_options_t +{ + bool allow_soft_fallback; + bool use_preadder; + bool use_postadder; + bool preserve_names; + bool balance_adders; + bool prune_unused_high_products; + uint32_t a_unsigned_chunk_width; + uint32_t b_unsigned_chunk_width; + uint32_t max_dsps_per_multiply; +} sn_dsp_map_options_t; + +static inline sn_dsp_map_options_t sn_dsp_map_default_options(void) +{ + sn_dsp_map_options_t options = {true, false, false, true, true, true, 0, 0, 0}; + return options; +} + +static inline bool sn_dsp_tech_supports_mul(const sn_dsp_tech_t* tech, uint32_t a_width, uint32_t b_width, + uint32_t result_width, bool a_signed, bool b_signed) +{ + assert(tech); + if (!a_width || !b_width || !result_width || a_width > tech->a_width || b_width > tech->b_width || + result_width > tech->p_width) + return false; + if (a_width < tech->min_a_width || b_width < tech->min_b_width || result_width < tech->min_p_width) + return false; + if (tech->signed_only && (!a_signed || !b_signed)) + return false; + return true; +} + +static inline sn_module_id_t sn_map_dsp_primitive_module(sn_design_t* design, const sn_dsp_tech_t* tech, + uint32_t a_width, uint32_t b_width, uint32_t y_width, + bool a_signed, bool b_signed) +{ + char name[128]; + int length = snprintf(name, sizeof(name), "__sn_%s_mul_%u_%u_%u_s%u%u", tech->name, a_width, b_width, + y_width, a_signed ? 1u : 0u, b_signed ? 1u : 0u); + assert(length >= 0 && (size_t)length < sizeof(name)); + sn_module_id_t existing = sn_design_find_module(design, name); + if (existing != SN_INVALID_ID) + return existing; + sn_module_id_t id = sn_design_add_module(design, name); + sn_module_t* module = sn_design_get_module(design, id); + sn_obj_id_t a = sn_module_add_pi(module, a_width, a_signed, "A"); + sn_obj_id_t b = sn_module_add_pi(module, b_width, b_signed, "B"); + sn_obj_id_t fanins[] = {a, b}; + sn_obj_id_t product = sn_module_add_operator(module, SN_MUL, y_width, a_signed || b_signed, 2, fanins, "P"); + sn_module_add_po(module, y_width, a_signed || b_signed, "Y", product); + return id; +} + +ABC_NAMESPACE_HEADER_END + +#endif diff --git a/src/base/sn/snMapLut.h b/src/base/sn/snMapLut.h new file mode 100644 index 000000000..e4330253d --- /dev/null +++ b/src/base/sn/snMapLut.h @@ -0,0 +1,270 @@ +/**CFile**************************************************************** + + FileName [snMapLut.h] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [New word-level design interface.] + + Synopsis [Partitioned synthesis and LUT mapping of hierarchical SN designs.] + + Author [Alan Mishchenko] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: snMapLut.h,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#ifndef SN_MAP_LUT_H +#define SN_MAP_LUT_H + +// Natural-hierarchy LUT-mapping harness. Each reachable user module is extracted as one combinational MiniAIG while +// child instances, registers, and mapped hard blocks remain boundary terminals. A caller-supplied callback maps this +// partition and returns a MiniLUT. The harness reconstructs the module at its stable ID, preserving parent references. + +#include "snBlast.h" +#include "snMiniLut.h" +#include "snPth.h" + +ABC_NAMESPACE_HEADER_START + +typedef Mini_Lut_t* (*sn_map_lut_partition_fn)(void* context, sn_module_id_t module, const char* name, + Mini_Aig_t* aig, const sn_blast_boundary_t* boundary); + +typedef struct sn_map_lut_stats_t +{ + uint32_t reachable_modules; + uint32_t mapped_modules; + uint32_t trivial_modules; + uint32_t primitive_modules; + uint32_t generic_memory_modules; + uint64_t input_ands; + uint64_t output_luts; + sn_module_id_t failed_module; +} sn_map_lut_stats_t; + +typedef struct sn_map_lut_job_t +{ + sn_module_id_t module; + sn_name_id_t name; + Mini_Aig_t* aig; + Mini_Lut_t* lut; + sn_blast_boundary_t boundary; +} sn_map_lut_job_t; + +typedef struct sn_map_lut_runner_t +{ + sn_design_t* design; + sn_map_lut_partition_fn function; + void* context; +} sn_map_lut_runner_t; + +static inline void sn_map_lut_run_job(void* argument, void* job_argument) +{ + sn_map_lut_runner_t* runner = (sn_map_lut_runner_t*)argument; + sn_map_lut_job_t* job = (sn_map_lut_job_t*)job_argument; + job->lut = runner->function(runner->context, job->module, + sn_name_get(&runner->design->names, job->name), job->aig, &job->boundary); +} + +static inline bool sn_map_lut_boundary_has_generic_memories(const sn_blast_boundary_t* boundary) +{ + for (size_t i = 0; i < boundary->cis.size; i++) + if (sn_vec_at(sn_blast_boundary_bit_t, &boundary->cis, i).kind == SN_BLAST_BOUNDARY_MEMORY_OUTPUT) + return true; + for (size_t i = 0; i < boundary->cos.size; i++) + if (sn_vec_at(sn_blast_boundary_bit_t, &boundary->cos, i).kind == SN_BLAST_BOUNDARY_MEMORY_INPUT) + return true; + return false; +} + +static inline void sn_design_replace_appended_module(sn_design_t* design, sn_module_id_t module, + sn_name_id_t name, sn_module_id_t temporary) +{ + sn_module_t* old_module; + sn_module_t* new_module; + bool interface_locked; + assert(design && module < design->modules.size); + old_module = sn_design_get_module(design, module); + assert(old_module->name == name); + assert(temporary + 1 == design->modules.size && temporary != module); + new_module = sn_design_get_module(design, temporary); + interface_locked = old_module->interface_locked; + sn_module_destroy(old_module); + free(old_module); + new_module->id = module; + new_module->name = name; + new_module->interface_locked = interface_locked; + sn_vec_at(sn_module_t*, &design->modules, module) = new_module; + design->modules.size--; +} + +// Maps all user modules reachable from root. The callback borrows aig and boundary for the duration of the call and +// returns a newly allocated MiniLUT owned by this harness. A NULL result aborts the pass. Modules containing generic +// memories are skipped; map their memories into primitive instances first if their surrounding logic should be mapped. +// The operation is in-place, so a transactional client should invoke it on a duplicate design and install that design +// only after this API succeeds. +static inline bool sn_design_map_lut_hierarchy(sn_design_t* design, sn_module_id_t root, + sn_map_lut_partition_fn map_partition, void* context, + unsigned processes, bool extract_only, + sn_map_lut_stats_t* returned_stats) +{ + sn_map_lut_stats_t stats = {0}; + size_t module_count; + bool* reachable; + sn_vec_t pending; + sn_vec_t jobs; + stats.failed_module = SN_INVALID_ID; + assert(design && root < design->modules.size && map_partition && processes >= 1); + module_count = design->modules.size; + // Module replacement invalidates optional duplication maps that may have been cached by earlier mapping passes. + for (sn_module_id_t module_id = 0; module_id < module_count; module_id++) + { + sn_module_t* module = sn_design_get_module(design, module_id); + sn_vec_destroy(&module->copy_ids); + sn_vec_init(&module->copy_ids); + module->copy_module = SN_INVALID_ID; + } + reachable = (bool*)calloc(module_count, sizeof(bool)); + assert(reachable); + sn_vec_init(&pending); + sn_vec_init(&jobs); + *sn_vec_push(sn_module_id_t, &pending) = root; + while (pending.size) + { + sn_module_id_t module_id = sn_vec_at(sn_module_id_t, &pending, --pending.size); + const sn_module_t* module; + if (reachable[module_id]) + continue; + reachable[module_id] = true; + stats.reachable_modules++; + module = sn_design_get_module_const(design, module_id); + for (size_t i = 0; i < module->inst_modules.size; i++) + *sn_vec_push(sn_module_id_t, &pending) = sn_vec_at(sn_module_id_t, &module->inst_modules, i); + } + for (sn_module_id_t module_id = 0; module_id < module_count; module_id++) + { + const sn_module_t* module; + sn_name_id_t name_id; + sn_blast_options_t options; + sn_blast_boundary_t boundary; + Mini_Aig_t* aig; + if (!reachable[module_id]) + continue; + module = sn_design_get_module_const(design, module_id); + if (sn_module_is_technology_primitive(module)) + { + stats.primitive_modules++; + continue; + } + name_id = module->name; + options = sn_blast_default_options(); + options.mode = SN_BLAST_COMB; + options.abstract_instances = true; + sn_blast_boundary_init(&boundary); + aig = sn_design_blast_hier_boundary_options(design, module_id, options, NULL, &boundary); + if (sn_map_lut_boundary_has_generic_memories(&boundary)) + { + stats.generic_memory_modules++; + Mini_AigStop(aig); + sn_blast_boundary_destroy(&boundary); + continue; + } + if (Mini_AigAndNum(aig) == 0) + { + stats.trivial_modules++; + Mini_AigStop(aig); + sn_blast_boundary_destroy(&boundary); + continue; + } + stats.input_ands += (uint64_t)Mini_AigAndNum(aig); + if (processes == 1) + { + Mini_Lut_t* lut = map_partition(context, module_id, sn_name_get(&design->names, name_id), aig, &boundary); + Mini_AigStop(aig); + if (!lut) + { + stats.failed_module = module_id; + sn_blast_boundary_destroy(&boundary); + sn_vec_destroy(&pending); + sn_vec_destroy(&jobs); + free(reachable); + if (returned_stats) + *returned_stats = stats; + return false; + } + if (extract_only) + { + Mini_LutStop(lut); + sn_blast_boundary_destroy(&boundary); + stats.mapped_modules++; + continue; + } + sn_lut_stats_t lut_stats = sn_lut_analyze(lut, &boundary); + sn_module_id_t temporary = sn_design_add_lut_module(design, module_id, lut, &boundary, + "__sn_lut_partition"); + Mini_LutStop(lut); + sn_blast_boundary_destroy(&boundary); + sn_design_replace_appended_module(design, module_id, name_id, temporary); + stats.mapped_modules++; + stats.output_luts += lut_stats.lut_count; + continue; + } + sn_map_lut_job_t* job = sn_vec_push(sn_map_lut_job_t, &jobs); + job->module = module_id; + job->name = name_id; + job->aig = aig; + job->lut = NULL; + job->boundary = boundary; + } + void** job_pointers = jobs.size ? (void**)malloc(sizeof(void*) * jobs.size) : NULL; + assert(job_pointers || jobs.size == 0); + for (size_t i = 0; i < jobs.size; i++) + job_pointers[i] = &sn_vec_at(sn_map_lut_job_t, &jobs, i); + sn_map_lut_runner_t runner = {design, map_partition, context}; + sn_pth_process(job_pointers, jobs.size, processes, sn_map_lut_run_job, &runner); + free(job_pointers); + bool success = true; + for (size_t i = 0; i < jobs.size; i++) + if (!sn_vec_at(sn_map_lut_job_t, &jobs, i).lut) + { + stats.failed_module = sn_vec_at(sn_map_lut_job_t, &jobs, i).module; + success = false; + break; + } + if (success && !extract_only) + for (size_t i = 0; i < jobs.size; i++) + { + sn_map_lut_job_t* job = &sn_vec_at(sn_map_lut_job_t, &jobs, i); + sn_lut_stats_t lut_stats = sn_lut_analyze(job->lut, &job->boundary); + sn_module_id_t temporary = sn_design_add_lut_module(design, job->module, job->lut, &job->boundary, + "__sn_lut_partition"); + sn_design_replace_appended_module(design, job->module, job->name, temporary); + stats.mapped_modules++; + stats.output_luts += lut_stats.lut_count; + } + else if (success) + stats.mapped_modules += (uint32_t)jobs.size; + for (size_t i = 0; i < jobs.size; i++) + { + sn_map_lut_job_t* job = &sn_vec_at(sn_map_lut_job_t, &jobs, i); + Mini_AigStop(job->aig); + if (job->lut) + Mini_LutStop(job->lut); + sn_blast_boundary_destroy(&job->boundary); + } + sn_vec_destroy(&pending); + sn_vec_destroy(&jobs); + free(reachable); + assert(!success || sn_design_is_topo(design)); + if (returned_stats) + *returned_stats = stats; + return success; +} + +ABC_NAMESPACE_HEADER_END + +#endif diff --git a/src/base/sn/snMapMem.h b/src/base/sn/snMapMem.h new file mode 100644 index 000000000..0c8f7ac26 --- /dev/null +++ b/src/base/sn/snMapMem.h @@ -0,0 +1,101 @@ +/**CFile**************************************************************** + + FileName [snMapMem.h] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [New word-level design interface.] + + Synopsis [Mapping technology-independent memories into FPGA memory primitives.] + + Author [Alan Mishchenko] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: snMapMem.h,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#ifndef SN_MAP_MEM_H +#define SN_MAP_MEM_H + +#include "sn.h" +#include "snTech.h" + +ABC_NAMESPACE_HEADER_START + +typedef enum sn_mem_split_order_t +{ + SN_MEM_SPLIT_AUTO = 0, + SN_MEM_SPLIT_WIDTH_FIRST, + SN_MEM_SPLIT_DEPTH_FIRST +} sn_mem_split_order_t; + +typedef struct sn_mem_map_options_t +{ + bool allow_lutram_fallback; + bool allow_register_fallback; + bool preserve_names; + uint32_t min_memory_bits; + uint32_t max_primitives_per_memory; + sn_mem_split_order_t split_order; +} sn_mem_map_options_t; + +static inline sn_mem_map_options_t sn_mem_map_default_options(void) +{ + sn_mem_map_options_t options = {false, false, true, 0, 0, SN_MEM_SPLIT_AUTO}; + return options; +} + +// Returns true when a memory's dimensions and port protocol can be represented +// by one technology primitive. This conservative predicate is used before the +// rewriting pass; splitting, packing, and primitive-inst construction are +// the next mapper milestone. +static inline bool sn_mem_tech_supports(const sn_mem_tech_t* tech, uint32_t width, uint32_t depth, + sn_mem_port_mode_t port_mode) +{ + assert(tech); + if (port_mode != tech->port_mode || !width || !depth || width > UINT32_MAX / depth) + return false; + if (width * depth > tech->cap_bits) + return false; + if (depth > (1u << tech->address_bits)) + return false; + for (size_t i = 0; i < tech->width_count; i++) + if (tech->widths[i] == width) + return true; + return false; +} + +// Creates a behavioral wrapper for one technology memory shape. Keeping the +// wrapper as an SN module makes the mapped result simulatable; a later Verilog +// technology writer can replace this module by RAMB/URAM cells. +static inline sn_module_id_t sn_map_mem_primitive_module(sn_design_t* design, const sn_mem_tech_t* tech, + uint32_t width, uint32_t depth) +{ + char name[128]; + int length = snprintf(name, sizeof(name), "__sn_%s_mem_%u_%u", tech->name, width, depth); + assert(length >= 0 && (size_t)length < sizeof(name)); + sn_module_id_t existing = sn_design_find_module(design, name); + if (existing != SN_INVALID_ID) + return existing; + sn_module_id_t id = sn_design_add_module(design, name); + sn_module_t* module = sn_design_get_module(design, id); + sn_obj_id_t clock = sn_module_add_pi(module, 1, false, "clock"); + sn_obj_id_t enable = sn_module_add_pi(module, 1, false, "enable"); + sn_obj_id_t write_address = sn_module_add_pi(module, 32, false, "write_address"); + sn_obj_id_t data = sn_module_add_pi(module, width, false, "write_data"); + sn_obj_id_t read_address = sn_module_add_pi(module, 32, false, "read_address"); + sn_obj_pair_t pair = sn_module_add_mem_pair(module, width, false, depth, "mem_out", "mem_in"); + sn_module_add_mem_write(module, pair.in, clock, enable, data, write_address, "write"); + sn_obj_id_t read = sn_module_add_mem_read(module, pair.out, SN_INVALID_ID, SN_INVALID_ID, read_address, "read"); + sn_module_add_po(module, width, false, "read_data", read); + sn_design_reorder_module_topo(design, id); + return id; +} + +ABC_NAMESPACE_HEADER_END + +#endif diff --git a/src/base/sn/snMapTech.h b/src/base/sn/snMapTech.h new file mode 100644 index 000000000..64e722cf3 --- /dev/null +++ b/src/base/sn/snMapTech.h @@ -0,0 +1,1264 @@ +/**CFile**************************************************************** + + FileName [snMapTech.h] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [New word-level design interface.] + + Synopsis [Technology mapping infrastructure for SN hierarchy and hard primitives.] + + Author [Alan Mishchenko] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: snMapTech.h,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#ifndef SN_MAP_TECH_H +#define SN_MAP_TECH_H + +#include "snMapAdd.h" +#include "snMapDsp.h" +#include "snMapMem.h" + +ABC_NAMESPACE_HEADER_START + +// Combined technology mapping rebuilds each module once. Memory and multiplier +// expansion may temporarily introduce forward references; the result is +// finalized through one dependency-aware topological duplication. + +typedef struct sn_tech_map_options_t +{ + bool map_memories; + bool map_multipliers; + bool map_adders; + sn_mem_map_options_t memory; + sn_dsp_map_options_t dsp; + sn_add_map_options_t add; +} sn_tech_map_options_t; + +typedef struct sn_tech_map_stats_t +{ + size_t mem_insts; + size_t dsp_insts; + size_t carry_insts; +} sn_tech_map_stats_t; + +typedef struct sn_tech_mem_plan_t +{ + sn_obj_id_t memory; + sn_obj_id_t memory_in; + sn_obj_id_t read; + sn_obj_id_t write; + sn_obj_id_t reads[2]; + sn_obj_id_t writes[2]; + uint32_t read_count; + uint32_t write_count; + int8_t port_reads[2]; + int8_t port_writes[2]; + const sn_mem_tech_t* primitive; + uint32_t port_width; + uint32_t tile_depth; + uint32_t width_tiles; + uint32_t depth_tiles; +} sn_tech_mem_plan_t; + +typedef struct sn_tech_dsp_chunk_t +{ + sn_obj_id_t object; + uint32_t offset; + bool unsigned_correction; +} sn_tech_dsp_chunk_t; + +static inline sn_tech_mem_plan_t* sn_tech_read_plan(const sn_module_t* module, sn_tech_mem_plan_t* plans, + sn_obj_id_t object) +{ + return sn_obj_type(module, object) == SN_MEM_READ ? &plans[sn_obj_type_id(module, object)] : NULL; +} + +static inline sn_tech_map_options_t sn_tech_map_default_options(void) +{ + sn_tech_map_options_t options; + options.map_memories = true; + options.map_multipliers = true; + options.map_adders = false; + options.memory = sn_mem_map_default_options(); + options.dsp = sn_dsp_map_default_options(); + options.add = sn_add_map_default_options(); + return options; +} + +static inline uint32_t sn_tech_ceil_div(uint32_t value, uint32_t divisor) +{ + assert(divisor); + return value / divisor + (value % divisor != 0); +} + +static inline uint32_t sn_tech_floor_pow2(uint32_t value) +{ + assert(value); + uint32_t result = 1; + while (result <= value / 2) + result <<= 1; + return result; +} + +static inline uint32_t sn_tech_ceil_log2(uint32_t value) +{ + assert(value); + uint32_t result = 0; + for (value--; value; value >>= 1) + result++; + return result; +} + +static inline sn_obj_id_t sn_tech_add_uint_const(sn_module_t* module, uint32_t width, uint32_t value) +{ + assert(width && width <= 32); + assert(width == 32 || value < (1u << width)); + return sn_module_add_const(module, width, false, &value, NULL); +} + +static inline sn_obj_id_t sn_tech_add_cast(sn_module_t* module, sn_obj_id_t value, uint32_t width, bool is_signed) +{ + return sn_module_add_operator(module, SN_CAST, width, is_signed, 1, &value, NULL); +} + +static inline sn_obj_id_t sn_tech_add_zero_extend(sn_module_t* module, sn_obj_id_t value, uint32_t width) +{ + uint32_t old_width = sn_obj_width(module, value); + assert(old_width <= width); + if (old_width == width) + return value; + sn_obj_id_t padding = sn_module_add_named_obj(module, SN_CONST0, width - old_width, false, 0, NULL); + sn_obj_id_t fanins[2] = {value, padding}; + return sn_module_add_concat(module, 2, fanins, NULL); +} + +static inline uint32_t sn_tech_dsp_chunk_count(uint32_t width, uint32_t port_width, uint32_t low_width, + uint32_t min_width) +{ + assert(width && port_width && low_width && low_width <= port_width && min_width <= port_width); + if (width < min_width || low_width < min_width) + return UINT32_MAX; + uint32_t count = 1; + while (width > port_width) + { + uint32_t chunk_width = low_width; + if (width - chunk_width < min_width) + chunk_width = width - min_width; + if (chunk_width < min_width || chunk_width > port_width) + return UINT32_MAX; + width -= chunk_width; + count++; + } + return count; +} + +static inline uint32_t sn_tech_dsp_box_count(const sn_module_t* module, const sn_dsp_tech_t* dsp, + const sn_dsp_map_options_t* options, sn_obj_id_t a, sn_obj_id_t b) +{ + bool signed_operands = sn_obj_is_signed(module, a) && sn_obj_is_signed(module, b); + uint32_t a_width = sn_obj_width(module, a) + !signed_operands; + uint32_t b_width = sn_obj_width(module, b) + !signed_operands; + uint32_t a_low = options->a_unsigned_chunk_width ? options->a_unsigned_chunk_width : dsp->a_width; + uint32_t b_low = options->b_unsigned_chunk_width ? options->b_unsigned_chunk_width : dsp->b_width; + if (a_low < dsp->min_a_width || a_low > dsp->a_width || b_low < dsp->min_b_width || b_low > dsp->b_width) + return UINT32_MAX; + uint32_t direct_a = sn_tech_dsp_chunk_count(a_width, dsp->a_width, a_low, dsp->min_a_width); + uint32_t direct_b = sn_tech_dsp_chunk_count(b_width, dsp->b_width, b_low, dsp->min_b_width); + uint32_t swapped_a = sn_tech_dsp_chunk_count(b_width, dsp->a_width, a_low, dsp->min_a_width); + uint32_t swapped_b = sn_tech_dsp_chunk_count(a_width, dsp->b_width, b_low, dsp->min_b_width); + uint32_t direct = direct_a == UINT32_MAX || direct_b == UINT32_MAX || direct_a > UINT32_MAX / direct_b + ? UINT32_MAX + : direct_a * direct_b; + uint32_t swapped = swapped_a == UINT32_MAX || swapped_b == UINT32_MAX || swapped_a > UINT32_MAX / swapped_b + ? UINT32_MAX + : swapped_a * swapped_b; + return direct < swapped ? direct : swapped; +} + +static inline void sn_tech_dsp_make_chunks(sn_module_t* module, sn_obj_id_t value, uint32_t port_width, + uint32_t low_width, uint32_t min_width, sn_vec_t* chunks) +{ + assert(module && chunks); + uint32_t width = sn_obj_width(module, value); + uint32_t count = sn_tech_dsp_chunk_count(width, port_width, low_width, min_width); + assert(count != UINT32_MAX); + uint32_t offset = 0, remaining = width; + for (uint32_t i = 0; i < count; i++) + { + uint32_t chunk_width = remaining <= port_width ? remaining : low_width; + if (remaining > port_width && remaining - chunk_width < min_width) + chunk_width = remaining - min_width; + assert(chunk_width >= min_width && chunk_width <= port_width); + sn_obj_id_t chunk = count == 1 + ? value + : sn_module_add_slice(module, value, (int32_t)(offset + chunk_width - 1), + (int32_t)offset, NULL); + chunk = sn_tech_add_cast(module, chunk, chunk_width, true); + sn_tech_dsp_chunk_t* entry = sn_vec_push(sn_tech_dsp_chunk_t, chunks); + entry->object = chunk; + entry->offset = offset; + entry->unsigned_correction = i + 1 != count; + offset += chunk_width; + remaining -= chunk_width; + } + assert(!remaining && offset == width); +} + +static inline sn_obj_id_t sn_tech_dsp_align(sn_module_t* module, sn_obj_id_t value, uint32_t result_width, + uint32_t shift, bool is_signed) +{ + value = sn_tech_add_cast(module, value, result_width, is_signed); + if (!shift) + return value; + sn_obj_id_t amount = sn_tech_add_uint_const(module, 32, shift); + sn_obj_id_t fanins[2] = {value, amount}; + return sn_module_add_operator(module, SN_SHL, result_width, is_signed, 2, fanins, NULL); +} + +static inline void sn_tech_dsp_add_gated_correction(sn_module_t* module, sn_vec_t* partials, sn_obj_id_t condition, + sn_obj_id_t value, uint32_t result_width, uint32_t shift) +{ + if (shift >= result_width) + return; + sn_obj_id_t selected = sn_tech_add_cast(module, value, result_width, true); + sn_obj_id_t zero = sn_module_add_named_obj(module, SN_CONST0, result_width, false, 0, NULL); + sn_obj_id_t gated = sn_module_add_mux(module, condition, selected, zero, NULL); + *sn_vec_push(sn_obj_id_t, partials) = sn_tech_dsp_align(module, gated, result_width, shift, true); +} + +static inline sn_obj_id_t sn_tech_map_multiplier(sn_module_t* module, const sn_dsp_tech_t* dsp, + const sn_dsp_map_options_t* options, sn_obj_id_t a, + sn_obj_id_t b, uint32_t result_width, bool result_signed) +{ + assert(module && dsp && options && result_width); + bool signed_operands = sn_obj_is_signed(module, a) && sn_obj_is_signed(module, b); + if (!signed_operands) + { + uint32_t zero = 0; + sn_obj_id_t sign = sn_module_add_const(module, 1, false, &zero, NULL); + sn_obj_id_t fanins[2] = {a, sign}; + a = sn_module_add_concat(module, 2, fanins, NULL); + a = sn_tech_add_cast(module, a, sn_obj_width(module, a), true); + fanins[0] = b; + b = sn_module_add_concat(module, 2, fanins, NULL); + b = sn_tech_add_cast(module, b, sn_obj_width(module, b), true); + } + + uint32_t a_low = options->a_unsigned_chunk_width ? options->a_unsigned_chunk_width : dsp->a_width; + uint32_t b_low = options->b_unsigned_chunk_width ? options->b_unsigned_chunk_width : dsp->b_width; + assert(a_low <= dsp->a_width && b_low <= dsp->b_width); + uint32_t direct_a = sn_tech_dsp_chunk_count(sn_obj_width(module, a), dsp->a_width, a_low, dsp->min_a_width); + uint32_t direct_b = sn_tech_dsp_chunk_count(sn_obj_width(module, b), dsp->b_width, b_low, dsp->min_b_width); + uint32_t swapped_a = sn_tech_dsp_chunk_count(sn_obj_width(module, b), dsp->a_width, a_low, dsp->min_a_width); + uint32_t swapped_b = sn_tech_dsp_chunk_count(sn_obj_width(module, a), dsp->b_width, b_low, dsp->min_b_width); + uint32_t direct = direct_a == UINT32_MAX || direct_b == UINT32_MAX || direct_a > UINT32_MAX / direct_b + ? UINT32_MAX + : direct_a * direct_b; + uint32_t swapped = swapped_a == UINT32_MAX || swapped_b == UINT32_MAX || swapped_a > UINT32_MAX / swapped_b + ? UINT32_MAX + : swapped_a * swapped_b; + if (swapped < direct) + { + sn_obj_id_t temporary = a; + a = b; + b = temporary; + } + + sn_vec_t a_chunks, b_chunks, partials; + sn_vec_init(&a_chunks); + sn_vec_init(&b_chunks); + sn_vec_init(&partials); + sn_tech_dsp_make_chunks(module, a, dsp->a_width, a_low, dsp->min_a_width, &a_chunks); + sn_tech_dsp_make_chunks(module, b, dsp->b_width, b_low, dsp->min_b_width, &b_chunks); + assert(!options->max_dsps_per_multiply || + a_chunks.size * b_chunks.size <= options->max_dsps_per_multiply); + + for (size_t diagonal = 0; diagonal < a_chunks.size + b_chunks.size - 1; diagonal++) + for (size_t i = 0; i < a_chunks.size; i++) + { + if (diagonal < i) + continue; + size_t j = diagonal - i; + if (j >= b_chunks.size) + continue; + const sn_tech_dsp_chunk_t* ac = &sn_vec_at(sn_tech_dsp_chunk_t, &a_chunks, i); + const sn_tech_dsp_chunk_t* bc = &sn_vec_at(sn_tech_dsp_chunk_t, &b_chunks, j); + uint32_t shift = ac->offset + bc->offset; + if (options->prune_unused_high_products && shift >= result_width) + continue; + uint32_t product_width = sn_obj_width(module, ac->object) + sn_obj_width(module, bc->object); + uint32_t primitive_width = product_width < dsp->min_p_width ? dsp->min_p_width : product_width; + assert(primitive_width <= dsp->p_width); + sn_module_id_t primitive = sn_map_dsp_primitive_module( + module->design, dsp, sn_obj_width(module, ac->object), sn_obj_width(module, bc->object), + primitive_width, true, true); + sn_obj_id_t inputs[2] = {ac->object, bc->object}; + sn_obj_id_t product = sn_module_add_inst(module, primitive, 2, inputs, NULL, NULL); + *sn_vec_push(sn_obj_id_t, &partials) = sn_tech_dsp_align(module, product, result_width, shift, true); + + // A non-top radix chunk is unsigned even though the DSP input is signed. For a W-bit chunk U, + // U = signed(U) + msb(U)*2^W. Add the resulting one-bit-gated correction terms around the signed + // DSP product. This uses the full 27x18 multiplier while preserving exact unsigned chunk semantics. + uint32_t ac_width = sn_obj_width(module, ac->object); + uint32_t bc_width = sn_obj_width(module, bc->object); + sn_obj_id_t ac_sign = SN_INVALID_ID, bc_sign = SN_INVALID_ID; + if (ac->unsigned_correction) + { + ac_sign = sn_module_add_slice(module, ac->object, (int32_t)(ac_width - 1), + (int32_t)(ac_width - 1), NULL); + sn_tech_dsp_add_gated_correction(module, &partials, ac_sign, bc->object, result_width, + shift + ac_width); + } + if (bc->unsigned_correction) + { + bc_sign = sn_module_add_slice(module, bc->object, (int32_t)(bc_width - 1), + (int32_t)(bc_width - 1), NULL); + sn_tech_dsp_add_gated_correction(module, &partials, bc_sign, ac->object, result_width, + shift + bc_width); + } + if (ac->unsigned_correction && bc->unsigned_correction && shift + ac_width + bc_width < result_width) + { + sn_obj_id_t fanins[2] = {ac_sign, bc_sign}; + sn_obj_id_t both = sn_module_add_operator(module, SN_BIT_AND, 1, false, 2, fanins, NULL); + *sn_vec_push(sn_obj_id_t, &partials) = + sn_tech_dsp_align(module, both, result_width, shift + ac_width + bc_width, false); + } + } + + if (!partials.size) + *sn_vec_push(sn_obj_id_t, &partials) = sn_tech_add_uint_const(module, result_width, 0); + while (partials.size > 1) + { + sn_vec_t next; + sn_vec_init(&next); + if (options->balance_adders) + { + for (size_t i = 0; i < partials.size; i += 2) + { + if (i + 1 == partials.size) + *sn_vec_push(sn_obj_id_t, &next) = sn_vec_at(sn_obj_id_t, &partials, i); + else + { + sn_obj_id_t fanins[2] = {sn_vec_at(sn_obj_id_t, &partials, i), + sn_vec_at(sn_obj_id_t, &partials, i + 1)}; + *sn_vec_push(sn_obj_id_t, &next) = + sn_module_add_operator(module, SN_ADD, result_width, true, 2, fanins, NULL); + } + } + } + else + { + sn_obj_id_t fanins[2] = {sn_vec_at(sn_obj_id_t, &partials, 0), + sn_vec_at(sn_obj_id_t, &partials, 1)}; + *sn_vec_push(sn_obj_id_t, &next) = + sn_module_add_operator(module, SN_ADD, result_width, true, 2, fanins, NULL); + for (size_t i = 2; i < partials.size; i++) + *sn_vec_push(sn_obj_id_t, &next) = sn_vec_at(sn_obj_id_t, &partials, i); + } + sn_vec_destroy(&partials); + partials = next; + } + sn_obj_id_t result = sn_tech_add_cast(module, sn_vec_at(sn_obj_id_t, &partials, 0), result_width, result_signed); + sn_vec_destroy(&a_chunks); + sn_vec_destroy(&b_chunks); + sn_vec_destroy(&partials); + return result; +} + +static inline bool sn_tech_choose_memory_for_mode(const sn_tech_t* tech, uint32_t width, uint32_t depth, + const sn_mem_map_options_t* options, bool simple_dual, + sn_tech_mem_plan_t* plan) +{ + assert(tech && options && plan && width && depth); + uint64_t best_cost = UINT64_MAX; + uint32_t best_port_width = 0; + for (size_t i = 0; i < tech->memory_count; i++) + { + const sn_mem_tech_t* primitive = &tech->memories[i]; + const uint32_t* widths = simple_dual && primitive->simple_dual_width_count + ? primitive->simple_dual_widths + : primitive->widths; + size_t width_count = simple_dual && primitive->simple_dual_width_count + ? primitive->simple_dual_width_count + : primitive->width_count; + for (size_t j = 0; j < width_count; j++) + { + uint32_t port_width = widths[j]; + uint32_t tile_depth = sn_tech_floor_pow2(primitive->cap_bits / port_width); + if (tile_depth > (1u << primitive->address_bits)) + tile_depth = 1u << primitive->address_bits; + uint32_t width_tiles = sn_tech_ceil_div(width, port_width); + uint32_t depth_tiles = sn_tech_ceil_div(depth, tile_depth); + uint64_t count = (uint64_t)width_tiles * depth_tiles; + if (options->max_primitives_per_memory && count > options->max_primitives_per_memory) + continue; + uint64_t cost = count * primitive->mapping_cost; + bool prefer_tie = cost == best_cost && + ((options->split_order == SN_MEM_SPLIT_WIDTH_FIRST && port_width > best_port_width) || + (options->split_order == SN_MEM_SPLIT_DEPTH_FIRST && port_width < best_port_width)); + if (cost > best_cost || (cost == best_cost && !prefer_tie)) + continue; + best_cost = cost; + best_port_width = port_width; + plan->primitive = primitive; + plan->port_width = port_width; + plan->tile_depth = tile_depth; + plan->width_tiles = width_tiles; + plan->depth_tiles = depth_tiles; + } + } + return best_cost != UINT64_MAX; +} + +static inline bool sn_tech_choose_memory(const sn_tech_t* tech, uint32_t width, uint32_t depth, + const sn_mem_map_options_t* options, sn_tech_mem_plan_t* plan) +{ + return sn_tech_choose_memory_for_mode(tech, width, depth, options, false, plan); +} + +static inline sn_module_id_t sn_tech_memory_tile_module(sn_design_t* design, const sn_mem_tech_t* primitive, + uint32_t width, uint32_t depth) +{ + uint32_t address_width = sn_tech_ceil_log2(depth); + char name[128]; + int length = snprintf(name, sizeof(name), "__sn_%s_tile_%u_%u", primitive->name, width, depth); + assert(length >= 0 && (size_t)length < sizeof(name)); + sn_module_id_t existing = sn_design_find_module(design, name); + if (existing != SN_INVALID_ID) + return existing; + sn_module_id_t id = sn_design_add_module(design, name); + sn_module_t* module = sn_design_get_module(design, id); + sn_obj_id_t clock = sn_module_add_pi(module, 1, false, "clock"); + sn_obj_id_t enable = sn_module_add_pi(module, 1, false, "enable"); + sn_obj_id_t write_address = sn_module_add_pi(module, address_width, false, "write_address"); + sn_obj_id_t data = sn_module_add_pi(module, width, false, "write_data"); + sn_obj_id_t read_address = sn_module_add_pi(module, address_width, false, "read_address"); + sn_obj_pair_t pair = sn_module_add_mem_pair(module, width, false, depth, "mem_out", "mem_in"); + sn_module_add_mem_write(module, pair.in, clock, enable, data, write_address, "write"); + sn_obj_id_t read = sn_module_add_mem_read(module, pair.out, SN_INVALID_ID, SN_INVALID_ID, read_address, "read"); + sn_module_add_po(module, width, false, "read_data", read); + sn_design_reorder_module_topo(design, id); + return id; +} + +// A behavioral true-dual-port tile. Reads are deliberately asynchronous in +// this wrapper: frontend-imported synchronous reads are represented by the +// existing registers driven by SN_MEM_READ objects. Keeping those registers +// outside the wrapper preserves latency until a later RAM-cell emitter absorbs +// them into the physical primitive's registered read ports. +static inline sn_module_id_t sn_tech_memory_tdp_tile_module(sn_design_t* design, const sn_mem_tech_t* primitive, + uint32_t width, uint32_t depth) +{ + uint32_t address_width = sn_tech_ceil_log2(depth); + char name[128]; + int length = snprintf(name, sizeof(name), "__sn_%s_tdp_tile_%u_%u", primitive->name, width, depth); + assert(length >= 0 && (size_t)length < sizeof(name)); + sn_module_id_t existing = sn_design_find_module(design, name); + if (existing != SN_INVALID_ID) + return existing; + + sn_module_id_t id = sn_design_add_module(design, name); + sn_module_t* module = sn_design_get_module(design, id); + sn_obj_id_t clock[2], write_enable[2], address[2], data[2]; + for (uint32_t port = 0; port < 2; port++) + { + char suffix = (char)('a' + port); + char object_name[32]; + snprintf(object_name, sizeof(object_name), "clock_%c", suffix); + clock[port] = sn_module_add_pi(module, 1, false, object_name); + snprintf(object_name, sizeof(object_name), "write_enable_%c", suffix); + write_enable[port] = sn_module_add_pi(module, 1, false, object_name); + snprintf(object_name, sizeof(object_name), "address_%c", suffix); + address[port] = sn_module_add_pi(module, address_width, false, object_name); + snprintf(object_name, sizeof(object_name), "write_data_%c", suffix); + data[port] = sn_module_add_pi(module, width, false, object_name); + } + + sn_obj_pair_t pair = sn_module_add_mem_pair(module, width, false, depth, "mem_out", "mem_in"); + sn_module_add_mem_write(module, pair.in, clock[0], write_enable[0], data[0], address[0], "write_a"); + sn_module_add_mem_write(module, pair.in, clock[1], write_enable[1], data[1], address[1], "write_b"); + sn_obj_id_t read_a = sn_module_add_mem_read(module, pair.out, SN_INVALID_ID, SN_INVALID_ID, address[0], "read_a"); + sn_obj_id_t read_b = sn_module_add_mem_read(module, pair.out, SN_INVALID_ID, SN_INVALID_ID, address[1], "read_b"); + sn_module_add_po(module, width, false, "read_data_a", read_a); + sn_module_add_po(module, width, false, "read_data_b", read_b); + sn_design_reorder_module_topo(design, id); + return id; +} + +static inline bool sn_tech_assign_tdp_ports(const sn_module_t* source, sn_tech_mem_plan_t* plan) +{ + assert(source && plan && plan->read_count <= 2 && plan->write_count <= 2); + plan->port_reads[0] = plan->port_reads[1] = -1; + plan->port_writes[0] = plan->port_writes[1] = -1; + for (uint32_t write = 0; write < plan->write_count; write++) + plan->port_writes[write] = (int8_t)write; + + // Prefer sharing a physical port when the logical read and write use the + // same address. This recognizes the usual read-first HLS R/W port. + for (uint32_t read = 0; read < plan->read_count; read++) + { + sn_obj_id_t read_address = sn_obj_fanin(source, plan->reads[read], SN_MEM_READ_ADDRESS); + for (uint32_t port = 0; port < 2; port++) + { + int8_t write = plan->port_writes[port]; + if (write >= 0 && plan->port_reads[port] < 0 && + sn_obj_fanin(source, plan->writes[(uint32_t)write], SN_MEM_WRITE_ADDRESS) == read_address) + { + plan->port_reads[port] = (int8_t)read; + break; + } + } + } + for (uint32_t read = 0; read < plan->read_count; read++) + { + bool assigned = false; + for (uint32_t port = 0; port < 2; port++) + assigned |= plan->port_reads[port] == (int8_t)read; + if (assigned) + continue; + for (uint32_t port = 0; port < 2; port++) + if (plan->port_reads[port] < 0 && plan->port_writes[port] < 0) + { + plan->port_reads[port] = (int8_t)read; + assigned = true; + break; + } + if (!assigned) + return false; + } + return true; +} + +static inline sn_obj_id_t sn_tech_memory_bank_select(sn_module_t* module, sn_obj_id_t address, + uint32_t address_bits, uint32_t bank_bits, uint32_t bank) +{ + if (!bank_bits) + return sn_tech_add_uint_const(module, 1, 1); + uint32_t needed = address_bits + bank_bits; + address = sn_tech_add_zero_extend(module, address, needed > sn_obj_width(module, address) + ? needed + : sn_obj_width(module, address)); + sn_obj_id_t index = sn_module_add_slice(module, address, (int32_t)(needed - 1), (int32_t)address_bits, NULL); + sn_obj_id_t value = sn_tech_add_uint_const(module, bank_bits, bank); + sn_obj_id_t fanins[2] = {index, value}; + return sn_module_add_operator(module, SN_EQ, 1, false, 2, fanins, NULL); +} + +static inline sn_obj_id_t sn_tech_memory_local_address(sn_module_t* module, sn_obj_id_t address, + uint32_t address_bits) +{ + if (sn_obj_width(module, address) > address_bits) + return sn_module_add_slice(module, address, (int32_t)(address_bits - 1), 0, NULL); + return sn_tech_add_zero_extend(module, address, address_bits); +} + +static inline sn_obj_id_t sn_tech_map_memory(sn_module_t* module, const sn_module_t* source, + const sn_tech_mem_plan_t* plan, const sn_mem_map_options_t* options) +{ + assert(module && source && plan && options); + sn_obj_id_t clock = sn_vec_at(sn_obj_id_t, &source->copy_ids, + sn_obj_fanin(source, plan->write, SN_MEM_WRITE_CLOCK)); + sn_obj_id_t enable_old = sn_obj_fanin(source, plan->write, SN_MEM_WRITE_ENABLE); + sn_obj_id_t enable = enable_old == SN_INVALID_ID + ? sn_tech_add_uint_const(module, 1, 1) + : sn_vec_at(sn_obj_id_t, &source->copy_ids, enable_old); + sn_obj_id_t data = sn_vec_at(sn_obj_id_t, &source->copy_ids, + sn_obj_fanin(source, plan->write, SN_MEM_WRITE_DATA)); + sn_obj_id_t write_address = sn_vec_at(sn_obj_id_t, &source->copy_ids, + sn_obj_fanin(source, plan->write, SN_MEM_WRITE_ADDRESS)); + sn_obj_id_t read_address = sn_vec_at(sn_obj_id_t, &source->copy_ids, + sn_obj_fanin(source, plan->read, SN_MEM_READ_ADDRESS)); + uint32_t address_bits = sn_tech_ceil_log2(plan->tile_depth); + uint32_t bank_bits = sn_tech_ceil_log2(plan->depth_tiles); + sn_obj_id_t local_write = sn_tech_memory_local_address(module, write_address, address_bits); + sn_obj_id_t local_read = sn_tech_memory_local_address(module, read_address, address_bits); + sn_module_id_t primitive = + sn_tech_memory_tile_module(module->design, plan->primitive, plan->port_width, plan->tile_depth); + + sn_vec_t bank_enables, read_selects; + sn_vec_init(&bank_enables); + sn_vec_init(&read_selects); + for (uint32_t d = 0; d < plan->depth_tiles; d++) + { + sn_obj_id_t write_select = + sn_tech_memory_bank_select(module, write_address, address_bits, bank_bits, d); + sn_obj_id_t enable_fanins[2] = {enable, write_select}; + *sn_vec_push(sn_obj_id_t, &bank_enables) = + sn_module_add_operator(module, SN_BIT_AND, 1, false, 2, enable_fanins, NULL); + *sn_vec_push(sn_obj_id_t, &read_selects) = + sn_tech_memory_bank_select(module, read_address, address_bits, bank_bits, d); + } + + sn_vec_t width_results; + sn_vec_init(&width_results); + for (uint32_t w = 0; w < plan->width_tiles; w++) + { + uint32_t offset = w * plan->port_width; + uint32_t actual = sn_obj_width(source, plan->memory) - offset; + if (actual > plan->port_width) + actual = plan->port_width; + sn_obj_id_t write_data = sn_module_add_slice(module, data, (int32_t)(offset + actual - 1), + (int32_t)offset, NULL); + write_data = sn_tech_add_zero_extend(module, write_data, plan->port_width); + sn_vec_t banks; + sn_vec_init(&banks); + for (uint32_t d = 0; d < plan->depth_tiles; d++) + { + sn_obj_id_t tile_enable = sn_vec_at(sn_obj_id_t, &bank_enables, d); + sn_obj_id_t inputs[5] = {clock, tile_enable, local_write, write_data, local_read}; + *sn_vec_push(sn_obj_id_t, &banks) = sn_module_add_inst(module, primitive, 5, inputs, NULL, NULL); + } + sn_obj_id_t selected = sn_vec_at(sn_obj_id_t, &banks, 0); + for (uint32_t d = 1; d < plan->depth_tiles; d++) + { + sn_obj_id_t select = sn_vec_at(sn_obj_id_t, &read_selects, d); + selected = sn_module_add_mux(module, select, sn_vec_at(sn_obj_id_t, &banks, d), selected, NULL); + } + if (actual != plan->port_width) + selected = sn_module_add_slice(module, selected, (int32_t)(actual - 1), 0, NULL); + *sn_vec_push(sn_obj_id_t, &width_results) = selected; + sn_vec_destroy(&banks); + } + sn_obj_id_t result = width_results.size == 1 + ? sn_vec_at(sn_obj_id_t, &width_results, 0) + : sn_module_add_concat(module, (uint32_t)width_results.size, + sn_vec_data(sn_obj_id_t, &width_results), NULL); + sn_vec_destroy(&width_results); + sn_vec_destroy(&read_selects); + sn_vec_destroy(&bank_enables); + return result; +} + +static inline void sn_tech_map_tdp_memory(sn_module_t* module, const sn_module_t* source, + const sn_tech_mem_plan_t* plan, sn_obj_id_t results[2]) +{ + assert(module && source && plan && plan->read_count > 1 && plan->read_count <= 2 && + plan->write_count <= 2 && plan->primitive->port_mode == SN_MEM_PORT_TRUE_DUAL); + results[0] = results[1] = SN_INVALID_ID; + uint32_t address_bits = sn_tech_ceil_log2(plan->tile_depth); + uint32_t bank_bits = sn_tech_ceil_log2(plan->depth_tiles); + sn_module_id_t primitive = + sn_tech_memory_tdp_tile_module(module->design, plan->primitive, plan->port_width, plan->tile_depth); + sn_obj_id_t zero = sn_tech_add_uint_const(module, 1, 0); + sn_obj_id_t zero_data = sn_module_add_named_obj(module, SN_CONST0, plan->port_width, false, 0, NULL); + + sn_obj_id_t port_clocks[2] = {zero, zero}; + sn_obj_id_t port_addresses[2] = {SN_INVALID_ID, SN_INVALID_ID}; + sn_obj_id_t port_write_data[2] = {SN_INVALID_ID, SN_INVALID_ID}; + sn_vec_t port_enables[2], read_selects[2]; + for (uint32_t port = 0; port < 2; port++) + { + sn_vec_init(&port_enables[port]); + int8_t read_index = plan->port_reads[port]; + int8_t write_index = plan->port_writes[port]; + sn_obj_id_t old_address = read_index >= 0 + ? sn_obj_fanin(source, plan->reads[(uint32_t)read_index], SN_MEM_READ_ADDRESS) + : sn_obj_fanin(source, plan->writes[(uint32_t)write_index], + SN_MEM_WRITE_ADDRESS); + sn_obj_id_t address = sn_vec_at(sn_obj_id_t, &source->copy_ids, old_address); + port_addresses[port] = sn_tech_memory_local_address(module, address, address_bits); + sn_obj_id_t enable = zero; + if (write_index >= 0) + { + sn_obj_id_t write = plan->writes[(uint32_t)write_index]; + port_clocks[port] = sn_vec_at(sn_obj_id_t, &source->copy_ids, + sn_obj_fanin(source, write, SN_MEM_WRITE_CLOCK)); + sn_obj_id_t old_enable = sn_obj_fanin(source, write, SN_MEM_WRITE_ENABLE); + enable = old_enable == SN_INVALID_ID ? sn_tech_add_uint_const(module, 1, 1) + : sn_vec_at(sn_obj_id_t, &source->copy_ids, old_enable); + port_write_data[port] = sn_vec_at(sn_obj_id_t, &source->copy_ids, + sn_obj_fanin(source, write, SN_MEM_WRITE_DATA)); + } + for (uint32_t depth_tile = 0; depth_tile < plan->depth_tiles; depth_tile++) + { + sn_obj_id_t bank_enable = zero; + if (write_index >= 0) + { + sn_obj_id_t bank_select = + sn_tech_memory_bank_select(module, address, address_bits, bank_bits, depth_tile); + sn_obj_id_t enable_fanins[2] = {enable, bank_select}; + bank_enable = sn_module_add_operator(module, SN_BIT_AND, 1, false, 2, enable_fanins, NULL); + } + *sn_vec_push(sn_obj_id_t, &port_enables[port]) = bank_enable; + } + } + for (uint32_t read = 0; read < plan->read_count; read++) + { + sn_vec_init(&read_selects[read]); + sn_obj_id_t address = sn_vec_at(sn_obj_id_t, &source->copy_ids, + sn_obj_fanin(source, plan->reads[read], SN_MEM_READ_ADDRESS)); + for (uint32_t depth_tile = 0; depth_tile < plan->depth_tiles; depth_tile++) + *sn_vec_push(sn_obj_id_t, &read_selects[read]) = + sn_tech_memory_bank_select(module, address, address_bits, bank_bits, depth_tile); + } + + sn_vec_t read_width_results[2]; + for (uint32_t read = 0; read < plan->read_count; read++) + sn_vec_init(&read_width_results[read]); + + for (uint32_t width_tile = 0; width_tile < plan->width_tiles; width_tile++) + { + uint32_t offset = width_tile * plan->port_width; + uint32_t actual = sn_obj_width(source, plan->memory) - offset; + if (actual > plan->port_width) + actual = plan->port_width; + sn_vec_t bank_results[2]; + for (uint32_t read = 0; read < plan->read_count; read++) + sn_vec_init(&bank_results[read]); + + for (uint32_t depth_tile = 0; depth_tile < plan->depth_tiles; depth_tile++) + { + sn_obj_id_t inputs[8]; + for (uint32_t port = 0; port < 2; port++) + { + int8_t write_index = plan->port_writes[port]; + sn_obj_id_t write_data = zero_data; + if (write_index >= 0) + { + write_data = sn_module_add_slice(module, port_write_data[port], + (int32_t)(offset + actual - 1), + (int32_t)offset, NULL); + write_data = sn_tech_add_zero_extend(module, write_data, plan->port_width); + } + inputs[4 * port + 0] = port_clocks[port]; + inputs[4 * port + 1] = sn_vec_at(sn_obj_id_t, &port_enables[port], depth_tile); + inputs[4 * port + 2] = port_addresses[port]; + inputs[4 * port + 3] = write_data; + } + sn_obj_id_t inst = sn_module_add_inst(module, primitive, 8, inputs, NULL, NULL); + for (uint32_t port = 0; port < 2; port++) + if (plan->port_reads[port] >= 0) + { + uint32_t read = (uint32_t)plan->port_reads[port]; + *sn_vec_push(sn_obj_id_t, &bank_results[read]) = sn_inst_output(module, inst, port); + } + } + + for (uint32_t read = 0; read < plan->read_count; read++) + { + sn_obj_id_t selected = sn_vec_at(sn_obj_id_t, &bank_results[read], 0); + for (uint32_t depth_tile = 1; depth_tile < plan->depth_tiles; depth_tile++) + { + sn_obj_id_t select = sn_vec_at(sn_obj_id_t, &read_selects[read], depth_tile); + selected = sn_module_add_mux(module, select, + sn_vec_at(sn_obj_id_t, &bank_results[read], depth_tile), selected, NULL); + } + if (actual != plan->port_width) + selected = sn_module_add_slice(module, selected, (int32_t)(actual - 1), 0, NULL); + *sn_vec_push(sn_obj_id_t, &read_width_results[read]) = selected; + sn_vec_destroy(&bank_results[read]); + } + } + + for (uint32_t read = 0; read < plan->read_count; read++) + { + results[read] = read_width_results[read].size == 1 + ? sn_vec_at(sn_obj_id_t, &read_width_results[read], 0) + : sn_module_add_concat(module, (uint32_t)read_width_results[read].size, + sn_vec_data(sn_obj_id_t, &read_width_results[read]), NULL); + sn_vec_destroy(&read_width_results[read]); + sn_vec_destroy(&read_selects[read]); + } + for (uint32_t port = 0; port < 2; port++) + sn_vec_destroy(&port_enables[port]); +} + +static inline sn_module_id_t sn_design_map_tech_internal(sn_design_t* design, sn_module_id_t source_module_id, + const sn_tech_t* tech, + const sn_tech_map_options_t* user_options, bool force_copy) +{ + assert(design && tech && source_module_id < design->modules.size); + sn_tech_map_options_t defaults = sn_tech_map_default_options(); + const sn_tech_map_options_t* options = user_options ? user_options : &defaults; + sn_module_t* source = sn_design_get_module(design, source_module_id); + size_t object_count = source->obj_types.size; + size_t read_count = source->type_objects[SN_MEM_READ].size; + bool* omit = (bool*)calloc(object_count, sizeof(bool)); + bool* map_mul = (bool*)calloc(object_count, sizeof(bool)); + bool* map_add = (bool*)calloc(object_count, sizeof(bool)); + sn_tech_mem_plan_t* read_plans = (sn_tech_mem_plan_t*)calloc(read_count, sizeof(sn_tech_mem_plan_t)); + assert((!object_count || omit) && (!object_count || map_mul) && (!object_count || map_add) && + (!read_count || read_plans)); + for (size_t i = 0; i < read_count; i++) + { + memset(&read_plans[i], 0, sizeof(read_plans[i])); + read_plans[i].memory = SN_INVALID_ID; + } + + if (options->map_memories) + for (size_t i = 0; i < source->type_objects[SN_MEM_OUT].size; i++) + { + sn_obj_id_t memory = sn_vec_at(sn_obj_id_t, &source->type_objects[SN_MEM_OUT], i); + uint64_t bits = (uint64_t)sn_obj_width(source, memory) * sn_obj_mem_depth(source, memory); + if (bits < options->memory.min_memory_bits || sn_obj_mem_init_data(source, memory) != SN_INVALID_ID) + continue; + sn_obj_id_t reads_found[2] = {SN_INVALID_ID, SN_INVALID_ID}; + sn_obj_id_t writes_found[2] = {SN_INVALID_ID, SN_INVALID_ID}; + uint32_t reads = 0, writes = 0; + for (size_t j = 0; j < source->type_objects[SN_MEM_READ].size; j++) + { + sn_obj_id_t candidate = sn_vec_at(sn_obj_id_t, &source->type_objects[SN_MEM_READ], j); + if (sn_obj_fanin(source, candidate, SN_MEM_READ_MEMORY) == memory) + { + if (reads < 2) + reads_found[reads] = candidate; + reads++; + } + } + sn_obj_id_t memory_in = sn_obj_pair_in(source, memory); + for (uint32_t j = 0; j < sn_obj_fanin_count(source, memory_in); j++) + { + sn_obj_id_t candidate = sn_obj_fanin(source, memory_in, j); + if (candidate != SN_INVALID_ID && sn_obj_type(source, candidate) == SN_MEM_WRITE) + { + if (writes < 2) + writes_found[writes] = candidate; + writes++; + } + } + if (!reads || reads > 2 || !writes || writes > 2) + continue; + // A single read with two independent writes needs true-dual-port + // collision analysis; preserve it until that case is modeled. + if (reads == 1 && writes != 1) + continue; + bool asynchronous_reads = true; + for (uint32_t read = 0; read < reads; read++) + asynchronous_reads &= sn_obj_fanin(source, reads_found[read], SN_MEM_READ_CLOCK) == SN_INVALID_ID && + sn_obj_fanin(source, reads_found[read], SN_MEM_READ_ENABLE) == SN_INVALID_ID; + if (!asynchronous_reads) + continue; + sn_tech_mem_plan_t plan = {0}; + plan.memory = memory; + plan.memory_in = memory_in; + plan.read = reads_found[0]; + plan.write = writes_found[0]; + plan.read_count = reads; + plan.write_count = writes; + for (uint32_t read = 0; read < reads; read++) + plan.reads[read] = reads_found[read]; + for (uint32_t write = 0; write < writes; write++) + plan.writes[write] = writes_found[write]; + if (!sn_tech_choose_memory_for_mode(tech, sn_obj_width(source, memory), + sn_obj_mem_depth(source, memory), &options->memory, + reads == 1 && writes == 1, &plan)) + continue; + if (reads > 1 && (!plan.primitive || plan.primitive->port_mode != SN_MEM_PORT_TRUE_DUAL || + !sn_tech_assign_tdp_ports(source, &plan))) + continue; + for (uint32_t read = 0; read < reads; read++) + read_plans[sn_obj_type_id(source, reads_found[read])] = plan; + omit[memory] = omit[memory_in] = true; + for (uint32_t write = 0; write < writes; write++) + omit[writes_found[write]] = true; + } + + if (options->map_multipliers) + { + assert(tech->dsp_count); + const sn_dsp_tech_t* dsp = &tech->dsps[0]; + bool mapping_failed = false; + for (size_t i = 0; i < source->type_objects[SN_MUL].size; i++) + { + sn_obj_id_t mul = sn_vec_at(sn_obj_id_t, &source->type_objects[SN_MUL], i); + sn_obj_id_t a = sn_obj_fanin(source, mul, 0); + sn_obj_id_t b = sn_obj_fanin(source, mul, 1); + uint32_t count = sn_tech_dsp_box_count(source, dsp, &options->dsp, a, b); + map_mul[mul] = count != UINT32_MAX && + (!options->dsp.max_dsps_per_multiply || + count <= options->dsp.max_dsps_per_multiply); + mapping_failed |= !map_mul[mul] && !options->dsp.allow_soft_fallback; + } + if (mapping_failed) + { + free(omit); + free(map_mul); + free(map_add); + free(read_plans); + return SN_INVALID_ID; + } + } + + if (options->map_adders) + { + assert(tech->carry_count); + const sn_carry_tech_t* carry = &tech->carries[0]; + for (sn_obj_id_t object = 0; object < object_count; object++) + map_add[object] = sn_add_tech_supports(carry, &options->add, sn_obj_type(source, object), + sn_obj_width(source, object)); + } + + bool changed = false; + for (sn_obj_id_t object = 0; object < object_count && !changed; object++) + { + const sn_tech_mem_plan_t* read_plan = sn_tech_read_plan(source, read_plans, object); + changed = omit[object] || map_mul[object] || map_add[object] || + (read_plan && read_plan->memory != SN_INVALID_ID); + } + if (!changed && !force_copy) + { + free(omit); + free(map_mul); + free(map_add); + free(read_plans); + return source_module_id; + } + + const char* source_name = sn_name_get(&design->names, source->name); + char mapped_name[256]; + int length = snprintf(mapped_name, sizeof(mapped_name), "%s_techmap", source_name); + assert(length >= 0 && (size_t)length < sizeof(mapped_name)); + for (uint32_t suffix = 1; sn_design_find_module(design, mapped_name) != SN_INVALID_ID; suffix++) + { + length = snprintf(mapped_name, sizeof(mapped_name), "%s_techmap_%u", source_name, suffix); + assert(length >= 0 && (size_t)length < sizeof(mapped_name)); + } + sn_module_id_t mapped_id = sn_design_add_module(design, mapped_name); + sn_module_t* mapped = sn_design_get_module(design, mapped_id); + sn_vec_t order = sn_module_topo_order(source); + sn_vec_resize(sn_obj_id_t, &source->copy_ids, object_count); + for (size_t i = 0; i < object_count; i++) + sn_vec_at(sn_obj_id_t, &source->copy_ids, i) = SN_INVALID_ID; + + for (size_t i = 0; i < order.size; i++) + { + sn_obj_id_t old_object = sn_vec_at(sn_obj_id_t, &order, i); + if (omit[old_object]) + continue; + bool special_mul = map_mul[old_object]; + const sn_tech_mem_plan_t* read_plan = sn_tech_read_plan(source, read_plans, old_object); + bool special_mem = read_plan && read_plan->memory != SN_INVALID_ID; + bool special_add = map_add[old_object]; + sn_obj_id_t new_object = special_mul || special_mem || special_add + ? sn_module_add_obj(mapped, SN_BUF, sn_obj_width(source, old_object), + sn_obj_is_signed(source, old_object), 1, + sn_obj_name_id(source, old_object)) + : sn_module_dup_obj_skeleton(mapped, source, old_object); + sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object) = new_object; + } + + // Topological traversal can order paired IN objects differently from their + // source-order OUT objects. Restore matching type IDs before metadata and + // fanins are copied so state pairs remain paired in the provisional graph. + sn_module_clean_rebuild_pair_ids(mapped, source, SN_REG_OUT, SN_REG_IN); + sn_module_clean_rebuild_pair_ids(mapped, source, SN_MEM_OUT, SN_MEM_IN); + sn_module_clean_rebuild_pair_ids(mapped, source, SN_LOOP_OUT, SN_LOOP_IN); + + const sn_dsp_tech_t* dsp = tech->dsp_count ? &tech->dsps[0] : NULL; + const sn_carry_tech_t* carry = tech->carry_count ? &tech->carries[0] : NULL; + for (size_t i = 0; i < order.size; i++) + { + sn_obj_id_t old_object = sn_vec_at(sn_obj_id_t, &order, i); + sn_obj_id_t placeholder = sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object); + const sn_tech_mem_plan_t* read_plan = sn_tech_read_plan(source, read_plans, old_object); + if (placeholder == SN_INVALID_ID) + continue; + if (map_mul[old_object]) + { + assert(dsp && sn_obj_fanin_count(source, old_object) == 2); + sn_obj_id_t a = sn_vec_at(sn_obj_id_t, &source->copy_ids, sn_obj_fanin(source, old_object, 0)); + sn_obj_id_t b = sn_vec_at(sn_obj_id_t, &source->copy_ids, sn_obj_fanin(source, old_object, 1)); + sn_obj_id_t result = sn_tech_map_multiplier(mapped, dsp, &options->dsp, a, b, + sn_obj_width(source, old_object), + sn_obj_is_signed(source, old_object)); + sn_obj_connect(mapped, placeholder, 0, result); + } + else if (map_add[old_object]) + { + assert(carry && sn_obj_fanin_count(source, old_object) == 2); + sn_obj_id_t a = sn_vec_at(sn_obj_id_t, &source->copy_ids, sn_obj_fanin(source, old_object, 0)); + sn_obj_id_t b = sn_vec_at(sn_obj_id_t, &source->copy_ids, sn_obj_fanin(source, old_object, 1)); + const char* name = sn_obj_name_id(source, old_object) == SN_INVALID_ID ? NULL + : sn_obj_name(source, old_object); + sn_obj_id_t result = sn_add_map_carry_chain( + mapped, carry, sn_obj_type(source, old_object), a, b, sn_obj_width(source, old_object), + sn_obj_is_signed(source, old_object), name); + sn_obj_connect(mapped, placeholder, 0, result); + } + else if (read_plan && read_plan->memory != SN_INVALID_ID) + { + const sn_tech_mem_plan_t* plan = read_plan; + if (plan->read_count == 1) + { + sn_obj_id_t result = sn_tech_map_memory(mapped, source, plan, &options->memory); + sn_obj_connect(mapped, placeholder, 0, result); + } + else if (old_object == plan->reads[0]) + { + sn_obj_id_t results[2]; + sn_tech_map_tdp_memory(mapped, source, plan, results); + for (uint32_t read = 0; read < plan->read_count; read++) + { + sn_obj_id_t read_placeholder = + sn_vec_at(sn_obj_id_t, &source->copy_ids, plan->reads[read]); + sn_obj_connect(mapped, read_placeholder, 0, results[read]); + } + } + } + } + + for (size_t i = 0; i < order.size; i++) + { + sn_obj_id_t old_object = sn_vec_at(sn_obj_id_t, &order, i); + sn_obj_id_t new_object = sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object); + const sn_tech_mem_plan_t* read_plan = sn_tech_read_plan(source, read_plans, old_object); + bool special = map_mul[old_object] || map_add[old_object] || + (read_plan && read_plan->memory != SN_INVALID_ID); + if (new_object == SN_INVALID_ID || special) + continue; + sn_module_dup_obj_metadata(mapped, sn_obj_type_id(mapped, new_object), source, old_object); + if (sn_obj_type(source, old_object) == SN_FAN) + { + sn_obj_id_t old_inst = sn_fan_inst_id(source, old_object); + sn_vec_at(sn_obj_id_t, &mapped->fan_insts, sn_obj_type_id(mapped, new_object)) = + sn_vec_at(sn_obj_id_t, &source->copy_ids, old_inst); + } + for (uint32_t j = 0; j < sn_obj_fanin_count(source, old_object); j++) + { + sn_obj_id_t old_fanin = sn_obj_fanin(source, old_object, j); + sn_obj_id_t new_fanin = old_fanin == SN_INVALID_ID + ? SN_INVALID_ID + : sn_vec_at(sn_obj_id_t, &source->copy_ids, old_fanin); + assert(new_fanin != SN_INVALID_ID || old_fanin == SN_INVALID_ID); + sn_obj_connect(mapped, new_object, j, new_fanin); + } + } + + // Finalize the provisional graph and compose its reorder map with the + // persistent source-to-mapped copy array. + char temporary_name[96]; + uint32_t temporary_suffix = 0; + do + { + length = snprintf(temporary_name, sizeof(temporary_name), "__sn_tech_topo_%u_%u", mapped_id, + temporary_suffix++); + assert(length >= 0 && (size_t)length < sizeof(temporary_name) && temporary_suffix != 0); + } while (sn_name_find(&design->names, temporary_name) != SN_INVALID_ID); + sn_module_id_t final_id = sn_design_dup_module_topo(design, mapped_id, temporary_name); + assert(final_id + 1 == design->modules.size); + sn_module_t* provisional = sn_design_get_module(design, mapped_id); + sn_module_t* final_module = sn_design_get_module(design, final_id); + sn_name_id_t temporary_name_id = final_module->name; + for (size_t i = 0; i < source->copy_ids.size; i++) + { + sn_obj_id_t provisional_object = sn_vec_at(sn_obj_id_t, &source->copy_ids, i); + if (provisional_object != SN_INVALID_ID) + sn_vec_at(sn_obj_id_t, &source->copy_ids, i) = + sn_vec_at(sn_obj_id_t, &provisional->copy_ids, provisional_object); + } + sn_name_id_t mapped_name_id = provisional->name; + bool interface_locked = provisional->interface_locked; + sn_design_invalidate_copies_to_module_except(design, mapped_id, source); + sn_module_destroy(provisional); + free(provisional); + final_module->id = mapped_id; + final_module->name = mapped_name_id; + final_module->interface_locked = interface_locked; + sn_vec_at(sn_module_t*, &design->modules, mapped_id) = final_module; + design->modules.size--; + sn_name_remove_last(&design->names, temporary_name_id); + source->copy_module = mapped_id; + assert(sn_module_is_topo(final_module)); + + free(omit); + free(map_mul); + free(map_add); + free(read_plans); + sn_vec_destroy(&order); + return mapped_id; +} + +static inline sn_module_id_t sn_design_map_tech(sn_design_t* design, sn_module_id_t source_module_id, + const sn_tech_t* tech, const sn_tech_map_options_t* user_options) +{ + return sn_design_map_tech_internal(design, source_module_id, tech, user_options, false); +} + +typedef struct sn_tech_count_frame_t +{ + sn_module_id_t module; + size_t next_inst; +} sn_tech_count_frame_t; + +static inline void sn_tech_count_hierarchy_instances(const sn_design_t* design, sn_module_id_t root, + sn_tech_map_stats_t* stats) +{ + size_t module_count = design->modules.size; + uint8_t* states = (uint8_t*)calloc(module_count, 1); + sn_tech_map_stats_t* cached = (sn_tech_map_stats_t*)calloc(module_count, sizeof(sn_tech_map_stats_t)); + sn_vec_t stack; + assert(design && root < module_count && stats && states && cached); + sn_vec_init(&stack); + states[root] = 1; + sn_tech_count_frame_t* first = sn_vec_push(sn_tech_count_frame_t, &stack); + first->module = root; + first->next_inst = 0; + while (stack.size) + { + sn_tech_count_frame_t* frame = &sn_vec_at(sn_tech_count_frame_t, &stack, stack.size - 1); + const sn_module_t* module = sn_design_get_module_const(design, frame->module); + if (frame->next_inst < module->inst_modules.size) + { + sn_module_id_t child_id = sn_vec_at(sn_module_id_t, &module->inst_modules, frame->next_inst++); + const sn_module_t* child = sn_design_get_module_const(design, child_id); + if (!sn_module_is_technology_primitive(child) && states[child_id] == 0) + { + states[child_id] = 1; + sn_tech_count_frame_t* child_frame = sn_vec_push(sn_tech_count_frame_t, &stack); + child_frame->module = child_id; + child_frame->next_inst = 0; + } + continue; + } + sn_tech_map_stats_t total = {0}; + for (size_t i = 0; i < module->inst_modules.size; i++) + { + sn_module_id_t child_id = sn_vec_at(sn_module_id_t, &module->inst_modules, i); + const sn_module_t* child = sn_design_get_module_const(design, child_id); + const char* name = sn_name_get(&design->names, child->name); + if (strncmp(name, "__sn_RAM", 8) == 0 || strncmp(name, "__sn_URAM", 9) == 0) + total.mem_insts++; + else if (strncmp(name, "__sn_DSP", 8) == 0) + total.dsp_insts++; + else if (strncmp(name, "__sn_CARRY", 10) == 0) + total.carry_insts++; + else + { + total.mem_insts += cached[child_id].mem_insts; + total.dsp_insts += cached[child_id].dsp_insts; + total.carry_insts += cached[child_id].carry_insts; + } + } + cached[frame->module] = total; + states[frame->module] = 2; + stack.size--; + } + *stats = cached[root]; + sn_vec_destroy(&stack); + free(cached); + free(states); +} + +typedef struct sn_tech_hierarchy_frame_t +{ + sn_module_id_t module; + size_t next_inst; +} sn_tech_hierarchy_frame_t; + +// Visits the reachable hierarchy bottom-up. A module is copied only when it contains a primitive selected by this +// pass or when one of its child definitions changed and its instance reference must be redirected. Untouched +// subhierarchies retain their original module IDs. The original definitions remain in the design. +static inline sn_module_id_t sn_design_map_tech_hierarchy(sn_design_t* design, sn_module_id_t top_id, + const sn_tech_t* tech, + const sn_tech_map_options_t* options, + sn_tech_map_stats_t* returned_stats) +{ + assert(design && top_id < design->modules.size && tech && options); + size_t original_count = design->modules.size; + sn_module_id_t* replacements = (sn_module_id_t*)malloc(sizeof(sn_module_id_t) * original_count); + uint8_t* states = (uint8_t*)calloc(original_count, sizeof(uint8_t)); + sn_vec_t stack, postorder; + assert(replacements && states); + for (sn_module_id_t id = 0; id < original_count; id++) + replacements[id] = id; + sn_vec_init(&stack); + sn_vec_init(&postorder); + states[top_id] = 1; + sn_tech_hierarchy_frame_t* root = sn_vec_push(sn_tech_hierarchy_frame_t, &stack); + root->module = top_id; + root->next_inst = 0; + while (stack.size) + { + sn_tech_hierarchy_frame_t* frame = &sn_vec_at(sn_tech_hierarchy_frame_t, &stack, stack.size - 1); + const sn_module_t* module = sn_design_get_module_const(design, frame->module); + if (frame->next_inst < module->inst_modules.size) + { + sn_module_id_t child = sn_vec_at(sn_module_id_t, &module->inst_modules, frame->next_inst++); + assert(child < original_count); + assert(states[child] != 1 && "recursive module instantiation is unsupported"); + if (states[child] == 0) + { + states[child] = 1; + sn_tech_hierarchy_frame_t* child_frame = sn_vec_push(sn_tech_hierarchy_frame_t, &stack); + child_frame->module = child; + child_frame->next_inst = 0; + } + continue; + } + states[frame->module] = 2; + *sn_vec_push(sn_module_id_t, &postorder) = frame->module; + stack.size--; + } + + for (size_t order = 0; order < postorder.size; order++) + { + sn_module_id_t id = sn_vec_at(sn_module_id_t, &postorder, order); + const sn_module_t* module = sn_design_get_module_const(design, id); + if (sn_module_is_technology_primitive(module)) + continue; + bool child_changed = false; + for (size_t i = 0; i < module->inst_modules.size; i++) + { + sn_module_id_t child = sn_vec_at(sn_module_id_t, &module->inst_modules, i); + child_changed |= replacements[child] != child; + } + replacements[id] = sn_design_map_tech_internal(design, id, tech, options, child_changed); + if (replacements[id] == SN_INVALID_ID) + { + for (sn_module_id_t appended = (sn_module_id_t)original_count; + appended < design->modules.size; appended++) + { + sn_module_t* discarded = sn_design_get_module(design, appended); + sn_module_destroy(discarded); + free(discarded); + } + design->modules.size = original_count; + for (sn_module_id_t reachable = 0; reachable < original_count; reachable++) + if (states[reachable]) + { + sn_module_t* original = sn_design_get_module(design, reachable); + sn_vec_destroy(&original->copy_ids); + sn_vec_init(&original->copy_ids); + original->copy_module = SN_INVALID_ID; + } + sn_vec_destroy(&postorder); + sn_vec_destroy(&stack); + free(states); + free(replacements); + if (returned_stats) + memset(returned_stats, 0, sizeof(*returned_stats)); + return SN_INVALID_ID; + } + if (replacements[id] == id) + continue; + sn_module_t* mapped = sn_design_get_module(design, replacements[id]); + for (size_t i = 0; i < mapped->inst_modules.size; i++) + { + sn_module_id_t child = sn_vec_at(sn_module_id_t, &mapped->inst_modules, i); + if (child < original_count) + sn_vec_at(sn_module_id_t, &mapped->inst_modules, i) = replacements[child]; + } + } + + sn_tech_map_stats_t stats = {0}; + sn_module_id_t result = replacements[top_id]; + sn_tech_count_hierarchy_instances(design, result, &stats); + sn_vec_destroy(&postorder); + sn_vec_destroy(&stack); + free(states); + free(replacements); + if (returned_stats) + *returned_stats = stats; + return result; +} + +ABC_NAMESPACE_HEADER_END + +#endif diff --git a/src/base/sn/snMiniAig.h b/src/base/sn/snMiniAig.h new file mode 100644 index 000000000..2698743bc --- /dev/null +++ b/src/base/sn/snMiniAig.h @@ -0,0 +1,142 @@ +/**CFile**************************************************************** + + FileName [snMiniAig.h] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [New word-level design interface.] + + Synopsis [Reconstruction of SN logic from an unmapped MiniAIG network.] + + Author [Alan Mishchenko] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: snMiniAig.h,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#ifndef ABC__base__sn__snMiniAig_h +#define ABC__base__sn__snMiniAig_h + +#include "snMiniLut.h" + +ABC_NAMESPACE_HEADER_START + +static inline sn_obj_id_t sn_aig_lit_object(sn_module_t* module, Mini_Aig_t* aig, const sn_obj_id_t* objects, int lit) +{ + int variable = Mini_AigLit2Var(lit); + assert(variable >= 0 && variable < Mini_AigNodeNum(aig)); + sn_obj_id_t object = objects[variable]; + assert(object != SN_INVALID_ID); + if (!Mini_AigLitIsCompl(lit)) + return object; + if (variable == 0) + { + uint32_t one = 1; + return sn_module_add_const(module, 1, false, &one, "aig_const1"); + } + return sn_module_add_operator(module, SN_BIT_NOT, 1, false, 1, &object, "aig_inv"); +} + +// Reconstructs an unmapped combinational MiniAIG as explicit one-bit SN_BIT_AND and SN_BIT_NOT objects. The MiniAIG +// CI/CO order is matched positionally against the boundary recorded by @blast. Register endpoints are reconnected by +// the shared boundary reconstruction stage; RAM/DSP endpoints are rejected by the command until they are supported. +static inline sn_module_id_t sn_design_add_aig_module(sn_design_t* design, sn_module_id_t source_top_id, + Mini_Aig_t* aig, const sn_blast_boundary_t* boundary, + const char* module_name) +{ + assert(design && source_top_id < design->modules.size && aig && boundary && module_name); + assert(Mini_AigRegNum(aig) == 0); + assert((size_t)Mini_AigPiNum(aig) == boundary->cis.size); + assert((size_t)Mini_AigPoNum(aig) == boundary->cos.size); + + const sn_module_t* source = sn_design_get_module_const(design, source_top_id); + sn_module_id_t result_id = sn_design_add_module(design, module_name); + sn_module_t* result = sn_design_get_module(design, result_id); + sn_obj_id_t* top_inputs = (sn_obj_id_t*)malloc(sizeof(sn_obj_id_t) * source->obj_types.size); + sn_obj_id_t* objects = (sn_obj_id_t*)malloc(sizeof(sn_obj_id_t) * Mini_AigNodeNum(aig)); + sn_obj_id_t* drivers = (sn_obj_id_t*)malloc(sizeof(sn_obj_id_t) * boundary->cos.size); + sn_boundary_regs_t regs; + assert(top_inputs && objects && drivers); + for (size_t i = 0; i < source->obj_types.size; i++) + top_inputs[i] = SN_INVALID_ID; + for (int i = 0; i < Mini_AigNodeNum(aig); i++) + objects[i] = SN_INVALID_ID; + + for (size_t i = 0; i < source->type_objects[SN_PI].size; i++) + { + sn_obj_id_t old_pi = sn_vec_at(sn_obj_id_t, &source->type_objects[SN_PI], i); + top_inputs[old_pi] = sn_module_add_pi(result, sn_obj_width(source, old_pi), sn_obj_is_signed(source, old_pi), + sn_obj_name(source, old_pi)); + } + sn_boundary_regs_init(®s, design, boundary, result, top_inputs); + uint32_t zero = 0; + objects[0] = sn_module_add_const(result, 1, false, &zero, "aig_const0"); + + uint32_t ci_index = 0; + int mini_object; + Mini_AigForEachPi(aig, mini_object) + { + sn_blast_boundary_bit_t bit = sn_vec_at(sn_blast_boundary_bit_t, &boundary->cis, ci_index++); + if (bit.kind == SN_BLAST_BOUNDARY_TOP_PI) + { + assert(bit.signal.occurrence == 0 && top_inputs[bit.signal.object] != SN_INVALID_ID); + objects[mini_object] = sn_module_add_slice(result, top_inputs[bit.signal.object], (int32_t)bit.signal.bit, + (int32_t)bit.signal.bit, "aig_pi_bit"); + } + else if (bit.kind == SN_BLAST_BOUNDARY_REG_OUTPUT) + objects[mini_object] = sn_boundary_reg_output_bit(®s, bit.owner, bit.signal.bit); + else if (bit.kind == SN_BLAST_BOUNDARY_LOOP_OUTPUT) + objects[mini_object] = sn_boundary_loop_output_bit(®s, bit.owner, bit.signal.bit); + else if (bit.kind == SN_BLAST_BOUNDARY_PRIMITIVE_OUTPUT) + objects[mini_object] = sn_boundary_primitive_output_bit(®s, bit.owner, bit.port, bit.signal.bit); + else + assert(false); + } + assert(ci_index == boundary->cis.size); + + Mini_AigForEachAnd(aig, mini_object) + { + sn_obj_id_t fanins[2] = { + sn_aig_lit_object(result, aig, objects, Mini_AigNodeFanin0(aig, mini_object)), + sn_aig_lit_object(result, aig, objects, Mini_AigNodeFanin1(aig, mini_object))}; + objects[mini_object] = sn_module_add_operator(result, SN_BIT_AND, 1, false, 2, fanins, "aig_and"); + } + + uint32_t co_index = 0; + Mini_AigForEachPo(aig, mini_object) + drivers[co_index++] = sn_aig_lit_object(result, aig, objects, Mini_AigNodeFanin0(aig, mini_object)); + assert(co_index <= boundary->cos.size); + + co_index = 0; + for (size_t i = 0; i < source->type_objects[SN_PO].size; i++) + { + sn_obj_id_t old_po = sn_vec_at(sn_obj_id_t, &source->type_objects[SN_PO], i); + uint32_t width = sn_obj_width(source, old_po); + for (uint32_t bit = 0; bit < width; bit++) + { + sn_blast_boundary_bit_t endpoint = sn_vec_at(sn_blast_boundary_bit_t, &boundary->cos, co_index + bit); + assert(endpoint.kind == SN_BLAST_BOUNDARY_TOP_PO && endpoint.port == i && endpoint.signal.bit == bit); + } + sn_obj_id_t driver = sn_lut_pack_bits(result, drivers + co_index, width, "aig_po_word"); + sn_module_add_po(result, width, sn_obj_is_signed(source, old_po), sn_obj_name(source, old_po), driver); + co_index += width; + } + assert(co_index <= boundary->cos.size); + sn_boundary_regs_finish(®s, drivers); + + free(drivers); + free(objects); + free(top_inputs); + if (!sn_module_is_topo(result)) + sn_design_reorder_module_topo(design, result_id); + assert(sn_module_is_topo(sn_design_get_module_const(design, result_id))); + return result_id; +} + +ABC_NAMESPACE_HEADER_END + +#endif diff --git a/src/base/sn/snMiniGate.h b/src/base/sn/snMiniGate.h new file mode 100644 index 000000000..c9763a7cf --- /dev/null +++ b/src/base/sn/snMiniGate.h @@ -0,0 +1,178 @@ +/**CFile**************************************************************** + + FileName [snMiniGate.h] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [New word-level design interface.] + + Synopsis [Reconstruction of technology-mapped SN gates from mini-mapping data.] + + Author [Alan Mishchenko] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: snMiniGate.h,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#ifndef ABC__base__sn__snMiniGate_h +#define ABC__base__sn__snMiniGate_h + +#include "snMiniLut.h" + +ABC_NAMESPACE_HEADER_START + +typedef uint32_t (*sn_gate_id_resolver_t)(void* context, const char* gate_name); + +// Reconstructs ABC's mini-mapping array as one-bit SN_GATE objects. Mini-mapping numbers CIs first and mapped nodes +// afterward in topological order. Gate names stored at the end of the array are resolved into the current library's +// stable gate IDs; the name is also retained as the SN object name for structural Verilog emission. +static inline sn_module_id_t sn_design_add_gate_module(sn_design_t* design, sn_module_id_t source_top_id, + const int* mapping, const sn_blast_boundary_t* boundary, + sn_gate_id_resolver_t resolver, void* resolver_context, + const char* module_name) +{ + assert(design && source_top_id < design->modules.size && mapping && boundary && resolver && module_name); + uint32_t ci_count = (uint32_t)mapping[0]; + uint32_t co_count = (uint32_t)mapping[1]; + uint32_t node_count = (uint32_t)mapping[2]; + uint32_t reg_count = (uint32_t)mapping[3]; + assert(reg_count == 0 && ci_count == boundary->cis.size && co_count == boundary->cos.size); + + // Resolve all gate names before mutating the design. A changed genlib can otherwise leave a partially constructed + // module behind or turn a user-level @put error into an assertion failure. + uint32_t position = 4; + for (uint32_t i = 0; i < node_count; i++) + { + uint32_t count = (uint32_t)mapping[position++]; + position += count; + } + position += co_count; + const char* gate_name = (const char*)(mapping + position); + uint32_t* gate_ids = node_count ? (uint32_t*)malloc(sizeof(uint32_t) * node_count) : NULL; + assert(gate_ids || node_count == 0); + for (uint32_t i = 0; i < node_count; i++) + { + gate_ids[i] = resolver(resolver_context, gate_name); + if (gate_ids[i] == SN_INVALID_ID) + { + free(gate_ids); + return SN_INVALID_ID; + } + gate_name += strlen(gate_name) + 1; + } + + const sn_module_t* source = sn_design_get_module_const(design, source_top_id); + sn_module_id_t result_id = sn_design_add_module(design, module_name); + sn_module_t* result = sn_design_get_module(design, result_id); + sn_obj_id_t* top_inputs = (sn_obj_id_t*)malloc(sizeof(sn_obj_id_t) * source->obj_types.size); + sn_obj_id_t* objects = (sn_obj_id_t*)malloc(sizeof(sn_obj_id_t) * (ci_count + node_count)); + sn_boundary_regs_t regs; + assert(top_inputs && objects); + for (size_t i = 0; i < source->obj_types.size; i++) + top_inputs[i] = SN_INVALID_ID; + for (uint32_t i = 0; i < ci_count + node_count; i++) + objects[i] = SN_INVALID_ID; + + for (size_t i = 0; i < source->type_objects[SN_PI].size; i++) + { + sn_obj_id_t old_pi = sn_vec_at(sn_obj_id_t, &source->type_objects[SN_PI], i); + top_inputs[old_pi] = sn_module_add_pi(result, sn_obj_width(source, old_pi), sn_obj_is_signed(source, old_pi), + sn_obj_name(source, old_pi)); + } + sn_boundary_regs_init(®s, design, boundary, result, top_inputs); + for (uint32_t i = 0; i < ci_count; i++) + { + sn_blast_boundary_bit_t bit = sn_vec_at(sn_blast_boundary_bit_t, &boundary->cis, i); + if (bit.kind == SN_BLAST_BOUNDARY_TOP_PI) + { + assert(bit.signal.occurrence == 0 && top_inputs[bit.signal.object] != SN_INVALID_ID); + objects[i] = sn_module_add_slice(result, top_inputs[bit.signal.object], (int32_t)bit.signal.bit, + (int32_t)bit.signal.bit, "gate_pi_bit"); + } + else if (bit.kind == SN_BLAST_BOUNDARY_REG_OUTPUT) + objects[i] = sn_boundary_reg_output_bit(®s, bit.owner, bit.signal.bit); + else if (bit.kind == SN_BLAST_BOUNDARY_LOOP_OUTPUT) + objects[i] = sn_boundary_loop_output_bit(®s, bit.owner, bit.signal.bit); + else if (bit.kind == SN_BLAST_BOUNDARY_PRIMITIVE_OUTPUT) + objects[i] = sn_boundary_primitive_output_bit(®s, bit.owner, bit.port, bit.signal.bit); + else + assert(false); + } + + position = 4; + uint32_t* fanin_counts = (uint32_t*)malloc(sizeof(uint32_t) * node_count); + const uint32_t** fanin_indices = (const uint32_t**)malloc(sizeof(uint32_t*) * node_count); + assert((fanin_counts && fanin_indices) || node_count == 0); + for (uint32_t i = 0; i < node_count; i++) + { + fanin_counts[i] = (uint32_t)mapping[position++]; + fanin_indices[i] = (const uint32_t*)(mapping + position); + position += fanin_counts[i]; + } + const uint32_t* output_indices = (const uint32_t*)(mapping + position); + position += co_count; + gate_name = (const char*)(mapping + position); + + for (uint32_t i = 0; i < node_count; i++) + { + uint32_t count = fanin_counts[i]; + sn_obj_id_t* fanins = count ? (sn_obj_id_t*)malloc(sizeof(sn_obj_id_t) * count) : NULL; + assert(fanins || count == 0); + for (uint32_t k = 0; k < count; k++) + { + uint32_t fanin = fanin_indices[i][k]; + assert(fanin < ci_count + i && objects[fanin] != SN_INVALID_ID); + fanins[k] = objects[fanin]; + } + objects[ci_count + i] = sn_module_add_gate(result, count, fanins, gate_ids[i], gate_name); + free(fanins); + gate_name += strlen(gate_name) + 1; + } + + uint32_t co_index = 0; + for (size_t i = 0; i < source->type_objects[SN_PO].size; i++) + { + sn_obj_id_t old_po = sn_vec_at(sn_obj_id_t, &source->type_objects[SN_PO], i); + uint32_t width = sn_obj_width(source, old_po); + sn_obj_id_t* bits = (sn_obj_id_t*)malloc(sizeof(sn_obj_id_t) * width); + assert(bits); + for (uint32_t bit = 0; bit < width; bit++) + { + sn_blast_boundary_bit_t endpoint = sn_vec_at(sn_blast_boundary_bit_t, &boundary->cos, co_index); + assert(endpoint.kind == SN_BLAST_BOUNDARY_TOP_PO && endpoint.port == i && endpoint.signal.bit == bit); + assert(output_indices[co_index] < ci_count + node_count); + bits[bit] = objects[output_indices[co_index++]]; + } + sn_obj_id_t driver = sn_lut_pack_bits(result, bits, width, "gate_po_word"); + sn_module_add_po(result, width, sn_obj_is_signed(source, old_po), sn_obj_name(source, old_po), driver); + free(bits); + } + assert(co_index <= co_count); + sn_obj_id_t* co_drivers = (sn_obj_id_t*)malloc(sizeof(sn_obj_id_t) * co_count); + assert(co_drivers || co_count == 0); + for (uint32_t i = 0; i < co_count; i++) + { + assert(output_indices[i] < ci_count + node_count); + co_drivers[i] = objects[output_indices[i]]; + } + sn_boundary_regs_finish(®s, co_drivers); + + free(co_drivers); + free(fanin_indices); + free(fanin_counts); + free(gate_ids); + free(objects); + free(top_inputs); + if (!sn_module_is_topo(result)) + sn_design_reorder_module_topo(design, result_id); + assert(sn_module_is_topo(sn_design_get_module_const(design, result_id))); + return result_id; +} + +ABC_NAMESPACE_HEADER_END + +#endif diff --git a/src/base/sn/snMiniLut.h b/src/base/sn/snMiniLut.h new file mode 100644 index 000000000..fb20a39a8 --- /dev/null +++ b/src/base/sn/snMiniLut.h @@ -0,0 +1,406 @@ +/**CFile**************************************************************** + + FileName [snMiniLut.h] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [New word-level design interface.] + + Synopsis [Analysis and reconstruction of SN LUTs from MiniLUT networks.] + + Author [Alan Mishchenko] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: snMiniLut.h,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#ifndef SN_MINI_LUT_H +#define SN_MINI_LUT_H + +// Utilities for validating and analyzing the MiniLUT files written by ABC's +// "&write -l" command. + +#include "snBoundary.h" +#include "aig/miniaig/minilut.h" + +#include +#include +#include + +ABC_NAMESPACE_HEADER_START + +typedef struct sn_lut_stats_t +{ + uint32_t pi_count; + uint32_t po_count; + uint32_t register_count; + uint32_t lut_count; + uint32_t lut_size; + uint32_t lut_levels; + uint32_t top_output_levels; + uint32_t register_control_levels; + uint32_t memory_input_levels; + uint32_t primitive_input_levels; + uint32_t loop_input_levels; + uint32_t register_input_levels; +} sn_lut_stats_t; + +static inline uint32_t sn_lut_max_u32(uint32_t a, uint32_t b) +{ + return a > b ? a : b; +} + +static inline Mini_Lut_t* sn_lut_load(const char* file_name) +{ + if (!file_name) + return NULL; + FILE* file = fopen(file_name, "rb"); + if (!file) + return NULL; + int32_t node_count = 0, register_count = 0, lut_size = 0; + bool valid = fread(&node_count, sizeof(node_count), 1, file) == 1 && + fread(®ister_count, sizeof(register_count), 1, file) == 1 && + fread(&lut_size, sizeof(lut_size), 1, file) == 1; + uint64_t array_count = 0, truth_count = 0; + if (valid && node_count >= 2 && register_count >= 0 && register_count <= node_count && lut_size >= 2 && + lut_size <= 16) + { + array_count = (uint64_t)(uint32_t)node_count * (uint32_t)lut_size; + truth_count = (uint64_t)(uint32_t)node_count * (uint32_t)Mini_LutWordNum(lut_size); + valid = array_count <= SIZE_MAX / sizeof(int) && truth_count <= SIZE_MAX / sizeof(unsigned); + } + else + valid = false; + if (valid) + { + uint64_t payload_bytes = (array_count + truth_count) * sizeof(uint32_t); +#if defined(_WIN32) + __int64 position = _ftelli64(file); + valid = position >= 0 && _fseeki64(file, 0, SEEK_END) == 0; + __int64 end = valid ? _ftelli64(file) : -1; + valid = end >= position && (uint64_t)(end - position) == payload_bytes && + _fseeki64(file, position, SEEK_SET) == 0; +#else + long position = ftell(file); + valid = position >= 0 && fseek(file, 0, SEEK_END) == 0; + long end = valid ? ftell(file) : -1; + valid = end >= position && (uint64_t)(end - position) == payload_bytes && fseek(file, position, SEEK_SET) == 0; +#endif + } + Mini_Lut_t* lut = valid ? (Mini_Lut_t*)calloc(1, sizeof(Mini_Lut_t)) : NULL; + if (lut) + { + lut->nSize = lut->nCap = node_count; + lut->nRegs = register_count; + lut->LutSize = lut_size; + lut->pArray = (int*)malloc((size_t)array_count * sizeof(int)); + lut->pTruths = (unsigned*)malloc((size_t)truth_count * sizeof(unsigned)); + if (!lut->pArray || !lut->pTruths) + valid = false; + else + valid = fread(lut->pArray, sizeof(int), (size_t)array_count, file) == array_count && + fread(lut->pTruths, sizeof(unsigned), (size_t)truth_count, file) == truth_count && + fgetc(file) == EOF && !ferror(file); + } + if (fclose(file) != 0) + valid = false; + if (!valid || !lut) + { + if (lut) + Mini_LutStop(lut); + return NULL; + } + for (int object = 2; object < node_count; object++) + { + int* fanins = lut->pArray + (size_t)object * lut_size; + if (fanins[0] == MINI_LUT_NULL) + { + for (int i = 1; i < lut_size; i++) + valid &= fanins[i] == MINI_LUT_NULL; + continue; + } + if (fanins[0] < 0 || fanins[0] >= object) + valid = false; + else if (fanins[0] >= 2) + { + int* source = lut->pArray + (size_t)fanins[0] * lut_size; + if (source[0] != MINI_LUT_NULL && source[1] == MINI_LUT_NULL2) + valid = false; + } + if (fanins[1] == MINI_LUT_NULL2) + { + for (int i = 2; i < lut_size; i++) + valid &= fanins[i] == MINI_LUT_NULL; + continue; + } + bool padding = false; + for (int i = 0; i < lut_size; i++) + if (fanins[i] == MINI_LUT_NULL) + padding = true; + else if (padding || fanins[i] < 0 || fanins[i] >= object || fanins[i] == MINI_LUT_NULL2) + valid = false; + else if (fanins[i] >= 2) + { + int* source = lut->pArray + (size_t)fanins[i] * lut_size; + if (source[0] != MINI_LUT_NULL && source[1] == MINI_LUT_NULL2) + valid = false; + } + } + if (!valid) + { + Mini_LutStop(lut); + return NULL; + } + return lut; +} + +static inline bool sn_lut_interface_matches(Mini_Lut_t* lut, const sn_blast_boundary_t* boundary) +{ + if (!lut || !boundary) + return false; + uint32_t pi_count = 0, po_count = 0; + int object; + Mini_LutForEachPi(lut, object) + pi_count++; + Mini_LutForEachPo(lut, object) + po_count++; + uint32_t register_count = (uint32_t)Mini_LutRegNum(lut); + if (pi_count != boundary->cis.size || po_count != boundary->cos.size || + register_count != boundary->register_bits || register_count > pi_count || register_count > po_count) + return false; + for (uint32_t i = 0; i < register_count; i++) + if (sn_vec_at(sn_blast_boundary_bit_t, &boundary->cis, pi_count - register_count + i).kind != + SN_BLAST_BOUNDARY_REG_OUTPUT || + sn_vec_at(sn_blast_boundary_bit_t, &boundary->cos, po_count - register_count + i).kind != + SN_BLAST_BOUNDARY_REG_INPUT) + return false; + return true; +} + +// Validates MiniLUT topology and its positional interface against the boundary +// saved while deriving the pre-ABC MiniAIG. Levels count LUTs; constants and +// CIs have level zero. The boundary CO vector is in the same order as MiniLUT +// POs, so depths can be reported separately for top outputs, hard-block inputs, +// register controls, and register inputs. +static inline sn_lut_stats_t sn_lut_analyze(Mini_Lut_t* lut, const sn_blast_boundary_t* boundary) +{ + assert(lut && boundary); + assert(sn_lut_interface_matches(lut, boundary)); + assert(Mini_LutSize(lut) >= 2 && Mini_LutSize(lut) <= 16); + size_t object_count = (size_t)Mini_LutNodeNum(lut); + uint32_t* levels = (uint32_t*)calloc(object_count, sizeof(uint32_t)); + assert(levels); + sn_lut_stats_t stats = {0}; + stats.lut_size = (uint32_t)Mini_LutSize(lut); + + for (int object = 0; object < Mini_LutNodeNum(lut); object++) + { + if (Mini_LutNodeIsConst(lut, object)) + continue; + if (Mini_LutNodeIsPi(lut, object)) + { + stats.pi_count++; + continue; + } + if (Mini_LutNodeIsNode(lut, object)) + { + uint32_t level = 0; + int fanin, slot; + Mini_LutForEachFanin(lut, object, fanin, slot) + { + assert(fanin >= 0 && fanin < object); + level = sn_lut_max_u32(level, levels[fanin]); + } + levels[object] = level + 1; + stats.lut_levels = sn_lut_max_u32(stats.lut_levels, levels[object]); + stats.lut_count++; + continue; + } + assert(Mini_LutNodeIsPo(lut, object)); + int fanin = Mini_LutNodeFanin(lut, object, 0); + assert(fanin >= 0 && fanin < object); + levels[object] = levels[fanin]; + stats.po_count++; + } + + stats.register_count = (uint32_t)Mini_LutRegNum(lut); + + uint32_t po_index = 0; + int object; + Mini_LutForEachPo(lut, object) + { + sn_blast_boundary_kind_t kind = + sn_vec_at(sn_blast_boundary_bit_t, &boundary->cos, po_index++).kind; + uint32_t level = levels[object]; + if (kind == SN_BLAST_BOUNDARY_TOP_PO) + stats.top_output_levels = sn_lut_max_u32(stats.top_output_levels, level); + else if (kind == SN_BLAST_BOUNDARY_REG_CONTROL) + stats.register_control_levels = sn_lut_max_u32(stats.register_control_levels, level); + else if (kind == SN_BLAST_BOUNDARY_MEMORY_INPUT) + stats.memory_input_levels = sn_lut_max_u32(stats.memory_input_levels, level); + else if (kind == SN_BLAST_BOUNDARY_PRIMITIVE_INPUT) + stats.primitive_input_levels = sn_lut_max_u32(stats.primitive_input_levels, level); + else if (kind == SN_BLAST_BOUNDARY_LOOP_INPUT) + stats.loop_input_levels = sn_lut_max_u32(stats.loop_input_levels, level); + else if (kind == SN_BLAST_BOUNDARY_REG_INPUT) + stats.register_input_levels = sn_lut_max_u32(stats.register_input_levels, level); + else + assert(false); + } + assert(po_index == stats.po_count); + free(levels); + return stats; +} + +static inline sn_obj_id_t sn_lut_pack_bits(sn_module_t* module, const sn_obj_id_t* bits, uint32_t width, + const char* name) +{ + assert(width && bits); + if (width == 1) + return bits[0]; + return sn_module_add_operator(module, SN_CONCAT, width, false, width, bits, name); +} + +static inline uint64_t sn_lut_node_truth(Mini_Lut_t* lut, int object) +{ + unsigned* words = Mini_LutNodeTruth(lut, object); + return (uint64_t)words[0] | (Mini_LutWordNum(Mini_LutSize(lut)) > 1 ? (uint64_t)words[1] << 32 : 0); +} + +// Decomposes a mapped LUT wider than the physical SN_LUT6 primitive by Shannon expansion on its most-significant +// inputs. The leaves are LUT6 objects and each internal selector is another LUT3. MiniLUT and SN both use fanin 0 as +// the least-significant truth-table variable, so each cofactor is a contiguous truth-table interval. +static inline sn_obj_id_t sn_lut_add_physical_rec(sn_module_t* module, const sn_obj_id_t* fanins, + uint32_t count, const unsigned* truth, uint32_t offset) +{ + assert(module && fanins && truth && count > 0 && count <= 16); + if (count <= 6) + { + uint64_t leaf_truth = 0; + for (uint32_t bit = 0; bit < (UINT32_C(1) << count); bit++) + leaf_truth |= (uint64_t)((truth[(offset + bit) >> 5] >> ((offset + bit) & 31)) & 1) << bit; + return sn_module_add_lut(module, count, fanins, leaf_truth, "lut"); + } + uint32_t select_bit = count - 1; + sn_obj_id_t low = sn_lut_add_physical_rec(module, fanins, select_bit, truth, offset); + sn_obj_id_t high = sn_lut_add_physical_rec(module, fanins, select_bit, truth, + offset + (UINT32_C(1) << select_bit)); + sn_obj_id_t mux_fanins[3] = {fanins[select_bit], high, low}; + return sn_module_add_lut(module, 3, mux_fanins, UINT64_C(0xd8), "lut_wide_mux"); +} + +// Reconstructs the MiniLUT combinational network and its top-level/register +// boundary as a new flat SN module. Hard-block and control reconnection is +// added by subsequent reconstruction stages; this core establishes the direct +// MiniLUT-object-to-SN-object mapping and preserves MiniLUT register order. +static inline sn_module_id_t sn_design_add_lut_module(sn_design_t* design, sn_module_id_t source_top_id, + Mini_Lut_t* lut, const sn_blast_boundary_t* boundary, + const char* module_name) +{ + assert(design && source_top_id < design->modules.size && lut && boundary && module_name); + sn_lut_analyze(lut, boundary); + const sn_module_t* source = sn_design_get_module_const(design, source_top_id); + sn_module_id_t result_id = sn_design_add_module(design, module_name); + sn_module_t* result = sn_design_get_module(design, result_id); + sn_obj_id_t* top_inputs = (sn_obj_id_t*)malloc(sizeof(sn_obj_id_t) * source->obj_types.size); + sn_obj_id_t* mini_objects = (sn_obj_id_t*)malloc(sizeof(sn_obj_id_t) * Mini_LutNodeNum(lut)); + sn_obj_id_t* co_drivers = (sn_obj_id_t*)malloc(sizeof(sn_obj_id_t) * boundary->cos.size); + sn_boundary_regs_t regs; + assert(top_inputs && mini_objects && co_drivers); + for (size_t i = 0; i < source->obj_types.size; i++) + top_inputs[i] = SN_INVALID_ID; + for (int i = 0; i < Mini_LutNodeNum(lut); i++) + mini_objects[i] = SN_INVALID_ID; + + for (size_t i = 0; i < source->type_objects[SN_PI].size; i++) + { + sn_obj_id_t old_pi = sn_vec_at(sn_obj_id_t, &source->type_objects[SN_PI], i); + top_inputs[old_pi] = sn_module_add_pi(result, sn_obj_width(source, old_pi), sn_obj_is_signed(source, old_pi), + sn_obj_name(source, old_pi)); + } + sn_boundary_regs_init(®s, design, boundary, result, top_inputs); + uint32_t zero_word = 0, one_word = 1; + mini_objects[Mini_LutNodeConst0()] = sn_module_add_const(result, 1, false, &zero_word, "lut_const0"); + mini_objects[Mini_LutNodeConst1()] = sn_module_add_const(result, 1, false, &one_word, "lut_const1"); + + uint32_t ci_index = 0; + int mini_object; + Mini_LutForEachPi(lut, mini_object) + { + sn_blast_boundary_bit_t bit = sn_vec_at(sn_blast_boundary_bit_t, &boundary->cis, ci_index++); + if (bit.kind == SN_BLAST_BOUNDARY_TOP_PI) + { + assert(bit.signal.occurrence == 0 && top_inputs[bit.signal.object] != SN_INVALID_ID); + mini_objects[mini_object] = + sn_module_add_slice(result, top_inputs[bit.signal.object], (int32_t)bit.signal.bit, + (int32_t)bit.signal.bit, "lut_pi_bit"); + } + else if (bit.kind == SN_BLAST_BOUNDARY_REG_OUTPUT) + mini_objects[mini_object] = sn_boundary_reg_output_bit(®s, bit.owner, bit.signal.bit); + else if (bit.kind == SN_BLAST_BOUNDARY_LOOP_OUTPUT) + mini_objects[mini_object] = sn_boundary_loop_output_bit(®s, bit.owner, bit.signal.bit); + else if (bit.kind == SN_BLAST_BOUNDARY_PRIMITIVE_OUTPUT) + mini_objects[mini_object] = + sn_boundary_primitive_output_bit(®s, bit.owner, bit.port, bit.signal.bit); + else + assert(false); + } + assert(ci_index == boundary->cis.size); + + Mini_LutForEachNode(lut, mini_object) + { + sn_obj_id_t fanins[16]; + int fanin, slot, count = 0; + Mini_LutForEachFanin(lut, mini_object, fanin, slot) + { + assert(count < 16 && mini_objects[fanin] != SN_INVALID_ID); + fanins[count++] = mini_objects[fanin]; + } + mini_objects[mini_object] = count <= 6 + ? sn_module_add_lut(result, (uint32_t)count, fanins, + sn_lut_node_truth(lut, mini_object), "lut") + : sn_lut_add_physical_rec(result, fanins, (uint32_t)count, + Mini_LutNodeTruth(lut, mini_object), 0); + } + uint32_t co_index = 0; + Mini_LutForEachPo(lut, mini_object) + { + int fanin = Mini_LutNodeFanin(lut, mini_object, 0); + assert(mini_objects[fanin] != SN_INVALID_ID); + co_drivers[co_index++] = mini_objects[fanin]; + } + assert(co_index == boundary->cos.size); + + co_index = 0; + for (size_t i = 0; i < source->type_objects[SN_PO].size; i++) + { + sn_obj_id_t old_po = sn_vec_at(sn_obj_id_t, &source->type_objects[SN_PO], i); + uint32_t width = sn_obj_width(source, old_po); + sn_obj_id_t* bits = co_drivers + co_index; + for (uint32_t bit = 0; bit < width; bit++) + { + sn_blast_boundary_bit_t endpoint = sn_vec_at(sn_blast_boundary_bit_t, &boundary->cos, co_index + bit); + assert(endpoint.kind == SN_BLAST_BOUNDARY_TOP_PO && endpoint.port == i && endpoint.signal.bit == bit); + } + co_index += width; + sn_obj_id_t driver = sn_lut_pack_bits(result, bits, width, "lut_po_word"); + sn_module_add_po(result, width, sn_obj_is_signed(source, old_po), sn_obj_name(source, old_po), driver); + } + sn_boundary_regs_finish(®s, co_drivers); + + free(co_drivers); + free(mini_objects); + free(top_inputs); + if (!sn_module_is_topo(result)) + sn_design_reorder_module_topo(design, result_id); + assert(sn_module_is_topo(sn_design_get_module_const(design, result_id))); + return result_id; +} + +ABC_NAMESPACE_HEADER_END + +#endif diff --git a/src/base/sn/snMux.h b/src/base/sn/snMux.h new file mode 100644 index 000000000..781f7c8e3 --- /dev/null +++ b/src/base/sn/snMux.h @@ -0,0 +1,889 @@ +/**CFile**************************************************************** + + FileName [snMux.h] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [New word-level design interface.] + + Synopsis [Mux-path sharing and restructuring for word-level SN designs.] + + Author [Alan Mishchenko] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: snMux.h,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#ifndef SN_MUX_H +#define SN_MUX_H + +// Word-level mux-path sharing for register-fed SN_MUX trees and nested SN_PMUX objects. Root-to-terminal paths are +// enumerated, structurally equal LSB-first words are represented once, and their path conditions are ORed. A hold +// terminal is moved into SN_REG_ENABLE when controls are provably exclusive. General PMUX alternatives preserve SN's +// one-hot-select semantics; as for SN_PMUX itself, behavior for multi-hot selects is unspecified. Modules are +// duplicated and rewritten transactionally; hierarchy, stable module IDs, and the complete canonical register +// interface are preserved. + +#include "sn.h" + +#include +#include +#include +#include + +ABC_NAMESPACE_HEADER_START + +typedef struct sn_share_options_t +{ + uint32_t min_width; + uint32_t min_alternatives; + uint32_t min_saved_paths; +} sn_share_options_t; + +typedef struct sn_share_stats_t +{ + uint64_t modules; + uint64_t registers; + uint64_t muxes; + uint64_t paths_before; + uint64_t paths_after; +} sn_share_stats_t; + +typedef struct sn_share_step_t +{ + sn_obj_id_t select; + uint32_t bit; + bool positive; +} sn_share_step_t; + +typedef struct sn_share_path_t +{ + sn_obj_id_t term; + uint32_t step_offset; + uint32_t step_count; + uint32_t group; +} sn_share_path_t; + +enum +{ + SN_SHARE_MAX_PATHS = 1 << 20, + SN_SHARE_MAX_DEPTH = 4096, + SN_SHARE_MAX_STEPS = 1 << 24 +}; + +static inline sn_share_options_t sn_share_default_options(void) +{ + sn_share_options_t options = {4, 6, 2}; + return options; +} + +static inline sn_obj_id_t sn_share_strip_value(const sn_module_t* module, sn_obj_id_t object) +{ + while (object != SN_INVALID_ID) + { + sn_obj_type_t type = sn_obj_type(module, object); + if ((type != SN_BUF && type != SN_POS && type != SN_CAST) || sn_obj_fanin_count(module, object) != 1 || + sn_obj_width(module, object) != sn_obj_width(module, sn_obj_fanin(module, object, 0))) + break; + object = sn_obj_fanin(module, object, 0); + } + return object; +} + +static inline bool sn_share_const_equal(const sn_module_t* module, sn_obj_id_t a, sn_obj_id_t b) +{ + if (sn_obj_width(module, a) != sn_obj_width(module, b)) + return false; + sn_obj_type_t ta = sn_obj_type(module, a), tb = sn_obj_type(module, b); + if ((ta != SN_CONST0 && ta != SN_CONST1 && ta != SN_CONST) || + (tb != SN_CONST0 && tb != SN_CONST1 && tb != SN_CONST)) + return false; + uint32_t width = sn_obj_width(module, a); + for (uint32_t bit = 0; bit < width; bit++) + { + bool av = (ta == SN_CONST1 && bit == 0) || + (ta == SN_CONST && ((sn_const_words(module, a)[bit >> 5] >> (bit & 31)) & 1)); + bool bv = (tb == SN_CONST1 && bit == 0) || + (tb == SN_CONST && ((sn_const_words(module, b)[bit >> 5] >> (bit & 31)) & 1)); + if (av != bv) + return false; + } + return true; +} + +// Returns the unique raw selector value accepted by an equality comparison, +// accounting for the comparison's signed extension. A wider constant whose +// high bits cannot equal the extended selector makes the predicate impossible. +static inline bool sn_share_decode_value(const sn_module_t* module, sn_obj_id_t value, + sn_obj_id_t constant, uint32_t* decoded) +{ + uint32_t value_width = sn_obj_width(module, value); + uint32_t constant_width = sn_obj_width(module, constant); + bool sign = sn_obj_is_signed(module, value) && sn_obj_is_signed(module, constant); + if (!value_width || value_width >= 31) + return false; + uint32_t result = 0; + for (uint32_t bit = 0; bit < value_width; bit++) + { + bool constant_bit = bit < constant_width ? sn_const_bit(module, constant, bit) + : sign && sn_const_bit(module, constant, constant_width - 1); + result |= (uint32_t)constant_bit << bit; + } + if (constant_width > value_width) + { + bool extension = sign && ((result >> (value_width - 1)) & 1); + for (uint32_t bit = value_width; bit < constant_width; bit++) + if (sn_const_bit(module, constant, bit) != extension) + return false; + } + *decoded = result; + return true; +} + +// Structural word identity through the inexpensive wiring operators used heavily by Slang lowering. This is the +// object-level counterpart of UtilMux's canonical bit-vector IDs: separately-created slices/concatenations of the +// same LSB-first source bits are recognized as the same mux terminal without bit-blasting the module. +static inline bool sn_share_value_equal(const sn_module_t* module, sn_obj_id_t a, sn_obj_id_t b) +{ + a = sn_share_strip_value(module, a); + b = sn_share_strip_value(module, b); + if (a == b) + return true; + if (sn_obj_width(module, a) != sn_obj_width(module, b)) + return false; + sn_obj_type_t ta = sn_obj_type(module, a), tb = sn_obj_type(module, b); + if ((ta == SN_CONST0 || ta == SN_CONST1 || ta == SN_CONST) && + (tb == SN_CONST0 || tb == SN_CONST1 || tb == SN_CONST)) + return sn_share_const_equal(module, a, b); + if (ta != tb) + return false; + if (ta == SN_SLICE) + { + const sn_slice_info_t* ia = sn_obj_slice_info(module, a); + const sn_slice_info_t* ib = sn_obj_slice_info(module, b); + return ia->left_index == ib->left_index && ia->right_index == ib->right_index && + sn_share_value_equal(module, sn_obj_fanin(module, a, 0), sn_obj_fanin(module, b, 0)); + } + if (ta == SN_REPLICATE) + return sn_obj_repeat_count(module, a) == sn_obj_repeat_count(module, b) && + sn_share_value_equal(module, sn_obj_fanin(module, a, 0), sn_obj_fanin(module, b, 0)); + if (ta == SN_CONCAT && sn_obj_fanin_count(module, a) == sn_obj_fanin_count(module, b)) + { + for (uint32_t i = 0; i < sn_obj_fanin_count(module, a); i++) + if (!sn_share_value_equal(module, sn_obj_fanin(module, a, i), sn_obj_fanin(module, b, i))) + return false; + return true; + } + return false; +} + +static inline uint64_t sn_share_hash_mix(uint64_t hash, uint64_t value) +{ + hash ^= value; + return hash * UINT64_C(1099511628211); +} + +// Compute structural hashes for the inexpensive wiring words recognized by sn_share_value_equal(). Modules entering +// @opt_mux are topologically ordered, so every hashed wiring fanin is already available. Unsupported terminals retain +// object identity. Hash collisions are always resolved with the exact structural comparison. +static inline uint64_t* sn_share_value_hashes(const sn_module_t* module) +{ + uint64_t* hashes = (uint64_t*)calloc(module->obj_types.size, sizeof(uint64_t)); + assert(hashes || module->obj_types.size == 0); + for (sn_obj_id_t object = 0; object < module->obj_types.size; object++) + { + sn_obj_type_t type = sn_obj_type(module, object); + uint64_t hash = sn_share_hash_mix(UINT64_C(1469598103934665603), sn_obj_width(module, object)); + if (type == SN_BUF || type == SN_POS || type == SN_CAST) + { + sn_obj_id_t fanin = sn_obj_fanin(module, object, 0); + hashes[object] = sn_obj_width(module, object) == sn_obj_width(module, fanin) + ? hashes[fanin] : sn_share_hash_mix(hash, object); + continue; + } + if (type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST) + { + uint32_t count = sn_const_word_count(sn_obj_width(module, object)); + for (uint32_t i = 0; i < count; i++) + { + uint32_t word = type == SN_CONST ? sn_const_words(module, object)[i] + : type == SN_CONST1 && i == 0 ? 1 : 0; + if (i + 1 == count && (sn_obj_width(module, object) & 31)) + word &= (UINT32_C(1) << (sn_obj_width(module, object) & 31)) - 1; + hash = sn_share_hash_mix(hash, word); + } + hashes[object] = hash; + continue; + } + hash = sn_share_hash_mix(hash, type); + if (type == SN_SLICE) + { + const sn_slice_info_t* info = sn_obj_slice_info(module, object); + hash = sn_share_hash_mix(hash, (uint32_t)info->left_index); + hash = sn_share_hash_mix(hash, (uint32_t)info->right_index); + hash = sn_share_hash_mix(hash, hashes[sn_obj_fanin(module, object, 0)]); + } + else if (type == SN_REPLICATE) + { + hash = sn_share_hash_mix(hash, sn_obj_repeat_count(module, object)); + hash = sn_share_hash_mix(hash, hashes[sn_obj_fanin(module, object, 0)]); + } + else if (type == SN_CONCAT) + for (uint32_t i = 0; i < sn_obj_fanin_count(module, object); i++) + hash = sn_share_hash_mix(hash, hashes[sn_obj_fanin(module, object, i)]); + else + hash = sn_share_hash_mix(hash, object); + hashes[object] = hash; + } + return hashes; +} + +static inline bool sn_share_hashed_equal(const sn_module_t* module, const uint64_t* hashes, + sn_obj_id_t a, sn_obj_id_t b) +{ + a = sn_share_strip_value(module, a); + b = sn_share_strip_value(module, b); + return hashes[a] == hashes[b] && sn_share_value_equal(module, a, b); +} + +// Recognize a binary decode. The equality predicates compare one common selector against distinct constants, so at +// most one PMUX select bit is true and ordinary combinational CEC is valid. An incomplete decode uses the PMUX default. +static inline bool sn_share_select_is_decode(const sn_module_t* module, sn_obj_id_t select) +{ + if (sn_obj_type(module, select) != SN_CONCAT || sn_obj_fanin_count(module, select) < 2) + return false; + sn_obj_id_t common = SN_INVALID_ID; + sn_vec_t decoded_values; + sn_vec_init(&decoded_values); + for (uint32_t i = 0; i < sn_obj_fanin_count(module, select); i++) + { + sn_obj_id_t compare = sn_obj_fanin(module, select, i); + sn_obj_type_t type = sn_obj_type(module, compare); + if ((type != SN_EQ && type != SN_CASE_EQ) || sn_obj_fanin_count(module, compare) != 2) + { + sn_vec_destroy(&decoded_values); + return false; + } + sn_obj_id_t value = sn_obj_fanin(module, compare, 0), constant = sn_obj_fanin(module, compare, 1); + sn_obj_type_t constant_type = sn_obj_type(module, constant); + if (constant_type != SN_CONST0 && constant_type != SN_CONST1 && constant_type != SN_CONST) + { + sn_vec_destroy(&decoded_values); + return false; + } + if (common == SN_INVALID_ID) + common = value; + else if (sn_share_strip_value(module, value) != sn_share_strip_value(module, common) || + sn_obj_width(module, value) != sn_obj_width(module, common) || + sn_obj_is_signed(module, value) != sn_obj_is_signed(module, common)) + { + sn_vec_destroy(&decoded_values); + return false; + } + uint32_t decoded; + if (!sn_share_decode_value(module, value, constant, &decoded)) + continue; + for (size_t j = 0; j < decoded_values.size; j++) + if (sn_vec_at(uint32_t, &decoded_values, j) == decoded) + { + sn_vec_destroy(&decoded_values); + return false; + } + *sn_vec_push(uint32_t, &decoded_values) = decoded; + } + uint32_t width = sn_obj_width(module, common); + bool result = width < 31 && sn_obj_fanin_count(module, select) <= (UINT32_C(1) << width); + sn_vec_destroy(&decoded_values); + return result; +} + +static inline bool sn_share_pmux_words(const sn_module_t* module, sn_obj_id_t pmux, sn_vec_t* words) +{ + assert(sn_obj_type(module, pmux) == SN_PMUX); + sn_obj_id_t select = sn_obj_fanin(module, pmux, SN_PMUX_SELECT); + sn_obj_id_t packed = sn_obj_fanin(module, pmux, SN_PMUX_ALTERNATIVES); + uint32_t count = sn_obj_width(module, select), width = sn_obj_width(module, pmux); + sn_vec_init(words); + if (sn_obj_type(module, packed) != SN_CONCAT || sn_obj_fanin_count(module, packed) != count) + return false; + sn_vec_reserve(sn_obj_id_t, words, count); + for (uint32_t i = 0; i < count; i++) + { + sn_obj_id_t word = sn_obj_fanin(module, packed, i); + if (sn_obj_width(module, word) != width) + { + sn_vec_destroy(words); + sn_vec_init(words); + return false; + } + *sn_vec_push(sn_obj_id_t, words) = sn_share_strip_value(module, word); + } + return true; +} + +static inline sn_obj_id_t sn_share_or(sn_module_t* module, const sn_obj_id_t* values, uint32_t count) +{ + assert(count); + if (count == 1) + return values[0]; + sn_vec_t level, next; + sn_vec_init(&level); + sn_vec_init(&next); + sn_vec_reserve(sn_obj_id_t, &level, count); + for (uint32_t i = 0; i < count; i++) + *sn_vec_push(sn_obj_id_t, &level) = values[i]; + while (level.size > 1) + { + next.size = 0; + for (size_t i = 0; i < level.size; i += 2) + { + if (i + 1 == level.size) + *sn_vec_push(sn_obj_id_t, &next) = sn_vec_at(sn_obj_id_t, &level, i); + else + { + sn_obj_id_t fanins[2] = {sn_vec_at(sn_obj_id_t, &level, i), + sn_vec_at(sn_obj_id_t, &level, i + 1)}; + *sn_vec_push(sn_obj_id_t, &next) = + sn_module_add_operator(module, SN_BIT_OR, 1, false, 2, fanins, NULL); + } + } + sn_vec_t swap = level; + level = next; + next = swap; + } + sn_obj_id_t result = sn_vec_at(sn_obj_id_t, &level, 0); + sn_vec_destroy(&level); + sn_vec_destroy(&next); + return result; +} + +static inline sn_obj_id_t sn_share_and(sn_module_t* module, const sn_obj_id_t* values, uint32_t count) +{ + assert(count); + if (count == 1) + return values[0]; + sn_vec_t level, next; + sn_vec_init(&level); + sn_vec_init(&next); + for (uint32_t i = 0; i < count; i++) + *sn_vec_push(sn_obj_id_t, &level) = values[i]; + while (level.size > 1) + { + next.size = 0; + for (size_t i = 0; i < level.size; i += 2) + { + if (i + 1 == level.size) + *sn_vec_push(sn_obj_id_t, &next) = sn_vec_at(sn_obj_id_t, &level, i); + else + { + sn_obj_id_t fanins[2] = {sn_vec_at(sn_obj_id_t, &level, i), + sn_vec_at(sn_obj_id_t, &level, i + 1)}; + *sn_vec_push(sn_obj_id_t, &next) = + sn_module_add_operator(module, SN_BIT_AND, 1, false, 2, fanins, NULL); + } + } + sn_vec_t swap = level; + level = next; + next = swap; + } + sn_obj_id_t result = sn_vec_at(sn_obj_id_t, &level, 0); + sn_vec_destroy(&level); + sn_vec_destroy(&next); + return result; +} + +static inline void sn_share_collect_mux_paths(const sn_module_t* module, sn_obj_id_t object, sn_vec_t* stack, + sn_vec_t* steps, sn_vec_t* paths, uint8_t* active, bool* exclusive, + bool* overflow) +{ + if (*overflow) + return; + if (stack->size >= SN_SHARE_MAX_DEPTH || paths->size >= SN_SHARE_MAX_PATHS || + steps->size > SN_SHARE_MAX_STEPS - stack->size) + { + *overflow = true; + return; + } + object = sn_share_strip_value(module, object); + if (sn_obj_type(module, object) == SN_MUX && !active[object]) + { + active[object] = 1; + sn_share_step_t* step = sn_vec_push(sn_share_step_t, stack); + step->select = sn_obj_fanin(module, object, SN_MUX_SELECT); + step->bit = 0; + step->positive = true; + sn_share_collect_mux_paths(module, sn_obj_fanin(module, object, SN_MUX_SELECTED), stack, steps, paths, + active, exclusive, overflow); + sn_vec_at(sn_share_step_t, stack, stack->size - 1).positive = false; + sn_share_collect_mux_paths(module, sn_obj_fanin(module, object, SN_MUX_DEFAULT), stack, steps, paths, + active, exclusive, overflow); + stack->size--; + active[object] = 0; + return; + } + if (sn_obj_type(module, object) == SN_PMUX && !active[object]) + { + sn_obj_id_t select = sn_obj_fanin(module, object, SN_PMUX_SELECT); + sn_vec_t words; + sn_vec_init(&words); + if (sn_share_pmux_words(module, object, &words)) + { + if (!sn_share_select_is_decode(module, select)) + *exclusive = false; + active[object] = 1; + for (uint32_t i = 0; i < words.size; i++) + { + sn_share_step_t* step = sn_vec_push(sn_share_step_t, stack); + step->select = select; + step->bit = i; + step->positive = true; + sn_share_collect_mux_paths(module, sn_vec_at(sn_obj_id_t, &words, i), stack, steps, paths, active, + exclusive, overflow); + stack->size--; + } + size_t old_stack_size = stack->size; + for (uint32_t i = 0; i < words.size; i++) + { + sn_share_step_t* step = sn_vec_push(sn_share_step_t, stack); + step->select = select; + step->bit = i; + step->positive = false; + } + sn_share_collect_mux_paths(module, sn_obj_fanin(module, object, SN_PMUX_DEFAULT), stack, steps, paths, + active, exclusive, overflow); + stack->size = old_stack_size; + active[object] = 0; + sn_vec_destroy(&words); + return; + } + sn_vec_destroy(&words); + } + assert(steps->size <= UINT32_MAX && stack->size <= UINT32_MAX); + sn_share_path_t* path = sn_vec_push(sn_share_path_t, paths); + path->term = object; + path->step_offset = (uint32_t)steps->size; + path->step_count = (uint32_t)stack->size; + path->group = SN_INVALID_ID; + sn_vec_reserve(sn_share_step_t, steps, steps->size + stack->size); + for (size_t i = 0; i < stack->size; i++) + *sn_vec_push(sn_share_step_t, steps) = sn_vec_at(sn_share_step_t, stack, i); +} + +static inline sn_obj_id_t sn_share_path_condition(sn_module_t* target, const sn_module_t* source, + const sn_share_path_t* path, const sn_vec_t* steps) +{ + sn_vec_t literals; + sn_vec_init(&literals); + for (uint32_t i = 0; i < path->step_count; i++) + { + sn_share_step_t step = sn_vec_at(sn_share_step_t, steps, path->step_offset + i); + sn_obj_id_t literal = sn_obj_dup(source, step.select); + if (sn_obj_width(target, literal) != 1) + literal = sn_module_add_slice(target, literal, (int32_t)step.bit, (int32_t)step.bit, NULL); + if (!step.positive) + literal = sn_module_add_operator(target, SN_LOG_NOT, 1, false, 1, &literal, NULL); + *sn_vec_push(sn_obj_id_t, &literals) = literal; + } + sn_obj_id_t result = sn_share_and(target, sn_vec_data(sn_obj_id_t, &literals), (uint32_t)literals.size); + sn_vec_destroy(&literals); + return result; +} + +static inline bool sn_share_reg_mux_tree(sn_module_t* target, const sn_module_t* source, sn_obj_id_t old_reg, + const uint64_t* hashes, sn_share_options_t options, sn_share_stats_t* stats) +{ + sn_obj_id_t old_in = sn_obj_pair_in(source, old_reg); + sn_obj_id_t old_root = sn_obj_fanin(source, old_in, 0); + if (old_root == SN_INVALID_ID || sn_obj_type(source, sn_share_strip_value(source, old_root)) != SN_MUX) + return false; + if (sn_obj_width(source, old_reg) < options.min_width) + return false; + sn_vec_t stack, steps, paths, terms, term_hashes, term_links, data_terms, controls; + sn_obj_id_t hold = SN_INVALID_ID, data = SN_INVALID_ID, new_reg = SN_INVALID_ID; + uint32_t* term_buckets = NULL; + uint32_t term_bucket_count = 0; + size_t hold_index = 0; + bool exclusive = true, overflow = false; + sn_vec_init(&stack); + sn_vec_init(&steps); + sn_vec_init(&paths); + sn_vec_init(&terms); + sn_vec_init(&term_hashes); + sn_vec_init(&term_links); + sn_vec_init(&data_terms); + sn_vec_init(&controls); + uint8_t* active = (uint8_t*)calloc(source->obj_types.size, sizeof(uint8_t)); + assert(active); + sn_share_collect_mux_paths(source, old_root, &stack, &steps, &paths, active, &exclusive, &overflow); + free(active); + if (overflow) + goto unchanged; + if (paths.size < options.min_alternatives) + goto unchanged; + term_bucket_count = 1; + while (term_bucket_count < 2 * paths.size) + term_bucket_count <<= 1; + term_buckets = (uint32_t*)malloc((size_t)term_bucket_count * sizeof(uint32_t)); + assert(term_buckets); + memset(term_buckets, 0xff, (size_t)term_bucket_count * sizeof(uint32_t)); + for (size_t i = 0; i < paths.size; i++) + { + sn_share_path_t* path = &sn_vec_at(sn_share_path_t, &paths, i); + sn_obj_id_t term = path->term; + uint64_t term_hash = hashes[sn_share_strip_value(source, term)]; + uint32_t bucket = (uint32_t)term_hash & (term_bucket_count - 1); + uint32_t k; + for (k = term_buckets[bucket]; k != SN_INVALID_ID; k = sn_vec_at(uint32_t, &term_links, k)) + if (sn_vec_at(uint64_t, &term_hashes, k) == term_hash && + sn_share_value_equal(source, sn_vec_at(sn_obj_id_t, &terms, k), term)) + break; + if (k == SN_INVALID_ID) + { + k = (uint32_t)terms.size; + *sn_vec_push(sn_obj_id_t, &terms) = term; + *sn_vec_push(uint64_t, &term_hashes) = term_hash; + *sn_vec_push(uint32_t, &term_links) = term_buckets[bucket]; + term_buckets[bucket] = k; + } + path->group = k; + } + if (paths.size <= terms.size || paths.size - terms.size < options.min_saved_paths || paths.size < 2 * terms.size) + goto unchanged; + + hold = sn_share_strip_value(source, old_reg); + hold_index = terms.size; + if (exclusive) + for (size_t k = 0; k < terms.size; k++) + if (sn_share_hashed_equal(source, hashes, sn_vec_at(sn_obj_id_t, &terms, k), hold)) + hold_index = k; + for (size_t k = 0; k < terms.size; k++) + { + if (k == hold_index) + continue; + sn_vec_t cubes; + sn_vec_init(&cubes); + for (size_t i = 0; i < paths.size; i++) + { + sn_share_path_t* path = &sn_vec_at(sn_share_path_t, &paths, i); + if (path->group == k) + *sn_vec_push(sn_obj_id_t, &cubes) = sn_share_path_condition(target, source, path, &steps); + } + *sn_vec_push(sn_obj_id_t, &controls) = + sn_share_or(target, sn_vec_data(sn_obj_id_t, &cubes), (uint32_t)cubes.size); + *sn_vec_push(sn_obj_id_t, &data_terms) = sn_obj_dup(source, sn_vec_at(sn_obj_id_t, &terms, k)); + sn_vec_destroy(&cubes); + } + if (!data_terms.size) + { + goto unchanged; + } + data = sn_vec_at(sn_obj_id_t, &data_terms, data_terms.size - 1); + if (data_terms.size > 1) + { + sn_obj_id_t packed_select = + sn_module_add_concat(target, (uint32_t)controls.size, sn_vec_data(sn_obj_id_t, &controls), NULL); + sn_obj_id_t packed_data = + sn_module_add_concat(target, (uint32_t)data_terms.size, sn_vec_data(sn_obj_id_t, &data_terms), NULL); + data = sn_module_add_pmux(target, packed_select, packed_data, data, NULL); + } + new_reg = sn_obj_dup(source, old_reg); + sn_obj_connect(target, sn_obj_pair_in(target, new_reg), 0, data); + if (hold_index < terms.size) + { + sn_obj_id_t update = sn_share_or(target, sn_vec_data(sn_obj_id_t, &controls), (uint32_t)controls.size); + sn_obj_id_t enable = sn_obj_fanin(target, new_reg, SN_REG_ENABLE); + if (enable != SN_INVALID_ID) + { + sn_obj_id_t fanins[2] = {enable, update}; + update = sn_module_add_operator(target, SN_BIT_AND, 1, false, 2, fanins, NULL); + } + sn_reg_set_fanin(target, new_reg, SN_REG_ENABLE, update); + } + stats->registers++; + stats->muxes++; + stats->paths_before += paths.size; + stats->paths_after += terms.size; + sn_vec_destroy(&data_terms); + sn_vec_destroy(&controls); + sn_vec_destroy(&stack); + sn_vec_destroy(&steps); + sn_vec_destroy(&paths); + sn_vec_destroy(&terms); + sn_vec_destroy(&term_hashes); + sn_vec_destroy(&term_links); + free(term_buckets); + return true; + +unchanged: + sn_vec_destroy(&stack); + sn_vec_destroy(&steps); + sn_vec_destroy(&paths); + sn_vec_destroy(&terms); + sn_vec_destroy(&term_hashes); + sn_vec_destroy(&term_links); + free(term_buckets); + sn_vec_destroy(&data_terms); + sn_vec_destroy(&controls); + return false; +} + +static inline sn_obj_id_t sn_share_select_bit(sn_module_t* module, sn_obj_id_t select, uint32_t bit) +{ + if (sn_obj_width(module, select) == 1) + return select; + return sn_module_add_slice(module, select, (int32_t)bit, (int32_t)bit, NULL); +} + +static inline bool sn_share_reg_pmux(sn_module_t* target, const sn_module_t* source, sn_obj_id_t old_reg, + const uint64_t* hashes, sn_share_options_t options, sn_share_stats_t* stats) +{ + sn_obj_id_t old_in = sn_obj_pair_in(source, old_reg); + sn_obj_id_t old_root = sn_obj_fanin(source, old_in, 0); + if (old_root != SN_INVALID_ID) + old_root = sn_share_strip_value(source, old_root); + if (old_root == SN_INVALID_ID || sn_obj_type(source, old_root) != SN_PMUX) + return false; + if (sn_obj_width(source, old_reg) < options.min_width) + return false; + sn_vec_t words; + if (!sn_share_pmux_words(source, old_root, &words)) + return false; + uint32_t count = (uint32_t)words.size; + if (count < options.min_alternatives || count > UINT16_MAX) + { + sn_vec_destroy(&words); + return false; + } + sn_obj_id_t old_default = sn_share_strip_value(source, sn_obj_fanin(source, old_root, SN_PMUX_DEFAULT)); + sn_obj_id_t old_hold = sn_share_strip_value(source, old_reg); + bool extracts_hold = sn_share_select_is_decode(source, sn_obj_fanin(source, old_root, SN_PMUX_SELECT)) && + sn_share_hashed_equal(source, hashes, old_default, old_hold); + sn_vec_t unique, unique_hashes, unique_links, conditions, members; + sn_vec_init(&unique); + sn_vec_init(&unique_hashes); + sn_vec_init(&unique_links); + sn_vec_init(&conditions); + sn_vec_init(&members); + uint32_t bucket_count = 1; + while (bucket_count < 2 * count) + bucket_count <<= 1; + uint32_t* buckets = (uint32_t*)malloc((size_t)bucket_count * sizeof(uint32_t)); + assert(buckets); + memset(buckets, 0xff, (size_t)bucket_count * sizeof(uint32_t)); + for (uint32_t i = 0; i < count; i++) + { + sn_obj_id_t value = sn_vec_at(sn_obj_id_t, &words, i); + if (extracts_hold && sn_share_hashed_equal(source, hashes, value, old_hold)) + continue; + uint64_t value_hash = hashes[sn_share_strip_value(source, value)]; + uint32_t bucket = (uint32_t)value_hash & (bucket_count - 1); + uint32_t k; + for (k = buckets[bucket]; k != SN_INVALID_ID; k = sn_vec_at(uint32_t, &unique_links, k)) + if (sn_vec_at(uint64_t, &unique_hashes, k) == value_hash && + sn_share_value_equal(source, sn_vec_at(sn_obj_id_t, &unique, k), value)) + break; + if (k == SN_INVALID_ID) + { + k = (uint32_t)unique.size; + *sn_vec_push(sn_obj_id_t, &unique) = value; + *sn_vec_push(uint64_t, &unique_hashes) = value_hash; + *sn_vec_push(uint32_t, &unique_links) = buckets[bucket]; + buckets[bucket] = k; + } + assert(k <= UINT16_MAX && i <= UINT16_MAX); + *sn_vec_push(uint32_t, &members) = ((uint32_t)k << 16) | i; + } + uint32_t after = (uint32_t)unique.size; + uint32_t before = count + 1; + if (!extracts_hold) + after++; + if (!unique.size || before <= after || before - after < options.min_saved_paths || before < 2 * after) + { + sn_vec_destroy(&words); + sn_vec_destroy(&unique); + sn_vec_destroy(&unique_hashes); + sn_vec_destroy(&unique_links); + sn_vec_destroy(&conditions); + sn_vec_destroy(&members); + free(buckets); + return false; + } + + sn_obj_id_t new_reg = sn_obj_dup(source, old_reg); + sn_obj_id_t new_in = sn_obj_pair_in(target, new_reg); + sn_obj_id_t new_select = sn_obj_dup(source, sn_obj_fanin(source, old_root, SN_PMUX_SELECT)); + for (size_t k = 0; k < unique.size; k++) + { + sn_vec_t bits; + sn_vec_init(&bits); + for (size_t j = 0; j < members.size; j++) + { + uint32_t member = sn_vec_at(uint32_t, &members, j); + if ((member >> 16) == k) + *sn_vec_push(sn_obj_id_t, &bits) = sn_share_select_bit(target, new_select, member & UINT16_MAX); + } + *sn_vec_push(sn_obj_id_t, &conditions) = + sn_share_or(target, sn_vec_data(sn_obj_id_t, &bits), (uint32_t)bits.size); + sn_vec_destroy(&bits); + } + sn_obj_id_t new_data; + if (unique.size == 1) + { + sn_obj_id_t alternative = sn_obj_dup(source, sn_vec_at(sn_obj_id_t, &unique, 0)); + new_data = extracts_hold ? alternative + : sn_module_add_mux(target, sn_vec_at(sn_obj_id_t, &conditions, 0), alternative, + sn_obj_dup(source, old_default), NULL); + } + else + { + sn_vec_t alternatives; + sn_vec_init(&alternatives); + for (size_t k = 0; k < unique.size; k++) + *sn_vec_push(sn_obj_id_t, &alternatives) = sn_obj_dup(source, sn_vec_at(sn_obj_id_t, &unique, k)); + sn_obj_id_t packed_select = + sn_module_add_concat(target, (uint32_t)conditions.size, sn_vec_data(sn_obj_id_t, &conditions), NULL); + sn_obj_id_t packed_data = + sn_module_add_concat(target, (uint32_t)alternatives.size, sn_vec_data(sn_obj_id_t, &alternatives), NULL); + sn_obj_id_t default_data = extracts_hold ? sn_vec_at(sn_obj_id_t, &alternatives, alternatives.size - 1) + : sn_obj_dup(source, old_default); + new_data = sn_module_add_pmux(target, packed_select, packed_data, default_data, NULL); + sn_vec_destroy(&alternatives); + } + sn_obj_connect(target, new_in, 0, new_data); + if (extracts_hold) + { + sn_obj_id_t update = + sn_share_or(target, sn_vec_data(sn_obj_id_t, &conditions), (uint32_t)conditions.size); + sn_obj_id_t enable = sn_obj_fanin(target, new_reg, SN_REG_ENABLE); + if (enable != SN_INVALID_ID) + { + sn_obj_id_t fanins[2] = {enable, update}; + update = sn_module_add_operator(target, SN_BIT_AND, 1, false, 2, fanins, NULL); + } + sn_reg_set_fanin(target, new_reg, SN_REG_ENABLE, update); + } + stats->registers++; + stats->muxes++; + stats->paths_before += before; + stats->paths_after += after; + sn_vec_destroy(&words); + sn_vec_destroy(&unique); + sn_vec_destroy(&unique_hashes); + sn_vec_destroy(&unique_links); + sn_vec_destroy(&conditions); + sn_vec_destroy(&members); + free(buckets); + return true; +} + +static inline void sn_share_replace_module(sn_design_t* design, sn_module_id_t old_id, sn_module_id_t new_id) +{ + assert(new_id + 1 == design->modules.size && old_id != new_id); + sn_module_t* old_module = sn_design_get_module(design, old_id); + sn_module_t* new_module = sn_design_get_module(design, new_id); + sn_name_id_t temporary_name_id = new_module->name; + sn_name_id_t name = old_module->name; + bool interface_locked = old_module->interface_locked; + sn_design_invalidate_copies_to_module(design, old_id); + sn_module_destroy(old_module); + free(old_module); + new_module->id = old_id; + new_module->name = name; + new_module->interface_locked = interface_locked; + sn_vec_at(sn_module_t*, &design->modules, old_id) = new_module; + design->modules.size--; + sn_name_remove_last(&design->names, temporary_name_id); +} + +static inline bool sn_share_module_has_candidate(const sn_module_t* module, sn_share_options_t options) +{ + for (size_t i = 0; i < module->type_objects[SN_REG_OUT].size; i++) + { + sn_obj_id_t reg = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_REG_OUT], i); + if (sn_obj_width(module, reg) < options.min_width) + continue; + sn_obj_id_t root = sn_obj_fanin(module, sn_obj_pair_in(module, reg), 0); + if (root == SN_INVALID_ID) + continue; + root = sn_share_strip_value(module, root); + if (sn_obj_type(module, root) == SN_MUX) + return true; + if (sn_obj_type(module, root) == SN_PMUX) + { + sn_obj_id_t select = sn_obj_fanin(module, root, SN_PMUX_SELECT); + sn_obj_id_t alternatives = sn_obj_fanin(module, root, SN_PMUX_ALTERNATIVES); + uint32_t count = sn_obj_width(module, select); + if (count >= options.min_alternatives && + (uint64_t)count * sn_obj_width(module, root) == sn_obj_width(module, alternatives)) + return true; + } + } + return false; +} + +static inline bool sn_design_share_module(sn_design_t* design, sn_module_id_t module_id, + sn_share_options_t options, sn_share_stats_t* stats) +{ + sn_module_t* source = sn_design_get_module(design, module_id); + if (!sn_share_module_has_candidate(source, options)) + return false; + char name[96]; + uint32_t suffix = 0; + do + { + int length = snprintf(name, sizeof(name), "__sn_share_%u_%u", module_id, suffix++); + assert(length > 0 && (size_t)length < sizeof(name) && suffix != 0); + (void)length; + } while (sn_name_find(&design->names, name) != SN_INVALID_ID); + sn_module_id_t target_id = sn_design_dup_module_topo(design, module_id, name); + sn_module_t* target = sn_design_get_module(design, target_id); + uint64_t* hashes = sn_share_value_hashes(source); + bool changed = false; + for (size_t i = 0; i < source->type_objects[SN_REG_OUT].size; i++) + { + sn_obj_id_t reg = sn_vec_at(sn_obj_id_t, &source->type_objects[SN_REG_OUT], i); + bool reg_changed = sn_share_reg_mux_tree(target, source, reg, hashes, options, stats); + if (!reg_changed) + reg_changed = sn_share_reg_pmux(target, source, reg, hashes, options, stats); + changed |= reg_changed; + } + free(hashes); + if (!changed) + { + sn_name_id_t temporary_name_id = target->name; + sn_module_destroy(target); + free(target); + design->modules.size--; + sn_name_remove_last(&design->names, temporary_name_id); + sn_vec_destroy(&source->copy_ids); + sn_vec_init(&source->copy_ids); + source->copy_module = SN_INVALID_ID; + return false; + } + sn_share_replace_module(design, module_id, target_id); + // Do not use observable-cone cleanup here: even a constant or externally unobservable register is part of the + // canonical transition interface used by pre/post CEC. Reordering preserves every pair and its type ID. Dangling + // mux objects retained by this first implementation are harmless because hierarchical blasting is demand-driven. + sn_design_reorder_module_topo(design, module_id); + stats->modules++; + return true; +} + +static inline sn_share_stats_t sn_design_share(sn_design_t* design, sn_share_options_t options) +{ + assert(design && sn_design_is_topo(design)); + sn_share_stats_t stats = {0}; + size_t module_count = design->modules.size; + for (sn_module_id_t module = 0; module < module_count; module++) + sn_design_share_module(design, module, options, &stats); + assert(design->modules.size == module_count && sn_design_is_topo(design)); + return stats; +} + +ABC_NAMESPACE_HEADER_END + +#endif diff --git a/src/base/sn/snPth.h b/src/base/sn/snPth.h new file mode 100644 index 000000000..a9f237fac --- /dev/null +++ b/src/base/sn/snPth.h @@ -0,0 +1,136 @@ +/**CFile**************************************************************** + + FileName [snPth.h] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [New word-level design interface.] + + Synopsis [Bounded worker support for parallel SN mapping jobs.] + + Author [Alan Mishchenko] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: snPth.h,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#ifndef SN_PTH_H +#define SN_PTH_H + +// Small self-contained pthread scheduler for SN passes. The requested process count includes the coordinating caller, +// so P > 1 creates exactly P-1 workers. Windows and builds without ABC_USE_PTHREADS compile this scheduler as a +// sequential loop, avoiding any SN dependency on pthreads while retaining full P=1 functionality. + +#include +#include +#include + +#if defined(ABC_USE_PTHREADS) && !defined(_WIN32) +#define SN_PTH_USE_THREADS 1 +#include +#else +#define SN_PTH_USE_THREADS 0 +#endif + +ABC_NAMESPACE_HEADER_START + +typedef void (*sn_pth_job_fn)(void* context, void* job); + +typedef struct sn_pth_pool_t +{ + void** jobs; + size_t count; + size_t next; + void* context; + sn_pth_job_fn function; +#if SN_PTH_USE_THREADS + pthread_mutex_t mutex; +#endif +} sn_pth_pool_t; + +#if SN_PTH_USE_THREADS +static inline void* sn_pth_worker(void* argument) +{ + sn_pth_pool_t* pool = (sn_pth_pool_t*)argument; + for (;;) + { + size_t index; + int status = pthread_mutex_lock(&pool->mutex); + if (status != 0) + return NULL; + index = pool->next++; + status = pthread_mutex_unlock(&pool->mutex); + assert(status == 0); + (void)status; + if (index >= pool->count) + return NULL; + pool->function(pool->context, pool->jobs[index]); + } +} +#endif + +static inline int sn_pth_parallel_available(void) +{ + return SN_PTH_USE_THREADS; +} + +static inline void sn_pth_process(void** jobs, size_t count, unsigned processes, + sn_pth_job_fn function, void* context) +{ + assert((jobs || count == 0) && processes >= 1 && function); +#if !SN_PTH_USE_THREADS + (void)processes; + for (size_t i = 0; i < count; i++) + function(context, jobs[i]); +#else + if (processes == 1 || count < 2) + { + for (size_t i = 0; i < count; i++) + function(context, jobs[i]); + return; + } + unsigned worker_count = processes - 1; + if (worker_count > count) + worker_count = (unsigned)count; + sn_pth_pool_t pool; + pool.jobs = jobs; + pool.count = count; + pool.next = 0; + pool.context = context; + pool.function = function; + int status = pthread_mutex_init(&pool.mutex, NULL); + assert(status == 0); + (void)status; + pthread_t* workers = (pthread_t*)malloc(sizeof(pthread_t) * worker_count); + assert(workers); + unsigned created = 0; + for (; created < worker_count; created++) + { + if (pthread_create(&workers[created], NULL, sn_pth_worker, &pool) != 0) + break; + } + for (unsigned i = 0; i < created; i++) + { + int status = pthread_join(workers[i], NULL); + assert(status == 0); + (void)status; + } + // A worker that could not use the mutex leaves its unclaimed suffix for the coordinator. + while (pool.next < count) + function(context, jobs[pool.next++]); + status = pthread_mutex_destroy(&pool.mutex); + assert(status == 0); + (void)status; + free(workers); +#endif +} + +#undef SN_PTH_USE_THREADS + +ABC_NAMESPACE_HEADER_END + +#endif diff --git a/src/base/sn/snTech.h b/src/base/sn/snTech.h new file mode 100644 index 000000000..49d9710c7 --- /dev/null +++ b/src/base/sn/snTech.h @@ -0,0 +1,137 @@ +/**CFile**************************************************************** + + FileName [snTech.h] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [New word-level design interface.] + + Synopsis [Target-technology descriptions for SN mapping passes.] + + Author [Alan Mishchenko] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: snTech.h,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#ifndef SN_TECH_H +#define SN_TECH_H + +// Technology-independent descriptions used by the SN memory and DSP mappers. +// These describe legal primitive configurations and mapping costs; they do not +// describe device placement or the total number of resources on a die. + +#include +#include +#include + +#include "misc/util/abc_namespaces.h" + +ABC_NAMESPACE_HEADER_START + +typedef enum sn_mem_port_mode_t +{ + SN_MEM_PORT_SINGLE = 0, + SN_MEM_PORT_SIMPLE_DUAL, + SN_MEM_PORT_TRUE_DUAL +} sn_mem_port_mode_t; + +typedef enum sn_mem_read_write_mode_t +{ + SN_MEM_READ_WRITE_NO_CHANGE = 0, + SN_MEM_READ_WRITE_READ_FIRST, + SN_MEM_READ_WRITE_WRITE_FIRST +} sn_mem_read_write_mode_t; + +typedef struct sn_mem_tech_t +{ + const char* name; + uint32_t cap_bits; + uint32_t address_bits; + const uint32_t* widths; + size_t width_count; + sn_mem_port_mode_t port_mode; + uint32_t byte_width; + bool synchronous_read; + bool has_clock_enable; + bool has_byte_enable; + bool supports_init; + bool supports_read_first; + bool supports_write_first; + bool supports_no_change; + uint32_t mapping_cost; + const uint32_t* simple_dual_widths; + size_t simple_dual_width_count; +} sn_mem_tech_t; + +typedef struct sn_dsp_tech_t +{ + const char* name; + uint32_t a_width; + uint32_t b_width; + uint32_t p_width; + uint32_t preadder_width; + uint32_t min_a_width; + uint32_t min_b_width; + uint32_t min_p_width; + bool signed_only; + bool has_preadder; + bool has_postadder; + bool has_simd; + bool has_cascade; + uint32_t max_cascade_length; + uint32_t latency; + uint32_t mapping_cost; +} sn_dsp_tech_t; + +typedef struct sn_carry_tech_t +{ + const char* name; + uint32_t width; + uint32_t min_op_width; + uint32_t mapping_cost; +} sn_carry_tech_t; + +typedef struct sn_tech_t +{ + const sn_mem_tech_t* memories; + size_t memory_count; + const sn_dsp_tech_t* dsps; + size_t dsp_count; + const sn_carry_tech_t* carries; + size_t carry_count; +} sn_tech_t; + +// AMD/Xilinx UltraScale+ primitives used by the initial mapper. Width lists +// follow the legal BRAM/URAM port widths in the Yosys Xilinx memory library. +static inline sn_tech_t sn_tech_xilinx_ultrascale(void) +{ + static const uint32_t bram18_widths[] = {1, 2, 4, 9, 18}; + static const uint32_t bram36_widths[] = {1, 2, 4, 9, 18, 36}; + static const uint32_t bram18_sdp_widths[] = {1, 2, 4, 9, 18, 36}; + static const uint32_t bram36_sdp_widths[] = {1, 2, 4, 9, 18, 36, 72}; + static const uint32_t uram_widths[] = {72, 144}; + static const sn_mem_tech_t memories[] = { + {"RAMB18E2", 18u * 1024u, 14, bram18_widths, 5, SN_MEM_PORT_TRUE_DUAL, 9, true, true, true, true, true, + true, true, 129, bram18_sdp_widths, 6}, + {"RAMB36E2", 36u * 1024u, 15, bram36_widths, 6, SN_MEM_PORT_TRUE_DUAL, 9, true, true, true, true, true, + true, true, 257, bram36_sdp_widths, 7}, + {"URAM288", 288u * 1024u, 12, uram_widths, 2, SN_MEM_PORT_TRUE_DUAL, 9, true, true, true, true, false, + true, true, 1024, NULL, 0}, + }; + static const sn_dsp_tech_t dsps[] = { + {"DSP48E2", 27, 18, 48, 27, 2, 2, 9, true, true, true, true, true, 20, 0, 1}, + }; + static const sn_carry_tech_t carries[] = {{"CARRY4", 4, 3, 1}}; + sn_tech_t result = {memories, sizeof(memories) / sizeof(memories[0]), dsps, sizeof(dsps) / sizeof(dsps[0]), + carries, sizeof(carries) / sizeof(carries[0])}; + return result; +} + +ABC_NAMESPACE_HEADER_END + +#endif From 094c1ca741dd17733e92500c94fa288ff35d129a Mon Sep 17 00:00:00 2001 From: Alan Mishchenko Date: Sat, 15 Aug 2026 09:35:04 -0700 Subject: [PATCH 13/14] Fix windows build --- src/base/sn/sn.h | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/base/sn/sn.h b/src/base/sn/sn.h index 9508ae60c..43150488c 100644 --- a/src/base/sn/sn.h +++ b/src/base/sn/sn.h @@ -288,11 +288,9 @@ enum sn_obj_type_enum SN_OBJ_TYPE_COUNT }; -#ifdef __cplusplus -static_assert(SN_OBJ_TYPE_COUNT <= UINT8_MAX, "sn_obj_type_t cannot represent every object type"); -#else -_Static_assert(SN_OBJ_TYPE_COUNT <= UINT8_MAX, "sn_obj_type_t cannot represent every object type"); -#endif +// Use a negative-size array as a compile-time check because ABC is also built +// as C by MSVC, whose C frontend does not accept the C11 _Static_assert keyword. +typedef char sn_obj_type_count_must_fit_in_uint8_t[(SN_OBJ_TYPE_COUNT <= UINT8_MAX) ? 1 : -1]; typedef enum sn_mux_fanin_t { From 324081b6a43c13f8d6d128160524e999f2dad985 Mon Sep 17 00:00:00 2001 From: Alan Mishchenko Date: Sun, 16 Aug 2026 23:13:47 -0700 Subject: [PATCH 14/14] Update word-level data-structure --- src/base/sn/readme.md | 53 +++++-- src/base/sn/sn.h | 224 +++++++++++++++++++++++----- src/base/sn/snBlast.h | 7 +- src/base/sn/snBoundary.h | 155 +++++++++++++++---- src/base/sn/snCheck.h | 312 ++++++++++++++++++++++++++++++++------- src/base/sn/snCom.c | 229 ++++++++++++++++++++++------ src/base/sn/snMiniAig.h | 1 + src/base/sn/snMiniGate.h | 91 ++++++++---- src/base/sn/snMiniLut.h | 1 + src/base/sn/snMux.h | 44 +++++- src/base/sn/snPth.h | 18 ++- 11 files changed, 915 insertions(+), 220 deletions(-) diff --git a/src/base/sn/readme.md b/src/base/sn/readme.md index 06b598f49..824d666d2 100644 --- a/src/base/sn/readme.md +++ b/src/base/sn/readme.md @@ -63,14 +63,20 @@ set snslang /path/to/sn_slang `@slang` uses `sn_slang` from `PATH` unless the `snslang` setting overrides it. It accepts `-M` for the top module, repeatable `-D NAME` or `-D NAME=value` preprocessor definitions, `-F` for one additional source file, and any number of positional source files. For example, `-D WIDTH=8 -D SIGNED=1` defines two macros. `-T` is not used because ABC -conventionally reserves it for a time limit. `-v` prints the external command and frontend timing. Black-box patterns -and include-directory options remain unsupported. +conventionally reserves it for a time limit. `-v` prints the external command and frontend timing. A module declared +inside SystemVerilog `` `celldefine`` / `` `endcelldefine``, or marked by a nonzero `black_box` or `syn_black_box` +module attribute, is imported as an opaque technology primitive with its elaborated PI/PO interface; its simulation +body is not lowered. For example, both `` `celldefine`` around a module definition and +`(* syn_black_box = 1 *) module macro (...);` create an opaque leaf. An explicit zero or false attribute does not. +The declaration is still required: slang must know every port's name, direction, width, and signedness, so an +undefined-module inst remains an error. Undefined-module patterns and include-directory options remain unsupported. `@read` and `@write` provide binary persistence. `@write` selects SN or Verilog output from the `.sn`, `.v`, or `.sv` extension. `@read -M module` selects the top stored in a multi-top design; otherwise the last top is used. Before installing external binary data, `@read` validates the encoding and runs the same non-aborting structural and semantic checks as `@check`. A failed `@write` removes its incomplete output file. Every design installed in ABC is -topologically ordered. +topologically ordered. The current writer emits binary format version 6; the reader also accepts version 5 and treats +its modules as ordinary non-black-box modules because that format predates module flags. `@status` prints the current design and top names, SN revision, selected technology, hierarchy form, last extraction mode/module/revision, saved boundary hash, current GIA dimensions, and `@put` compatibility. A new `@read` or `@slang` @@ -94,12 +100,14 @@ instance/FAN ordering, hierarchy recursion, LUTs, gates, and mapped primitive in line per module. Memory, DSP, and carry mapping commands run the same checker transactionally before and after each transformation, so an invalid result is diagnosed and rejected without replacing the current design. -`@ps` prints compact statistics for the selected top module. `@ps -v` adds the hierarchy and statistics for every -module definition. Like `%ps -d`, `@ps -d` prints occurrences by object type and output/input width signature. Its -counts cover the elaborated hierarchy rooted at the selected top, including the multiplicity of repeated insts. The -hierarchical occurrence totals are accumulated over the module DAG rather than by recursively revisiting every inst, -so statistics remain practical for deeply repeated hierarchy. Memory is reported as used/allocated storage with -rounded K, M, or G suffixes. +`@ps` prints compact statistics for every module definition by default. `@ps -M module` prints the selected module +instead and uses it as the root for optional hierarchy and detailed reports. `@ps -v` adds the selected hierarchy and +keeps opaque definitions annotated with `[blackbox]`. Like `%ps -d`, `@ps -d` prints occurrences by object type and +output/input width signature. It also reports every reachable black-box type, its instance-occurrence multiplicity, +PI/PO port and bit counts, and totals for abstract AIG inputs and outputs. Counts cover the elaborated hierarchy rooted +at the selected module (or the current design top when `-M` is absent), including repeated insts. Hierarchical totals +are accumulated over the module DAG rather than by recursively revisiting every inst, so statistics remain practical +for deeply repeated hierarchy. Memory is reported as used/allocated storage with rounded K, M, or G suffixes. `@map_mem`, `@map_dsp`, and `@map_add` map into the initial AMD/Xilinx UltraScale+ technology description. Transformations are transactional and keep the original user-visible top-module name. `@map_add` replaces word-level @@ -108,6 +116,23 @@ operand inversion, extension, and final slicing remain ordinary SN logic for sub before carry mapping so future DSP preadder and postadder recognition is not hidden. `@collapse` flattens user hierarchy while retaining mapped hard-block leaf instances. +Opaque `SN_MODULE_BLACKBOX` insts are preserved by hierarchy collapse even when ordinary user hierarchy is flattened. +During `@blast`, each opaque output is an additional GIA input and each opaque input is an additional GIA output, in +natural port and LSB-first bit order. A black-box `SN_PO` has `SN_INVALID_ID` as its sole fanin, explicitly recording +that its value has no SN implementation; no zero-valued placeholder is created. `@write` emits the preserved interface +as a port-only `(* blackbox *)` module. Internally an opaque module contains only its declared `SN_PI` and `SN_PO` +objects; an `inout` is a same-named PI/PO pair. Its body and descendants are absent from SN. `@check` permits the +invalid PO fanin only for this boundary representation, and `@ps -v` / `@ps -d` expose the retained black boxes and +their reachable occurrence counts. + +`SN_CAST` is a one-fanin operator whose object width and signedness define the result type. It does not permute bits. +An equal-width cast only changes the signedness annotation; widening sign-extends a signed result and zero-extends an +unsigned result; narrowing discards high bits and retains the LSB-first low-order portion. `sn_slang` adds casts for +explicit and implicit slang conversions, `$signed` / `$unsigned`, dynamic selected-value normalization, packed-value +updates, and final normalization of `SN_MUX` data branches to the mux result width. Memory, DSP, and carry mapping may +also introduce casts while adapting word-level values to primitive interfaces. The Verilog writer uses `$signed` or +`$unsigned` on a result-width wire, and the bit-blaster implements the same extension or truncation directly. + `@opt_mux` restructures register mux cones by collecting root-to-terminal paths, grouping structurally identical LSB-first word values, and ORing the corresponding path conditions. A register-output terminal is converted into an explicit enable when the path controls are provably exclusive. The pass currently recognizes ordinary `SN_MUX` @@ -194,10 +219,12 @@ instances as well as ordinary SN logic. Mapped RAM/DSP/CARRY4 instances are reconstructed as technology leaf instances. SN loop-breaker pairs connect their output ports while the new flat module is built and are placed into a legal order by the final topological reorder. -Temporary loop pairs are pruned after reconnection unless an actual feedback dependency remains, so acyclic datapaths -do not gain artificial loop-breakers. Generic unmapped memory endpoints are recorded and abstracted by `@blast`, but -`@put` currently rejects them because the boundary does not yet retain enough per-memory-port ownership data. This -check prevents silent loss or misconnection of stateful memories. +Temporary primitive-output loop pairs are pruned after reconnection unless an actual feedback dependency remains, so +acyclic datapaths do not gain artificial loop-breakers. Explicit loop boundaries extracted from the original SN module +are reconstructed unchanged; they are not currently re-proved unnecessary after `&`-space optimization. Generic +unmapped memory endpoints are recorded and abstracted by `@blast`, but `@put` currently rejects them because the +boundary does not yet retain enough per-memory-port ownership data. This check prevents silent loss or misconnection +of stateful memories. ## Source files diff --git a/src/base/sn/sn.h b/src/base/sn/sn.h index 43150488c..415953433 100644 --- a/src/base/sn/sn.h +++ b/src/base/sn/sn.h @@ -82,6 +82,14 @@ ABC_NAMESPACE_HEADER_START // bidirectional lookup without storing another object ID. Constructors create // adjacent OUT/IN objects, but topologically reordered modules need not retain // that adjacency; the shared type ID is the authoritative pairing invariant. +// +// A module marked SN_MODULE_BLACKBOX retains only its declared PI/PO interface. +// Its implementation is intentionally opaque: hierarchy collapse preserves its +// insts, AIG construction abstracts their outputs as CIs and inputs as COs, and +// the Verilog writer emits a black-box module declaration without a body. Each +// black-box PO has SN_INVALID_ID as its sole fanin, denoting an unimplemented +// boundary value rather than an ordinary undriven net or a zero constant. An +// inout port is represented by same-named PI and PO objects. #define SN_INVALID_ID UINT32_MAX @@ -91,6 +99,14 @@ typedef uint32_t sn_name_id_t; typedef uint32_t sn_type_id_t; typedef uint16_t sn_fanin_count_t; +typedef enum sn_module_flag_t +{ + SN_MODULE_NO_FLAGS = 0, + SN_MODULE_BLACKBOX = 1u << 0 +} sn_module_flag_t; + +#define SN_MODULE_ALL_FLAGS ((uint32_t)SN_MODULE_BLACKBOX) + // A C-style generic vector. Cap and size are measured in elements. // Access macros take the element type explicitly, for example: // @@ -272,6 +288,10 @@ enum sn_obj_type_enum // least-significant result bits, and later fanins supply successively more- // significant bits. Repetition has one fanin and a type-indexed repeat count. // Slice has one fanin and type-indexed left, right, and direction data. + // Cast also has one fanin. Its object width and signedness define the result: + // widening sign-extends a signed result and zero-extends an unsigned result; + // narrowing retains the LSB-first low-order bits; equal-width conversion + // changes only the signedness annotation. SN_CONCAT, SN_REPLICATE, SN_SLICE, @@ -419,6 +439,7 @@ typedef struct sn_module_t sn_design_t* design; sn_module_id_t id; sn_name_id_t name; + uint32_t flags; // Core object attributes, all indexed by sn_obj_id_t. sn_vec_t obj_types; @@ -838,6 +859,7 @@ static inline void sn_module_init(sn_module_t* module, sn_design_t* design, sn_m module->design = design; module->id = id; module->name = name; + module->flags = SN_MODULE_NO_FLAGS; sn_vec_init(&module->obj_types); sn_vec_init(&module->width_signed); @@ -923,21 +945,27 @@ static inline void sn_design_destroy(sn_design_t* design) free(design); } +static inline sn_module_id_t sn_design_add_module_name_id(sn_design_t* design, sn_name_id_t name) +{ + assert(design); + assert(name < design->names.names.size); + assert(design->modules.size < SN_INVALID_ID); + sn_module_id_t id = (sn_module_id_t)design->modules.size; + sn_module_t* module = (sn_module_t*)calloc(1, sizeof(sn_module_t)); + assert(module); + sn_module_init(module, design, id, name); + *sn_vec_push(sn_module_t*, &design->modules) = module; + return id; +} + static inline sn_module_id_t sn_design_add_module(sn_design_t* design, const char* name) { assert(design); assert(name); - assert(design->modules.size < SN_INVALID_ID); sn_name_id_t name_id = sn_name_intern(&design->names, name); for (size_t i = 0; i < design->modules.size; i++) assert(sn_vec_at(sn_module_t*, &design->modules, i)->name != name_id); - - sn_module_id_t id = (sn_module_id_t)design->modules.size; - sn_module_t* module = (sn_module_t*)calloc(1, sizeof(sn_module_t)); - assert(module); - sn_module_init(module, design, id, name_id); - *sn_vec_push(sn_module_t*, &design->modules) = module; - return id; + return sn_design_add_module_name_id(design, name_id); } static inline sn_module_t* sn_design_get_module(sn_design_t* design, sn_module_id_t id) @@ -967,6 +995,21 @@ static inline sn_module_id_t sn_design_find_module(const sn_design_t* design, co return SN_INVALID_ID; } +static inline bool sn_module_is_blackbox(const sn_module_t* module) +{ + assert(module); + return (module->flags & SN_MODULE_BLACKBOX) != 0; +} + +static inline void sn_module_set_blackbox(sn_module_t* module, bool blackbox) +{ + assert(module); + if (blackbox) + module->flags |= SN_MODULE_BLACKBOX; + else + module->flags &= ~((uint32_t)SN_MODULE_BLACKBOX); +} + // Deep-copy the semantic design state directly in memory. Derived constant-interner tables are intentionally left // empty and rebuilt lazily, matching binary roundtrip behavior. Fanout caches and optional object-copy maps are // preserved because callers may intentionally retain them between transformations. @@ -986,6 +1029,7 @@ static inline sn_design_t* sn_design_dup(const sn_design_t* source) sn_module_id_t new_id = sn_design_add_module(target, sn_name_get(&source->names, old_module->name)); assert(new_id == module_id); sn_module_t* new_module = sn_design_get_module(target, new_id); + new_module->flags = old_module->flags; new_module->fanouts_valid = old_module->fanouts_valid; new_module->interface_locked = old_module->interface_locked; new_module->copy_module = old_module->copy_module; @@ -1224,36 +1268,46 @@ static inline void sn_obj_connect(sn_module_t* module, sn_obj_id_t object, uint3 sn_module_invalidate_fanouts(module); } -static inline void sn_obj_add_fanin(sn_module_t* module, sn_obj_id_t object, sn_obj_id_t fanin) +static inline void sn_obj_add_fanins(sn_module_t* module, sn_obj_id_t object, uint32_t added_count, + const sn_obj_id_t* added_fanins) { assert(module); assert(object < module->obj_types.size); - assert(fanin < module->obj_types.size); - assert(module->fanins.size < UINT32_MAX); + assert(added_count == 0 || added_fanins); + assert(module->fanins.size + added_count <= UINT32_MAX); + for (uint32_t i = 0; i < added_count; i++) + assert(added_fanins[i] < module->obj_types.size); + if (!added_count) + return; uint32_t offset = sn_vec_at(uint32_t, &module->fanin_offsets, object); uint32_t count = sn_obj_fanin_count(module, object); - assert(count < UINT16_MAX); + assert(added_count <= UINT16_MAX - count); size_t insertion = (size_t)offset + count; assert(insertion <= module->fanins.size); size_t old_size = module->fanins.size; - sn_vec_resize(sn_obj_id_t, &module->fanins, old_size + 1); + sn_vec_resize(sn_obj_id_t, &module->fanins, old_size + added_count); sn_obj_id_t* fanins = sn_vec_data(sn_obj_id_t, &module->fanins); - memmove(fanins + insertion + 1, fanins + insertion, (old_size - insertion) * sizeof(*fanins)); - fanins[insertion] = fanin; - sn_vec_at(sn_fanin_count_t, &module->fanin_counts, object) = (sn_fanin_count_t)(count + 1); + memmove(fanins + insertion + added_count, fanins + insertion, (old_size - insertion) * sizeof(*fanins)); + memcpy(fanins + insertion, added_fanins, (size_t)added_count * sizeof(*fanins)); + sn_vec_at(sn_fanin_count_t, &module->fanin_counts, object) = (sn_fanin_count_t)(count + added_count); // Preserve fanin-span order for every object after the modified object. for (sn_obj_id_t other = object + 1; other < module->obj_types.size; other++) { uint32_t other_offset = sn_vec_at(uint32_t, &module->fanin_offsets, other); if (other_offset >= insertion) - sn_vec_at(uint32_t, &module->fanin_offsets, other) = other_offset + 1; + sn_vec_at(uint32_t, &module->fanin_offsets, other) = other_offset + added_count; } sn_module_invalidate_fanouts(module); } +static inline void sn_obj_add_fanin(sn_module_t* module, sn_obj_id_t object, sn_obj_id_t fanin) +{ + sn_obj_add_fanins(module, object, 1, &fanin); +} + static inline sn_obj_id_t sn_module_add_pi(sn_module_t* module, uint32_t width, bool is_signed, const char* name) { assert(module); @@ -1265,6 +1319,7 @@ static inline sn_obj_id_t sn_module_add_po(sn_module_t* module, uint32_t width, sn_obj_id_t driver) { assert(module); + assert(!sn_module_is_blackbox(module)); assert(!module->interface_locked); assert(driver < module->obj_types.size); assert(sn_obj_width(module, driver) == width); @@ -1273,6 +1328,36 @@ static inline sn_obj_id_t sn_module_add_po(sn_module_t* module, uint32_t width, return output; } +// Adds an output port to an opaque module. Unlike an ordinary SN_PO, whose +// only fanin is its RTL driver, a black-box output deliberately has no driver +// inside SN. Parent insts expose it through their normal SN_INST/SN_FAN +// boundary objects; collapse preserves that boundary and blasting abstracts +// the value as a new combinational input. +static inline sn_obj_id_t sn_module_add_blackbox_po(sn_module_t* module, uint32_t width, bool is_signed, + const char* name) +{ + assert(module); + assert(sn_module_is_blackbox(module)); + assert(!module->interface_locked); + return sn_module_add_named_obj(module, SN_PO, width, is_signed, 1, name); +} + +static inline bool sn_obj_fanin_may_be_invalid(const sn_module_t* module, sn_obj_type_t type, uint32_t index) +{ + assert(module); + if (type == SN_PO) + return sn_module_is_blackbox(module) && index == 0; + if (type == SN_REG_OUT) + return index != SN_REG_DATA; + if (type == SN_MEM_OUT) + return index == SN_MEM_INIT_DATA || index == SN_MEM_INIT_MASK; + if (type == SN_MEM_READ) + return index == SN_MEM_READ_CLOCK || index == SN_MEM_READ_ENABLE; + if (type == SN_MEM_WRITE) + return index == SN_MEM_WRITE_ENABLE; + return false; +} + static inline bool sn_obj_type_is_operator(sn_obj_type_t type) { return type == SN_BUF || (type >= SN_POS && type < SN_OBJ_TYPE_COUNT); @@ -1858,9 +1943,10 @@ static inline sn_obj_id_t sn_module_add_mem_read(sn_module_t* module, sn_obj_id_ return read; } -static inline sn_obj_id_t sn_module_add_mem_write(sn_module_t* module, sn_obj_id_t mem_in, sn_obj_id_t clock, - sn_obj_id_t enable, sn_obj_id_t data, sn_obj_id_t address, - const char* name) +static inline sn_obj_id_t sn_module_add_mem_write_unlinked(sn_module_t* module, sn_obj_id_t mem_in, + sn_obj_id_t clock, sn_obj_id_t enable, + sn_obj_id_t data, sn_obj_id_t address, + const char* name) { assert(module); assert(sn_obj_type(module, mem_in) == SN_MEM_IN); @@ -1875,6 +1961,14 @@ static inline sn_obj_id_t sn_module_add_mem_write(sn_module_t* module, sn_obj_id sn_obj_connect(module, write, SN_MEM_WRITE_ENABLE, enable); sn_obj_connect(module, write, SN_MEM_WRITE_DATA, data); sn_obj_connect(module, write, SN_MEM_WRITE_ADDRESS, address); + return write; +} + +static inline sn_obj_id_t sn_module_add_mem_write(sn_module_t* module, sn_obj_id_t mem_in, sn_obj_id_t clock, + sn_obj_id_t enable, sn_obj_id_t data, sn_obj_id_t address, + const char* name) +{ + sn_obj_id_t write = sn_module_add_mem_write_unlinked(module, mem_in, clock, enable, data, address, name); sn_obj_add_fanin(module, mem_in, write); return write; } @@ -2005,6 +2099,11 @@ static inline void sn_design_print_hierarchy_rec(FILE* out, const sn_design_t* d fputs(" [recursive]\n", out); return; } + if (sn_module_is_blackbox(module)) + { + fputs(" [blackbox]\n", out); + return; + } fputc('\n', out); active_modules[module_id] = true; @@ -2533,6 +2632,7 @@ static inline sn_module_id_t sn_design_dup_module_topo(sn_design_t* design, sn_m sn_vec_t order = sn_module_topo_order(source); sn_module_id_t target_module_id = sn_design_add_module(design, new_name); sn_module_t* target = sn_design_get_module(design, target_module_id); + target->flags = source->flags; sn_vec_resize(sn_obj_id_t, &source->copy_ids, source->obj_types.size); for (size_t i = 0; i < source->copy_ids.size; i++) @@ -3054,6 +3154,7 @@ static inline sn_module_id_t sn_design_dup_module_clean_topo(sn_design_t* design sn_module_id_t target_id = sn_design_add_module(design, new_name); sn_module_t* target = sn_design_get_module(design, target_id); + target->flags = source->flags; sn_vec_resize(sn_obj_id_t, &source->copy_ids, object_count); for (size_t i = 0; i < object_count; i++) sn_vec_at(sn_obj_id_t, &source->copy_ids, i) = SN_INVALID_ID; @@ -3071,9 +3172,19 @@ static inline sn_module_id_t sn_design_dup_module_clean_topo(sn_design_t* design continue; uint32_t bits = sn_obj_width(source, old_object); uint32_t* words = (uint32_t*)calloc(sn_const_word_count(bits), sizeof(uint32_t)); + sn_name_id_t source_name_id = sn_obj_name_id(source, old_object); + const char* source_name = source_name_id == SN_INVALID_ID ? NULL : + sn_name_get(&source->design->names, source_name_id); + char* name = source_name ? (char*)malloc(strlen(source_name) + 1) : NULL; assert(words); + if (source_name) + { + assert(name); + memcpy(name, source_name, strlen(source_name) + 1); + } sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object) = - sn_module_add_const(target, bits, sn_obj_is_signed(source, old_object), words, NULL); + sn_module_add_const(target, bits, sn_obj_is_signed(source, old_object), words, name); + free(name); free(words); } for (size_t i = input_count; i < order.size; i++) @@ -3179,6 +3290,8 @@ typedef struct sn_collapse_context_t static inline bool sn_module_is_technology_primitive(const sn_module_t* module) { assert(module); + if (sn_module_is_blackbox(module)) + return true; const char* name = sn_name_get(&module->design->names, module->name); return strncmp(name, "__sn_RAM", 8) == 0 || strncmp(name, "__sn_URAM", 9) == 0 || strncmp(name, "__sn_DSP", 8) == 0 || strncmp(name, "__sn_CARRY", 10) == 0; @@ -3187,15 +3300,15 @@ static inline bool sn_module_is_technology_primitive(const sn_module_t* module) static inline bool sn_collapse_preserves_object(const sn_collapse_context_t* context, const sn_module_t* source, sn_obj_id_t object) { - if (!context->preserve_technology_primitives) - return false; sn_obj_type_t type = sn_obj_type(source, object); sn_obj_id_t inst = type == SN_INST ? object : type == SN_FAN ? sn_fan_inst_id(source, object) : SN_INVALID_ID; if (inst == SN_INVALID_ID) return false; - return sn_module_is_technology_primitive( - sn_design_get_module_const(context->design, sn_inst_module_id(source, inst))); + const sn_module_t* child = sn_design_get_module_const(context->design, sn_inst_module_id(source, inst)); + if (sn_module_is_blackbox(child)) + return true; + return context->preserve_technology_primitives && sn_module_is_technology_primitive(child); } static inline bool sn_collapse_obj_is_copied(sn_obj_type_t type, bool is_top) @@ -3427,11 +3540,9 @@ static inline sn_module_id_t sn_design_collapse_module_internal(sn_design_t* des sn_module_rebuild_pair_type_ids(flat, SN_MEM_OUT, SN_MEM_IN, SN_MEM_STATE); sn_module_rebuild_pair_type_ids(flat, SN_LOOP_OUT, SN_LOOP_IN, 0); - if (!preserve_technology_primitives) - { - assert(flat->type_objects[SN_INST].size == 0); - assert(flat->type_objects[SN_FAN].size == 0); - } + for (size_t i = 0; i < flat->inst_modules.size; i++) + assert(sn_module_is_technology_primitive( + sn_design_get_module_const(design, sn_vec_at(sn_module_id_t, &flat->inst_modules, i)))); assert(sn_module_is_topo(flat)); return flat_module_id; } @@ -4270,6 +4381,8 @@ static inline void sn_module_write_verilog_as(FILE* out, const sn_module_t* modu write_memories[write_id] = memory; } } + if (sn_module_is_blackbox(module)) + fputs("(* blackbox *) ", out); fputs("module ", out); sn_write_verilog_identifier(out, emitted_name); fputs(" (", out); @@ -4314,6 +4427,13 @@ static inline void sn_module_write_verilog_as(FILE* out, const sn_module_t* modu fputc(10, out); } + if (sn_module_is_blackbox(module)) + { + fputs("endmodule\n\n", out); + free(write_memories); + return; + } + for (sn_obj_id_t object = 0; object < module->obj_types.size; object++) { sn_obj_type_t type = sn_obj_type(module, object); @@ -4527,7 +4647,8 @@ static inline void sn_design_write_verilog_file(const sn_design_t* design, const // data words are unsigned 32-bit values. A format change must increment the // version below. -#define SN_BINARY_FORMAT_VERSION 5u +#define SN_BINARY_FORMAT_VERSION 6u +#define SN_BINARY_MIN_READ_VERSION 5u // The format version covers field-layout changes. This signature additionally binds every serialized object type to // its numeric value, so reordering the enum cannot silently reinterpret an otherwise same-sized binary design. @@ -4839,6 +4960,7 @@ static inline void sn_module_assert_valid(const sn_module_t* module) assert(module->id < module->design->modules.size); assert(sn_design_get_module_const(module->design, module->id) == module); assert(module->name < module->design->names.names.size); + assert((module->flags & ~SN_MODULE_ALL_FLAGS) == 0); size_t object_count = module->obj_types.size; assert(object_count < SN_INVALID_ID); @@ -4865,12 +4987,23 @@ static inline void sn_module_assert_valid(const sn_module_t* module) for (uint32_t i = 0; i < fanin_count; i++) { sn_obj_id_t fanin = sn_vec_at(sn_obj_id_t, &module->fanins, fanin_offset + i); - assert(fanin == SN_INVALID_ID || fanin < object_count); + assert(fanin < object_count || + (fanin == SN_INVALID_ID && sn_obj_fanin_may_be_invalid(module, type, i))); } expected_fanin_offset += fanin_count; } assert(expected_fanin_offset == module->fanins.size); + if (sn_module_is_blackbox(module)) + for (sn_obj_id_t object = 0; object < object_count; object++) + { + sn_obj_type_t type = sn_obj_type(module, object); + assert(type == SN_PI || type == SN_PO); + if (type == SN_PO) + assert(sn_obj_fanin_count(module, object) == 1 && + sn_obj_fanin(module, object, 0) == SN_INVALID_ID); + } + for (uint32_t type = 0; type < SN_OBJ_TYPE_COUNT; type++) for (uint32_t type_id = 0; type_id < module->type_objects[type].size; type_id++) { @@ -5048,6 +5181,7 @@ static inline void sn_design_assert_valid(const sn_design_t* design) static inline void sn_binary_write_module(sn_binary_writer_t* writer, const sn_module_t* module) { sn_binary_write_u32(writer, module->name); + sn_binary_write_u32(writer, module->flags); sn_binary_write_u32(writer, module->fanouts_valid ? 1u : 0u); sn_binary_write_u32(writer, module->interface_locked ? 1u : 0u); sn_binary_write_u32(writer, module->copy_module); @@ -5075,23 +5209,27 @@ static inline void sn_binary_write_module(sn_binary_writer_t* writer, const sn_m sn_binary_write_u32_vec(writer, &module->copy_ids); } -static inline bool sn_binary_read_module(sn_binary_reader_t* reader, sn_design_t* design, sn_module_id_t expected_id) +static inline bool sn_binary_read_module(sn_binary_reader_t* reader, sn_design_t* design, sn_module_id_t expected_id, + uint32_t version, uint8_t* module_name_seen) { sn_name_id_t name = sn_binary_read_u32(reader); + uint32_t flags = version >= 6 ? sn_binary_read_u32(reader) : SN_MODULE_NO_FLAGS; uint32_t fanouts_valid = sn_binary_read_u32(reader); uint32_t interface_locked = sn_binary_read_u32(reader); sn_module_id_t copy_module = sn_binary_read_u32(reader); - if (!reader->valid || name >= design->names.names.size || fanouts_valid > 1 || interface_locked > 1 || - sn_design_find_module(design, sn_name_get(&design->names, name)) != SN_INVALID_ID) + if (!reader->valid || name >= design->names.names.size || module_name_seen[name] || + (flags & ~SN_MODULE_ALL_FLAGS) != 0 || fanouts_valid > 1 || interface_locked > 1) { reader->valid = false; return false; } - sn_module_id_t id = sn_design_add_module(design, sn_name_get(&design->names, name)); + module_name_seen[name] = 1; + sn_module_id_t id = sn_design_add_module_name_id(design, name); assert(id == expected_id); sn_module_t* module = sn_design_get_module(design, id); assert(module->name == name); + module->flags = flags; module->fanouts_valid = fanouts_valid != 0; module->interface_locked = interface_locked != 0; module->copy_module = copy_module; @@ -5194,7 +5332,7 @@ static inline sn_design_t* sn_design_read_binary_raw_status(FILE* in, sn_binary_ status = SN_BINARY_READ_IO; else if (memcmp(magic, expected_magic, sizeof(magic)) != 0) status = SN_BINARY_READ_MAGIC; - else if (version != SN_BINARY_FORMAT_VERSION) + else if (version < SN_BINARY_MIN_READ_VERSION || version > SN_BINARY_FORMAT_VERSION) status = SN_BINARY_READ_VERSION; else if (layout_signature != sn_binary_layout_signature() || object_type_count != SN_OBJ_TYPE_COUNT || register_fanin_count != SN_REG_FANIN_COUNT || @@ -5220,7 +5358,11 @@ static inline sn_design_t* sn_design_read_binary_raw_status(FILE* in, sn_binary_ break; } char* name = (char*)malloc(length + 1); - assert(name); + if (!name) + { + reader.valid = false; + break; + } sn_binary_read_bytes(&reader, name, length); name[length] = 0; if (memchr(name, 0, length) != NULL) @@ -5241,8 +5383,12 @@ static inline sn_design_t* sn_design_read_binary_raw_status(FILE* in, sn_binary_ size_t module_count = sn_binary_read_size(&reader); if (!reader.valid || module_count >= SN_INVALID_ID || module_count > reader.remaining / 16) reader.valid = false; + uint8_t* module_name_seen = name_count ? (uint8_t*)calloc(name_count, 1) : NULL; + if (reader.valid && module_count && !module_name_seen) + reader.valid = false; for (sn_module_id_t i = 0; reader.valid && i < module_count; i++) - sn_binary_read_module(&reader, design, i); + sn_binary_read_module(&reader, design, i, version, module_name_seen); + free(module_name_seen); if (!reader.valid) { sn_design_destroy(design); diff --git a/src/base/sn/snBlast.h b/src/base/sn/snBlast.h index d9e908a33..561defa2a 100644 --- a/src/base/sn/snBlast.h +++ b/src/base/sn/snBlast.h @@ -957,6 +957,7 @@ static inline int* sn_blast_shift(sn_blast_ctx_t* ctx, sn_obj_id_t object, bool int fill = arithmetic && !left && sn_obj_is_signed(m, value_id) ? current[work_width - 1] : Mini_AigLitConst0(); uint32_t useful_stages = 0; + // SN widths are capped below 2^31, so this unsigned shift never reaches 32. while ((UINT32_C(1) << useful_stages) < work_width) useful_stages++; uint32_t stage_count = amount_width < useful_stages ? amount_width : useful_stages; @@ -1186,6 +1187,7 @@ static inline int* sn_blast_eval(sn_blast_ctx_t* ctx, sn_obj_id_t object) { uint32_t count = sn_obj_fanin_count(m, object); int inputs[6]; + assert(count <= sizeof(inputs) / sizeof(inputs[0])); for (uint32_t i = 0; i < count; i++) inputs[i] = sn_blast_eval(ctx, sn_obj_fanin(m, object, i))[0]; result = sn_blast_alloc_bits(1); @@ -1206,8 +1208,7 @@ static inline int* sn_blast_eval(sn_blast_ctx_t* ctx, sn_obj_id_t object) ctx->options.delay_comparators); result = sn_blast_alloc_bits(width); for (uint32_t i = 0; i < width; i++) - result[i] = i < work_width ? quotient_or_remainder[i] - : (signed_operands ? quotient_or_remainder[work_width - 1] : 0); + result[i] = quotient_or_remainder[i]; free(a); free(b); free(quotient_or_remainder); @@ -1475,6 +1476,8 @@ static inline bool sn_blast_hier_is_abstract_inst(const sn_blast_hier_t* hierarc { const sn_module_t* child = sn_design_get_module_const(hierarchy->design, sn_inst_module_id(module, inst)); + if (sn_module_is_blackbox(child)) + return true; const char* name = sn_name_get(&hierarchy->design->names, child->name); bool memory = strncmp(name, "__sn_RAM", 8) == 0 || strncmp(name, "__sn_URAM", 9) == 0; bool multiplier = strncmp(name, "__sn_DSP", 8) == 0; diff --git a/src/base/sn/snBoundary.h b/src/base/sn/snBoundary.h index 1d878f20d..d955a7281 100644 --- a/src/base/sn/snBoundary.h +++ b/src/base/sn/snBoundary.h @@ -488,37 +488,99 @@ static inline sn_obj_id_t sn_boundary_co_word(sn_boundary_regs_t* regs, const sn return sn_boundary_pack_bits(regs->result, co_drivers + begin, width, "boundary_word"); } -static inline bool sn_boundary_depends_on(const sn_module_t* module, sn_obj_id_t root, sn_obj_id_t dependency) +typedef struct sn_boundary_dfs_frame_t { - uint8_t* visited = (uint8_t*)calloc(module->obj_types.size, sizeof(uint8_t)); - sn_vec_t stack; - assert(visited); + sn_obj_id_t object; + uint32_t next_fanout; +} sn_boundary_dfs_frame_t; + +// Marks tentative primitive-output substitutions that create combinational feedback. All temporary pair outputs +// have already been replaced by the corresponding primitive outputs. One iterative Kosaraju traversal identifies +// the resulting strongly connected components; a substituted edge whose endpoints share a component must retain +// its loop pair. This replaces one complete cone walk per primitive output by linear whole-module graph work. +static inline void sn_boundary_mark_feedback_pairs(sn_module_t* module, const sn_obj_id_t* actual_to_pair, + uint8_t* keep) +{ + size_t object_count = module->obj_types.size; + uint8_t* visited = (uint8_t*)calloc(object_count, sizeof(uint8_t)); + uint32_t* components = object_count ? (uint32_t*)malloc(object_count * sizeof(uint32_t)) : NULL; + sn_vec_t order, stack; + assert(visited && (components || object_count == 0)); + sn_vec_init(&order); sn_vec_init(&stack); - *sn_vec_push(sn_obj_id_t, &stack) = root; - while (stack.size) + sn_vec_reserve(sn_obj_id_t, &order, object_count); + sn_module_build_fanouts(module); + + for (sn_obj_id_t start = 0; start < object_count; start++) { - sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &stack, --stack.size); - if (object == dependency) - { - sn_vec_destroy(&stack); - free(visited); - return true; - } - if (visited[object]) + if (visited[start]) continue; - visited[object] = 1; - if (sn_obj_type_is_pair_out(sn_obj_type(module, object))) - continue; - for (uint32_t i = 0; i < sn_obj_fanin_count(module, object); i++) + visited[start] = 1; + sn_boundary_dfs_frame_t* first = sn_vec_push(sn_boundary_dfs_frame_t, &stack); + first->object = start; + first->next_fanout = 0; + while (stack.size) { - sn_obj_id_t fanin = sn_obj_fanin(module, object, i); - if (fanin != SN_INVALID_ID && !visited[fanin]) - *sn_vec_push(sn_obj_id_t, &stack) = fanin; + sn_boundary_dfs_frame_t* frame = + &sn_vec_at(sn_boundary_dfs_frame_t, &stack, stack.size - 1); + uint32_t count = sn_obj_fanout_count(module, frame->object); + if (frame->next_fanout < count) + { + sn_obj_id_t fanout = sn_obj_fanout(module, frame->object, frame->next_fanout++); + if (!visited[fanout]) + { + visited[fanout] = 1; + sn_boundary_dfs_frame_t* child = sn_vec_push(sn_boundary_dfs_frame_t, &stack); + child->object = fanout; + child->next_fanout = 0; + } + continue; + } + *sn_vec_push(sn_obj_id_t, &order) = frame->object; + stack.size--; } } + + for (sn_obj_id_t object = 0; object < object_count; object++) + components[object] = UINT32_MAX; + uint32_t component_count = 0; + for (size_t i = order.size; i-- > 0;) + { + sn_obj_id_t start = sn_vec_at(sn_obj_id_t, &order, i); + if (components[start] != UINT32_MAX) + continue; + components[start] = component_count; + *sn_vec_push(sn_obj_id_t, &stack) = start; + while (stack.size) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &stack, --stack.size); + for (uint32_t k = 0; k < sn_obj_fanin_count(module, object); k++) + { + sn_obj_id_t fanin = sn_obj_fanin(module, object, k); + if (fanin != SN_INVALID_ID && components[fanin] == UINT32_MAX) + { + components[fanin] = component_count; + *sn_vec_push(sn_obj_id_t, &stack) = fanin; + } + } + } + component_count++; + } + + for (sn_obj_id_t object = 0; object < object_count; object++) + for (uint32_t i = 0; i < sn_obj_fanin_count(module, object); i++) + { + sn_obj_id_t actual = sn_obj_fanin(module, object, i); + sn_obj_id_t pair_out = actual == SN_INVALID_ID ? SN_INVALID_ID : actual_to_pair[actual]; + if (pair_out != SN_INVALID_ID && components[actual] == components[object]) + keep[pair_out] = 1; + } + + sn_module_invalidate_fanouts(module); + sn_vec_destroy(&order); sn_vec_destroy(&stack); + free(components); free(visited); - return false; } // Duplicates a module in topological order while omitting an explicitly unreferenced set of objects. This is used @@ -618,8 +680,13 @@ static inline void sn_boundary_prune_primitive_pairs(sn_boundary_regs_t* regs) sn_module_t* source = regs->result; size_t object_count = source->obj_types.size; uint8_t* remove = (uint8_t*)calloc(object_count, sizeof(uint8_t)); + uint8_t* keep = (uint8_t*)calloc(object_count, sizeof(uint8_t)); + sn_obj_id_t* replacement = object_count ? (sn_obj_id_t*)malloc(object_count * sizeof(sn_obj_id_t)) : NULL; + sn_obj_id_t* actual_to_pair = object_count ? (sn_obj_id_t*)malloc(object_count * sizeof(sn_obj_id_t)) : NULL; size_t remove_count = 0; - assert(remove); + assert(remove && keep && (replacement || object_count == 0) && (actual_to_pair || object_count == 0)); + for (sn_obj_id_t object = 0; object < object_count; object++) + replacement[object] = actual_to_pair[object] = SN_INVALID_ID; for (size_t i = 0; i < regs->boundary->primitives.size; i++) { const sn_blast_primitive_t* entry = &sn_vec_at(sn_blast_primitive_t, ®s->boundary->primitives, i); @@ -628,16 +695,39 @@ static inline void sn_boundary_prune_primitive_pairs(sn_boundary_regs_t* regs) { sn_obj_pair_t pair = regs->primitive_pairs[regs->primitive_offsets[i] + output]; sn_obj_id_t actual = sn_obj_fanin(source, pair.in, 0); - sn_obj_id_t inst = sn_obj_type(source, actual) == SN_FAN ? sn_fan_inst_id(source, actual) : actual; - if (sn_boundary_depends_on(source, inst, pair.out)) - continue; - for (size_t k = 0; k < source->fanins.size; k++) - if (sn_vec_at(sn_obj_id_t, &source->fanins, k) == pair.out) - sn_vec_at(sn_obj_id_t, &source->fanins, k) = actual; - remove[pair.out] = remove[pair.in] = 1; - remove_count += 2; + assert(replacement[pair.out] == SN_INVALID_ID && actual_to_pair[actual] == SN_INVALID_ID); + replacement[pair.out] = actual; + actual_to_pair[actual] = pair.out; } } + for (size_t i = 0; i < source->fanins.size; i++) + { + sn_obj_id_t fanin = sn_vec_at(sn_obj_id_t, &source->fanins, i); + if (fanin != SN_INVALID_ID && replacement[fanin] != SN_INVALID_ID) + sn_vec_at(sn_obj_id_t, &source->fanins, i) = replacement[fanin]; + } + sn_module_invalidate_fanouts(source); + sn_boundary_mark_feedback_pairs(source, actual_to_pair, keep); + for (sn_obj_id_t object = 0; object < object_count; object++) + for (uint32_t i = 0; i < sn_obj_fanin_count(source, object); i++) + { + sn_obj_id_t actual = sn_obj_fanin(source, object, i); + sn_obj_id_t pair_out = actual == SN_INVALID_ID ? SN_INVALID_ID : actual_to_pair[actual]; + if (pair_out != SN_INVALID_ID && keep[pair_out] && object != sn_obj_pair_in(source, pair_out)) + sn_obj_connect(source, object, i, pair_out); + } + for (sn_obj_id_t pair_out = 0; pair_out < object_count; pair_out++) + if (replacement[pair_out] != SN_INVALID_ID && !keep[pair_out]) + { + sn_obj_id_t actual = replacement[pair_out]; + sn_obj_id_t pair_in = sn_obj_pair_in(source, pair_out); + if (sn_obj_type(source, actual) == SN_FAN && sn_obj_name_id(source, actual) == SN_INVALID_ID && + sn_obj_name_id(source, pair_out) != SN_INVALID_ID) + sn_vec_at(sn_name_id_t, &source->name_ids, actual) = sn_obj_name_id(source, pair_out); + remove[pair_out] = remove[pair_in] = 1; + remove_count += 2; + } + sn_module_invalidate_fanouts(source); if (remove_count) { char name[96]; @@ -664,6 +754,9 @@ static inline void sn_boundary_prune_primitive_pairs(sn_boundary_regs_t* regs) sn_name_remove_last(®s->design->names, temporary_name); regs->result = filtered; } + free(actual_to_pair); + free(replacement); + free(keep); free(remove); } diff --git a/src/base/sn/snCheck.h b/src/base/sn/snCheck.h index bc80534a6..c559419de 100644 --- a/src/base/sn/snCheck.h +++ b/src/base/sn/snCheck.h @@ -80,6 +80,20 @@ static inline bool sn_check_const_type(sn_obj_type_t type) return type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST; } +static inline bool sn_check_name_is_emittable(const char* name) +{ + size_t i; + if (!name || !name[0]) + return false; + for (i = 0; name[i]; i++) + { + unsigned char c = (unsigned char)name[i]; + if (c <= 32 || c >= 127 || c == '\\') + return false; + } + return true; +} + static inline int sn_check_fixed_fanin_count(sn_obj_type_t type) { if (type == SN_PI || type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST) @@ -108,19 +122,6 @@ static inline int sn_check_fixed_fanin_count(sn_obj_type_t type) return -1; } -static inline bool sn_check_optional_fanin(sn_obj_type_t type, uint32_t index) -{ - if (type == SN_REG_OUT) - return index != SN_REG_DATA; - if (type == SN_MEM_OUT) - return index == SN_MEM_INIT_DATA || index == SN_MEM_INIT_MASK; - if (type == SN_MEM_READ) - return index == SN_MEM_READ_CLOCK || index == SN_MEM_READ_ENABLE; - if (type == SN_MEM_WRITE) - return index == SN_MEM_WRITE_ENABLE; - return false; -} - static inline bool sn_check_module_core(sn_check_ctx_t* ctx, const sn_module_t* module) { const sn_design_t* design = module ? module->design : NULL; @@ -136,12 +137,14 @@ static inline bool sn_check_module_core(sn_check_ctx_t* ctx, const sn_module_t* "module table does not point back to this module"); SN_CHECK(ctx, module, SN_INVALID_ID, module->name < design->names.names.size, "module name ID %u is out of range", module->name); + SN_CHECK(ctx, module, SN_INVALID_ID, (module->flags & ~SN_MODULE_ALL_FLAGS) == 0, + "module flags 0x%x contain unsupported bits", module->flags); if (module->name < design->names.names.size) { - SN_CHECK(ctx, module, SN_INVALID_ID, sn_name_get(&design->names, module->name)[0] != '\0', - "module name is empty"); const char* module_name = sn_name_get(&design->names, module->name); - if (strncmp(module_name, "__sn_", 5) == 0) + SN_CHECK(ctx, module, SN_INVALID_ID, module_name != NULL && module_name[0] != '\0', + "module name is null or empty"); + if (module_name && strncmp(module_name, "__sn_", 5) == 0) SN_CHECK(ctx, module, SN_INVALID_ID, sn_module_is_technology_primitive(module), "module uses the reserved internal prefix __sn_"); } @@ -194,9 +197,14 @@ static inline bool sn_check_module_core(sn_check_ctx_t* ctx, const sn_module_t* SN_CHECK(ctx, module, object, name_id == SN_INVALID_ID || name_id < design->names.names.size, "name ID %u is out of range", name_id); if (type_valid && (type == SN_PI || type == SN_PO || type == SN_GATE)) + { + const char* object_name = name_id < design->names.names.size + ? sn_name_get(&design->names, name_id) + : NULL; SN_CHECK(ctx, module, object, - name_id < design->names.names.size && sn_name_get(&design->names, name_id)[0] != '\0', + object_name != NULL && object_name[0] != '\0', "type %u requires a nonempty Verilog name", (unsigned)type); + } if (type_valid) { int expected = sn_check_fixed_fanin_count(type); @@ -213,7 +221,8 @@ static inline bool sn_check_module_core(sn_check_ctx_t* ctx, const sn_module_t* { sn_obj_id_t fanin = sn_vec_at(sn_obj_id_t, &module->fanins, offset + i); SN_CHECK(ctx, module, object, fanin < object_count || - (fanin == SN_INVALID_ID && type_valid && sn_check_optional_fanin(type, i)), + (fanin == SN_INVALID_ID && type_valid && + sn_obj_fanin_may_be_invalid(module, type, i)), "fanin %u has invalid object ID %u", i, fanin); } offset += count; @@ -221,6 +230,23 @@ static inline bool sn_check_module_core(sn_check_ctx_t* ctx, const sn_module_t* SN_CHECK(ctx, module, SN_INVALID_ID, offset == module->fanins.size, "fanin spans use %zu entries but storage contains %zu", offset, module->fanins.size); + if (sn_module_is_blackbox(module)) + for (sn_obj_id_t object = 0; object < object_count; object++) + { + sn_obj_type_t type = sn_vec_at(sn_obj_type_t, &module->obj_types, object); + SN_CHECK(ctx, module, object, type == SN_PI || type == SN_PO, + "black-box module contains non-port object of type %u", (unsigned)type); + if (type == SN_PO) + { + uint32_t count = sn_vec_at(sn_fanin_count_t, &module->fanin_counts, object); + uint32_t po_offset = sn_vec_at(uint32_t, &module->fanin_offsets, object); + SN_CHECK(ctx, module, object, + count == 1 && po_offset < module->fanins.size && + sn_vec_at(sn_obj_id_t, &module->fanins, po_offset) == SN_INVALID_ID, + "black-box output must have one intentionally undriven fanin"); + } + } + for (uint32_t type = 0; type < SN_OBJ_TYPE_COUNT; type++) for (size_t type_id = 0; type_id < module->type_objects[type].size; type_id++) { @@ -940,47 +966,218 @@ static inline void sn_check_topology(sn_check_ctx_t* ctx, const sn_module_t* mod SN_CHECK(ctx, module, SN_INVALID_ID, valid, "objects are not in legal SN topological order"); } +static inline bool sn_check_parse_u32(const char** cursor, uint32_t* value, char delimiter) +{ + char* end; + unsigned long parsed; + if (!cursor || !*cursor || !value || **cursor < '0' || **cursor > '9') + return false; + parsed = strtoul(*cursor, &end, 10); + if (end == *cursor || parsed > UINT32_MAX || *end != delimiter) + return false; + *value = (uint32_t)parsed; + *cursor = delimiter ? end + 1 : end; + return true; +} + +static inline bool sn_check_slice_bit(const sn_module_t* module, sn_obj_id_t object, sn_obj_id_t source, + uint32_t bit) +{ + if (object == SN_INVALID_ID || object >= module->obj_types.size || sn_obj_type(module, object) != SN_SLICE || + sn_obj_fanin(module, object, 0) != source) + return false; + const sn_slice_info_t* info = sn_obj_slice_info(module, object); + return info->left_index == (int32_t)bit && info->right_index == (int32_t)bit; +} + +static inline void sn_check_carry_primitive(sn_check_ctx_t* ctx, const sn_module_t* module) +{ + size_t pi_count = module->type_objects[SN_PI].size; + size_t po_count = module->type_objects[SN_PO].size; + const uint32_t pi_widths[] = {1, 1, 4, 4}; + SN_CHECK(ctx, module, SN_INVALID_ID, pi_count == 4 && po_count == 2, + "carry primitive interface must have 4 inputs and 2 outputs"); + for (size_t i = 0; i < pi_count && i < 4; i++) + { + sn_obj_id_t pi = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], i); + SN_CHECK(ctx, module, pi, sn_obj_width(module, pi) == pi_widths[i], + "carry primitive input %zu has the wrong width", i); + } + for (size_t i = 0; i < po_count; i++) + { + sn_obj_id_t po = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PO], i); + SN_CHECK(ctx, module, po, sn_obj_width(module, po) == 4, + "carry primitive output %zu has the wrong width", i); + } + if (pi_count != 4 || po_count != 2) + return; + sn_obj_id_t ci = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], 0); + sn_obj_id_t cyinit = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], 1); + sn_obj_id_t di = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], 2); + sn_obj_id_t s = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], 3); + sn_obj_id_t o = sn_obj_fanin(module, sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PO], 0), 0); + sn_obj_id_t co = sn_obj_fanin(module, sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PO], 1), 0); + bool packed = sn_obj_type(module, o) == SN_CONCAT && sn_obj_fanin_count(module, o) == 4 && + sn_obj_type(module, co) == SN_CONCAT && sn_obj_fanin_count(module, co) == 4; + SN_CHECK(ctx, module, SN_INVALID_ID, packed, + "carry primitive outputs must be four-bit concatenations"); + if (!packed) + return; + sn_obj_id_t first_o = sn_obj_fanin(module, o, 0); + sn_obj_id_t carry = sn_obj_type(module, first_o) == SN_BIT_XOR && sn_obj_fanin_count(module, first_o) == 2 + ? sn_obj_fanin(module, first_o, 1) + : SN_INVALID_ID; + bool initial = carry != SN_INVALID_ID && sn_obj_type(module, carry) == SN_BIT_OR && + sn_obj_fanin_count(module, carry) == 2 && + ((sn_obj_fanin(module, carry, 0) == ci && sn_obj_fanin(module, carry, 1) == cyinit) || + (sn_obj_fanin(module, carry, 0) == cyinit && sn_obj_fanin(module, carry, 1) == ci)); + SN_CHECK(ctx, module, carry, initial, "carry primitive has an invalid initial carry expression"); + for (uint32_t bit = 0; bit < 4; bit++) + { + sn_obj_id_t o_bit = sn_obj_fanin(module, o, bit); + sn_obj_id_t co_bit = sn_obj_fanin(module, co, bit); + bool o_valid = sn_obj_type(module, o_bit) == SN_BIT_XOR && sn_obj_fanin_count(module, o_bit) == 2; + bool co_valid = sn_obj_type(module, co_bit) == SN_MUX && sn_obj_fanin_count(module, co_bit) == 3; + sn_obj_id_t s_bit = o_valid ? sn_obj_fanin(module, o_bit, 0) : SN_INVALID_ID; + sn_obj_id_t di_bit = co_valid ? sn_obj_fanin(module, co_bit, SN_MUX_DEFAULT) : SN_INVALID_ID; + o_valid &= s_bit != SN_INVALID_ID && sn_obj_fanin(module, o_bit, 1) == carry && + sn_check_slice_bit(module, s_bit, s, bit); + co_valid &= s_bit != SN_INVALID_ID && di_bit != SN_INVALID_ID && + sn_obj_fanin(module, co_bit, SN_MUX_SELECT) == s_bit && + sn_obj_fanin(module, co_bit, SN_MUX_SELECTED) == carry && + sn_check_slice_bit(module, di_bit, di, bit); + SN_CHECK(ctx, module, o_bit, o_valid, "carry primitive O[%u] has invalid logic", bit); + SN_CHECK(ctx, module, co_bit, co_valid, "carry primitive CO[%u] has invalid logic", bit); + carry = co_bit; + } +} + +static inline void sn_check_dsp_primitive(sn_check_ctx_t* ctx, const sn_module_t* module, const char* name) +{ + size_t pi_count = module->type_objects[SN_PI].size; + size_t po_count = module->type_objects[SN_PO].size; + size_t mul_count = module->type_objects[SN_MUL].size; + SN_CHECK(ctx, module, SN_INVALID_ID, pi_count == 2 && po_count == 1, + "DSP primitive interface must have 2 inputs and 1 output"); + SN_CHECK(ctx, module, SN_INVALID_ID, mul_count == 1, + "DSP primitive behavioral wrapper must contain one multiplier"); + const char* shape = strstr(name, "_mul_"); + uint32_t a_width = 0, b_width = 0, y_width = 0; + bool parsed = shape != NULL; + const char* cursor = parsed ? shape + 5 : NULL; + parsed &= sn_check_parse_u32(&cursor, &a_width, '_'); + parsed &= sn_check_parse_u32(&cursor, &b_width, '_'); + parsed &= sn_check_parse_u32(&cursor, &y_width, '_'); + parsed &= cursor && cursor[0] == 's' && (cursor[1] == '0' || cursor[1] == '1') && + (cursor[2] == '0' || cursor[2] == '1') && cursor[3] == '\0'; + SN_CHECK(ctx, module, SN_INVALID_ID, parsed, "DSP primitive name does not encode a valid interface"); + if (pi_count != 2 || po_count != 1 || mul_count != 1 || !parsed) + return; + bool a_signed = cursor[1] == '1'; + bool b_signed = cursor[2] == '1'; + sn_obj_id_t a = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], 0); + sn_obj_id_t b = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], 1); + sn_obj_id_t mul = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_MUL], 0); + sn_obj_id_t po = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PO], 0); + bool valid = sn_obj_width(module, a) == a_width && sn_obj_is_signed(module, a) == a_signed && + sn_obj_width(module, b) == b_width && sn_obj_is_signed(module, b) == b_signed && + sn_obj_width(module, mul) == y_width && sn_obj_is_signed(module, mul) == (a_signed || b_signed) && + sn_obj_fanin_count(module, mul) == 2 && sn_obj_fanin(module, mul, 0) == a && + sn_obj_fanin(module, mul, 1) == b && sn_obj_width(module, po) == y_width && + sn_obj_is_signed(module, po) == (a_signed || b_signed) && sn_obj_fanin(module, po, 0) == mul; + SN_CHECK(ctx, module, SN_INVALID_ID, valid, + "DSP primitive behavior does not match its encoded interface"); +} + +static inline uint32_t sn_check_address_width(uint32_t depth) +{ + uint32_t width = 0; + for (uint32_t value = depth - 1; value; value >>= 1) + width++; + return width ? width : 1; +} + +static inline void sn_check_memory_primitive(sn_check_ctx_t* ctx, const sn_module_t* module, const char* name) +{ + const char* marker = strstr(name, "_tdp_tile_"); + bool tdp = marker != NULL; + bool legacy = false; + if (!marker) + marker = strstr(name, "_tile_"); + if (!marker) + { + marker = strstr(name, "_mem_"); + legacy = marker != NULL; + } + const char* cursor = marker ? marker + (tdp ? 10 : legacy ? 5 : 6) : NULL; + uint32_t width = 0, depth = 0; + bool parsed = marker && sn_check_parse_u32(&cursor, &width, '_') && + sn_check_parse_u32(&cursor, &depth, '\0') && cursor && *cursor == '\0' && width && depth; + SN_CHECK(ctx, module, SN_INVALID_ID, parsed, "memory primitive name does not encode valid dimensions"); + size_t pi_count = module->type_objects[SN_PI].size; + size_t po_count = module->type_objects[SN_PO].size; + size_t reads = module->type_objects[SN_MEM_READ].size; + size_t writes = module->type_objects[SN_MEM_WRITE].size; + SN_CHECK(ctx, module, SN_INVALID_ID, module->type_objects[SN_MEM_OUT].size == 1, + "memory primitive wrapper must contain one memory"); + SN_CHECK(ctx, module, SN_INVALID_ID, reads == (tdp ? 2u : 1u) && writes == (tdp ? 2u : 1u), + "memory primitive wrapper has the wrong number of read or write ports"); + SN_CHECK(ctx, module, SN_INVALID_ID, pi_count == (tdp ? 8u : 5u) && po_count == reads, + "memory primitive interface has the wrong number of ports"); + if (!parsed || module->type_objects[SN_MEM_OUT].size != 1 || reads != (tdp ? 2u : 1u) || + writes != (tdp ? 2u : 1u) || pi_count != (tdp ? 8u : 5u) || po_count != reads) + return; + sn_obj_id_t memory = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_MEM_OUT], 0); + uint32_t address_width = legacy ? 32 : sn_check_address_width(depth); + bool valid = sn_obj_width(module, memory) == width && sn_obj_mem_depth(module, memory) == depth; + for (uint32_t port = 0; port < reads; port++) + { + uint32_t base = tdp ? 4 * port : 0; + sn_obj_id_t clock = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], base); + sn_obj_id_t enable = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], base + 1); + sn_obj_id_t address = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], base + 2); + sn_obj_id_t data = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], base + 3); + sn_obj_id_t read_address = tdp ? address : sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], 4); + sn_obj_id_t write = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_MEM_WRITE], port); + sn_obj_id_t read = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_MEM_READ], port); + sn_obj_id_t po = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PO], port); + valid &= sn_obj_width(module, clock) == 1 && sn_obj_width(module, enable) == 1 && + sn_obj_width(module, address) == address_width && sn_obj_width(module, data) == width && + sn_obj_fanin(module, write, SN_MEM_WRITE_CLOCK) == clock && + sn_obj_fanin(module, write, SN_MEM_WRITE_ENABLE) == enable && + sn_obj_fanin(module, write, SN_MEM_WRITE_DATA) == data && + sn_obj_fanin(module, write, SN_MEM_WRITE_ADDRESS) == address && + sn_obj_fanin(module, read, SN_MEM_READ_MEMORY) == memory && + sn_obj_fanin(module, read, SN_MEM_READ_CLOCK) == SN_INVALID_ID && + sn_obj_fanin(module, read, SN_MEM_READ_ENABLE) == SN_INVALID_ID && + sn_obj_width(module, read_address) == address_width && + sn_obj_fanin(module, read, SN_MEM_READ_ADDRESS) == read_address && + sn_obj_width(module, po) == width && sn_obj_fanin(module, po, 0) == read; + } + SN_CHECK(ctx, module, SN_INVALID_ID, valid, + "memory primitive behavior does not match its encoded interface"); +} + static inline void sn_check_primitive(sn_check_ctx_t* ctx, const sn_module_t* module) { const char* name = sn_check_module_name(module); - size_t pi_count = module->type_objects[SN_PI].size; - size_t po_count = module->type_objects[SN_PO].size; - if (strncmp(name, "__sn_CARRY", 10) == 0) + bool carry = strncmp(name, "__sn_CARRY", 10) == 0; + bool dsp = strncmp(name, "__sn_DSP", 8) == 0; + bool memory = strncmp(name, "__sn_RAM", 8) == 0 || strncmp(name, "__sn_URAM", 9) == 0; + if (!carry && !dsp && !memory) + return; + if (sn_module_is_blackbox(module)) { - const uint32_t pi_widths[] = {1, 1, 4, 4}; - SN_CHECK(ctx, module, SN_INVALID_ID, pi_count == 4 && po_count == 2, - "carry primitive interface must have 4 inputs and 2 outputs"); - for (size_t i = 0; i < pi_count && i < 4; i++) - { - sn_obj_id_t pi = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], i); - SN_CHECK(ctx, module, pi, (sn_vec_at(uint32_t, &module->width_signed, pi) >> 1) == pi_widths[i], - "carry primitive input %zu has the wrong width", i); - } - for (size_t i = 0; i < po_count; i++) - { - sn_obj_id_t po = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PO], i); - SN_CHECK(ctx, module, po, (sn_vec_at(uint32_t, &module->width_signed, po) >> 1) == 4, - "carry primitive output %zu has the wrong width", i); - } - } - else if (strncmp(name, "__sn_DSP", 8) == 0) - { - SN_CHECK(ctx, module, SN_INVALID_ID, pi_count == 2 && po_count == 1, - "DSP primitive interface must have 2 inputs and 1 output"); - SN_CHECK(ctx, module, SN_INVALID_ID, module->type_objects[SN_MUL].size == 1, - "DSP primitive behavioral wrapper must contain one multiplier"); - } - else if (strncmp(name, "__sn_RAM", 8) == 0 || strncmp(name, "__sn_URAM", 9) == 0) - { - size_t reads = module->type_objects[SN_MEM_READ].size; - size_t writes = module->type_objects[SN_MEM_WRITE].size; - SN_CHECK(ctx, module, SN_INVALID_ID, module->type_objects[SN_MEM_OUT].size == 1, - "memory primitive wrapper must contain one memory"); - SN_CHECK(ctx, module, SN_INVALID_ID, reads >= 1 && reads <= 2 && writes >= 1 && writes <= 2, - "memory primitive wrapper must contain one or two read and write ports"); - SN_CHECK(ctx, module, SN_INVALID_ID, po_count == reads, - "memory primitive output count %zu differs from read-port count %zu", po_count, reads); + SN_CHECK(ctx, module, SN_INVALID_ID, false, + "reserved __sn_ technology primitives must have a validated behavioral body"); + return; } + if (carry) + sn_check_carry_primitive(ctx, module); + else if (dsp) + sn_check_dsp_primitive(ctx, module, name); + else + sn_check_memory_primitive(ctx, module, name); } typedef struct sn_check_hierarchy_frame_t @@ -1058,6 +1255,9 @@ static inline bool sn_design_check(const sn_design_t* design, FILE* out, bool ve { const char* name = sn_vec_at(char*, &design->names.names, i); SN_CHECK(&ctx, NULL, SN_INVALID_ID, name != NULL, "name %zu has a null string", i); + if (name) + SN_CHECK(&ctx, NULL, SN_INVALID_ID, sn_check_name_is_emittable(name), + "name %zu cannot be emitted losslessly as a Verilog identifier", i); } uint8_t* name_seen = design->names.names.size ? (uint8_t*)calloc(design->names.names.size, 1) : NULL; SN_CHECK(&ctx, NULL, SN_INVALID_ID, design->names.names.size == 0 || name_seen != NULL, @@ -1169,8 +1369,8 @@ static inline sn_design_t* sn_design_read_binary_checked(FILE* in, FILE* errors) { FILE* out = errors ? errors : stderr; if (status == SN_BINARY_READ_VERSION) - fprintf(out, "Cannot read SN binary format version %u; this build requires version %u.\n", version, - SN_BINARY_FORMAT_VERSION); + fprintf(out, "Cannot read SN binary format version %u; this build supports versions %u through %u.\n", + version, SN_BINARY_MIN_READ_VERSION, SN_BINARY_FORMAT_VERSION); else if (status == SN_BINARY_READ_MAGIC) fprintf(out, "Input is not an SN binary file.\n"); else if (status == SN_BINARY_READ_LAYOUT) diff --git a/src/base/sn/snCom.c b/src/base/sn/snCom.c index 3af193e66..5e9ead0a7 100644 --- a/src/base/sn/snCom.c +++ b/src/base/sn/snCom.c @@ -291,6 +291,9 @@ static int Sn_RunProcess( char ** ppArgs ) if ( Child == 0 ) { execvp( ppArgs[0], ppArgs ); + // execvp() returns only on failure. Release the child copy so memory checkers do not report it as leaked; + // the parent's copy is unaffected and is freed by the caller. + ABC_FREE( ppArgs ); _exit( 127 ); } if ( waitpid(Child, &Status, 0) != Child ) @@ -404,8 +407,8 @@ static int Sn_DistribEntryCompare( const void * pLeft, const void * pRight ) return (int)pL->Signs - (int)pR->Signs; } -static void Sn_DistribAdd( Sn_DistribEntry_t ** ppEntries, size_t * pnEntries, size_t * pnCap, - const sn_module_t * pModule, sn_obj_id_t Obj, uint64_t Mult ) +static int Sn_DistribAdd( Sn_DistribEntry_t ** ppEntries, size_t * pnEntries, size_t * pnCap, + const sn_module_t * pModule, sn_obj_id_t Obj, uint64_t Mult ) { Sn_DistribEntry_t Entry; uint32_t nFanins = sn_obj_fanin_count( pModule, Obj ); @@ -431,34 +434,44 @@ static void Sn_DistribAdd( Sn_DistribEntry_t ** ppEntries, size_t * pnEntries, s if ( pOld->OutWidth == Entry.OutWidth && pOld->In0Width == Entry.In0Width && pOld->In1Width == Entry.In1Width && pOld->FaninNum == Entry.FaninNum && pOld->Signs == Entry.Signs ) { - assert( UINT64_MAX - pOld->Occur >= Mult ); + if ( UINT64_MAX - pOld->Occur < Mult ) + return 0; pOld->Occur += Mult; - return; + return 1; } } if ( *pnEntries == *pnCap ) { + Sn_DistribEntry_t * pNew; + if ( *pnCap > SIZE_MAX / 2 / sizeof(Sn_DistribEntry_t) ) + return 0; *pnCap = *pnCap ? 2 * *pnCap : 8; - *ppEntries = ABC_REALLOC( Sn_DistribEntry_t, *ppEntries, *pnCap ); - assert( *ppEntries != NULL ); + pNew = ABC_REALLOC( Sn_DistribEntry_t, *ppEntries, *pnCap ); + if ( pNew == NULL ) + return 0; + *ppEntries = pNew; } Entry.Occur = Mult; (*ppEntries)[(*pnEntries)++] = Entry; + return 1; } -static void Sn_ModuleCollectDistrib( const sn_design_t * pDesign, sn_module_id_t ModuleId, uint64_t Mult, - Sn_DistribEntry_t ** ppEntries, size_t * pnEntries, size_t * pnCaps, - uint64_t * pTypeCounts ) +static int Sn_ModuleCollectDistrib( const sn_design_t * pDesign, sn_module_id_t ModuleId, uint64_t Mult, + Sn_DistribEntry_t ** ppEntries, size_t * pnEntries, size_t * pnCaps, + uint64_t * pTypeCounts ) { const sn_module_t * pModule = sn_design_get_module_const( pDesign, ModuleId ); sn_obj_id_t Obj; for ( Obj = 0; Obj < pModule->obj_types.size; Obj++ ) { sn_obj_type_t Type = sn_obj_type( pModule, Obj ); - assert( UINT64_MAX - pTypeCounts[Type] >= Mult ); + if ( UINT64_MAX - pTypeCounts[Type] < Mult ) + return 0; pTypeCounts[Type] += Mult; - Sn_DistribAdd( ppEntries + Type, pnEntries + Type, pnCaps + Type, pModule, Obj, Mult ); + if ( !Sn_DistribAdd(ppEntries + Type, pnEntries + Type, pnCaps + Type, pModule, Obj, Mult) ) + return 0; } + return 1; } typedef struct Sn_DistribFrame_t_ @@ -467,19 +480,25 @@ typedef struct Sn_DistribFrame_t_ size_t NextInst; } Sn_DistribFrame_t; -static void Sn_DesignPrintDistrib( FILE * pOut, const sn_design_t * pDesign, sn_module_id_t Top ) +// Returns the number of reachable occurrences of each module definition under Top. The hierarchy is a DAG, so a +// reverse-postorder propagation accounts for repeated insts without expanding every hierarchical occurrence. +static uint64_t * Sn_DesignCountModuleOccurrences( const sn_design_t * pDesign, sn_module_id_t Top ) { - Sn_DistribEntry_t * pEntries[SN_OBJ_TYPE_COUNT] = { NULL }; - size_t nEntries[SN_OBJ_TYPE_COUNT] = { 0 }; - size_t nCaps[SN_OBJ_TYPE_COUNT] = { 0 }; - uint64_t TypeCounts[SN_OBJ_TYPE_COUNT] = { 0 }; - unsigned char * pStates = ABC_CALLOC( unsigned char, pDesign->modules.size ); - uint64_t * pMults = ABC_CALLOC( uint64_t, pDesign->modules.size ); + unsigned char * pStates; + uint64_t * pMults; sn_vec_t Stack, Postorder; Sn_DistribFrame_t * pFrame; size_t i; - int Type; - assert( pStates != NULL && pMults != NULL ); + if ( pDesign == NULL || Top >= pDesign->modules.size ) + return NULL; + pStates = ABC_CALLOC( unsigned char, pDesign->modules.size ); + pMults = ABC_CALLOC( uint64_t, pDesign->modules.size ); + if ( pStates == NULL || pMults == NULL ) + { + ABC_FREE( pStates ); + ABC_FREE( pMults ); + return NULL; + } sn_vec_init( &Stack ); sn_vec_init( &Postorder ); pStates[Top] = 1; @@ -494,7 +513,8 @@ static void Sn_DesignPrintDistrib( FILE * pOut, const sn_design_t * pDesign, sn_ if ( pFrame->NextInst < pModule->inst_modules.size ) { sn_module_id_t Child = sn_vec_at( sn_module_id_t, &pModule->inst_modules, pFrame->NextInst++ ); - assert( pStates[Child] != 1 ); + if ( Child >= pDesign->modules.size || pStates[Child] == 1 ) + goto fail; if ( pStates[Child] == 0 ) { pStates[Child] = 1; @@ -516,14 +536,117 @@ static void Sn_DesignPrintDistrib( FILE * pOut, const sn_design_t * pDesign, sn_ uint64_t Mult = pMults[Module]; if ( Mult == 0 ) continue; - Sn_ModuleCollectDistrib( pDesign, Module, Mult, pEntries, nEntries, nCaps, TypeCounts ); for ( size_t k = 0; k < pModule->inst_modules.size; k++ ) { sn_module_id_t Child = sn_vec_at( sn_module_id_t, &pModule->inst_modules, k ); - assert( UINT64_MAX - pMults[Child] >= Mult ); + if ( Child >= pDesign->modules.size || UINT64_MAX - pMults[Child] < Mult ) + goto fail; pMults[Child] += Mult; } } + sn_vec_destroy( &Postorder ); + sn_vec_destroy( &Stack ); + ABC_FREE( pStates ); + return pMults; + +fail: + sn_vec_destroy( &Postorder ); + sn_vec_destroy( &Stack ); + ABC_FREE( pStates ); + ABC_FREE( pMults ); + return NULL; +} + +static int Sn_ModulePortBits( const sn_module_t * pModule, sn_obj_type_t Type, uint64_t * pBits ) +{ + uint64_t Bits = 0; + size_t i; + assert( Type == SN_PI || Type == SN_PO ); + for ( i = 0; i < pModule->type_objects[Type].size; i++ ) + { + uint32_t Width = sn_obj_width( pModule, sn_vec_at(sn_obj_id_t, &pModule->type_objects[Type], i) ); + if ( UINT64_MAX - Bits < Width ) + return 0; + Bits += Width; + } + *pBits = Bits; + return 1; +} + +static void Sn_DesignPrintBlackboxes( FILE * pOut, const sn_design_t * pDesign, sn_module_id_t Top ) +{ + const sn_module_t * pTop = sn_design_get_module_const( pDesign, Top ); + uint64_t * pMults = Sn_DesignCountModuleOccurrences( pDesign, Top ); + uint64_t nOccurrences = 0, nAigInputs = 0, nAigOutputs = 0; + size_t nTypes = 0, i; + if ( pMults == NULL ) + { + fprintf( pOut, "Cannot count black-box occurrences: hierarchy is invalid or the count overflows.\n" ); + return; + } + fprintf( pOut, "Black boxes reachable from \"%s\":\n", sn_name_get(&pDesign->names, pTop->name) ); + fprintf( pOut, "Module occurrences PI ports/bits PO ports/bits " + "AIG inputs AIG outputs\n" ); + for ( i = 0; i < pDesign->modules.size; i++ ) + { + const sn_module_t * pModule = sn_design_get_module_const( pDesign, (sn_module_id_t)i ); + uint64_t Mult = pMults[i], PiBits, PoBits, AigInputs, AigOutputs; + if ( Mult == 0 || !sn_module_is_blackbox(pModule) ) + continue; + if ( !Sn_ModulePortBits(pModule, SN_PI, &PiBits) || !Sn_ModulePortBits(pModule, SN_PO, &PoBits) || + (PoBits != 0 && Mult > UINT64_MAX / PoBits) || + (PiBits != 0 && Mult > UINT64_MAX / PiBits) ) + goto overflow; + AigInputs = Mult * PoBits; + AigOutputs = Mult * PiBits; + if ( UINT64_MAX - nOccurrences < Mult || UINT64_MAX - nAigInputs < AigInputs || + UINT64_MAX - nAigOutputs < AigOutputs ) + goto overflow; + nTypes++; + nOccurrences += Mult; + nAigInputs += AigInputs; + nAigOutputs += AigOutputs; + fprintf( pOut, "%-32s %10llu %6zu/%-6llu %6zu/%-6llu %10llu %11llu\n", + sn_name_get(&pDesign->names, pModule->name), (unsigned long long)Mult, + pModule->type_objects[SN_PI].size, (unsigned long long)PiBits, + pModule->type_objects[SN_PO].size, (unsigned long long)PoBits, + (unsigned long long)AigInputs, (unsigned long long)AigOutputs ); + } + fprintf( pOut, "Black-box totals: types = %zu occurrences = %llu AIG inputs = %llu AIG outputs = %llu\n", + nTypes, (unsigned long long)nOccurrences, (unsigned long long)nAigInputs, + (unsigned long long)nAigOutputs ); + ABC_FREE( pMults ); + return; + +overflow: + fprintf( pOut, "Cannot print black-box statistics: a bit or occurrence total overflows 64 bits.\n" ); + ABC_FREE( pMults ); +} + +static void Sn_DesignPrintDistrib( FILE * pOut, const sn_design_t * pDesign, sn_module_id_t Top ) +{ + Sn_DistribEntry_t * pEntries[SN_OBJ_TYPE_COUNT] = { NULL }; + size_t nEntries[SN_OBJ_TYPE_COUNT] = { 0 }; + size_t nCaps[SN_OBJ_TYPE_COUNT] = { 0 }; + uint64_t TypeCounts[SN_OBJ_TYPE_COUNT] = { 0 }; + uint64_t * pMults = Sn_DesignCountModuleOccurrences( pDesign, Top ); + size_t i; + int Type; + if ( pMults == NULL ) + { + fprintf( pOut, "Cannot print object distribution: hierarchy is invalid or the occurrence count overflows.\n" ); + return; + } + for ( i = 0; i < pDesign->modules.size; i++ ) + if ( pMults[i] && !Sn_ModuleCollectDistrib(pDesign, (sn_module_id_t)i, pMults[i], pEntries, + nEntries, nCaps, TypeCounts) ) + { + fprintf( pOut, "Cannot print object distribution: a count overflows or allocation failed.\n" ); + for ( Type = 0; Type < SN_OBJ_TYPE_COUNT; Type++ ) + ABC_FREE( pEntries[Type] ); + ABC_FREE( pMults ); + return; + } fprintf( pOut, "ID : name occurrence (occurrence)=. ...\n" ); for ( Type = 0; Type < SN_OBJ_TYPE_COUNT; Type++ ) { @@ -551,10 +674,7 @@ static void Sn_DesignPrintDistrib( FILE * pOut, const sn_design_t * pDesign, sn_ fprintf( pOut, "\n" ); ABC_FREE( pEntries[Type] ); } - sn_vec_destroy( &Postorder ); - sn_vec_destroy( &Stack ); ABC_FREE( pMults ); - ABC_FREE( pStates ); } void Sn_Init( Abc_Frame_t * pAbc ) @@ -997,7 +1117,7 @@ static int Sn_CommandOptMux( Abc_Frame_t * pAbc, int argc, char ** argv ) if ( !Sn_CommandCheckDesign(pAbc) ) return 1; pCurrent = Sn_AbcGetMan( pAbc ); - if ( !sn_design_check(pCurrent->pDesign, pAbc->Err, 0) ) + if ( !sn_design_check(pCurrent->pDesign, Abc_FrameReadErr(pAbc), 0) ) { Abc_Print( -1, "Cannot @opt_mux: the current SN design is inconsistent.\n" ); return 1; @@ -1009,7 +1129,7 @@ static int Sn_CommandOptMux( Abc_Frame_t * pAbc, int argc, char ** argv ) return 1; } Stats = sn_design_share( p->pDesign, Options ); - if ( !sn_design_check(p->pDesign, pAbc->Err, 0) ) + if ( !sn_design_check(p->pDesign, Abc_FrameReadErr(pAbc), 0) ) { Abc_Print( -1, "Cannot @opt_mux: the transformed SN design is inconsistent.\n" ); Sn_ManFree( p ); @@ -1393,8 +1513,9 @@ static int Sn_CommandPut( Abc_Frame_t * pAbc, int argc, char ** argv ) } pNtk = Abc_NtkFromCellMappedGia( pGia, 0 ); vMapping = Abc_NtkWriteMiniMapping( pNtk ); - Top = sn_design_add_gate_module( p->pDesign, p->BlastModule, Vec_IntArray(vMapping), &p->Boundary, - Sn_GateIdResolver, pLibrary, "__sn_gate_mapped" ); + Top = sn_design_add_gate_module( p->pDesign, p->BlastModule, Vec_IntArray(vMapping), + (size_t)Vec_IntSize(vMapping), &p->Boundary, Sn_GateIdResolver, + pLibrary, "__sn_gate_mapped" ); if ( Top == SN_INVALID_ID ) { Abc_Print( -1, "Cannot @put: the current genlib does not contain every gate used by the mapped GIA.\n" ); @@ -1826,7 +1947,6 @@ usage: static int Sn_CommandWrite( Abc_Frame_t * pAbc, int argc, char ** argv ) { Sn_Man_t * p; - const sn_module_t * pTop; char * pFileName; FILE * pFile; int c, Status = 0; @@ -1838,7 +1958,6 @@ static int Sn_CommandWrite( Abc_Frame_t * pAbc, int argc, char ** argv ) if ( !Sn_CommandCheckDesign(pAbc) ) return 1; p = Sn_AbcGetMan( pAbc ); - pTop = sn_design_get_module_const( p->pDesign, p->Top ); pFileName = argv[globalUtilOptind]; if ( Sn_FileHasSuffix(pFileName, ".sn") ) { @@ -1887,17 +2006,27 @@ usage: static int Sn_CommandPs( Abc_Frame_t * pAbc, int argc, char ** argv ) { Sn_Man_t * p; - const sn_module_t * pTop; + const sn_module_t * pReport; sn_design_mem_usage_t Mem; + char * pModuleName = NULL; + sn_module_id_t Report; size_t nObjects = 0; size_t i; int c, fDistrib = 0, fVerbose = 0, fMem, fLut, fGate; char UsedMemory[32], AllocatedMemory[32]; Extra_UtilGetoptReset(); - while ( (c = Extra_UtilGetopt(argc, argv, "dvh")) != EOF ) + while ( (c = Extra_UtilGetopt(argc, argv, "Mdvh")) != EOF ) { switch ( c ) { + case 'M': + if ( globalUtilOptind >= argc ) + { + Abc_Print( -1, "Command line switch \"-M\" should be followed by a module name.\n" ); + goto usage; + } + pModuleName = argv[globalUtilOptind++]; + break; case 'd': fDistrib ^= 1; break; @@ -1914,7 +2043,13 @@ static int Sn_CommandPs( Abc_Frame_t * pAbc, int argc, char ** argv ) if ( !Sn_CommandCheckDesign(pAbc) ) return 1; p = Sn_AbcGetMan( pAbc ); - pTop = sn_design_get_module_const( p->pDesign, p->Top ); + Report = pModuleName ? sn_design_find_module( p->pDesign, pModuleName ) : p->Top; + if ( Report == SN_INVALID_ID ) + { + Abc_Print( -1, "Cannot find module \"%s\" in the current SN design.\n", pModuleName ); + return 1; + } + pReport = sn_design_get_module_const( p->pDesign, Report ); for ( i = 0; i < p->pDesign->modules.size; i++ ) nObjects += sn_design_get_module_const( p->pDesign, (sn_module_id_t)i )->obj_types.size; sn_design_get_mem_usage( p->pDesign, &Mem ); @@ -1925,27 +2060,35 @@ static int Sn_CommandPs( Abc_Frame_t * pAbc, int argc, char ** argv ) Sn_FormatMemory( Mem.total.allocated_bytes, AllocatedMemory, sizeof(AllocatedMemory) ); fprintf( pAbc->Out, "SN design: top = %s modules = %zu objects = %zu memory = %s/%s " "(used/allocated)\n", - sn_name_get(&p->pDesign->names, pTop->name), p->pDesign->modules.size, nObjects, + sn_name_get(&p->pDesign->names, pReport->name), p->pDesign->modules.size, nObjects, UsedMemory, AllocatedMemory ); - Sn_ModulePrintStats( pAbc->Out, pTop, fMem, fLut, fGate ); - if ( fVerbose ) + if ( pModuleName ) + Sn_ModulePrintStats( pAbc->Out, pReport, fMem, fLut, fGate ); + else { - fprintf( pAbc->Out, "Hierarchy:\n" ); - sn_design_print_hierarchy( pAbc->Out, p->pDesign, p->Top ); fprintf( pAbc->Out, "Modules:\n" ); for ( i = 0; i < p->pDesign->modules.size; i++ ) Sn_ModulePrintStats( pAbc->Out, sn_design_get_module_const(p->pDesign, (sn_module_id_t)i), fMem, fLut, fGate ); } + if ( fVerbose ) + { + fprintf( pAbc->Out, "Hierarchy:\n" ); + sn_design_print_hierarchy( pAbc->Out, p->pDesign, Report ); + } if ( fDistrib ) - Sn_DesignPrintDistrib( pAbc->Out, p->pDesign, p->Top ); + { + Sn_DesignPrintBlackboxes( pAbc->Out, p->pDesign, Report ); + Sn_DesignPrintDistrib( pAbc->Out, p->pDesign, Report ); + } return 0; usage: - Abc_Print( -2, "usage: @ps [-dvh]\n" ); + Abc_Print( -2, "usage: @ps [-M module] [-dvh]\n" ); Abc_Print( -2, "\t prints statistics for the current SN design\n" ); + Abc_Print( -2, "\t-M name : select one module and its hierarchy [default = print all module definitions]\n" ); Abc_Print( -2, "\t-d : print object-type and width distribution for the elaborated hierarchy\n" ); - Abc_Print( -2, "\t-v : print hierarchy and per-module statistics\n" ); + Abc_Print( -2, "\t-v : print the hierarchy rooted at the selected module\n" ); Abc_Print( -2, "\t-h : print the command usage\n" ); return 1; } diff --git a/src/base/sn/snMiniAig.h b/src/base/sn/snMiniAig.h index 2698743bc..ec7ab5d72 100644 --- a/src/base/sn/snMiniAig.h +++ b/src/base/sn/snMiniAig.h @@ -127,6 +127,7 @@ static inline sn_module_id_t sn_design_add_aig_module(sn_design_t* design, sn_mo } assert(co_index <= boundary->cos.size); sn_boundary_regs_finish(®s, drivers); + result = sn_design_get_module(design, result_id); free(drivers); free(objects); diff --git a/src/base/sn/snMiniGate.h b/src/base/sn/snMiniGate.h index c9763a7cf..f122958a0 100644 --- a/src/base/sn/snMiniGate.h +++ b/src/base/sn/snMiniGate.h @@ -31,38 +31,86 @@ typedef uint32_t (*sn_gate_id_resolver_t)(void* context, const char* gate_name); // afterward in topological order. Gate names stored at the end of the array are resolved into the current library's // stable gate IDs; the name is also retained as the SN object name for structural Verilog emission. static inline sn_module_id_t sn_design_add_gate_module(sn_design_t* design, sn_module_id_t source_top_id, - const int* mapping, const sn_blast_boundary_t* boundary, + const int* mapping, size_t mapping_count, + const sn_blast_boundary_t* boundary, sn_gate_id_resolver_t resolver, void* resolver_context, const char* module_name) { assert(design && source_top_id < design->modules.size && mapping && boundary && resolver && module_name); + if (mapping_count < 4 || mapping[0] < 0 || mapping[1] < 0 || mapping[2] < 0 || mapping[3] < 0) + return SN_INVALID_ID; uint32_t ci_count = (uint32_t)mapping[0]; uint32_t co_count = (uint32_t)mapping[1]; uint32_t node_count = (uint32_t)mapping[2]; uint32_t reg_count = (uint32_t)mapping[3]; - assert(reg_count == 0 && ci_count == boundary->cis.size && co_count == boundary->cos.size); + if (reg_count != 0 || ci_count != boundary->cis.size || co_count != boundary->cos.size || + node_count > UINT32_MAX - ci_count) + return SN_INVALID_ID; - // Resolve all gate names before mutating the design. A changed genlib can otherwise leave a partially constructed - // module behind or turn a user-level @put error into an assertion failure. - uint32_t position = 4; - for (uint32_t i = 0; i < node_count; i++) + // Validate the complete structural prefix and resolve all bounded gate-name strings before mutating the design. + // A changed genlib or malformed mini-mapping can otherwise leave a partially constructed module behind. + size_t position = 4; + uint32_t* fanin_counts = node_count ? (uint32_t*)malloc(sizeof(uint32_t) * node_count) : NULL; + const uint32_t** fanin_indices = + node_count ? (const uint32_t**)malloc(sizeof(uint32_t*) * node_count) : NULL; + uint32_t* gate_ids = node_count ? (uint32_t*)malloc(sizeof(uint32_t) * node_count) : NULL; + bool valid = true; + assert((fanin_counts && fanin_indices && gate_ids) || node_count == 0); + for (uint32_t i = 0; valid && i < node_count; i++) { + if (position >= mapping_count || mapping[position] < 0) + { + valid = false; + break; + } uint32_t count = (uint32_t)mapping[position++]; + if (count > mapping_count - position) + { + valid = false; + break; + } + fanin_counts[i] = count; + fanin_indices[i] = (const uint32_t*)(mapping + position); + for (uint32_t k = 0; k < count; k++) + if (mapping[position + k] < 0 || (uint32_t)mapping[position + k] >= ci_count + i) + valid = false; position += count; } - position += co_count; - const char* gate_name = (const char*)(mapping + position); - uint32_t* gate_ids = node_count ? (uint32_t*)malloc(sizeof(uint32_t) * node_count) : NULL; - assert(gate_ids || node_count == 0); - for (uint32_t i = 0; i < node_count; i++) + if (valid && co_count > mapping_count - position) + valid = false; + const uint32_t* output_indices = valid ? (const uint32_t*)(mapping + position) : NULL; + for (uint32_t i = 0; valid && i < co_count; i++) + if (mapping[position + i] < 0 || (uint32_t)mapping[position + i] >= ci_count + node_count) + valid = false; + if (valid) + position += co_count; + const char* gate_names = valid ? (const char*)(mapping + position) : NULL; + const char* gate_name = gate_names; + size_t name_bytes = valid ? (mapping_count - position) * sizeof(int) : 0; + for (uint32_t i = 0; valid && i < node_count; i++) { + const char* end = (const char*)memchr(gate_name, '\0', name_bytes); + if (!end || end == gate_name) + { + valid = false; + break; + } gate_ids[i] = resolver(resolver_context, gate_name); if (gate_ids[i] == SN_INVALID_ID) { - free(gate_ids); - return SN_INVALID_ID; + valid = false; + break; } - gate_name += strlen(gate_name) + 1; + size_t length = (size_t)(end - gate_name) + 1; + gate_name += length; + name_bytes -= length; + } + if (!valid) + { + free(gate_ids); + free(fanin_indices); + free(fanin_counts); + return SN_INVALID_ID; } const sn_module_t* source = sn_design_get_module_const(design, source_top_id); @@ -103,19 +151,7 @@ static inline sn_module_id_t sn_design_add_gate_module(sn_design_t* design, sn_m assert(false); } - position = 4; - uint32_t* fanin_counts = (uint32_t*)malloc(sizeof(uint32_t) * node_count); - const uint32_t** fanin_indices = (const uint32_t**)malloc(sizeof(uint32_t*) * node_count); - assert((fanin_counts && fanin_indices) || node_count == 0); - for (uint32_t i = 0; i < node_count; i++) - { - fanin_counts[i] = (uint32_t)mapping[position++]; - fanin_indices[i] = (const uint32_t*)(mapping + position); - position += fanin_counts[i]; - } - const uint32_t* output_indices = (const uint32_t*)(mapping + position); - position += co_count; - gate_name = (const char*)(mapping + position); + gate_name = gate_names; for (uint32_t i = 0; i < node_count; i++) { @@ -160,6 +196,7 @@ static inline sn_module_id_t sn_design_add_gate_module(sn_design_t* design, sn_m co_drivers[i] = objects[output_indices[i]]; } sn_boundary_regs_finish(®s, co_drivers); + result = sn_design_get_module(design, result_id); free(co_drivers); free(fanin_indices); diff --git a/src/base/sn/snMiniLut.h b/src/base/sn/snMiniLut.h index fb20a39a8..b26bcbc62 100644 --- a/src/base/sn/snMiniLut.h +++ b/src/base/sn/snMiniLut.h @@ -391,6 +391,7 @@ static inline sn_module_id_t sn_design_add_lut_module(sn_design_t* design, sn_mo sn_module_add_po(result, width, sn_obj_is_signed(source, old_po), sn_obj_name(source, old_po), driver); } sn_boundary_regs_finish(®s, co_drivers); + result = sn_design_get_module(design, result_id); free(co_drivers); free(mini_objects); diff --git a/src/base/sn/snMux.h b/src/base/sn/snMux.h index 781f7c8e3..03e235bd9 100644 --- a/src/base/sn/snMux.h +++ b/src/base/sn/snMux.h @@ -505,6 +505,8 @@ static inline bool sn_share_reg_mux_tree(sn_module_t* target, const sn_module_t* sn_vec_t stack, steps, paths, terms, term_hashes, term_links, data_terms, controls; sn_obj_id_t hold = SN_INVALID_ID, data = SN_INVALID_ID, new_reg = SN_INVALID_ID; uint32_t* term_buckets = NULL; + uint32_t* group_heads = NULL; + uint32_t* path_links = NULL; uint32_t term_bucket_count = 0; size_t hold_index = 0; bool exclusive = true, overflow = false; @@ -553,6 +555,18 @@ static inline bool sn_share_reg_mux_tree(sn_module_t* target, const sn_module_t* } if (paths.size <= terms.size || paths.size - terms.size < options.min_saved_paths || paths.size < 2 * terms.size) goto unchanged; + group_heads = (uint32_t*)malloc(terms.size * sizeof(uint32_t)); + path_links = (uint32_t*)malloc(paths.size * sizeof(uint32_t)); + assert(group_heads && path_links); + for (size_t k = 0; k < terms.size; k++) + group_heads[k] = SN_INVALID_ID; + for (size_t i = 0; i < paths.size; i++) + { + uint32_t group = sn_vec_at(sn_share_path_t, &paths, i).group; + assert(group < terms.size); + path_links[i] = group_heads[group]; + group_heads[group] = (uint32_t)i; + } hold = sn_share_strip_value(source, old_reg); hold_index = terms.size; @@ -566,11 +580,10 @@ static inline bool sn_share_reg_mux_tree(sn_module_t* target, const sn_module_t* continue; sn_vec_t cubes; sn_vec_init(&cubes); - for (size_t i = 0; i < paths.size; i++) + for (uint32_t i = group_heads[k]; i != SN_INVALID_ID; i = path_links[i]) { sn_share_path_t* path = &sn_vec_at(sn_share_path_t, &paths, i); - if (path->group == k) - *sn_vec_push(sn_obj_id_t, &cubes) = sn_share_path_condition(target, source, path, &steps); + *sn_vec_push(sn_obj_id_t, &cubes) = sn_share_path_condition(target, source, path, &steps); } *sn_vec_push(sn_obj_id_t, &controls) = sn_share_or(target, sn_vec_data(sn_obj_id_t, &cubes), (uint32_t)cubes.size); @@ -615,6 +628,8 @@ static inline bool sn_share_reg_mux_tree(sn_module_t* target, const sn_module_t* sn_vec_destroy(&terms); sn_vec_destroy(&term_hashes); sn_vec_destroy(&term_links); + free(path_links); + free(group_heads); free(term_buckets); return true; @@ -625,6 +640,8 @@ unchanged: sn_vec_destroy(&terms); sn_vec_destroy(&term_hashes); sn_vec_destroy(&term_links); + free(path_links); + free(group_heads); free(term_buckets); sn_vec_destroy(&data_terms); sn_vec_destroy(&controls); @@ -672,6 +689,8 @@ static inline bool sn_share_reg_pmux(sn_module_t* target, const sn_module_t* sou while (bucket_count < 2 * count) bucket_count <<= 1; uint32_t* buckets = (uint32_t*)malloc((size_t)bucket_count * sizeof(uint32_t)); + uint32_t* member_heads = NULL; + uint32_t* member_links = NULL; assert(buckets); memset(buckets, 0xff, (size_t)bucket_count * sizeof(uint32_t)); for (uint32_t i = 0; i < count; i++) @@ -712,6 +731,18 @@ static inline bool sn_share_reg_pmux(sn_module_t* target, const sn_module_t* sou free(buckets); return false; } + member_heads = (uint32_t*)malloc(unique.size * sizeof(uint32_t)); + member_links = (uint32_t*)malloc(members.size * sizeof(uint32_t)); + assert(member_heads && member_links); + for (size_t k = 0; k < unique.size; k++) + member_heads[k] = SN_INVALID_ID; + for (size_t j = 0; j < members.size; j++) + { + uint32_t group = sn_vec_at(uint32_t, &members, j) >> 16; + assert(group < unique.size); + member_links[j] = member_heads[group]; + member_heads[group] = (uint32_t)j; + } sn_obj_id_t new_reg = sn_obj_dup(source, old_reg); sn_obj_id_t new_in = sn_obj_pair_in(target, new_reg); @@ -720,11 +751,10 @@ static inline bool sn_share_reg_pmux(sn_module_t* target, const sn_module_t* sou { sn_vec_t bits; sn_vec_init(&bits); - for (size_t j = 0; j < members.size; j++) + for (uint32_t j = member_heads[k]; j != SN_INVALID_ID; j = member_links[j]) { uint32_t member = sn_vec_at(uint32_t, &members, j); - if ((member >> 16) == k) - *sn_vec_push(sn_obj_id_t, &bits) = sn_share_select_bit(target, new_select, member & UINT16_MAX); + *sn_vec_push(sn_obj_id_t, &bits) = sn_share_select_bit(target, new_select, member & UINT16_MAX); } *sn_vec_push(sn_obj_id_t, &conditions) = sn_share_or(target, sn_vec_data(sn_obj_id_t, &bits), (uint32_t)bits.size); @@ -776,6 +806,8 @@ static inline bool sn_share_reg_pmux(sn_module_t* target, const sn_module_t* sou sn_vec_destroy(&unique_links); sn_vec_destroy(&conditions); sn_vec_destroy(&members); + free(member_links); + free(member_heads); free(buckets); return true; } diff --git a/src/base/sn/snPth.h b/src/base/sn/snPth.h index a9f237fac..eee9c4771 100644 --- a/src/base/sn/snPth.h +++ b/src/base/sn/snPth.h @@ -103,10 +103,22 @@ static inline void sn_pth_process(void** jobs, size_t count, unsigned processes, pool.context = context; pool.function = function; int status = pthread_mutex_init(&pool.mutex, NULL); - assert(status == 0); - (void)status; + if (status != 0) + { + for (size_t i = 0; i < count; i++) + function(context, jobs[i]); + return; + } pthread_t* workers = (pthread_t*)malloc(sizeof(pthread_t) * worker_count); - assert(workers); + if (!workers) + { + status = pthread_mutex_destroy(&pool.mutex); + assert(status == 0); + (void)status; + for (size_t i = 0; i < count; i++) + function(context, jobs[i]); + return; + } unsigned created = 0; for (; created < worker_count; created++) {