From 2258c861c9d65a25a488eb4f587f2b1eb362dc2b Mon Sep 17 00:00:00 2001 From: Tomasz Wlostowski Date: Wed, 23 Sep 2020 23:49:34 +0200 Subject: [PATCH] drc: initial version of matched trace lengths test provider --- .../drc/drc_test_provider_matched_length.cpp | 542 ++++++++++++++++++ 1 file changed, 542 insertions(+) create mode 100644 pcbnew/drc/drc_test_provider_matched_length.cpp diff --git a/pcbnew/drc/drc_test_provider_matched_length.cpp b/pcbnew/drc/drc_test_provider_matched_length.cpp new file mode 100644 index 0000000000..a90b4b4a9f --- /dev/null +++ b/pcbnew/drc/drc_test_provider_matched_length.cpp @@ -0,0 +1,542 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2004-2020 KiCad Developers. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +/* + Single-ended matched length test. + Errors generated: + - DRCE_SIGNAL_LENGTH + +*/ + +namespace test { + +class DRC_TEST_PROVIDER_MATCHED_LENGTH : public DRC_TEST_PROVIDER +{ +public: + DRC_TEST_PROVIDER_MATCHED_LENGTH () + { + } + + virtual ~DRC_TEST_PROVIDER_MATCHED_LENGTH() + { + } + + virtual bool Run() override; + + virtual const wxString GetName() const override + { + return "length"; + }; + + virtual const wxString GetDescription() const override + { + return "Tests matched track lengths."; + } + + virtual int GetNumPhases() const override + { + return 1; + } + + virtual std::set GetConstraintTypes() const override; + + bool checkFromToPath( BOARD_CONNECTED_ITEM* aItem, const wxString& aFrom, const wxString& aTo ); + +private: + + struct FROM_TO_ENDPOINT + { + wxString name; + D_PAD* parent; + }; + + struct FROM_TO_PATH + { + int net; + D_PAD *from; + D_PAD *to; + wxString fromName, toName; + wxString fromWildcard, toWildcard; + bool isUnique; + std::set pathItems; + }; + + int cacheFromToPaths( const wxString& aFrom, const wxString& aTo ); + void buildEndpointList(); + + std::vector m_ftEndpoints; + std::vector m_ftPaths; + + BOARD* m_board; + int m_largestClearance; +}; + +}; + + +bool exprFromTo( LIBEVAL::CONTEXT* aCtx, void* self ) +{ + PCB_EXPR_VAR_REF* vref = static_cast( self ); + BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; + LIBEVAL::VALUE* result = aCtx->AllocValue(); + + LIBEVAL::VALUE* argTo = aCtx->Pop(); + LIBEVAL::VALUE* argFrom = aCtx->Pop(); + + result->Set(0.0); + aCtx->Push( result ); + + if(!item) + return false; + + auto drcEngine = item->GetBoard()->GetDesignSettings().m_DRCEngine; + + assert ( drcEngine ); + + auto lengthTestProvider = static_cast( drcEngine->GetTestProvider("length") ); + + if( lengthTestProvider->checkFromToPath( static_cast( item ), + argFrom->AsString(), + argTo->AsString() ) ) + { + result->Set(1.0); + } + + return true; +} + +void test::DRC_TEST_PROVIDER_MATCHED_LENGTH::buildEndpointList( ) +{ + m_ftEndpoints.clear(); + + for( auto mod : m_board->Modules() ) + { + for( auto pad : mod->Pads() ) + { + FROM_TO_ENDPOINT ent; + ent.name = mod->GetReference() + "-" + pad->GetName(); + ent.parent = pad; + m_ftEndpoints.push_back( ent ); + ent.name = mod->GetReference(); + ent.parent = pad; + m_ftEndpoints.push_back( ent ); + } + } +} + + +enum PATH_STATUS { + PS_OK = 0, + PS_MULTIPLE_PATHS = -1, + PS_NO_PATH = -2 +}; + +static bool isVertexVisited( CN_ITEM* v, const std::vector& path ) +{ + for( auto u : path ) + { + if ( u == v ) + return true; + } + + return false; +} + +static PATH_STATUS uniquePathBetweenNodes( CN_ITEM* u, CN_ITEM* v, std::vector& outPath ) +{ + using Path = std::vector; + std::deque Q; + + Path p; + int pathFound = false; + p.push_back( u ); + Q.push_back( p ); + + while( Q.size() ) + { + Path path = Q.front(); + Q.pop_front(); + CN_ITEM *last = path.back(); + + if( last == v ) + { + outPath = path; + if( pathFound ) + return PS_MULTIPLE_PATHS; + pathFound = true; + } + + for( auto ci : last->ConnectedItems() ) + { + bool vertexVisited = isVertexVisited(ci, path); + + for( auto &p : Q ) + if (isVertexVisited(ci, p)) + { + vertexVisited = true; + break; + } + + if (!vertexVisited) { + Path newpath(path); + newpath.push_back(ci); + Q.push_back(newpath); + } + } + } + + return pathFound ? PS_OK : PS_NO_PATH; +}; + + +int test::DRC_TEST_PROVIDER_MATCHED_LENGTH::cacheFromToPaths( const wxString& aFrom, const wxString& aTo ) +{ + std::vector paths; + auto connectivity = m_board->GetConnectivity(); + auto cnAlgo = connectivity->GetConnectivityAlgo(); + + for( auto& endpoint : m_ftEndpoints ) + { + if( WildCompareString( aFrom, endpoint.name, false ) ) + { + FROM_TO_PATH p; + p.net = endpoint.parent->GetNetCode(); + p.from = endpoint.parent; + p.to = nullptr; + paths.push_back(p); + } + } + + for( auto &path : paths ) + { + int count = 0; + auto netName = path.from->GetNetname(); + + wxString fromName = path.from->GetParent()->GetReference() + "-" + path.from->GetName(); + + const KICAD_T onlyRouting[] = { PCB_PAD_T, PCB_ARC_T, PCB_VIA_T, PCB_TRACE_T, EOT }; + + auto padCandidates = connectivity->GetConnectedItems( path.from, onlyRouting ); + D_PAD* toPad = nullptr; + + for( auto pitem : padCandidates ) + { + if( pitem == path.from ) + continue; + + if( pitem->Type() != PCB_PAD_T ) + continue; + + D_PAD *pad = static_cast( pitem ); + + wxString toName = pad->GetParent()->GetReference() + "-" + pad->GetName(); + + + for ( auto& endpoint : m_ftEndpoints ) + { + if( pad == endpoint.parent ) + { + if( WildCompareString( aTo, endpoint.name, false ) ) + { + //printf("match from-to: %s -> %s [net %s] p %p\n", (const char *)fromName, (const char *) toName, (const char *) netName, endpoint.parent ); + count++; + toPad = endpoint.parent; + + path.to = toPad; + path.fromName = fromName; + path.toName = toName; + path.fromWildcard = aFrom; + path.toWildcard = aTo; + + if( count >= 2 ) + { + //printf("Multiple targets found, aborting...\n"); + path.to = nullptr; + } + } + } + } + } + } + + int newPaths = 0; + + for( auto &path : paths ) + { + if( !path.from || !path.to ) + continue; + + + CN_ITEM *cnFrom = cnAlgo->ItemEntry( path.from ).GetItems().front(); + CN_ITEM *cnTo = cnAlgo->ItemEntry( path.to ).GetItems().front(); + CN_ITEM::CONNECTED_ITEMS upath; + + auto result = uniquePathBetweenNodes( cnFrom, cnTo, upath ); + + if( result == PS_OK ) + path.isUnique = true; + else + path.isUnique = false; + + + reportAux( wxString::Format( _("Check path: %s -> %s (net %s)"), path.fromName, path.toName, cnFrom->Parent()->GetNetname() ) ); + + if( result == PS_NO_PATH ) + continue; + + for( auto item : upath ) + { + path.pathItems.insert( item->Parent() ); + } + + m_ftPaths.push_back(path); + newPaths++; + } + + reportAux( _("Cached %d paths\n"), newPaths ); + + return newPaths; +} + +bool test::DRC_TEST_PROVIDER_MATCHED_LENGTH::checkFromToPath( BOARD_CONNECTED_ITEM* aItem, const wxString& aFrom, const wxString& aTo ) +{ + int nFromTosFound = 0; + //printf("Check %d cached paths [%p]\n", m_ftPaths.size(), aItem ); + for( int attempt = 0; attempt < 2; attempt++ ) + { + // item already belongs to path + for( auto& ftPath : m_ftPaths ) + { + if( aFrom == ftPath.fromWildcard && + aTo == ftPath.toWildcard ) + { + nFromTosFound++; + + if( ftPath.pathItems.count( aItem ) ) + { + // printf("Found cached path for %p [%s->%s]\n", aItem, (const char *)ftPath.fromName, (const char *) ftPath.toName ); + return true; + } + } + } + + if( !nFromTosFound ) + cacheFromToPaths( aFrom, aTo ); + else + return false; + } + + return false; +} + + +bool test::DRC_TEST_PROVIDER_MATCHED_LENGTH::Run() +{ + m_board = m_drcEngine->GetBoard(); + + reportPhase(( "Gathering length-constrained connections..." )); + + typedef std::vector CITEMS; + std::map itemSets; + + auto evaluateLengthConstraints = + [&]( BOARD_ITEM *item ) -> bool + { + auto constraint = m_drcEngine->EvalRulesForItems( DRC_CONSTRAINT_TYPE_LENGTH, item ); + + if( constraint.IsNull() ) + return true; + + auto citem = static_cast( item ); + + itemSets[ constraint.GetParentRule() ].push_back( citem ); + + return true; + }; + + buildEndpointList( ); + + forEachGeometryItem( { PCB_TRACE_T, PCB_VIA_T, PCB_ARC_T }, + LSET::AllCuMask(), evaluateLengthConstraints ); + + + struct LENGTH_ENTRY { + int netcode; + wxString netname; + CITEMS items; + int viaCount; + int totalRoute; + int totalVia; + int totalPadToDie; + int total; + }; + + typedef std::vector LENGTH_ENTRIES; + std::map matches; + + for( auto it : itemSets ) + { + std::map netMap; + + for( auto citem : it.second ) + netMap[ citem->GetNetCode() ].push_back( citem ); + + + for( auto nitem : netMap ) + { + LENGTH_ENTRY ent; + ent.items = nitem.second; + ent.netcode = nitem.first; + ent.netname = m_board->GetNetInfo().GetNetItem( ent.netcode )->GetNetname(); + + ent.viaCount = 0; + ent.totalRoute = 0; + ent.totalVia = 0; + ent.totalPadToDie = 0; + + for( auto citem : nitem.second ) + { + if ( auto bi = dyn_cast( citem ) ) + { + ent.viaCount++; + ent.totalVia += 0; // fixme: via thru distance + } + else if ( auto bi = dyn_cast(citem )) + { + ent.totalRoute += bi->GetLength(); + } + } + + ent.total = ent.totalRoute + ent.totalVia + ent.totalPadToDie; + + matches[ it.first ].push_back(ent); + } + } + + for( auto it : matches ) + { + DRC_RULE *rule = it.first; + auto& matchedConnections = it.second; + + std::sort( matchedConnections.begin(), matchedConnections.end(), + [] ( const LENGTH_ENTRY&a, const LENGTH_ENTRY&b ) -> int + { + return a.netname < b.netname; + } + ); + + reportAux( wxString::Format( _("Length-constrained traces for rule '%s':"), it.first->m_Name ) ); + + for( const auto& ent : matchedConnections ) + { + reportAux(wxString::Format( + " - net %s: %d matching items, total: %s (tracks: %s, vias: %s, pad-to-die: %s), vias: %d", + ent.netname, + (int) ent.items.size(), + MessageTextFromValue( userUnits(), ent.total, true ), + MessageTextFromValue( userUnits(), ent.totalRoute, true ), + MessageTextFromValue( userUnits(), ent.totalVia, true ), + MessageTextFromValue( userUnits(), ent.totalPadToDie, true ), + ent.viaCount + ) ); + } + + + OPT lengthConstraint = rule->FindConstraint( DRC_CONSTRAINT_TYPE_LENGTH ); + + if( lengthConstraint ) + { + for( const auto& ent : matchedConnections ) + { + bool minViolation = false; + bool maxViolation = false; + int minLen, maxLen; + + if( lengthConstraint->GetValue().HasMin() && ent.total < lengthConstraint->GetValue().Min() ) + { + //printf("Min violation %d %d\n", ent.total, lengthConstraint->GetValue().Min() ); + minViolation = true; + minLen = lengthConstraint->GetValue().Min(); + } + else if( lengthConstraint->GetValue().HasMax() && ent.total > lengthConstraint->GetValue().Max() ) + { + //printf("Max violation %d %d\n", ent.total, lengthConstraint->GetValue().Min() ); + maxViolation = true; + maxLen = lengthConstraint->GetValue().Max(); + } + + if( (minViolation || maxViolation) ) + { + std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_LENGTH_OUT_OF_RANGE ); + wxString msg = drcItem->GetErrorText() + " (" + lengthConstraint->GetParentRule()->m_Name + " "; + + if( minViolation ) + msg += wxString::Format( _("minimum length: %s; actual: %s)" ), + MessageTextFromValue( userUnits(), minLen, true ), + MessageTextFromValue( userUnits(), ent.total, true ) ); + else if( maxViolation ) + msg += wxString::Format( _("maximum length: %s; actual: %s)" ), + MessageTextFromValue( userUnits(), maxLen, true ), + MessageTextFromValue( userUnits(), ent.total, true ) ); + + drcItem->SetErrorMessage( msg ); + // drcItem->SetItems( aRefItem->parent, aTestItem->parent ); + drcItem->SetViolatingRule( lengthConstraint->GetParentRule() ); + + reportViolation( drcItem, wxPoint(0, 0) ); //aRefItem->parent->GetPosition() ); + } + } + } + } + + reportRuleStatistics(); + + return true; +} + + +std::set test::DRC_TEST_PROVIDER_MATCHED_LENGTH::GetConstraintTypes() const +{ + return { DRC_CONSTRAINT_TYPE_LENGTH, DRC_CONSTRAINT_TYPE_SKEW }; +} + + +namespace detail +{ + static DRC_REGISTER_TEST_PROVIDER dummy; +} \ No newline at end of file