/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2018 CERN * Copyright (C) 2021-2023 KiCad Developers, see AUTHORS.txt for contributors. * * @author Jon Evans * * 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, see . */ #include #include #include #include #include #include #include #include #include #include #include /** * * Buses can be defined in multiple ways. A bus vector consists of a prefix and * a numeric range of suffixes: * * BUS_NAME[M..N] * * For example, the bus A[3..0] will contain nets A3, A2, A1, and A0. * The BUS_NAME is required. M and N must be integers but do not need to be in * any particular order -- A[0..3] produces the same result. * * Like net names, bus names cannot contain whitespace. * * A bus group is just a grouping of signals, separated by spaces, some * of which may be bus vectors. Bus groups can have names, but do not need to. * * MEMORY{A[15..0] D[7..0] RW CE OE} * * In named bus groups, the net names are expanded as . * In the above example, the nets would be named like MEMORY.A15, MEMORY.D0, etc. * * {USB_DP USB_DN} * * In the above example, the bus is unnamed and so the underlying net names are * just USB_DP and USB_DN. * */ SCH_CONNECTION::SCH_CONNECTION( SCH_ITEM* aParent, const SCH_SHEET_PATH& aPath ) : m_sheet( aPath ), m_local_sheet( aPath ), m_parent( aParent ), m_driver( nullptr ), m_graph( nullptr ) { Reset(); } SCH_CONNECTION::SCH_CONNECTION( CONNECTION_GRAPH* aGraph ) : m_sheet( SCH_SHEET_PATH() ), m_local_sheet( SCH_SHEET_PATH() ), m_parent( nullptr ), m_driver( nullptr ), m_graph( aGraph ) { Reset(); } bool SCH_CONNECTION::operator==( const SCH_CONNECTION& aOther ) const { // NOTE: Not comparing m_dirty or net/bus/subgraph codes if( ( aOther.m_driver == m_driver ) && ( aOther.m_type == m_type ) && ( aOther.m_name == m_name ) && ( aOther.m_sheet == m_sheet ) ) { return true; } return false; } void SCH_CONNECTION::SetDriver( SCH_ITEM* aItem ) { m_driver = aItem; recacheName(); for( const std::shared_ptr& member : m_members ) member->SetDriver( aItem ); } void SCH_CONNECTION::SetSheet( SCH_SHEET_PATH aSheet ) { m_sheet = aSheet; m_local_sheet = aSheet; recacheName(); for( const std::shared_ptr& member : m_members ) member->SetSheet( aSheet ); } bool SCH_CONNECTION::operator!=( const SCH_CONNECTION& aOther ) const { return !( aOther == *this ); } void SCH_CONNECTION::ConfigureFromLabel( const wxString& aLabel ) { m_members.clear(); m_name = aLabel; m_local_name = aLabel; m_local_prefix = m_prefix; wxString prefix; std::vector members; wxString unescaped = UnescapeString( aLabel ); if( NET_SETTINGS::ParseBusVector( unescaped, &prefix, &members ) ) { m_type = CONNECTION_TYPE::BUS; m_vector_prefix = prefix; long i = 0; for( const wxString& vector_member : members ) { auto member = std::make_shared( m_parent, m_sheet ); member->m_type = CONNECTION_TYPE::NET; member->m_prefix = m_prefix; member->m_local_name = vector_member; member->m_local_prefix = m_prefix; member->m_vector_index = i++; member->SetName( vector_member ); member->SetGraph( m_graph ); m_members.push_back( member ); } } else if( NET_SETTINGS::ParseBusGroup( unescaped, &prefix, &members ) ) { m_type = CONNECTION_TYPE::BUS_GROUP; m_bus_prefix = prefix; // Named bus groups generate a net prefix, unnamed ones don't if( !prefix.IsEmpty() ) prefix += wxT( "." ); for( const wxString& group_member : members ) { // Handle alias inside bus group member list if( auto alias = m_graph->GetBusAlias( group_member ) ) { for( const wxString& alias_member : alias->Members() ) { auto member = std::make_shared< SCH_CONNECTION >( m_parent, m_sheet ); member->SetPrefix( prefix ); member->SetGraph( m_graph ); member->ConfigureFromLabel( alias_member ); m_members.push_back( member ); } } else { auto member = std::make_shared< SCH_CONNECTION >( m_parent, m_sheet ); member->SetPrefix( prefix ); member->SetGraph( m_graph ); member->ConfigureFromLabel( group_member ); m_members.push_back( member ); } } } else { m_type = CONNECTION_TYPE::NET; } recacheName(); } void SCH_CONNECTION::Reset() { m_type = CONNECTION_TYPE::NONE; m_name.Empty(); m_local_name.Empty(); m_local_prefix.Empty(); m_cached_name.Empty(); m_cached_name_with_path.Empty(); m_prefix.Empty(); m_bus_prefix.Empty(); m_suffix .Empty(); m_lastDriver = m_driver; m_driver = nullptr; m_members.clear(); m_dirty = true; m_net_code = 0; m_bus_code = 0; m_subgraph_code = 0; m_vector_start = 0; m_vector_end = 0; m_vector_index = 0; m_vector_prefix.Empty(); } void SCH_CONNECTION::Clone( const SCH_CONNECTION& aOther ) { m_graph = aOther.m_graph; // Note: m_lastDriver is not cloned as it needs to be the last driver of *this* connection m_driver = aOther.Driver(); m_sheet = aOther.Sheet(); // Note: m_local_sheet is not cloned m_name = aOther.m_name; // Note: m_local_name is not cloned if not set yet if( m_local_name.IsEmpty() ) { m_local_name = aOther.LocalName(); m_local_prefix = aOther.Prefix(); } m_prefix = aOther.Prefix(); // m_bus_prefix is not cloned; only used for local names m_suffix = aOther.Suffix(); m_net_code = aOther.NetCode(); m_bus_code = aOther.BusCode(); m_vector_start = aOther.VectorStart(); m_vector_end = aOther.VectorEnd(); // Note: m_vector_index is not cloned m_vector_prefix = aOther.VectorPrefix(); // Note: subgraph code isn't cloned, it should remain with the original object // Handle vector bus members: make sure local names are preserved where possible const std::vector>& otherMembers = aOther.Members(); if( m_type == CONNECTION_TYPE::BUS && aOther.Type() == CONNECTION_TYPE::BUS ) { if( m_members.empty() ) { m_members = otherMembers; } else { size_t cloneLimit = std::min( m_members.size(), otherMembers.size() ); for( size_t i = 0; i < cloneLimit; ++i ) m_members[i]->Clone( *otherMembers[i] ); } } else if( m_type == CONNECTION_TYPE::BUS_GROUP && aOther.Type() == CONNECTION_TYPE::BUS_GROUP ) { if( m_members.empty() ) { m_members = otherMembers; } else { // TODO: refactor this once we support deep nesting for( std::shared_ptr& member : m_members ) { auto it = std::find_if( otherMembers.begin(), otherMembers.end(), [&]( const std::shared_ptr& aTest ) { return aTest->LocalName() == member->LocalName(); } ); if( it != otherMembers.end() ) member->Clone( **it ); } } } m_type = aOther.Type(); recacheName(); } bool SCH_CONNECTION::IsDriver() const { wxASSERT( Parent() ); switch( Parent()->Type() ) { case SCH_LABEL_T: case SCH_GLOBAL_LABEL_T: case SCH_HIER_LABEL_T: case SCH_SHEET_PIN_T: case SCH_SHEET_T: case LIB_PIN_T: return true; case SCH_PIN_T: { SCH_PIN* pin = static_cast( Parent() ); // Only annotated symbols should drive nets. return pin->IsGlobalPower() || pin->GetParentSymbol()->IsAnnotated( &m_sheet ); } default: return false; } } bool SCH_CONNECTION::HasDriverChanged() const { return m_driver != m_lastDriver; } void SCH_CONNECTION::ClearDriverChanged() { m_lastDriver = m_driver; } wxString SCH_CONNECTION::Name( bool aIgnoreSheet ) const { wxASSERT( !m_cached_name.IsEmpty() ); return aIgnoreSheet ? m_cached_name : m_cached_name_with_path; } void SCH_CONNECTION::recacheName() { m_cached_name = m_name.IsEmpty() ? wxString( wxT( "" ) ) : wxString( m_prefix ) << m_name << m_suffix; bool prepend_path = true; if( !Parent() || m_type == CONNECTION_TYPE::NONE ) prepend_path = false; if( m_driver ) { switch( m_driver->Type() ) { case SCH_GLOBAL_LABEL_T: case SCH_PIN_T: // Pins are either power connections or belong to a uniquely-annotated // symbol, so they don't need a path if they are driving the subgraph. prepend_path = false; break; default: break; } } m_cached_name_with_path = prepend_path ? m_sheet.PathHumanReadable() << m_cached_name : m_cached_name; } void SCH_CONNECTION::SetPrefix( const wxString& aPrefix ) { m_prefix = aPrefix; recacheName(); for( const std::shared_ptr& m : Members() ) m->SetPrefix( aPrefix ); } void SCH_CONNECTION::SetSuffix( const wxString& aSuffix ) { m_suffix = aSuffix; recacheName(); for( const std::shared_ptr& m : Members() ) m->SetSuffix( aSuffix ); } void SCH_CONNECTION::AppendInfoToMsgPanel( std::vector& aList ) const { wxString msg, group_name, members; std::vector group_members; aList.emplace_back( _( "Connection Name" ), UnescapeString( Name() ) ); if( std::shared_ptr alias = m_graph->GetBusAlias( m_name ) ) { msg.Printf( _( "Bus Alias %s Members" ), m_name ); aList.emplace_back( msg, boost::algorithm::join( alias->Members(), " " ) ); } else if( NET_SETTINGS::ParseBusGroup( m_name, &group_name, &group_members ) ) { for( const wxString& group_member : group_members ) { if( std::shared_ptr group_alias = m_graph->GetBusAlias( group_member ) ) { msg.Printf( _( "Bus Alias %s Members" ), group_alias->GetName() ); aList.emplace_back( msg, boost::algorithm::join( group_alias->Members(), " " ) ); } } } #if defined(DEBUG) // These messages are not flagged as translatable, because they are only debug messages if( IsBus() ) aList.emplace_back( wxT( "Bus Code" ), wxString::Format( "%d", m_bus_code ) ); aList.emplace_back( wxT( "Subgraph Code" ), wxString::Format( "%d", m_subgraph_code ) ); if( SCH_ITEM* driver = Driver() ) { UNITS_PROVIDER unitsProvider( schIUScale, EDA_UNITS::MILLIMETRES ); msg.Printf( wxS( "%s at %p" ), driver->GetItemDescription( &unitsProvider ), driver ); aList.emplace_back( wxT( "Connection Source" ), msg ); } #endif } bool SCH_CONNECTION::IsBusLabel( const wxString& aLabel ) { const wxString& unescaped = UnescapeString( aLabel ); return NET_SETTINGS::ParseBusVector( unescaped, nullptr, nullptr ) || NET_SETTINGS::ParseBusGroup( unescaped, nullptr, nullptr ); } bool SCH_CONNECTION::MightBeBusLabel( const wxString& aLabel ) { // Weak heuristic for performance reasons. Stronger test will be used for connectivity wxString label = UnescapeString( aLabel ); return label.Contains( wxT( "[" ) ) || label.Contains( wxT( "{" ) ); } const std::vector< std::shared_ptr< SCH_CONNECTION > > SCH_CONNECTION::AllMembers() const { std::vector< std::shared_ptr< SCH_CONNECTION > > ret( m_members ); for( const std::shared_ptr& member : m_members ) { if( member->IsBus() ) ret.insert( ret.end(), member->Members().begin(), member->Members().end() ); } return ret; } static bool isSuperSubOverbar( wxChar c ) { return c == '_' || c == '^' || c == '~'; }; wxString SCH_CONNECTION::PrintBusForUI( const wxString& aGroup ) { size_t groupLen = aGroup.length(); size_t i = 0; wxString ret; int braceNesting = 0; // Parse prefix // for( ; i < groupLen; ++i ) { if( isSuperSubOverbar( aGroup[i] ) && i + 1 < groupLen && aGroup[i+1] == '{' ) { braceNesting++; i++; continue; } else if( aGroup[i] == '}' ) { braceNesting--; continue; } ret += aGroup[i]; if( aGroup[i] == '{' ) break; } // Parse members // i++; // '{' character for( ; i < groupLen; ++i ) { if( isSuperSubOverbar( aGroup[i] ) && i + 1 < groupLen && aGroup[i+1] == '{' ) { braceNesting++; i++; continue; } else if( aGroup[i] == '}' ) { braceNesting--; continue; } ret += aGroup[i]; if( aGroup[i] == '}' ) break; } return ret; } bool SCH_CONNECTION::IsSubsetOf( SCH_CONNECTION* aOther ) const { if( !aOther->IsBus() ) return false; for( const std::shared_ptr& member : aOther->Members() ) { if( member->FullLocalName() == FullLocalName() ) return true; } return false; } bool SCH_CONNECTION::IsMemberOfBus( SCH_CONNECTION* aOther ) const { if( !aOther->IsBus() ) return false; wxString me = Name( true ); for( const std::shared_ptr& m : aOther->Members() ) { if( m->Name( true ) == me ) return true; } return false; }