7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-07 18:35:32 +00:00

Implement within_diff_pairs and group_matched DRC skew arguments

Enables DRC to calculate skew based on new arguments to skew
constraint DRC clauses:

Using (group_matched): calculate skew across all matching nets
Using (within_diff_pairs): calculate skew within every diff pair
   found within the matching nets

Additionally fixes DRC skew calculation to calculate skew relative
to the longest net in the skew check set (in line with PNS meander
placer calculations).
This commit is contained in:
JamesJCode 2024-07-29 00:05:13 +01:00
parent a7634e57b5
commit 5187ea721d
14 changed files with 7238 additions and 48 deletions

View File

@ -15,6 +15,7 @@ error
exclusion
footprint
graphic
group_matched
hole
hole_clearance
hole_size
@ -55,5 +56,6 @@ via
via_count
via_diameter
warning
within_diff_pairs
zone
zone_connection

View File

@ -237,10 +237,42 @@ void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
|| token == wxT( "via" )
|| token == wxT( "zone" );
};
auto isConstraintTypeToken =
[]( const wxString& token ) -> bool
{
return token == wxT( "annular_width" )
|| token == wxT( "assertion" )
|| token == wxT( "clearance" )
|| token == wxT( "connection_width" )
|| token == wxT( "courtyard_clearance" )
|| token == wxT( "diff_pair_gap" )
|| token == wxT( "diff_pair_uncoupled" )
|| token == wxT( "disallow" )
|| token == wxT( "edge_clearance" )
|| token == wxT( "length" )
|| token == wxT( "hole_clearance" )
|| token == wxT( "hole_size" )
|| token == wxT( "hole_to_hole" )
|| token == wxT( "min_resolved_spokes" )
|| token == wxT( "physical_clearance" )
|| token == wxT( "physical_hole_clearance" )
|| token == wxT( "silk_clearance" )
|| token == wxT( "skew" )
|| token == wxT( "text_height" )
|| token == wxT( "text_thickness" )
|| token == wxT( "thermal_relief_gap" )
|| token == wxT( "thermal_spoke_width" )
|| token == wxT( "track_width" )
|| token == wxT( "via_count" )
|| token == wxT( "via_diameter" )
|| token == wxT( "zone_connection" );
};
std::stack<wxString> sexprs;
wxString partial;
wxString last;
wxString constraintType;
int context = NONE;
int expr_context = NONE;
@ -313,7 +345,18 @@ void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
}
if( !sexprs.empty() )
sexprs.pop();
{
// Ignore argument-less tokens
if( partial != wxT( "group_matched" ) && partial != wxT( "within_diff_pairs" ) )
{
if( sexprs.top() == wxT( "constraint" ) )
{
constraintType = wxEmptyString;
}
sexprs.pop();
}
}
context = NONE;
}
@ -364,6 +407,10 @@ void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
context = SEXPR_STRING;
continue;
}
else if( isConstraintTypeToken( partial ) )
{
constraintType = partial;
}
context = NONE;
}
@ -391,7 +438,10 @@ void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
}
else if( sexprs.top() == wxT( "constraint" ) )
{
tokens = wxT( "max|min|opt" );
if( constraintType == wxT( "skew" ) )
tokens = wxT( "max|min|opt|group_matched|within_diff_pairs" );
else
tokens = wxT( "max|min|opt" );
}
}
else if( context == SEXPR_TOKEN )

View File

@ -31,6 +31,7 @@ public:
{
int netcode;
wxString netname;
NETINFO_ITEM* netinfo;
BOARD_CONNECTED_ITEM* fromItem;
BOARD_CONNECTED_ITEM* toItem;
DRC_RULE* matchingRule;

View File

@ -24,6 +24,7 @@
#ifndef DRC_RULE_H
#define DRC_RULE_H
#include <bitset>
#include <kiid.h>
#include <core/typeinfo.h>
#include <optional>
@ -134,6 +135,12 @@ class DRC_CONSTRAINT
{
}
enum class OPTIONS : std::size_t
{
SKEW_GROUP_MATCHED = 0,
SKEW_WITHIN_DIFF_PAIRS = 1
};
bool IsNull() const
{
return m_Type == NULL_CONSTRAINT;
@ -168,6 +175,13 @@ class DRC_CONSTRAINT
return RPT_SEVERITY_UNDEFINED;
}
void SetOption( OPTIONS option ) { m_options.set( static_cast<std::size_t>( option ), true ); }
bool GetOption( OPTIONS option ) const
{
return m_options.test( static_cast<std::size_t>( option ) );
}
public:
DRC_CONSTRAINT_T m_Type;
MINOPTMAX<int> m_Value;
@ -178,6 +192,7 @@ public:
private:
wxString m_name; // For just-in-time constraints
DRC_RULE* m_parentRule; // For constraints found in rules
std::bitset<2> m_options; // Constraint-specific option bits
};

