KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_test_provider_schematic_parity.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.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU 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 <board.h>
21#include <footprint.h>
22#include <pad.h>
23#include <drc/drc_engine.h>
24#include <drc/drc_item.h>
25#include <drc/drc_rule.h>
27
28#include <kiway.h>
30
31/*
32 Schematic parity test.
33
34 Errors generated:
35 - DRCE_MISSING_FOOTPRINT
36 - DRCE_DUPLICATE_FOOTPRINT
37 - DRCE_EXTRA_FOOTPRINT
38 - DRCE_SCHEMATIC_PARITY
39 - DRCE_FOOTPRINT_FILTERS
40
41 TODO:
42 - cross-check PCB netlist against SCH netlist
43 - cross-check PCB fields against SCH fields
44*/
45
47{
48public:
53
55
56 virtual bool Run() override;
57
58 virtual const wxString GetName() const override { return wxT( "schematic_parity" ); };
59
60private:
61 void testNetlist( NETLIST& aNetlist );
62};
63
64
66{
67 BOARD* board = m_drcEngine->GetBoard();
68
69 auto compare = []( const FOOTPRINT* x, const FOOTPRINT* y )
70 {
71 return x->GetReference().CmpNoCase( y->GetReference() ) < 0;
72 };
73
74 auto footprints = std::set<FOOTPRINT*, decltype( compare )>( compare );
75
76 // Search for duplicate footprints on the board
77 for( FOOTPRINT* footprint : board->Footprints() )
78 {
79 if( m_drcEngine->IsErrorLimitExceeded( DRCE_DUPLICATE_FOOTPRINT ) )
80 break;
81
82 auto ins = footprints.insert( footprint );
83
84 if( !ins.second && !( footprint->GetAttributes() & FP_BOARD_ONLY ) )
85 {
86 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_DUPLICATE_FOOTPRINT );
87 drcItem->SetItems( footprint, *ins.first );
88
89 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
90 }
91 }
92
93 // Search for component footprints in the netlist but not on the board.
94 for( unsigned ii = 0; ii < aNetlist.GetCount(); ii++ )
95 {
96 COMPONENT* component = aNetlist.GetComponent( ii );
97 FOOTPRINT* footprint = board->FindFootprintByReference( component->GetReference() );
98
99 if( footprint == nullptr )
100 {
101 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_MISSING_FOOTPRINT ) )
102 {
103 wxString msg;
104 msg.Printf( _( "Missing footprint %s (%s)" ),
105 component->GetReference(),
106 component->GetValue() );
107
108 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_MISSING_FOOTPRINT );
109
110 drcItem->SetErrorMessage( msg );
112 }
113 }
114 else
115 {
116 if( component->GetValue() != footprint->GetValue()
117 && !m_drcEngine->IsErrorLimitExceeded( DRCE_SCHEMATIC_PARITY ) )
118 {
119 wxString msg;
120 msg.Printf( _( "Value (%s) doesn't match symbol value (%s)" ),
121 footprint->GetValue(),
122 component->GetValue() );
123
124 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SCHEMATIC_PARITY );
125 drcItem->SetErrorMessage( msg );
126 drcItem->SetItems( footprint );
127 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
128 }
129
130 if( component->GetFPID().GetUniStringLibId() != footprint->GetFPID().GetUniStringLibId()
131 && !m_drcEngine->IsErrorLimitExceeded( DRCE_SCHEMATIC_PARITY ) )
132 {
133 wxString msg;
134 msg.Printf( _( "%s doesn't match footprint given by symbol (%s)" ),
135 footprint->GetFPID().GetUniStringLibId(),
136 component->GetFPID().GetUniStringLibId() );
137
138 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SCHEMATIC_PARITY );
139 drcItem->SetErrorMessage( msg );
140 drcItem->SetItems( footprint );
141 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
142 }
143
144 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_FOOTPRINT_FILTERS ) )
145 {
146 wxString libIdLower = footprint->GetFPID().GetUniStringLibId().Lower();
147 wxString fpNameLower = footprint->GetFPID().GetUniStringLibItemName().Lower();
148 size_t filtercount = component->GetFootprintFilters().GetCount();
149 bool found = ( 0 == filtercount ); // if no entries, do not filter
150
151 for( size_t jj = 0; jj < filtercount && !found; jj++ )
152 {
153 wxString filterLower = component->GetFootprintFilters()[jj].Lower();
154
155 if( filterLower.Find( ':' ) == wxNOT_FOUND )
156 found = fpNameLower.Matches( filterLower );
157 else
158 found = libIdLower.Matches( filterLower );
159 }
160
161 if( !found )
162 {
163 wxString msg;
164 msg.Printf( _( "%s doesn't match symbol's footprint filters (%s)" ),
165 footprint->GetFPID().GetUniStringLibId(),
166 wxJoin( component->GetFootprintFilters(), ' ' ) );
167
168 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_FOOTPRINT_FILTERS );
169 drcItem->SetErrorMessage( msg );
170 drcItem->SetItems( footprint );
171 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
172 }
173 }
174
175 if( ( component->GetProperties().count( "dnp" ) > 0 )
176 != ( ( footprint->GetAttributes() & FP_DNP ) > 0 )
177 && !m_drcEngine->IsErrorLimitExceeded( DRCE_SCHEMATIC_PARITY ) )
178 {
179 wxString msg;
180 msg.Printf( _( "'%s' settings differ" ), _( "Do not populate" ) );
181
182 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SCHEMATIC_PARITY );
183 drcItem->SetErrorMessage( drcItem->GetErrorMessage( true ) + wxS( ": " ) + msg );
184 drcItem->SetItems( footprint );
185 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
186 }
187
188 if( ( component->GetProperties().count( "exclude_from_bom" ) > 0 )
189 != ( (footprint->GetAttributes() & FP_EXCLUDE_FROM_BOM ) > 0 )
190 && !m_drcEngine->IsErrorLimitExceeded( DRCE_SCHEMATIC_PARITY ) )
191 {
192 wxString msg;
193 msg.Printf( _( "'%s' settings differ" ), _( "Exclude from bill of materials" ) );
194
195 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SCHEMATIC_PARITY );
196 drcItem->SetErrorMessage( drcItem->GetErrorMessage( true ) + wxS( ": " ) + msg );
197 drcItem->SetItems( footprint );
198 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
199 }
200
201 if( ( component->GetProperties().count( "exclude_from_sim" ) > 0 )
202 != ( ( footprint->GetAttributes() & FP_EXCLUDE_FROM_SIM ) > 0 )
203 && !m_drcEngine->IsErrorLimitExceeded( DRCE_SCHEMATIC_PARITY ) )
204 {
205 wxString msg;
206 msg.Printf( _( "'%s' settings differ" ), _( "Exclude from simulation" ) );
207
208 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SCHEMATIC_PARITY );
209 drcItem->SetErrorMessage( drcItem->GetErrorMessage( true ) + wxS( ": " ) + msg );
210 drcItem->SetItems( footprint );
211 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
212 }
213
214 // Compare custom fields between schematic component and PCB footprint
215 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_SCHEMATIC_FIELDS_PARITY ) )
216 {
217 std::unordered_map<wxString, wxString> fpFieldsAsMap;
218
219 for( PCB_FIELD* field : footprint->GetFields() )
220 {
221 wxCHECK2( field, continue );
222
223 if( field->IsReference() || field->IsValue() || field->IsComponentClass() )
224 continue;
225
226 fpFieldsAsMap[field->GetName()] = field->GetText();
227 }
228
229 // Remove the extra component fields we don't want to evaluate here
230 nlohmann::ordered_map<wxString, wxString> compFields = component->GetFields();
232 compFields.erase( GetDefaultFieldName( FIELD_T::VALUE, UNTRANSLATED ) );
234 compFields.erase( wxT( "Component Class" ) );
235
236 const wxString datasheetName = GetDefaultFieldName( FIELD_T::DATASHEET, UNTRANSLATED );
237
238 // Boards from 9.0.3+ store a "~" datasheet that the 10.0 schematic loader reads as empty
239 auto isBlank =
240 []( const wxString& aText )
241 {
242 return aText.IsEmpty() || aText == wxS( "~" );
243 };
244
245 bool fieldsMatch = true;
246 wxString mismatchDetail;
247
248 for( const auto& [name, value] : compFields )
249 {
250 auto it = fpFieldsAsMap.find( name );
251
252 if( it == fpFieldsAsMap.end() )
253 {
254 fieldsMatch = false;
255 mismatchDetail = wxString::Format( _( "Missing symbol field '%s' in footprint" ), name );
256 break;
257 }
258
259 if( name == datasheetName && isBlank( it->second ) && isBlank( value ) )
260 continue;
261
262 if( it->second != value )
263 {
264 fieldsMatch = false;
265 mismatchDetail = wxString::Format( _( "Field '%s' differs (PCB: '%s', Schematic: '%s')" ),
266 name, it->second, value );
267 break;
268 }
269 }
270
271 if( !fieldsMatch && !mismatchDetail.IsEmpty() )
272 {
273 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SCHEMATIC_FIELDS_PARITY );
274
275 drcItem->SetErrorMessage( mismatchDetail );
276 drcItem->SetItems( footprint );
277 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
278 }
279 }
280
281 for( PAD* pad : footprint->Pads() )
282 {
283 if( m_drcEngine->IsErrorLimitExceeded( DRCE_NET_CONFLICT ) )
284 break;
285
286 if( !pad->CanHaveNumber() )
287 continue;
288
289 const COMPONENT_NET& sch_net = component->GetNet( pad->GetNumber() );
290 const wxString& pcb_netname = pad->GetNetname();
291
292 if( !pcb_netname.IsEmpty() && sch_net.GetPinName().IsEmpty() )
293 {
294 wxString msg;
295 msg.Printf( _( "No corresponding pin found in schematic" ) );
296
297 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_NET_CONFLICT );
298 drcItem->SetErrorMessage( msg );
299 drcItem->SetItems( pad );
300 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
301 }
302 else if( pcb_netname.IsEmpty() && !sch_net.GetNetName().IsEmpty() )
303 {
304 wxString msg;
305 msg.Printf( _( "Pad missing net given by schematic (%s)" ),
306 sch_net.GetNetName() );
307
308 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_NET_CONFLICT );
309 drcItem->SetErrorMessage( msg );
310 drcItem->SetItems( pad );
311 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
312 }
313 else if( pcb_netname != sch_net.GetNetName()
314 && !( pcb_netname.starts_with(
315 wxT( "unconnected-" ) )
316 && pcb_netname.starts_with( sch_net.GetNetName() ) )
317 && !( pad->IsNoConnectPad()
318 && pcb_netname.starts_with( sch_net.GetNetName() + wxT( "_" ) ) ))
319 {
320 wxString msg;
321 msg.Printf( _( "Pad net (%s) doesn't match net given by schematic (%s)" ),
322 pcb_netname,
323 sch_net.GetNetName() );
324
325 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_NET_CONFLICT );
326 drcItem->SetErrorMessage( msg );
327 drcItem->SetItems( pad );
328 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
329 }
330 }
331
332 for( unsigned jj = 0; jj < component->GetNetCount(); ++jj )
333 {
334 if( m_drcEngine->IsErrorLimitExceeded( DRCE_NET_CONFLICT ) )
335 break;
336
337 const COMPONENT_NET& sch_net = component->GetNet( jj );
338
339 if( !footprint->FindPadByNumber( sch_net.GetPinName() ) )
340 {
341 wxString msg;
342
343 if( sch_net.GetNetName().StartsWith( wxT( "unconnected-" ) ) )
344 {
345 msg = sch_net.GetPinName();
346 }
347 else
348 {
349 msg = wxString::Format( wxT( "%s (%s)" ),
350 sch_net.GetPinName(),
351 sch_net.GetNetName() );
352 }
353
354 msg.Printf( _( "No pad found for pin %s in schematic" ), msg );
355
356 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_NET_CONFLICT );
357 drcItem->SetErrorMessage( msg );
358 drcItem->SetItems( footprint );
359 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
360 }
361 }
362 }
363 }
364
365 // Search for component footprints found on board but not in netlist.
366 for( FOOTPRINT* footprint : board->Footprints() )
367 {
368 if( m_drcEngine->IsErrorLimitExceeded( DRCE_EXTRA_FOOTPRINT ) )
369 break;
370
371 if( footprint->GetAttributes() & FP_BOARD_ONLY )
372 continue;
373
374 if( !aNetlist.GetComponentByReference( footprint->GetReference() ) )
375 {
376 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_EXTRA_FOOTPRINT );
377
378 drcItem->SetItems( footprint );
379 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
380 }
381 }
382}
383
384
386{
387 if( m_drcEngine->GetTestFootprints() )
388 {
389 if( !reportPhase( _( "Checking PCB to schematic parity..." ) ) )
390 return false;
391
392 auto netlist = m_drcEngine->GetSchematicNetlist();
393
394 if( !netlist )
395 {
396 REPORT_AUX( wxT( "No netlist provided, skipping schematic parity tests." ) );
397 return true;
398 }
399
401 }
402
403 return !m_drcEngine->IsCancelled();
404}
405
406
407namespace detail
408{
410}
const char * name
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
const FOOTPRINTS & Footprints() const
Definition board.h:465
FOOTPRINT * FindFootprintByReference(const wxString &aReference) const
Search for a FOOTPRINT within this board with the given reference designator.
Definition board.cpp:2879
Used to store the component pin name to net name (and pin function) associations stored in a netlist.
const wxString & GetNetName() const
const wxString & GetPinName() const
Store all of the related component information found in a netlist.
const COMPONENT_NET & GetNet(unsigned aIndex) const
const wxString & GetReference() const
const wxString & GetValue() const
const nlohmann::ordered_map< wxString, wxString > & GetFields() const
const std::map< wxString, wxString > & GetProperties() const
const wxArrayString & GetFootprintFilters() const
const LIB_ID & GetFPID() const
unsigned GetNetCount() const
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:492
virtual const wxString GetName() const override
virtual bool Run() override
Run this provider against the given PCB with configured options (if any).
virtual ~DRC_TEST_PROVIDER_SCHEMATIC_PARITY()=default
virtual bool reportPhase(const wxString &aStageName)
void reportViolation(std::shared_ptr< DRC_ITEM > &item, const VECTOR2I &aMarkerPos, int aMarkerLayer, const std::function< void(PCB_MARKER *)> &aPathGenerator=[](PCB_MARKER *){})
std::deque< PAD * > & Pads()
Definition footprint.h:355
int GetAttributes() const
Definition footprint.h:509
const LIB_ID & GetFPID() const
Definition footprint.h:417
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
const wxString & GetValue() const
Definition footprint.h:817
const wxString & GetReference() const
Definition footprint.h:793
VECTOR2I GetPosition() const override
Definition footprint.h:379
PAD * FindPadByNumber(const wxString &aPadNumber, PAD *aSearchAfterMe=nullptr) const
Return a PAD with a matching number.
wxString GetUniStringLibId() const
Definition lib_id.h:144
const wxString GetUniStringLibItemName() const
Get strings for display messages in dialogs.
Definition lib_id.h:108
Store information read from a netlist along with the flags used to update the NETLIST in the BOARD.
unsigned GetCount() const
COMPONENT * GetComponentByReference(const wxString &aReference)
Return a COMPONENT by aReference.
COMPONENT * GetComponent(unsigned aIndex)
Return the COMPONENT at aIndex.
Definition pad.h:61
@ DRCE_FOOTPRINT_FILTERS
Definition drc_item.h:93
@ DRCE_SCHEMATIC_FIELDS_PARITY
Definition drc_item.h:137
@ DRCE_DUPLICATE_FOOTPRINT
Definition drc_item.h:89
@ DRCE_EXTRA_FOOTPRINT
Definition drc_item.h:90
@ DRCE_NET_CONFLICT
Definition drc_item.h:91
@ DRCE_MISSING_FOOTPRINT
Definition drc_item.h:88
@ DRCE_SCHEMATIC_PARITY
Definition drc_item.h:92
#define REPORT_AUX(s)
#define _(s)
@ FP_DNP
Definition footprint.h:92
@ FP_BOARD_ONLY
Definition footprint.h:90
@ FP_EXCLUDE_FROM_BOM
Definition footprint.h:89
@ FP_EXCLUDE_FROM_SIM
Definition footprint.h:93
@ UNDEFINED_LAYER
Definition layer_ids.h:57
static DRC_REGISTER_TEST_PROVIDER< DRC_TEST_PROVIDER_ANNULAR_WIDTH > dummy
wxString GetDefaultFieldName(FIELD_T aFieldId, TRANSLATION aTranslation)
Return a default symbol field name for a mandatory field type.
@ FOOTPRINT
Field Name Module PCB, i.e. "16DIP300".
@ DATASHEET
name of datasheet
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
@ UNTRANSLATED
std::string netlist
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708