mirror of https://github.com/VLSIDA/OpenRAM.git
Merge branch 'dev' into tech_migration
This commit is contained in:
commit
ad98137cd4
16
README.md
16
README.md
|
|
@ -18,7 +18,7 @@ An open-source static random access memory (SRAM) compiler.
|
|||
# What is OpenRAM?
|
||||
<img align="right" width="25%" src="images/SCMOS_16kb_sram.jpg">
|
||||
|
||||
OpenRAM is an open-source Python framework to create the layout,
|
||||
OpenRAM is an award winning open-source Python framework to create the layout,
|
||||
netlists, timing and power models, placement and routing models, and
|
||||
other views necessary to use SRAMs in ASIC design. OpenRAM supports
|
||||
integration in both commercial and open-source flows with both
|
||||
|
|
@ -195,7 +195,7 @@ specific technology (e.g., [FreePDK45]) should be a subdirectory
|
|||
+ Report bugs by submitting [Github issues].
|
||||
+ Develop new features (see [how to contribute](./CONTRIBUTING.md))
|
||||
+ Submit code/fixes using a [Github pull request]
|
||||
+ Follow our [project][Github projects].
|
||||
+ Follow our [project][Github project].
|
||||
+ Read and cite our [ICCAD paper][OpenRAMpaper]
|
||||
|
||||
# Further Help
|
||||
|
|
@ -214,15 +214,7 @@ OpenRAM is licensed under the [BSD 3-clause License](./LICENSE).
|
|||
|
||||
- [Matthew Guthaus] from [VLSIDA] created the OpenRAM project and is the lead architect.
|
||||
- [James Stine] from [VLSIARCH] co-founded the project.
|
||||
- Hunter Nichols maintains and updates the timing characterization.
|
||||
- Michael Grimes created and maintains the multiport netlist code.
|
||||
- Jennifer Sowash is creating the OpenRAM IP library.
|
||||
- Jesse Cirimelli-Low created the datasheet generation.
|
||||
- Samira Ataei created early multi-bank layouts and control logic.
|
||||
- Bin Wu created early parameterized cells.
|
||||
- Yusu Wang is porting parameterized cells to new technologies.
|
||||
- Brian Chen created early prototypes of the timing characterizer.
|
||||
- Jeff Butera created early prototypes of the bank layout.
|
||||
- Many students: Hunter Nichols, Michael Grimes, Jennifer Sowash, Yusu Wang, Joey Kunzler, Jesse Cirimelli-Low, Samira Ataei, Bin Wu, Brian Chen, Jeff Butera
|
||||
|
||||
If I forgot to add you, please let me know!
|
||||
|
||||
|
|
@ -236,7 +228,7 @@ If I forgot to add you, please let me know!
|
|||
|
||||
[Github issues]: https://github.com/VLSIDA/OpenRAM/issues
|
||||
[Github pull request]: https://github.com/VLSIDA/OpenRAM/pulls
|
||||
[Github projects]: https://github.com/VLSIDA/OpenRAM/projects
|
||||
[Github project]: https://github.com/VLSIDA/OpenRAM
|
||||
|
||||
[documentation]: https://docs.google.com/presentation/d/10InGB33N51I6oBHnqpU7_w9DXlx-qe9zdrlco2Yc5co/edit?usp=sharing
|
||||
[dev-group]: mailto:openram-dev-group@ucsc.edu
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class layout():
|
|||
self.objs = [] # Holds all other objects (labels, geometries, etc)
|
||||
self.pin_map = {} # Holds name->pin_layout map for all pins
|
||||
self.visited = [] # List of modules we have already visited
|
||||
self.is_library_cell = False # Flag for library cells
|
||||
self.is_library_cell = False # Flag for library cells
|
||||
self.gds_read()
|
||||
try:
|
||||
from tech import power_grid
|
||||
|
|
@ -71,7 +71,7 @@ class layout():
|
|||
(inv_num + 1) * height - \
|
||||
(inv_num % 2) * drc["minwidth_m1"])
|
||||
y_dir = -1
|
||||
|
||||
|
||||
return (base_offset, y_dir)
|
||||
|
||||
def find_lowest_coords(self):
|
||||
|
|
@ -90,7 +90,7 @@ class layout():
|
|||
lowesty2 = min(inst.by() for inst in self.insts)
|
||||
else:
|
||||
lowestx2 = lowesty2 = None
|
||||
|
||||
|
||||
if lowestx1 == None and lowestx2 == None:
|
||||
return None
|
||||
elif lowestx1 == None:
|
||||
|
|
@ -146,7 +146,7 @@ class layout():
|
|||
subcoord = inst.mod.find_highest_layer_coords(layer) + inst.offset
|
||||
highestx = max(highestx, subcoord.x)
|
||||
highesty = max(highesty, subcoord.y)
|
||||
|
||||
|
||||
return vector(highestx, highesty)
|
||||
|
||||
def find_lowest_layer_coords(self, layer):
|
||||
|
|
@ -172,7 +172,7 @@ class layout():
|
|||
lowesty = min(lowesty, subcoord.y)
|
||||
|
||||
return vector(lowestx, lowesty)
|
||||
|
||||
|
||||
def translate_all(self, offset):
|
||||
"""
|
||||
Translates all objects, instances, and pins by the given (x,y) offset
|
||||
|
|
@ -189,7 +189,7 @@ class layout():
|
|||
pin_list = self.pin_map[pin_name]
|
||||
for pin in pin_list:
|
||||
pin.rect = [pin.ll() - offset, pin.ur() - offset]
|
||||
|
||||
|
||||
def add_inst(self, name, mod, offset=[0, 0], mirror="R0", rotate=0):
|
||||
""" Adds an instance of a mod to this module """
|
||||
self.insts.append(geometry.instance(name, mod, offset, mirror, rotate))
|
||||
|
|
@ -204,7 +204,7 @@ class layout():
|
|||
if inst.name == name:
|
||||
return inst
|
||||
return None
|
||||
|
||||
|
||||
def add_rect(self, layer, offset, width=None, height=None):
|
||||
"""
|
||||
Adds a rectangle on a given layer,offset with width and height
|
||||
|
|
@ -260,7 +260,7 @@ class layout():
|
|||
start-offset,
|
||||
minwidth_layer,
|
||||
end.y-start.y)
|
||||
|
||||
|
||||
def get_pin(self, text):
|
||||
"""
|
||||
Return the pin or list of pins
|
||||
|
|
@ -275,7 +275,7 @@ class layout():
|
|||
except Exception:
|
||||
self.gds_write("missing_pin.gds")
|
||||
debug.error("No pin found with name {0} on {1}. Saved as missing_pin.gds.".format(text,self.name),-1)
|
||||
|
||||
|
||||
def get_pins(self, text):
|
||||
"""
|
||||
Return a pin list (instead of a single pin)
|
||||
|
|
@ -290,17 +290,17 @@ class layout():
|
|||
Return a pin list of all pins
|
||||
"""
|
||||
return self.pin_map.keys()
|
||||
|
||||
|
||||
def copy_layout_pin(self, instance, pin_name, new_name=""):
|
||||
"""
|
||||
Create a copied version of the layout pin at the current level.
|
||||
You can optionally rename the pin to a new name.
|
||||
"""
|
||||
pins = instance.get_pins(pin_name)
|
||||
|
||||
|
||||
debug.check(len(pins) > 0,
|
||||
"Could not find pin {}".format(pin_name))
|
||||
|
||||
|
||||
for pin in pins:
|
||||
if new_name == "":
|
||||
new_name = pin.name
|
||||
|
|
@ -317,7 +317,7 @@ class layout():
|
|||
"""
|
||||
for pin_name in self.pin_map.keys():
|
||||
self.copy_layout_pin(instance, pin_name, prefix + pin_name)
|
||||
|
||||
|
||||
def add_layout_pin_segment_center(self, text, layer, start, end):
|
||||
"""
|
||||
Creates a path like pin with center-line convention
|
||||
|
|
@ -326,9 +326,9 @@ class layout():
|
|||
file_name = "non_rectilinear.gds"
|
||||
self.gds_write(file_name)
|
||||
debug.error("Cannot have a non-manhatten layout pin: {}".format(file_name), -1)
|
||||
|
||||
|
||||
minwidth_layer = drc["minwidth_{}".format(layer)]
|
||||
|
||||
|
||||
# one of these will be zero
|
||||
width = max(start.x, end.x) - min(start.x, end.x)
|
||||
height = max(start.y, end.y) - min(start.y, end.y)
|
||||
|
|
@ -342,7 +342,7 @@ class layout():
|
|||
# This makes sure it is long enough, but also it is not 0 width!
|
||||
height = max(minwidth_layer, height)
|
||||
width = max(minwidth_layer, width)
|
||||
|
||||
|
||||
return self.add_layout_pin(text,
|
||||
layer,
|
||||
ll_offset,
|
||||
|
|
@ -359,13 +359,13 @@ class layout():
|
|||
ll_offset = offset - vector(0.5 * width, 0.5 * height)
|
||||
|
||||
return self.add_layout_pin(text, layer, ll_offset, width, height)
|
||||
|
||||
|
||||
def remove_layout_pin(self, text):
|
||||
"""
|
||||
Delete a labeled pin (or all pins of the same name)
|
||||
"""
|
||||
self.pin_map[text] = set()
|
||||
|
||||
|
||||
def add_layout_pin(self, text, layer, offset, width=None, height=None):
|
||||
"""
|
||||
Create a labeled pin
|
||||
|
|
@ -409,7 +409,7 @@ class layout():
|
|||
layer=layer,
|
||||
offset=offset + vector(0.5 * width,
|
||||
0.5 * height))
|
||||
|
||||
|
||||
def add_label(self, text, layer, offset=[0, 0], zoom=-1):
|
||||
"""Adds a text label on the given layer,offset, and zoom level"""
|
||||
# negative layers indicate "unused" layers in a given technology
|
||||
|
|
@ -448,7 +448,7 @@ class layout():
|
|||
layer_stack=layers,
|
||||
path=coordinates,
|
||||
layer_widths=layer_widths)
|
||||
|
||||
|
||||
def add_wire(self, layers, coordinates):
|
||||
"""Connects a routing path on given layer,coordinates,width.
|
||||
The layers are the (horizontal, via, vertical). """
|
||||
|
|
@ -463,14 +463,14 @@ class layout():
|
|||
""" Return the preferred routing directions """
|
||||
from tech import preferred_directions
|
||||
return preferred_directions[layer]
|
||||
|
||||
|
||||
def add_via(self, layers, offset, size=[1,1], directions=None, implant_type=None, well_type=None):
|
||||
""" Add a three layer via structure. """
|
||||
|
||||
if not directions:
|
||||
directions = (self.get_preferred_direction(layers[0]),
|
||||
self.get_preferred_direction(layers[2]))
|
||||
|
||||
|
||||
from sram_factory import factory
|
||||
via = factory.create(module_type="contact",
|
||||
layer_stack=layers,
|
||||
|
|
@ -495,7 +495,7 @@ class layout():
|
|||
if not directions:
|
||||
directions = (self.get_preferred_direction(layers[0]),
|
||||
self.get_preferred_direction(layers[2]))
|
||||
|
||||
|
||||
from sram_factory import factory
|
||||
via = factory.create(module_type="contact",
|
||||
layer_stack=layers,
|
||||
|
|
@ -505,7 +505,7 @@ class layout():
|
|||
well_type=well_type)
|
||||
height = via.height
|
||||
width = via.width
|
||||
|
||||
|
||||
corrected_offset = offset + vector(-0.5 * width,
|
||||
-0.5 * height)
|
||||
|
||||
|
|
@ -602,11 +602,11 @@ class layout():
|
|||
# This must be done for netlist only mode too
|
||||
if os.path.isfile(self.gds_file):
|
||||
self.is_library_cell = True
|
||||
|
||||
|
||||
if OPTS.netlist_only:
|
||||
self.gds = None
|
||||
return
|
||||
|
||||
|
||||
# open the gds file if it exists or else create a blank layout
|
||||
if os.path.isfile(self.gds_file):
|
||||
debug.info(3, "opening {}".format(self.gds_file))
|
||||
|
|
@ -662,7 +662,7 @@ class layout():
|
|||
height=height,
|
||||
center=False)
|
||||
debug.info(2, "Adding {0} boundary {1}".format(self.name, boundary))
|
||||
|
||||
|
||||
self.visited.append(self.name)
|
||||
|
||||
def gds_write(self, gds_name):
|
||||
|
|
@ -681,10 +681,10 @@ class layout():
|
|||
# which may have been previously processed!
|
||||
# MRG: 10/4/18 We need to clear if we make changes and write a second GDS!
|
||||
self.clear_visited()
|
||||
|
||||
|
||||
# recursively create all the remaining objects
|
||||
self.gds_write_file(self.gds)
|
||||
|
||||
|
||||
# populates the xyTree data structure for gds
|
||||
# self.gds.prepareForWrite()
|
||||
writer.writeToFile(gds_name)
|
||||
|
|
@ -706,7 +706,7 @@ class layout():
|
|||
lpp = techlayer[layer]
|
||||
else:
|
||||
lpp = layer
|
||||
|
||||
|
||||
blockages = []
|
||||
for i in self.objs:
|
||||
blockages += i.get_blockages(lpp)
|
||||
|
|
@ -724,7 +724,7 @@ class layout():
|
|||
pin_names = copy.deepcopy(self.pins)
|
||||
if self.name.startswith("pmos") or self.name.startswith("nmos"):
|
||||
pin_names.remove("B")
|
||||
|
||||
|
||||
blockages = []
|
||||
for pin_name in pin_names:
|
||||
pin_list = self.get_pins(pin_name)
|
||||
|
|
@ -784,7 +784,7 @@ class layout():
|
|||
|
||||
# half minwidth so we can return the center line offsets
|
||||
half_minwidth = 0.5 * drc["minwidth_{}".format(layer)]
|
||||
|
||||
|
||||
line_positions = {}
|
||||
if vertical:
|
||||
for i in range(len(names)):
|
||||
|
|
@ -829,7 +829,7 @@ class layout():
|
|||
layer_stack=("m1", "via1", "m2")):
|
||||
""" Vertical version of connect_bus. """
|
||||
self.connect_bus(mapping, inst, bus_offsets, layer_stack, False)
|
||||
|
||||
|
||||
def connect_bus(self, mapping, inst, bus_offsets, layer_stack, horizontal):
|
||||
"""
|
||||
Connect a mapping of pin -> name for a bus. This could be
|
||||
|
|
@ -842,7 +842,7 @@ class layout():
|
|||
route_layer = vertical_layer
|
||||
else:
|
||||
route_layer = horizontal_layer
|
||||
|
||||
|
||||
for (pin_name, bus_name) in mapping:
|
||||
pin = inst.get_pin(pin_name)
|
||||
pin_pos = pin.center()
|
||||
|
|
@ -854,7 +854,7 @@ class layout():
|
|||
else:
|
||||
# left/right then up/down
|
||||
mid_pos = vector(bus_pos.x, pin_pos.y)
|
||||
|
||||
|
||||
self.add_wire(layer_stack,
|
||||
[bus_pos, mid_pos, pin_pos])
|
||||
|
||||
|
|
@ -865,7 +865,7 @@ class layout():
|
|||
offset=pin_pos)
|
||||
# FIXME: output pins tend to not be rotate,
|
||||
# but supply pins are. Make consistent?
|
||||
|
||||
|
||||
# We only need a via if they happened to align perfectly
|
||||
# so the add_wire didn't add a via
|
||||
if (horizontal and bus_pos.y == pin_pos.y) or (not horizontal and bus_pos.x == pin_pos.x):
|
||||
|
|
@ -899,7 +899,7 @@ class layout():
|
|||
self.m3_space)
|
||||
else:
|
||||
debug.error("Cannot find layer pitch.")
|
||||
|
||||
|
||||
def add_horizontal_trunk_route(self,
|
||||
pins,
|
||||
trunk_offset,
|
||||
|
|
@ -915,7 +915,7 @@ class layout():
|
|||
# if we are less than a pitch, just create a non-preferred layer jog
|
||||
if max_x-min_x <= pitch:
|
||||
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)]
|
||||
|
||||
|
||||
# Add the horizontal trunk on the vertical layer!
|
||||
self.add_path(self.vertical_layer,
|
||||
[vector(min_x - half_layer_width, trunk_offset.y),
|
||||
|
|
@ -955,7 +955,7 @@ class layout():
|
|||
if max_y-min_y <= pitch:
|
||||
|
||||
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)]
|
||||
|
||||
|
||||
# Add the vertical trunk on the horizontal layer!
|
||||
self.add_path(self.horizontal_layer,
|
||||
[vector(trunk_offset.x, min_y - half_layer_width),
|
||||
|
|
@ -978,7 +978,7 @@ class layout():
|
|||
self.add_path(self.horizontal_layer, [pin.center(), mid])
|
||||
self.add_via_center(layers=layer_stack,
|
||||
offset=mid)
|
||||
|
||||
|
||||
def create_channel_route(self, netlist,
|
||||
offset,
|
||||
layer_stack,
|
||||
|
|
@ -996,7 +996,7 @@ class layout():
|
|||
Remove the pin from the graph and all conflicts
|
||||
"""
|
||||
g.pop(pin, None)
|
||||
|
||||
|
||||
# Remove the pin from all conflicts
|
||||
# FIXME: This is O(n^2), so maybe optimize it.
|
||||
for other_pin,conflicts in g.items():
|
||||
|
|
@ -1010,21 +1010,21 @@ class layout():
|
|||
Check all the pin pairs on two nets and return a pin
|
||||
overlap if any pin overlaps.
|
||||
"""
|
||||
|
||||
|
||||
for pin1 in net1:
|
||||
for pin2 in net2:
|
||||
if vcg_pin_overlap(pin1, pin2, vertical, pitch):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def vcg_pin_overlap(pin1, pin2, vertical, pitch):
|
||||
""" Check for vertical or horizontal overlap of the two pins """
|
||||
# FIXME: If the pins are not in a row, this may break.
|
||||
# However, a top pin shouldn't overlap another top pin,
|
||||
# for example, so the
|
||||
# extra comparison *shouldn't* matter.
|
||||
|
||||
|
||||
# Pin 1 must be in the "BOTTOM" set
|
||||
x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x-pin2.center().x)<pitch
|
||||
|
||||
|
|
@ -1049,7 +1049,7 @@ class layout():
|
|||
# too if we want to minimize the
|
||||
# number of tracks!
|
||||
# hcg = {}
|
||||
|
||||
|
||||
# Initialize the vertical conflict graph (vcg)
|
||||
# and make a list of all pins
|
||||
vcg = collections.OrderedDict()
|
||||
|
|
@ -1084,7 +1084,7 @@ class layout():
|
|||
vertical,
|
||||
self.horizontal_pitch):
|
||||
vcg[net_name2].append(net_name1)
|
||||
|
||||
|
||||
# list of routes to do
|
||||
while vcg:
|
||||
# from pprint import pformat
|
||||
|
|
@ -1105,7 +1105,7 @@ class layout():
|
|||
|
||||
# Remove the net from other constriants in the VCG
|
||||
vcg = remove_net_from_graph(net_name, vcg)
|
||||
|
||||
|
||||
# Add the trunk routes from the bottom up for
|
||||
# horizontal or the left to right for vertical
|
||||
if vertical:
|
||||
|
|
@ -1137,7 +1137,7 @@ class layout():
|
|||
""" Add boundary for debugging dimensions """
|
||||
if OPTS.netlist_only:
|
||||
return
|
||||
|
||||
|
||||
if "stdc" in techlayer.keys():
|
||||
boundary_layer = "stdc"
|
||||
else:
|
||||
|
|
@ -1152,7 +1152,7 @@ class layout():
|
|||
offset=ll,
|
||||
height=ur.y-ll.y,
|
||||
width=ur.x-ll.x)
|
||||
|
||||
|
||||
def add_enclosure(self, insts, layer="nwell"):
|
||||
""" Add a layer that surrounds the given instances. Useful
|
||||
for creating wells, for example. Doesn't check for minimum widths or
|
||||
|
|
@ -1193,19 +1193,25 @@ class layout():
|
|||
"supply router."
|
||||
.format(name,inst.name,self.pwr_grid_layer))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def add_power_pin(self, name, loc, size=[1, 1], vertical=False, start_layer="m1"):
|
||||
"""
|
||||
Add a single power pin from the lowest power_grid layer down to M1 at
|
||||
the given center location. The starting layer is specified to determine
|
||||
which vias are needed.
|
||||
"""
|
||||
|
||||
# Force vdd/gnd via stack to be vertically or horizontally oriented
|
||||
# Default: None, uses prefered metal directions
|
||||
if vertical:
|
||||
direction = ("V", "V")
|
||||
else:
|
||||
elif not vertical and vertical is not None:
|
||||
direction = ("H", "H")
|
||||
|
||||
else:
|
||||
direction = None
|
||||
|
||||
|
||||
via = self.add_via_stack_center(from_layer=start_layer,
|
||||
to_layer=self.pwr_grid_layer,
|
||||
size=size,
|
||||
|
|
@ -1222,7 +1228,7 @@ class layout():
|
|||
offset=loc,
|
||||
width=via.width,
|
||||
height=via.height)
|
||||
|
||||
|
||||
def add_power_ring(self, bbox):
|
||||
"""
|
||||
Create vdd and gnd power rings around an area of the bounding box
|
||||
|
|
@ -1287,7 +1293,7 @@ class layout():
|
|||
offset=offset,
|
||||
width=width,
|
||||
height=self.supply_rail_width)
|
||||
|
||||
|
||||
# TOP horizontal rails
|
||||
offset = vector(ll.x, ur.y) + vector(-2 * self.supply_rail_pitch,
|
||||
0)
|
||||
|
|
@ -1328,7 +1334,7 @@ class layout():
|
|||
else:
|
||||
self.supply_vias -= 1
|
||||
break
|
||||
|
||||
|
||||
via_points = [vector(self.left_gnd_x_center, self.bottom_gnd_y_center),
|
||||
vector(self.left_gnd_x_center, self.top_gnd_y_center),
|
||||
vector(self.right_gnd_x_center, self.bottom_gnd_y_center),
|
||||
|
|
@ -1337,7 +1343,7 @@ class layout():
|
|||
vector(self.left_vdd_x_center, self.top_vdd_y_center),
|
||||
vector(self.right_vdd_x_center, self.bottom_vdd_y_center),
|
||||
vector(self.right_vdd_x_center, self.top_vdd_y_center)]
|
||||
|
||||
|
||||
for pt in via_points:
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=pt,
|
||||
|
|
@ -1390,4 +1396,3 @@ class layout():
|
|||
debug.info(0, "name={0} : mod={1} : offset={2}".format(inst.name,
|
||||
inst.mod.name,
|
||||
inst.offset))
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import re
|
|||
import copy
|
||||
import importlib
|
||||
|
||||
VERSION = "1.1.3"
|
||||
VERSION = "1.1.4"
|
||||
NAME = "OpenRAM v{}".format(VERSION)
|
||||
USAGE = "openram.py [options] <config file>\nUse -h for help.\n"
|
||||
|
||||
|
|
|
|||
|
|
@ -101,13 +101,20 @@ class bitcell_base_array(design.design):
|
|||
width=self.width,
|
||||
height=wl_pin.height())
|
||||
|
||||
# For every second row and column, add a via for gnd and vdd
|
||||
# For non-square via stacks, vertical/horizontal direction refers to the stack orientation in 2d space
|
||||
# Default uses prefered directions for each layer; this cell property is only currently used by s8 tech (03/20)
|
||||
try:
|
||||
force_power_pins_vertical = cell_properties.bitcell_force_power_pins_vertical
|
||||
except AttributeError:
|
||||
force_power_pins_vertical = None
|
||||
|
||||
# Add vdd/gnd via stacks
|
||||
for row in range(self.row_size):
|
||||
for col in range(self.column_size):
|
||||
inst = self.cell_inst[row,col]
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
for pin in inst.get_pins(pin_name):
|
||||
self.add_power_pin(name=pin_name, loc=pin.center(), vertical=True, start_layer=pin.layer)
|
||||
self.add_power_pin(name=pin_name, loc=pin.center(), vertical=force_power_pins_vertical, start_layer=pin.layer)
|
||||
|
||||
def _adjust_x_offset(self, xoffset, col, col_offset):
|
||||
tempx = xoffset
|
||||
|
|
|
|||
Loading…
Reference in New Issue