View File

@ -482,6 +482,50 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule )
switch( token )
{
case T_group_matched:
if( c.m_Type != SKEW_CONSTRAINT )
{
reportError( _( "group_matched option invalid for constraint type." ) );
break;
}
if( c.GetOption( DRC_CONSTRAINT::OPTIONS::SKEW_WITHIN_DIFF_PAIRS ) )
{
reportError( _( "within_diff_pairs argument already set for skew constraint." ) );
break;
}
c.SetOption( DRC_CONSTRAINT::OPTIONS::SKEW_GROUP_MATCHED );
if( (int) NextTok() != DSN_RIGHT )
{
reportError( wxString::Format( _( "Unrecognized item '%s'." ), FromUTF8() ) );
parseUnknown();
}
break;
case T_within_diff_pairs:
if( c.m_Type != SKEW_CONSTRAINT )
{
reportError( _( "within_diff_pairs option invalid for constraint type." ) );
break;
}
if( c.GetOption( DRC_CONSTRAINT::OPTIONS::SKEW_GROUP_MATCHED ) )
{
reportError( _( "group_matched argument already set for skew constraint." ) );
break;
}
c.SetOption( DRC_CONSTRAINT::OPTIONS::SKEW_WITHIN_DIFF_PAIRS );
if( (int) NextTok() != DSN_RIGHT )
{
reportError( wxString::Format( _( "Unrecognized item '%s'." ), FromUTF8() ) );
parseUnknown();
}
break;
case T_min:
token = NextTok();

View File

@ -140,58 +140,103 @@ void DRC_TEST_PROVIDER_MATCHED_LENGTH::checkLengths( const DRC_CONSTRAINT& aCons
void DRC_TEST_PROVIDER_MATCHED_LENGTH::checkSkews( const DRC_CONSTRAINT& aConstraint,
const std::vector<CONNECTION>& aMatchedConnections )
{
double avgLength = 0;
for( const DRC_LENGTH_REPORT::ENTRY& ent : aMatchedConnections )
avgLength += ent.total;
avgLength /= (double) aMatchedConnections.size();
for( const auto& ent : aMatchedConnections )
auto checkSkewsImpl = [this, &aConstraint]( const std::vector<CONNECTION>& connections )
{
int skew = KiROUND( ent.total - avgLength );
bool fail_min = false;
bool fail_max = false;
double maxLength = 0;
wxString maxNetname;
if( aConstraint.GetValue().HasMax() && abs( skew ) > aConstraint.GetValue().Max() )
fail_max = true;
else if( aConstraint.GetValue().HasMin() && abs( skew ) < aConstraint.GetValue().Min() )
fail_min = true;
if( fail_min || fail_max )
for( const DRC_LENGTH_REPORT::ENTRY& ent : connections )
{
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SKEW_OUT_OF_RANGE );
wxString msg;
if( fail_min )
if( ent.total > maxLength )
{
msg.Printf( _( "(%s min skew %s; actual %s; average net length %s; actual %s)" ),
aConstraint.GetName(),
MessageTextFromValue( aConstraint.GetValue().Min() ),
MessageTextFromValue( skew ),
MessageTextFromValue( avgLength ),
MessageTextFromValue( ent.total ) );
maxLength = ent.total;
maxNetname = ent.netname;
}
else
{
msg.Printf( _( "(%s max skew %s; actual %s; average net length %s; actual %s)" ),
aConstraint.GetName(),
MessageTextFromValue( aConstraint.GetValue().Max() ),
MessageTextFromValue( skew ),
MessageTextFromValue( avgLength ),
MessageTextFromValue( ent.total ) );
}
drcItem->SetErrorMessage( drcItem->GetErrorText() + " " + msg );
for( BOARD_CONNECTED_ITEM* offendingTrack : ent.items )
drcItem->SetItems( offendingTrack );
drcItem->SetViolatingRule( aConstraint.GetParentRule() );
reportViolation( drcItem, ( *ent.items.begin() )->GetPosition(),
( *ent.items.begin() )->GetLayer() );
}
for( const auto& ent : connections )
{
int skew = KiROUND( ent.total - maxLength );
bool fail_min = false;
bool fail_max = false;
if( aConstraint.GetValue().HasMax() && abs( skew ) > aConstraint.GetValue().Max() )
fail_max = true;
else if( aConstraint.GetValue().HasMin() && abs( skew ) < aConstraint.GetValue().Min() )
fail_min = true;
if( fail_min || fail_max )
{
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SKEW_OUT_OF_RANGE );
wxString msg;
if( fail_min )
{
msg.Printf( _( "(%s min skew %s; actual %s; target net length %s (from %s); "
"actual %s)" ),
aConstraint.GetName(),
MessageTextFromValue( aConstraint.GetValue().Min() ),
MessageTextFromValue( skew ), MessageTextFromValue( maxLength ),
maxNetname, MessageTextFromValue( ent.total ) );
}
else
{
msg.Printf( _( "(%s max skew %s; actual %s; target net length %s (from %s); "
"actual %s)" ),
aConstraint.GetName(),
MessageTextFromValue( aConstraint.GetValue().Max() ),
MessageTextFromValue( skew ), MessageTextFromValue( maxLength ),
maxNetname, MessageTextFromValue( ent.total ) );
}
drcItem->SetErrorMessage( drcItem->GetErrorText() + " " + msg );
for( BOARD_CONNECTED_ITEM* offendingTrack : ent.items )
drcItem->SetItems( offendingTrack );
drcItem->SetViolatingRule( aConstraint.GetParentRule() );
reportViolation( drcItem, ( *ent.items.begin() )->GetPosition(),
( *ent.items.begin() )->GetLayer() );
}
}
};
if( aConstraint.GetOption( DRC_CONSTRAINT::OPTIONS::SKEW_WITHIN_DIFF_PAIRS ) )
{
// Find all pairs of nets in the matched connections
std::map<int, CONNECTION> netcodeMap;
for( const DRC_LENGTH_REPORT::ENTRY& ent : aMatchedConnections )
netcodeMap[ent.netcode] = ent;
std::vector<std::vector<CONNECTION>> matchedDiffPairs;
for( auto& [netcode, connection] : netcodeMap )
{
NETINFO_ITEM* matchedNet = m_board->DpCoupledNet( connection.netinfo );
if( matchedNet )
{
int matchedNetcode = matchedNet->GetNetCode();
if( netcodeMap.count( matchedNetcode ) )
{
std::vector<CONNECTION> pair{ connection, netcodeMap[matchedNetcode] };
matchedDiffPairs.emplace_back( std::move( pair ) );
netcodeMap.erase( matchedNetcode );
}
}
}
// Test all found pairs of nets
for( const std::vector<CONNECTION>& matchedDiffPair : matchedDiffPairs )
checkSkewsImpl( matchedDiffPair );
}
else
{
// Test all matched nets as a group
checkSkewsImpl( aMatchedConnections );
}
}
@ -315,6 +360,7 @@ bool DRC_TEST_PROVIDER_MATCHED_LENGTH::runInternal( bool aDelayReportMode )
ent.items = nitem.second;
ent.netcode = nitem.first;
ent.netname = m_board->GetNetInfo().GetNetItem( ent.netcode )->GetNetname();
ent.netinfo = m_board->GetNetInfo().GetNetItem( ent.netcode );
ent.viaCount = 0;
ent.totalRoute = 0;

