kicad/pcbnew/router/pns_diff_pair_placer.cpp

888 lines
21 KiB
C++

/*
* KiRouter - a push-and-(sometimes-)shove PCB router
*
* Copyright (C) 2013-2015 CERN
* Copyright (C) 2016-2023 KiCad Developers, see AUTHORS.txt for contributors.
* Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "pns_walkaround.h"
#include "pns_shove.h"
#include "pns_router.h"
#include "pns_diff_pair_placer.h"
#include "pns_solid.h"
#include "pns_topology.h"
#include "pns_debug_decorator.h"
#include "pns_arc.h"
namespace PNS {
DIFF_PAIR_PLACER::DIFF_PAIR_PLACER( ROUTER* aRouter ) :
PLACEMENT_ALGO( aRouter )
{
m_state = RT_START;
m_chainedPlacement = false;
m_initialDiagonal = false;
m_startDiagonal = false;
m_fitOk = false;
m_netP = nullptr;
m_netN = nullptr;
m_iteration = 0;
m_world = nullptr;
m_shove = nullptr;
m_currentNode = nullptr;
m_lastNode = nullptr;
m_placingVia = false;
m_viaDiameter = 0;
m_viaDrill = 0;
m_currentWidth = 0;
m_currentLayer = 0;
m_startsOnVia = false;
m_orthoMode = false;
m_snapOnTarget = false;
m_currentEndItem = nullptr;
m_currentTraceOk = false;
m_idle = true;
}
DIFF_PAIR_PLACER::~DIFF_PAIR_PLACER()
{}
void DIFF_PAIR_PLACER::setWorld( NODE* aWorld )
{
m_world = aWorld;
}
const VIA DIFF_PAIR_PLACER::makeVia( const VECTOR2I& aP, NET_HANDLE aNet )
{
const LAYER_RANGE layers( m_sizes.GetLayerTop(), m_sizes.GetLayerBottom() );
VIA v( aP, layers, m_sizes.ViaDiameter(), m_sizes.ViaDrill(), aNet, m_sizes.ViaType() );
return v;
}
void DIFF_PAIR_PLACER::SetOrthoMode ( bool aOrthoMode )
{
m_orthoMode = aOrthoMode;
if( !m_idle )
Move( m_currentEnd, nullptr );
}
bool DIFF_PAIR_PLACER::ToggleVia( bool aEnabled )
{
m_placingVia = aEnabled;
if( !m_idle )
Move( m_currentEnd, nullptr );
return true;
}
bool DIFF_PAIR_PLACER::rhMarkObstacles( const VECTOR2I& aP )
{
if( !routeHead( aP ) )
return false;
bool collP = static_cast<bool>( m_currentNode->CheckColliding( &m_currentTrace.PLine() ) );
bool collN = static_cast<bool>( m_currentNode->CheckColliding( &m_currentTrace.NLine() ) );
m_fitOk = !( collP || collN ) ;
return m_fitOk;
}
bool DIFF_PAIR_PLACER::propagateDpHeadForces ( const VECTOR2I& aP, VECTOR2I& aNewP )
{
VIA virtHead = makeVia( aP, nullptr );
if( m_placingVia )
{
virtHead.SetDiameter( viaGap() + 2 * virtHead.Diameter() );
}
else
{
virtHead.SetLayer( m_currentLayer );
virtHead.SetDiameter( m_sizes.DiffPairGap() + 2 * m_sizes.DiffPairWidth() );
}
bool solidsOnly = true;
if( Settings().Mode() == RM_MarkObstacles )
{
aNewP = aP;
return true;
}
else if( Settings().Mode() == RM_Walkaround )
{
solidsOnly = false;
}
// fixme: I'm too lazy to do it well. Circular approximaton will do for the moment.
// Note: this code is lifted from VIA::PushoutForce and then optimized for this use case and to
// check proper clearances to the diff pair line. It can be removed if some specialized
// pushout for traces / diff pairs is implemented. Just calling VIA::PushoutForce does not work
// as the via may have different resolved clearance to items than the diff pair should.
int maxIter = 40;
int iter = 0;
bool collided = false;
VECTOR2I force, totalForce;
std::set<const ITEM*> handled;
while( iter < maxIter )
{
NODE::OPT_OBSTACLE obs = m_currentNode->CheckColliding( &virtHead, solidsOnly ?
ITEM::SOLID_T :
ITEM::ANY_T );
if( !obs || handled.count( obs->m_item ) )
break;
int clearance = m_currentNode->GetClearance( obs->m_item, &m_currentTrace.PLine(), false );
if( obs->m_item->Shape()->Collide( virtHead.Shape(), clearance, &force ) )
{
collided = true;
totalForce += force;
virtHead.SetPos( virtHead.Pos() + force );
}
handled.insert( obs->m_item );
iter++;
}
bool succeeded = ( !collided || iter != maxIter );
if( succeeded )
{
aNewP = aP + force;
return true;
}
return false;
}
bool DIFF_PAIR_PLACER::attemptWalk( NODE* aNode, DIFF_PAIR* aCurrent, DIFF_PAIR& aWalk,
bool aPFirst, bool aWindCw, bool aSolidsOnly )
{
WALKAROUND walkaround( aNode, Router() );
WALKAROUND::WALKAROUND_STATUS wf1;
walkaround.SetSolidsOnly( aSolidsOnly );
walkaround.SetIterationLimit( Settings().WalkaroundIterationLimit() );
SHOVE shove( aNode, Router() );
LINE walkP, walkN;
aWalk = *aCurrent;
int iter = 0;
DIFF_PAIR cur( *aCurrent );
bool currentIsP = aPFirst;
int mask = aSolidsOnly ? ITEM::SOLID_T : ITEM::ANY_T;
do
{
LINE preWalk = ( currentIsP ? cur.PLine() : cur.NLine() );
LINE preShove = ( currentIsP ? cur.NLine() : cur.PLine() );
LINE postWalk;
if( !aNode->CheckColliding ( &preWalk, mask ) )
{
currentIsP = !currentIsP;
if( !aNode->CheckColliding( &preShove, mask ) )
break;
else
continue;
}
wf1 = walkaround.Route( preWalk, postWalk, false );
if( wf1 != WALKAROUND::DONE )
return false;
LINE postShove( preShove );
shove.ForceClearance( true, cur.Gap() - 2 * PNS_HULL_MARGIN );
SHOVE::SHOVE_STATUS sh1;
sh1 = shove.ShoveObstacleLine( postWalk, preShove, postShove );
if( sh1 != SHOVE::SH_OK )
return false;
postWalk.Line().Simplify();
postShove.Line().Simplify();
cur.SetShape( postWalk.CLine(), postShove.CLine(), !currentIsP );
currentIsP = !currentIsP;
if( !aNode->CheckColliding( &postShove, mask ) )
break;
iter++;
}
while( iter < 3 );
if( iter == 3 )
return false;
aWalk.SetShape( cur.CP(), cur.CN() );
return true;
}
bool DIFF_PAIR_PLACER::tryWalkDp( NODE* aNode, DIFF_PAIR &aPair, bool aSolidsOnly )
{
DIFF_PAIR best;
double bestScore = 100000000000000.0;
for( int attempt = 0; attempt <= 3; attempt++ )
{
DIFF_PAIR p;
NODE *tmp = m_currentNode->Branch();
bool pfirst = ( attempt & 1 ) ? true : false;
bool wind_cw = ( attempt & 2 ) ? true : false;
if( attemptWalk( tmp, &aPair, p, pfirst, wind_cw, aSolidsOnly ) )
{
// double len = p.TotalLength();
double cl = 1 + p.CoupledLength();
double skew = p.Skew();
double score = cl + fabs( skew ) * 3.0;
if( score < bestScore )
{
bestScore = score;
best = p;
}
}
delete tmp;
}
if( bestScore > 0.0 )
{
OPTIMIZER optimizer( m_currentNode );
aPair.SetShape( best );
optimizer.Optimize( &aPair );
return true;
}
return false;
}
bool DIFF_PAIR_PLACER::rhWalkOnly( const VECTOR2I& aP )
{
if( !routeHead ( aP ) )
return false;
m_fitOk = tryWalkDp( m_currentNode, m_currentTrace, false );
return m_fitOk;
}
bool DIFF_PAIR_PLACER::route( const VECTOR2I& aP )
{
switch( Settings().Mode() )
{
case RM_MarkObstacles:
return rhMarkObstacles( aP );
case RM_Walkaround:
return rhWalkOnly( aP );
case RM_Shove:
return rhShoveOnly( aP );
default:
break;
}
return false;
}
bool DIFF_PAIR_PLACER::rhShoveOnly( const VECTOR2I& aP )
{
m_currentNode = m_shove->CurrentNode();
bool ok = routeHead( aP );
m_fitOk = false;
if( !ok )
return false;
if( !tryWalkDp( m_currentNode, m_currentTrace, true ) )
return false;
LINE pLine( m_currentTrace.PLine() );
LINE nLine( m_currentTrace.NLine() );
ITEM_SET head;
head.Add( &pLine );
head.Add( &nLine );
SHOVE::SHOVE_STATUS status = m_shove->ShoveMultiLines( head );
m_currentNode = m_shove->CurrentNode();
if( status == SHOVE::SH_OK )
{
m_currentNode = m_shove->CurrentNode();
if( !m_currentNode->CheckColliding( &m_currentTrace.PLine() ) &&
!m_currentNode->CheckColliding( &m_currentTrace.NLine() ) )
{
m_fitOk = true;
}
}
return m_fitOk;
}
const ITEM_SET DIFF_PAIR_PLACER::Traces()
{
ITEM_SET t;
t.Add( &m_currentTrace.PLine() );
t.Add( &m_currentTrace.NLine() );
return t;
}
void DIFF_PAIR_PLACER::FlipPosture()
{
m_startDiagonal = !m_startDiagonal;
if( !m_idle )
Move( m_currentEnd, nullptr );
}
NODE* DIFF_PAIR_PLACER::CurrentNode( bool aLoopsRemoved ) const
{
if( m_lastNode )
return m_lastNode;
return m_currentNode;
}
bool DIFF_PAIR_PLACER::SetLayer( int aLayer )
{
if( m_idle )
{
m_currentLayer = aLayer;
return true;
}
else if( m_chainedPlacement || !m_prevPair )
{
return false;
}
else if( !m_prevPair->PrimP() || ( m_prevPair->PrimP()->OfKind( ITEM::VIA_T ) &&
m_prevPair->PrimP()->Layers().Overlaps( aLayer ) ) )
{
m_currentLayer = aLayer;
m_start = *m_prevPair;
initPlacement();
Move( m_currentEnd, nullptr );
return true;
}
return false;
}
OPT_VECTOR2I getDanglingAnchor( NODE* aNode, ITEM* aItem )
{
switch( aItem->Kind() )
{
case ITEM::LINE_T:
{
LINE* l = static_cast<LINE*>( aItem );
if( !l->PointCount() )
return OPT_VECTOR2I();
else
return l->CPoint( 0 );
}
case ITEM::VIA_T:
case ITEM::SOLID_T:
return aItem->Anchor( 0 );
case ITEM::ARC_T:
{
ARC* a = static_cast<ARC*>( aItem );
const JOINT* jA = aNode->FindJoint( aItem->Anchor( 0 ), aItem );
const JOINT* jB = aNode->FindJoint( aItem->Anchor( 1 ), aItem );
if( jA && jA->LinkCount() == 1 )
return a->Arc().GetP0();
else if( jB && jB->LinkCount() == 1 )
return a->Arc().GetP1();
else
return OPT_VECTOR2I();
}
case ITEM::SEGMENT_T:
{
SEGMENT* s = static_cast<SEGMENT*>( aItem );
const JOINT* jA = aNode->FindJoint( aItem->Anchor( 0 ), aItem );
const JOINT* jB = aNode->FindJoint( aItem->Anchor( 1 ), aItem );
if( jA && jA->LinkCount() == 1 )
return s->Seg().A;
else if( jB && jB->LinkCount() == 1 )
return s->Seg().B;
else
return OPT_VECTOR2I();
}
default:
return OPT_VECTOR2I();
}
}
bool DIFF_PAIR_PLACER::FindDpPrimitivePair( NODE* aWorld, const VECTOR2I& aP, ITEM* aItem,
DP_PRIMITIVE_PAIR& aPair, wxString* aErrorMsg )
{
NET_HANDLE netP, netN;
bool result = aWorld->GetRuleResolver()->DpNetPair( aItem, netP, netN );
if( !result )
{
if( aErrorMsg )
{
*aErrorMsg = _( "Unable to find complementary differential pair "
"nets. Make sure the names of the nets belonging "
"to a differential pair end with either N/P or +/-." );
}
return false;
}
NET_HANDLE refNet = aItem->Net();
NET_HANDLE coupledNet = ( refNet == netP ) ? netN : netP;
OPT_VECTOR2I refAnchor = getDanglingAnchor( aWorld, aItem );
ITEM* primRef = aItem;
if( !refAnchor )
{
if( aErrorMsg )
{
*aErrorMsg = _( "Can't find a suitable starting point. If starting "
"from an existing differential pair make sure you are "
"at the end." );
}
return false;
}
std::set<ITEM*> coupledItems;
aWorld->AllItemsInNet( coupledNet, coupledItems );
double bestDist = std::numeric_limits<double>::max();
bool found = false;
for( ITEM* item : coupledItems )
{
if( item->Kind() == aItem->Kind() )
{
OPT_VECTOR2I anchor = getDanglingAnchor( aWorld, item );
if( !anchor )
continue;
double dist = ( *anchor - *refAnchor ).EuclideanNorm();
bool shapeMatches = true;
if( item->OfKind( ITEM::SOLID_T | ITEM::VIA_T ) && item->Layers() != aItem->Layers() )
{
shapeMatches = false;
}
if( dist < bestDist && shapeMatches )
{
found = true;
bestDist = dist;
if( refNet != netP )
{
aPair = DP_PRIMITIVE_PAIR ( item, primRef );
aPair.SetAnchors( *anchor, *refAnchor );
}
else
{
aPair = DP_PRIMITIVE_PAIR( primRef, item );
aPair.SetAnchors( *refAnchor, *anchor );
}
}
}
}
if( !found )
{
if( aErrorMsg )
{
*aErrorMsg = wxString::Format( _( "Can't find a suitable starting point "
"for coupled net \"%s\"." ),
aWorld->GetRuleResolver()->NetName( coupledNet ) );
}
return false;
}
return true;
}
int DIFF_PAIR_PLACER::viaGap() const
{
return std::max( m_sizes.DiffPairViaGap(),
m_sizes.GetDiffPairHoleToHole() + m_sizes.ViaDrill() - m_sizes.ViaDiameter() );
}
int DIFF_PAIR_PLACER::gap() const
{
return m_sizes.DiffPairGap() + m_sizes.DiffPairWidth();
}
bool DIFF_PAIR_PLACER::Start( const VECTOR2I& aP, ITEM* aStartItem )
{
VECTOR2I p( aP );
setWorld( Router()->GetWorld() );
m_currentNode = m_world;
wxString err_msg;
if( !FindDpPrimitivePair( m_currentNode, aP, aStartItem, m_start, &err_msg ) )
{
Router()->SetFailureReason( err_msg );
return false;
}
m_netP = m_start.PrimP()->Net();
m_netN = m_start.PrimN()->Net();
m_currentStart = p;
m_currentEnd = p;
m_placingVia = false;
m_chainedPlacement = false;
m_currentTraceOk = false;
m_currentTrace = DIFF_PAIR();
m_currentTrace.SetNets( m_netP, m_netN );
initPlacement();
return true;
}
void DIFF_PAIR_PLACER::initPlacement()
{
m_idle = false;
m_orthoMode = false;
m_currentEndItem = nullptr;
m_startDiagonal = m_initialDiagonal;
NODE* world = Router()->GetWorld();
world->KillChildren();
NODE* rootNode = world->Branch();
setWorld( rootNode );
m_lastNode = nullptr;
m_currentNode = rootNode;
m_shove = std::make_unique<SHOVE>( m_currentNode, Router() );
}
bool DIFF_PAIR_PLACER::routeHead( const VECTOR2I& aP )
{
m_fitOk = false;
DP_GATEWAYS gwsEntry( gap() );
DP_GATEWAYS gwsTarget( gap() );
if( !m_prevPair )
m_prevPair = m_start;
gwsEntry.BuildFromPrimitivePair( *m_prevPair, m_startDiagonal );
DP_PRIMITIVE_PAIR target;
if( FindDpPrimitivePair( m_currentNode, aP, m_currentEndItem, target ) )
{
gwsTarget.BuildFromPrimitivePair( target, m_startDiagonal );
m_snapOnTarget = true;
}
else
{
VECTOR2I fp;
if( !propagateDpHeadForces( aP, fp ) )
return false;
VECTOR2I midp, dirV;
m_prevPair->CursorOrientation( fp, midp, dirV );
VECTOR2I fpProj = SEG( midp, midp + dirV ).LineProject( fp );
// compute 'leader point' distance from the cursor (project cursor position
// on the extension of the starting segment pair of the DP)
int lead_dist = ( fpProj - fp ).EuclideanNorm();
gwsTarget.SetFitVias( m_placingVia, m_sizes.ViaDiameter(), viaGap() );
// far from the initial segment extension line -> allow a 45-degree obtuse turn
if( lead_dist > ( m_sizes.DiffPairGap() + m_sizes.DiffPairWidth() ) / 2 )
{
gwsTarget.BuildForCursor( fp );
}
else
{
// close to the initial segment extension line -> keep straight part only, project
// as close as possible to the cursor.
gwsTarget.BuildForCursor( fpProj );
gwsTarget.FilterByOrientation( DIRECTION_45::ANG_STRAIGHT | DIRECTION_45::ANG_HALF_FULL,
DIRECTION_45( dirV ) );
}
m_snapOnTarget = false;
}
m_currentTrace.SetGap( gap() );
m_currentTrace.SetLayer( m_currentLayer );
bool result = gwsEntry.FitGateways( gwsEntry, gwsTarget, m_startDiagonal, m_currentTrace );
if( result )
{
m_currentTraceOk = true;
m_currentTrace.SetNets( m_netP, m_netN );
m_currentTrace.SetWidth( m_sizes.DiffPairWidth() );
m_currentTrace.SetGap( m_sizes.DiffPairGap() );
if( m_placingVia )
{
m_currentTrace.AppendVias ( makeVia( m_currentTrace.CP().CPoint( -1 ), m_netP ),
makeVia( m_currentTrace.CN().CPoint( -1 ), m_netN ) );
}
else
{
m_currentTrace.RemoveVias();
}
return true;
}
return m_currentTraceOk;
}
bool DIFF_PAIR_PLACER::Move( const VECTOR2I& aP , ITEM* aEndItem )
{
m_currentEndItem = aEndItem;
m_fitOk = false;
delete m_lastNode;
m_lastNode = nullptr;
bool retval = route( aP );
NODE* latestNode = m_currentNode;
m_lastNode = latestNode->Branch();
assert( m_lastNode != nullptr );
m_currentEnd = aP;
updateLeadingRatLine();
return retval;
}
void DIFF_PAIR_PLACER::UpdateSizes( const SIZES_SETTINGS& aSizes )
{
m_sizes = aSizes;
if( !m_idle )
{
m_currentTrace.SetWidth( m_sizes.DiffPairWidth() );
m_currentTrace.SetGap( m_sizes.DiffPairGap() );
if( m_currentTrace.EndsWithVias() )
{
m_currentTrace.SetViaDiameter( m_sizes.ViaDiameter() );
m_currentTrace.SetViaDrill( m_sizes.ViaDrill() );
}
}
}
bool DIFF_PAIR_PLACER::FixRoute( const VECTOR2I& aP, ITEM* aEndItem, bool aForceFinish )
{
if( !m_fitOk && !Settings().AllowDRCViolations() )
return false;
if( m_currentTrace.CP().SegmentCount() < 1 || m_currentTrace.CN().SegmentCount() < 1 )
return false;
if( m_currentTrace.CP().SegmentCount() > 1 )
m_initialDiagonal = !DIRECTION_45( m_currentTrace.CP().CSegment( -2 ) ).IsDiagonal();
TOPOLOGY topo( m_lastNode );
if( !m_snapOnTarget && !m_currentTrace.EndsWithVias() && !aForceFinish &&
!Settings().GetFixAllSegments() )
{
SHAPE_LINE_CHAIN newP( m_currentTrace.CP() );
SHAPE_LINE_CHAIN newN( m_currentTrace.CN() );
if( newP.SegmentCount() > 1 && newN.SegmentCount() > 1 )
{
newP.Remove( -1, -1 );
newN.Remove( -1, -1 );
}
m_currentTrace.SetShape( newP, newN );
}
if( m_currentTrace.EndsWithVias() )
{
m_lastNode->Add( Clone( m_currentTrace.PLine().Via() ) );
m_lastNode->Add( Clone( m_currentTrace.NLine().Via() ) );
m_chainedPlacement = false;
}
else
{
m_chainedPlacement = !m_snapOnTarget && !aForceFinish;
}
LINE lineP( m_currentTrace.PLine() );
LINE lineN( m_currentTrace.NLine() );
m_lastNode->Add( lineP );
m_lastNode->Add( lineN );
topo.SimplifyLine( &lineP );
topo.SimplifyLine( &lineN );
m_prevPair = m_currentTrace.EndingPrimitives();
CommitPlacement();
m_placingVia = false;
if( m_snapOnTarget || aForceFinish )
{
m_idle = true;
return true;
}
else
{
initPlacement();
return false;
}
}
bool DIFF_PAIR_PLACER::AbortPlacement()
{
m_world->KillChildren();
return true;
}
bool DIFF_PAIR_PLACER::HasPlacedAnything() const
{
return m_currentTrace.CP().SegmentCount() > 0 || m_currentTrace.CN().SegmentCount() > 0;
}
bool DIFF_PAIR_PLACER::CommitPlacement()
{
if( m_lastNode )
Router()->CommitRouting( m_lastNode );
m_lastNode = nullptr;
m_currentNode = nullptr;
return true;
}
void DIFF_PAIR_PLACER::GetModifiedNets( std::vector<NET_HANDLE> &aNets ) const
{
aNets.push_back( m_netP );
aNets.push_back( m_netN );
}
void DIFF_PAIR_PLACER::updateLeadingRatLine()
{
SHAPE_LINE_CHAIN ratLineN, ratLineP;
TOPOLOGY topo( m_lastNode );
if( topo.LeadingRatLine( &m_currentTrace.PLine(), ratLineP ) )
m_router->GetInterface()->DisplayRatline( ratLineP, m_netP );
if( topo.LeadingRatLine ( &m_currentTrace.NLine(), ratLineN ) )
m_router->GetInterface()->DisplayRatline( ratLineN, m_netN );
}
const std::vector<NET_HANDLE> DIFF_PAIR_PLACER::CurrentNets() const
{
std::vector<NET_HANDLE> rv;
rv.push_back( m_netP );
rv.push_back( m_netN );
return rv;
}
}