KiCad PCB EDA Suite
Loading...
Searching...
No Matches
conn_bindings.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include "conn_bindings.h"
21
22#include <algorithm>
23#include <map>
24#include <stdexcept>
25#include <set>
26#include <utility>
27#include <wx/thread.h>
28
29namespace SCH_CONNECTIVITY
30{
32 SESSION_KEYS& aKeys )
33{
34 wxASSERT( wxThread::IsMain() );
36 result.parent = aPartition.anchor;
37
38 for( const auto& [node, version] : aPartition.identity )
39 {
40 const NODE_KEY& nodeKey = aKeys.Node( node );
41
42 if( std::holds_alternative<NAME_KEY>( nodeKey ) && version == 0 )
43 continue;
44
45 const auto* key = std::get_if<RECORD_NODE>( &nodeKey );
46 const auto* entry = key ? aRecords.Find( key->record ) : nullptr;
47
48 if( !entry || entry->version != version || key->kind != KIND::BUNDLE || entry->value.kind != KIND::BUNDLE )
49 throw std::invalid_argument( "Bundle partition does not match current records" );
50
51 const ISLAND_RECORD& record = entry->value;
52
53 for( const KIID& item : record.items )
54 result.items.push_back( { item, key->record.inst } );
55
56 result.netclasses.insert( result.netclasses.end(), record.netclasses.begin(), record.netclasses.end() );
57
58 for( const CLAIM& claim : record.claims )
59 {
60 if( claim.schema )
61 result.claims.push_back( { key->record, claim, {}, &claim == &record.claims.front() } );
62 }
63 }
64
65 std::ranges::sort( result.items, KEY_LESS{ aKeys } );
66 std::ranges::sort( result.netclasses, NAME_LESS{ &aKeys } );
67 result.netclasses.erase( std::ranges::unique( result.netclasses ).begin(), result.netclasses.end() );
68
69 if( result.claims.empty() )
70 return result;
71
72 std::swap( result.claims.front(),
73 *std::ranges::max_element( result.claims, CLAIM_LESS{ aKeys }, &BUNDLE_CLAIM::claim ) );
74
75 for( BUNDLE_CLAIM& source : result.claims )
76 {
77 const auto& leaves = source.claim.schema->leaves;
78 const wxString path = aKeys.Name( source.claim.path );
79
80 if( !leaves.empty() && !std::in_range<uint32_t>( leaves.size() - 1 ) )
81 throw std::length_error( "Bus schema exceeds slot ordinal range" );
82
83 source.leaves.reserve( leaves.size() );
84
85 for( const BUS_SCHEMA::LEAF& leaf : leaves )
86 {
87 wxString local;
88
89 for( const wxString& group : leaf.groupPath )
90 local += group + ".";
91
92 local += leaf.localName;
93 source.leaves.push_back( { aKeys.InternName( leaf.name ), aKeys.InternName( path + leaf.name ),
94 aKeys.InternName( local ) } );
95 }
96 }
97
98 return result;
99}
100
102{
104 result.items = aInput.items;
105 result.netclasses = aInput.netclasses;
106
107 if( aInput.claims.empty() )
108 return result;
109
110 const BUNDLE_CLAIM& canonical = aInput.claims.front();
111
112 if( aInput.parent == INVALID_ID || !canonical.claim.schema )
113 throw std::invalid_argument( "Bundle binding requires a parent and canonical schema" );
114
115 result.canonical = canonical.claim;
116 const auto makeSlot = [&]( const SLOT_KEY& key, const BUNDLE_CLAIM& owner, size_t aLeaf )
117 {
118 const BUNDLE_CLAIM::LEAF_NAME& leaf = owner.leaves.at( aLeaf );
119 SLOT_INPUT slot;
120 slot.key = key;
122 slot.claim.depth = owner.claim.depth;
123 slot.claim.path = owner.claim.path;
124 slot.claim.name = slot.claim.ncName = leaf.name;
125 slot.claim.fullName = leaf.fullName;
126 slot.claim.source = owner.claim.source;
127 slot.localName = leaf.localName;
128 slot.parentNetclasses = aInput.netclasses;
129 slot.parentBundle = aInput.parent;
130 return slot;
131 };
132
133 for( size_t i = 0; i < canonical.leaves.size(); ++i )
134 {
135 const SLOT_KEY key{ canonical.claim.source, static_cast<uint32_t>( i ) };
136 result.members.push_back( key );
137 result.slots.push_back( makeSlot( key, canonical, i ) );
138 }
139
140 const CLAIM_LESS claimLess{ aKeys };
141 std::map<std::pair<INST_ID, NAME_ID>, const BUNDLE_CLAIM*> answers;
142
143 for( const BUNDLE_CLAIM& source : aInput.claims )
144 {
145 if( source.claim.priority != PRIORITY::HIER_LABEL )
146 continue;
147
148 const auto [found, inserted] = answers.try_emplace( { source.record.inst, source.claim.name }, &source );
149
150 if( !inserted && claimLess( found->second->claim, source.claim ) )
151 found->second = &source;
152 }
153
154 for( const BUNDLE_CLAIM& source : aInput.claims )
155 {
156 if( !source.claim.schema || source.leaves.size() != source.claim.schema->leaves.size() )
157 throw std::invalid_argument( "Prepared bundle leaves do not match their schema" );
158
159 const bool sheetPin = source.claim.priority == PRIORITY::SHEET_PIN;
160 const BUNDLE_CLAIM* answer = nullptr;
161
162 if( sheetPin && source.claim.portInstance )
163 {
164 if( auto found = answers.find( { *source.claim.portInstance, source.claim.name } ); found != answers.end() )
165 answer = found->second;
166 }
167
168 // A sheet pin names parent members only while it drives its bus there, and answering labels cover children
169 const bool named = !sheetPin || source.drivesRecord;
170
171 // Such a pin would publish member nets that hold no item on either sheet
172 if( !named && !answer )
173 continue;
174
175 const BUS_ALIGNMENT alignment = Align( *source.claim.schema, *canonical.claim.schema );
176 const auto parentEdges = [&]( SLOT_INPUT& aSlot ) -> std::vector<NAME_KEY>&
177 {
178 return sheetPin ? aSlot.parentEdges : aSlot.edges;
179 };
180
181 if( named )
182 {
183 for( const auto& [from, to] : alignment.matched )
184 {
185 parentEdges( result.slots[to] )
186 .push_back( { SCOPE::SHEET, source.record.inst, source.leaves[from].name } );
187 }
188 }
189
190 for( size_t from : alignment.unmappedLeft )
191 {
192 // An answered pin member takes the label's claim so that both slots name the child member alike
193 const auto ordinal = static_cast<uint32_t>( from );
194 SLOT_INPUT slot = makeSlot( { source.claim.source, ordinal }, answer ? *answer : source, from );
195
196 if( named )
197 parentEdges( slot ).push_back( { SCOPE::SHEET, source.record.inst, source.leaves[from].name } );
198
199 if( answer )
200 slot.edges.push_back( { SCOPE::SHEET, answer->record.inst, source.leaves[from].name } );
201
202 result.slots.push_back( std::move( slot ) );
203 }
204 }
205
206 const KEY_LESS keyLess{ aKeys };
207
208 for( SLOT_INPUT& slot : result.slots )
209 {
210 for( std::vector<NAME_KEY>* edges : { &slot.edges, &slot.parentEdges } )
211 {
212 std::ranges::sort( *edges, keyLess );
213 edges->erase( std::ranges::unique( *edges ).begin(), edges->end() );
214 }
215 }
216
217 std::ranges::sort( result.slots, keyLess, &SLOT_INPUT::key );
218 return result;
219}
220
222 m_slots( aVersions, KEY_LESS{ aKeys } )
223{
224}
225
227{
228 wxASSERT( wxThread::IsMain() );
229 try
230 {
231 std::set<SLOT_KEY, KEY_LESS> live( m_slots.Entries().key_comp() );
232 std::set<NODE_ID> parents;
233
234 for( const auto& bundle : aBundles.Entries() )
235 {
236 const NODE_ID parent = bundle->input.anchor;
237
238 if( !parents.insert( parent ).second )
239 throw std::invalid_argument( "Duplicate bundle component" );
240
241 const auto [previous, inserted] = m_bundleInputs.try_emplace( parent, bundle->version );
242 const bool changed = inserted || std::exchange( previous->second, bundle->version ) != bundle->version;
243
244 for( const SLOT_INPUT& slot : bundle->value.slots )
245 {
246 if( slot.parentBundle != parent || !live.insert( slot.key ).second )
247 throw std::invalid_argument( "Slot has a duplicate key or inconsistent parent" );
248
249 if( changed )
250 m_slots.Set( slot.key, slot );
251 }
252 }
253
254 for( auto it = m_slots.Entries().begin(); it != m_slots.Entries().end(); )
255 {
256 // Erasing by a key stored in the erased node is not guaranteed safe
257 const SLOT_KEY key = ( it++ )->first;
258
259 if( !live.contains( key ) )
260 m_slots.Erase( key );
261 }
262
263 std::erase_if( m_bundleInputs,
264 [&]( const auto& entry )
265 {
266 return !parents.contains( entry.first );
267 } );
268 }
269 catch( ... )
270 {
271 Clear();
272 throw;
273 }
274}
275
277{
278 m_slots.Clear();
279 m_bundleInputs.clear();
280}
281
282std::vector<NODE_INPUT> SignalNodes( const RECORD_STORE::RECORD_CACHE& aRecords, const SLOT_STORE::SLOT_CACHE& aSlots,
283 SESSION_KEYS& aKeys )
284{
285 auto result = RecordNodes( aRecords, KIND::SIGNAL, aKeys );
286 result.reserve( result.size() + aSlots.Entries().size() );
287 std::set<NODE_ID> claimedNames;
288
289 for( const NODE_INPUT& record : result )
290 claimedNames.insert( record.edges.begin(), record.edges.end() );
291
292 for( const auto& [key, entry] : aSlots.Entries() )
293 {
294 NODE_INPUT input{ aKeys.InternNode( key ), entry->version, {} };
295 input.edges.reserve( entry->value.edges.size() );
296
297 for( const NAME_KEY& edge : entry->value.edges )
298 input.edges.push_back( aKeys.InternNode( edge ) );
299
300 // Two buses must not join through a parent name that no signal on that sheet claims
301 for( const NAME_KEY& edge : entry->value.parentEdges )
302 {
303 if( const NODE_ID node = aKeys.InternNode( edge ); claimedNames.contains( node ) )
304 input.edges.push_back( node );
305 }
306
307 result.push_back( std::move( input ) );
308 }
309
310 return result;
311}
312
313} // namespace SCH_CONNECTIVITY
Definition kiid.h:46
const auto & Entries() const
Definition conn_cache.h:105
const ENTRY * Find(const KEY &aKey) const
Entry references survive unchanged writes, but not replacement or erasure of their key.
Definition conn_cache.h:80
One sequence for all cache tables for the lifetime of an engine session.
Definition conn_cache.h:37
Cache current component evaluations by exact node/version identity, within one stratum and key sessio...
CACHE_TABLE< RECORD_KEY, ISLAND_RECORD, KEY_LESS > RECORD_CACHE
Session IDs are dense handles, never a canonical ordering.
Definition conn_keys.h:146
const NODE_KEY & Node(NODE_ID aId) const
Definition conn_keys.h:164
const wxString & Name(NAME_ID aId) const
Definition conn_keys.h:163
NAME_ID InternName(const wxString &aText)
Definition conn_keys.h:153
NODE_ID InternNode(NODE_KEY aKey)
Definition conn_keys.cpp:28
CACHE_TABLE< SLOT_KEY, SLOT_INPUT, KEY_LESS > SLOT_CACHE
std::map< NODE_ID, uint64_t > m_bundleInputs
void Update(const COMPONENT_CACHE< BUNDLE_BINDING > &aBundles)
SLOT_STORE(CACHE_VERSIONS &aVersions, SESSION_KEYS &aKeys)
Value keys and the key session of the schematic connectivity engine.
std::variant< RECORD_NODE, NAME_KEY, SLOT_KEY > NODE_KEY
Any node of the union-find graph.
Definition conn_keys.h:136
BUNDLE_INPUT PrepareBundle(const PARTITION &aPartition, const RECORD_STORE::RECORD_CACHE &aRecords, SESSION_KEYS &aKeys)
Collect the bus claims of a bundle partition and intern their leaf names on the main thread.
uint32_t NODE_ID
Session handle of a NODE_KEY graph node.
Definition conn_keys.h:43
BUS_ALIGNMENT Align(const BUS_SCHEMA &aLeft, const BUS_SCHEMA &aRight)
Match the leaves of aLeft to the leaves of aRight.
Definition conn_bus.cpp:282
constexpr uint32_t INVALID_ID
Marks an unset handle.
Definition conn_keys.h:47
BUNDLE_BINDING BindBundle(const BUNDLE_INPUT &aInput, const SESSION_KEYS &aKeys)
Align the prepared claims to the canonical claim and make the member slots.
std::vector< NODE_INPUT > SignalNodes(const RECORD_STORE::RECORD_CACHE &aRecords, const SLOT_STORE::SLOT_CACHE &aSlots, SESSION_KEYS &aKeys)
Main-thread interning of current signal records and slots for the signal fold.
@ SHEET_PIN
Sheet pin. The name belongs to the child instance.
Definition conn_claims.h:40
@ BUS_MEMBER
Synthetic claim of a bus member slot, set by BindBundle().
Definition conn_claims.h:44
@ HIER_LABEL
Hierarchical label. The first strong priority.
Definition conn_claims.h:41
std::vector< NODE_INPUT > RecordNodes(const RECORD_STORE::RECORD_CACHE &aRecords, KIND aKind, SESSION_KEYS &aKeys)
Main-thread node interning for one record stratum.
Bound result of one bus component.
NAME_ID fullName
Leaf name after the sheet path of this claim.
NAME_ID localName
Group path and leaf local name joined with dots.
NAME_ID name
Leaf name with all group prefixes.
One bus claim of a bundle partition, with its leaf names interned.
bool drivesRecord
The claim drives its own island, which lets a sheet pin name members in the parent sheet.
std::vector< LEAF_NAME > leaves
One entry for each leaf of the claim schema, in the same order.
RECORD_KEY record
Record of the island that holds the claim.
Main-thread input of BindBundle() for one bundle partition.
std::vector< NAME_ID > netclasses
NODE_ID parent
Anchor node of the partition.
std::vector< ITEM_KEY > items
std::vector< BUNDLE_CLAIM > claims
Bus claims. The canonical claim is first.
Member correspondence between two bus schemas.
Definition conn_bus.h:181
std::vector< size_t > unmappedLeft
Left leaf ordinals without a right partner.
Definition conn_bus.h:183
std::vector< std::pair< size_t, size_t > > matched
Pairs of left and right leaf ordinals.
Definition conn_bus.h:182
Strict weak order on claims by value.
Definition conn_claims.h:77
The claim of one item for the name of its island.
Definition conn_claims.h:53
ITEM_KEY source
The claiming item, the last tie-break.
Definition conn_claims.h:60
uint16_t depth
Sheet path size, or zero if unscoped.
Definition conn_claims.h:55
std::optional< INST_ID > portInstance
Sheet pin child. Members bind there, not in the parent.
Definition conn_claims.h:66
NAME_ID ncName
Name of an unconnected pin net.
Definition conn_claims.h:58
NAME_ID name
Item text without the sheet prefix.
Definition conn_claims.h:57
std::shared_ptr< const BUS_SCHEMA > schema
Parsed bus, or null for a signal.
Definition conn_claims.h:65
NAME_ID path
Sheet prefix, or the empty name if unscoped.
Definition conn_claims.h:56
NAME_ID fullName
path followed by name.
Definition conn_claims.h:59
std::vector< NAME_ID > netclasses
std::vector< CLAIM > claims
Descending by CLAIM_LESS, so front() is the island driver.
Orders keys by value through SESSION_KEYS::Less().
Definition conn_keys.h:255
The graph node of one name in one scope.
Definition conn_keys.h:110
Orders name handles by UTF-8 value.
Definition conn_keys.h:246
std::vector< NODE_ID > edges
Exact identity of one connected component.
std::vector< std::pair< NODE_ID, uint64_t > > identity
INST_ID inst
The sheet instance of the record.
Definition conn_keys.h:88
One bus member as a node of the signal stratum.
std::vector< NAME_KEY > parentEdges
Parent-sheet names from a driving sheet pin, which join only names a signal record already claims.
SLOT_KEY key
Source item of the declaring claim and the leaf ordinal in its schema.
CLAIM claim
PRIORITY::BUS_MEMBER claim with the leaf name and the path of its owning claim.
std::vector< NAME_ID > parentNetclasses
Netclasses of the bus component.
NAME_ID localName
Group path and leaf local name joined with dots.
NODE_ID parentBundle
Anchor node of the bus component.
std::vector< NAME_KEY > edges
Sheet name keys that always join this slot.
One member position of a bus.
Definition conn_keys.h:127
std::string path
wxString result
Test unit parsing edge cases and error handling.