diff --git a/docs/source/Configuration/PadConfiguration.md b/docs/source/Configuration/PadConfiguration.md new file mode 100644 index 000000000..a330de70c --- /dev/null +++ b/docs/source/Configuration/PadConfiguration.md @@ -0,0 +1,154 @@ +# Pad Configuration +The pads of the design can be configured using `.hjson` files, which are then read by `mcu-gen` and template files to generate the pad ring RTL and back-end pad IO of the design. +An example pad configuration file is `pad_cfg.hjson`. + +## Basic pad configuration +Each pad of the design is defined as an object in the `pads` list. For example, +``` +clk: { + num: 1, + type: input + } +``` +defines a single input pad named `clk`. + +### Mandatory pad attributes +Pad objects must contain the following mandatory attributes: +* `num`: The number of pads to be defined. +* `type`: The type of pad. + +If the `num` attribute is greater than 1, then a group of pads is defined with names `_n`, where `n` is the sum of the index of the pad and the optional `offset` attribute. + +The possible values of `type` are `input`, `output`, `inout`, `bypass_input`, `bypass_output`, `bypass_inout`, and `supply`. The `supply` type is reserved for power pads like VDD and VSS. The `bypass` type pads are meant to be internal signals that are not included in the pad I/O of the top-level design, but are needed internally. For example, these can be pads that are inputs to the Core-V-Mini MCU. This is essentially the same functionality as the `keep_internal` optional parameter described below. + +### Optional pad attributes +Pad objects can contain the following optional attributes: +* `num_offset`: The offset of the naming of the first pad when `num`>1. Default: 0 +* `mapping`: The side of the design on which the pad is located (i.e. `top`, `bottom`, `right`, or `left`). Default: `top` +* `active`: Whether the pad is active-high (`high`) or active-low (`low`). Default: `high` +* `driven_manually`: Whether pads are driven manually or not. Default: `false` +* `mux`: Multiplexing of pads between two signals. +* `skip_declaration`: Whether or not to skip the definition of the pad in the Core-V-Mini MCU interface. Default: `false` +* `keep_internal`: Whether or not to keep the pad internally (i.e. for the MCU) but exclude it from the top-level pad I/O. + +The `driven_manually` attribute is the opposite of the `keep_internal` attribute and `bypass` pad type; it is meant for pads that are part of the top-level I/O interface but not part of the Core-V-Mini MCU interface. + +Multiplexing is when two signals share the same pad, which can save space on the I/O ring of the design. Here is an example of a multiplexed pad design: +``` +i2s_sck: { + num: 1, + type: inout + mux: { + i2s_sck: { + type: inout + }, + gpio_20: { + type: inout + } + } +``` +Please note that if any of the multiplexed signals is of type `inout`, then the global type should also be `inout`. + +### Pad layout attributes +When doing the physical layout of the design, it is useful to define certain attributes to be able to place each pad along the I/O ring of the design. These attributes can be defined in the `layout_attributes` dictionary. They are not mandatory for generating the pad RTL, but can be used, for example, to populate a `.io` file template that is read into the placement and routing tool. + +The `layout_attributes` dictionary of a given pad object contains the following mandatory attributes: +* `index`: The index of the pad on its side of the I/O ring. +* `cell`: Specific cell to use for the pad. You can get the value from the LEF file of the pads in the desired technology. +* `bondpad`: Specific bondpad cell to use. + +Additionally, the following optional attributes can be defined: +* `orient`: Orientation of the pad on the physical layout. Options: `R0|R90|R180|R270|MX|MX90|MY|MY90` +* `offset`: offset from edge (in um). If this parameter is not defined, the offsets are calculated automatically using the physical_attributes (see the Pad spacing section below). +* `skip`: distance from neighboring pad (in um). If this parameter is not defined, the skips are calculated automatically using the physical_attributes (see the Pad spacing section below). + + +### Example pad definition +To put it all together, here is an example of a pad with several of the above attributes defined: +``` +gpio_0: { + num: 1, + type: inout + mapping: left + layout_attributes: { + index: 8 + cell: PAD1 + orient: mx90 + bondpad: BONDPAD1 + } +} +``` + + +## Defining additional RTL inputs for all pad objects +Depending on the technology used, some pads cells require additional I/Os other than the basic input and output ports. The amount of additional information can be defined in the `bits` field of the `attributes` object as follows: +``` +attributes: { + bits: 7:0 +} +``` +Then, users can connect these additional attributes in RTL to the correct I/Os of the pad cell. + +## Physical attributes +An optional dictionary called `physical_attributes` can be added in order to trigger an `mcu-gen` funcionality that automatically calculates the ideal locations of the pads on the I/O ring. In order to compute these locations, the following attributes must be set: + +* `floorplan_dimensions`: Dimensions of the design floorplan (in um) + * `width`: Width of chip (in um) + * `length`: Length of the chip (in um) +* `edge_offset`: Offsets from the edge of the design to the specified objects (in um) + * `bondpad`: Distance (in um) from design edge to bondpad + * `pad`: Distance (in um) from design edge to pad +* `spacing`: Chosen spacing (in um) between objects + * `bondpad`: Spacing between bondpads. Make sure the minimum bondpad pitch of the packaging supplier is not violated. +* `dimensions`: Dimensions of bondpads and pads in the design. These should exactly match the names in the "cell" field of the "layout_attributes". Found in the respective LEF files of each object. + * `insert_cell_name_here`: Not the actual name of the attribute, but a placeholder for a bondpad name (ex. PAD...) + * `width`: Width in um + +The names of the cells in the `dimensions` field should exactly match those of the `layout_attributes`:`cell` or `layout_attributes`:`bondpad` parameter of each cell. Similarly, each `cell` or `bondpad` name defined among all of the pads should have dimensions associated in order for the pad offset calculation to work correctly. The width of the cell can be found in the LEF file of th pads/bondpads. + +Here is an example of the `physical_attributes` definition: +``` + physical_attributes: { + floorplan_dimensions: { + width: 2000 + length: 1500 + }, + edge_offset: { + bondpad: 20 + pad: 90 + }, + spacing: { + bondpad: 25 + }, + dimensions: { + # Bondpads + BONDPAD1: { + width: 50 + }, + BONDPAD2: { + width: 60 + }, + # Pads + PAD1: { + width: 40 + }, + PAD2: { + width: 45 + }, + } + } +``` + +### Pad spacing +The following section describes how the spacing between pads is performed. When placing a pad object along the I/O boundary, the `.io` file needs to know either the offset from the pad to the core ring (i.e. the `offset` parameter of the `layout_attributes` of the `pad` object), or the distance from the pad to its neighbor in the clockwise direction (i.e. the `skip` parameter of the `layout_attributes` of the `pad` object). Therefore, either the `offset` or `skip` attribute needs to be defined for all pads to automatically populate the `.io` file. These parameters can be defined one of two ways: +1. Manually defining the `offset` or `skip` attribute for each pad in its `layout_attributes`. +2. Leaving these attributes empty and allowing `mcu-gen` to calculate them automatically using the global `physical_attributes`. +Please note that a combination of the two can be performed; If a given `skip` or `offset` parameter is defined for a pad, then the calculated values are overwritten. This gives the designer flexibility to modify the pad locations on only one side, for example. + +![Physical attributes and layout attributes illustration](../images/pad_spacing.png) + +The automatic pad placement calculates the pad `offset` and `skip` parameters such that: +1. Bondpads are evenly spaced and centered on their respecitve side +2. Pads are centered with their respective bondpad + +Furthermore, `mcu-gen` displays an error message if the number of pads on a given side and the bondpad spacing cause the bondpads to overflow past the I/O boundary on a given side. A solution could be to move pads to a different side, or reduce the bondpad spacing parameter. However, make sure that the bondpad spacing does not exceed the minimum possible spacing defined by the packaging provider. diff --git a/docs/source/images/pad_spacing.png b/docs/source/images/pad_spacing.png new file mode 100644 index 000000000..ea22c2194 Binary files /dev/null and b/docs/source/images/pad_spacing.png differ diff --git a/pad_cfg.hjson b/pad_cfg.hjson index b0720c2ec..77e6763a0 100644 --- a/pad_cfg.hjson +++ b/pad_cfg.hjson @@ -3,31 +3,9 @@ // SPDX-License-Identifier: SHL-0.51 // Derived from Occamy: https://github.com/pulp-platform/snitch/blob/master/hw/system/occamy/src/occamy_cfg.hjson // -// Pads configuration for core-v-mini-mcu. Read by mcu_gen.py. +// Pad configuration for core-v-mini-mcu. Read by mcu_gen.py. // -// The pads contains the list of all the pads available in the design. -// Each pad is defined by its name and can have the following attributes: -// num: (mandatory) - the number of pads of this type -// type: (mandatory) - the type of the pad -// num_offset: (optional) - the offset to the first pad of this type (default 0) -// mapping: (optional) - the mapping of the pad in the design. Useful for ASICs (default top) -// active: (optional) - the active level of the pad (default high) -// driven_manually: (optional) - the pad is driven manually (default False) -// mux: (optional) - the muxing options for the pad -// skip_declaration: (optional) - skip the declaration of the pad in the top level (default False) -// keep_internal: (optional) - keep the pad internal to the design (default False) -// layout_attributes: (optional) - collection of attributes related to the physical (ASIC) layout of the pads -// index: (mandatory) index of the pad on its side of the I/O ring -// orient: (optional) - orientation of the pad -// cell: (mandatory for type "supply") - specific cell to use if not a default pad cell (ex. for VDD/VSS pads) -// offset: (optional) - offset from edge (in um) -// skip: (optional) - distance from neighboring pad (in um) -// -// Add this field at the same level of pads (not inside) if you want to define PADs attributes -// attributes: { -// bits: 7:0 -// resval: 0x3 -// }, +// For detailed documentation and usage instructions, please refer to docs/source/Configuration/PadConfiguration.md { diff --git a/util/mcu_gen.py b/util/mcu_gen.py index c187bf549..b5a1c11ab 100755 --- a/util/mcu_gen.py +++ b/util/mcu_gen.py @@ -210,7 +210,7 @@ def create_pad_ring_bonding(self): self.pad_ring_bonding_bonding += ' .' + self.signal_name + 'oe_i(' + oe_internal_signals + '),' self.x_heep_system_interface += ' inout wire ' + self.signal_name + 'io,' - def __init__(self, name, cell_name, pad_type, pad_mapping, index, pad_active, pad_driven_manually, pad_skip_declaration, pad_mux_list, has_attribute, attribute_bits, pad_layout_index, pad_layout_orient, pad_layout_cell, pad_layout_offset, pad_layout_skip): + def __init__(self, name, cell_name, pad_type, pad_mapping, index, pad_active, pad_driven_manually, pad_skip_declaration, pad_mux_list, has_attribute, attribute_bits, pad_layout_index, pad_layout_orient, pad_layout_cell, pad_layout_bondpad, pad_layout_offset, pad_layout_skip): self.name = name self.cell_name = cell_name @@ -244,6 +244,7 @@ def __init__(self, name, cell_name, pad_type, pad_mapping, index, pad_active, pa self.layout_index = pad_layout_index self.layout_orient = pad_layout_orient self.layout_cell = pad_layout_cell + self.layout_bondpad = pad_layout_bondpad self.layout_offset = pad_layout_offset self.layout_skip = pad_layout_skip @@ -305,6 +306,140 @@ def write_template(tpl_path, outdir, outfile, **kwargs): else: raise FileNotFoundError +def prepare_pads_for_layout(total_pad_list, physical_attributes): + """ + Separate pads into pad lists for the top, bottom, left, and right pads and order them according to their layout_index attribute, and set their positions on the floorplan. + """ + + # Separate pads according to side + top_pad_list = [] + bottom_pad_list = [] + right_pad_list = [] + left_pad_list = [] + for pad in total_pad_list: + if (pad.pad_mapping == "top"): + top_pad_list.append(pad) + elif (pad.pad_mapping == "bottom"): + bottom_pad_list.append(pad) + elif (pad.pad_mapping == "right"): + right_pad_list.append(pad) + elif (pad.pad_mapping == "left"): + left_pad_list.append(pad) + + # Order pads according to layout index + top_pad_list.sort(key=lambda x: x.layout_index) + bottom_pad_list.sort(key=lambda x: x.layout_index) + left_pad_list.sort(key=lambda x: x.layout_index) + right_pad_list.sort(key=lambda x: x.layout_index) + + # Calculate pad offsets and check whether requested pad configuration fits in the floorplan + top_pad_list, bondpad_offset_top = set_pad_positions(top_pad_list, physical_attributes) + bottom_pad_list, bondpad_offset_bottom = set_pad_positions(bottom_pad_list, physical_attributes) + left_pad_list, bondpad_offset_left = set_pad_positions(left_pad_list, physical_attributes) + right_pad_list, bondpad_offset_right = set_pad_positions(right_pad_list, physical_attributes) + + bondpad_offsets = { + "top": bondpad_offset_top, + "bottom": bondpad_offset_bottom, + "left": bondpad_offset_left, + "right": bondpad_offset_right + } + + return top_pad_list, bottom_pad_list, left_pad_list, right_pad_list, bondpad_offsets + +def set_pad_positions(pad_list, physical_attributes): + """Calculate the `offset` and `skip` attributes of the pads such that the bondpads are centered on each side and the pads are aligned with their respective bondpads. + Perform checks to make sure the pads can all fit on the requested side without violating design constraints or exceeding layout margins. + """ + + # Ensure the physical attributes were properly set in the pad config file + try: + fp_width = float(physical_attributes["floorplan_dimensions"]["width"]) + fp_length = float(physical_attributes["floorplan_dimensions"]["length"]) + edge_to_bp = float(physical_attributes["edge_offset"]["bondpad"]) + edge_to_pad = float(physical_attributes["edge_offset"]["pad"]) + bp_spacing = float(physical_attributes["spacing"]["bondpad"]) + pad_dims = physical_attributes["dimensions"] + except KeyError: + print("ERROR: Please set all of the mandatory fields of the physical_attributes in the pad config file.") + return + + # Determine which dimension we are dealing with + side = pad_list[0].pad_mapping + if ((side == "top") | (side == "bottom")): + side_length = fp_width + else: + side_length = fp_length + + # Calculate space occupied by bondpads on the designated side of the chip + bp_space = 0 + for pad in pad_list: + bp_cell = pad.layout_bondpad + if bp_cell is not None: + # Get bondpad width from physical attributes + try: + bp_width = float(pad_dims[bp_cell]["width"]) + except KeyError: + print("ERROR: Width not defined for bondpad cell {0} of pad {1}".format(bp_cell, pad.cell_name)) + return + else: + print("ERROR: A bondpad cell is not defined for pad {1}".format(pad.cell_name)) + return + bp_space += bp_width + bp_space += bp_spacing*(len(pad_list)-1) + + # Check if the bondpads are able to fit on the side + extra_space = side_length - bp_space - 2*edge_to_bp + if (extra_space<0): + print("ERROR: Bondpads cannot fit on side {0}. Either reduce bondpad spacing or move some pads to another side".format(side)) + + # Calculate distance from edge to first bondpad (i.e. bondpad offset) to center the pads + bp_offset = extra_space/2 + + # Calculate skip parameter between one pad and the next to center the pads + for i, pad in enumerate(pad_list): + + # Get bondpad width from physical attributes + bp_cell = pad.layout_bondpad + bp_width = float(pad_dims[bp_cell]["width"]) + + if (i>0): + last_bp_cell = pad_list[i-1].layout_bondpad + last_bp_width = float(pad_dims[last_bp_cell]["width"]) + + # Get pad width from physical attributes + pad_cell = pad.layout_cell + if pad_cell is not None: + try: + pad_width = float(pad_dims[pad_cell]["width"]) + except KeyError: + print("ERROR: Width not defined for pad cell {0} of pad {1}".format(pad_cell, pad.cell_name)) + return + else: + print("ERROR: A pad cell is not defined for pad {1}".format(pad.cell_name)) + return + + if (i>0): + last_pad_cell = pad_list[i-1].layout_cell + if pad_cell is not None: + try: + last_pad_width = float(pad_dims[last_pad_cell]["width"]) + except KeyError: + print("ERROR: Width not defined for pad cell {0} of pad {1}".format(last_pad_cell, pad_list[i-1].cell_name)) + return + else: + print("ERROR: A pad cell is not defined for pad {1}".format(pad_list[i-1].cell_name)) + return + if (i==0)&(pad.layout_offset is None)& (pad.layout_skip is None): + pad.layout_offset = bp_offset - (edge_to_pad - edge_to_bp) + (bp_width/2) - (pad_width/2) + + # If the layout/skip of the pads is not predefined, calculate automatically + if (pad.layout_offset is None) & (pad.layout_skip is None): + pad.layout_skip = (last_bp_width + bp_width)/2 + bp_spacing - (last_pad_width + pad_width)/2 + + return pad_list, bp_offset + + def main(): parser = argparse.ArgumentParser(prog="mcugen") parser.add_argument("--cfg_peripherals", @@ -682,6 +817,11 @@ def len_extracted_peripherals(peripherals): except KeyError: pad_layout_cell = None + try: + pad_layout_bondpad = pads[key]['layout_attributes']['bondpad'] + except KeyError: + pad_layout_bondpad = None + try: pad_layout_offset = pads[key]['layout_attributes']['offset'] except KeyError: @@ -722,13 +862,13 @@ def len_extracted_peripherals(peripherals): except KeyError: pad_skip_declaration_mux = False - p = Pad(pad_mux, '', pads[key]['mux'][pad_mux]['type'], pad_mapping, 0, pad_active_mux, pad_driven_manually_mux, pad_skip_declaration_mux, [], pads_attributes!=None, pads_attributes_bits, pad_layout_index, pad_layout_orient, pad_layout_cell, pad_layout_offset, pad_layout_skip) + p = Pad(pad_mux, '', pads[key]['mux'][pad_mux]['type'], pad_mapping, 0, pad_active_mux, pad_driven_manually_mux, pad_skip_declaration_mux, [], pads_attributes!=None, pads_attributes_bits, pad_layout_index, pad_layout_orient, pad_layout_cell, pad_layout_bondpad, pad_layout_offset, pad_layout_skip) pad_mux_list.append(p) if pad_num > 1: for p in range(pad_num): pad_cell_name = "pad_" + key + "_" + str(p+pad_offset) + "_i" - pad_obj = Pad(pad_name + "_" + str(p+pad_offset), pad_cell_name, pad_type, pad_mapping, pad_index_counter, pad_active, pad_driven_manually, pad_skip_declaration, pad_mux_list, pads_attributes!=None, pads_attributes_bits, pad_layout_index, pad_layout_orient, pad_layout_cell, pad_layout_offset, pad_layout_skip) + pad_obj = Pad(pad_name + "_" + str(p+pad_offset), pad_cell_name, pad_type, pad_mapping, pad_index_counter, pad_active, pad_driven_manually, pad_skip_declaration, pad_mux_list, pads_attributes!=None, pads_attributes_bits, pad_layout_index, pad_layout_orient, pad_layout_cell, pad_layout_bondpad, pad_layout_offset, pad_layout_skip) if not pad_keep_internal: pad_obj.create_pad_ring() pad_obj.create_core_v_mini_mcu_ctrl() @@ -747,7 +887,7 @@ def len_extracted_peripherals(peripherals): else: pad_cell_name = "pad_" + key + "_i" - pad_obj = Pad(pad_name, pad_cell_name, pad_type, pad_mapping, pad_index_counter, pad_active, pad_driven_manually, pad_skip_declaration, pad_mux_list, pads_attributes!=None, pads_attributes_bits, pad_layout_index, pad_layout_orient, pad_layout_cell, pad_layout_offset, pad_layout_skip) + pad_obj = Pad(pad_name, pad_cell_name, pad_type, pad_mapping, pad_index_counter, pad_active, pad_driven_manually, pad_skip_declaration, pad_mux_list, pads_attributes!=None, pads_attributes_bits, pad_layout_index, pad_layout_orient, pad_layout_cell, pad_layout_bondpad, pad_layout_offset, pad_layout_skip) if not pad_keep_internal: pad_obj.create_pad_ring() pad_obj.create_core_v_mini_mcu_ctrl() @@ -817,6 +957,11 @@ def len_extracted_peripherals(peripherals): pad_layout_cell = external_pads[key]['layout_attributes']['cell'] except KeyError: pad_layout_cell = None + + try: + pad_layout_bondpad = external_pads[key]['layout_attributes']['bondpad'] + except KeyError: + pad_layout_bondpad = None try: pad_layout_offset = external_pads[key]['layout_attributes']['offset'] @@ -858,13 +1003,13 @@ def len_extracted_peripherals(peripherals): except KeyError: pad_skip_declaration_mux = False - p = Pad(pad_mux, '', external_pads[key]['mux'][pad_mux]['type'], pad_mapping, 0, pad_active_mux, pad_driven_manually_mux, pad_skip_declaration_mux, [], pads_attributes!=None, pads_attributes_bits, pad_layout_index, pad_layout_orient, pad_layout_cell, pad_layout_offset, pad_layout_skip) + p = Pad(pad_mux, '', external_pads[key]['mux'][pad_mux]['type'], pad_mapping, 0, pad_active_mux, pad_driven_manually_mux, pad_skip_declaration_mux, [], pads_attributes!=None, pads_attributes_bits, pad_layout_index, pad_layout_orient, pad_layout_cell, pad_layout_bondpad, pad_layout_offset, pad_layout_skip) pad_mux_list.append(p) if pad_num > 1: for p in range(pad_num): pad_cell_name = "pad_" + key + "_" + str(p+pad_offset) + "_i" - pad_obj = Pad(pad_name + "_" + str(p+pad_offset), pad_cell_name, pad_type, pad_mapping, external_pad_index, pad_active, pad_driven_manually, pad_skip_declaration, pad_mux_list, pads_attributes!=None, pads_attributes_bits, pad_layout_index, pad_layout_orient, pad_layout_cell, pad_layout_offset, pad_layout_skip) + pad_obj = Pad(pad_name + "_" + str(p+pad_offset), pad_cell_name, pad_type, pad_mapping, external_pad_index, pad_active, pad_driven_manually, pad_skip_declaration, pad_mux_list, pads_attributes!=None, pads_attributes_bits, pad_layout_index, pad_layout_orient, pad_layout_cell, pad_layout_bondpad, pad_layout_offset, pad_layout_skip) pad_obj.create_pad_ring() pad_obj.create_pad_ring_bonding() pad_obj.create_internal_signals() @@ -880,7 +1025,7 @@ def len_extracted_peripherals(peripherals): else: pad_cell_name = "pad_" + key + "_i" - pad_obj = Pad(pad_name, pad_cell_name, pad_type, pad_mapping, external_pad_index, pad_active, pad_driven_manually, pad_skip_declaration, pad_mux_list, pads_attributes!=None, pads_attributes_bits, pad_layout_index, pad_layout_orient, pad_layout_cell, pad_layout_offset, pad_layout_skip) + pad_obj = Pad(pad_name, pad_cell_name, pad_type, pad_mapping, external_pad_index, pad_active, pad_driven_manually, pad_skip_declaration, pad_mux_list, pads_attributes!=None, pads_attributes_bits, pad_layout_index, pad_layout_orient, pad_layout_cell, pad_layout_bondpad, pad_layout_offset, pad_layout_skip) pad_obj.create_pad_ring() pad_obj.create_pad_ring_bonding() pad_obj.create_internal_signals() @@ -913,6 +1058,18 @@ def len_extracted_peripherals(peripherals): last_pad.remove_comma_io_interface() total_pad_list.append(last_pad) + # If layout parameters exist in the pad config file, compute the pad offset/skip parameters and order the pads on each side + try: + physical_attributes = obj_pad['physical_attributes'] + top_pad_list, bottom_pad_list, left_pad_list, right_pad_list, bondpad_offsets = prepare_pads_for_layout(total_pad_list, physical_attributes) + except KeyError: + physical_attributes = None + top_pad_list = None + bottom_pad_list = None + left_pad_list = None + right_pad_list = None + bondpad_offsets = None + kwargs = { "xheep" : xheep, "cpu_type" : cpu_type, @@ -945,6 +1102,12 @@ def len_extracted_peripherals(peripherals): "external_pad_list" : external_pad_list, "total_pad_list" : total_pad_list, "total_pad" : total_pad, + "right_pad_list" : right_pad_list, + "left_pad_list" : left_pad_list, + "top_pad_list" : top_pad_list, + "bottom_pad_list" : bottom_pad_list, + "physical_attributes" : physical_attributes, + "bondpad_offsets" : bondpad_offsets, "pad_constant_driver_assign" : pad_constant_driver_assign, "pad_mux_process" : pad_mux_process, "pad_muxed_list" : pad_muxed_list,