diff --git a/src/drc/drc/built-in-macros/_drc_engine.rb b/src/drc/drc/built-in-macros/_drc_engine.rb new file mode 100644 index 000000000..0029eb3dd --- /dev/null +++ b/src/drc/drc/built-in-macros/_drc_engine.rb @@ -0,0 +1,1414 @@ +# $autorun-early + +module DRC + + # The DRC engine + + # %DRC% + # @scope + # @name global + # @brief DRC Reference: Global Functions + # Some functions are available on global level and can be used without any object. + # Most of them are convenience functions that basically act on some default object + # or provide function-like alternatives for the methods. + + class DRCEngine + + def initialize + + cv = RBA::CellView::active + + @def_layout = cv && cv.layout + @def_cell = cv && cv.cell + @def_source = nil + @dbu_read = false + use_dbu(@def_layout && @def_layout.dbu) + @output_layout = nil + @output_rdb = nil + @output_rdb_file = nil + @output_rdb_cell = nil + @used_output_layers = {} + @output_layers = [] + @vnum = 1 + @layout_sources = {} + @lnum = 1 + @log_file = nil + @dss = nil + @deep = false + @netter = nil + + @verbose = false + + end + + def joined + DRCJoinFlag::new(true) + end + + def diamond_limit + DRCSizingMode::new(0) + end + + def octagon_limit + DRCSizingMode::new(1) + end + + def square_limit + DRCSizingMode::new(2) + end + + def acute_limit + DRCSizingMode::new(3) + end + + def no_limit + DRCSizingMode::new(4) + end + + def projection_limits(*args) + DRCProjectionLimits::new(*args) + end + + def angle_limit(a) + DRCAngleLimit::new(a) + end + + def whole_edges(f = true) + DRCWholeEdges::new(f) + end + + def euclidian + DRCMetrics::new(RBA::Region::Euclidian) + end + + def square + DRCMetrics::new(RBA::Region::Square) + end + + def projection + DRCMetrics::new(RBA::Region::Projection) + end + + def pattern(p) + DRCPattern::new(true, p) + end + + def text(p) + DRCPattern::new(false, p) + end + + def as_dots + DRCAsDots::new(true) + end + + def as_edges + DRCAsDots::new(true) + end + + def as_boxes + DRCAsDots::new(false) + end + + # %DRC% + # @name verbose? + # @brief Returns true, if verbose mode is enabled + # @synopsis verbose? + # In verbose mode, more output is generated in the log file + + def verbose? + @verbose + end + + # %DRC% + # @name verbose + # @brief Sets or resets verbose mode + # @synopsis verbose + # @synopsis verbose(m) + # In verbose mode, more output is generated in the log file + + def verbose(f = true) + self.verbose = f + end + + # %DRC% + # @name silent + # @brief Resets verbose mode + # @synopsis silent + # This function is equivalent to "verbose(false)" (see \verbose) + + def silent(f = true) + self.verbose = !f + end + + def verbose=(f) + @verbose = f + end + + # %DRC% + # @name info + # @brief Outputs as message to the logger window + # @synopsis info(message) + # Prints the message to the log window in verbose mode. + # In non-verbose more, nothing is printed. + # \log is a function that always prints a message. + + def info(arg) + @verbose && log(arg) + end + + # %DRC% + # @name log + # @brief Outputs as message to the logger window + # @synopsis log(message) + # Prints the message to the log window. + # \info is a function that prints a message only if + # verbose mode is enabled. + + def log(arg) + if @log_file + @log_file.puts(arg) + else + RBA::Logger::info(arg) + end + end + + # %DRC% + # @name error + # @brief Prints an error + # @synopsis error(message) + # Similar to \log, but the message is printed formatted as an error + + def error(arg) + if @log_file + @log_file.puts("ERROR: " + arg) + else + RBA::Logger::error(arg) + end + end + + # %DRC% + # @name log_file + # @brief Specify the log file where to send to log to + # @synopsis log_file(filename) + # After using that method, the log output is sent to the + # given file instead of the logger window or the terminal. + + def log_file(arg) + @log_file && @log_file.close + @log_file = File.open(arg, "w") + end + + # Specify the database unit to use + + def use_dbu(d) + if @dbu_read + raise "Cannot change the database unit at this point" + end + # Should have a "context", but no such thing for Float or Fixnum + 1.0.class._dbu = d + 1.class._dbu = d + @dbu = d + end + + # %DRC% + # @name dbu + # @brief Gets or sets the database unit to use + # @synopsis dbu(dbu) + # @synopsis dbu + # Without any argument, this method gets the database unit + # used inside the DRC engine. + # + # With an argument, sets the database unit used internally in the DRC engine. + # Without using that method, the database unit is automatically + # taken as the database unit of the last input. + # A specific database unit can be set in order to optimize + # for two layouts (i.e. take the largest common denominator). + # When the database unit is set, it must be set at the beginning + # of the script and before any operation that uses it. + + def dbu(d = nil) + if !d + @dbu || raise("No database unit specified") + else + use_dbu(d.to_f) + end + @dbu_read = true + @dbu + end + + # %DRC% + # @name tiles + # @brief Specifies tiling + # @synopsis tiles(t) + # @synopsis tiles(w, h) + # Specifies tiling mode. In tiling mode, the DRC operations are evaluated in tiles + # with width w and height h. With one argument, square tiles with width and height + # t are used. + # + # Special care must be taken when using tiling mode, since some operations may not + # behave as expected at the borders of the tile. Tiles can be made overlapping by + # specifying a tile border dimension with \tile_borders. Some operations like sizing, + # the DRC functions specify a tile border implicitly. Other operations without a + # defined range won't do so and the consequences of tiling mode can be difficult to + # predict. + # + # In tiling mode, the memory requirements are usually smaller (depending on the + # choice of the tile size) and multi-CPU support is enabled (see \threads). + # To disable tiling mode use \flat or \deep. + # + # Tiling mode will disable deep mode (see \deep). + + def tiles(tx, ty = nil) + @tx = tx.to_f + @ty = (ty || tx).to_f + @deep = false + end + + # %DRC% + # @name is_tiled? + # @brief Returns true, if in tiled mode + # @synopsis is_tiled? + + def is_tiled? + @tx != nil + end + + # %DRC% + # @name deep + # @brief Enters deep (hierarchical) mode + # @synopsis deep + # + # In deep mode, the operations will be performed in a hierarchical fashion. + # Sometimes this reduces the time and memory required for an operation, but this + # will also add some overhead for the hierarchical analysis. + # + # "deepness" is a property of layers. Layers created with "input" while in + # deep mode carry hierarchy. Operations involving such layers at the only + # or the first argument are carried out in hierarchical mode. + # + # Hierarchical mode has some more implications, like "merged_semantics" being + # implied always. Sometimes cell variants will be created. + # + # Deep mode can be cancelled with \tiles or \flat. + + def deep + @deep = true + @tx = @ty = nil + end + + # %DRC% + # @name is_deep? + # @brief Returns true, if in deep mode + # @synopsis is_deep? + + def is_deep? + @deep + end + + # %DRC% + # @name tile_borders + # @brief Specifies a minimum tile border + # @synopsis tile_border(b) + # @synopsis tile_border(bx, by) + # The tile border specifies the distance to which shapes are collected into the + # tile. In order words, when processing a tile, shapes within the border distance + # participate in the operations. + # + # For some operations such as booleans (\and, \or, ...), \size and the DRC functions (\width, \space, ...) + # a tile border is automatically established. For other operations such as \with_area + # or \edges, the exact distance is unknown, because such operations may have a long range. + # In that cases, no border is used. The tile_borders function may be used to specify a minimum border + # which is used in that case. That allows taking into account at least shapes within the + # given range, although not necessarily all. + # + # To reset the tile borders, use \no_borders or "tile_borders(nil)". + + def tile_borders(bx, by = nil) + @bx = bx.to_f + @by = (by || bx).to_f + end + + # %DRC% + # @name no_borders + # @brief Reset the tile borders + # @synopsis no_borders + # Resets the tile borders - see \tile_borders for a description of tile borders. + + def no_borders + @bx = @by = nil + end + + # %DRC% + # @name flat + # @brief Disables tiling mode + # @synopsis flat + # Disables tiling mode. Tiling mode can be enabled again with \tiles later. + + def flat + @tx = @ty = nil + @deep = false + end + + # %DRC% + # @name threads + # @brief Specifies the number of CPU cores to use in tiling mode + # @synopsis threads(n) + # If using threads, tiles are distributed on multiple CPU cores for + # parallelization. Still, all tiles must be processed before the + # operation proceeds with the next statement. + + def threads(n) + @tt = n.to_i + end + + # %DRC% + # @name make_layer + # @brief Creates an empty polygon layer based on the hierarchical scheme selected + # @synopsis make_layer + # The intention of this method is to provide an empty polygon layer based on the + # hierarchical scheme selected. This will create a new layer with the hierarchy + # of the current layout in deep mode and a flat layer in flat mode. + # This method is similar to \polygon_layer, but the latter does not create + # a hierarchical layer. Hence the layer created by \make_layer is suitable + # for use in device extraction for example, while the one + # delivered by \polygon_layer is not. + # + # On the other hand, a layer created by the \make_layer method is not intended to be + # filled with \Layer#insert. + + def make_layer + layout.make_layer + end + + # %DRC% + # @name polygon_layer + # @brief Creates an empty polygon layer + # @synopsis polygon_layer + # The intention of that method is to create an empty layer which can be + # filled with polygon-like objects using \Layer#insert. + # A similar method which creates a hierarchical layer in deep mode is + # \make_layer. This other layer is better suited for use with device extraction. + + def polygon_layer + DRCLayer::new(self, RBA::Region::new) + end + + # %DRC% + # @name edge_layer + # @brief Creates an empty edge layer + # @synopsis edge_layer + # The intention of that method is to create an empty layer which can be + # filled with edge objects using \Layer#insert. + + def edge_layer + DRCLayer::new(self, RBA::Edges::new) + end + + # %DRC% + # @name source + # @brief Specifies a source layout + # @synopsis source + # @synopsis source(what) + # This function replaces the default source layout by the specified + # file. If this function is not used, the currently active layout + # is used as input. + # + # \layout is a similar method which specifies @i a additional @/i input layout. + # + # "what" specifies what input to use. "what" be either + # + # @ul + # @li A string "\@n" specifying input from a layout in the current panel @/li + # @li A layout filename plus an optional cell name@/li + # @li A RBA::Layout object plus an optional cell name@/li + # @li A RBA::Cell object @/li + # @/ul + # + # Without any arguments the default layout is returned. If a filename is given, a cell name + # can be specified as the second argument. If none is specified, the top cell is taken which + # must be unique in that case. + # + # @code + # # XOR between layers 1 of "first_layout.gds" and "second_layout.gds" and sends the results to "xor_layout.gds": + # target("xor_layout.gds") + # source("first_layout.gds") + # l2 = layout("second_layout.gds") + # (input(1, 0) ^ l2.input(1, 0)).output(100, 0) + # @/code + # + # For further methods on the source object see \Source. + + def source(arg = nil, arg2 = nil) + + if arg + + if arg.is_a?(String) + + if arg =~ /^@(\d+)/ + n = $1.to_i - 1 + view = RBA::LayoutView::current + view || raise("No view open") + (n >= 0 && view.cellviews > n) || raise("Invalid layout index @#{n + 1}") + cv = view.cellview(n) + cv.is_valid? || raise("Invalid layout @#{n + 1}") + @def_source = make_source(cv.layout, cv.cell) + else + layout = RBA::Layout::new + info("Reading #{arg} ..") + layout.read(arg) + cell = nil + if arg2 + arg2.is_a?(String) || raise("Second argument of 'source' must be a string") + cell = layout.cell(arg2) + cell || raise("Cell name #{arg2} not found in input layout") + end + @def_source = make_source(layout, cell) + end + + elsif arg.is_a?(RBA::Layout) + + cell = arg2 + if cell.is_a?(String) + cell = arg.cell(cell) + cell || raise("Cell name #{cell} not found in input layout") + elsif !cell.is_a?(RBA::Cell) + raise("Second argument of 'source' must be a string or RBA::Cell object") + end + @def_source = make_source(arg, cell) + + elsif arg.is_a?(RBA::Cell) + @def_source = make_source(arg.layout, arg) + else + raise("Invalid argument for 'source' method") + end + + else + @def_source || @def_layout || raise("No layout loaded - no default layout. Use 'layout' or 'source' to explicitly specify a layout.") + @def_source ||= make_source(@def_layout, @def_cell) + end + + # make default input also default output if none is set yet. + @def_layout ||= @def_source.layout + @def_cell ||= @def_source.cell_obj + + # use the DBU of the new input as DBU reference + @dbu_read || use_dbu(@def_source.layout.dbu) + + @def_source + + end + + # %DRC% + # @name layout + # @brief Specifies an additional layout for the input source. + # @synopsis layout + # @synopsis layout(what) + # This function can be used to specify a new layout for input. + # It returns an Source object representing that layout. The "input" method + # of that object can be used to get input layers for that layout. + # + # "what" specifies what input to use. "what" be either + # + # @ul + # @li A string "\@n" specifying input from a cellview in the current view @/li + # @li A layout filename plus an optional cell name @/li + # @li A RBA::Layout object @/li + # @li A RBA::Cell object @/li + # @/ul + # + # Without any arguments the default layout is returned. + # + # If a file name is given, a cell name can be specified as the second argument. + # If not, the top cell is taken which must be unique in that case. + # + # Having specified a layout for input enables to use the input method + # for getting input: + # + # @code + # # XOR between layers 1 or the default input and "second_layout.gds": + # l2 = layout("second_layout.gds") + # (input(1, 0) ^ l2.input(1, 0)).output(100, 0) + # @/code + # + # For further methods on the source object see \Source. + + def layout(arg = nil, arg2 = nil) + + if arg + + if arg.is_a?(String) + + if arg =~ /^@(\d+)/ + n = $1.to_i - 1 + view = RBA::LayoutView::current + view || raise("No view open") + (n >= 0 && view.cellviews > n) || raise("Invalid layout index @#{n + 1}") + cv = view.cellview(n) + cv.is_valid? || raise("Invalid layout @#{n + 1}") + return make_source(cv.layout, cv.cell) + else + layout = RBA::Layout::new + info("Reading #{arg} ..") + layout.read(arg) + cell = nil + if arg2 + arg2.is_a?(String) || raise("Second argument of 'source' must be a string") + cell = layout.cell(arg2) + cell || raise("Cell name #{arg2} not found in input layout") + end + return make_source(layout, cell) + end + + elsif arg.is_a?(RBA::Layout) + return make_source(layout) + elsif arg.is_a?(RBA::Cell) + return make_source(arg.layout, arg) + else + raise("Invalid argument for 'layout' method") + end + + else + @def_source || @def_layout || raise("No layout loaded - no default layout. Use 'layout' or 'source' to explicitly specify a layout.") + @def_source ||= make_source(@def_layout, @def_cell) + @def_source + end + + end + + # %DRC% + # @name report + # @brief Specifies a report database for output + # @synopsis report(description [, filename [, cellname ] ]) + # After specifying a report database for output, \output method calls are redirected to + # the report database. The format of the \output calls changes and a category name plus + # description can be specified rather than a layer/datatype number of layer name. + # See the description of the output method for details. + # + # If a filename is given, the report database will be written to the specified file name. + # Otherwise it will be shown but not written. + # + # If external input is specified with \source, + # "report" must be called after "source". + # + # The cellname specifies the top cell used for the report file. + # By default this is the cell name of the default source. If there + # is no source layout you'll need to give the cell name in the + # third parameter. + + def report(description, filename = nil, cellname = nil) + + @output_rdb_file = filename + + name = filename && File::basename(filename) + name ||= "DRC" + + lv = RBA::LayoutView::current + if lv + @output_rdb_index = lv.create_rdb(name) + @output_rdb = lv.rdb(@output_rdb_index) + else + @output_rdb = RBA::ReportDatabase::new(name) + end + + @output_layout = nil + @output_cell = nil + @output_layout_file = nil + + cn = nil + cn ||= @def_cell && @def_cell.name + cn ||= source && source.cell_name + cn ||= cellname + + cn || raise("No cell name specified - either the source was not specified before 'report' or there is no default source. In the latter case, specify a cell name as the third parameter of 'report'") + + @output_rdb_cell = @output_rdb.create_cell(cn) + @output_rdb.generator = $0 + @output_rdb.top_cell_name = cn + @output_rdb.description = description + + end + + # %DRC% + # @name output_cell + # @brief Specifies a target cell, but does not change the target layout + # @synopsis output_cell(cellname) + # This method switches output to the specified cell, but does not + # change the target layout nor does it switch the output channel to + # layout if is report database. + + def output_cell(cellname) + + # finish what we got so far + _flush + + if @output_rdb + + cell = nil + @output_rdb.each_cell do |c| + if c.name == cellname + cell = c + end + end + + cell ||= @output_rdb.create_cell(cellname) + @output_rdb_cell = cell + + else + + @output_layout ||= @def_layout + if @output_layout + @output_cell = @output_layout.cell(cellname.to_s) || @output_layout.create_cell(cellname.to_s) + end + + end + + end + + # %DRC% + # @name target + # @brief Specify the target layout + # @synopsis target(what [, cellname]) + # This function can be used to specify a target layout for output. + # Subsequent calls of "output" will send their results to that target + # layout. Using "target" will disable output to a report database. + # If any target was specified before, that target will be closed and + # a new target will be set up. + # + # "what" specifies what input to use. "what" be either + # + # @ul + # @li A string "\@n" specifying output to a layout in the current panel @/li + # @li A layout filename @/li + # @li A RBA::Layout object @/li + # @li A RBA::Cell object @/li + # @/ul + # + # Except if the argument is a RBA::Cell object, a cellname can be specified + # stating the cell name under which the results are saved. If no cellname is + # specified, either the current cell or "TOP" is used. + # + + def target(arg, cellname = nil) + + # finish what we got so far + _finish(false) + + if arg.is_a?(String) + + if arg =~ /^@(\d+)/ + n = $1.to_i - 1 + view = RBA::LayoutView::current + view || raise("No view open") + (n >= 0 && view.cellviews > n) || raise("Invalid layout index @#{n + 1}") + cv = view.cellview(n) + cv.is_valid? || raise("Invalid layout @#{n + 1}") + @output_layout = cv.layout + @output_cell = cellname ? (@output_layout.cell(cellname.to_s) || @output_layout.create_cell(cellname.to_s)) : cv.cell + @output_layout_file = nil + else + @output_layout = RBA::Layout::new + @output_cell = cellname && @output_layout.create_cell(cellname.to_s) + @output_layout_file = arg + end + + elsif arg.is_a?(RBA::Layout) + + @output_layout = arg + @output_cell = cellname && (@output_layout.cell(cellname.to_s) || @output_layout.create_cell(cellname.to_s)) + @output_layout_file = nil + + elsif arg.is_a?(RBA::Cell) + + @output_layout = arg.layout + @output_cell = arg + @output_layout_file = nil + + else + raise("Invalid argument for 'target' method") + end + + end + + # %DRC% + # @name box + # @brief Creates a box object + # @synopsis box(...) + # This function creates a box object. The arguments are the same than for the + # RBA::DBox constructors. + + def box(*args) + RBA::DBox::new(*args) + end + + # %DRC% + # @name path + # @brief Creates a path object + # @synopsis path(...) + # This function creates a path object. The arguments are the same than for the + # RBA::DPath constructors. + + def path(*args) + RBA::DPath::new(*args) + end + + # %DRC% + # @name polygon + # @brief Creates a polygon object + # @synopsis polygon(...) + # This function creates a polygon object. The arguments are the same than for the + # RBA::DPolygon constructors. + + def polygon(*args) + RBA::DPolygon::new(*args) + end + + # %DRC% + # @name p + # @brief Creates a point object + # @synopsis p(x, y) + # A point is not a valid object by itself, but it is useful for creating + # paths for polygons: + # + # @code + # x = polygon_layer + # x.insert(polygon([ p(0, 0), p(16.0, 0), p(8.0, 8.0) ])) + # @/code + + def p(x, y) + RBA::DPoint::new(x, y) + end + + # %DRC% + # @name edge + # @brief Creates an edge object + # @synopsis edge(...) + # This function creates an edge object. The arguments are the same than for the + # RBA::DEdge constructors. + + def edge(*args) + RBA::DEdge::new(*args) + end + + # %DRC% + # @name extent + # @brief Creates a new layer with the bounding box of the default source + # @synopsis extent + # See \Source#extent for a description of that function. + + def extent + layout.extent + end + + # %DRC% + # @name input + # @brief Fetches the shapes from the specified input from the default source + # @synopsis input(args) + # See \Source#input for a description of that function. This method will fetch + # polygons and labels. See \polygons and \labels for more specific versions of + # this method. + + def input(*args) + layout.input(*args) + end + + # %DRC% + # @name polygons + # @brief Fetches the polygons (or shapes that can be converted to polygons) from the specified input from the default source + # @synopsis polygons(args) + # See \Source#polygons for a description of that function. + + def polygons(*args) + layout.polygons(*args) + end + + # %DRC% + # @name labels + # @brief Gets the labels (text) from an original layer + # @synopsis labels + # See \Source#labels for a description of that function. + + def labels(*args) + layout.labels(*args) + end + + # %DRC% + # @name output + # @brief Outputs a layer to the report database or output layout + # @synopsis output(layer, args) + # This function is equivalent to "layer.output(args)". See \Layer#output for details about this function. + + def output(layer, *args) + layer.output(*args) + end + + # %DRC% + # @name layers + # @brief Gets the layers contained in the default source + # @synopsis layers + # See \Source#layers for a description of that function. + + def layers + layout.layers + end + + # %DRC% + # @name cell + # @brief Selects a cell for input on the default source + # @synopsis cell(args) + # See \Source#cell for a description of that function. + # In addition to the functionality described there, the global function will also send the output + # to the specified cell. + # + # The following code will select cell "MACRO" from the input layout: + # + # @code + # cell("MACRO") + # # shapes now will be taken from cell "MACRO" + # l1 = input(1, 0) + # @/code + + def cell(*args) + @def_source = layout.cell(*args) + output_cell(*args) + nil + end + + # %DRC% + # @name select + # @brief Specifies cell filters on the default source + # @synopsis select(args) + # See \Source#select for a description of that function. + + def select(*args) + @def_source = layout.select(*args) + nil + end + + # %DRC% + # @name clip + # @brief Specifies clipped input on the default source + # @synopsis clip(args) + # See \Source#clip for a description of that function. + # + # The following code will select shapes within a 500x600 micron rectangle (lower left corner at 0,0) + # from the input layout. The shapes will be clipped to that rectangle: + # + # @code + # clip(0.mm, 0.mm, 0.5.mm, 0.6.mm) + # # shapes now will be taken from the given rectangle and clipped to it + # l1 = input(1, 0) + # @/code + + def clip(*args) + @def_source = layout.clip(*args) + nil + end + + # make some DRCLayer methods available as functions + # for the engine + %w(join and or xor not + in touching overlapping inside outside interacting + select_touching select_overlapping select_inside select_outside select_interacting + merge merged rectangles rectilinear non_rectangles non_rectilinear + with_area with_perimeter with_angle with_length with_bbox_width with_bbox_area with_bbox_height with_bbox_min with_bbox_max + without_area without_perimeter without_length without_angle without_bbox_width without_bbox_area without_bbox_height without_bbox_min without_bbox_max + bbox + area length perimeter + is_box? is_empty? is_merged? is_clean? is_raw? polygons? edges? edge_pairs? + strict non_strict is_strict? + centers end_segments start_segments + extended extended_in extended_out + extents hulls holes + scaled scale rotated rotate + move moved transform transformed + width space notch isolated overlap + size sized + rounded_corners odd_polygons).each do |f| + eval <<"CODE" + def #{f}(*args) + obj = args.shift + obj.#{f}(*args) + end +CODE + end + + # %DRC% + # @name netter + # @brief Creates a new netter object + # @synopsis netter + # See \Netter for more details + + def netter + DRC::DRCNetter::new + end + + # %DRC% + # @name connect + # @brief Specifies a connection between two layers + # @synopsis connect(a, b) + # See \Netter#connect for a description of that function. + + # %DRC% + # @name connect_global + # @brief Specifies a connection to a global net + # @synopsis connect_global(l, name) + # See \Netter#connect_global for a description of that function. + + # %DRC% + # @name clear_connections + # @brief Clears all connections stored so far + # @synopsis clear_connections + # See \Netter#clear_connections for a description of that function + + # %DRC% + # @name join_nets + # @brief Specifies a label pattern for implicit net connections + # @synopsis join_nets(label_pattern) + # See \Netter#join_nets for a description of that function + + # %DRC% + # @name antenna_check + # @brief Performs an antenna check + # @synopsis antenna_check(gate, metal, ratio, [ diode_specs ... ]) + # See \Netter#antenna_check for a description of that function + + # %DRC% + # @name l2n_data + # @brief Gets the internal RBA::LayoutToNetlist object for the default \Netter + # @synopsis l2n_data + # See \Netter#l2n_data for a description of that function + + # %DRC% + # @name extract_devices + # @brief Extracts devices for a given device extractor and device layer selection + # @synopsis extract_devices(extractor, layer_hash) + # See \Netter#extract_devices for a description of that function + + %w(connect connect_global clear_connections join_nets antenna_check l2n_data extract_devices).each do |f| + eval <<"CODE" + def #{f}(*args) + _netter.#{f}(*args) + end +CODE + end + + def src_line + cc = caller.find do |c| + c !~ /drc.lym:/ && c !~ /\(eval\)/ + end + if cc =~ /(.*)\s*:\s*(\d+)\s*:\s*in.*$/ + return File::basename($1) + ":" + $2 + else + return cc + end + end + + def run_timed(desc, obj) + + info(desc) + + # enable progress + if obj.is_a?(RBA::Region) || obj.is_a?(RBA::Edges) || obj.is_a?(RBA::EdgePairs) + obj.enable_progress(desc) + end + + t = RBA::Timer::new + t.start + GC.start # force a garbage collection before the operation to free unused memory + res = yield + t.stop + + info("Elapsed: #{'%.3f'%(t.sys+t.user)}s") + + # disable progress + if obj.is_a?(RBA::Region) || obj.is_a?(RBA::Edges) || obj.is_a?(RBA::EdgePairs) + obj.disable_progress + end + + res + + end + + def _cmd(obj, method, *args) + run_timed("\"#{method}\" in: #{src_line}", obj) do + obj.send(method, *args) + end + end + + def _tcmd(obj, border, result_cls, method, *args) + + if @tx && @ty + + tp = RBA::TilingProcessor::new + tp.dbu = self.dbu + tp.scale_to_dbu = false + tp.tile_size(@tx, @ty) + bx = [ @bx || 0.0, border * self.dbu ].max + by = [ @by || 0.0, border * self.dbu ].max + tp.tile_border(bx, by) + + res = result_cls.new + tp.output("res", res) + tp.input("self", obj) + tp.threads = (@tt || 1) + args.each_with_index do |a,i| + if a.is_a?(RBA::Edges) || a.is_a?(RBA::Region) + tp.input("a#{i}", a) + else + tp.var("a#{i}", a) + end + end + av = args.size.times.collect { |i| "a#{i}" }.join(", ") + tp.queue("_output(res, self.#{method}(#{av}))") + run_timed("\"#{method}\" in: #{src_line}", obj) do + tp.execute("Tiled \"#{method}\" in: #{src_line}") + end + + else + res = nil + run_timed("\"#{method}\" in: #{src_line}", obj) do + res = obj.send(method, *args) + end + end + + # enable progress + if obj.is_a?(RBA::Region) + obj.disable_progress + end + + res + + end + + # used for area and perimeter only + def _tdcmd(obj, border, method) + + if @tx && @ty + + tp = RBA::TilingProcessor::new + tp.tile_size(@tx, @ty) + tp.tile_border(border * self.dbu, border * self.dbu) + + res = RBA::Value::new + res.value = 0.0 + tp.output("res", res) + tp.input("self", obj) + tp.threads = (@tt || 1) + tp.queue("_output(res, _tile ? self.#{method}(_tile.bbox) : self.#{method})") + run_timed("\"#{method}\" in: #{src_line}", obj) do + tp.execute("Tiled \"#{method}\" in: #{src_line}") + end + + res = res.value + + else + res = nil + run_timed("\"#{method}\" in: #{src_line}", obj) do + res = obj.send(method) + end + end + + # enable progress + if obj.is_a?(RBA::Region) + obj.disable_progress + end + + res + + end + + def _rcmd(obj, method, *args) + run_timed("\"#{method}\" in: #{src_line}", obj) do + RBA::Region::new(obj.send(method, *args)) + end + end + + def _vcmd(obj, method, *args) + run_timed("\"#{method}\" in: #{src_line}", obj) do + obj.send(method, *args) + end + end + + def _start + + # clearing the selection avoids some nasty problems + view = RBA::LayoutView::current + view && view.cancel + + end + + def _flush + + # clean up resources (i.e. temp layers) + @layout_sources.each do |n,l| + l.finish + end + + end + + def _finish(final = true) + + _flush + + view = RBA::LayoutView::current + + # save the report database if requested + if @output_rdb_file + info("Writing #{@output_rdb_file} ..") + @output_rdb.save(@output_rdb_file) + end + if @output_rdb && final && view + view.show_rdb(@output_rdb_index, view.active_cellview_index) + end + + # save the output file if requested + if @output_layout && @output_layout_file + opt = RBA::SaveLayoutOptions::new + gzip = opt.set_format_from_filename(@output_layout_file) + info("Writing #{@output_layout_file} ..") + @output_layout.write(@output_layout_file, gzip, opt) + end + + # create the new layers as visual layers if necessary + if view + + output = @output_layout || @def_layout + cv_index = nil + view.cellviews.times do |cvi| + view.cellview(cvi).layout == output && cv_index = cvi + end + if cv_index + + view = RBA::LayoutView::current + + # clear selection + view.cancel + + # create layer views for those layers which are not present yet + + present_layers = {} + l = view.begin_layers + while !l.at_end? + if l.current.cellview == cv_index + present_layers[l.current.layer_index] = true + end + l.next + end + + @output_layers.each do |li| + if !present_layers[li] + info = @def_layout.get_info(li) + lp = RBA::LayerProperties::new + lp.source_layer = info.layer + lp.source_datatype = info.datatype + lp.source_name = info.name + lp.source_cellview = cv_index + view.init_layer_properties(lp) + view.insert_layer(view.end_layers, lp) + end + end + + view.update_content + + end + + end + + @output_layout = nil + @output_layout_file = nil + @output_cell = nil + @output_rdb_file = nil + @output_rdb_cell = nil + @output_rdb = nil + @output_rdb_index = nil + + # clean up temp data + @dss && @dss._destroy + @netter && @netter._finish + + if final && @log_file + @log_file.close + @log_file = nil + end + + end + + def _dss + @dss + end + + def _netter + @netter ||= DRC::DRCNetter::new(self) + end + + private + + def _make_string(v) + if v.class.respond_to?(:from_s) + v.class.to_s + "::from_s(" + v.to_s.inspect + ")" + else + v.inspect + end + end + + def _input(layout, cell_index, layers, sel, box, clip, overlapping, shape_flags) + + if layers.empty? && ! @deep + + r = RBA::Region::new + + else + + if box + iter = RBA::RecursiveShapeIterator::new(layout, layout.cell(cell_index), layers, box, overlapping) + else + iter = RBA::RecursiveShapeIterator::new(layout, layout.cell(cell_index), layers) + end + iter.shape_flags = shape_flags + + sel.each do |s| + if s == "-" + iter.unselect_cells(cell.cell_index) + elsif s == "-*" + iter.unselect_all_cells + elsif s == "+*" + iter.select_all_cells + elsif s =~ /^-(.*)/ + iter.unselect_cells($1) + elsif s =~ /^\+(.*)/ + iter.select_cells($1) + else + iter.select_cells(s) + end + end + + sf = layout.dbu / self.dbu + if @deep + @dss ||= RBA::DeepShapeStore::new + # TODO: align with LayoutToNetlist by using a "master" L2N + # object which keeps the DSS. + @dss.text_property_name = "LABEL" + @dss.text_enlargement = 1 + r = RBA::Region::new(iter, @dss, RBA::ICplxTrans::new(sf.to_f)) + else + r = RBA::Region::new(iter, RBA::ICplxTrans::new(sf.to_f)) + end + + # clip if a box is specified + if box && clip + r &= RBA::Region::new(box) + end + + end + + r + + end + + def _layout(name) + @layout_sources[name].layout + end + + def _output(data, *args) + + if @output_rdb + + if args.size < 1 + raise("Invalid number of arguments for 'output' on report - category name and optional description expected") + end + + cat = @output_rdb.create_category(args[0].to_s) + args[1] && cat.description = args[1] + + cat.scan_collection(@output_rdb_cell, RBA::CplxTrans::new(self.dbu), data) + + else + + if @output_layout + output = @output_layout + if @output_cell + output_cell = @output_cell + elsif @def_cell + output_cell = @output_layout.cell(@def_cell.name) || @output_layout.create_cell(@def_cell.name) + end + output_cell || raise("No output cell specified (see 'target' instruction)") + else + output = @def_layout + output || raise("No output layout specified") + output_cell = @output_cell || @def_cell + output_cell || raise("No output cell specified") + end + + info = nil + if args.size == 1 + if args[0].is_a?(1.class) + info = RBA::LayerInfo::new(args[0], 0) + elsif args[0].is_a?(RBA::LayerInfo) + info = args[0] + elsif args[0].is_a?(String) + info = RBA::LayerInfo::from_string(args[0]) + else + raise("Invalid parameter type for 'output' - must be string or number") + end + elsif args.size == 2 || args.size == 3 + info = RBA::LayerInfo::new(*args) + else + raise("Invalid number of arguments for 'output' - one, two or three arguments expected") + end + li = output.find_layer(info) + if !li + li = output.insert_layer(info) + end + + # make sure the output has the right database unit + output.dbu = self.dbu + + tmp = li + + begin + + if !@used_output_layers[li] + @output_layers.push(li) + # Note: to avoid issues with output onto the input layer, we + # output to a temp layer and later swap both. The simple implementation + # did a clear here and the effect of that was that the data potentially + # got invalidated. + tmp = output.insert_layer(RBA::LayerInfo::new) + @used_output_layers[li] = true + end + + # insert the data into the output layer + if data.is_a?(RBA::EdgePairs) + data.insert_into_as_polygons(output, output_cell.cell_index, tmp, 1) + else + data.insert_into(output, output_cell.cell_index, tmp) + end + + # make the temp layer the output layer + if tmp != li + output.swap_layers(tmp, li) + end + + ensure + # clean up the original layer if requested + if tmp != li + output.delete_layer(tmp) + end + end + + end + end + + def make_source(layout, cell = nil) + name = "layout" + @lnum.to_s + @lnum += 1 + @dbu ||= layout.dbu + src = DRCSource::new(self, layout, layout, cell || layout.top_cell) + @layout_sources[name] = src + src + end + + end + +end + diff --git a/src/drc/drc/built-in-macros/drc.lym b/src/drc/drc/built-in-macros/_drc_layer.rb similarity index 59% rename from src/drc/drc/built-in-macros/drc.lym rename to src/drc/drc/built-in-macros/_drc_layer.rb index e869a76c4..afaabe2ba 100644 --- a/src/drc/drc/built-in-macros/drc.lym +++ b/src/drc/drc/built-in-macros/_drc_layer.rb @@ -1,248 +1,7 @@ - - - - - - - - - true - true - - false - - - ruby - - -# Extend the Float class by methods which convert -# values with units, i.e. 1.3nm gives 0.0013 - -1.0.class.class_eval do - - class << self - @dbu = nil - def _dbu=(dbu) - @dbu = dbu - end - def _dbu - @dbu - end - end - - def um - self - end - def micron - self - end - def degree - self - end - def nm - self*0.001 - end - def mm - self*1000.0 - end - def m - self*1000000.0 - end - def nm2 - self*1e-6 - end - def um2 - self - end - def micron2 - self - end - def mm2 - self*1.0e6 - end - def m2 - self*1.0e12 - end - def dbu - self.class._dbu || raise("No layout loaded - cannot determine database unit") - self*self.class._dbu - end - -end - -# Extend the Fixnum class, so it is possible to -# convert a value to Float with a unit spec, i.e. -# 5.nm -> 0.005. A spec with ".dbu" gives the -# Fixnum value itself. This indicates a value in -# database units for most methods of the DRC -# framework. - -1.class.class_eval do - - class << self - @dbu = nil - def _dbu=(dbu) - @dbu = dbu - end - def _dbu - @dbu - end - end - - def um - self.to_f - end - def micron - self.to_f - end - def degree - self.to_f - end - def nm - self*0.001 - end - def mm - self*1000.0 - end - def m - self*1000000.0 - end - def nm2 - self*1.0e-6 - end - def um2 - self.to_f - end - def micron2 - self.to_f - end - def mm2 - self*1.0e6 - end - def m2 - self*1.0e12 - end - def dbu - self.class._dbu || raise("No layout loaded - cannot determine database unit") - self*self.class._dbu - end - -end +# $autorun-early module DRC - include RBA - - # A wrapper for a named value which is stored in - # a variable for delayed execution - class DRCVar - def initialize(name) - @name = name - end - def inspect - @name - end - def to_s - @name - end - end - - # A wrapper for the sizing mode value - class DRCSizingMode - attr_accessor :value - def initialize(v) - self.value = v - end - end - - # A wrapper for the join flag for extended - class DRCJoinFlag - attr_accessor :value - def initialize(v) - self.value = v - end - end - - # A wrapper for the angle limit - # The purpose of this wrapper is to identify the - # angle limit specification - class DRCAngleLimit - attr_accessor :value - def initialize(v) - self.value = v - end - end - - # A wrapper for a metrics constant - # The purpose of this wrapper is to identify the - # metrics constant by the class. - class DRCMetrics - attr_accessor :value - def initialize(v) - self.value = v - end - end - - # A wrapper for the "whole edges" flag for - # the DRC functions. The purpose of this class - # is to identify the value by the class. - class DRCWholeEdges - attr_accessor :value - def initialize(v) - self.value = v - end - end - - # A wrapper for the "as_dots" or "as_boxes" flag for - # some DRC functions. The purpose of this class - # is to identify the value by the class. - class DRCAsDots - attr_accessor :value - def initialize(v) - self.value = v - end - end - - # A wrapper for a glob-pattern style text selection for - # some DRC functions. The purpose of this class - # is to identify the value by the class. - class DRCPattern - attr_accessor :as_pattern - attr_accessor :pattern - def initialize(f, p) - self.as_pattern = f - self.pattern = p - end - end - - # A wrapper for a pair of limit values - # This class is used to identify projection limits for DRC - # functions - class DRCProjectionLimits - attr_accessor :min - attr_accessor :max - def initialize(*a) - if a.size > 2 || a.size == 0 - raise("A projection limits specification requires a maximum of two values and at least one argument") - elsif a.size == 1 - if !a[0].is_a?(Range) || (!a[0].min.is_a?(Float) && !a[0].min.is_a?(1.class)) - raise("A projection limit requires an interval of two length values or two individual length values") - end - self.min = a[0].min - self.max = a[0].max - elsif a.size == 2 - if a[0] && !a[0].is_a?(Float) && !a[0].is_a?(1.class) - raise("First argument to a projection limit must be either nil or a length value") - end - if a[1] && !a[1].is_a?(Float) && !a[1].is_a?(1.class) - raise("Second argument to a projection limit must be either nil or a length value") - end - self.min = a[0] - self.max = a[1] - end - end - end - # A single DRC layer which is either # an edge pair, edge or region layer @@ -488,7 +247,7 @@ module DRC %w(area).each do |f| [true, false].each do |inv| mn = (inv ? "without" : "with") + "_" + f - eval <<"CODE" + eval <<"CODE" def #{mn}(*args) requires_region("#{f}") if args.size == 1 @@ -638,7 +397,7 @@ CODE %w(bbox_height bbox_max bbox_min bbox_width perimeter).each do |f| [true, false].each do |inv| mn = (inv ? "without" : "with") + "_" + f - eval <<"CODE" + eval <<"CODE" def #{mn}(*args) requires_region("#{mn}") if args.size == 1 @@ -687,7 +446,7 @@ CODE %w(length).each do |f| [true, false].each do |inv| mn = (inv ? "without" : "with") + "_" + f - eval <<"CODE" + eval <<"CODE" def #{mn}(*args) requires_edges("#{mn}") if args.size == 1 @@ -754,7 +513,7 @@ CODE [true, false].each do |inv| mn = (inv ? "without" : "with") + "_angle" - eval <<"CODE" + eval <<"CODE" def #{mn}(*args) requires_edges_or_region("#{mn}") result_class = @data.is_a?(RBA::Region) ? RBA::EdgePairs : RBA::Edges @@ -928,7 +687,7 @@ CODE args.each do |a| if a.is_a?(Range) - if (!a.min.is_a?(1.0.class) && !a.min.is_a?(1.class)) || (!a.max.is_a?(1.0.class) && !a.max.is_a?(1.class)) + if (!a.min.is_a?(1.0.class) && !a.min.is_a?(1.class)) || (!a.max.is_a?(1.0.class) && !a.max.is_a?(1.class)) raise("An angle limit requires an interval of two angles") end amin = a.min.to_f @@ -1066,7 +825,7 @@ CODE # @/table %w(middle extent_refs).each do |f| - eval <<"CODE" + eval <<"CODE" def #{f}(*args) requires_region("#{f}") @@ -1106,11 +865,11 @@ CODE } args.each do |a| - if a.is_a?(1.0.class) && :#{f} != :middle - f << a + if a.is_a?(1.0.class) && :#{f} != :middle + f << a elsif a.is_a?(DRCAsDots) as_edges = a.value - elsif @@std_refs[a] && :#{f} != :middle + elsif @@std_refs[a] && :#{f} != :middle f = @@std_refs[a] else raise("Invalid argument for '#{f}' method") @@ -1127,7 +886,7 @@ CODE DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :extent_refs_edges, *f)) else # add oversize for point- and edge-like regions - zero_area = (f[0] - f[2]).abs < 1e-7 || (f[1] - f[3]).abs < 1e-7 + zero_area = (f[0] - f[2]).abs < 1e-7 || (f[1] - f[3]).abs < 1e-7 f += [ zero_area ? 1 : 0 ] * 2 DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :extent_refs, *f)) end @@ -1153,12 +912,12 @@ CODE # new_layer = layer.select { |polygon| polygon.area >= 10.0 } # @/code - def select(&block) + def select(&block) new_data = @data.class.new t = RBA::CplxTrans::new(@engine.dbu) @engine.run_timed("\"select\" in: #{@engine.src_line}", @data) do @data.send(new_data.is_a?(RBA::EdgePairs) ? :each : :each_merged) do |object| - block.call(object.transformed(t)) && new_data.insert(object) + block.call(object.transformed(t)) && new_data.insert(object) end end DRCLayer::new(@engine, new_data) @@ -1174,7 +933,7 @@ CODE # Because this method executes inside the interpreter, it's inherently slow. Tiling does not # apply to this method. - def each(&block) + def each(&block) t = RBA::CplxTrans::new(@engine.dbu) @engine.run_timed("\"select\" in: #{@engine.src_line}", @data) do @data.send(@data.is_a?(RBA::EdgePairs) ? :each : :each_merged) do |object| @@ -1238,8 +997,8 @@ CODE # deliver RBA::EdgePair, RBA::DEdgePair or RBA::EdgePairs objects. %w(collect collect_to_region collect_to_edges collect_to_edge_pairs).each do |f| - eval <<"CODE" - def #{f}(&block) + eval <<"CODE" + def #{f}(&block) if :#{f} == :collect new_data = @data.class.new @@ -1325,7 +1084,7 @@ CODE # not modify the layer but returns a snapped copy. %w(snap snapped).each do |f| - eval <<"CODE" + eval <<"CODE" def #{f}(*args) requires_region("#{f}") gx = gy = 0 @@ -1338,7 +1097,7 @@ CODE raise("Invalid number of arguments for method 'ongrid'") end aa = args.collect { |a| prep_value(a) } - if :#{f} == :snap && @engine.is_tiled? + if :#{f} == :snap && @engine.is_tiled? # in tiled mode, no modifying versions are available @data = @engine._tcmd(@data, 0, @data.class, :snapped, gx, gy) self @@ -1357,7 +1116,7 @@ CODE # @brief Boolean AND operation # @synopsis layer.and(other) # The method computes a boolean AND between self and other. - # It is an alias for the "&" operator. + # It is an alias for the "&" operator. # # This method is available for polygon and edge layers. # If the first operand is an edge layer and the second is a polygon layer, the @@ -1376,7 +1135,7 @@ CODE # @/table def and(other) - self & other + self & other end # %DRC% @@ -1476,9 +1235,9 @@ CODE end # %DRC% - # @name & + # @name & # @brief Boolean AND operation - # @synopsis self & other + # @synopsis self & other # The method computes a boolean AND between self and other. # # This method is available for polygon and edge layers. An alias @@ -1847,7 +1606,7 @@ CODE # @brief Returns the parts of the edges inside the given region # @synopsis layer.inside_part(region) # This method returns the parts of the edges which are inside the given region. This is similar to the - # "&" operator, but this method does not return edges that are exactly on the boundaries + # "&" operator, but this method does not return edges that are exactly on the boundaries # of the polygons of the region. # # This method is available for edge layers. The argument must be a polygon layer. @@ -1863,7 +1622,7 @@ CODE # @brief Returns the parts of the edges outside the given region # @synopsis layer.outside_part(region) # This method returns the parts of the edges which are outside the given region. This is similar to the - # "&" operator, but this method does not remove edges that are exactly on the boundaries + # "&" operator, but this method does not remove edges that are exactly on the boundaries # of the polygons of the region. # # This method is available for edge layers. The argument must be a polygon layer. @@ -1874,10 +1633,10 @@ CODE # @/tr # @/table - %w(& | ^ - + interacting not_interacting overlapping not_overlapping inside not_inside outside not_outside in not_in).each do |f| - eval <<"CODE" + %w(& | ^ - + interacting not_interacting overlapping not_overlapping inside not_inside outside not_outside in not_in).each do |f| + eval <<"CODE" def #{f}(other) - if :#{f} != :interacting && :#{f} != :not_interacting && :#{f} != :& && :#{f} != :- + if :#{f} != :interacting && :#{f} != :not_interacting && :#{f} != :& && :#{f} != :- requires_same_type(other, "#{f}") else other.requires_edges_or_region("#{f}") @@ -1893,9 +1652,9 @@ CODE %w(interacting not_interacting overlapping not_overlapping inside not_inside outside not_outside).each do |fi| f = "select_" + fi # In tiled mode, there are no modifying versions. Emulate using the non-modifying one. - eval <<"CODE" + eval <<"CODE" def #{f}(other) - if :#{fi} != :interacting && :#{f} != :not_interacting + if :#{fi} != :interacting && :#{f} != :not_interacting requires_same_type(other, "#{f}") else other.requires_edges_or_region("#{f}") @@ -1913,7 +1672,7 @@ CODE %w(inside_part outside_part).each do |f| # In tiled mode, there are no modifying versions. Emulate using the non-modifying one. - eval <<"CODE" + eval <<"CODE" def #{f}(other) other.requires_region("#{f}") requires_edges("#{f}") @@ -1999,7 +1758,7 @@ CODE %w(rectangles rectilinear non_rectangles non_rectilinear holes hulls).each do |f| - eval <<"CODE" + eval <<"CODE" def #{f} requires_region("#{f}") DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :#{f})) @@ -2080,7 +1839,7 @@ CODE # @/table %w(end_segments start_segments centers).each do |f| - eval <<"CODE" + eval <<"CODE" def #{f}(length, fraction = 0.0) requires_edges("#{f}") length = prep_value(length) @@ -2092,7 +1851,7 @@ CODE # %DRC% # @name extended # @brief Returns polygons describing an area along the edges of the input - # @synopsis layer.extended([:begin => b,] [:end => e,] [:out => o,] [:in => i], [:joined => true]) + # @synopsis layer.extended([:begin => b,] [:end => e,] [:out => o,] [:in => i], [:joined => true]) # @synopsis layer.extended(b, e, o, i) # @synopsis layer.extended(b, e, o, i, joined) # @@ -2123,7 +1882,7 @@ CODE # @/table %w(extended).each do |f| - eval <<"CODE" + eval <<"CODE" def #{f}(*args) requires_edges("#{f}") @@ -2131,13 +1890,13 @@ CODE av = [ 0, 0, 0, 0, false ] args.each_with_index do |a,i| if a.is_a?(Hash) - a[:begin] && av[0] = prep_value(a[:begin]) - a[:end] && av[1] = prep_value(a[:end]) - a[:out] && av[2] = prep_value(a[:out]) - a[:in] && av[3] = prep_value(a[:in]) - a[:joined] && av[4] = true - elsif i < 4 - if !a.is_a?(1.class) && !a.is_a?(Float) + a[:begin] && av[0] = prep_value(a[:begin]) + a[:end] && av[1] = prep_value(a[:end]) + a[:out] && av[2] = prep_value(a[:out]) + a[:in] && av[3] = prep_value(a[:in]) + a[:joined] && av[4] = true + elsif i < 4 + if !a.is_a?(1.class) && !a.is_a?(Float) raise("Invalid type for argument " + (i+1).to_s + " (method '#{f}')") end av[i] = prep_value(a) @@ -2183,7 +1942,7 @@ CODE # A version extending to the inside is \extended_in. %w(extended_in extended_out).each do |f| - eval <<"CODE" + eval <<"CODE" def #{f}(dist) requires_edges("#{f}") DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :#{f}, prep_value(dist))) @@ -2204,7 +1963,7 @@ CODE # individual ones unless raw mode is chosen. %w(edges).each do |f| - eval <<"CODE" + eval <<"CODE" def #{f} if @data.is_a?(RBA::Region) DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Edges, :#{f})) @@ -2234,7 +1993,7 @@ CODE # Returns the second edges of the edge pairs in the collection. %w(first_edges second_edges).each do |f| - eval <<"CODE" + eval <<"CODE" def #{f} requires_edge_pairs("#{f}") DRCLayer::new(@engine, @engine._cmd(@data, :#{f})) @@ -2286,7 +2045,7 @@ CODE # @synopsis layer.is_deep? def is_deep? - @data.respond_to?(:is_deep?) && @data.is_deep? + @data.respond_to?(:is_deep?) && @data.is_deep? end # %DRC% @@ -2656,7 +2415,7 @@ CODE # @/table %w(width space overlap enclosing separation).each do |f| - eval <<"CODE" + eval <<"CODE" def #{f}(*args) requires_edges_or_region("#{f}") @@ -2683,7 +2442,7 @@ CODE minp = prep_value(a.min) maxp = prep_value(a.max) elsif a.is_a?(Float) || a.is_a?(1.class) - value && raise("Value already specified") + value && raise("Value already specified") value = prep_value(a) else raise("#{f}: Parameter #" + n.to_s + " does not have an expected type") @@ -2715,7 +2474,7 @@ CODE end %w(isolated notch).each do |f| - eval <<"CODE" + eval <<"CODE" def #{f}(*args) requires_region("#{f}") @@ -2742,7 +2501,7 @@ CODE minp = prep_value(a.min) maxp = prep_value(a.max) elsif a.is_a?(Float) || a.is_a?(1.class) - value && raise("Value already specified") + value && raise("Value already specified") value = prep_value(a) else raise("#{f}: Parameter #" + n.to_s + " does not have an expected type") @@ -2902,7 +2661,7 @@ CODE # it is called on. The input layer is returned and available for further processing. %w(size sized).each do |f| - eval <<"CODE" + eval <<"CODE" def #{f}(*args) requires_region("#{f}") @@ -2914,7 +2673,7 @@ CODE args.each do |a| if a.is_a?(1.class) || a.is_a?(Float) v = prep_value(a) - v.abs > dist && dist = v.abs + v.abs > dist && dist = v.abs values.push(v) elsif a.is_a?(DRCSizingMode) mode = a.value @@ -2922,9 +2681,9 @@ CODE end aa = [] - if values.size < 1 + if values.size < 1 raise "#{f}: Method requires one or two sizing values" - elsif values.size > 2 + elsif values.size > 2 raise "#{f}: Method must not have more than two values" else aa.push(values[0]) @@ -2933,7 +2692,7 @@ CODE aa.push(mode) - if :#{f} == :size && @engine.is_tiled? + if :#{f} == :size && @engine.is_tiled? # in tiled mode, no modifying versions are available @data = @engine._tcmd(@data, dist, RBA::Region, :sized, *aa) self @@ -2962,7 +2721,7 @@ CODE def polygons(*args) requires_edge_pairs("polygons") - args.size <= 1 || raise("polygons: Method requires 0 or 1 arguments") + args.size <= 1 || raise("polygons: Method requires 0 or 1 arguments") aa = args.collect { |a| prep_value(a) } DRCLayer::new(@engine, @engine._cmd(@data, :polygons, *aa)) end @@ -3050,7 +2809,7 @@ CODE # further processing. %w(extents moved transformed).each do |f| - eval <<"CODE" + eval <<"CODE" def #{f}(*args) aa = args.collect { |a| prep_value(a) } DRCLayer::new(@engine, @engine._cmd(@data, :#{f}, *aa)) @@ -3059,7 +2818,7 @@ CODE end %w(move transform).each do |f| - eval <<"CODE" + eval <<"CODE" def #{f}(*args) aa = args.collect { |a| prep_value(a) } @engine._cmd(@data, :#{f}, *aa) @@ -3229,2176 +2988,6 @@ CODE end end - - # A layout source representative object. - # This object describes an input. It consists of a layout reference plus - # some attributes describing how input is to be gathered. - - # %DRC% - # @scope - # @name Source - # @brief DRC Reference: Source Object - # The layer object represents a collection of polygons, edges or edge pairs. - # A source specifies where to take layout from. That includes the actual layout, - # the top cell and options such as clip/query boxes, cell filters etc. - - class DRCSource - - def initialize(engine, layout, layout_var, cell) - @engine = engine - @layout = layout - @layout_var = layout_var - @cell = cell - @inside = nil - @box = nil - @layers = nil - @sel = [] - @clip = false - @overlapping = false - @tmp_layers = [] - end - - # %DRC% - # @name layout - # @brief Returns the RBA::Layout object associated with this source - # @synopsis layout - - def layout - @layout - end - - # %DRC% - # @name cell_name - # @brief Returns the name of the currently selected cell - # @synopsis cell_name - - def cell_name - @cell.name - end - - # %DRC% - # @name cell_obj - # @brief Returns the RBA::Cell object of the currently selected cell - # @synopsis cell_obj - - def cell_obj - @cell - end - - def finish - @tmp_layers.each do |li| - @layout.delete_layer(li) - end - end - - def set_box(method, *args) - box = nil - if args.size == 1 - box = args[0] - box.is_a?(RBA::DBox) || raise("'#{method}' method requires a box specification") - elsif args.size == 2 - (args[0].is_a?(RBA::DPoint) && args[1].is_a?(RBA::DPoint)) || raise("'#{method}' method requires a box specification with two points") - box = RBA::DBox::new(args[0], args[1]) - elsif args.size == 4 - box = RBA::DBox::new(*args) - else - raise("Invalid number of arguments for '#{method}' method") - end - @box = RBA::Box::from_dbox(box * (1.0 / @layout.dbu)) - self - end - - def inplace_clip(*args) - set_box("clip", *args) - @clip = true - @overlapping = true - end - - def inplace_touching(*args) - set_box("touching", *args) - @clip = false - @overlapping = false - end - - def inplace_overlapping(*args) - set_box("overlapping", *args) - @clip = false - @overlapping = true - end - - def inplace_cell(arg) - @cell = layout.cell(arg) - @cell ||= layout.create_cell(arg) - self - end - - def inplace_select(*args) - args.each do |a| - a.is_a?(String) || raise("Invalid arguments to 'select' method - must be strings") - @sel.push(a) - end - self - end - - # %DRC% - # @name select - # @brief Adds cell name expressions to the cell filters - # @synopsis source.select(filter1, filter2, ...) - # This method will construct a new source object with the given cell filters - # applied. - # Cell filters will enable or disable cells plus their subtree. - # Cells can be switched on and off, which makes the hierarchy traversal - # stop or begin delivering shapes at the given cell. The arguments of - # the select method form a sequence of enabling or disabling instructions - # using cell name pattern in the glob notation ("*" as the wildcard, like shell). - # Disabling instructions start with a "-", enabling instructions with a "+" or - # no specification. - # - # The following options are available: - # - # @ul - # @li @tt+@/tt @i name_filter @/i: Cells matching the name filter will be enabled @/li - # @li @i name_filter @/i: Same as "+name_filter" @/li - # @li @tt-@/tt @i name_filter @/i: Cells matching the name filter will be disabled @/li - # @/ul - # - # To disable the TOP cell but enabled a hypothetical cell B below the top cell, use that - # code: - # - # @code - # layout_with_selection = layout.select("-TOP", "+B") - # l1 = layout_with_selection.input(1, 0) - # ... - # @/code - # - # Please note that the sample above will deliver the children of "B" because there is - # nothing said about how to proceed with cells other than "TOP" or "B". - # The following code will just select "B" without it's children, because in the - # first "-*" selection, all cells including the children of "B" are disabled: - # - # @code - # layout_with_selection = layout.select("-*", "+B") - # l1 = layout_with_selection.input(1, 0) - # ... - # @/code - - # %DRC% - # @name cell - # @brief Specifies input from a specific cell - # @synopsis source.cell(name) - # This method will create a new source that delivers shapes from the - # specified cell. - - # %DRC% - # @name clip - # @brief Specifies clipped input - # @synopsis source.clip(box) - # @synopsis source.clip(p1, p2) - # @synopsis source.clip(l, b, r, t) - # Creates a source which represents a rectangular part of the - # original input. Three ways are provided to specify the rectangular - # region: a single RBA::DBox object (micron units), two RBA::DPoint - # objects (lower/left and upper/right coordinate in micron units) - # or four coordinates: left, bottom, right and top coordinate. - # - # This method will create a new source which delivers the shapes - # from that region clipped to the rectangle. A method doing the - # same but without clipping is \touching or \overlapping. - - # %DRC% - # @name touching - # @brief Specifies input selected from a region in touching mode - # @synopsis source.touching(box) - # @synopsis source.touching(p1, p2) - # @synopsis source.touching(l, b, r, t) - # Like \clip, this method will create a new source delivering shapes - # from a specified rectangular region. In contrast to clip, all shapes - # touching the region with their bounding boxes are delivered as a whole - # and are not clipped. Hence shapes may extent beyond the limits of - # the specified rectangle. - # - # \overlapping is a similar method which delivers shapes overlapping - # the search region with their bounding box (and not just touching) - - # %DRC% - # @name overlapping - # @brief Specifies input selected from a region in overlapping mode - # @synopsis source.overlapping(...) - # Like \clip, this method will create a new source delivering shapes - # from a specified rectangular region. In contrast to clip, all shapes - # overlapping the region with their bounding boxes are delivered as a whole - # and are not clipped. Hence shapes may extent beyond the limits of - # the specified rectangle. - # - # \touching is a similar method which delivers shapes touching - # the search region with their bounding box (without the requirement to overlap) - - # export inplace_* as * out-of-place - %w(select cell clip touching overlapping).each do |f| - eval <<"CODE" - def #{f}(*args) - s = self.dup - s.inplace_#{f}(*args) - s - end -CODE - end - - # %DRC% - # @name extent - # @brief Returns a layer with the bounding box of the selected layout - # @synopsis source.extent - # The extent function is useful to invert a layer: - # - # @code - # inverse_1 = extent.sized(100.0) - input(1, 0) - # @/code - - def extent - layer = input - if @box - layer.insert(RBA::DBox::from_ibox(@box) * @layout.dbu) - else - layer.insert(RBA::DBox::from_ibox(@cell.bbox) * @layout.dbu) - end - layer - end - - # %DRC% - # @name input - # @brief Specifies input from a source - # @synopsis source.input(layer) - # @synopsis source.input(layer, datatype) - # @synopsis source.input(layer_into) - # @synopsis source.input(filter, ...) - # Creates a layer with the shapes from the given layer of the source. - # The layer can be specified by layer and optionally datatype, by a RBA::LayerInfo - # object or by a sequence of filters. - # Filters are expressions describing ranges - # of layers and/or datatype numbers or layer names. Multiple filters - # can be given and all layers matching at least one of these filter - # expressions are joined to render the input layer for the DRC engine. - # - # Some filter expressions are: - # - # @ul - # @li @tt 1/0-255 @/tt: Datatypes 0 to 255 for layer 1 @/li - # @li @tt 1-10 @/tt: Layers 1 to 10, datatype 0 @/li - # @li @tt METAL @/tt: A layer named "METAL" @/li - # @li @tt METAL (17/0) @/tt: A layer named "METAL" or layer 17, datatype 0 (for GDS, which does - # not have names)@/li - # @/ul - # - # Layers created with "input" contain both texts and polygons. There is a subtle - # difference between flat and deep mode: in flat mode, texts are not visible in polygon - # operations. In deep mode, texts appear as small 2x2 DBU rectangles. In flat mode, - # some operations such as clipping are not fully supported for texts. Also, texts will - # vanish in most polygon operations such as booleans etc. - # - # Texts can later be selected on the layer returned by "input" with the \Layer#texts method. - # - # If you don't want to see texts, use \polygons to create an input layer with polygon data - # only. If you only want to see texts, use \labels to create an input layer with texts only. - # - # Use the global version of "input" without a source object to address the default source. - - def input(*args) - layers = parse_input_layers(*args) - DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SAll)) - end - - # %DRC% - # @name labels - # @brief Gets the labels (texts) from an input layer - # @synopsis source.labels(layer) - # @synopsis source.labels(layer, datatype) - # @synopsis source.labels(layer_into) - # @synopsis source.labels(filter, ...) - # - # Creates a layer with the labels from the given layer of the source. - # - # This method is identical to \input, but takes only texts from the given input - # layer. - # - # Use the global version of "labels" without a source object to address the default source. - - def labels(*args) - layers = parse_input_layers(*args) - DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::STexts)) - end - - # %DRC% - # @name polygons - # @brief Gets the polygon shapes (or shapes that can be converted polygons) from an input layer - # @synopsis source.polygons(layer) - # @synopsis source.polygons(layer, datatype) - # @synopsis source.polygons(layer_into) - # @synopsis source.polygons(filter, ...) - # - # Creates a layer with the polygon shapes from the given layer of the source. - # With "polygon shapes" we mean all kind of shapes that can be converted to polygons. - # Those are boxes, paths and real polygons. - # - # This method is identical to \input with respect to the options supported. - # - # Use the global version of "polygons" without a source object to address the default source. - - def polygons(*args) - layers = parse_input_layers(*args) - DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SBoxes | RBA::Shapes::SPaths | RBA::Shapes::SPolygons | RBA::Shapes::SEdgePairs)) - end - - # %DRC% - # @name make_layer - # @brief Creates an empty polygon layer based on the hierarchy of the layout - # @synopsis make_layer - # This method delivers a new empty original layer. - - def make_layer - layers = [] - DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SAll)) - end - - # %DRC% - # @name layers - # @brief Gets the layers the source contains - # @synopsis source.layers - # Delivers a list of RBA::LayerInfo objects representing the layers - # inside the source. - # - # One application is to read all layers from a source. In the following - # example, the "and" operation is used to perform a clip with the given - # rectangle. Note that this solution is not efficient - it's provided - # as an example only: - # - # @code - # output_cell("Clipped") - # - # clip_box = polygon_layer - # clip_box.insert(box(0.um, -4.um, 4.um, 0.um)) - # - # layers.each { |l| (input(l) & clip_box).output(l) } - # @/code - - def layers - @layout.layer_indices.collect { |li| @layout.get_info(li) } - end - - private - - def parse_input_layers(*args) - - layers = [] - - if args.size == 0 - - li = @layout.insert_layer(RBA::LayerInfo::new) - li && layers.push(li) - li && @tmp_layers.push(li) - - elsif (args.size == 1 && args[0].is_a?(RBA::LayerInfo)) - - li = @layout.find_layer(args[0]) - li && layers.push(li) - - elsif (args.size == 1 || args.size == 2) && args[0].is_a?(1.class) - - li = @layout.find_layer(args[0], args[1] || 0) - li && layers.push(li) - - else - - args.each do |a| - if a.is_a?(String) - # use the LayerMap class to fetch the matching layers - lm = RBA::LayerMap::new - lm.map(a, 0) - @layout.layer_indices.each do |li| - if lm.is_mapped?(@layout.get_info(li)) - layers.push(li) - end - end - end - end - - end - - layers - - end - - end - - # The netter object - - # %DRC% - # @scope - # @name Netter - # @brief DRC Reference: Netter object - # The Netter object provides services related to network extraction - # from a layout. The relevant methods of this object are available - # as global functions too where they act on a default incarnation - # of the netter. Usually it's not required to instantiate a Netter - # object, but it serves as a container for this functionality. - # - # An individual netter object can be created, if the netter results - # need to be kept for multiple extractions. If you really need - # a Netter object, use the global \netter function: - # - # @code - # # create a new Netter object: - # nx = netter - # nx.connect(poly, contact) - # ... - # @/code - # - # Network formation: - # - # A basic Service the Netter object provides is the formation of - # connected networks of conductive shapes. To do so, the Netter - # must be given a connection specification. This happens by calling - # "connect" with two polygon layers. The Netter will then regard all - # overlaps of shapes on these layers as connections between the - # respective materials. Networks are the basis for netlist extraction, - # network geometry deduction and the antenna check. - # - # Connections can be cleared with "clear_connections". If not, - # connections add atop of the already defined ones. Here is an - # example for the antenna check: - # - # @code - # # build connction of poly+gate to metal1 - # connect(gate, poly) - # connect(poly, contact) - # connect(contact, metal1) - # - # # runs an antenna check for metal1 with a ratio of 50 - # m1_antenna_errors = antenna_check(gate, metal1, 50.0) - # - # # add connections to metal2 - # connect(metal1, via1) - # connect(via1, metal2) - # - # # runs an antenna check for metal2 with a ratio of 70.0 - # m2_antenna_errors = antenna_check(gate, metal2, 70.0) - # - # # this will remove all connections made - # clear_connections - # ... - # @/code - # - # Further functionality of the Netter object: - # - # More methods will be added in the future to support network-related features. - - class DRCNetter - - def initialize(engine) - @engine = engine - clear_connections - end - - # %DRC% - # @name connect - # @brief Specifies a connection between two layers - # @synopsis connect(a, b) - # a and b must be polygon layers. After calling this function, the - # Netter regards all overlapping or touching shapes on these layers - # to form an electrical connection between the materials formed by - # these layers. This also implies intra-layer connections: shapes - # on these layers touching or overlapping other shapes on these - # layers will form bigger, electrically connected areas. - # - # Multiple connect calls must be made to form larger connectivity - # stacks across multiple layers. Such stacks may include forks and - # joins. - # - # Connections are accumulated. The connections defined so far - # can be cleared with \clear_connections. - - def connect(a, b) - a.is_a?(DRC::DRCLayer) || raise("First argument of Netter#connect must be a layer") - b.is_a?(DRC::DRCLayer) || raise("Second argument of Netter#connect must be a layer") - a.requires_region("Netter#connect (first argument)") - b.requires_region("Netter#connect (second argument)") - [ a, b ].each { |l| @layers[l.data.data_id] = l.data } - @connections << [ a, b ].collect { |l| l.data.data_id } - modified - end - - # %DRC% - # @name connect_global - # @brief Connects a layer with a global net - # @synopsis connect_global(l, name) - # Connects the shapes from the given layer l to a global net with the given name. - # Global nets are common to all cells. Global nets automatically connect to parent - # cells throughs implied pins. An example is the substrate (bulk) net which connects - # to shapes belonging to tie-down diodes. - - def connect_global(l, name) - l.is_a?(DRC::DRCLayer) || raise("Layer argument of Netter#connect_global must be a layer") - l.requires_region("Netter#connect_global (layer argument)") - @layers[l.data.data_id] = l.data - @global_connections << [ l.data.data_id, name.to_s ] - end - - # %DRC% - # @name extract_devices - # @brief Extracts devices based on the given extractor class, name and device layer selection - # @synopsis extract_devices(extractor, layer_hash) - # Runs the device extraction for given device extractor class. - # - # The device extractor is either an instance of one of the predefined extractor - # classes (e.g. RBA::DeviceExtractorMOS4Transistor) or a custom class. It provides the - # algorithms for deriving the device parameters from the device geometry. It needs - # several device recognition layers which are passed in the layer hash. - # - # Each device class (e.g. n-MOS/p-MOS or high Vt/low Vt) needs it's own instance - # of device extractor. The device extractor beside the algorithm and specific - # extraction settings defines the name of the device to be built. - # - # The layer hash is a map of device type specific functional names (key) and - # polygon layers (value). Here is an example: - # - # @code - # deep - # - # nwell = input(1, 0) - # active = input(2, 0) - # poly = input(3, 0) - # bulk = make_layer # renders an empty layer used for putting the terminals on - # - # nactive = active - nwell # active area of NMOS - # nsd = nactive - poly # source/drain area - # gate = nactive & poly # gate area - # - # mos4_ex = RBA::DeviceExtractorMOS4Transistor::new("NMOS4") - # extract_devices(mos4_ex, { :SD => nsd, :G => gate, :P => poly, :W => bulk }) - # @/code - - def extract_devices(devex, layer_selection) - - devex.is_a?(RBA::DeviceExtractorBase) || raise("First argument of Netter#extract_devices must be a device extractor instance") - layer_selection.is_a?(Hash) || raise("Second argument of Netter#extract_devices must be a hash") - - ls = {} - layer_selection.each do |n,l| - l.requires_region("Netter#extract_devices (#{n} layer)") - @layers[l.data.data_id] = l.data - ls[n.to_s] = l.data - end - - @devices_to_extract << [ devex, ls ] - modified - - end - - # %DRC% - # @name clear_connections - # @brief Clears all connections stored so far - # @synopsis clear_connections - # See \connect for more details. - - def clear_connections - @devices_to_extract = [] - @connections = [] - @global_connections = [] - @layers = {} - @join_nets = "" - modified - end - - # %DRC% - # @name join_nets - # @brief Specifies a search pattern for labels which create implicit net connections - # @synopsis join_nets(label_pattern) - # Use this method to supply a glob pattern for labels which create implicit net connections - # on the top level circuit. This feature is useful to connect identically labelled nets - # while a component isn't integrated yet. If the component is integrated, net may be connected - # on a higher hierarchy level - e.g. by a power mesh. Inside the component this net consists - # of individual islands. To properly perform netlist extraction and comparison, these islands - # need to be connected even though there isn't a physical connection. "join_nets" can - # achive this if these islands are labelled with the same text on the top level of the - # component. - # - # Glob pattern are used which resemble shell file pattern: "*" is for all labels, "VDD" - # for all "VDD" labels (pattern act case sensitive). "VDD*" is for all labels beginning - # with "VDD" (still different labels will be connected to different nets!). "{VDD,VSS}" - # is either "VDD" or "VSS". - # - # The search pattern is applied on the next net extraction. The search pattern is cleared - # on "clear_connections". - - def join_nets(arg) - @join_nets = arg - modified - end - - # %DRC% - # @brief Performs an antenna check - # @name antenna_check - # @synopsis antenna_check(gate, metal, ratio, [ diode_specs ... ]) - # - # The antenna check is used to avoid plasma induced damage. Physically, - # the damage happes if during the manufacturing of a metal layer with - # plasma etching charge accumulates on the metal islands. On reaching a - # certain threshold, this charge may discarge over gate oxide attached of - # devices attached to such metal areas hence damaging it. - # - # Antenna checks are performed by collecting all connected nets up to - # a certain metal layer and then computing the area of all metal shapes - # and all connected gates of a certain kind (e.g. thin and thick oxide gates). - # The ratio of metal area divided by the gate area must not exceed a certain - # threshold. - # - # A simple antenna check is this: - # - # @code - # poly = ... # poly layer - # diff = ... # diffusion layer - # contact = ... # contact layer - # metal1 = ... # metal layer - # - # # compute gate area - # gate = poly & diff - # - # # note that gate and poly have to be included - gate is - # # a subset of poly, but forms the sensitive area - # connect(gate, poly) - # connect(poly, contact) - # connect(contact, metal1) - # errors = antenna_check(gate, metal1, 50.0) - # @/code - # - # Plasma induced damage can be rectified by including diodes - # which create a safe current path for discharging the metal - # islands. Such diodes can be identified with a recognition layer - # (usually the diffusion area of a certain kind). You can include - # such diode recognition layers in the antenna check. If a connection - # is detected to a diode, the respective network is skipped: - # - # @code - # ... - # diode = ... # diode recognition layer - # - # connect(diode, contact) - # errors = antenna_check(gate, metal1, 50.0, diode) - # @/code - # - # You can also make diode connections decreases the - # sensitivity of the antenna check depending on the size - # of the diode. The following specification makes - # diode connections increase the ratio threshold by - # 10 per square micrometer of diode area: - # - # @code - # ... - # diode = ... # diode recognition layer - # - # connect(diode, contact) - # # each square micrometer of diode area connected to a network - # # will add 10 to the ratio: - # errors = antenna_check(gate, metal1, 50.0, [ diode, 10.0 ]) - # @/code - # - # Multiple diode specifications are allowed. Just add them - # to the antenna_check call. - # - # The error shapes produced by the antenna check are copies - # of the metal shapes on the metal layers of each network - # violating the antenna rule. - - def antenna_check(gate, metal, ratio, *diodes) - - gate.is_a?(DRC::DRCLayer) || raise("gate argument of Netter#antenna_check must be a layer") - gate.requires_region("Netter#antenna_check (gate argument)") - - metal.is_a?(DRC::DRCLayer) || raise("metal argument of Netter#antenna_check must be a layer") - metal.requires_region("Netter#antenna_check (metal argument)") - - if !ratio.is_a?(1.class) && !ratio.is_a?(Float) - raise("ratio argument Netter#antenna_check is not a number") - end - - dl = diodes.collect do |d| - if d.is_a?(Array) - d.size == 2 || raise("diode specification pair expects two elements") - d[0].requires_region("Netter#antenna_check (diode layer)") - [ d[0].data, d[1].to_f ] - else - d.requires_region("Netter#antenna_check (diode layer)") - [ d.data, 0.0 ] - end - end - - @l2n || make_l2n - DRC::DRCLayer::new(@engine, @engine._cmd(@l2n, :antenna_check, gate.data, metal.data, ratio, dl)) - - end - - # %DRC% - # @name l2n_data - # @brief Gets the internal RBA::LayoutToNetlist object - # @synopsis l2n_data - # The RBA::LayoutToNetlist object provides access to the internal details of - # the netter object. - - def l2n_data - @l2n || make_l2n - @l2n - end - - def _finish - clear_connections - # cleans up the L2N object - modified - end - - private - - def modified - @l2n && @l2n._destroy - @l2n = nil - end - - def make_l2n - - if @engine._dss - # TODO: check whether all layers are deep and come from the dss and layout index, - # then use this layout index. This will remove the need for this check: - @engine._dss.is_singular? || raise("The DRC script features more than one or no layout source - network extraction cannot be performed in such configurations") - @l2n = RBA::LayoutToNetlist::new(@engine._dss) - else - layout = @engine.source.layout - @l2n = RBA::LayoutToNetlist::new(layout.top_cell.name, layout.dbu) - end - - @layers.each { |id,l| @l2n.register(l, "l" + id.to_s) } - - @devices_to_extract.each do |devex,ls| - @engine._cmd(@l2n, :extract_devices, devex, ls) - end - - @layers.each { |id,l| @l2n.connect(l) } - @connections.each { |a,b| @l2n.connect(@layers[a], @layers[b]) } - @global_connections.each { |l,n| @l2n.connect_global(@layers[l], n) } - - # run extraction in a timed environment - @engine._cmd(@l2n, :extract_netlist, @join_nets) - @l2n - - end - - end - - # The DRC engine - - # %DRC% - # @scope - # @name global - # @brief DRC Reference: Global Functions - # Some functions are available on global level and can be used without any object. - # Most of them are convenience functions that basically act on some default object - # or provide function-like alternatives for the methods. - - class DRCEngine - - def initialize - - cv = RBA::CellView::active - - @def_layout = cv && cv.layout - @def_cell = cv && cv.cell - @def_source = nil - @dbu_read = false - use_dbu(@def_layout && @def_layout.dbu) - @output_layout = nil - @output_rdb = nil - @output_rdb_file = nil - @output_rdb_cell = nil - @used_output_layers = {} - @output_layers = [] - @vnum = 1 - @layout_sources = {} - @lnum = 1 - @log_file = nil - @dss = nil - @deep = false - @netter = nil - - @verbose = false - - end - - def joined - DRCJoinFlag::new(true) - end - - def diamond_limit - DRCSizingMode::new(0) - end - - def octagon_limit - DRCSizingMode::new(1) - end - - def square_limit - DRCSizingMode::new(2) - end - - def acute_limit - DRCSizingMode::new(3) - end - - def no_limit - DRCSizingMode::new(4) - end - - def projection_limits(*args) - DRCProjectionLimits::new(*args) - end - - def angle_limit(a) - DRCAngleLimit::new(a) - end - - def whole_edges(f = true) - DRCWholeEdges::new(f) - end - - def euclidian - DRCMetrics::new(RBA::Region::Euclidian) - end - - def square - DRCMetrics::new(RBA::Region::Square) - end - - def projection - DRCMetrics::new(RBA::Region::Projection) - end - - def pattern(p) - DRCPattern::new(true, p) - end - - def text(p) - DRCPattern::new(false, p) - end - - def as_dots - DRCAsDots::new(true) - end - - def as_edges - DRCAsDots::new(true) - end - - def as_boxes - DRCAsDots::new(false) - end - - # %DRC% - # @name verbose? - # @brief Returns true, if verbose mode is enabled - # @synopsis verbose? - # In verbose mode, more output is generated in the log file - - def verbose? - @verbose - end - - # %DRC% - # @name verbose - # @brief Sets or resets verbose mode - # @synopsis verbose - # @synopsis verbose(m) - # In verbose mode, more output is generated in the log file - - def verbose(f = true) - self.verbose = f - end - - # %DRC% - # @name silent - # @brief Resets verbose mode - # @synopsis silent - # This function is equivalent to "verbose(false)" (see \verbose) - - def silent(f = true) - self.verbose = !f - end - - def verbose=(f) - @verbose = f - end - - # %DRC% - # @name info - # @brief Outputs as message to the logger window - # @synopsis info(message) - # Prints the message to the log window in verbose mode. - # In non-verbose more, nothing is printed. - # \log is a function that always prints a message. - - def info(arg) - @verbose && log(arg) - end - - # %DRC% - # @name log - # @brief Outputs as message to the logger window - # @synopsis log(message) - # Prints the message to the log window. - # \info is a function that prints a message only if - # verbose mode is enabled. - - def log(arg) - if @log_file - @log_file.puts(arg) - else - RBA::Logger::info(arg) - end - end - - # %DRC% - # @name error - # @brief Prints an error - # @synopsis error(message) - # Similar to \log, but the message is printed formatted as an error - - def error(arg) - if @log_file - @log_file.puts("ERROR: " + arg) - else - RBA::Logger::error(arg) - end - end - - # %DRC% - # @name log_file - # @brief Specify the log file where to send to log to - # @synopsis log_file(filename) - # After using that method, the log output is sent to the - # given file instead of the logger window or the terminal. - - def log_file(arg) - @log_file && @log_file.close - @log_file = File.open(arg, "w") - end - - # Specify the database unit to use - - def use_dbu(d) - if @dbu_read - raise "Cannot change the database unit at this point" - end - # Should have a "context", but no such thing for Float or Fixnum - 1.0.class._dbu = d - 1.class._dbu = d - @dbu = d - end - - # %DRC% - # @name dbu - # @brief Gets or sets the database unit to use - # @synopsis dbu(dbu) - # @synopsis dbu - # Without any argument, this method gets the database unit - # used inside the DRC engine. - # - # With an argument, sets the database unit used internally in the DRC engine. - # Without using that method, the database unit is automatically - # taken as the database unit of the last input. - # A specific database unit can be set in order to optimize - # for two layouts (i.e. take the largest common denominator). - # When the database unit is set, it must be set at the beginning - # of the script and before any operation that uses it. - - def dbu(d = nil) - if !d - @dbu || raise("No database unit specified") - else - use_dbu(d.to_f) - end - @dbu_read = true - @dbu - end - - # %DRC% - # @name tiles - # @brief Specifies tiling - # @synopsis tiles(t) - # @synopsis tiles(w, h) - # Specifies tiling mode. In tiling mode, the DRC operations are evaluated in tiles - # with width w and height h. With one argument, square tiles with width and height - # t are used. - # - # Special care must be taken when using tiling mode, since some operations may not - # behave as expected at the borders of the tile. Tiles can be made overlapping by - # specifying a tile border dimension with \tile_borders. Some operations like sizing, - # the DRC functions specify a tile border implicitly. Other operations without a - # defined range won't do so and the consequences of tiling mode can be difficult to - # predict. - # - # In tiling mode, the memory requirements are usually smaller (depending on the - # choice of the tile size) and multi-CPU support is enabled (see \threads). - # To disable tiling mode use \flat or \deep. - # - # Tiling mode will disable deep mode (see \deep). - - def tiles(tx, ty = nil) - @tx = tx.to_f - @ty = (ty || tx).to_f - @deep = false - end - - # %DRC% - # @name is_tiled? - # @brief Returns true, if in tiled mode - # @synopsis is_tiled? - - def is_tiled? - @tx != nil - end - - # %DRC% - # @name deep - # @brief Enters deep (hierarchical) mode - # @synopsis deep - # - # In deep mode, the operations will be performed in a hierarchical fashion. - # Sometimes this reduces the time and memory required for an operation, but this - # will also add some overhead for the hierarchical analysis. - # - # "deepness" is a property of layers. Layers created with "input" while in - # deep mode carry hierarchy. Operations involving such layers at the only - # or the first argument are carried out in hierarchical mode. - # - # Hierarchical mode has some more implications, like "merged_semantics" being - # implied always. Sometimes cell variants will be created. - # - # Deep mode can be cancelled with \tiles or \flat. - - def deep - @deep = true - @tx = @ty = nil - end - - # %DRC% - # @name is_deep? - # @brief Returns true, if in deep mode - # @synopsis is_deep? - - def is_deep? - @deep - end - - # %DRC% - # @name tile_borders - # @brief Specifies a minimum tile border - # @synopsis tile_border(b) - # @synopsis tile_border(bx, by) - # The tile border specifies the distance to which shapes are collected into the - # tile. In order words, when processing a tile, shapes within the border distance - # participate in the operations. - # - # For some operations such as booleans (\and, \or, ...), \size and the DRC functions (\width, \space, ...) - # a tile border is automatically established. For other operations such as \with_area - # or \edges, the exact distance is unknown, because such operations may have a long range. - # In that cases, no border is used. The tile_borders function may be used to specify a minimum border - # which is used in that case. That allows taking into account at least shapes within the - # given range, although not necessarily all. - # - # To reset the tile borders, use \no_borders or "tile_borders(nil)". - - def tile_borders(bx, by = nil) - @bx = bx.to_f - @by = (by || bx).to_f - end - - # %DRC% - # @name no_borders - # @brief Reset the tile borders - # @synopsis no_borders - # Resets the tile borders - see \tile_borders for a description of tile borders. - - def no_borders - @bx = @by = nil - end - - # %DRC% - # @name flat - # @brief Disables tiling mode - # @synopsis flat - # Disables tiling mode. Tiling mode can be enabled again with \tiles later. - - def flat - @tx = @ty = nil - @deep = false - end - - # %DRC% - # @name threads - # @brief Specifies the number of CPU cores to use in tiling mode - # @synopsis threads(n) - # If using threads, tiles are distributed on multiple CPU cores for - # parallelization. Still, all tiles must be processed before the - # operation proceeds with the next statement. - - def threads(n) - @tt = n.to_i - end - - # %DRC% - # @name make_layer - # @brief Creates an empty polygon layer based on the hierarchical scheme selected - # @synopsis make_layer - # The intention of this method is to provide an empty polygon layer based on the - # hierarchical scheme selected. This will create a new layer with the hierarchy - # of the current layout in deep mode and a flat layer in flat mode. - # This method is similar to \polygon_layer, but the latter does not create - # a hierarchical layer. Hence the layer created by \make_layer is suitable - # for use in device extraction for example, while the one - # delivered by \polygon_layer is not. - # - # On the other hand, a layer created by the \make_layer method is not intended to be - # filled with \Layer#insert. - - def make_layer - layout.make_layer - end - - # %DRC% - # @name polygon_layer - # @brief Creates an empty polygon layer - # @synopsis polygon_layer - # The intention of that method is to create an empty layer which can be - # filled with polygon-like objects using \Layer#insert. - # A similar method which creates a hierarchical layer in deep mode is - # \make_layer. This other layer is better suited for use with device extraction. - - def polygon_layer - DRCLayer::new(self, RBA::Region::new) - end - - # %DRC% - # @name edge_layer - # @brief Creates an empty edge layer - # @synopsis edge_layer - # The intention of that method is to create an empty layer which can be - # filled with edge objects using \Layer#insert. - - def edge_layer - DRCLayer::new(self, RBA::Edges::new) - end - - # %DRC% - # @name source - # @brief Specifies a source layout - # @synopsis source - # @synopsis source(what) - # This function replaces the default source layout by the specified - # file. If this function is not used, the currently active layout - # is used as input. - # - # \layout is a similar method which specifies @i a additional @/i input layout. - # - # "what" specifies what input to use. "what" be either - # - # @ul - # @li A string "\@n" specifying input from a layout in the current panel @/li - # @li A layout filename plus an optional cell name@/li - # @li A RBA::Layout object plus an optional cell name@/li - # @li A RBA::Cell object @/li - # @/ul - # - # Without any arguments the default layout is returned. If a filename is given, a cell name - # can be specified as the second argument. If none is specified, the top cell is taken which - # must be unique in that case. - # - # @code - # # XOR between layers 1 of "first_layout.gds" and "second_layout.gds" and sends the results to "xor_layout.gds": - # target("xor_layout.gds") - # source("first_layout.gds") - # l2 = layout("second_layout.gds") - # (input(1, 0) ^ l2.input(1, 0)).output(100, 0) - # @/code - # - # For further methods on the source object see \Source. - - def source(arg = nil, arg2 = nil) - - if arg - - if arg.is_a?(String) - - if arg =~ /^@(\d+)/ - n = $1.to_i - 1 - view = RBA::LayoutView::current - view || raise("No view open") - (n >= 0 && view.cellviews > n) || raise("Invalid layout index @#{n + 1}") - cv = view.cellview(n) - cv.is_valid? || raise("Invalid layout @#{n + 1}") - @def_source = make_source(cv.layout, cv.cell) - else - layout = RBA::Layout::new - info("Reading #{arg} ..") - layout.read(arg) - cell = nil - if arg2 - arg2.is_a?(String) || raise("Second argument of 'source' must be a string") - cell = layout.cell(arg2) - cell || raise("Cell name #{arg2} not found in input layout") - end - @def_source = make_source(layout, cell) - end - - elsif arg.is_a?(RBA::Layout) - - cell = arg2 - if cell.is_a?(String) - cell = arg.cell(cell) - cell || raise("Cell name #{cell} not found in input layout") - elsif !cell.is_a?(RBA::Cell) - raise("Second argument of 'source' must be a string or RBA::Cell object") - end - @def_source = make_source(arg, cell) - - elsif arg.is_a?(RBA::Cell) - @def_source = make_source(arg.layout, arg) - else - raise("Invalid argument for 'source' method") - end - - else - @def_source || @def_layout || raise("No layout loaded - no default layout. Use 'layout' or 'source' to explicitly specify a layout.") - @def_source ||= make_source(@def_layout, @def_cell) - end - - # make default input also default output if none is set yet. - @def_layout ||= @def_source.layout - @def_cell ||= @def_source.cell_obj - - # use the DBU of the new input as DBU reference - @dbu_read || use_dbu(@def_source.layout.dbu) - - @def_source - - end - - # %DRC% - # @name layout - # @brief Specifies an additional layout for the input source. - # @synopsis layout - # @synopsis layout(what) - # This function can be used to specify a new layout for input. - # It returns an Source object representing that layout. The "input" method - # of that object can be used to get input layers for that layout. - # - # "what" specifies what input to use. "what" be either - # - # @ul - # @li A string "\@n" specifying input from a cellview in the current view @/li - # @li A layout filename plus an optional cell name @/li - # @li A RBA::Layout object @/li - # @li A RBA::Cell object @/li - # @/ul - # - # Without any arguments the default layout is returned. - # - # If a file name is given, a cell name can be specified as the second argument. - # If not, the top cell is taken which must be unique in that case. - # - # Having specified a layout for input enables to use the input method - # for getting input: - # - # @code - # # XOR between layers 1 or the default input and "second_layout.gds": - # l2 = layout("second_layout.gds") - # (input(1, 0) ^ l2.input(1, 0)).output(100, 0) - # @/code - # - # For further methods on the source object see \Source. - - def layout(arg = nil, arg2 = nil) - - if arg - - if arg.is_a?(String) - - if arg =~ /^@(\d+)/ - n = $1.to_i - 1 - view = RBA::LayoutView::current - view || raise("No view open") - (n >= 0 && view.cellviews > n) || raise("Invalid layout index @#{n + 1}") - cv = view.cellview(n) - cv.is_valid? || raise("Invalid layout @#{n + 1}") - return make_source(cv.layout, cv.cell) - else - layout = RBA::Layout::new - info("Reading #{arg} ..") - layout.read(arg) - cell = nil - if arg2 - arg2.is_a?(String) || raise("Second argument of 'source' must be a string") - cell = layout.cell(arg2) - cell || raise("Cell name #{arg2} not found in input layout") - end - return make_source(layout, cell) - end - - elsif arg.is_a?(RBA::Layout) - return make_source(layout) - elsif arg.is_a?(RBA::Cell) - return make_source(arg.layout, arg) - else - raise("Invalid argument for 'layout' method") - end - - else - @def_source || @def_layout || raise("No layout loaded - no default layout. Use 'layout' or 'source' to explicitly specify a layout.") - @def_source ||= make_source(@def_layout, @def_cell) - @def_source - end - - end - - # %DRC% - # @name report - # @brief Specifies a report database for output - # @synopsis report(description [, filename [, cellname ] ]) - # After specifying a report database for output, \output method calls are redirected to - # the report database. The format of the \output calls changes and a category name plus - # description can be specified rather than a layer/datatype number of layer name. - # See the description of the output method for details. - # - # If a filename is given, the report database will be written to the specified file name. - # Otherwise it will be shown but not written. - # - # If external input is specified with \source, - # "report" must be called after "source". - # - # The cellname specifies the top cell used for the report file. - # By default this is the cell name of the default source. If there - # is no source layout you'll need to give the cell name in the - # third parameter. - - def report(description, filename = nil, cellname = nil) - - @output_rdb_file = filename - - name = filename && File::basename(filename) - name ||= "DRC" - - lv = RBA::LayoutView::current - if lv - @output_rdb_index = lv.create_rdb(name) - @output_rdb = lv.rdb(@output_rdb_index) - else - @output_rdb = RBA::ReportDatabase::new(name) - end - - @output_layout = nil - @output_cell = nil - @output_layout_file = nil - - cn = nil - cn ||= @def_cell && @def_cell.name - cn ||= source && source.cell_name - cn ||= cellname - - cn || raise("No cell name specified - either the source was not specified before 'report' or there is no default source. In the latter case, specify a cell name as the third parameter of 'report'") - - @output_rdb_cell = @output_rdb.create_cell(cn) - @output_rdb.generator = $0 - @output_rdb.top_cell_name = cn - @output_rdb.description = description - - end - - # %DRC% - # @name output_cell - # @brief Specifies a target cell, but does not change the target layout - # @synopsis output_cell(cellname) - # This method switches output to the specified cell, but does not - # change the target layout nor does it switch the output channel to - # layout if is report database. - - def output_cell(cellname) - - # finish what we got so far - _flush - - if @output_rdb - - cell = nil - @output_rdb.each_cell do |c| - if c.name == cellname - cell = c - end - end - - cell ||= @output_rdb.create_cell(cellname) - @output_rdb_cell = cell - - else - - @output_layout ||= @def_layout - if @output_layout - @output_cell = @output_layout.cell(cellname.to_s) || @output_layout.create_cell(cellname.to_s) - end - - end - - end - - # %DRC% - # @name target - # @brief Specify the target layout - # @synopsis target(what [, cellname]) - # This function can be used to specify a target layout for output. - # Subsequent calls of "output" will send their results to that target - # layout. Using "target" will disable output to a report database. - # If any target was specified before, that target will be closed and - # a new target will be set up. - # - # "what" specifies what input to use. "what" be either - # - # @ul - # @li A string "\@n" specifying output to a layout in the current panel @/li - # @li A layout filename @/li - # @li A RBA::Layout object @/li - # @li A RBA::Cell object @/li - # @/ul - # - # Except if the argument is a RBA::Cell object, a cellname can be specified - # stating the cell name under which the results are saved. If no cellname is - # specified, either the current cell or "TOP" is used. - # - - def target(arg, cellname = nil) - - # finish what we got so far - _finish(false) - - if arg.is_a?(String) - - if arg =~ /^@(\d+)/ - n = $1.to_i - 1 - view = RBA::LayoutView::current - view || raise("No view open") - (n >= 0 && view.cellviews > n) || raise("Invalid layout index @#{n + 1}") - cv = view.cellview(n) - cv.is_valid? || raise("Invalid layout @#{n + 1}") - @output_layout = cv.layout - @output_cell = cellname ? (@output_layout.cell(cellname.to_s) || @output_layout.create_cell(cellname.to_s)) : cv.cell - @output_layout_file = nil - else - @output_layout = RBA::Layout::new - @output_cell = cellname && @output_layout.create_cell(cellname.to_s) - @output_layout_file = arg - end - - elsif arg.is_a?(RBA::Layout) - - @output_layout = arg - @output_cell = cellname && (@output_layout.cell(cellname.to_s) || @output_layout.create_cell(cellname.to_s)) - @output_layout_file = nil - - elsif arg.is_a?(RBA::Cell) - - @output_layout = arg.layout - @output_cell = arg - @output_layout_file = nil - - else - raise("Invalid argument for 'target' method") - end - - end - - # %DRC% - # @name box - # @brief Creates a box object - # @synopsis box(...) - # This function creates a box object. The arguments are the same than for the - # RBA::DBox constructors. - - def box(*args) - RBA::DBox::new(*args) - end - - # %DRC% - # @name path - # @brief Creates a path object - # @synopsis path(...) - # This function creates a path object. The arguments are the same than for the - # RBA::DPath constructors. - - def path(*args) - RBA::DPath::new(*args) - end - - # %DRC% - # @name polygon - # @brief Creates a polygon object - # @synopsis polygon(...) - # This function creates a polygon object. The arguments are the same than for the - # RBA::DPolygon constructors. - - def polygon(*args) - RBA::DPolygon::new(*args) - end - - # %DRC% - # @name p - # @brief Creates a point object - # @synopsis p(x, y) - # A point is not a valid object by itself, but it is useful for creating - # paths for polygons: - # - # @code - # x = polygon_layer - # x.insert(polygon([ p(0, 0), p(16.0, 0), p(8.0, 8.0) ])) - # @/code - - def p(x, y) - RBA::DPoint::new(x, y) - end - - # %DRC% - # @name edge - # @brief Creates an edge object - # @synopsis edge(...) - # This function creates an edge object. The arguments are the same than for the - # RBA::DEdge constructors. - - def edge(*args) - RBA::DEdge::new(*args) - end - - # %DRC% - # @name extent - # @brief Creates a new layer with the bounding box of the default source - # @synopsis extent - # See \Source#extent for a description of that function. - - def extent - layout.extent - end - - # %DRC% - # @name input - # @brief Fetches the shapes from the specified input from the default source - # @synopsis input(args) - # See \Source#input for a description of that function. This method will fetch - # polygons and labels. See \polygons and \labels for more specific versions of - # this method. - - def input(*args) - layout.input(*args) - end - - # %DRC% - # @name polygons - # @brief Fetches the polygons (or shapes that can be converted to polygons) from the specified input from the default source - # @synopsis polygons(args) - # See \Source#polygons for a description of that function. - - def polygons(*args) - layout.polygons(*args) - end - - # %DRC% - # @name labels - # @brief Gets the labels (text) from an original layer - # @synopsis labels - # See \Source#labels for a description of that function. - - def labels(*args) - layout.labels(*args) - end - - # %DRC% - # @name output - # @brief Outputs a layer to the report database or output layout - # @synopsis output(layer, args) - # This function is equivalent to "layer.output(args)". See \Layer#output for details about this function. - - def output(layer, *args) - layer.output(*args) - end - - # %DRC% - # @name layers - # @brief Gets the layers contained in the default source - # @synopsis layers - # See \Source#layers for a description of that function. - - def layers - layout.layers - end - - # %DRC% - # @name cell - # @brief Selects a cell for input on the default source - # @synopsis cell(args) - # See \Source#cell for a description of that function. - # In addition to the functionality described there, the global function will also send the output - # to the specified cell. - # - # The following code will select cell "MACRO" from the input layout: - # - # @code - # cell("MACRO") - # # shapes now will be taken from cell "MACRO" - # l1 = input(1, 0) - # @/code - - def cell(*args) - @def_source = layout.cell(*args) - output_cell(*args) - nil - end - - # %DRC% - # @name select - # @brief Specifies cell filters on the default source - # @synopsis select(args) - # See \Source#select for a description of that function. - - def select(*args) - @def_source = layout.select(*args) - nil - end - - # %DRC% - # @name clip - # @brief Specifies clipped input on the default source - # @synopsis clip(args) - # See \Source#clip for a description of that function. - # - # The following code will select shapes within a 500x600 micron rectangle (lower left corner at 0,0) - # from the input layout. The shapes will be clipped to that rectangle: - # - # @code - # clip(0.mm, 0.mm, 0.5.mm, 0.6.mm) - # # shapes now will be taken from the given rectangle and clipped to it - # l1 = input(1, 0) - # @/code - - def clip(*args) - @def_source = layout.clip(*args) - nil - end - - # make some DRCLayer methods available as functions - # for the engine - %w(join and or xor not - in touching overlapping inside outside interacting - select_touching select_overlapping select_inside select_outside select_interacting - merge merged rectangles rectilinear non_rectangles non_rectilinear - with_area with_perimeter with_angle with_length with_bbox_width with_bbox_area with_bbox_height with_bbox_min with_bbox_max - without_area without_perimeter without_length without_angle without_bbox_width without_bbox_area without_bbox_height without_bbox_min without_bbox_max - bbox - area length perimeter - is_box? is_empty? is_merged? is_clean? is_raw? polygons? edges? edge_pairs? - strict non_strict is_strict? - centers end_segments start_segments - extended extended_in extended_out - extents hulls holes - scaled scale rotated rotate - move moved transform transformed - width space notch isolated overlap - size sized - rounded_corners odd_polygons).each do |f| - eval <<"CODE" - def #{f}(*args) - obj = args.shift - obj.#{f}(*args) - end -CODE - end - - # %DRC% - # @name netter - # @brief Creates a new netter object - # @synopsis netter - # See \Netter for more details - - def netter - DRC::DRCNetter::new - end - - # %DRC% - # @name connect - # @brief Specifies a connection between two layers - # @synopsis connect(a, b) - # See \Netter#connect for a description of that function. - - # %DRC% - # @name connect_global - # @brief Specifies a connection to a global net - # @synopsis connect_global(l, name) - # See \Netter#connect_global for a description of that function. - - # %DRC% - # @name clear_connections - # @brief Clears all connections stored so far - # @synopsis clear_connections - # See \Netter#clear_connections for a description of that function - - # %DRC% - # @name join_nets - # @brief Specifies a label pattern for implicit net connections - # @synopsis join_nets(label_pattern) - # See \Netter#join_nets for a description of that function - - # %DRC% - # @name antenna_check - # @brief Performs an antenna check - # @synopsis antenna_check(gate, metal, ratio, [ diode_specs ... ]) - # See \Netter#antenna_check for a description of that function - - # %DRC% - # @name l2n_data - # @brief Gets the internal RBA::LayoutToNetlist object for the default \Netter - # @synopsis l2n_data - # See \Netter#l2n_data for a description of that function - - # %DRC% - # @name extract_devices - # @brief Extracts devices for a given device extractor and device layer selection - # @synopsis extract_devices(extractor, layer_hash) - # See \Netter#extract_devices for a description of that function - - %w(connect connect_global clear_connections join_nets antenna_check l2n_data extract_devices).each do |f| - eval <<"CODE" - def #{f}(*args) - _netter.#{f}(*args) - end -CODE - end - - def src_line - cc = caller.find do |c| - c !~ /drc.lym:/ && c !~ /\(eval\)/ - end - if cc =~ /(.*)\s*:\s*(\d+)\s*:\s*in.*$/ - return File::basename($1) + ":" + $2 - else - return cc - end - end - - def run_timed(desc, obj) - - info(desc) - - # enable progress - if obj.is_a?(RBA::Region) || obj.is_a?(RBA::Edges) || obj.is_a?(RBA::EdgePairs) - obj.enable_progress(desc) - end - - t = RBA::Timer::new - t.start - GC.start # force a garbage collection before the operation to free unused memory - res = yield - t.stop - - info("Elapsed: #{'%.3f'%(t.sys+t.user)}s") - - # disable progress - if obj.is_a?(RBA::Region) || obj.is_a?(RBA::Edges) || obj.is_a?(RBA::EdgePairs) - obj.disable_progress - end - - res - - end - - def _cmd(obj, method, *args) - run_timed("\"#{method}\" in: #{src_line}", obj) do - obj.send(method, *args) - end - end - - def _tcmd(obj, border, result_cls, method, *args) - - if @tx && @ty - - tp = RBA::TilingProcessor::new - tp.dbu = self.dbu - tp.scale_to_dbu = false - tp.tile_size(@tx, @ty) - bx = [ @bx || 0.0, border * self.dbu ].max - by = [ @by || 0.0, border * self.dbu ].max - tp.tile_border(bx, by) - - res = result_cls.new - tp.output("res", res) - tp.input("self", obj) - tp.threads = (@tt || 1) - args.each_with_index do |a,i| - if a.is_a?(RBA::Edges) || a.is_a?(RBA::Region) - tp.input("a#{i}", a) - else - tp.var("a#{i}", a) - end - end - av = args.size.times.collect { |i| "a#{i}" }.join(", ") - tp.queue("_output(res, self.#{method}(#{av}))") - run_timed("\"#{method}\" in: #{src_line}", obj) do - tp.execute("Tiled \"#{method}\" in: #{src_line}") - end - - else - res = nil - run_timed("\"#{method}\" in: #{src_line}", obj) do - res = obj.send(method, *args) - end - end - - # enable progress - if obj.is_a?(RBA::Region) - obj.disable_progress - end - - res - - end - - # used for area and perimeter only - def _tdcmd(obj, border, method) - - if @tx && @ty - - tp = RBA::TilingProcessor::new - tp.tile_size(@tx, @ty) - tp.tile_border(border * self.dbu, border * self.dbu) - - res = RBA::Value::new - res.value = 0.0 - tp.output("res", res) - tp.input("self", obj) - tp.threads = (@tt || 1) - tp.queue("_output(res, _tile ? self.#{method}(_tile.bbox) : self.#{method})") - run_timed("\"#{method}\" in: #{src_line}", obj) do - tp.execute("Tiled \"#{method}\" in: #{src_line}") - end - - res = res.value - - else - res = nil - run_timed("\"#{method}\" in: #{src_line}", obj) do - res = obj.send(method) - end - end - - # enable progress - if obj.is_a?(RBA::Region) - obj.disable_progress - end - - res - - end - - def _rcmd(obj, method, *args) - run_timed("\"#{method}\" in: #{src_line}", obj) do - RBA::Region::new(obj.send(method, *args)) - end - end - - def _vcmd(obj, method, *args) - run_timed("\"#{method}\" in: #{src_line}", obj) do - obj.send(method, *args) - end - end - - def _start - - # clearing the selection avoids some nasty problems - view = RBA::LayoutView::current - view && view.cancel - - end - - def _flush - - # clean up resources (i.e. temp layers) - @layout_sources.each do |n,l| - l.finish - end - - end - - def _finish(final = true) - - _flush - - view = RBA::LayoutView::current - - # save the report database if requested - if @output_rdb_file - info("Writing #{@output_rdb_file} ..") - @output_rdb.save(@output_rdb_file) - end - if @output_rdb && final && view - view.show_rdb(@output_rdb_index, view.active_cellview_index) - end - - # save the output file if requested - if @output_layout && @output_layout_file - opt = RBA::SaveLayoutOptions::new - gzip = opt.set_format_from_filename(@output_layout_file) - info("Writing #{@output_layout_file} ..") - @output_layout.write(@output_layout_file, gzip, opt) - end - - # create the new layers as visual layers if necessary - if view - - output = @output_layout || @def_layout - cv_index = nil - view.cellviews.times do |cvi| - view.cellview(cvi).layout == output && cv_index = cvi - end - if cv_index - - view = RBA::LayoutView::current - - # clear selection - view.cancel - - # create layer views for those layers which are not present yet - - present_layers = {} - l = view.begin_layers - while !l.at_end? - if l.current.cellview == cv_index - present_layers[l.current.layer_index] = true - end - l.next - end - - @output_layers.each do |li| - if !present_layers[li] - info = @def_layout.get_info(li) - lp = RBA::LayerProperties::new - lp.source_layer = info.layer - lp.source_datatype = info.datatype - lp.source_name = info.name - lp.source_cellview = cv_index - view.init_layer_properties(lp) - view.insert_layer(view.end_layers, lp) - end - end - - view.update_content - - end - - end - - @output_layout = nil - @output_layout_file = nil - @output_cell = nil - @output_rdb_file = nil - @output_rdb_cell = nil - @output_rdb = nil - @output_rdb_index = nil - - # clean up temp data - @dss && @dss._destroy - @netter && @netter._finish - - if final && @log_file - @log_file.close - @log_file = nil - end - - end - - def _dss - @dss - end - - def _netter - @netter ||= DRC::DRCNetter::new(self) - end - - private - - def _make_string(v) - if v.class.respond_to?(:from_s) - v.class.to_s + "::from_s(" + v.to_s.inspect + ")" - else - v.inspect - end - end - - def _input(layout, cell_index, layers, sel, box, clip, overlapping, shape_flags) - - if layers.empty? && ! @deep - - r = RBA::Region::new - - else - - if box - iter = RBA::RecursiveShapeIterator::new(layout, layout.cell(cell_index), layers, box, overlapping) - else - iter = RBA::RecursiveShapeIterator::new(layout, layout.cell(cell_index), layers) - end - iter.shape_flags = shape_flags - - sel.each do |s| - if s == "-" - iter.unselect_cells(cell.cell_index) - elsif s == "-*" - iter.unselect_all_cells - elsif s == "+*" - iter.select_all_cells - elsif s =~ /^-(.*)/ - iter.unselect_cells($1) - elsif s =~ /^\+(.*)/ - iter.select_cells($1) - else - iter.select_cells(s) - end - end - - sf = layout.dbu / self.dbu - if @deep - @dss ||= RBA::DeepShapeStore::new - # TODO: align with LayoutToNetlist by using a "master" L2N - # object which keeps the DSS. - @dss.text_property_name = "LABEL" - @dss.text_enlargement = 1 - r = RBA::Region::new(iter, @dss, RBA::ICplxTrans::new(sf.to_f)) - else - r = RBA::Region::new(iter, RBA::ICplxTrans::new(sf.to_f)) - end - - # clip if a box is specified - if box && clip - r &= RBA::Region::new(box) - end - - end - - r - - end - - def _layout(name) - @layout_sources[name].layout - end - - def _output(data, *args) - - if @output_rdb - - if args.size < 1 - raise("Invalid number of arguments for 'output' on report - category name and optional description expected") - end - - cat = @output_rdb.create_category(args[0].to_s) - args[1] && cat.description = args[1] - - cat.scan_collection(@output_rdb_cell, RBA::CplxTrans::new(self.dbu), data) - - else - - if @output_layout - output = @output_layout - if @output_cell - output_cell = @output_cell - elsif @def_cell - output_cell = @output_layout.cell(@def_cell.name) || @output_layout.create_cell(@def_cell.name) - end - output_cell || raise("No output cell specified (see 'target' instruction)") - else - output = @def_layout - output || raise("No output layout specified") - output_cell = @output_cell || @def_cell - output_cell || raise("No output cell specified") - end - - info = nil - if args.size == 1 - if args[0].is_a?(1.class) - info = RBA::LayerInfo::new(args[0], 0) - elsif args[0].is_a?(RBA::LayerInfo) - info = args[0] - elsif args[0].is_a?(String) - info = RBA::LayerInfo::from_string(args[0]) - else - raise("Invalid parameter type for 'output' - must be string or number") - end - elsif args.size == 2 || args.size == 3 - info = RBA::LayerInfo::new(*args) - else - raise("Invalid number of arguments for 'output' - one, two or three arguments expected") - end - li = output.find_layer(info) - if !li - li = output.insert_layer(info) - end - - # make sure the output has the right database unit - output.dbu = self.dbu - - tmp = li - - begin - - if !@used_output_layers[li] - @output_layers.push(li) - # Note: to avoid issues with output onto the input layer, we - # output to a temp layer and later swap both. The simple implementation - # did a clear here and the effect of that was that the data potentially - # got invalidated. - tmp = output.insert_layer(RBA::LayerInfo::new) - @used_output_layers[li] = true - end - - # insert the data into the output layer - if data.is_a?(RBA::EdgePairs) - data.insert_into_as_polygons(output, output_cell.cell_index, tmp, 1) - else - data.insert_into(output, output_cell.cell_index, tmp) - end - - # make the temp layer the output layer - if tmp != li - output.swap_layers(tmp, li) - end - - ensure - # clean up the original layer if requested - if tmp != li - output.delete_layer(tmp) - end - end - - end - end - - def make_source(layout, cell = nil) - name = "layout" + @lnum.to_s - @lnum += 1 - @dbu ||= layout.dbu - src = DRCSource::new(self, layout, layout, cell || layout.top_cell) - @layout_sources[name] = src - src - end - - end end - - + diff --git a/src/drc/drc/built-in-macros/_drc_netter.rb b/src/drc/drc/built-in-macros/_drc_netter.rb new file mode 100644 index 000000000..eed364f48 --- /dev/null +++ b/src/drc/drc/built-in-macros/_drc_netter.rb @@ -0,0 +1,368 @@ +# $autorun-early + +module DRC + + # The netter object + + # %DRC% + # @scope + # @name Netter + # @brief DRC Reference: Netter object + # The Netter object provides services related to network extraction + # from a layout. The relevant methods of this object are available + # as global functions too where they act on a default incarnation + # of the netter. Usually it's not required to instantiate a Netter + # object, but it serves as a container for this functionality. + # + # An individual netter object can be created, if the netter results + # need to be kept for multiple extractions. If you really need + # a Netter object, use the global \netter function: + # + # @code + # # create a new Netter object: + # nx = netter + # nx.connect(poly, contact) + # ... + # @/code + # + # Network formation: + # + # A basic Service the Netter object provides is the formation of + # connected networks of conductive shapes. To do so, the Netter + # must be given a connection specification. This happens by calling + # "connect" with two polygon layers. The Netter will then regard all + # overlaps of shapes on these layers as connections between the + # respective materials. Networks are the basis for netlist extraction, + # network geometry deduction and the antenna check. + # + # Connections can be cleared with "clear_connections". If not, + # connections add atop of the already defined ones. Here is an + # example for the antenna check: + # + # @code + # # build connction of poly+gate to metal1 + # connect(gate, poly) + # connect(poly, contact) + # connect(contact, metal1) + # + # # runs an antenna check for metal1 with a ratio of 50 + # m1_antenna_errors = antenna_check(gate, metal1, 50.0) + # + # # add connections to metal2 + # connect(metal1, via1) + # connect(via1, metal2) + # + # # runs an antenna check for metal2 with a ratio of 70.0 + # m2_antenna_errors = antenna_check(gate, metal2, 70.0) + # + # # this will remove all connections made + # clear_connections + # ... + # @/code + # + # Further functionality of the Netter object: + # + # More methods will be added in the future to support network-related features. + + class DRCNetter + + def initialize(engine) + @engine = engine + clear_connections + end + + # %DRC% + # @name connect + # @brief Specifies a connection between two layers + # @synopsis connect(a, b) + # a and b must be polygon layers. After calling this function, the + # Netter regards all overlapping or touching shapes on these layers + # to form an electrical connection between the materials formed by + # these layers. This also implies intra-layer connections: shapes + # on these layers touching or overlapping other shapes on these + # layers will form bigger, electrically connected areas. + # + # Multiple connect calls must be made to form larger connectivity + # stacks across multiple layers. Such stacks may include forks and + # joins. + # + # Connections are accumulated. The connections defined so far + # can be cleared with \clear_connections. + + def connect(a, b) + a.is_a?(DRC::DRCLayer) || raise("First argument of Netter#connect must be a layer") + b.is_a?(DRC::DRCLayer) || raise("Second argument of Netter#connect must be a layer") + a.requires_region("Netter#connect (first argument)") + b.requires_region("Netter#connect (second argument)") + [ a, b ].each { |l| @layers[l.data.data_id] = l.data } + @connections << [ a, b ].collect { |l| l.data.data_id } + modified + end + + # %DRC% + # @name connect_global + # @brief Connects a layer with a global net + # @synopsis connect_global(l, name) + # Connects the shapes from the given layer l to a global net with the given name. + # Global nets are common to all cells. Global nets automatically connect to parent + # cells throughs implied pins. An example is the substrate (bulk) net which connects + # to shapes belonging to tie-down diodes. + + def connect_global(l, name) + l.is_a?(DRC::DRCLayer) || raise("Layer argument of Netter#connect_global must be a layer") + l.requires_region("Netter#connect_global (layer argument)") + @layers[l.data.data_id] = l.data + @global_connections << [ l.data.data_id, name.to_s ] + end + + # %DRC% + # @name extract_devices + # @brief Extracts devices based on the given extractor class, name and device layer selection + # @synopsis extract_devices(extractor, layer_hash) + # Runs the device extraction for given device extractor class. + # + # The device extractor is either an instance of one of the predefined extractor + # classes (e.g. RBA::DeviceExtractorMOS4Transistor) or a custom class. It provides the + # algorithms for deriving the device parameters from the device geometry. It needs + # several device recognition layers which are passed in the layer hash. + # + # Each device class (e.g. n-MOS/p-MOS or high Vt/low Vt) needs it's own instance + # of device extractor. The device extractor beside the algorithm and specific + # extraction settings defines the name of the device to be built. + # + # The layer hash is a map of device type specific functional names (key) and + # polygon layers (value). Here is an example: + # + # @code + # deep + # + # nwell = input(1, 0) + # active = input(2, 0) + # poly = input(3, 0) + # bulk = make_layer # renders an empty layer used for putting the terminals on + # + # nactive = active - nwell # active area of NMOS + # nsd = nactive - poly # source/drain area + # gate = nactive & poly # gate area + # + # mos4_ex = RBA::DeviceExtractorMOS4Transistor::new("NMOS4") + # extract_devices(mos4_ex, { :SD => nsd, :G => gate, :P => poly, :W => bulk }) + # @/code + + def extract_devices(devex, layer_selection) + + devex.is_a?(RBA::DeviceExtractorBase) || raise("First argument of Netter#extract_devices must be a device extractor instance") + layer_selection.is_a?(Hash) || raise("Second argument of Netter#extract_devices must be a hash") + + ls = {} + layer_selection.each do |n,l| + l.requires_region("Netter#extract_devices (#{n} layer)") + @layers[l.data.data_id] = l.data + ls[n.to_s] = l.data + end + + @devices_to_extract << [ devex, ls ] + modified + + end + + # %DRC% + # @name clear_connections + # @brief Clears all connections stored so far + # @synopsis clear_connections + # See \connect for more details. + + def clear_connections + @devices_to_extract = [] + @connections = [] + @global_connections = [] + @layers = {} + @join_nets = "" + modified + end + + # %DRC% + # @name join_nets + # @brief Specifies a search pattern for labels which create implicit net connections + # @synopsis join_nets(label_pattern) + # Use this method to supply a glob pattern for labels which create implicit net connections + # on the top level circuit. This feature is useful to connect identically labelled nets + # while a component isn't integrated yet. If the component is integrated, net may be connected + # on a higher hierarchy level - e.g. by a power mesh. Inside the component this net consists + # of individual islands. To properly perform netlist extraction and comparison, these islands + # need to be connected even though there isn't a physical connection. "join_nets" can + # achive this if these islands are labelled with the same text on the top level of the + # component. + # + # Glob pattern are used which resemble shell file pattern: "*" is for all labels, "VDD" + # for all "VDD" labels (pattern act case sensitive). "VDD*" is for all labels beginning + # with "VDD" (still different labels will be connected to different nets!). "{VDD,VSS}" + # is either "VDD" or "VSS". + # + # The search pattern is applied on the next net extraction. The search pattern is cleared + # on "clear_connections". + + def join_nets(arg) + @join_nets = arg + modified + end + + # %DRC% + # @brief Performs an antenna check + # @name antenna_check + # @synopsis antenna_check(gate, metal, ratio, [ diode_specs ... ]) + # + # The antenna check is used to avoid plasma induced damage. Physically, + # the damage happes if during the manufacturing of a metal layer with + # plasma etching charge accumulates on the metal islands. On reaching a + # certain threshold, this charge may discarge over gate oxide attached of + # devices attached to such metal areas hence damaging it. + # + # Antenna checks are performed by collecting all connected nets up to + # a certain metal layer and then computing the area of all metal shapes + # and all connected gates of a certain kind (e.g. thin and thick oxide gates). + # The ratio of metal area divided by the gate area must not exceed a certain + # threshold. + # + # A simple antenna check is this: + # + # @code + # poly = ... # poly layer + # diff = ... # diffusion layer + # contact = ... # contact layer + # metal1 = ... # metal layer + # + # # compute gate area + # gate = poly & diff + # + # # note that gate and poly have to be included - gate is + # # a subset of poly, but forms the sensitive area + # connect(gate, poly) + # connect(poly, contact) + # connect(contact, metal1) + # errors = antenna_check(gate, metal1, 50.0) + # @/code + # + # Plasma induced damage can be rectified by including diodes + # which create a safe current path for discharging the metal + # islands. Such diodes can be identified with a recognition layer + # (usually the diffusion area of a certain kind). You can include + # such diode recognition layers in the antenna check. If a connection + # is detected to a diode, the respective network is skipped: + # + # @code + # ... + # diode = ... # diode recognition layer + # + # connect(diode, contact) + # errors = antenna_check(gate, metal1, 50.0, diode) + # @/code + # + # You can also make diode connections decreases the + # sensitivity of the antenna check depending on the size + # of the diode. The following specification makes + # diode connections increase the ratio threshold by + # 10 per square micrometer of diode area: + # + # @code + # ... + # diode = ... # diode recognition layer + # + # connect(diode, contact) + # # each square micrometer of diode area connected to a network + # # will add 10 to the ratio: + # errors = antenna_check(gate, metal1, 50.0, [ diode, 10.0 ]) + # @/code + # + # Multiple diode specifications are allowed. Just add them + # to the antenna_check call. + # + # The error shapes produced by the antenna check are copies + # of the metal shapes on the metal layers of each network + # violating the antenna rule. + + def antenna_check(gate, metal, ratio, *diodes) + + gate.is_a?(DRC::DRCLayer) || raise("gate argument of Netter#antenna_check must be a layer") + gate.requires_region("Netter#antenna_check (gate argument)") + + metal.is_a?(DRC::DRCLayer) || raise("metal argument of Netter#antenna_check must be a layer") + metal.requires_region("Netter#antenna_check (metal argument)") + + if !ratio.is_a?(1.class) && !ratio.is_a?(Float) + raise("ratio argument Netter#antenna_check is not a number") + end + + dl = diodes.collect do |d| + if d.is_a?(Array) + d.size == 2 || raise("diode specification pair expects two elements") + d[0].requires_region("Netter#antenna_check (diode layer)") + [ d[0].data, d[1].to_f ] + else + d.requires_region("Netter#antenna_check (diode layer)") + [ d.data, 0.0 ] + end + end + + @l2n || make_l2n + DRC::DRCLayer::new(@engine, @engine._cmd(@l2n, :antenna_check, gate.data, metal.data, ratio, dl)) + + end + + # %DRC% + # @name l2n_data + # @brief Gets the internal RBA::LayoutToNetlist object + # @synopsis l2n_data + # The RBA::LayoutToNetlist object provides access to the internal details of + # the netter object. + + def l2n_data + @l2n || make_l2n + @l2n + end + + def _finish + clear_connections + # cleans up the L2N object + modified + end + + private + + def modified + @l2n && @l2n._destroy + @l2n = nil + end + + def make_l2n + + if @engine._dss + # TODO: check whether all layers are deep and come from the dss and layout index, + # then use this layout index. This will remove the need for this check: + @engine._dss.is_singular? || raise("The DRC script features more than one or no layout source - network extraction cannot be performed in such configurations") + @l2n = RBA::LayoutToNetlist::new(@engine._dss) + else + layout = @engine.source.layout + @l2n = RBA::LayoutToNetlist::new(layout.top_cell.name, layout.dbu) + end + + @layers.each { |id,l| @l2n.register(l, "l" + id.to_s) } + + @devices_to_extract.each do |devex,ls| + @engine._cmd(@l2n, :extract_devices, devex, ls) + end + + @layers.each { |id,l| @l2n.connect(l) } + @connections.each { |a,b| @l2n.connect(@layers[a], @layers[b]) } + @global_connections.each { |l,n| @l2n.connect_global(@layers[l], n) } + + # run extraction in a timed environment + @engine._cmd(@l2n, :extract_netlist, @join_nets) + @l2n + + end + + end + +end + diff --git a/src/drc/drc/built-in-macros/_drc_patch.rb b/src/drc/drc/built-in-macros/_drc_patch.rb new file mode 100644 index 000000000..d51ee850e --- /dev/null +++ b/src/drc/drc/built-in-macros/_drc_patch.rb @@ -0,0 +1,116 @@ +# $autorun-early + +# Extend the Float class by methods which convert +# values with units, i.e. 1.3nm gives 0.0013 + +1.0.class.class_eval do + + class << self + @dbu = nil + def _dbu=(dbu) + @dbu = dbu + end + def _dbu + @dbu + end + end + + def um + self + end + def micron + self + end + def degree + self + end + def nm + self*0.001 + end + def mm + self*1000.0 + end + def m + self*1000000.0 + end + def nm2 + self*1e-6 + end + def um2 + self + end + def micron2 + self + end + def mm2 + self*1.0e6 + end + def m2 + self*1.0e12 + end + def dbu + self.class._dbu || raise("No layout loaded - cannot determine database unit") + self*self.class._dbu + end + +end + +# Extend the Fixnum class, so it is possible to +# convert a value to Float with a unit spec, i.e. +# 5.nm -> 0.005. A spec with ".dbu" gives the +# Fixnum value itself. This indicates a value in +# database units for most methods of the DRC +# framework. + +1.class.class_eval do + + class << self + @dbu = nil + def _dbu=(dbu) + @dbu = dbu + end + def _dbu + @dbu + end + end + + def um + self.to_f + end + def micron + self.to_f + end + def degree + self.to_f + end + def nm + self*0.001 + end + def mm + self*1000.0 + end + def m + self*1000000.0 + end + def nm2 + self*1.0e-6 + end + def um2 + self.to_f + end + def micron2 + self.to_f + end + def mm2 + self*1.0e6 + end + def m2 + self*1.0e12 + end + def dbu + self.class._dbu || raise("No layout loaded - cannot determine database unit") + self*self.class._dbu + end + +end + diff --git a/src/drc/drc/built-in-macros/_drc_source.rb b/src/drc/drc/built-in-macros/_drc_source.rb new file mode 100644 index 000000000..90973a7ce --- /dev/null +++ b/src/drc/drc/built-in-macros/_drc_source.rb @@ -0,0 +1,405 @@ +# $autorun-early + +module DRC + + # A layout source representative object. + # This object describes an input. It consists of a layout reference plus + # some attributes describing how input is to be gathered. + + # %DRC% + # @scope + # @name Source + # @brief DRC Reference: Source Object + # The layer object represents a collection of polygons, edges or edge pairs. + # A source specifies where to take layout from. That includes the actual layout, + # the top cell and options such as clip/query boxes, cell filters etc. + + class DRCSource + + def initialize(engine, layout, layout_var, cell) + @engine = engine + @layout = layout + @layout_var = layout_var + @cell = cell + @inside = nil + @box = nil + @layers = nil + @sel = [] + @clip = false + @overlapping = false + @tmp_layers = [] + end + + # %DRC% + # @name layout + # @brief Returns the RBA::Layout object associated with this source + # @synopsis layout + + def layout + @layout + end + + # %DRC% + # @name cell_name + # @brief Returns the name of the currently selected cell + # @synopsis cell_name + + def cell_name + @cell.name + end + + # %DRC% + # @name cell_obj + # @brief Returns the RBA::Cell object of the currently selected cell + # @synopsis cell_obj + + def cell_obj + @cell + end + + def finish + @tmp_layers.each do |li| + @layout.delete_layer(li) + end + end + + def set_box(method, *args) + box = nil + if args.size == 1 + box = args[0] + box.is_a?(RBA::DBox) || raise("'#{method}' method requires a box specification") + elsif args.size == 2 + (args[0].is_a?(RBA::DPoint) && args[1].is_a?(RBA::DPoint)) || raise("'#{method}' method requires a box specification with two points") + box = RBA::DBox::new(args[0], args[1]) + elsif args.size == 4 + box = RBA::DBox::new(*args) + else + raise("Invalid number of arguments for '#{method}' method") + end + @box = RBA::Box::from_dbox(box * (1.0 / @layout.dbu)) + self + end + + def inplace_clip(*args) + set_box("clip", *args) + @clip = true + @overlapping = true + end + + def inplace_touching(*args) + set_box("touching", *args) + @clip = false + @overlapping = false + end + + def inplace_overlapping(*args) + set_box("overlapping", *args) + @clip = false + @overlapping = true + end + + def inplace_cell(arg) + @cell = layout.cell(arg) + @cell ||= layout.create_cell(arg) + self + end + + def inplace_select(*args) + args.each do |a| + a.is_a?(String) || raise("Invalid arguments to 'select' method - must be strings") + @sel.push(a) + end + self + end + + # %DRC% + # @name select + # @brief Adds cell name expressions to the cell filters + # @synopsis source.select(filter1, filter2, ...) + # This method will construct a new source object with the given cell filters + # applied. + # Cell filters will enable or disable cells plus their subtree. + # Cells can be switched on and off, which makes the hierarchy traversal + # stop or begin delivering shapes at the given cell. The arguments of + # the select method form a sequence of enabling or disabling instructions + # using cell name pattern in the glob notation ("*" as the wildcard, like shell). + # Disabling instructions start with a "-", enabling instructions with a "+" or + # no specification. + # + # The following options are available: + # + # @ul + # @li @tt+@/tt @i name_filter @/i: Cells matching the name filter will be enabled @/li + # @li @i name_filter @/i: Same as "+name_filter" @/li + # @li @tt-@/tt @i name_filter @/i: Cells matching the name filter will be disabled @/li + # @/ul + # + # To disable the TOP cell but enabled a hypothetical cell B below the top cell, use that + # code: + # + # @code + # layout_with_selection = layout.select("-TOP", "+B") + # l1 = layout_with_selection.input(1, 0) + # ... + # @/code + # + # Please note that the sample above will deliver the children of "B" because there is + # nothing said about how to proceed with cells other than "TOP" or "B". + # The following code will just select "B" without it's children, because in the + # first "-*" selection, all cells including the children of "B" are disabled: + # + # @code + # layout_with_selection = layout.select("-*", "+B") + # l1 = layout_with_selection.input(1, 0) + # ... + # @/code + + # %DRC% + # @name cell + # @brief Specifies input from a specific cell + # @synopsis source.cell(name) + # This method will create a new source that delivers shapes from the + # specified cell. + + # %DRC% + # @name clip + # @brief Specifies clipped input + # @synopsis source.clip(box) + # @synopsis source.clip(p1, p2) + # @synopsis source.clip(l, b, r, t) + # Creates a source which represents a rectangular part of the + # original input. Three ways are provided to specify the rectangular + # region: a single RBA::DBox object (micron units), two RBA::DPoint + # objects (lower/left and upper/right coordinate in micron units) + # or four coordinates: left, bottom, right and top coordinate. + # + # This method will create a new source which delivers the shapes + # from that region clipped to the rectangle. A method doing the + # same but without clipping is \touching or \overlapping. + + # %DRC% + # @name touching + # @brief Specifies input selected from a region in touching mode + # @synopsis source.touching(box) + # @synopsis source.touching(p1, p2) + # @synopsis source.touching(l, b, r, t) + # Like \clip, this method will create a new source delivering shapes + # from a specified rectangular region. In contrast to clip, all shapes + # touching the region with their bounding boxes are delivered as a whole + # and are not clipped. Hence shapes may extent beyond the limits of + # the specified rectangle. + # + # \overlapping is a similar method which delivers shapes overlapping + # the search region with their bounding box (and not just touching) + + # %DRC% + # @name overlapping + # @brief Specifies input selected from a region in overlapping mode + # @synopsis source.overlapping(...) + # Like \clip, this method will create a new source delivering shapes + # from a specified rectangular region. In contrast to clip, all shapes + # overlapping the region with their bounding boxes are delivered as a whole + # and are not clipped. Hence shapes may extent beyond the limits of + # the specified rectangle. + # + # \touching is a similar method which delivers shapes touching + # the search region with their bounding box (without the requirement to overlap) + + # export inplace_* as * out-of-place + %w(select cell clip touching overlapping).each do |f| + eval <<"CODE" + def #{f}(*args) + s = self.dup + s.inplace_#{f}(*args) + s + end +CODE + end + + # %DRC% + # @name extent + # @brief Returns a layer with the bounding box of the selected layout + # @synopsis source.extent + # The extent function is useful to invert a layer: + # + # @code + # inverse_1 = extent.sized(100.0) - input(1, 0) + # @/code + + def extent + layer = input + if @box + layer.insert(RBA::DBox::from_ibox(@box) * @layout.dbu) + else + layer.insert(RBA::DBox::from_ibox(@cell.bbox) * @layout.dbu) + end + layer + end + + # %DRC% + # @name input + # @brief Specifies input from a source + # @synopsis source.input(layer) + # @synopsis source.input(layer, datatype) + # @synopsis source.input(layer_into) + # @synopsis source.input(filter, ...) + # Creates a layer with the shapes from the given layer of the source. + # The layer can be specified by layer and optionally datatype, by a RBA::LayerInfo + # object or by a sequence of filters. + # Filters are expressions describing ranges + # of layers and/or datatype numbers or layer names. Multiple filters + # can be given and all layers matching at least one of these filter + # expressions are joined to render the input layer for the DRC engine. + # + # Some filter expressions are: + # + # @ul + # @li @tt 1/0-255 @/tt: Datatypes 0 to 255 for layer 1 @/li + # @li @tt 1-10 @/tt: Layers 1 to 10, datatype 0 @/li + # @li @tt METAL @/tt: A layer named "METAL" @/li + # @li @tt METAL (17/0) @/tt: A layer named "METAL" or layer 17, datatype 0 (for GDS, which does + # not have names)@/li + # @/ul + # + # Layers created with "input" contain both texts and polygons. There is a subtle + # difference between flat and deep mode: in flat mode, texts are not visible in polygon + # operations. In deep mode, texts appear as small 2x2 DBU rectangles. In flat mode, + # some operations such as clipping are not fully supported for texts. Also, texts will + # vanish in most polygon operations such as booleans etc. + # + # Texts can later be selected on the layer returned by "input" with the \Layer#texts method. + # + # If you don't want to see texts, use \polygons to create an input layer with polygon data + # only. If you only want to see texts, use \labels to create an input layer with texts only. + # + # Use the global version of "input" without a source object to address the default source. + + def input(*args) + layers = parse_input_layers(*args) + DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SAll)) + end + + # %DRC% + # @name labels + # @brief Gets the labels (texts) from an input layer + # @synopsis source.labels(layer) + # @synopsis source.labels(layer, datatype) + # @synopsis source.labels(layer_into) + # @synopsis source.labels(filter, ...) + # + # Creates a layer with the labels from the given layer of the source. + # + # This method is identical to \input, but takes only texts from the given input + # layer. + # + # Use the global version of "labels" without a source object to address the default source. + + def labels(*args) + layers = parse_input_layers(*args) + DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::STexts)) + end + + # %DRC% + # @name polygons + # @brief Gets the polygon shapes (or shapes that can be converted polygons) from an input layer + # @synopsis source.polygons(layer) + # @synopsis source.polygons(layer, datatype) + # @synopsis source.polygons(layer_into) + # @synopsis source.polygons(filter, ...) + # + # Creates a layer with the polygon shapes from the given layer of the source. + # With "polygon shapes" we mean all kind of shapes that can be converted to polygons. + # Those are boxes, paths and real polygons. + # + # This method is identical to \input with respect to the options supported. + # + # Use the global version of "polygons" without a source object to address the default source. + + def polygons(*args) + layers = parse_input_layers(*args) + DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SBoxes | RBA::Shapes::SPaths | RBA::Shapes::SPolygons | RBA::Shapes::SEdgePairs)) + end + + # %DRC% + # @name make_layer + # @brief Creates an empty polygon layer based on the hierarchy of the layout + # @synopsis make_layer + # This method delivers a new empty original layer. + + def make_layer + layers = [] + DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SAll)) + end + + # %DRC% + # @name layers + # @brief Gets the layers the source contains + # @synopsis source.layers + # Delivers a list of RBA::LayerInfo objects representing the layers + # inside the source. + # + # One application is to read all layers from a source. In the following + # example, the "and" operation is used to perform a clip with the given + # rectangle. Note that this solution is not efficient - it's provided + # as an example only: + # + # @code + # output_cell("Clipped") + # + # clip_box = polygon_layer + # clip_box.insert(box(0.um, -4.um, 4.um, 0.um)) + # + # layers.each { |l| (input(l) & clip_box).output(l) } + # @/code + + def layers + @layout.layer_indices.collect { |li| @layout.get_info(li) } + end + + private + + def parse_input_layers(*args) + + layers = [] + + if args.size == 0 + + li = @layout.insert_layer(RBA::LayerInfo::new) + li && layers.push(li) + li && @tmp_layers.push(li) + + elsif (args.size == 1 && args[0].is_a?(RBA::LayerInfo)) + + li = @layout.find_layer(args[0]) + li && layers.push(li) + + elsif (args.size == 1 || args.size == 2) && args[0].is_a?(1.class) + + li = @layout.find_layer(args[0], args[1] || 0) + li && layers.push(li) + + else + + args.each do |a| + if a.is_a?(String) + # use the LayerMap class to fetch the matching layers + lm = RBA::LayerMap::new + lm.map(a, 0) + @layout.layer_indices.each do |li| + if lm.is_mapped?(@layout.get_info(li)) + layers.push(li) + end + end + end + end + + end + + layers + + end + + end + +end + diff --git a/src/drc/drc/built-in-macros/_drc_tags.rb b/src/drc/drc/built-in-macros/_drc_tags.rb new file mode 100644 index 000000000..510918dd0 --- /dev/null +++ b/src/drc/drc/built-in-macros/_drc_tags.rb @@ -0,0 +1,116 @@ +# $autorun-early + +module DRC + + # A wrapper for a named value which is stored in + # a variable for delayed execution + class DRCVar + def initialize(name) + @name = name + end + def inspect + @name + end + def to_s + @name + end + end + + # A wrapper for the sizing mode value + class DRCSizingMode + attr_accessor :value + def initialize(v) + self.value = v + end + end + + # A wrapper for the join flag for extended + class DRCJoinFlag + attr_accessor :value + def initialize(v) + self.value = v + end + end + + # A wrapper for the angle limit + # The purpose of this wrapper is to identify the + # angle limit specification + class DRCAngleLimit + attr_accessor :value + def initialize(v) + self.value = v + end + end + + # A wrapper for a metrics constant + # The purpose of this wrapper is to identify the + # metrics constant by the class. + class DRCMetrics + attr_accessor :value + def initialize(v) + self.value = v + end + end + + # A wrapper for the "whole edges" flag for + # the DRC functions. The purpose of this class + # is to identify the value by the class. + class DRCWholeEdges + attr_accessor :value + def initialize(v) + self.value = v + end + end + + # A wrapper for the "as_dots" or "as_boxes" flag for + # some DRC functions. The purpose of this class + # is to identify the value by the class. + class DRCAsDots + attr_accessor :value + def initialize(v) + self.value = v + end + end + + # A wrapper for a glob-pattern style text selection for + # some DRC functions. The purpose of this class + # is to identify the value by the class. + class DRCPattern + attr_accessor :as_pattern + attr_accessor :pattern + def initialize(f, p) + self.as_pattern = f + self.pattern = p + end + end + + # A wrapper for a pair of limit values + # This class is used to identify projection limits for DRC + # functions + class DRCProjectionLimits + attr_accessor :min + attr_accessor :max + def initialize(*a) + if a.size > 2 || a.size == 0 + raise("A projection limits specification requires a maximum of two values and at least one argument") + elsif a.size == 1 + if !a[0].is_a?(Range) || (!a[0].min.is_a?(Float) && !a[0].min.is_a?(1.class)) + raise("A projection limit requires an interval of two length values or two individual length values") + end + self.min = a[0].min + self.max = a[0].max + elsif a.size == 2 + if a[0] && !a[0].is_a?(Float) && !a[0].is_a?(1.class) + raise("First argument to a projection limit must be either nil or a length value") + end + if a[1] && !a[1].is_a?(Float) && !a[1].is_a?(1.class) + raise("Second argument to a projection limit must be either nil or a length value") + end + self.min = a[0] + self.max = a[1] + end + end + end + +end + diff --git a/src/drc/drc/drcResources.qrc b/src/drc/drc/drcResources.qrc index d9ae49029..6af97f61d 100644 --- a/src/drc/drc/drcResources.qrc +++ b/src/drc/drc/drcResources.qrc @@ -1,6 +1,11 @@ - built-in-macros/drc.lym + built-in-macros/_drc_engine.rb + built-in-macros/_drc_layer.rb + built-in-macros/_drc_netter.rb + built-in-macros/_drc_patch.rb + built-in-macros/_drc_source.rb + built-in-macros/_drc_tags.rb built-in-macros/drc_interpreters.lym diff --git a/src/lym/lym/lymMacro.cc b/src/lym/lym/lymMacro.cc index 90755ea28..1fa6d5e54 100644 --- a/src/lym/lym/lymMacro.cc +++ b/src/lym/lym/lymMacro.cc @@ -684,16 +684,19 @@ void Macro::sync_properties_with_text () for (size_t i = 0; i < sizeof (property_fields) / sizeof (property_fields[0]); ++i) { + tl::Extractor pex = ex; + const PropertyField *pf = property_fields + i; - if (ex.test (pf->name)) { + if (pex.test (pf->name) && (pex.at_end () || pex.test (":"))) { if (pf->string_setter) { - ex.test (":"); - (this->*(pf->string_setter)) (unescape_pta_string (ex.skip ())); + (this->*(pf->string_setter)) (unescape_pta_string (pex.skip ())); } else if (pf->bool_setter) { (this->*(pf->bool_setter)) (true); } + break; + } } @@ -1334,6 +1337,7 @@ void MacroCollection::scan (const std::string &path) ResourceWithChildren res (tl::to_qstring (path)); QStringList children = res.children (); + children.sort (); for (QStringList::const_iterator c = children.begin (); c != children.end (); ++c) {