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->GetReference(), 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 // Compare custom fields between schematic component and PCB footprint
202 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_SCHEMATIC_FIELDS_PARITY ) )
203 {
204 std::unordered_map<wxString, wxString> fpFieldsAsMap;
205
206 for( PCB_FIELD* field : footprint->GetFields() )
207 {
208 wxCHECK2( field, continue );
209
210 if( field->IsReference() || field->IsValue() || field->IsComponentClass() )
211 continue;
212
213 fpFieldsAsMap[field->GetName()] = field->GetText();
214 }
215
216 // Remove the extra component fields we don't want to evaluate here
217 nlohmann::ordered_map<wxString, wxString> compFields = component->GetFields();
218 compFields.erase( GetCanonicalFieldName( FIELD_T::REFERENCE ) );
219 compFields.erase( GetCanonicalFieldName( FIELD_T::VALUE ) );
220 compFields.erase( GetCanonicalFieldName( FIELD_T::FOOTPRINT ) );
221 compFields.erase( wxT( "Component Class" ) );
222
223 bool fieldsMatch = true;
224 wxString mismatchDetail;
225
226 for( const auto& [name, value] : compFields )
227 {
228 auto it = fpFieldsAsMap.find( name );
229
230 if( it == fpFieldsAsMap.end() )
231 {
232 fieldsMatch = false;
233 mismatchDetail = wxString::Format( _( "Missing symbol field '%s' in footprint" ), name );
234 break;
235 }
236
237 if( it->second != value )
238 {
239 fieldsMatch = false;
240 mismatchDetail = wxString::Format( _( "Field '%s' differs (PCB: '%s', Schematic: '%s')" ),
241 name, it->second, value );
242 break;
243 }
244 }
245
246 if( !fieldsMatch && !mismatchDetail.IsEmpty() )
247 {
248 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SCHEMATIC_FIELDS_PARITY );
249
250 drcItem->SetErrorMessage( mismatchDetail );
251 drcItem->SetItems( footprint );
252 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
253 }
254 }
255
256 for( PAD* pad : footprint->Pads() )
257 {
258 if( m_drcEngine->IsErrorLimitExceeded( DRCE_NET_CONFLICT ) )
259 break;
260
261 if( !pad->CanHaveNumber() )
262 continue;
263
264 const COMPONENT_NET& sch_net = component->GetNet( pad->GetNumber() );
265 const wxString& pcb_netname = pad->GetNetname();
266
267 if( !pcb_netname.IsEmpty() && sch_net.GetPinName().IsEmpty() )
268 {
269 wxString msg;
270 msg.Printf( _( "No corresponding pin found in schematic" ) );
271
272 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_NET_CONFLICT );
273 drcItem->SetErrorMessage( msg );
274 drcItem->SetItems( pad );
275 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
276 }
277 else if( pcb_netname.IsEmpty() && !sch_net.GetNetName().IsEmpty() )
278 {
279 wxString msg;
280 msg.Printf( _( "Pad missing net given by schematic (%s)" ),
281 sch_net.GetNetName() );
282
283 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_NET_CONFLICT );
284 drcItem->SetErrorMessage( msg );
285 drcItem->SetItems( pad );
286 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
287 }
288 else if( pcb_netname != sch_net.GetNetName()
289 && !( pcb_netname.starts_with(
290 wxT( "unconnected-" ) )
291 && pcb_netname.starts_with( sch_net.GetNetName() ) )
292 && !( pad->IsNoConnectPad()
293 && pcb_netname.starts_with( sch_net.GetNetName() + wxT( "_" ) ) ))
294 {
295 wxString msg;
296 msg.Printf( _( "Pad net (%s) doesn't match net given by schematic (%s)" ),
297 pcb_netname,
298 sch_net.GetNetName() );
299
300 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_NET_CONFLICT );
301 drcItem->SetErrorMessage( msg );
302 drcItem->SetItems( pad );
303 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
304 }
305 }
306
307 for( unsigned jj = 0; jj < component->GetNetCount(); ++jj )
308 {
309 if( m_drcEngine->IsErrorLimitExceeded( DRCE_NET_CONFLICT ) )
310 break;
311
312 const COMPONENT_NET& sch_net = component->GetNet( jj );
313
314 if( !footprint->FindPadByNumber( sch_net.GetPinName() ) )
315 {
316 wxString msg;
317
318 if( sch_net.GetNetName().StartsWith( wxT( "unconnected-" ) ) )
319 {
320 msg = sch_net.GetPinName();
321 }
322 else
323 {
324 msg = wxString::Format( wxT( "%s (%s)" ),
325 sch_net.GetPinName(),
326 sch_net.GetNetName() );
327 }
328
329 msg.Printf( _( "No pad found for pin %s in schematic" ), msg );
330
331 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_NET_CONFLICT );
332 drcItem->SetErrorMessage( msg );
333 drcItem->SetItems( footprint );
334 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
335 }
336 }
337 }
338 }
339
340 // Search for component footprints found on board but not in netlist.
341 for( FOOTPRINT* footprint : board->Footprints() )
342 {
343 if( m_drcEngine->IsErrorLimitExceeded( DRCE_EXTRA_FOOTPRINT ) )
344 break;
345
346 if( footprint->GetAttributes() & FP_BOARD_ONLY )
347 continue;
348
349 if( !aNetlist.GetComponentByReference( footprint->GetReference() ) )
350 {
351 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_EXTRA_FOOTPRINT );
352
353 drcItem->SetItems( footprint );
354 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
355 }
356 }
357}
358
359
361{
362 if( m_drcEngine->GetTestFootprints() )
363 {
364 if( !reportPhase( _( "Checking PCB to schematic parity..." ) ) )
365 return false;
366
367 auto netlist = m_drcEngine->GetSchematicNetlist();
368
369 if( !netlist )
370 {
371 REPORT_AUX( wxT( "No netlist provided, skipping schematic parity tests." ) );
372 return true;
373 }
374
376 }
377
378 return !m_drcEngine->IsCancelled();
379}
380
381
382namespace detail
383{
385}
const char * name
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:372
const FOOTPRINTS & Footprints() const
Definition board.h:420
FOOTPRINT * FindFootprintByReference(const wxString &aReference) const
Search for a FOOTPRINT within this board with the given reference designator.
Definition board.cpp:2739
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:417
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:375
int GetAttributes() const
Definition footprint.h:507
const LIB_ID & GetFPID() const
Definition footprint.h:441
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
const wxString & GetValue() const
Definition footprint.h:863
const wxString & GetReference() const
Definition footprint.h:841
VECTOR2I GetPosition() const override
Definition footprint.h:403
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:76
@ DRCE_SCHEMATIC_FIELDS_PARITY
Definition drc_item.h:119
@ DRCE_DUPLICATE_FOOTPRINT
Definition drc_item.h:72
@ DRCE_EXTRA_FOOTPRINT
Definition drc_item.h:73
@ DRCE_NET_CONFLICT
Definition drc_item.h:74
@ DRCE_MISSING_FOOTPRINT
Definition drc_item.h:71
@ DRCE_SCHEMATIC_PARITY
Definition drc_item.h:75
#define REPORT_AUX(s)
#define _(s)
@ FP_DNP
Definition footprint.h:89
@ FP_BOARD_ONLY
Definition footprint.h:87
@ FP_EXCLUDE_FROM_BOM
Definition footprint.h:86
@ UNDEFINED_LAYER
Definition layer_ids.h:57
static DRC_REGISTER_TEST_PROVIDER< DRC_TEST_PROVIDER_ANNULAR_WIDTH > dummy
@ FOOTPRINT
Field Name Module PCB, i.e. "16DIP300".
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
wxString GetCanonicalFieldName(FIELD_T aFieldType)
std::string netlist
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683