View File

@ -0,0 +1,6 @@
(version 1)
(rule diff_skew
(constraint skew (max 3mil) (group_matched))
(condition "A.hasNetclass('DIFF_PAIR')")
)

View File

LOADING design file

View File

@ -0,0 +1,655 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": false,
"text_position": 0,
"units_format": 1
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.8,
"height": 1.27,
"width": 2.54
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.5
}
},
"diff_pair_dimensions": [],
"drc_exclusions": [
[
"silk_over_copper|74282330|76842330|c052616e-f433-48e4-989c-8abaf3b77f5f|00000000-0000-0000-0000-000000000000",
""
],
[
"silk_overlap|74412857|76874819|c052616e-f433-48e4-989c-8abaf3b77f5f|38949ed8-66c5-4186-b682-6b5f0ff4a317",
""
]
],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "warning",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_dangling": "warning",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpadsmd": true,
"td_onroundshapesonly": false,
"td_ontrackend": false,
"td_onviapad": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "skew_group_matched_drc.kicad_pro",
"version": 1
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
},
"net_colors": null,
"netclass_assignments": {
"/NET_1": [
"DIFF_PAIR"
],
"/NET_2": [
"DIFF_PAIR"
],
"/NET_3N": [
"DIFF_PAIR"
],
"/NET_3P": [
"DIFF_PAIR"
],
"/NET_4": [
"DIFF_PAIR"
],
"/NET_4+": [
"DIFF_PAIR"
],
"/NET_4-": [
"DIFF_PAIR"
],
"/NET_N": [
"DIFF_PAIR"
],
"/NET_P": [
"DIFF_PAIR"
]
},
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
},
{
"group_by": true,
"label": "Exclude from BOM",
"name": "${EXCLUDE_FROM_BOM}",
"show": true
},
{
"group_by": true,
"label": "Exclude from Board",
"name": "${EXCLUDE_FROM_BOARD}",
"show": true
},
{
"group_by": true,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
},
{
"group_by": false,
"label": "Description",
"name": "Description",
"show": false
},
{
"group_by": false,
"label": "#",
"name": "${ITEM_NUMBER}",
"show": false
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": true,
"name": "",
"sort_asc": true,
"sort_field": "Reference"
},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"page_layout_descr_file": "",
"plot_directory": "",
"space_save_all_events": true,
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
[
"9bf9fc31-f3c1-4c56-8687-162359204658",
"Root"
]
],
"text_variables": {}
}

