/* * 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 3 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, see . */ #include #include #include #include #include #include #include #include #include #include #include /* Single-ended matched length + skew + via count test. Errors generated: - DRCE_LENGTH_OUT_OF_RANGE - DRCE_SKEW_OUT_OF_RANGE - DRCE_TOO_MANY_VIAS Todo: arc support */ class DRC_TEST_PROVIDER_MATCHED_LENGTH : public DRC_TEST_PROVIDER { public: DRC_TEST_PROVIDER_MATCHED_LENGTH () : m_board( nullptr ) { } 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; DRC_LENGTH_REPORT BuildLengthReport() const; private: bool runInternal( bool aDelayReportMode = false ); using LENGTH_ENTRY = DRC_LENGTH_REPORT::ENTRY; typedef std::set CITEMS; typedef std::vector LENGTH_ENTRIES; void checkLengthViolations( DRC_CONSTRAINT& aConstraint, LENGTH_ENTRIES& aMatchedConnections ); void checkSkewViolations( DRC_CONSTRAINT& aConstraint, LENGTH_ENTRIES& aMatchedConnections ); void checkViaCountViolations( DRC_CONSTRAINT& aConstraint, LENGTH_ENTRIES& aMatchedConnections ); BOARD* m_board; DRC_LENGTH_REPORT m_report; }; static int computeViaThruLength( VIA *aVia, const std::set &conns ) { return 0; // fixme: not yet there... } void DRC_TEST_PROVIDER_MATCHED_LENGTH::checkLengthViolations( DRC_CONSTRAINT& aConstraint, LENGTH_ENTRIES& matchedConnections ) { for( const DRC_LENGTH_REPORT::ENTRY& ent : matchedConnections ) { bool minViolation = false; bool maxViolation = false; int minLen = 0; int maxLen = 0; if( aConstraint.GetValue().HasMin() && ent.total < aConstraint.GetValue().Min() ) { minViolation = true; minLen = aConstraint.GetValue().Min(); } else if( aConstraint.GetValue().HasMax() && ent.total > aConstraint.GetValue().Max() ) { maxViolation = true; maxLen = aConstraint.GetValue().Max(); } if( ( minViolation || maxViolation ) ) { std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_LENGTH_OUT_OF_RANGE ); if( minViolation ) { m_msg.Printf( _( "(%s min length: %s; actual: %s)" ), aConstraint.GetName(), MessageTextFromValue( userUnits(), minLen ), MessageTextFromValue( userUnits(), ent.total ) ); } else if( maxViolation ) { m_msg.Printf( _( "(%s max length: %s; actual: %s)" ), aConstraint.GetName(), MessageTextFromValue( userUnits(), maxLen ), MessageTextFromValue( userUnits(), ent.total ) ); } drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + m_msg ); for( auto offendingTrack : ent.items ) drcItem->AddItem( offendingTrack ); drcItem->SetViolatingRule( aConstraint.GetParentRule() ); reportViolation( drcItem, (*ent.items.begin() )->GetPosition() ); } } } void DRC_TEST_PROVIDER_MATCHED_LENGTH::checkSkewViolations( DRC_CONSTRAINT& aConstraint, LENGTH_ENTRIES& matchedConnections ) { int avgLength = 0; for( const DRC_LENGTH_REPORT::ENTRY& ent : matchedConnections ) avgLength += ent.total; avgLength /= matchedConnections.size(); for( const auto& ent : matchedConnections ) { int skew = ent.total - avgLength; if( aConstraint.GetValue().HasMax() && abs( skew ) > aConstraint.GetValue().Max() ) { std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_SKEW_OUT_OF_RANGE ); m_msg.Printf( _( "(%s max skew: %s; actual: %s; average net length: %s; actual: %s)" ), aConstraint.GetName(), MessageTextFromValue( userUnits(), aConstraint.GetValue().Max() ), MessageTextFromValue( userUnits(), skew ), MessageTextFromValue( userUnits(), avgLength ), MessageTextFromValue( userUnits(), ent.total ) ); drcItem->SetErrorMessage( drcItem->GetErrorText() + " " + m_msg ); for( BOARD_CONNECTED_ITEM* offendingTrack : ent.items ) drcItem->SetItems( offendingTrack ); drcItem->SetViolatingRule( aConstraint.GetParentRule() ); reportViolation( drcItem, (*ent.items.begin() )->GetPosition() ); } } } void DRC_TEST_PROVIDER_MATCHED_LENGTH::checkViaCountViolations( DRC_CONSTRAINT& aConstraint, LENGTH_ENTRIES& matchedConnections ) { for( const auto& ent : matchedConnections ) { if( aConstraint.GetValue().HasMax() && ent.viaCount > aConstraint.GetValue().Max() ) { std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_TOO_MANY_VIAS ); m_msg.Printf( _( "(%s max count: %d; actual: %d)" ), aConstraint.GetName(), aConstraint.GetValue().Max(), ent.viaCount ); drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + m_msg ); for( auto offendingTrack : ent.items ) drcItem->SetItems( offendingTrack ); drcItem->SetViolatingRule( aConstraint.GetParentRule() ); reportViolation( drcItem, (*ent.items.begin() )->GetPosition() ); } } } bool DRC_TEST_PROVIDER_MATCHED_LENGTH::Run() { return runInternal( false ); } bool DRC_TEST_PROVIDER_MATCHED_LENGTH::runInternal( bool aDelayReportMode ) { m_board = m_drcEngine->GetBoard(); m_report.Clear(); if( !aDelayReportMode ) { if( !reportPhase( _( "Gathering length-constrained connections..." ) ) ) return false; } std::map itemSets; auto evaluateLengthConstraints = [&]( BOARD_ITEM *item ) -> bool { const DRC_CONSTRAINT_TYPE_T constraintsToCheck[] = { LENGTH_CONSTRAINT, SKEW_CONSTRAINT, VIA_COUNT_CONSTRAINT, }; for( int i = 0; i < 3; i++ ) { auto constraint = m_drcEngine->EvalRulesForItems( constraintsToCheck[i], item, nullptr, item->GetLayer() ); if( constraint.IsNull() ) continue; auto citem = static_cast( item ); itemSets[ constraint.GetParentRule() ].insert( citem ); } return true; }; auto ftCache = m_board->GetConnectivity()->GetFromToCache(); ftCache->Rebuild( m_board ); forEachGeometryItem( { PCB_TRACE_T, PCB_VIA_T, PCB_ARC_T }, LSET::AllCuMask(), evaluateLengthConstraints ); std::map matches; for( auto it : itemSets ) { std::map netMap; for( auto citem : it.second ) netMap[ citem->GetNetCode() ].insert( 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; ent.fromItem = nullptr; ent.toItem = nullptr; for( BOARD_CONNECTED_ITEM* citem : nitem.second ) { if( citem->Type() == PCB_VIA_T ) { ent.viaCount++; ent.totalVia += computeViaThruLength( static_cast( citem ), nitem.second ); } else if( citem->Type() == PCB_TRACE_T ) { ent.totalRoute += static_cast( citem )->GetLength(); } else if ( citem->Type() == PCB_ARC_T ) { ent.totalRoute += static_cast( citem )->GetLength(); } else if( citem->Type() == PCB_PAD_T ) { ent.totalPadToDie += static_cast( citem )->GetPadToDieLength(); } } ent.total = ent.totalRoute + ent.totalVia + ent.totalPadToDie; ent.matchingRule = it.first; // fixme: doesn't seem to work ;-) auto ftPath = ftCache->QueryFromToPath( ent.items ); if( ftPath ) { ent.from = ftPath->fromName; ent.to = ftPath->toName; } else { ent.from = ent.to = _(""); } m_report.Add( ent ); matches[ it.first ].push_back(ent); } } if( !aDelayReportMode ) { 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( auto& ent : matchedConnections ) { reportAux(wxString::Format( " - net: %s, from: %s, to: %s, " "%d matching items, " "total: %s (tracks: %s, vias: %s, pad-to-die: %s), " "vias: %d", ent.netname, ent.from, ent.to, (int) ent.items.size(), MessageTextFromValue( userUnits(), ent.total ), MessageTextFromValue( userUnits(), ent.totalRoute ), MessageTextFromValue( userUnits(), ent.totalVia ), MessageTextFromValue( userUnits(), ent.totalPadToDie ), ent.viaCount ) ); } OPT lengthConstraint = rule->FindConstraint( LENGTH_CONSTRAINT ); if( lengthConstraint ) checkLengthViolations( *lengthConstraint, matchedConnections ); OPT skewConstraint = rule->FindConstraint( SKEW_CONSTRAINT ); if( skewConstraint ) checkSkewViolations( *skewConstraint, matchedConnections ); OPT viaCountConstraint = rule->FindConstraint( VIA_COUNT_CONSTRAINT ); if( viaCountConstraint ) checkViaCountViolations( *viaCountConstraint, matchedConnections ); } reportRuleStatistics(); } return true; } std::set DRC_TEST_PROVIDER_MATCHED_LENGTH::GetConstraintTypes() const { return { LENGTH_CONSTRAINT, SKEW_CONSTRAINT, VIA_COUNT_CONSTRAINT }; } namespace detail { static DRC_REGISTER_TEST_PROVIDER dummy; }