ordec.ord2 — ORD2 language

ORD2 is a programming language that offers full support of Python, plus additional ORD syntax (a Python-superset) to improve textual IC design within the ORDeC project. It currently focuses on simplifying the schematic entry phase, while also supporting the usual Python syntax for simulations or layouts. The language revision described in this file is called ORD2; it is a reworked version of the former ORD1, which had its own grammar and was not based on the Python language. Execution of ORD code results in a one-pass compiler step that transforms the input into context-based Python code. This is only made possible by leveraging the power of the OrdContext, which is explained in a later paragraph. The actual ORD grammar is written in Lark. Lark is a well-known and efficient Python parsing framework for grammars in EBNF form. The function call ord2_to_py() summarizes the necessary function calls for a proper ORD to Python conversion. The conversion is mostly dependent on the Ord2Transformer that inherits from PythonTransformer. The PythonTransformer is capable of transforming any Python code written in ORD back to Python, and the Ord2Transformer handles the conversion of the ORD syntax. The following paragraphs will summarize the logic behind the ORD to Python conversion.

For a practical demonstration, please visit the ORD tutorial ORD Language Tutorial page!

ORD2 to Python in detail

ORD is not a general-purpose programming language. It is developed to simplify certain steps in IC design, especially for the ORDeC project. The entire backend of ORDeC is written in Python, but using Python for tasks like schematic entry can become complicated and cumbersome. ORD represents a more convenient syntax layer that makes structuring and describing IC designs much easier.

Mastering the ORD language requires understanding two crucial parts. First, the ORD language itself: what it offers and what it represents. Second, the converted code: understanding how ORD code is converted back to Python. This helps, especially if you run into trouble while programming or designing, and it also helps you understand how the project works under the hood. Especially for complex programs and debugging purposes, understanding the Python side can become important.

ORD2 Contexts

The dotted syntax of ORD, which accesses the parent element, requires having a reference to the parent element. This structure therefore necessitates that statements and expressions inside a context block have a reference to the parent even after transformation of ORD back to Python. This logic is implemented with the so-called OrdContext. It uses the Python with environment together with a context variable ContextVar to always maintain a reference without requiring information about the parent during transformation. With ORD, we try to keep the transformation logic as simple as possible and leverage the power of Python to supply the necessary constructs during execution.

# Type 1
port xyz:
        .pos=(1,2)
# Type 2
port xyz(.pos=(1,2))

To demonstrate how the ORD context works and how the conversion from ORD to Python looks, consider the following two examples. Every time a context element (viewgen, port, or a schematic instance) is defined, the element is saved as a local variable ctx and a with context is opened. The dotted access is converted into ctx.root. If multiple dots are written prior to the identifier, the dots are converted to ctx.root(.parent)*. Accesses outside the context are still possible through the local variable. An access like this is visible in the for loop of the example.

ORD code

cell Inv:
    viewgen symbol:
        inout vdd(.align=North)
        inout vss(.align=South)
        input a(.align=West)
        output y(.align=East)

    viewgen schematic:
        port vdd(.pos=(2,13); .align=North)
        port vss(.pos=(2,1); .align=South)
        port y (.pos=(9,7); .align=West)
        port a (.pos=(1,7); .align=East)

        Nmos pd:
            .s -- vss
            .b -- vss
            .d -- y
            .pos = (3,2)
            .$l = 400n
        Pmos pu:
            .s -- vdd
            .b -- vdd
            .d -- y
            .pos = (3,8)
            .$l = 400n

        for instance in pu, pd:
            instance.g -- a

Compiled Python code

