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:
PythonTransformerThe 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