/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2015 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2011 Wayne Stambaugh * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors. * * 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 "connection_graph.h" #include // for ExpandEnvVarSubstitutions #include #include #include #include #include #include #include #include #include #include #include #include /* ERC tests : * 1 - conflicts between connected pins ( example: 2 connected outputs ) * 2 - minimal connections requirements ( 1 input *must* be connected to an * output, or a passive pin ) */ /* * Minimal ERC requirements: * All pins *must* be connected (except ELECTRICAL_PINTYPE::PT_NC). * When a pin is not connected in schematic, the user must place a "non * connected" symbol to this pin. * This ensures a forgotten connection will be detected. */ // Messages for matrix rows: const wxString CommentERC_H[] = { _( "Input Pin" ), _( "Output Pin" ), _( "Bidirectional Pin" ), _( "Tri-State Pin" ), _( "Passive Pin" ), _( "Free Pin" ), _( "Unspecified Pin" ), _( "Power Input Pin" ), _( "Power Output Pin" ), _( "Open Collector" ), _( "Open Emitter" ), _( "No Connection" ) }; // Messages for matrix columns const wxString CommentERC_V[] = { _( "Input Pin" ), _( "Output Pin" ), _( "Bidirectional Pin" ), _( "Tri-State Pin" ), _( "Passive Pin" ), _( "Free Pin" ), _( "Unspecified Pin" ), _( "Power Input Pin" ), _( "Power Output Pin" ), _( "Open Collector" ), _( "Open Emitter" ), _( "No Connection" ) }; // List of pin types that are considered drivers for usual input pins // i.e. pin type = ELECTRICAL_PINTYPE::PT_INPUT, but not PT_POWER_IN // that need only a PT_POWER_OUT pin type to be driven const std::set DrivingPinTypes = { ELECTRICAL_PINTYPE::PT_OUTPUT, ELECTRICAL_PINTYPE::PT_POWER_OUT, ELECTRICAL_PINTYPE::PT_PASSIVE, ELECTRICAL_PINTYPE::PT_TRISTATE, ELECTRICAL_PINTYPE::PT_BIDI }; // List of pin types that are considered drivers for power pins // In fact only a ELECTRICAL_PINTYPE::PT_POWER_OUT pin type can drive // power input pins const std::set DrivingPowerPinTypes = { ELECTRICAL_PINTYPE::PT_POWER_OUT }; // List of pin types that require a driver elsewhere on the net const std::set DrivenPinTypes = { ELECTRICAL_PINTYPE::PT_INPUT, ELECTRICAL_PINTYPE::PT_POWER_IN }; int ERC_TESTER::TestDuplicateSheetNames( bool aCreateMarker ) { SCH_SCREEN* screen; int err_count = 0; SCH_SCREENS screenList( m_schematic->Root() ); for( screen = screenList.GetFirst(); screen != nullptr; screen = screenList.GetNext() ) { std::vector list; for( SCH_ITEM* item : screen->Items().OfType( SCH_SHEET_T ) ) list.push_back( static_cast( item ) ); for( size_t i = 0; i < list.size(); i++ ) { SCH_SHEET* sheet = list[i]; for( size_t j = i + 1; j < list.size(); j++ ) { SCH_SHEET* test_item = list[j]; // We have found a second sheet: compare names // we are using case insensitive comparison to avoid mistakes between // similar names like Mysheet and mysheet if( sheet->GetName().CmpNoCase( test_item->GetName() ) == 0 ) { if( aCreateMarker ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_DUPLICATE_SHEET_NAME ); ercItem->SetItems( sheet, test_item ); SCH_MARKER* marker = new SCH_MARKER( ercItem, sheet->GetPosition() ); screen->Append( marker ); } err_count++; } } } } return err_count; } void ERC_TESTER::TestTextVars( DS_PROXY_VIEW_ITEM* aDrawingSheet ) { DS_DRAW_ITEM_LIST wsItems; auto unresolved = [this]( wxString str ) { str = ExpandEnvVarSubstitutions( str, &m_schematic->Prj() ); return str.Matches( wxT( "*${*}*" ) ); }; if( aDrawingSheet ) { wsItems.SetMilsToIUfactor( IU_PER_MILS ); wsItems.SetPageNumber( wxT( "1" ) ); wsItems.SetSheetCount( 1 ); wsItems.SetFileName( wxT( "dummyFilename" ) ); wsItems.SetSheetName( wxT( "dummySheet" ) ); wsItems.SetSheetLayer( wxT( "dummyLayer" ) ); wsItems.SetProject( &m_schematic->Prj() ); wsItems.BuildDrawItemsList( aDrawingSheet->GetPageInfo(), aDrawingSheet->GetTitleBlock()); } SCH_SHEET_PATH savedCurrentSheet = m_schematic->CurrentSheet(); SCH_SHEET_LIST sheets = m_schematic->GetSheets(); for( SCH_SHEET_PATH& sheet : sheets ) { m_schematic->SetCurrentSheet( sheet ); SCH_SCREEN* screen = sheet.LastScreen(); for( SCH_ITEM* item : screen->Items().OfType( SCH_LOCATE_ANY_T ) ) { if( item->Type() == SCH_SYMBOL_T ) { SCH_SYMBOL* symbol = static_cast( item ); for( SCH_FIELD& field : symbol->GetFields() ) { if( unresolved( field.GetShownText() ) ) { wxPoint pos = field.GetPosition() - symbol->GetPosition(); pos = symbol->GetTransform().TransformCoordinate( pos ); pos += symbol->GetPosition(); std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_UNRESOLVED_VARIABLE ); ercItem->SetItems( &field ); SCH_MARKER* marker = new SCH_MARKER( ercItem, pos ); screen->Append( marker ); } } } else if( item->Type() == SCH_SHEET_T ) { SCH_SHEET* subSheet = static_cast( item ); for( SCH_FIELD& field : subSheet->GetFields() ) { if( unresolved( field.GetShownText() ) ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_UNRESOLVED_VARIABLE ); ercItem->SetItems( &field ); SCH_MARKER* marker = new SCH_MARKER( ercItem, field.GetPosition() ); screen->Append( marker ); } } for( SCH_SHEET_PIN* pin : static_cast( item )->GetPins() ) { if( pin->GetShownText().Matches( wxT( "*${*}*" ) ) ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_UNRESOLVED_VARIABLE ); ercItem->SetItems( pin ); SCH_MARKER* marker = new SCH_MARKER( ercItem, pin->GetPosition() ); screen->Append( marker ); } } } else if( SCH_TEXT* text = dynamic_cast( item ) ) { if( text->GetShownText().Matches( wxT( "*${*}*" ) ) ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_UNRESOLVED_VARIABLE ); ercItem->SetItems( text ); SCH_MARKER* marker = new SCH_MARKER( ercItem, text->GetPosition() ); screen->Append( marker ); } } } for( DS_DRAW_ITEM_BASE* item = wsItems.GetFirst(); item; item = wsItems.GetNext() ) { if( DS_DRAW_ITEM_TEXT* text = dynamic_cast( item ) ) { if( text->GetShownText().Matches( wxT( "*${*}*" ) ) ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_UNRESOLVED_VARIABLE ); ercItem->SetErrorMessage( _( "Unresolved text variable in drawing sheet." ) ); SCH_MARKER* marker = new SCH_MARKER( ercItem, text->GetPosition() ); screen->Append( marker ); } } } } m_schematic->SetCurrentSheet( savedCurrentSheet ); } int ERC_TESTER::TestConflictingBusAliases() { wxString msg; int err_count = 0; SCH_SCREENS screens( m_schematic->Root() ); std::vector< std::shared_ptr > aliases; for( SCH_SCREEN* screen = screens.GetFirst(); screen != nullptr; screen = screens.GetNext() ) { std::unordered_set< std::shared_ptr > screen_aliases = screen->GetBusAliases(); for( const std::shared_ptr& alias : screen_aliases ) { for( const std::shared_ptr& test : aliases ) { if( alias->GetName() == test->GetName() && alias->Members() != test->Members() ) { msg.Printf( _( "Bus alias %s has conflicting definitions on %s and %s" ), alias->GetName(), alias->GetParent()->GetFileName(), test->GetParent()->GetFileName() ); std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_BUS_ALIAS_CONFLICT ); ercItem->SetErrorMessage( msg ); SCH_MARKER* marker = new SCH_MARKER( ercItem, wxPoint() ); test->GetParent()->Append( marker ); ++err_count; } } } aliases.insert( aliases.end(), screen_aliases.begin(), screen_aliases.end() ); } return err_count; } int ERC_TESTER::TestMultiunitFootprints() { SCH_SHEET_LIST sheets = m_schematic->GetSheets(); int errors = 0; std::map footprints; SCH_MULTI_UNIT_REFERENCE_MAP refMap; sheets.GetMultiUnitSymbols( refMap, true ); for( std::pair& symbol : refMap ) { SCH_REFERENCE_LIST& refList = symbol.second; if( refList.GetCount() == 0 ) { wxFAIL; // it should not happen continue; } // Reference footprint SCH_SYMBOL* unit = nullptr; wxString unitName; wxString unitFP; for( unsigned i = 0; i < refList.GetCount(); ++i ) { SCH_SHEET_PATH sheetPath = refList.GetItem( i ).GetSheetPath(); unitFP = refList.GetItem( i ).GetFootprint(); if( !unitFP.IsEmpty() ) { unit = refList.GetItem( i ).GetSymbol(); unitName = unit->GetRef( &sheetPath, true ); break; } } for( unsigned i = 0; i < refList.GetCount(); ++i ) { SCH_REFERENCE& secondRef = refList.GetItem( i ); SCH_SYMBOL* secondUnit = secondRef.GetSymbol(); wxString secondName = secondUnit->GetRef( &secondRef.GetSheetPath(), true ); const wxString secondFp = secondRef.GetFootprint(); wxString msg; if( unit && !secondFp.IsEmpty() && unitFP != secondFp ) { msg.Printf( _( "Different footprints assigned to %s and %s" ), unitName, secondName ); std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_DIFFERENT_UNIT_FP ); ercItem->SetErrorMessage( msg ); ercItem->SetItems( unit, secondUnit ); SCH_MARKER* marker = new SCH_MARKER( ercItem, secondUnit->GetPosition() ); secondRef.GetSheetPath().LastScreen()->Append( marker ); ++errors; } } } return errors; } int ERC_TESTER::TestNoConnectPins() { int err_count = 0; for( const SCH_SHEET_PATH& sheet : m_schematic->GetSheets() ) { std::map> pinMap; for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) ) { SCH_SYMBOL* symbol = static_cast( item ); for( SCH_PIN* pin : symbol->GetPins( &sheet ) ) { if( pin->GetLibPin()->GetType() == ELECTRICAL_PINTYPE::PT_NC ) pinMap[pin->GetPosition()].emplace_back( pin ); } } for( auto& pair : pinMap ) { if( pair.second.size() > 1 ) { err_count++; std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_NOCONNECT_CONNECTED ); ercItem->SetItems( pair.second[0], pair.second[1], pair.second.size() > 2 ? pair.second[2] : nullptr, pair.second.size() > 3 ? pair.second[3] : nullptr ); ercItem->SetErrorMessage( _( "Pins with \"no connection\" type are connected" ) ); SCH_MARKER* marker = new SCH_MARKER( ercItem, pair.first ); sheet.LastScreen()->Append( marker ); } } } return err_count; } int ERC_TESTER::TestPinToPin() { ERC_SETTINGS& settings = m_schematic->ErcSettings(); const NET_MAP& nets = m_schematic->ConnectionGraph()->GetNetMap(); int errors = 0; for( const std::pair> net : nets ) { std::vector pins; std::unordered_map pinToScreenMap; for( CONNECTION_SUBGRAPH* subgraph: net.second ) { for( EDA_ITEM* item : subgraph->m_items ) { if( item->Type() == SCH_PIN_T ) { pins.emplace_back( static_cast( item ) ); pinToScreenMap[item] = subgraph->m_sheet.LastScreen(); } } } // Single-pin nets are handled elsewhere if( pins.size() < 2 ) continue; std::set> tested; SCH_PIN* needsDriver = nullptr; bool hasDriver = false; // We need different drivers for power nets and normal nets. // A power net has at least one pin having the ELECTRICAL_PINTYPE::PT_POWER_IN // and power nets can be driven only by ELECTRICAL_PINTYPE::PT_POWER_OUT pins bool ispowerNet = false; for( SCH_PIN* refPin : pins ) { if( refPin->GetType() == ELECTRICAL_PINTYPE::PT_POWER_IN ) { ispowerNet = true; break; } } for( SCH_PIN* refPin : pins ) { ELECTRICAL_PINTYPE refType = refPin->GetType(); if( DrivenPinTypes.count( refType ) ) { // needsDriver will be the pin shown in the error report eventually, so try to // upgrade to a "better" pin if possible: something visible and only a power symbol // if this net needs a power driver if( !needsDriver || ( !needsDriver->IsVisible() && refPin->IsVisible() ) || ( ispowerNet != ( needsDriver->GetType() == ELECTRICAL_PINTYPE::PT_POWER_IN ) && ispowerNet == ( refType == ELECTRICAL_PINTYPE::PT_POWER_IN ) ) ) { needsDriver = refPin; } } if( ispowerNet ) hasDriver |= ( DrivingPowerPinTypes.count( refType ) != 0 ); else hasDriver |= ( DrivingPinTypes.count( refType ) != 0 ); for( SCH_PIN* testPin : pins ) { if( testPin == refPin ) continue; std::pair pair1 = std::make_pair( refPin, testPin ); std::pair pair2 = std::make_pair( testPin, refPin ); if( tested.count( pair1 ) || tested.count( pair2 ) ) continue; tested.insert( pair1 ); tested.insert( pair2 ); ELECTRICAL_PINTYPE testType = testPin->GetType(); if( ispowerNet ) hasDriver |= ( DrivingPowerPinTypes.count( testType ) != 0 ); else hasDriver |= ( DrivingPinTypes.count( testType ) != 0 ); PIN_ERROR erc = settings.GetPinMapValue( refType, testType ); if( erc != PIN_ERROR::OK && settings.IsTestEnabled( ERCE_PIN_TO_PIN_WARNING ) ) { std::shared_ptr ercItem = ERC_ITEM::Create( erc == PIN_ERROR::WARNING ? ERCE_PIN_TO_PIN_WARNING : ERCE_PIN_TO_PIN_ERROR ); ercItem->SetItems( refPin, testPin ); ercItem->SetIsSheetSpecific(); ercItem->SetErrorMessage( wxString::Format( _( "Pins of type %s and %s are connected" ), ElectricalPinTypeGetText( refType ), ElectricalPinTypeGetText( testType ) ) ); SCH_MARKER* marker = new SCH_MARKER( ercItem, refPin->GetTransformedPosition() ); pinToScreenMap[refPin]->Append( marker ); errors++; } } } if( needsDriver && !hasDriver ) { int err_code = ispowerNet ? ERCE_POWERPIN_NOT_DRIVEN : ERCE_PIN_NOT_DRIVEN; if( settings.IsTestEnabled( err_code ) ) { std::shared_ptr ercItem = ERC_ITEM::Create( err_code ); ercItem->SetItems( needsDriver ); SCH_MARKER* marker = new SCH_MARKER( ercItem, needsDriver->GetTransformedPosition() ); pinToScreenMap[needsDriver]->Append( marker ); errors++; } } } return errors; } int ERC_TESTER::TestMultUnitPinConflicts() { const NET_MAP& nets = m_schematic->ConnectionGraph()->GetNetMap(); int errors = 0; std::unordered_map> pinToNetMap; for( const std::pair> net : nets ) { const wxString& netName = net.first.first; std::vector pins; for( CONNECTION_SUBGRAPH* subgraph : net.second ) { for( EDA_ITEM* item : subgraph->m_items ) { if( item->Type() == SCH_PIN_T ) { SCH_PIN* pin = static_cast( item ); if( !pin->GetLibPin()->GetParent()->IsMulti() ) continue; wxString name = pin->GetParentSymbol()->GetRef( &subgraph->m_sheet ) + + wxT( ":" ) + pin->GetShownNumber(); if( !pinToNetMap.count( name ) ) { pinToNetMap[name] = std::make_pair( netName, pin ); } else if( pinToNetMap[name].first != netName ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_DIFFERENT_UNIT_NET ); ercItem->SetErrorMessage( wxString::Format( _( "Pin %s is connected to both %s and %s" ), pin->GetShownNumber(), netName, pinToNetMap[name].first ) ); ercItem->SetItems( pin, pinToNetMap[name].second ); ercItem->SetIsSheetSpecific(); SCH_MARKER* marker = new SCH_MARKER( ercItem, pin->GetTransformedPosition() ); subgraph->m_sheet.LastScreen()->Append( marker ); errors += 1; } } } } } return errors; } int ERC_TESTER::TestSimilarLabels() { const NET_MAP& nets = m_schematic->ConnectionGraph()->GetNetMap(); int errors = 0; std::unordered_map labelMap; for( const std::pair> net : nets ) { std::vector pins; for( CONNECTION_SUBGRAPH* subgraph : net.second ) { for( EDA_ITEM* item : subgraph->m_items ) { switch( item->Type() ) { case SCH_LABEL_T: case SCH_HIER_LABEL_T: case SCH_GLOBAL_LABEL_T: { SCH_TEXT* text = static_cast( item ); wxString normalized = text->GetShownText().Lower(); if( !labelMap.count( normalized ) ) { labelMap[normalized] = text; } else if( labelMap.at( normalized )->GetShownText() != text->GetShownText() ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_SIMILAR_LABELS ); ercItem->SetItems( text, labelMap.at( normalized ) ); SCH_MARKER* marker = new SCH_MARKER( ercItem, text->GetPosition() ); subgraph->m_sheet.LastScreen()->Append( marker ); errors += 1; } break; } default: break; } } } } return errors; } int ERC_TESTER::TestLibSymbolIssues() { wxCHECK( m_schematic, 0 ); SYMBOL_LIB_TABLE* libTable = m_schematic->Prj().SchSymbolLibTable(); wxString msg; int err_count = 0; SCH_SCREENS screens( m_schematic->Root() ); for( SCH_SCREEN* screen = screens.GetFirst(); screen != nullptr; screen = screens.GetNext() ) { std::vector markers; for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) ) { SCH_SYMBOL* symbol = dynamic_cast( item ); wxCHECK2( symbol, continue ); LIB_SYMBOL* libSymbolInSchematic = symbol->GetLibSymbolRef().get(); wxCHECK2( libSymbolInSchematic, continue ); wxString libName = symbol->GetLibId().GetLibNickname(); LIB_TABLE_ROW* libTableRow = libTable->FindRow( libName, true ); if( !libTableRow ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_LIB_SYMBOL_ISSUES ); ercItem->SetItems( symbol ); msg.Printf( _( "The current configuration does not include the library '%s'." ), UnescapeString( libName ) ); ercItem->SetErrorMessage( msg ); markers.emplace_back( new SCH_MARKER( ercItem, symbol->GetPosition() ) ); continue; } else if( !libTable->HasLibrary( libName, true ) ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_LIB_SYMBOL_ISSUES ); ercItem->SetItems( symbol ); msg.Printf( _( "The library '%s' is not enabled in the current configuration." ), UnescapeString( libName ) ); ercItem->SetErrorMessage( msg ); markers.emplace_back( new SCH_MARKER( ercItem, symbol->GetPosition() ) ); continue; } wxString symbolName = symbol->GetLibId().GetLibItemName(); LIB_SYMBOL* libSymbol = SchGetLibSymbol( symbol->GetLibId(), libTable ); if( libSymbol == nullptr ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_LIB_SYMBOL_ISSUES ); ercItem->SetItems( symbol ); msg.Printf( wxT( "Symbol '%s' not found in symbol library '%s'." ), UnescapeString( symbolName ), UnescapeString( libName ) ); ercItem->SetErrorMessage( msg ); markers.emplace_back( new SCH_MARKER( ercItem, symbol->GetPosition() ) ); continue; } std::unique_ptr flattenedSymbol = libSymbol->Flatten(); constexpr int flags = LIB_ITEM::COMPARE_FLAGS::EQUALITY | LIB_ITEM::COMPARE_FLAGS::ERC; if( flattenedSymbol->Compare( *libSymbolInSchematic, flags ) != 0 ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_LIB_SYMBOL_ISSUES ); ercItem->SetItems( symbol ); msg.Printf( wxT( "Symbol '%s' has been modified in library '%s'." ), UnescapeString( symbolName ), UnescapeString( libName ) ); ercItem->SetErrorMessage( msg ); markers.emplace_back( new SCH_MARKER( ercItem, symbol->GetPosition() ) ); } } for( SCH_MARKER* marker : markers ) { screen->Append( marker ); err_count += 1; } } return err_count; }