class Inv(Cell):
    @generate
    def symbol(self) -> Symbol:
        with OrdContext(root=Symbol(cell=self), parent=self):
            vdd = ctx.add(('vdd',), Pin(pintype=PinType.Inout))
            with OrdContext(root=vdd):
                ctx.root.align = North
            vss = ctx.add(('vss',), Pin(pintype=PinType.Inout))
            with OrdContext(root=vss):
                ctx.root.align = South
            a = ctx.add(('a',), Pin(pintype=PinType.In))
            with OrdContext(root=a):
                ctx.root.align = West
            y = ctx.add(('y',), Pin(pintype=PinType.Out))
            with OrdContext(root=y):
                ctx.root.align = East
            return ctx.symbol_postprocess()

    @generate
    def schematic(self) -> Schematic:
        with OrdContext(root=Schematic(cell=self, symbol=self.symbol), parent=self):
            vss = ctx.add_port(('vss',))
            with OrdContext(root=vss):
                ctx.root.pos = (2,1)
                ctx.root.align = South
            vdd = ctx.add_port(('vdd',))
            with OrdContext(root=vdd):
                ctx.root.pos = (2,13)
                ctx.root.align = North
            y = ctx.add_port(('y',))
            with OrdContext(root=y):
                ctx.root.pos = (9,7)
                ctx.root.align = West
            a = ctx.add_port(('a',))
            with OrdContext(root=a):
                ctx.root.pos = (1,7)
                ctx.root.align = East

            pd = ctx.add(('pd',), SchemInstanceUnresolved(resolver = lambda **params: Nmos(**params).symbol))
            with OrdContext (root=pd):
                ctx.root.s.__wire_op__(vss.ref)
                ctx.root.b.__wire_op__(vss.ref)
                ctx.root.d.__wire_op__(y.ref)
                ctx.root.pos = (3,2)
                ctx.root.params.l = R('400n')

            pu = ctx.add(('pu',), SchemInstanceUnresolved(resolver = lambda **params: Pmos(**params).symbol))
            with OrdContext (root=pu):
                ctx.root.s.__wire_op__(vdd.ref)
                ctx.root.b.__wire_op__(vdd.ref)
                ctx.root.d.__wire_op__(y.ref)
                ctx.root.pos = (3,8)
                ctx.root.params.l = R('400n')

            for instance in pu, pd:
                instance.g.__wire_op__(a.ref)
            return ctx.schematic_postprocess()

The following summary shows the most important functions and classes of ORD2. Please refer to the Python codebase for more background information and details.

Parser

ordec.ord2.parse_with_errors(parser, code)

Function which parses an ORD string with improved error messages

Parameters:
  • parser – ORD Lark parser

  • code (str) – String containing ORD code

Returns:

AST of the parsed string

ordec.ord2.ord2_to_py(ord_string: str) Module

Function which parses an ORD string and returns the transformed result.

Parameters:

ord_string (str) – String containing ORD code

Returns:

AST of the parsed and transformed string

OrdContext

class ordec.ord2.OrdContext(root=None, parent=None)

Class which represents the context where a specific ORDB element is alive and accessible via relative accesses (dotted notation)

add(name_tuple, ref)

Add a value to the current context

add_port(name_tuple)

Add a port to the current context

symbol_postprocess()

Postprocess call when returning from symbol

layout_postprocess()

Postprocess call when returning from layout

schematic_postprocess()

Postprocess call when returning from schematic

OrdTransformer

class ordec.ord2.Ord2Transformer(source_text='')

Bases: PythonTransformer

The Ord2Transformer handles Ord specific sytnax and converts it back to valid Python ORDeC code. It inherits from the PythonTransformer for full support of the Python syntax.

celldef(nodes)

Definition of a ORDeC cell class

RATIONAL(token)

Rational numbers with SI suffix (100n, 20u)

viewgen(nodes)

Funcdef for cell (viewgen name -> Type: suite)

connect_stmt(nodes)

connect stmt x – b

constrain_stmt(nodes)

! x >= 200

extract_path(nodes)

Extract string list from nested attributes

context_element(nodes)

context_element (context_type context_target: suite)

depth_helper(value, depth=1)

Access parent attributes depending on the dotted depth

dotted_atom(nodes)

Dotted name (..x) or ellipsis (…)

getparam(nodes)

get/set param (.$l = 100n)

net_and_path_stmt_helper(nodes, stmt)

Helper for similar code from net and path statements

net_stmt(nodes)

Add net (net x)

path_stmt(nodes)

Add path (path x)

PythonTransformer

class ordec.ord2.PythonTransformer(source_text='')

Transformer that transforms any Python code back into a Python AST. This Class represents the base of the ORD language