601 lines
17 KiB
C
601 lines
17 KiB
C
/* rtrChannel.c -
|
||
*
|
||
* Code to handle channels and the obstacles within them.
|
||
*
|
||
* *********************************************************************
|
||
* * Copyright (C) 1985, 1990 Regents of the University of California. *
|
||
* * Permission to use, copy, modify, and distribute this *
|
||
* * software and its documentation for any purpose and without *
|
||
* * fee is hereby granted, provided that the above copyright *
|
||
* * notice appear in all copies. The University of California *
|
||
* * makes no representations about the suitability of this *
|
||
* * software for any purpose. It is provided "as is" without *
|
||
* * express or implied warranty. Export of this software outside *
|
||
* * of the United States of America may require an export license. *
|
||
* *********************************************************************
|
||
*/
|
||
|
||
#ifndef lint
|
||
static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/router/rtrChannel.c,v 1.1.1.1 2008/02/03 20:43:50 tim Exp $";
|
||
#endif /* not lint */
|
||
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
#include "utils/magic.h"
|
||
#include "utils/geometry.h"
|
||
#include "utils/geofast.h"
|
||
#include "utils/hash.h"
|
||
#include "utils/heap.h"
|
||
#include "tiles/tile.h"
|
||
#include "database/database.h"
|
||
#include "gcr/gcr.h"
|
||
#include "windows/windows.h"
|
||
#include "dbwind/dbwind.h"
|
||
#include "utils/signals.h"
|
||
#include "utils/main.h"
|
||
#include "router/router.h"
|
||
#include "router/rtrDcmpose.h"
|
||
#include "grouter/grouter.h"
|
||
#include "textio/textio.h"
|
||
#include "utils/styles.h"
|
||
|
||
/*
|
||
* Maps a tile pointer to a channel structure.
|
||
* We use this rather than the client fields of tiles because
|
||
* they are already used to hold flags during channel decomposition.
|
||
*/
|
||
HashTable RtrTileToChannel;
|
||
|
||
/* Plane in __CHANNEL__ def holding channel tiles */
|
||
Plane *RtrChannelPlane;
|
||
|
||
/* List of channels created from decomposed tile plane */
|
||
GCRChannel *RtrChannelList = NULL;
|
||
|
||
/* Multiplier for when to make end connections */
|
||
#ifndef lint
|
||
float RtrEndConst = 1.0;
|
||
#else
|
||
float RtrEndConst; /* Sun lint brain death */
|
||
#endif /* lint */
|
||
|
||
/* Forward declarations */
|
||
extern int rtrChannelObstacleMark();
|
||
extern void rtrChannelObstaclePins();
|
||
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------------
|
||
*
|
||
* RtrChannelRoute --
|
||
*
|
||
* This procedure invokes the channel router for the channel 'ch'.
|
||
* If the channel is taller than it is wide, swap in x and y before
|
||
* routing. If the channel has more pins on the left edge than the
|
||
* right, flip left to right before routing.
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* The parameter *pCount is incremented by the number of errors that
|
||
* occurred while routing this channel.
|
||
*
|
||
* ----------------------------------------------------------------------------
|
||
*/
|
||
|
||
void
|
||
RtrChannelRoute(ch, pCount)
|
||
GCRChannel *ch;
|
||
int *pCount;
|
||
{
|
||
GCRChannel *flipped, *flipped_again, *copy;
|
||
int errs1, errs2;
|
||
|
||
/*
|
||
* Mark unused stem-tip crossing points as unused once again.
|
||
* (They had been marked as blocked during global routing so
|
||
* the global router wouldn't try to use them to route over
|
||
* cell tops, thereby making it impossible to generate a stem).
|
||
*/
|
||
RtrPinsFixStems(ch);
|
||
|
||
/*
|
||
* If the channel is taller than it is long, flip it before
|
||
* routing, in order to give the router a better chance of success.
|
||
*/
|
||
if (ch->gcr_width < ch->gcr_length)
|
||
{
|
||
flipped = GCRNewChannel(ch->gcr_length, ch->gcr_width);
|
||
GCRNoFlip(ch, flipped);
|
||
errs1 = GCRroute(flipped);
|
||
if (errs1 == 0)
|
||
{
|
||
/* Save the routing results back in ch. Clean up and return */
|
||
GCRNoFlip(flipped, ch);
|
||
RtrFBPaint(0);
|
||
goto bottom;
|
||
}
|
||
|
||
/* Try again with a left-right flip */
|
||
RtrFBSwitch();
|
||
flipped_again = GCRNewChannel(ch->gcr_length, ch->gcr_width);
|
||
GCRFlipLeftRight(ch, flipped_again);
|
||
errs2 = GCRroute(flipped_again);
|
||
if (GcrDebug)
|
||
TxError(" Rerouting a channel with %d errors...", errs1);
|
||
if (errs2 < errs1)
|
||
{
|
||
errs1 = errs2;
|
||
GCRFlipLeftRight(flipped_again, ch);
|
||
if (GcrDebug)
|
||
TxError(" to get %d errors\n", errs1);
|
||
RtrFBPaint(1);
|
||
}
|
||
else
|
||
{
|
||
GCRNoFlip(flipped, ch);
|
||
if(GcrDebug)
|
||
TxError(" unsuccessfully.\n");
|
||
RtrFBPaint(0);
|
||
}
|
||
GCRFreeChannel(flipped_again);
|
||
goto bottom;
|
||
}
|
||
else
|
||
{
|
||
flipped = GCRNewChannel(ch->gcr_width, ch->gcr_length);
|
||
GCRFlipXY(ch, flipped);
|
||
errs1 = GCRroute(flipped);
|
||
if(errs1 == 0)
|
||
{
|
||
GCRFlipXY(flipped, ch);
|
||
RtrFBPaint(0);
|
||
goto bottom;
|
||
}
|
||
|
||
RtrFBSwitch();
|
||
flipped_again = GCRNewChannel(flipped->gcr_length, flipped->gcr_width);
|
||
GCRFlipXY(ch, flipped_again);
|
||
copy = GCRNewChannel(flipped->gcr_length, flipped->gcr_width);
|
||
GCRFlipLeftRight(flipped_again, copy);
|
||
if(GcrDebug)
|
||
TxError(" Rerouting a channel with %d errors ...", errs1);
|
||
errs2 = GCRroute(copy);
|
||
if(errs2 < errs1)
|
||
{
|
||
errs1 = errs2;
|
||
GCRFlipLeftRight(copy, flipped);
|
||
if(GcrDebug)
|
||
TxError(" successfully, with %d errors\n", errs1);
|
||
RtrFBPaint(1);
|
||
}
|
||
else
|
||
{
|
||
RtrFBPaint(0);
|
||
if(GcrDebug)
|
||
TxError(" unsuccessfully\n");
|
||
}
|
||
|
||
GCRFlipXY(flipped, ch);
|
||
GCRFreeChannel(flipped_again);
|
||
}
|
||
|
||
bottom:
|
||
GCRFreeChannel(flipped);
|
||
if (errs1 > 0)
|
||
gcrSaveChannel(ch);
|
||
*pCount += errs1;
|
||
RtrMilestonePrint();
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------------
|
||
*
|
||
* RtrChannelBounds --
|
||
*
|
||
* Figure out the dimensions of the given channel.
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* The parameters plength and pwidth are filled in with the number
|
||
* of usable columns and rows in channel. The Point pointed to
|
||
* by 'origin' is filled in with x and y coords to go in
|
||
* ch->origin.
|
||
*
|
||
* ----------------------------------------------------------------------------
|
||
*/
|
||
|
||
void
|
||
RtrChannelBounds(loc, pLength, pWidth, origin)
|
||
Rect *loc; /* Area the channel is to occupy */
|
||
int *pLength; /* Filled in with # columns in channel */
|
||
int *pWidth; /* Filled in with # rows in channel */
|
||
Point *origin; /* Filled in with coords of (0,0) grid point
|
||
* (one grid line below and to left of first
|
||
* usable grid point)
|
||
*/
|
||
{
|
||
char mesg[256];
|
||
int hi, lo;
|
||
|
||
/* Find rightmost and leftmost grid contained within channel area */
|
||
hi = RTR_GRIDDOWN(loc->r_xtop, RtrOrigin.p_x);
|
||
lo = RTR_GRIDUP(loc->r_xbot, RtrOrigin.p_x);
|
||
origin->p_x = lo - RtrGridSpacing;
|
||
if (hi < lo)
|
||
{
|
||
(void) sprintf(mesg, "Degenerate channel at (%d, %d) (%d, %d)",
|
||
loc->r_xbot, loc->r_ybot, loc->r_xtop, loc->r_ytop);
|
||
DBWFeedbackAdd(loc, mesg, EditCellUse->cu_def, 1, STYLE_PALEHIGHLIGHTS);
|
||
TxError("%s\n", mesg);
|
||
}
|
||
*pLength = (hi - lo) / RtrGridSpacing + 1;
|
||
|
||
/* Find topmost and bottommost grid contained within channel area */
|
||
hi = RTR_GRIDDOWN(loc->r_ytop, RtrOrigin.p_y);
|
||
lo = RTR_GRIDUP(loc->r_ybot, RtrOrigin.p_y);
|
||
origin->p_y = lo - RtrGridSpacing;
|
||
if (hi < lo)
|
||
{
|
||
(void) sprintf(mesg, "Degenerate channel at (%d, %d) (%d, %d)",
|
||
loc->r_xbot, loc->r_ybot, loc->r_xtop, loc->r_ytop);
|
||
DBWFeedbackAdd(loc, mesg, EditCellUse->cu_def, 1, STYLE_PALEHIGHLIGHTS);
|
||
TxError("%s\n", mesg);
|
||
}
|
||
*pWidth = (hi - lo) / RtrGridSpacing + 1;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------------
|
||
*
|
||
* RtrChannelObstacles --
|
||
*
|
||
* Searches a channel area for obstacles. Sets the channel obstacle map
|
||
* to indicate which grid locations have obstacles. Uses DBTreeSrTiles()
|
||
* to search for obstacles, rather than DBSrPaintArea, since channels can
|
||
* lie over existing cells.
|
||
*
|
||
* Mark obstacles to channel crossings. Reserve blocked pins for net
|
||
* GCR_BLOCKEDNETID. Flag obstructed pins to allow the size of their
|
||
* accompanying obstacle to be set later (by RtrHazards()).
|
||
*
|
||
* Only obstacles in this channel are considered; GLBlockPins() must be
|
||
* called later to propagate the obstacle information from the border of
|
||
* this channel to adjacent channels.
|
||
*
|
||
* NOTE: RtrChannelObstacles() and those procedures it calls are heavily
|
||
* tuned for speed, since DBTreeSrTiles can cause flat searching of
|
||
* large portions of a design hierarchy if invoked over the tops of cells.
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* Sets the channel obstacle map.
|
||
*
|
||
* ----------------------------------------------------------------------------
|
||
*/
|
||
|
||
void
|
||
RtrChannelObstacles(use, ch)
|
||
CellUse *use;
|
||
GCRChannel * ch;
|
||
{
|
||
int l, w, up = RtrSubcellSepUp, down = RtrSubcellSepDown;
|
||
TileTypeBitMask allObs;
|
||
SearchContext scx;
|
||
|
||
/*
|
||
* Set the obstacle maps in the metal and poly planes.
|
||
* Ensure that the combination of this channel and its
|
||
* neighbors are sufficient to cover all obstacles in
|
||
* the area between the outermost tracks/columns of this
|
||
* channel and those of its neighbors.
|
||
*/
|
||
if (RtrSubcellSepUp + RtrSubcellSepDown < RtrGridSpacing)
|
||
{
|
||
/* Shouldn't happen; this is just insurance */
|
||
up = RtrGridSpacing - RtrSubcellSepDown;
|
||
}
|
||
l = ch->gcr_length + 1;
|
||
w = ch->gcr_width + 1;
|
||
scx.scx_area.r_ll = scx.scx_area.r_ur = ch->gcr_origin;
|
||
scx.scx_area.r_xbot -= up;
|
||
scx.scx_area.r_ybot -= up;
|
||
scx.scx_area.r_xtop += l * RtrGridSpacing + down;
|
||
scx.scx_area.r_ytop += w * RtrGridSpacing + down;
|
||
scx.scx_use = use;
|
||
scx.scx_trans = GeoIdentityTransform;
|
||
TTMaskZero(&allObs);
|
||
TTMaskSetMask3(&allObs, &RtrMetalObstacles, &RtrPolyObstacles);
|
||
(void) DBTreeSrTiles(&scx, &allObs, 0, rtrChannelObstacleMark, (ClientData) ch);
|
||
|
||
rtrChannelObstaclePins(ch);
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------------
|
||
*
|
||
* rtrChannelObstaclePins --
|
||
*
|
||
* Mark obstacles to channel crossings. An obstacle affects a channel
|
||
* crossing if it lies within the area from the first track of this
|
||
* channel to the first track of the next channel. Reserve blocked pins
|
||
* for net GCR_BLOCKEDNETID (ILLEGAL). Flag obstructed pins to allow the
|
||
* size of their accompanying obstacle to be set later.
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* Sets the channel obstacle map.
|
||
*
|
||
* ----------------------------------------------------------------------------
|
||
*/
|
||
|
||
void
|
||
rtrChannelObstaclePins(ch)
|
||
GCRChannel *ch;
|
||
{
|
||
short **res;
|
||
int row, col, end;
|
||
|
||
res = ch->gcr_result;
|
||
end = ch->gcr_length + 1;
|
||
for (row = 1; row <= ch->gcr_width; row++)
|
||
{
|
||
if (BLOCK(res[0][row]))
|
||
{
|
||
ch->gcr_lPins[row].gcr_pId = GCR_BLOCKEDNETID;
|
||
ch->gcr_lPins[row].gcr_pFlags = GCRBLK;
|
||
}
|
||
else if (!CLEAR(res[0][row])) ch->gcr_lPins[row].gcr_pFlags = GCROBST;
|
||
|
||
if (BLOCK(res[end][row]))
|
||
{
|
||
ch->gcr_rPins[row].gcr_pId = GCR_BLOCKEDNETID;
|
||
ch->gcr_rPins[row].gcr_pFlags = GCRBLK;
|
||
}
|
||
else if (!CLEAR(res[end][row])) ch->gcr_rPins[row].gcr_pFlags = GCROBST;
|
||
}
|
||
|
||
end = ch->gcr_width+1;
|
||
for (col = 1; col <= ch->gcr_length; col++)
|
||
{
|
||
if (BLOCK(res[col][0]))
|
||
{
|
||
ch->gcr_bPins[col].gcr_pId = GCR_BLOCKEDNETID;
|
||
ch->gcr_bPins[col].gcr_pFlags = GCRBLK;
|
||
}
|
||
else if (!CLEAR(res[col][0])) ch->gcr_bPins[col].gcr_pFlags = GCROBST;
|
||
|
||
if (BLOCK(res[col][end]))
|
||
{
|
||
ch->gcr_tPins[col].gcr_pId = GCR_BLOCKEDNETID;
|
||
ch->gcr_tPins[col].gcr_pFlags = GCRBLK;
|
||
}
|
||
else if (!CLEAR(res[col][end])) ch->gcr_tPins[col].gcr_pFlags = GCROBST;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------------
|
||
*
|
||
* rtrChannelObstacleMark --
|
||
*
|
||
* Search routine called by DBTreeSrTiles() from RtrChannelObstacles
|
||
* for each tile found in the given area. If the tile is an obstacle,
|
||
* mark locations in the channel flag map to indicate their presence.
|
||
*
|
||
* In addition to setting the GCRBLKM/GCRBLKP flags, we set the bits
|
||
* GCRBLKT/GCRBLKC to indicate whether the obstacle blocks a track or
|
||
* a column. These latter bits are used only for initializing the
|
||
* density information for global routing, and should be reset to
|
||
* zero prior to calling the channel router (since they conflict
|
||
* with the bits used to show routing).
|
||
*
|
||
* Results:
|
||
* Always returns 0.
|
||
*
|
||
* Side effects:
|
||
* Sets the obstacle map for obstacles in a given channel.
|
||
*
|
||
* ----------------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
rtrChannelObstacleMark(tile, cxp)
|
||
Tile *tile;
|
||
TreeContext *cxp;
|
||
{
|
||
short **mcol, *mrow, *mrowend, mask;
|
||
GCRChannel *ch = (GCRChannel *) cxp->tc_filter->tf_arg;
|
||
TileType type = TiGetType(tile);
|
||
int loX, numX, loY;
|
||
short **mcolend;
|
||
int n;
|
||
Rect r, r2;
|
||
|
||
mask = 0;
|
||
if (TTMaskHasType(&RtrMetalObstacles, type)) mask |= GCRBLKM;
|
||
if (TTMaskHasType(&RtrPolyObstacles, type)) mask |= GCRBLKP;
|
||
if (mask == 0)
|
||
return (0);
|
||
|
||
TITORECT(tile, &r);
|
||
GEOTRANSRECT(&cxp->tc_scx->scx_trans, &r, &r2);
|
||
|
||
/*
|
||
* Determine the range of columns in this channel that are blocked
|
||
* by the obstacle: find the grid coordinates enclosing the tile.
|
||
* Clip to a grid 1 larger than the size of the channel.
|
||
*/
|
||
n = r2.r_xbot - RtrPaintSepsDown[type] + 1;
|
||
n = (RTR_GRIDUP(n, RtrOrigin.p_x) - ch->gcr_origin.p_x) / RtrGridSpacing;
|
||
loX = MAX(n, 0);
|
||
n = r2.r_xtop + RtrPaintSepsUp[type] - 1;
|
||
n = (RTR_GRIDUP(n, RtrOrigin.p_x) - ch->gcr_origin.p_x) / RtrGridSpacing;
|
||
numX = MIN(n, ch->gcr_length + 1) - loX;
|
||
|
||
/* Do the same thing for the rows (n will be the number of rows) */
|
||
n = r2.r_ybot - RtrPaintSepsDown[type] + 1;
|
||
n = (RTR_GRIDUP(n, RtrOrigin.p_y) - ch->gcr_origin.p_y) / RtrGridSpacing;
|
||
loY = MAX(n, 0);
|
||
n = r2.r_ytop + RtrPaintSepsUp[type] - 1;
|
||
n = (RTR_GRIDDOWN(n, RtrOrigin.p_y) - ch->gcr_origin.p_y) / RtrGridSpacing;
|
||
n = MIN(n, ch->gcr_width + 1) - loY;
|
||
|
||
/*
|
||
* Figure out whether tracks or columns are being blocked,
|
||
* for purposes of the density initialization.
|
||
*/
|
||
if ((mask & (GCRBLKM|GCRBLKP)) == (GCRBLKM|GCRBLKP))
|
||
{
|
||
/* 2-layer obstacles block both tracks and columns */
|
||
mask |= GCRBLKT|GCRBLKC;
|
||
}
|
||
else if (numX < n)
|
||
{
|
||
/* Tall and narrow obstacles block columns */
|
||
mask |= GCRBLKC;
|
||
}
|
||
else
|
||
{
|
||
/* Short and wide obstacles block tracks */
|
||
mask |= GCRBLKT;
|
||
}
|
||
|
||
/*
|
||
* Now set the flags in the channel.
|
||
* The following loop is tuned for speed.
|
||
*/
|
||
mcol = &ch->gcr_result[loX];
|
||
mcolend = &mcol[numX];
|
||
while (mcol <= mcolend)
|
||
{
|
||
mrow = &(*mcol++)[loY];
|
||
mrowend = &mrow[n];
|
||
while (mrow <= mrowend)
|
||
*mrow++ |= mask;
|
||
}
|
||
|
||
return (0);
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------------
|
||
*
|
||
* RtrChannelDensity --
|
||
*
|
||
* Adjusts the density variables gcr_dRowsByCol and gcr_dColsByRow
|
||
* to reflect the commitment of tracks or columns to existing wiring;
|
||
* for each column or track blocked by material, the density for
|
||
* that track is incremented. Also updates gcr_dMaxByCol and
|
||
* gcr_dMaxByRow, the maximum column and track densities.
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* Initializes ch->gcr_dRowsByCol, ch->gcr_dColsByRow arrays
|
||
* and the corresponding maximum values ch->gcr_dMaxByCol
|
||
* and ch->gcr_dMaxByRow.
|
||
*
|
||
* ----------------------------------------------------------------------------
|
||
*/
|
||
|
||
void
|
||
RtrChannelDensity(ch)
|
||
GCRChannel *ch;
|
||
{
|
||
short *hdens, *vdens, *rptr;
|
||
int col, density;
|
||
short *hdend, *vdend;
|
||
|
||
/*
|
||
* Adjust the density information for global routing.
|
||
* Whether a column or track is blocked is indicated by the
|
||
* presence of the GCRBLKC/GCRBLKT flags in the result array.
|
||
* If both bits are set, then both a column and a track are
|
||
* blocked.
|
||
*/
|
||
hdens = &ch->gcr_dRowsByCol[1];
|
||
hdend = &ch->gcr_dRowsByCol[ch->gcr_length];
|
||
for (col = 1; hdens <= hdend; hdens++, col++)
|
||
{
|
||
vdens = &ch->gcr_dColsByRow[1];
|
||
vdend = &ch->gcr_dColsByRow[ch->gcr_width];
|
||
rptr = &ch->gcr_result[col][1];
|
||
for ( ; vdens <= vdend; vdens++, rptr++)
|
||
{
|
||
if (*rptr & GCRBLKT) *hdens += 1;
|
||
if (*rptr & GCRBLKC) *vdens += 1;
|
||
}
|
||
}
|
||
|
||
#ifdef IDENSITY
|
||
bcopy((char *) ch->gcr_dColsByRow, (char *) ch->gcr_iColsByRow,
|
||
(ch->gcr_width + 2) * sizeof (short));
|
||
bcopy((char *) ch->gcr_dRowsByCol, (char *) ch->gcr_iRowsByCol,
|
||
(ch->gcr_length + 2) * sizeof (short));
|
||
#endif /* IDENSITY */
|
||
|
||
/* Compute the maximum row and column density */
|
||
|
||
/* Column density */
|
||
density = 0;
|
||
hdens = &ch->gcr_dRowsByCol[1];
|
||
hdend = &ch->gcr_dRowsByCol[ch->gcr_length];
|
||
for ( ; hdens <= hdend; hdens++)
|
||
if (*hdens > density) density = *hdens;
|
||
ch->gcr_dMaxByCol = density;
|
||
|
||
/* Row density */
|
||
density = 0;
|
||
vdens = &ch->gcr_dColsByRow[1];
|
||
vdend = &ch->gcr_dColsByRow[ch->gcr_width];
|
||
for ( ; vdens <= vdend; vdens++)
|
||
if (*vdens > density) density = *vdens;
|
||
ch->gcr_dMaxByRow = density;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------------
|
||
*
|
||
* RtrChannelCleanObstacles --
|
||
*
|
||
* Clears the GCRBLKC/GCRBLKT bits in the result array of 'ch'.
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* Resets the GCRBLKC/GCRBLKT flags in the result array,
|
||
* since these bits are also used to mean something else
|
||
* by the channel router.
|
||
*
|
||
* ----------------------------------------------------------------------------
|
||
*/
|
||
|
||
void
|
||
RtrChannelCleanObstacles(ch)
|
||
GCRChannel *ch;
|
||
{
|
||
short *rptr;
|
||
int row, rtop;
|
||
int col, ctop;
|
||
|
||
ctop = ch->gcr_length + 1;
|
||
rtop = ch->gcr_width + 1;
|
||
for (col = 0; col <= ctop; col++)
|
||
{
|
||
rptr = &ch->gcr_result[col][0];
|
||
for (row = 0; row <= rtop; row++)
|
||
*rptr++ &= ~(GCRBLKT|GCRBLKC);
|
||
}
|
||
}
|