View File

@ -0,0 +1,6 @@
(version 1)
(rule diff_skew
(constraint skew (max 3mil) (within_diff_pairs))
(condition "A.hasNetclass('DIFF_PAIR')")
)

View File

LOADING design file

View File

@ -0,0 +1,655 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": false,
"text_position": 0,
"units_format": 1
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.8,
"height": 1.27,
"width": 2.54
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.5
}
},
"diff_pair_dimensions": [],
"drc_exclusions": [
[
"silk_over_copper|74282330|76842330|c052616e-f433-48e4-989c-8abaf3b77f5f|00000000-0000-0000-0000-000000000000",
""
],
[
"silk_overlap|74412857|76874819|c052616e-f433-48e4-989c-8abaf3b77f5f|38949ed8-66c5-4186-b682-6b5f0ff4a317",
""
]
],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "warning",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_dangling": "warning",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpadsmd": true,
"td_onroundshapesonly": false,
"td_ontrackend": false,
"td_onviapad": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "skew_within_diff_pairs_drc.kicad_pro",
"version": 1
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
},
"net_colors": null,
"netclass_assignments": {
"/NET_1": [
"DIFF_PAIR"
],
"/NET_2": [
"DIFF_PAIR"
],
"/NET_3N": [
"DIFF_PAIR"
],
"/NET_3P": [
"DIFF_PAIR"
],
"/NET_4": [
"DIFF_PAIR"
],
"/NET_4+": [
"DIFF_PAIR"
],
"/NET_4-": [
"DIFF_PAIR"
],
"/NET_N": [
"DIFF_PAIR"
],
"/NET_P": [
"DIFF_PAIR"
]
},
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
},
{
"group_by": true,
"label": "Exclude from BOM",
"name": "${EXCLUDE_FROM_BOM}",
"show": true
},
{
"group_by": true,
"label": "Exclude from Board",
"name": "${EXCLUDE_FROM_BOARD}",
"show": true
},
{
"group_by": true,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
},
{
"group_by": false,
"label": "Description",
"name": "Description",
"show": false
},
{
"group_by": false,
"label": "#",
"name": "${ITEM_NUMBER}",
"show": false
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": true,
"name": "",
"sort_asc": true,
"sort_field": "Reference"
},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"page_layout_descr_file": "",
"plot_directory": "",
"space_save_all_events": true,
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
[
"9bf9fc31-f3c1-4c56-8687-162359204658",
"Root"
]
],
"text_variables": {}
}

