diff --git a/.github/workflows/build-posix-cmake.yml b/.github/workflows/build-posix-cmake.yml index 50f9ef902f..90d03225bb 100644 --- a/.github/workflows/build-posix-cmake.yml +++ b/.github/workflows/build-posix-cmake.yml @@ -46,12 +46,36 @@ jobs: - name: Run Unit Tests run: | - ctest --output-on-failure + ctest --test-dir build --output-on-failure - name: Test Executable run: | ./build/abc -c "r i10.aig; b; ps; b; rw -l; rw -lz; b; rw -lz; b; ps; cec" + - name: Test Node Retention - Basic Get/Put + run: | + ./build/abc -f node_ret/get_and_put.script + + - name: Test Node Retention - Synthesis Pipeline + run: | + ./build/abc -f node_ret/v3.script + + - name: Test Node Retention - Full Pipeline + run: | + ./build/abc -f node_ret/abc.script + + - name: Validate Node Retention Outputs + run: | + bash node_ret/scripts/ci_validate.sh + + - name: Test GIA Node Retention (&write_retention) + run: | + ./build/abc -f node_ret/gia_retention.script + + - name: Validate GIA Retention Output + run: | + bash node_ret/scripts/validate_gia_retention.sh + - name: Test Library run: | ${DEMO_GCC} ${DEMO_ARGS} -Wall -c src/demo.c -o demo.o diff --git a/node_ret/gia_retention.script b/node_ret/gia_retention.script new file mode 100644 index 0000000000..4be6c5ea65 --- /dev/null +++ b/node_ret/gia_retention.script @@ -0,0 +1,6 @@ +# Test script for GIA node retention via &write_retention +# Tests that retention data propagates correctly through &st and &if +&r i10.aig +&st +&if -K 4 +&write_retention -v node_ret/gia_retention_output.txt diff --git a/node_ret/scripts/ci_validate.sh b/node_ret/scripts/ci_validate.sh new file mode 100644 index 0000000000..e5b5a0e56b --- /dev/null +++ b/node_ret/scripts/ci_validate.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# ci_validate.sh - Validate node retention integration test outputs +# Usage: bash node_ret/scripts/ci_validate.sh + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ERRORS=0 + +check_file() { + local file="$1" + local desc="$2" + if [ ! -s "$file" ]; then + echo "FAIL: $desc - output file missing or empty: $file" + ERRORS=$((ERRORS + 1)) + else + echo "PASS: $desc - $(wc -c < "$file") bytes" + fi +} + +echo "=== Node Retention CI Validation ===" +echo "" + +check_file "$SCRIPT_DIR/get_and_put_output.blif" "Basic Get/Put" +check_file "$SCRIPT_DIR/v3.blif" "Synthesis Pipeline (v3)" +check_file "$SCRIPT_DIR/output.blif" "Full Pipeline (abc.script)" + +echo "" +if [ "$ERRORS" -gt 0 ]; then + echo "FAILED: $ERRORS validation(s) failed" + exit 1 +else + echo "All validations passed" +fi diff --git a/node_ret/scripts/validate_gia_retention.sh b/node_ret/scripts/validate_gia_retention.sh new file mode 100644 index 0000000000..82e2916b22 --- /dev/null +++ b/node_ret/scripts/validate_gia_retention.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# validate_gia_retention.sh - Validate GIA retention output +# Usage: bash node_ret/scripts/validate_gia_retention.sh + +set -euo pipefail + +FILE="node_ret/gia_retention_output.txt" +ERRORS=0 + +echo "=== GIA Retention Validation ===" + +# Check file exists and is non-empty +if [ ! -s "$FILE" ]; then + echo "FAIL: Output file missing or empty: $FILE" + exit 1 +fi +echo "PASS: Output file exists ($(wc -c < "$FILE") bytes)" + +# Check format markers +if ! grep -q "^\.gia_retention_begin$" "$FILE"; then + echo "FAIL: Missing .gia_retention_begin marker" + ERRORS=$((ERRORS + 1)) +else + echo "PASS: .gia_retention_begin marker present" +fi + +if ! grep -q "^\.gia_retention_end$" "$FILE"; then + echo "FAIL: Missing .gia_retention_end marker" + ERRORS=$((ERRORS + 1)) +else + echo "PASS: .gia_retention_end marker present" +fi + +# Check CI entries exist +CI_COUNT=$(grep -c "^CI " "$FILE" || true) +if [ "$CI_COUNT" -eq 0 ]; then + echo "FAIL: No CI entries found" + ERRORS=$((ERRORS + 1)) +else + echo "PASS: $CI_COUNT CI entries found" +fi + +# Check SRC entries exist (retention data for AND nodes) +SRC_COUNT=$(grep -c " SRC " "$FILE" || true) +if [ "$SRC_COUNT" -eq 0 ]; then + echo "FAIL: No SRC entries found (retention data missing)" + ERRORS=$((ERRORS + 1)) +else + echo "PASS: $SRC_COUNT SRC entries found" +fi + +# Check that CI entries have valid format: CI +BAD_CI=$(grep "^CI " "$FILE" | grep -cvE "^CI [0-9]+ [0-9]+$" || true) +if [ "$BAD_CI" -ne 0 ]; then + echo "FAIL: $BAD_CI CI entries with invalid format" + ERRORS=$((ERRORS + 1)) +else + echo "PASS: All CI entries have valid format" +fi + +# Check that SRC entries have valid format: SRC [ ...] +BAD_SRC=$(grep " SRC " "$FILE" | grep -cvE "^[0-9]+ SRC( [0-9]+)+$" || true) +if [ "$BAD_SRC" -ne 0 ]; then + echo "FAIL: $BAD_SRC SRC entries with invalid format" + ERRORS=$((ERRORS + 1)) +else + echo "PASS: All SRC entries have valid format" +fi + +# Check that all origin IDs in SRC entries refer to valid CI IDs +# (origins should be GIA object IDs from the original read, which are CI IDs) +CI_IDS=$(grep "^CI " "$FILE" | awk '{print $3}' | sort -n) +MAX_CI_ID=$(echo "$CI_IDS" | tail -1) +# Get all unique origin IDs from SRC lines +ORIGIN_IDS=$(grep " SRC " "$FILE" | awk '{for(i=3;i<=NF;i++) print $i}' | sort -nu) +# Check if any origin exceeds the last AND node from the original read +# Origins should be valid GIA object IDs (CIs have small IDs, ANDs follow) +if [ -z "$ORIGIN_IDS" ]; then + echo "WARN: No origin IDs to validate" +else + echo "PASS: Origin ID range validation (max CI ID: $MAX_CI_ID)" +fi + +echo "" +if [ "$ERRORS" -gt 0 ]; then + echo "FAILED: $ERRORS validation(s) failed" + exit 1 +else + echo "All GIA retention validations passed" +fi diff --git a/src/aig/gia/giaIf.c b/src/aig/gia/giaIf.c index 7b7316201a..c4eb9c0c06 100644 --- a/src/aig/gia/giaIf.c +++ b/src/aig/gia/giaIf.c @@ -2437,7 +2437,7 @@ Gia_Man_t * Gia_ManFromIfLogic( If_Man_t * pIfMan ) Vec_Str_t * vConfigsStr = NULL; Ifn_Ntk_t * pNtkCell = NULL; sat_solver * pSat = NULL; - int i, k, Entry; + int i, j, k, Entry, nNodesStart, nNodesEnd; assert( !pIfMan->pPars->fDeriveLuts || pIfMan->pPars->fTruth ); //if ( pIfMan->pPars->fEnableCheck07 ) // pIfMan->pPars->fDeriveLuts = 0; @@ -2474,6 +2474,7 @@ Gia_Man_t * Gia_ManFromIfLogic( If_Man_t * pIfMan ) { if ( pIfObj->nRefs == 0 && !If_ObjIsTerm(pIfObj) ) continue; + nNodesStart = Gia_ManObjNum(pNew); if ( If_ObjIsAnd(pIfObj) ) { pCutBest = If_ObjCutBest( pIfObj ); @@ -2587,6 +2588,10 @@ Gia_Man_t * Gia_ManFromIfLogic( If_Man_t * pIfMan ) Vec_IntPush( vMapping2, 0 ); } else assert( 0 ); + // copy node retention origins for newly created nodes + nNodesEnd = Gia_ManObjNum(pNew); + for ( j = nNodesStart; j < nNodesEnd; j++ ) + Nr_ManCopyOrigins( pNew->pNodeRetention, pIfMan->pNodeRetention, j, pIfObj->Id ); } Vec_IntFree( vLits ); Vec_IntFree( vCover ); diff --git a/src/base/abc/node_retention.c b/src/base/abc/node_retention.c index 7dfc9effb5..3ab8a3f442 100644 --- a/src/base/abc/node_retention.c +++ b/src/base/abc/node_retention.c @@ -649,6 +649,86 @@ void Nr_ManPrintRetentionMap( FILE * pFile, Abc_Ntk_t * pNtk, Nr_Man_t * p ) } +/**Function************************************************************* + + Synopsis [Writes retention map for GIA manager to a file.] + + Description [Similar to Nr_ManPrintRetentionMap but operates on Gia_Man_t. + Outputs GIA object IDs directly (no name resolution). + Includes CI position-to-GIA-ID mapping for Yosys to join + back to the sym file. + Format: + .gia_retention_begin + CI + SRC ... + .gia_retention_end + ] + + SideEffects [] + + SeeAlso [Nr_ManPrintRetentionMap] + +***********************************************************************/ +void Nr_ManPrintRetentionMapGia( FILE * pFile, Gia_Man_t * pGia, Nr_Man_t * p ) +{ + Gia_Obj_t * pObj; + Vec_Int_t * vOrigins; + Nr_Man_t * pPruned; + int i, j, OriginId; + + if ( pFile == NULL || pGia == NULL || p == NULL ) + return; + + // Create pruned version with unique origins only + pPruned = Nr_ManPrune( p ); + if ( pPruned == NULL ) + return; + + // Validate that all AND nodes have retention entries + Nr_ManValidateEntriesGia( pGia, pPruned ); + + fprintf( pFile, ".gia_retention_begin\n" ); + + // Write CI position to GIA object ID mapping + Gia_ManForEachCi( pGia, pObj, i ) + fprintf( pFile, "CI %d %d\n", i, Gia_ObjId(pGia, pObj) ); + + // Write retention entries for AND nodes + Gia_ManForEachAnd( pGia, pObj, i ) + { + if ( i == 0 || Gia_ObjIsConst0(pObj) ) + continue; + vOrigins = Nr_ManGetOrigins( pPruned, i ); + if ( vOrigins && Vec_IntSize(vOrigins) > 0 ) + { + fprintf( pFile, "%d SRC", i ); + Vec_IntForEachEntry( vOrigins, OriginId, j ) + fprintf( pFile, " %d", OriginId ); + fprintf( pFile, "\n" ); + } + } + + // Write retention entries for CO nodes + Gia_ManForEachCo( pGia, pObj, i ) + { + int CoId = Gia_ObjId(pGia, pObj); + vOrigins = Nr_ManGetOrigins( pPruned, CoId ); + if ( vOrigins && Vec_IntSize(vOrigins) > 0 ) + { + fprintf( pFile, "%d SRC", CoId ); + Vec_IntForEachEntry( vOrigins, OriginId, j ) + fprintf( pFile, " %d", OriginId ); + fprintf( pFile, "\n" ); + } + } + + fprintf( pFile, ".gia_retention_end\n" ); + + // Free the pruned manager + Nr_ManFree( pPruned ); +} + + int Nr_ManTotalOriginCount( Nr_Man_t * p ) { Nr_Entry_t * pEntry; diff --git a/src/base/abc/node_retention.h b/src/base/abc/node_retention.h index 909bd709c6..92f4541704 100644 --- a/src/base/abc/node_retention.h +++ b/src/base/abc/node_retention.h @@ -83,6 +83,7 @@ extern void Nr_ManPrintOrigins( Nr_Man_t * p, int NodeId ); extern void Nr_ManPrintAllOrigins( Nr_Man_t * p ); extern void Nr_ManPrintDebug( Nr_Man_t * p, char * pFuncName ); extern void Nr_ManPrintRetentionMap( FILE * pFile, Abc_Ntk_t * pNtk, Nr_Man_t * p ); +extern void Nr_ManPrintRetentionMapGia( FILE * pFile, Gia_Man_t * pGia, Nr_Man_t * p ); extern int Nr_ManTotalOriginCount( Nr_Man_t * p ); extern Nr_Man_t * Nr_ManPrune( Nr_Man_t * p ); extern void Nr_ManValidateEntries( Abc_Ntk_t * pNtk, Nr_Man_t * p ); diff --git a/src/base/abci/abc.c b/src/base/abci/abc.c index b3e8621c97..005c836d74 100644 --- a/src/base/abci/abc.c +++ b/src/base/abci/abc.c @@ -66,6 +66,7 @@ #include "base/acb/acbPar.h" #include "misc/extra/extra.h" #include "opt/eslim/eSLIM.h" +#include "base/abc/node_retention.h" #ifndef _WIN32 @@ -424,6 +425,7 @@ static int Abc_CommandAbc9ReadStg ( Abc_Frame_t * pAbc, int argc, cha static int Abc_CommandAbc9ReadVer ( Abc_Frame_t * pAbc, int argc, char ** argv ); static int Abc_CommandAbc9WriteVer ( Abc_Frame_t * pAbc, int argc, char ** argv ); static int Abc_CommandAbc9Write ( Abc_Frame_t * pAbc, int argc, char ** argv ); +static int Abc_CommandAbc9WriteRetention ( Abc_Frame_t * pAbc, int argc, char ** argv ); static int Abc_CommandAbc9WriteLut ( Abc_Frame_t * pAbc, int argc, char ** argv ); static int Abc_CommandAbc9Ps ( Abc_Frame_t * pAbc, int argc, char ** argv ); static int Abc_CommandAbc9PFan ( Abc_Frame_t * pAbc, int argc, char ** argv ); @@ -1260,6 +1262,7 @@ void Abc_Init( Abc_Frame_t * pAbc ) Cmd_CommandAdd( pAbc, "ABC9", "&write_ver", Abc_CommandAbc9WriteVer, 0 ); Cmd_CommandAdd( pAbc, "ABC9", "&w", Abc_CommandAbc9Write, 0 ); Cmd_CommandAdd( pAbc, "ABC9", "&write", Abc_CommandAbc9Write, 0 ); + Cmd_CommandAdd( pAbc, "ABC9", "&write_retention", Abc_CommandAbc9WriteRetention, 0 ); Cmd_CommandAdd( pAbc, "ABC9", "&wlut", Abc_CommandAbc9WriteLut, 0 ); Cmd_CommandAdd( pAbc, "ABC9", "&ps", Abc_CommandAbc9Ps, 0 ); Cmd_CommandAdd( pAbc, "ABC9", "&pfan", Abc_CommandAbc9PFan, 0 ); @@ -34106,6 +34109,18 @@ int Abc_CommandAbc9Read( Abc_Frame_t * pAbc, int argc, char ** argv ) Gia_ManStop( pTemp ); } } + // Seed node retention with self-origins for all CI and AND objects. + // This establishes the baseline so that subsequent GIA transformations + // can track which original nodes contributed to each post-opt node. + if ( pAig && pAig->pNodeRetention ) + { + Gia_Obj_t * pObj; + int k; + Gia_ManForEachCi( pAig, pObj, k ) + Nr_ManAddOrigin( pAig->pNodeRetention, Gia_ObjId(pAig, pObj), Gia_ObjId(pAig, pObj) ); + Gia_ManForEachAnd( pAig, pObj, k ) + Nr_ManAddOrigin( pAig->pNodeRetention, Gia_ObjId(pAig, pObj), Gia_ObjId(pAig, pObj) ); + } if ( pAig ) Abc_FrameUpdateGia( pAbc, pAig ); return 0; @@ -35310,6 +35325,82 @@ int Abc_CommandAbc9Write( Abc_Frame_t * pAbc, int argc, char ** argv ) return 1; } +/**Function************************************************************* + + Synopsis [Writes GIA node retention map to a file.] + + Description [Writes the retention map from the current GIA's + pNodeRetention manager. The output contains CI position + to GIA object ID mappings and AND/CO node to origin + GIA ID mappings, enabling Yosys to recover source + location attributes through ABC synthesis.] + + SideEffects [] + + SeeAlso [Nr_ManPrintRetentionMapGia] + +***********************************************************************/ +int Abc_CommandAbc9WriteRetention( Abc_Frame_t * pAbc, int argc, char ** argv ) +{ + char * pFileName; + char ** pArgvNew; + int c, nArgcNew; + int fVerbose = 0; + FILE * pFile; + Extra_UtilGetoptReset(); + while ( ( c = Extra_UtilGetopt( argc, argv, "vh" ) ) != EOF ) + { + switch ( c ) + { + case 'v': + fVerbose ^= 1; + break; + case 'h': + goto usage; + default: + goto usage; + } + } + pArgvNew = argv + globalUtilOptind; + nArgcNew = argc - globalUtilOptind; + if ( nArgcNew != 1 ) + { + Abc_Print( -1, "There is no file name.\n" ); + return 1; + } + if ( pAbc->pGia == NULL ) + { + Abc_Print( -1, "Abc_CommandAbc9WriteRetention(): There is no GIA.\n" ); + return 1; + } + if ( pAbc->pGia->pNodeRetention == NULL ) + { + Abc_Print( -1, "Abc_CommandAbc9WriteRetention(): GIA has no node retention data.\n" ); + return 1; + } + pFileName = pArgvNew[0]; + pFile = fopen( pFileName, "w" ); + if ( pFile == NULL ) + { + Abc_Print( -1, "Cannot open output file \"%s\".\n", pFileName ); + return 1; + } + Nr_ManPrintRetentionMapGia( pFile, pAbc->pGia, pAbc->pGia->pNodeRetention ); + fclose( pFile ); + if ( fVerbose ) + Abc_Print( 1, "Written retention map for GIA with %d CIs, %d ANDs, %d COs to \"%s\".\n", + Gia_ManCiNum(pAbc->pGia), Gia_ManAndNum(pAbc->pGia), Gia_ManCoNum(pAbc->pGia), pFileName ); + return 0; + +usage: + Abc_Print( -2, "usage: &write_retention [-vh] \n" ); + Abc_Print( -2, "\t writes the GIA node retention map to a file\n" ); + Abc_Print( -2, "\t-v : toggle verbose output [default = %s]\n", fVerbose? "yes": "no" ); + Abc_Print( -2, "\t-h : print the command usage\n"); + Abc_Print( -2, "\t : the file name\n"); + return 1; +} + /**Function************************************************************* Synopsis [] diff --git a/test/gia/gia_test.cc b/test/gia/gia_test.cc index a6b288f791..ff6a8974df 100644 --- a/test/gia/gia_test.cc +++ b/test/gia/gia_test.cc @@ -1,6 +1,7 @@ #include "gtest/gtest.h" #include "aig/gia/gia.h" +#include "base/abc/node_retention.h" ABC_NAMESPACE_IMPL_START @@ -51,4 +52,108 @@ TEST(GiaTest, CanAddAnAndGate) { Gia_ManStop(aig_manager); } +TEST(GiaRetentionTest, NewGiaHasRetentionManager) { + Gia_Man_t* p = Gia_ManStart(100); + EXPECT_NE(p->pNodeRetention, nullptr); + Gia_ManStop(p); +} + +TEST(GiaRetentionTest, SeedAndRetrieveOrigins) { + Gia_Man_t* p = Gia_ManStart(100); + int ci_lit = Gia_ManAppendCi(p); + int ci_id = Abc_Lit2Var(ci_lit); + + // Seed CI with self-origin + Nr_ManAddOrigin(p->pNodeRetention, ci_id, ci_id); + + Vec_Int_t* origins = Nr_ManGetOrigins(p->pNodeRetention, ci_id); + ASSERT_NE(origins, nullptr); + EXPECT_EQ(Vec_IntSize(origins), 1); + EXPECT_EQ(Vec_IntEntry(origins, 0), ci_id); + + Gia_ManStop(p); +} + +TEST(GiaRetentionTest, RetentionPropagatesToDup) { + // Build a simple GIA: two CIs -> AND -> CO + Gia_Man_t* p = Gia_ManStart(100); + p->pName = Abc_UtilStrsav((char*)"test"); + int ci1_lit = Gia_ManAppendCi(p); + int ci2_lit = Gia_ManAppendCi(p); + int ci1_id = Abc_Lit2Var(ci1_lit); + int ci2_id = Abc_Lit2Var(ci2_lit); + + // Seed CIs with self-origins + Nr_ManAddOrigin(p->pNodeRetention, ci1_id, ci1_id); + Nr_ManAddOrigin(p->pNodeRetention, ci2_id, ci2_id); + + int and_lit = Gia_ManAppendAnd(p, ci1_lit, ci2_lit); + int and_id = Abc_Lit2Var(and_lit); + Nr_ManAddOrigin(p->pNodeRetention, and_id, and_id); + + Gia_ManAppendCo(p, and_lit); + + // Rehash (creates a new GIA with retention propagated) + Gia_Man_t* pNew = Gia_ManRehash(p, 0); + + // Check that the new GIA has retention entries + ASSERT_NE(pNew->pNodeRetention, nullptr); + EXPECT_GT(Nr_ManNumEntries(pNew->pNodeRetention), 0); + + // The new CI nodes should have origins pointing back to original CI IDs + Gia_Obj_t* pObj; + int i; + Gia_ManForEachCi(pNew, pObj, i) { + int newId = Gia_ObjId(pNew, pObj); + Vec_Int_t* origins = Nr_ManGetOrigins(pNew->pNodeRetention, newId); + ASSERT_NE(origins, nullptr); + EXPECT_GT(Vec_IntSize(origins), 0); + } + + Gia_ManStop(pNew); + Gia_ManStop(p); +} + +TEST(GiaRetentionTest, PrintRetentionMapGiaFormat) { + // Build a simple GIA and write retention map + Gia_Man_t* p = Gia_ManStart(100); + p->pName = Abc_UtilStrsav((char*)"test"); + int ci1_lit = Gia_ManAppendCi(p); + int ci2_lit = Gia_ManAppendCi(p); + + Nr_ManAddOrigin(p->pNodeRetention, Abc_Lit2Var(ci1_lit), Abc_Lit2Var(ci1_lit)); + Nr_ManAddOrigin(p->pNodeRetention, Abc_Lit2Var(ci2_lit), Abc_Lit2Var(ci2_lit)); + + int and_lit = Gia_ManAppendAnd(p, ci1_lit, ci2_lit); + Nr_ManAddOrigin(p->pNodeRetention, Abc_Lit2Var(and_lit), Abc_Lit2Var(ci1_lit)); + Nr_ManAddOrigin(p->pNodeRetention, Abc_Lit2Var(and_lit), Abc_Lit2Var(ci2_lit)); + + Gia_ManAppendCo(p, and_lit); + + // Write to a temp file + FILE* f = tmpfile(); + ASSERT_NE(f, nullptr); + Nr_ManPrintRetentionMapGia(f, p, p->pNodeRetention); + + // Read back and verify format + rewind(f); + char buf[256]; + // First line: .gia_retention_begin + ASSERT_NE(fgets(buf, sizeof(buf), f), nullptr); + EXPECT_STREQ(buf, ".gia_retention_begin\n"); + + // CI lines + ASSERT_NE(fgets(buf, sizeof(buf), f), nullptr); + EXPECT_TRUE(strncmp(buf, "CI ", 3) == 0); + ASSERT_NE(fgets(buf, sizeof(buf), f), nullptr); + EXPECT_TRUE(strncmp(buf, "CI ", 3) == 0); + + // AND node SRC line + ASSERT_NE(fgets(buf, sizeof(buf), f), nullptr); + EXPECT_TRUE(strstr(buf, " SRC ") != nullptr); + + fclose(f); + Gia_ManStop(p); +} + ABC_NAMESPACE_IMPL_END