diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt
index 51219a4dd5..36b1f1d790 100644
--- a/pcbnew/CMakeLists.txt
+++ b/pcbnew/CMakeLists.txt
@@ -252,6 +252,7 @@ set( PCBNEW_DRC_SRCS
drc/drc_test_provider_silk_to_pad.cpp
drc/drc_test_provider_silk_to_silk.cpp
drc/drc_test_provider_matched_length.cpp
+ drc/drc_test_provider_diff_pair_coupling.cpp
)
set( PCBNEW_NETLIST_SRCS
diff --git a/pcbnew/drc/drc_test_provider_diff_pair_coupling.cpp b/pcbnew/drc/drc_test_provider_diff_pair_coupling.cpp
new file mode 100644
index 0000000000..9a4c9d8b31
--- /dev/null
+++ b/pcbnew/drc/drc_test_provider_diff_pair_coupling.cpp
@@ -0,0 +1,403 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2004-2020 KiCad Developers.
+ *
+ * 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 3 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
+#include
+
+#include
+
+/*
+ Differential pair gap/coupling test.
+ Errors generated:
+ - DRCE_DIFF_PAIR_GAP_OUT_OF_RANGE
+ - DRCE_DIFF_PAIR_UNCOUPLED_LENGTH_TOO_LONG
+ - DRCE_TOO_MANY_VIAS
+ Todo: arc support.
+*/
+
+namespace test {
+
+class DRC_TEST_PROVIDER_DIFF_PAIR_COUPLING : public DRC_TEST_PROVIDER
+{
+public:
+ DRC_TEST_PROVIDER_DIFF_PAIR_COUPLING ()
+ {
+ }
+
+ virtual ~DRC_TEST_PROVIDER_DIFF_PAIR_COUPLING()
+ {
+ }
+
+ virtual bool Run() override;
+
+ virtual const wxString GetName() const override
+ {
+ return "diff_pair_coupling";
+ };
+
+ virtual const wxString GetDescription() const override
+ {
+ return "Tests differential pair coupling";
+ }
+
+ virtual int GetNumPhases() const override
+ {
+ return 1;
+ }
+
+ virtual std::set GetConstraintTypes() const override;
+
+private:
+
+ BOARD* m_board;
+};
+
+};
+
+
+// fixme: move two functions below to pcbcommon?
+static int matchDpSuffix( const wxString& aNetName, wxString& aComplementNet,
+ wxString& aBaseDpName )
+{
+ int rv = 0;
+
+ if( aNetName.EndsWith( "+" ) )
+ {
+ aComplementNet = "-";
+ rv = 1;
+ }
+ else if( aNetName.EndsWith( "P" ) )
+ {
+ aComplementNet = "N";
+ rv = 1;
+ }
+ else if( aNetName.EndsWith( "-" ) )
+ {
+ aComplementNet = "+";
+ rv = -1;
+ }
+ else if( aNetName.EndsWith( "N" ) )
+ {
+ aComplementNet = "P";
+ rv = -1;
+ }
+ // Match P followed by 2 digits
+ else if( aNetName.Right( 2 ).IsNumber() && aNetName.Right( 3 ).Left( 1 ) == "P" )
+ {
+ aComplementNet = "N" + aNetName.Right( 2 );
+ rv = 1;
+ }
+ // Match P followed by 1 digit
+ else if( aNetName.Right( 1 ).IsNumber() && aNetName.Right( 2 ).Left( 1 ) == "P" )
+ {
+ aComplementNet = "N" + aNetName.Right( 1 );
+ rv = 1;
+ }
+ // Match N followed by 2 digits
+ else if( aNetName.Right( 2 ).IsNumber() && aNetName.Right( 3 ).Left( 1 ) == "N" )
+ {
+ aComplementNet = "P" + aNetName.Right( 2 );
+ rv = -1;
+ }
+ // Match N followed by 1 digit
+ else if( aNetName.Right( 1 ).IsNumber() && aNetName.Right( 2 ).Left( 1 ) == "N" )
+ {
+ aComplementNet = "P" + aNetName.Right( 1 );
+ rv = -1;
+ }
+ if( rv != 0 )
+ {
+ aBaseDpName = aNetName.Left( aNetName.Length() - aComplementNet.Length() );
+ aComplementNet = aBaseDpName + aComplementNet;
+ }
+
+ return rv;
+}
+
+
+static int isNetADiffPair( BOARD* aBoard, int aNet, int& aNetP, int& aNetN )
+{
+ wxString refName = aBoard->FindNet( aNet )->GetNetname();
+ wxString dummy, coupledNetName;
+
+ if( int polarity = matchDpSuffix( refName, coupledNetName, dummy ) )
+ {
+ NETINFO_ITEM* net = aBoard->FindNet( coupledNetName );
+
+ if( !net )
+ return false;
+
+ if( polarity > 0 )
+ {
+ aNetP = aNet;
+ aNetN = net->GetNet();
+ }
+ else
+ {
+ aNetP = net->GetNet();
+ aNetN = aNet;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+static bool commonParallelProjection( SEG p, SEG n, SEG &pClip, SEG& nClip )
+{
+ SEG n_proj_p( p.LineProject( n.A ), p.LineProject( n.B ) );
+
+ int64_t t_a = 0;
+ int64_t t_b = p.TCoef( p.B );
+
+ int64_t tproj_a = p.TCoef( n_proj_p.A );
+ int64_t tproj_b = p.TCoef( n_proj_p.B );
+
+ if( t_b < t_a )
+ std::swap( t_b, t_a );
+
+ if( tproj_b < tproj_a )
+ std::swap( tproj_b, tproj_a );
+
+ if( t_b <= tproj_a )
+ return false;
+
+ if( t_a >= tproj_b )
+ return false;
+
+ int64_t t[4] = { 0, p.TCoef( p.B ), p.TCoef( n_proj_p.A ), p.TCoef( n_proj_p.B ) };
+ std::vector tv( t, t + 4 );
+ std::sort( tv.begin(), tv.end() ); // fixme: awful and disgusting way of finding 2 midpoints
+
+ int64_t pLenSq = p.SquaredLength();
+
+ VECTOR2I dp = p.B - p.A;
+ pClip.A.x = p.A.x + rescale( (int64_t)dp.x, tv[1], pLenSq );
+ pClip.A.y = p.A.y + rescale( (int64_t)dp.y, tv[1], pLenSq );
+
+ pClip.B.x = p.A.x + rescale( (int64_t)dp.x, tv[2], pLenSq );
+ pClip.B.y = p.A.y + rescale( (int64_t)dp.y, tv[2], pLenSq );
+
+ nClip.A = n.LineProject( pClip.A );
+ nClip.B = n.LineProject( pClip.B );
+
+ return true;
+}
+
+
+struct DIFF_PAIR_KEY
+ {
+ bool operator<( const DIFF_PAIR_KEY& b ) const
+ {
+ if( netP < b.netP )
+ return true;
+ else if( netP > b.netP )
+ return false;
+ else // netP == b.netP
+ {
+ if( netN < b.netN )
+ return true;
+ else if( netN > b.netN )
+ return false;
+ else
+ {
+ if( parentRule < b.parentRule )
+ return true;
+ else
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ int netP, netN;
+ DRC_RULE* parentRule;
+ };
+
+ struct DIFF_PAIR_COUPLED_SEGMENTS
+ {
+ SEG coupledN, coupledP;
+ TRACK* parentN, *parentP;
+ };
+
+ struct DIFF_PAIR_ITEMS
+ {
+ std::set itemsP, itemsN;
+ std::vector coupled;
+ int totalCoupled;
+ int totalLengthN;
+ int totalLengthP;
+ };
+
+static void extractDiffPairCoupledItems( DIFF_PAIR_ITEMS& aDp )
+{
+
+ for( BOARD_CONNECTED_ITEM* itemP : aDp.itemsP )
+ {
+ auto sp = dyn_cast