# PyCircHDL - Python Logic Circuit Hardware Description Language Package

• The Google Colaboratory notebook has the advantage that you can run PyCircHDL from it without having to install PyCircHDL or even Python on your local device.
• You first need to copy it to your google drive (or github repository)
• You can also download it to your local device and open it as a Jupyter notebook (if you have it installed with your Python).

## Installing the PyCircHDL package¶

• The PyCircHDL package can be installed on your local system by running the following command from the command line
pip install pycirchdl
• Or you may try running one of the following commands from this notebook.
• If you are running it from a Jupyter notebook on your local system, then it will be installed on your device.
• After installation, you may need to restart this notebook.

• After installing the PyCircHDL package, you need to import it.
• The following command imports PyCircHDL to your Python interpreter.

## Introduction¶

• PyCircHDL is a new Python package for Logic Circuit Hardware Description Language (HDL). It was specifically designed for educational use in introductory computation and digital logic college courses.
• As such, it was primarily tuned for simplicity, readability convenience, and fast learning curve.
• Less for speed or industrial production.
• It is a lightweight package especially designed for small to medium scale circuits, such as those that are studied in introductory academic courses on the theory of computation and electronic digital design.
• Its main characteristic is that a digital circuit can be easily defined by a series of simple Python commands, rather than an external static language.
• So, the only requirement is a basic knowledge of the Python programming language, with a little programming skill.
• Experienced Python programmers can probably benefit a lot more from this package.
• It can be a useful companion for theoretical courses on computation models and languages who wish also to engage the students with some programming experience and skills.
• It is planned to be used in such a course by the author (Hebrew book at http://samyzaf.com/afl.pdf).
• It enables students to easily model and experiment with
• Typical logic circuit hardware design
• Logic Circuit Modeling and Validation
• Logic Circuit Testing
• Logic problem solving
• It does provide an opportunity for students to develop and practice some programming skills while covering the theoretical computation course.
• In this tutorial, we will cover:
1. Basic usage of PyCircHDL for modeling and manipulating Logic Circuits.
2. A short survey of the commands and tools of the PyCircHDL package.
3. Examples for Logic Circuits and their manipulation.
4. Advanced usage of PyCircHDL for experienced Python programmers (TODO).

## Example 1: The Circuit FOO¶

• We start with a very simple logic circuit which we call "FOO"

• Inputs gates: $x_1$, $x_2$, $x_3$
• Output gates: $y_1$, $y_2$
• Logic action: $(y_1, y_2) = (x_1 \land x_2 , \ \neg x_3)$
• This circuit can be represented by the following PyCirc Diagram

• A PyCirc diagram is a simplified form of circuit diagram in which gates are represented by text blocks rather than special shape symbols.

• Many students find the usual gate symbols intimidating and hard to memorize.
• Plain text on a circle or a rectangle seems to be more convenient, especially for computation theory courses in which most of the participants do not have any electronics background (or plan to go in this direction).
• Input and output gates are represented by circles with an INP/OUT labels.
• The other logic gates are represented by rectangular blocks with input/output pins near the block edges.
• Input gate names are blue colored.
• Output gate names are green colored.
• Logic gate names are brown colored.
• Connections ("Wires") are represented by arrowed lines from source pin to target pin.
• A pin is either an input/output gate or a named entry/exit point to a logic gate.
• In the above diagram we have 5 wires and 10 pins:
• 3 Input gates: x1, x2, x3
• 2 output gates: y1, y2
• 5 Block pins: g1/x1, g1/x2, g1/y, g2/x, g2/y
• Note the special notation g/p for the block pins.
• g stands for the gate name and p is the input/ouput name of the cell type of the gate.
• Here is the PyCirc code for modeling this circuit:
• Notic that this is a pure Python code!

• So you need to run its cell in order to execute it.
• A circuit definition starts with the Define() function call which accepts the circuit name as its first argument.

• The definition ends with the EndDef() function call.
• A circuit definition consists of GATE and WIRE commands.
• The GATE function accepts the name and the logic type of the gate.
• The WIRE function connects a source pin to a target pin.
• A pin notation g/p consists of a gate name g and a pin name p.
• The pin p is either an input or an output pin of the gate.
• For example the expression "g1/x2" designates the input pin "x2" of the gate "g1".
• The flag "name=" is optional. It is enough to specify the name only:
GATE("x1", type="inp")

• The 'type' flag indicates the logic cell name to which the gate is an instance of.
• Input gates are the circuit elements in which we feed the input (boolean values).
• Output gate are the circuit elements from which we read the output after its computation is done.
• All remaining gates are called logical gates.
• They usually have incoming wires and outgoing wires and they perform some logic action.
• We repeat: the above definition of the cell FOO is also a pure Python code!
• This means that you can include it inside longer Python programs/scripts and manipulate cells dynamically.
• For example, it is possible to add or remove gates or wires in a circuit dynamically and run it for optimization goals.

## Circuit libraries¶

• After successful design of a circuit such as FOO, it can be placed as a single file 'foo.py' in a circuit library and loaded by the load command
load("foo")

• A circuit library is simply a directory that contains circuit files.
• You may have several directories, including directories residing on far Internet locations.
• You specify the library with a list such as
set_path(["c:/eda/pycirc/lib", "d:/cmfl/code/logcirc/lib", "https://samyzaf.com/pycirc/lib"])

• We will use this library path in this notebook, but you can download all circuit files from the following url, place them on your local computer, and change the path accordingly.

• In most cases it is better to use the need command

need("foo")


• You may want to copy the circuit library that we use in this notebook to your local pc.
• Here is a link to a zip file that contains all the cells we use here:
• You can also browse this library and pick individual files from it
• After copying it to your local space, you can add more cells to it, or create more libraries like it.
• If you do, don't forget to update the new list with the set_path command:
set_path([ dir1, dir2, url1, url2, ...])


## Gates as Cell Instances¶

• The term cell means a packaged circuit or any other function which accepts input and provide output. After defining a circuit for example, it is "packaged" as a cell which hides its content and only exposes its input and output.
• Once we have defined a cell such as FOO, we can use it as a building block element inside the definitions of new cells.
• Every logic gate in a logic circuit must be an instance of some cell.
• Typically, there can be several instances of the same cell in a logic circuit definition.
• This is how the FOO cell is represented as a single block element in a logic circuit
• It appears as a named black box with only entry and exit points (pins)

## The Circuit FRED¶

• Here is an example of cell called FRED which uses an instance of the cell FOO as one of its building blocks.
• This is the standard for defining a new circuit from a regular Python code.
• Within a librarry tree, the Define/EndDef lines must be omitted since the name of the circuit, beginning and end, are easily inferred from the circuit file.
• Usually every circuit is defined in a single file "name.py", and kept within a library directory, but several circuit definitions can also reside in a single file and imported to a Python code via the Python import mechanism.
• You create a library by
• allocating a folder or a url for it.
• putting all your circuit definitions in this folder (one circuit per file).
• adding this directory (or url) to the set_path list (see above.
• Note that the Define/EndDef lines are omitted in a circuit file!
• Note the line GATE("g1", type="foo") in which the gate of type FOO is declared! It automatically triggers the loading of FOO (if it wasn't already loaded).
• It is easy to draw a PyCirc Diagram from the above definition

• Note that we used the ZERO cell which has no inputs, and only one constant 0 output.

• It is the hardware equivalent of a boolean constant.
• We use it to fix the input x3 of gate g1 to zero.
• The ZERO cell is a fundamental PyCirc cell which is automatically loaded when PyCirc starts.

## The Circuit HAM¶

• The follwing cell HAM contains two gates of type FOO and two gates of type XOR3.
• The XOR3 cell is a typical xor cell with 3 input gates (built in PyCirc).
• It also contains one gate of type NOT.
• The XOR3 and NOT cells are built in types of PyCirc and are loaded automatically when PyCirc starts.

• Here is a PyCirc code for modeling this cell.
• Note the compressed notation style for writing shorter code.
• Note that "x<1:4>" stands for x1, x2, x3, x4
• Instead of writing 4 lines of code
GATE("x1", type="inp")
GATE("x2", type="inp")
GATE("x3", type="inp")
GATE("x4", type="inp")

We can write one equivalent line!
GATE("x<1:4>", type="inp")

WIRE("g1/y2", "g3/x1")
WIRE("g1/y2", "g4/x")
WIRE("g1/y2", "g5/x1")

We can simply write
WIRE("g1/y2", "g3/x1; g4/x; g5/x1")

• Arguments can also be lists or tuples of compressed names!
WIRE("g1/y2", ["g%s/x%s" % (i,) for i in range(20)])

• More on compressed notation in the examples below.
• The only supported modes:
• one to one
• one to many
• many to one
• n to n

## Cell Query and Manipulation¶

• After defining the cell HAM we may start performing all kind of queries on it.
• First we need to get a reference to it.
• This gives us a PyCirc reference to our ham circuit object. which we can use to manipulate the cell.
• PyCirc is the name of the Python class which creates logic circuit objects.
• The class PyCirc keeps a map between the name and the reference of every logic circuit created by it.
• So if you have the name of an existing logic circuit, you can get a handle for it by using
ref = PyCirc[name]

• You may use ref as any other Python object handle for querying and manipulating the cell object.
• This gives us only gates and ignores pins.
• To get pin connections we need to dig a bit deeper.
• This is too verbose.
• We want to get a cleaner list of pin connections:
• Note the ham["g3"] expression in the first line.
• The PyCirc object ham has been overloaded as a dictionary which maps gate and wire names to their reference.
• The following diagram displays examples of typical circuit representations in the PyCirc package.

## 4x1 Multiplexer Design¶

• Multiplxers are important circuit elements in electronic design.
• Here is a simple PyCirc Design Diagram and code for a 4x1 Multiplexer circuit (aka MUX2)

# File for MUX2
# input:  x3, x2, x1, x0, s2, s1
# output: y

GATE("x0", type="inp")
GATE("x1", type="inp")
GATE("x2", type="inp")
GATE("x3", type="inp")
GATE("s1", type="inp")
GATE("s2", type="inp")
GATE("y", type="out")
GATE("g1", type="not")
GATE("g2", type="not")
GATE("g3", type="and3")
GATE("g4", type="and3")
GATE("g5", type="and3")
GATE("g6", type="and3")
GATE("g7", type="or4")

WIRE("s1", "g1/x")
WIRE("s1", "g5/x1")
WIRE("s1", "g6/x1")
WIRE("s2", "g2/x")
WIRE("s2", "g4/x3")
WIRE("s2", "g6/x3")
WIRE("x0", "g3/x2")
WIRE("x1", "g4/x2")
WIRE("x2", "g5/x2")
WIRE("x3", "g6/x2")
WIRE("g1/y", "g3/x1")
WIRE("g1/y", "g4/x1")
WIRE("g2/y", "g3/x3")
WIRE("g2/y", "g5/x3")
WIRE("g3/y", "g7/x1")
WIRE("g4/y", "g7/x2")
WIRE("g5/y", "g7/x3")
WIRE("g6/y", "g7/x4")
WIRE("g7/y", "y")

• This is long. Took 35 lines of code to define this circuit.
• With compressed notation it takes only 15 lines!
• In addition, compressed notation can help us understand better the circuit structure, as it is displayed in one paragraph.
• Remember that this is a clean Python code, so you can use Python comments, and other Python commands.
# File: mux2.py (compressed version!)
# MUX2  (4x1 Multiplexer)
# input:  x3, x2, x1, x0, s2, s1
# output: y

GATE("x<0:3>", type="inp")             # 4 bits
GATE("s1;s2", type="inp")              # 2 selectors
GATE("y", type="out")
GATE("g1; g2", type="not")
GATE("g<3:6>", type="and3")            # 4 gates of basic type "and3"
GATE("g7", type="or4")

WIRE("s1", "g1/x; g5/x1; g6/x1")
WIRE("s2", "g2/x; g4/x3; g6/x3")       # 3 wires!
WIRE("x<0:3>", "g<3:6>/x2")
WIRE("g1/y", "g3/x1; g4/x1")
WIRE("g2/y", "g3/x3; g5/x3")
WIRE("g<3:6>/y", "g7/x<1:4>")          # 4 wires
WIRE("g7/y", "y")

• Lets print the list of gates in our circuit mux2:
• This is too verbose, but useful for debugging purposes.
• To get more specific info, you may use code like this:

## Truth Assignments in PyCirc¶

• Truth assignment for a boolean formula or a boolean circuit is a mapping between its boolean variables and the two boolean values $\{0,1\}$.
• In PyCirc, trurth assignment is modeled by a Python class called Assign, which is a subclass of the standard Python dictionary class dict.
• An Assign object maps a list of gate names to one of three values: {0, 1, None}.
• The value None indicates uninitialized state.
• Here is a simple example for defining an assignment on four input variables.

Here are some code examples for manipulating an Assign object

• An assignment object a is callable (i.e., can be called as a function)
• The result of the call a() is the list of its bit values.
• You can pass bit names in expanded or compressed form:
• It is also possible to create an Assign object from keyword/value pairs, just like in a Python dictionary.
• The Assign class also accepts names in compressed form.
• The default of the second argument is None for all bits.
• The None value indicates that the input is uninitialized.
• Use the following to initialize all values to zero (or one).
• The Assign class also has a static method Assign.iter for creating an iterator for looping over all truth assignments!

## How to initialize a PyCirc object?¶

• When you create a new Pycirc circuit, none of the input gates are initialized.
• All gate values equal None.
• In order to run a circuit, we first need to initialize its input gates.
• Lets assign values to mux2 input gates, and check again the values of its gates.
• The PyCirc set method is used
• We see that all input gates are initialized with boolean values.
• All other gates value are still None.
• PyCirc has a step method which triggers the logic function of every logic gate whose inputs are initialized to boolean values.
• We now see that also gates g1 and g2 are initialized to boolean values.
• From the diagram of MUX2 we see that $g_1 = \neg s_1$.
• Since $s_1=1$, we get $g_1=0$.
• From the diagram we see that $g_2 = \neg s_2= \neg 0 = 1$.
• All other gates are still in None state.
• To reach the output gate, we need to apply the step method three more times.
• The number of times depends on the depth of the circuit.

## Getting the circuit output¶

• After initializing the circuit, we can run it, get the output, and print it.
• Now all gates are initialize and we can extract the circuit outcome by the PyCirc get method.
• This is of course not the normal way to use the circuit for computation.
• It is intended for exhibiting the way a logical circuit operates and for debugging.
• The straightforward way to compute the output of a circuit is quite simple
• In Python it is very easy to turn an object to also serve as a function ("callable object").
• The circuit handle mux2 serves as a function too!
• So in one line  o = mux2(a) we perform all the follwoing things
1. Initialize the circuit input gates with the assignment a.
2. Perform the step() method repeatedly in order to reach the output gates .
3. Return the circuit output via the get method.
• In some circumstances, we may have to initialize the input gates one by one.
• In such cases we can use the run() method for performing the computation, and the use the get() method for obtaining the circuit output
• A shorter way to do the same thing is:
• The output is also a Python dictionary which maps the circuit output names to their repective values.
• In our example, the circuit has only on output 'y'.
• Thus, we get a one key assignment y=0.
• The circuit object can also be used as a function for performing the circuit action.
• So instead of running three different commands
mux2.set(a)
mux2.run()
o = mux2.get()

We only need one command
o = mux2(a)

• However, in some cases we need to go step by step, so we need to go a little bit slower.
• For example, in order to observe intermediate values of the logic gates, we may need to initialize the circuit with the set method and proceed to the output step by step.

## Assignment Iterator¶

• The Assign.iter iterator is very useful for producing all possible outcomes of a given circuit.
• Here is a code for producing all the outcomes of the circuit HAM
• Here is a code snippet for generating the truth table for the cell HAM.
• Two circuits are called equivalent if they have the same boolean function.
• Here is a PyCirc code for implementing an algorithm that checks if two circuits are equivalent.
• In our cell library we have two definitions of the cell MUX2
• Having two definitions for the same cell is not a good idea, but in some circumstances is required for testing and research.
• In such cases it is a must that we have a tool for checking that indeed these definitions are equivalent! Or else we're in deep trouble ...
• This is where we use our is_equiv utility:

## 8x1 Multiplexer Design¶

• Now we build an 8x1 Multiplexer circuit (aka MUX3) by using our MUX2 circuit as one of its building blocks (two instance of MUX2 are needed).
• We also need one instance of 2x1 Multiplexer (aka MUX1), which we leave to the student as an easy exercise.

• As you can see from the diagram we now have 8 inputs bits: x0, x1, x2, x3, x4, x5, x6, x7,
and one output bit: y.
• We only need 3 logic gates: g1, g2, and g3.
• g1 and g2 are two instances of MUX2,
• g3 is an instance of MUX1.
• Here is the code for creating a PyCirc MUX3 object.
• Notice that this time we are using compressed notation technique for creating the 11 input gates in one line!
• The compressed notation can be used everywhere in PyCirc and saves a lot of typing!
• Note that this is the python code. Within a circuit file you must remove the openning Define and closing EndDef commands!
• This is quite verbose and not too helpful except for debugging.
• We can extract a more focused output with code like this.

• Here is a simple design for 3 bits adder with carry in (cin) and carry out (cout) bits

• This circuit acceps three types of input
• two binary numbers: $(a_2,a_1,a_0)$, $(b_2,b_1,b_0)$
• a carry in bit: cin.
• Its output $(y_2,y_1,y_0)$ is the binary sum of the two numbers (with the carry added).
• In case of addition overflow, we need a carry out (cout) output bit as well.
• The following PyCirc code is the PyCirc model for the ADDER3 cell.
• This is the compressed version of this code.
• The number of lines was reduced by half! (from 46 to 23).
• Lets test our adder by verifying that
011+011=110

• Here is a simple PyCirc Design Diagram for the standard 9-bits adder with a carry in (cin) and carry out (cout) bits.
• It uses 3 gates g1, g2, g3, of type ADDER3 which are chained by their cout/cin pins.
• Input: a<8:0> + b<8:0> + cin
• Output: y<8:0> + cout

• Here is a compressed PyCirc code for ADDER9:
• The full_run utility can be used for traversing all input/output pairs.
• Interactively.
• To stop: press "q".
• However this is not readable and takes forever!
• Number of inputs is 17 bits, so the loop has $2^{17} = 131072$ cycles!
• We will better off running only random assignments, and verify them manually.
• The pycirchdl package has the following utility for creating a random assignment.
def random_assignment(names):
names = expand(names)
a = Assign(names)
for x in names:
a[x] = randint(0,1)
return a

• Here are 5 random samples on our ADDER3 input
• Now we can generate random inputs for ADDER9, get the output, and verify its correctness.

• A half adder is a the same thing except that it does not have carry bits.
• It simply adds its two input bits and outputs the sum.
• In general, an n-bits half adder has $2n$ inputs bits "a0:n-1" + "b<0:n-1" and $n+1$ output bits "y0:n".
• We will use our ADDER3 cell to build a half 2-bits adder
• by injecting a zero constant to its cin input,
• and discarding its cout output bit.
• We will also use this opportunity to present a different style for creating a PyCirc cell.
• Notice that in this case, we do not have a Define(), EndDef() calls!
• We simply define a list of gates and a list of wires, and later use the class PyCirc to create the cell.
• The wires are created by the low level Wire class instead of the higher level WIRE function.
• The Wire class is suited for generating a single wire object while WIERE generates multiple wires and supports compressed names.
• An output pin which is not connected to any other pin is called a dangling pin.
• Notice that our 2-bits half adder ADDER2 has a dangling pin.
• The carry out bit (cout) of the gate ad3 is dangling.
• Also notice the constant gate g0 which fixes the carry in (cin) to a constant 0 value.
• A "dangling output" allert means that an output is not connected to anything.

• This is OK if you intended it as we did in this example.
• To achieve a half adder we had to discard the cout pin of ADDER3.

## Boolean Operators¶

• The PyCircHDL package contains a logops module which defines a small set of boolean functions.
• These functions are also called boolean operators since
• they act on a variable number of boolean values.
• return a boolean value.
• They are denoted with a first capital letter.
• These operators are needed for designing black box cells that play an important role in the VLSI design process.
• Here are a few examples of operators we have in the pycirchdl package:
• And, Or, Nor, Xor, Not, Mux.
• They all accept an Assign object as an argument,
• and return an Assign object.
def And(a, output="y"):
o = Assign(output)
for x in a:
if a[x] == 0:
for y in o: o[y] = 0
return o
for y in o: o[y] = 1
return o

def Or(a, output="y"):
o = Assign(output)
for x in a:
if a[x] == 1:
for y in o: o[y] = 1
return o
for y in o: o[y] = 0
return o

def Nor(a, output="y"):
o = Assign(output)
o1 = OR(a, Assign("y"))
b = Assign("x", o1["y"])
return NOT(b, o)

def Xor(a, output="y"):
o = Assign(output)
if sum(a[x] for x in a) == 1:
for y in o: o[y] = 1
return o
else:
for y in o: o[y] = 0
return o

• Users can easily add more operators in client code.
• Here are two examples that we used in the development of pycirchdl for testing the various 1-bit counter cells and the magnitude comparator cells.
• The first operator is simply a Python code for counting how many 1-bits a given assigmnet object a has?
• Note that the result is in numerical binary form: 011.
• The input "01011" has 3 occurrences of the bit 1, which in numerical binary form is 11.
• Here, the input "11101011" contains 6 bits of 1.
• As expected, the output is 110 which is a numerical binary representation of decimal 6.
• The following operators do not accept any input but produces a constant output.
• These are the constant operators.
• The Zero operator produces 0.
• The One operator produces 1.
• Here are the definitions of the ZERO and ONE operators
def Zero(a=None, output="y"):
o = Assign(output)
for y in o: o[y] = 0
return o

def One(a=None, output="y"):
o = Assign(output)
for y in o: o[y] = 1
return o

• The Magnitude Comparator operator acts on even length assignments only.
• It splits the input to two equal length binary numbers and compares their magnitude.

## Creating Cells with the Cell Class¶

• VLSI design usually means designing very large logic circuits consisting of millions and even billions of elements.
• This is achieved by dividing the main circuit to a dozen or so sub-circuits, usually called sections.
• Each section is divided to a set of smaller cells, usually called blocks.
• Each block is partitioned to smaller cells, usually called functional unit blocks (or fubs for short).
• And finally, each functional unit is partitioned to smaller cells taken from established cell libraries.
• This design method is usually called Hierarchical Design.
• For example, here is a high level hierarchical view of Intel's Skylake cpu

• It consists of roughly 5 billion transistors and 20 billion wires.
• In the early design phase, only a small number of the cell designs are available.
• Many other cells are replaced by "black boxes" that simulate cells, but their full circuit design is postponed to a later stage after passing many fire tests that verify the feasibility of the overall design.
• These simulation tests determine many of the desired properties and parameters for these cells, and thus provide a lot of information and clues needed to design them efficiently.
• In such scenarios, a Logic Cell is viewed as a "black box" with entry and exit points that can be defined more conveniently by means of simple numerical software algorithms.
• PyCircHDL provides a high level Cell class for defining such cells.
• So we have two ways to define a new cell:
1. By defining a PyCirc circuit as in the examples above
2. By defining a boolean operator numerically.
• A cell defined by an operator is named black box or box for short.
• It is a place holder for a real circuit which is not yet available, but the overall design need to be tested before it is decided if its design is feasible.
• Here is an example of a 4x3 cell that is defined by the Cell class.
• It accepts a 4-bits assignment and outputs a 3-bits assignment.
• It counts the number of 1-bits in the input.
• The operator COUNT_ONES was defined in the section above.
• We can now use count3 as a cell type and use it to define more complex cells.
• As an example, let's define a COUNT6 circuit by using two gates of type COUNT3.

• The plan is straightforward:
• COUNT6 accepts 6 input bits: x1, x2, x3, x4, x5, x6.
• The first 3 bits x1, x2, x3, are assigned to the gate c1 whose type is COUNT3.
• The last 3 bits x4, x5, x6, are assigned to the gate c2 whose type is also COUNT3.
• The outputs of c1 and c2 are sent to gate a which is an ADDER2 cell.
• The gate a is adding up the two numbers and sends its output to y1, y2, y3.
• Here is the PyCircHDL Program for the COUNT6 circuit.
• Here is the Python code for adding some of the box cells in PyCircHDL.
• See the factory module in the PyCircHDL package for more examples.

pycircLib.add_box(name="and2", operator=And,  input="x<1:2>", output=["y"])

• We can also use Python loops to add box cells to our cell library.

• The following Python loop, adds 32 box cellls to the PyCirc cell library

• 8 OR box cells: OR2, OR3, ..., OR8
• 8 XOR box cells: XOR2, XOR3, ..., XOR8
• 8 NOR box cells: NOR2, NOR3, ..., NOR8
for k in range(2,9):
inp = "x<1:%s>" % (k,)
name = "or" + str(k)
pycircLib.add_box(name, operator=Nand, input=inp, output=["y"])