View File

@ -58,6 +58,7 @@ set( QA_PCBNEW_SRCS
drc/test_drc_copper_sliver.cpp
drc/test_solder_mask_bridging.cpp
drc/test_drc_multi_netclasses.cpp
drc/test_drc_skew.cpp
pcb_io/altium/test_altium_rule_transformer.cpp
pcb_io/altium/test_altium_pcblib_import.cpp

View File

@ -0,0 +1,111 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 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 <qa_utils/wx_utils/unit_test_utils.h>
#include <pcbnew_utils/board_test_utils.h>
#include <board.h>
#include <board_design_settings.h>
#include <pad.h>
#include <pcb_track.h>
#include <pcb_marker.h>
#include <footprint.h>
#include <drc/drc_item.h>
#include <settings/settings_manager.h>
struct DRC_REGRESSION_TEST_FIXTURE
{
DRC_REGRESSION_TEST_FIXTURE() :
m_settingsManager( true /* headless */ )
{ }
SETTINGS_MANAGER m_settingsManager;
std::unique_ptr<BOARD> m_board;
};
BOOST_FIXTURE_TEST_CASE( DRCSkew, DRC_REGRESSION_TEST_FIXTURE )
{
// Check for minimum copper connection errors
std::vector<std::pair<wxString, int>> tests =
{
{ "skew_within_diff_pairs_drc", 2 },
{ "skew_group_matched_drc", 5 }
};
for( const std::pair<wxString, int>& test : tests )
{
KI_TEST::LoadBoard( m_settingsManager, test.first, m_board );
KI_TEST::FillZones( m_board.get() );
std::vector<DRC_ITEM> violations;
BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
// Disable DRC tests not useful or not handled in this testcase
bds.m_DRCSeverities[ DRCE_INVALID_OUTLINE ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[ DRCE_UNCONNECTED_ITEMS ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[ DRCE_COPPER_SLIVER ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[ DRCE_STARVED_THERMAL ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[ DRCE_DRILL_OUT_OF_RANGE ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[ DRCE_VIA_DIAMETER ] = SEVERITY::RPT_SEVERITY_IGNORE;
// These DRC tests are not useful and do not work because they need a footprint library
// associated to the board
bds.m_DRCSeverities[ DRCE_LIB_FOOTPRINT_ISSUES ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[ DRCE_LIB_FOOTPRINT_MISMATCH ] = SEVERITY::RPT_SEVERITY_IGNORE;
// Ensure that our desired error is fired
bds.m_DRCSeverities[ DRCE_SKEW_OUT_OF_RANGE ] = SEVERITY::RPT_SEVERITY_ERROR;
bds.m_DRCEngine->SetViolationHandler(
[&]( const std::shared_ptr<DRC_ITEM>& aItem, VECTOR2I aPos, int aLayer )
{
if( bds.GetSeverity( aItem->GetErrorCode() ) == SEVERITY::RPT_SEVERITY_ERROR )
violations.push_back( *aItem );
} );
bds.m_DRCEngine->RunTests( EDA_UNITS::MILLIMETRES, true, false );
if( violations.size() == test.second )
{
BOOST_CHECK_EQUAL( 1, 1 ); // quiet "did not check any assertions" warning
BOOST_TEST_MESSAGE( wxString::Format( "DRC skew: %s, passed", test.first ) );
}
else
{
UNITS_PROVIDER unitsProvider( pcbIUScale, EDA_UNITS::INCHES );
std::map<KIID, EDA_ITEM*> itemMap;
m_board->FillItemMap( itemMap );
for( const DRC_ITEM& item : violations )
{
BOOST_TEST_MESSAGE( item.ShowReport( &unitsProvider, RPT_SEVERITY_ERROR,
itemMap ) );
}
BOOST_ERROR( wxString::Format( "DRC skew: %s, failed (violations found %d expected %d)",
test.first, (int)violations.size(), test.second ) );
}
}
}