Presentation for beginnersΒΆ
What is PyGears ?
PyGears is a hardware description language (HDL) implemented as a Python
library focused on:
Functional programming
Module composition
Synchronization
2
Whatβs so functional about it ?
Function Module Gear
3
Whatβs so functional about it ?
Ζ
1
= A + B
Ζ
2
= A * C
Ζ
3
= Ζ
2
Ζ
1
= (A + B) * C
@gear
def add_and_mul (a, b, c):
return add(a, b) | mult(c)
@gear
def add (a, b):
return a + b
@gear
def mult (a, c):
return a * c
4
Is composition really that easy in PyGears ?
Mandatory flow control makes interfacing modules look less like signal wave
generation and more like passing data to function.
The main idea behind standardized interfaces is to provide easy composition of
the modules. Modules (gears) try to push this standardization all the way down
to the basic building blocks like: counters, MUXs and FIFOs.
@gear
def f4(input):
return input | f1 | f2 | f3
5
Why is synchronization important in PyGears ? - 1
This interface is also known as DTI (Data Transfer Interface), simple
synchronous, flow-controlled interface.
DTI connects two gears, one which sends the data and the other one which
receives it, called Producer and Consumer respectively.
Clock is implicitly defined, itβs globally synchronous and user donβt have to worry about
adding it, compiler will take care of it
6
Why is synchronization important in PyGears ? - 2
The main idea behind standardized interfaces is to provide easy composition of the modules. These
interfaces: AXI, Avalon, etc., have been used so far to compose large modules written in RTL called
IPs, and they are popular for developing SoCs (System on chip). Gears tries to push this
standardization all the way down to the basic building blocks like: counters, MUXs and FIFOs.
Gears proposes the use of a single interface type for gear communication, called DTI (Data Transfer
Interface), throughout the design. Interface connects two gears, one which sends the data and the
other one which receives it, called Producer and Consumer respectively. This interface DTI is a simple
synchronous, flow-controlled interface, somewhat similar to AXI4-Stream, consisting of the following
three signals:
β Data - Variable width signal, driven by the Producer, which carries the actual data.
β Valid - Single bit wide signal, driven by the Producer, which signals when valid data is available
on Data signal.
β Ready - Single bit wide signal, driven by the Consumer, which signals when the data provided by
the Producer has been consumed.
7
Ways to create function = module = gear
8
PyGears development flow
Generated HDL can be used furthermore for ASIC of FPGA depending on the
user needs.
9
What does it look like
Point = Tuple[Uint, Uint]
@gear
def foo(p: Point, x: Uint, y: Uint):
x_tr = p[0] + x
y_tr = p[1] + y
return ccat(x_tr, y_tr)
10
SPLIT - Gear that is automatically instantiated by PyGears compiler, main purpose is to split tuple, this is just simple rewiring
CCAT - Added at the end by user to concatenate two values back to Tuple
@gear
def add_sub(a: Uint[4], b: Uint[4], *, add_switch: bool):
if add_switch:
c = a + b
else:
c = a - b
return c
Quick syntax intro
Body
Input ports Parameter
,*
- sign is used to separate parameters from the input interfaces
- Output port is implicit and itβs returned by instantiation of a module
- In case of + output will be Uint[5] while in case of - output will be Int[5]
11
Design & Testbench
@gear
def add_sub(a: Uint[4], b: Uint[4], *, add_switch: bool):
if add_switch:
c = a + b
else:
c = a - b
return c
def testbench():
res_list = []
drv_a = drv(t=Uint[4], seq=[4, 2])
drv_b = drv(t=Uint[4], seq=[2, 1])
add_sub(drv_a, drv_b, add_switch=False) \
| collect(result=res_list)
sim()
print(res_list)
Design
Testbench
12
Example of FIR Direct
13
Example of FIR Direct
@gear
def fir_direct(din, *, b):
temp = din
add_s = temp * b[0]
for coef in b[1:]:
temp = temp | dreg(init=0)
add_s = add_s + (temp * coef)
return add_s | qround(fract=din.dtype.fract) | saturate(t=din.dtype)
14
PyGears flow top-level down - 1
def foo(din):
for i in range(N):
dout[i] = bar(din[i])
Algorithm in pure python
Parallelizable code
@gear
def foo(din):
dout = din | arraymap(f=bar)
Algorithm in PyGears
Parallel workers
1. Sequential algorithm analysis
2. Breaking down the algorithm into high
level parallel workers
15
We analyse the algorithm we want to implement in HW. We are
identifying the code which can be easily parallelised in HW.
We are designing the architecture of the top level module. It consists of a
number of sub-modules written in high level language which do not have
to be compilable to HDL.
It is important that every module is functionally correct and follows the
DTI interface rules. Designer is free to use various Python constructs such
as lists or libraries such as numpy to implement the functionality.
PyGears flow top-level down - 2
@gear
def bar(din):
return din \
| bar_init \
| arraymap(f=bar_par) \
| bar_post
Algorithm in PyGears
3. Incremental breaking down of
parallel workers to sub-workers
@gear
def foo(din):
dout = din | arraymap(f=bar)
Algorithm in PyGears
Parallel workers
2. Breaking down the algorithm into high
level parallel workers
16
Each sub-module is now broken down into several simpler ones if necessary. This step is repeated until all modules are compilable to HDL.
Overview
β Syntax introduction
β Modeling concepts
β Dataflow model
β HLS model
β Generating HDL
β Simulation and testing
β Anari Web SimViewer
17
Syntax introduction
Data types
&
Operators
18
Assignment
Instantiation + assignment
This will instantiate addition gear and assign its output interface to variable c
c = a + b
c = Intf(din.dtype)# Explicit instantiation of an DTI interface
c |= a_out2 # Assignment to the interface
Interface assignment has its own syntax for assignment |=
Interface assignment
19
@gear # Gear decorator
def dut ( # Module name
din: Uint # Input port name and Type
): # Output port is implicitly created
return din + 2 # Body
How to create module - Dataflow model example - 1
@gear
def dut(din: Uint[9]) -> Uint[10]:
return din + 2
Output type can be also declared explicitly, but it is important to note that
Output port always has default name dout
Syntax explanation
PyGears implicitly increase the bit width of the data. Compiler doesnβt allow
connections of interfaces of different data types, user has to take care of it
through rounding, truncation or saturation.
20
How to create module - Dataflow model example - 2
When user defines a gear it does not need to specify exact data type. It can be defined when
instantiating a gear. In this case the dut accepts any Uint type.
As for the output types similarly user does not need to define output type, if however specific output
type is defined than type check is being done by Pygears and appropriate error is provided
(egg. TypeError: [0], Cannot convert type 'Uint[11]' to 'Uint[10]').
Pygears does not do automatic type conversion so user must do the conversion using already
available gears (trunc, saturate, code .. ) or itβs own custom functions.
Arithmetical operations in PyGears implicitly increase the bit width of the data - user must do
additional steps to make sure that data is compatible with output type. More info on following slides
(Pygears Operators)
21
@gear
async def dut(din: Array,*, param) -> Queue['din[0]']:
i = Uint[bitw(len(din.dtype) - 1)](0)
async with din as val:
async for i, last in qrange(len(din.dtype)):
yield val[i], last
How to create module - HLS model example - 1
Parameters Output as Queue
Body
22
How to create module - HLS model example - 2
- Basic building block is a PyGears function
- Decorated with @gear
- Followed by
- keyword def (or async def )
- Module name
- Input ports and parameters in round brackets
- Output ports after -> and
- Body
- Input ports are defined by its name and type (can be omitted and decided in compiler time
- Output ports are defined only by its type, while the names are implicitly set as βdoutβ (names can be
defined separately with additional options)
* in the simple module example output port is implicitly created based on the moduleβs body
Input ports are defined by its name and type (can be omitted and decided in compiler time
Output ports are defined only by its type, while the names are implicitly set as βdoutβ (names can be defined
separately with additional options)
How to instantiate module
- Module is instantiated by calling its name and providing it with input interfaces and
compile-time parameters.
- Instantiation of a module returns its output interfaces, which in case of this
example is a single interface
- Output interfaces have to be assigned to variable or connected to another
interface.
Point = Tuple[Uint, Uint]
@gear
def foo(p: Point, x: Uint, y: Uint):
x_tr = p[0] + x
y_tr = p[1] + y
return ccat(x_tr, y_tr)
instantion of ccat with two interfaces connected as input and one output
interface
24
How to instantiate module
Instantiation of a module is done by simply calling itβs function which is defined with @gear decorator
Since modules will have input interfaces it when instantiating it we need to provide it with some input.
β In the simple module instance example we provide the βdutβ with βdinβ which should be of βIntfβ
pygears type, also βdoutβ will be βInftβ type variable βdinβ and βdoutβ can be later used to connect to
other βgearβ modules as part of design
β In the second code example βccatβ module is instantiated as part of another module definition.
Return type of the βtranslateβ module will be return type of the βccatβ module instance. Again
since βccatβ accepts two input DTI interfaces, they were instantiated as βx_trβ and βy_trβ and
forwarded as input to the βccatβ module
Important: All output interfaces of module are returned by instantiation of a module, which means
that module can have multiple output DTIs at the same time
25
How to instantiate module - 1
- PyGears will maintain a hierarchy of the instantiated gears
- Module instance gets the name of the gear function used to describe it
dout = add_sub(i1,i2, name="my_dut")
To supply a custom name via use built in gear
parameter name
seq_a = [4, 2]
seq_b = [2, 1]
drv_a = drv(t=Uint[4], seq=seq_a)
drv_b = drv(t=Uint[4], seq=seq_b)
add_sub(drv_a, drv_b, add_switch=True) \
| collect(result=res_list)
26
How to instantiate module - 2
If there are multiple instances they will be suffixed with indexing
Instances in the hierarchy can be accessed by the path string
where root β/β is auto-generated container for all the top gear instances
_________________________________________________________
Multiple modules instantiated and connected in two statements
β βadderβ, βdrvβ two times and βcollectβ
β βdrvβ and βcollectβ are helper module for testing
β βdrvβ does not have input interface, only parameters are passed
β βcollectβ does not have output interface
_________________________________________________________
*name parameter is added by the @gear decorator and need not be supplied in the function
signature
How to parametrize module
- Parameters can have default values in which case itβs not obligatory to define
them when module is instantiated
- Parameters are resolved in compile time and can be used in the module body
- Parameters can be of different types: constants, classes, functions β¦
@gear
def dut(din: Array, *, param_1=5, param_2) -> Queue['din[0]']:
@gear # Defining module and its parameter
def add_sub(a: Uint[4], b: Uint[4], *, add_switch: bool):
# Instantiating module and providing inputs and parameter that is TRUE
add_sub(Uint[4](2), Uint[4](3), add_switch=True)
Example
28
Pipe operator - 1
- For connecting gears a βpipeβ - β|β operator can be used
- to pass the output of one function as an input interface the next one
- to easily connect them in a single code line
@gear
def dut(din):
return din | module_B | module_A
drv(t=Uint[8], seq=sequence) \
| dut \
| collect(result=res)
- For convenience additional βbackslashβ β\β
operator can be used for better visibility
when instantiating and connecting gears
Pipe operator shall be used only in dataflow model
29
Pipe operator - 2
@gear
def module_a(i1, i2):
return i1 + i2
@gear
def top(din1, din2):
return din1 | module_a(i2=din2) | module_b
β Can be used in chain as long as each gear has single output
β In case where gear has multiple inputs, pipe has to be combined with either
ordered or named method
β For ordered method pipe operator will always connect with the last
input in order, so named method is recommended here as well
@gear
def module_b(i1):
return i1 + 1
30
Pipe operator - 3
Pipe operator β|β
β Is used to connect gears
β Reveals dataflow in code
β Can be used in chain as long as each gear has single output
β In case where gear has multiple inputs pipe has to be combined with either ordered or named
method
β For ordered method pipe operator will always connect with the last input in order, so
named method is recommended here as well
β We can pass only one interface with the pipe operator, so if a module has more than one
input interface, one can be passed with the pipe operator and the rest with either
oredered or named method. In this example module_a has two input interfaces so in
top_pipe din1 is passed to module_a i1 port with the pipe operator, while din2 was
passed to module_a i2 port with named method.
31
Register
- DTI registers can be manually inserted in Dataflow modules by instantiating
specific gear dreg
- Registers are automatically inferred in HLS modules where needed
# Inserting register
@gear
def dut(din):
return din | dreg
# Automatically inferring register
@gear
async def sum(din: Queue[Uint]) -> b'din':
acc = din.dtype.data(0)
async for d, eot in din:
acc = d + acc
if eot:
yield acc, eot
HLS: Since acc needs to hold and update its value across
multiple clock cycles, a register is automatically generated.
* bβdinβ -> output will be the same as βdinβ (input interface type)
Dataflow
32
- 2 types:
- Input interfaces are passed as arguments to the gear
- No type needs to be defined
- Output interfaces are passed as results of calling the gear
- Type needs to be defined, but can reference input interface types
or parameters
- In effect, all types need to be known at compile time but they donβt need to
be specified in the gear definition
- This allows maximum gear reuse and parameterization
Types of ports
# Module with 1 input port: din, and 1 output port of Queue type
@gear
def dut(din: Array, *, param_1=5, param_2) -> Queue['din[0]']:
input
output
33
Data Types
34
Data Types
3 groups of data types in PyGears:
Basic Parallel Structures Serial Structures
Uint
Int
Fixp
Bool
Tuple
Array
Union
Queue
35
Float type is supported in Python space, while when we are working with gears we should stick to PyGears typing since
PyGears compiler doesnβt know how to convert Float into basic types shown above
Data Types - Basic - 1
Uint[<bit_width>](<initial_value>)
Fixp[<integer_bits>, <bit_width>](<initial_value>)
#Integer
Uint[8](5)
Int[8](-11)
#Fixed point
Ufixp[5, 8](1.5)
Fixp[5, 8](-1.5)
Syntax
#Bool
Bool(True)
Uint[1](1) #Also an option
Initial value is optional
36
Data Types - Basic - 2
- Uint, Int:
- Unsigned (Uint) and signed (Int) integer types
- Constructor parameter specifies the bit width
- Constructor argument could specify the initial value as in the Int example
- Ufixp, Fixp:
- Unsigned (Ufixp) and signed (Fixp) fixed point type
- First constructor parameter specifies the number of bits used for integer part representation
while the second constructor parameter specifies the total bit width
- Constructor argument could specify the initial value as in the Fixp example
- Bool:
- Boolean value type
- 1 bit integer can be used instead
Data Types - Parallel Structures - 1
Array[ Uint[8], 4 ]
Tuple[Uint[8], Int[8], Fixp[5, 8]]
38
Data Types - Parallel Structures - 2
- Union:
- Generic type structure that only act as different views for
the underlying data
- Allows the user to read the same data in different formats,
called subtypes
- Useful when different data types go through the same
data bus
- Under the hood, it consists of data signal and ctrl signal
which indicates how will data be observed
Union[
Uint[16],
Tuple[Uint[7],
Uint[9]]]
17 bits in total (16 bits for data and 1 bit for ctrl). If ctrl is 0, data will be observed as
Uint[16] type, and if ctrl is 1, data will be observed as Tuple[Uint[7], Uint[9]] type.
39
Data Types - Parallel Structures - 3
- Array:
- Represents a generic homogeneous container type
- Base type can be any type, including another array
- Replicates the base type that is specified as the first constructor parameter
- The number of times that the base is replicated is specified as the second constructor
parameter
- Tuple:
- Represents a generic heterogeneous container type
- Contains any number of any combination of any types, even other tuples
- All of the containing types must be specified individually
Data Types - Parallel Structures - 4
result = []
seq = [(2,0),(2,1)]
@gear
def module(din: Union[Fixp[3,4], Uint[4]]):
return din
drv(t=Union[Fixp[3,4], Uint[4]],seq=seq) \
| module \
| collect(result=result)
sim()
print(bin(result[0].data))
print(bin(result[1].data))
Data Types - Serial Structures
- Queue:
- An array type of unknown length that
describes a transaction and spans
multiple cycles
- Transaction data can be of any type
- Data field carries the transaction data
and the End of Transaction (eot) field
marks the end of a Queue transaction
- βlvlβ parameter determines the number
of bits used for eot signal (if omitted, it
is one by default)
- More levels of eot can mark
transactions within larger transactions
Queue[Uint[8], βlvlβ]
42
Queue - EOT lv 2
43
Level parameter is useful in cases where we want to track multiple
end of transactions. For example if we are sending picture for
processing that is 4px X 4px, and we are sending pixel by pixel. We
can implement 2 EOTs, one for end of Row and second for end of
Column which is in this case last row. So, in this case EOT_1 will be
triggered every 4 cycles while EOR_2 will trigger at the last row and
will be HIGH until EOT_1 becomes HIGH alse, which means last row
is sent fully.
Operators
44
PyGears Operators
Binary
β Multiplication β *
β Add β +
β Subtract β -
β Modulus β %
β Divide β //
Unary
β Negative β -
@gear
def binary_example(a: Int, b: Int):
return a + b
@gear
def unary_example(din: Int):
return -din
Example
Example
Arithmetic operators
45
PyGears Operators
Equality operators
Compare two operands bit by bit, with zero/one left padding if the operands are
of unequal length. Return value is of Bool type.
Expression Possible values
a == b {Bool(0), Bool(1)}
a != b {Bool(0), Bool(1)}
a == b a != b
a = Uint[8](4), b = Int[4](4)
Bool(1) Bool(0)
a = Uint[8](15), b = Int[4](-1)
Bool(0) Bool(1)
Operands can be of different types
46
PyGears Operators
Relational operators
Compare two operands bit by bit, with zero/one left padding if the operands
are of unequal length. Return value is of Bool type.
Expressions a < b a <= b a > b a >=b
a < b a <= b a > b a >= b
a = Uint[8](4), b = Int[4](4)
Bool(0) Bool(1) Bool(0) Bool(1)
a = Uint[8](15), b = Int[4](-1)
Bool(0) Bool(0) Bool(1) Bool(1)
Operands can be of different types.
47
PyGears Operators
Bitwise operator
Bitwisely compute logics in parallel. Multi-bit result.
β AND β &
β OR β /
β XOR β ^
β NEG β ~
Example of bitwise and operator:
@gear
def bitwise_and_example(a: Int, b: Int):
return a & b
48
PyGears Operators
Shift operator
Shift to the right or left by a specified number of bits.
β Right shift β >>
β Left shift β <<
Example of right shift:
@gear
def shift_right(din: Int, *, shift_num):
return din >> shift_num
During shift operation, the vacant bit positions are filled with zeros if number is positive, or ones if
number is negative.
a = Int[4](-4) (1100)
2
a >> 2
>>> Int[2](-1) (11)
2
a << 2
>>> Int[6](-16) (110000)
2
b = Int[4](4) (0100)
2
b >> 2
>>> Int[2](1) (01)
2
b << 2
>>> Int[6](16) (010000)
2
49
PyGears Operators
Concatenation operator
Concatenation operator join up variables together and returns the result as one
variable.
Example of concatenation:
@gear
def concat(a: Int, b: Int):
return a @ b
c = a @ b
print(c)
>>> Uint[8](35) (00100011)
2
a = Uint[4](2) (0010)
2
b = Uint[4](3) (0011)
2
Concatenation is supported only for Uint and Bool types
50
PyGears Operators
Sign extension
Numbers in PyGears can be unsigned or signed. Sign extension is the operation
of increasing the number of bits while preserving the original operandβs sign and
value.
>>> a = Int[5](-8)
>>> a >> 2
Int[3](-2)
>>> a = Int[5](-8)
>>> trunc(a >> 2, t=Int[5])
Int[5](-2)
If we want to extend this back to 5 bits we shall use trunc
Wrong Correct
51
PyGears Operators
Limitation of number of bits
Arithmetical operations in PyGears implicitly increase the bit width of the data.
Compiler doenβt allow connections of interfaces of different data types, user has
to take care of keeping the desired bit width.
>>> a = Uint[8](10)
>>> a + 2
Uint[9](12)
>>> b = Uint[8](255)
>>> b + 2
Uint[9](257)
Default behavior
52
PyGears Operators
Limitation of number of bits: How to fix it
>>> a = Uint[8](10)
>>> trunc(a + 2, t=Uint[8])
Uint[8](12)
>>> b = Uint[8](255)
>>> trunc(b + 2, t=Uint[8])
Uint[8](1)
Truncation
>>> a = Uint[8](10)
>>> saturate(a + 2, t=Uint[8])
Uint[8](12)
>>> b = Uint[8](255)
>>> saturate(b + 2, t=Uint[8])
Uint[8](255)
Saturation
>>> a = Ufixp[4, 8](0.5625)
>>> qround(c)
Uint[5](1)
>>> qround(c, fract=1)
Ufixp[5, 6](0.5)
Rounding
IMPORTANT: trunc, saturate and qround
should be imported from pygears.lib in
dataflow gears and pygears.typing in HLS
gears.
53
PyGears Operators
Operators precedence
Unary arithmetic
Binary arithmetic
Binary arithmetic
Shift operators
Relational operators
Equality operators
Logical operators
Assign operator
54
Modeling concepts
Dataflow model
HLS model
Standard PyGears Library
55
Dataflow model
56
Connection of modules by passing arguments
@gear
def module_a(i1, i2):
return i1 + i2
@gear
def module_b(i1):
return i1 + 1
Gears can be connected by passing the appropriate interfaces as arguments during gear
instantiation. BY NAME approach is recommended as it leaves less room for mistakes
@gear
def top_ordered(din1, din2):
s1 = module_a(din1, din2)
return module_b(s1)
ORDERED
@gear
def top_named(din1, din2):
s1 = module_a(i1=din1, i2=din2)
return module_b(i1=s1)
BY NAME
57
Connection of modules by making feedback loop - 1
β Gear instantiation generates DTI interfaces for each output port of the gear,
they are connected to input ports of other gears
β If we want a feedback loop, we need to explicitly instantiate an interface
which will serve as the feedback loop
@gear
def module_c(i1, i2):
return i1 + 1, i2 + 2
@gear
def top(din):
# Explicit instantiation of an interface
feedback = Intf(din.dtype)
a_out1, a_out2 = module_c(din, feedback)
# Assignment to the interface
feedback |= a_out2
return a_out1
58
Connection of modules by making feedback loop - 2
Adding a feedback loop carries a danger of creating a combinational (algebraic) loop. For example, if
module_c has combinational path i2 -> a_out2 and we create a feedback without any register on its
path, the combinational loop will be created. PyGears does not allow combinational loops and will
report an error if it occurs.
To break combinational loops, user should insert register on the feedback path.
β dreg is not suitable for loop breaking as it does not break ready signal (explained on slide 60)
β decouple breaks data, valid and ready signals so it should be used to break loops
β pipeline with feedback parameter set to True can also be used to break loops
β pipeline(length=8, feedback=True) creates a chain of one decouple and 7
dreg gears
β decouple breaks the loop
β Total delay is 8 clock cycles
59
Connection of modules by making feedback loop - 3
Previous example with decouple gear inserted to break the loop:
@gear
def top(din):
# Explicit instantiation of an interface
feedback = Intf(din.dtype)
a_out1, a_out2 = module_c(din, feedback)
# Assignment to the interface
feedback |= a_out2 | decouple
return a_out1
60
Functors
In functional programming, a functor is a design pattern inspired by the
definition from category theory, that allows for a generic type to apply a function
inside without changing the structure of the generic type.
Benefits of functors in PyGears
β Powerful patterns for gear composition
β Improve possibilities for gear reuse
β There is one functor for each complex data type (parallel and serial)
β Functors allow for gears that operate on simpler data types to be used in
context where a more complex data type is needed.
61
Functors - Tuple
Tuple functors are useful in context where we need to operate on Tuples of some
data types, and we already have gears that implement desired transformation
but operate on data types that are individual fields of the Tuple.
producer | tuplemap(f=[g0, g1]) | consumer
Note: g0 & g1 are also gears
62
Functors - Array
Array functor works in a similar manner as tuple functor. The difference is that
array functor consists of identical gears which operate on individual fields of an
array.
producer | arraymap(f=g) | consumer
Note: g is also a gear
63
Functors - Union
Union functors are useful in context where we need to operate on Unions of some data types, and
we already have gears that implement desired transformation but operate on data types that are
part of the Union. ctrl signal selects the output where demux will output data and from which input
will mux take data. It is part of Union type.
Note: g0 & g1 are also gears
producer | unionmap(f=[g0,g1]) | consumer
64
Functors - Queue
Queue functors are useful in context where we need to operate on Queues of
some data types, and we already have gears that implement desired
transformation but operate on single data or lower level Queues.
producer | queuemap(f=[g]) | consumer
Note: g is also a gear
65
Conditional statements
PyGears uses conditional statements in dataflow modeling to conditionally
instantiate modules inside a gear of a higher level during compilation.
@gear
def conditional_example(din, *, param: bool):
if param:
return din + 1
else:
return din - 1
Variables in conditional statements are evaluated at compile-time and the gear
will be instantiated differently for different values of conditional expressions.
66
Loops
Loops are used in dataflow modeling in a similar manner as conditional
statements. Loops are unrolled during compilation and gears which are
instantiated inside loop body will be instantiated multiple times in a way
specified by the designer.
@gear
def simple_gear(a):
return a + 1
@gear
def for_example(a, *, n):
inputs = [a]
for i in range(n):
dout = inputs.pop(0) | simple_gear
inputs.append(dout)
return inputs.pop(0)
67
The example is using Python list.
β inputs = [a] creates a list with a as its only
element.
β inputs.pop(0) returns and deletes an
element of the list with an index 0.
β inputs.append(dout) adds dout to the end
of the list .
Standard PyGears Library
68
Standard gears from library
mux(ctrl: Uint, *din) β Union:
ctrl = drv(t=Uint[2], seq=[0, 1, 2, 0, 1, 2])
a = drv(t=Uint[4], seq=[10, 11])
b = drv(t=Uint[5], seq=[20, 21])
c = drv(t=Uint[6], seq=[30, 31])
mux(ctrl, a, b, c) \
| check(ref=[(10, 0), (20, 1), (30, 2),
(11, 0), (21, 1), (31, 2)])
69
Uses the value received at the ctrl input to select
from which input interface the data should be
forwarded to the output. din is a tuple of
interfaces, and the data received on ctrl input is
used as an index to select the interface to
forward.
Output is a Union type which means it carries
both data and ctrl signals. If all mux inputs were
of the same type, data can be extracted using
union_collapse gear
Standard gears from library
demux(ctrl: Uint, din, *, fcat=ccat, nout=None):
ctrl = drv(t=Uint[2], seq=[0, 1, 2, 3])
outs = drv(t=Uint[4], seq=[10, 11, 12, 13]) \
| demux(ctrl)
outs[0] | check(ref=[10])
outs[1] | check(ref=[11])
outs[2] | check(ref=[12])
outs[3] | check(ref=[13])
70
Sends the data received at din to one of its
outputs designated by the value received
at ctrl. The number of outputs is either
determined by the value of the nout
parameter, or equals 2**len(ctrl) if nout is
not specified.
Standard gears from library
β Used for pipelining by breaking the combinatorial path in the forward
direction, i.e. by registering the data and valid signals of the DTI interface,
without sacrificing the throughput.
β There is still a combinatorial path between ready control signals from the
dreg() gears output to its input.
dreg(din) β din:
71
Standard gears from library
β Used to break combinatorial paths on both data and control signals of the
DTI interface, without sacrificing the throughput.
β Adds a latency of one clock cycle to the path, but does not impact the
throughput no matter the pattern in which the data is written and read from
it.
decouple(din, *, depth=2) β din:
72
Standard gears from library
wr_addr_data = drv(t=Tuple[Uint[2], Uint[3]],
seq=[(0, 0), (1, 2), (2, 4), (3, 6)])
rd_addr = drv(t=Uint[2], seq=[0, 1, 2, 3]) | delay(1)
rd_addr \
| sdp(wr_addr_data) \
| check(ref=[0, 2, 4, 6])
sdp(wr_addr_data: Tuple[{βaddrβ: Uint, βdataβ: Any}], rd_addr: Uint, *, depth) β
wr_addr_data[βdataβ]:
74
Standard gears from library
β Used to gate input.
β If the value of cond signal is True, din is propagated to output.
cond = drv(t=Uint[1], seq=[1, 1, 0, 1, 0, 0])
din = drv(t=Uint[4], seq=[1, 2, 3, 4, 5, 6])
din | when(cond=cond) | check(ref=[1, 2, 4])
when(cond: Bool, din: Any) β din:
75
Example: Karatsuba multiplication
K_mul is a recursive dataflow gear
that implements multiplication using
the Karatsuba method
This method splits 1x(n)-bit
multiplication to 3x(n/2)-bit
multiplications
This splitting is performed until the
base multiplication width is reached,
which in this case is 16,
multiplication is implemented using
the PyGears library multiplication
gear mul
Wikipedia pseudocode
from pygears import gear
from pygears.typing import Uint
from pygears.lib import mul
@gear
def k_mul(mul_a, mul_b, *, width):
if(width > 16):
new_width = width//2
mul_a_ls = mul_a[:new_width]
mul_a_ms = mul_a[new_width:]
mul_b_ls = mul_b[:new_width]
mul_b_ms = mul_b[new_width:]
z2 = k_mul(mul_a_ms, mul_b_ms, width=new_width)
z0 = k_mul(mul_a_ls, mul_b_ls, width=new_width)
z1 = k_mul(
(mul_a_ms+mul_a_ls),
(mul_b_ms+mul_b_ls),
width=new_width
) - z2 - z0
res = (z2 << width) + (z1 << new_width) + z0
else:
res = mul(mul_a, mul_b)
return res
77
HLS model
78
Dataflow vs HLS - 1
Dataflow model only specify
how the internal gears and
interfaces are connected,
consists of only existing gears
@gear
def dataflow_adder(
data1: Tuple[Uint[8], Uint[8]],
data2: Uint[8]
) -> Uint[10]:
return add(data1[0], data1[1]) | add(data2)
@gear
async def async_adder(
data01: Tuple[Uint[8], Uint[8]],
data2: Uint[8]
) -> Uint[10]:
async with data01 as (data0_value, data1_value):
async with data2 as data2_value:
yield data0_value + data1_value +
data2_value
Async gears are gears that
are compiled to HDL using
the HLS compiler
79
Dataflow vs HLS - 2
PyGears is focused on two tpyes of modeling
β Dataflow - This is primar way of doing things in PyGears
β HLS - Currently in development, goal is to support fully python in the future
HLS
β HLS in PyGears should be used for smallest modules, usually we start in head with big system then we split it in less
complex modules, eventually we will come to modules that are aveliable in Dataflow lib pygears.lib, in case there is
no gear we need, then we should use HLS to create one. In that way HLS is going to help by avoiding to write modules
in SystemVerilog etc.
β Connecting modules developed in HLS should be done through Dataflow methodology
β HLS module is the one where we write sequentially code and PyGears compiler will figurout parallelism, in case
where code is to complex there is a chance of getting compile Error. Currently there are no well defined limits thus
PyGears HLS for now is more like hit or miss. Future upodates are going to solve this issue.
β After once call of function is finished gear goes in local reset that initialize module on initial values user defined
inside, in other words at the end of function gear goes to rest state. For example, code on following slide runs
function 4 times (4 transactions) then goes to reset and set values for s_out0 and s_out1 to zeroes.
β Same apply for dataflow gears (from library) for example, if we are sending Queue, when EOT is raised module
goes to local reset and initialize itself again
80
HLS async βgearsβ - 1
- Async gear is a procedural block
that repeats endlessly
- Async gears are pure functions,
meaning no memory is shared
between repeat loops of the
procedural block
- Async gears:
- Async with
- Async for
- Yield
@gear
async def transform_ext_qbc(
q_in: Uint[8],
b_in: Uint[8],
c_in: Uint[8]
) -> Uint[8]:
s_out0 = Uint[8](0)
s_out1 = Uint[8](0)
for i in range(4):
async with q_in as q:
async with b_in as b:
s_out0 ^= b
s_out1 ^= q
async with b_in as b:
async with c_in as c:
s_out0 ^= c
s_out1 ^= b
yield s_out0
yield s_out1
Partial implementation of AES encryption
81
HLS async βgearsβ - 2
β Async gear can last multiple clock cycles
β Explanation of example:
β Module recives 3 inputs: Q, B, C
β First double async with waits for Q and B data to be VALID then it does XOR operation
with s_out0 and s_out1 (could also be written s_out0 = s_out0 ^ b)
β Second fouble async with waits for Q and B (THIS IS NEXT B) and do the same
β All of this is repeted 4 times
β Receive Q and B
β Receive C and B
β Put data at the output (raise VALID and wait READY by consumer)
β Do local reset of module
82
async with
- Used for accessing data from input
interface
- New variable name is assigned to
the data read from the interface
- Effectively, data access is done by
raising ready signal on the
interface and blocking the
execution until valid signal is set by
the producer
@gear
async def transform_ext_qbc(
q_in: Uint[8],
b_in: Uint[8],
c_in: Uint[8]
) -> Uint[8]:
s_out0 = Uint[8](0)
s_out1 = Uint[8](0)
for i in range(4):
async with q_in as q:
async with b_in as b:
s_out0 ^= b
s_out1 ^= q
async with b_in as b:
async with c_in as c:
s_out0 ^= c
s_out1 ^= b
yield s_out0
yield s_out1
Partial implementation of AES encryption
83
- Queue input data can be accessed
by using either async with or async
for
- Async for unpacks the Queue
interface into data and eot
variables
- Reading is done by raising ready
signal and blocking execution until
valid is set
- After the loop body is executed,
eot is checked for loop termination
async for
@gear
async def q_sdp_wr(
q: Queue
) -> Tuple[Uint[4], 'q.data']:
cnt = Uint[5](0)
async for c, eot in q:
yield trunc(cnt, Uint[4]), c
cnt += 1
84
yield
- After processing is done the
output data needs to be converted
to the interface type to be able to
connect to other gears that expect
interface type arguments
- Yield converts the data to a
corresponding interface type and
outputs it as a return value of the
async gear
- Yield command blocks execution
(raises valid = 1) and continues
only when data is received (waits
also for ready=1)
@gear
async def q_sdp_wr(
q: Queue
) -> Tuple[Uint[4], 'q.data']:
cnt = Uint[5](0)
async for c, eot in q:
yield trunc(cnt, Uint[4]), c
cnt += 1
85
- Reading data from input interfaces requires no clock cycles all interfaces
read can be accessed in parallel
- If data is read from the same interface again a clock cycle is added to
accommodate the reception of the first transaction
- Likewise, there can be only one Yield per clock cycle
- Otherwise no additional clock cycles are added
- await clk() can be used to add clock cycles in the required spot in the
procedural execution of the block
Latency in HLS
86
Example - Speed controller FSM
class
State(IntEnum):
STOP = 0,
LOW = 1,
MED = 2,
HIGH = 3
@gear
async def speed_controller(a: Uint[1], b: Uint[1]) -> Uint[2]:
state_cur = Uint[2](State.STOP)
while True:
async with a as acc:
async with b as br:
if br:
if state_cur == State.STOP:
state_cur = State.STOP
elif state_cur == State.LOW:
state_cur = State.STOP
elif state_cur == State.MED:
state_cur = State.LOW
elif state_cur == State.HIGH:
state_cur = State.MED
elif acc:
if state_cur == State.STOP:
state_cur = State.LOW
elif state_cur == State.LOW:
state_cur = State.MED
elif state_cur == State.MED:
state_cur = State.HIGH
elif state_cur == State.HIGH:
state_cur = State.HIGH
yield state_cur
87
HDL Model
(Integrating SystemVerilog modules)
88
HLS precedence compared to developed HDL - 1
Async gears can also be used to import developed HDL modules
@gear (hdl={"impl":"./temp/unary_example.sv"})
async def add_sub(a, b):
pass
@gear
async def add_sub(a, b):
return a + b
@gear #Default path: ~/.pygears/svlib
async def add_sub(a, b):
pass
Explicite definition of path
where is HDL module
Function with implementation
= HLS if no explicit path
In non of above, try to find SV
module in default path
1
2
3
89
HLS precedence compared to developed HDL - 2
Precedence pseudo code
if cosim_for_this_module_enabled:
if explicit_path_defined:
try:
find_module(path=defined_path)
except:
print("Error file not found")
elif has_implementation:
try:
compile_hls()
except:
print("Error HLS failed")
else:
try:
find_module(path=default_path)
except:
print("Error file not found")
else:
use_python_model()
90
SystemVerilog module structure
module <module_name> #(
<param1_name> = <param1_default>,
<param2_name> = <param2_default>,
...
)(
input logic rst,
input logic clk,
dti.consumer <input1_name>,
dti.consumer <input2_name>,...
dti.producer <output1_name>,
dti.producer <output2_name>,...);
...
endmodule
module mul #(
FACTOR = 1,
)(
input logic rst,
input logic clk,
dti.consumer din,
dti.producer dout);
assign dout.data = din.data * FACTOR;
assign dout.valid = din.valid;
assign din.ready = dout.ready;
endmodule
@gear (hdl={"impl":"<path>"})
async def mul(din, *, factor=1) -> b'din':
pass
@gear
async def <module_name>(
<input1_name>, <input2_name>, ...,
*,
<param1_name>=<param1_default>,
<param2_name>=<param2_default>, ...)
pass
General template Example
DTI
Only SystemVerilog supported
91
To make sure your compiler know what is DTI you should add this file to your project folder:
https://github.com/bogdanvuk/pygears/blob/master/pygears/lib/svlib/dti.sv
Simulation & Verification
hdlgen | sim | cosim
&
Verification gears
92
hdlgen | sim | cosim
93
Types of compiling
94
β hdlgen - Generates HDL only, it is used when user want clean HDL that then can be used by other tools
β sim - Runs simulation, (Python only, Python & HDL or HDL Only) depending which gears are set to be compiled to HDL by cosim function
β cosim - Tells compiler which gear to compile to HDL and prepare those files for simulation (those HDL files are going to be tuned for Verilator
simulator, thus if user is not willing to use this simulator then hdlgen function is way to go). Itβs worth mentioning that cosim goes top-down
whlice compiling modules, which means that User has only to specify top module that he wants to compile and PyGears will automatically
compile all βchildβ modules.
Generated files
To save PyGears log write it to file use terminal command python3 test_fir.py | pytest.log
95
hdlgen
from pygears.hdl import hdlgen
from pygears.typing import Uint
from pygears import gear, Intf
@gear
def add_sub(a, b, *, add_switch: bool):
if add_switch:
c = a + b
else:
c = a - b
return c
a = Intf(Uint[4])
b = Intf(Uint[4])
dout = add_sub(a, b, add_switch=True)
hdlgen(top='/add_sub', lang='sv',
outdir='./dut_svlib', copy_files=True)
hdlgen parameters:
- top - path to the module to be
compiled to HDL
- lang - HDL in which files will be
compiled
- βsvβ - only supported
- outdir - output directory where
HDL files will be written
- copy_files - copies all gears from
standard lib to destination folder
- toplang - top level wrapper
- βvβ - only supported
Before calling hdlgen, the gear shall be connected with
interfaces on all of its inputs and outputs.
96
Sim - 1
sim() function is used to start the simulation in PyGears built-in simulator
from pygears import sim, reg
reg["debug/trace"] = ['*']
drv(t=Uint[8], seq=range(10)) \
| dut() \
| collect(result=res)
sim(resdir='/work/', timeout=128,
seed=1234)
print(res)
Sim parameters:
- resdir - where to output resulting
waveform VCD file
- timeout - how long to run (num of
clocks)
- seed - run the simulation with
custom seed
By default the waveform output file will not trace any signals - in order to trace
all available signals add following line before sim() call:
reg["debug/trace"] = ['*']
97
Sim - 2
Built-in simulator makes it easy to test and verify the modules.
Calling the PyGears simulator is done by simply calling βsim()β after the design has been constructed.
If βsim()β is not called PyGears will only construct the modules and connect them without any
simulation
Resdir -
- By default the waveform output file will not trace any signals - in order to trace all available
signals add this line before sim() call
- reg[βdebug/traceβ] = [β*β]
- Without this one vcd is empty, "*" means "Probe all signals to the VCD" there is also
possibility to probe only some signals by adding their path instead of "*"
- Sim output file will be saved under default name pygears.vcd
Timeout - if timeout value not provided the simulation will run until all data items have been driven
Seed - by default, if seed not provided PyGears will generate random seed
98
Cosim
Cosim() instruction compiles PyGears code into HDL and indicates that it will be
simulated in a desired simulator. sim() instruction has to be called afterwards.
from pygears import sim, reg
reg["debug/trace"] = ['*']
drv(t=Uint[8], seq=range(10)) \
| dut \
| collect(result=res)
cosim(top='/dut', sim='verilator',
outdir='~/dut_hdl', timeout=100,
rebuild=False)
sim()
cosim parameters:
- top - path toward module that
will be compiled to HDL
- sim - simulator of choice
- outdir - destination for HDL files
- timeout - how long to run
- rebuild - whether HDL files will be
rebuild or reused between
different runs
99
Verification gears
100
Drv gear - 1
Drv gear included in the pygears library
- Used to drive input interface
- Used for direct testing
- Not compiled to HDL
# drv gear definition
@gear
async def drv(*, t, seq) -> b't':
- Outputs one data at the time
- Iterable input seq cast to the type t
- Parameters:
- t - type of the data to output
- seq - any iterable python object
- Returns: Data of the type t
Driving simple unsigned integer data:
from pygears.lib.verif import drv
dut(drv(t=Uint[8], seq=range(10)))
Driving a complex Queue type of data
from pygears.lib.verif import drv
# preparing the sequence
q1 = ((11, 12), (21, 22, 23))
q2 = ((11, 12, 13))
# using the sequences to drive the data
drv(t=Queue[Uint[8], 2], seq=[q1, q2]) \
| dut
101
Drv gear - 2
- For simple verification of our newly created design we can use verification gears already
included in the pygears library.
- The basic gear for driving input data of the design is a βdrvβ gear.
- Drv outputs one data at the time for the iterable βseqβ input cast to the type βtβ - which should
correspond to the input type of a gear we want to verify
- The drv gear should be connected to our design gear which is under test
- Sequence that we want to drive can be a regular python type and should be prepared before
calling DRV and provided as an input parameter
- The βdrvβ gear will convert the input sequence to a type that we provided as a βtβ argument
- Multiple βdrvβ gears can be used in case the design has more than one input interface.
- User can define its own custom complex gear for driving data to the design under test and this
- 1st example -> simple Uint data drive n to the DUT that has one input interface where the
sequence is just a list of numbers. βdrvβ output is assigned to the βdutβ input
- 2nd example driving a queue of a certain level (2 in our example) - in that case the βseqβ should
be a nested iterable type of the same level. Alternative way of connecting gears using βpipeβ is
shown
102
Collect gear - 1
collect gear can be used to sample
output interfaces
# collect gear definition
def collect(val, *, result):
Parameters:
- val - input interface for
connection to the DUTβs
output
- result - should be
connected to the list that
accepts the simulation
results
Driving and collecting simple unsigned
integer data
from pygears.lib.verif import drv, collect
res = []
drv(t=Uint[8], seq=range(10)) \
| dut() \
| collect(result=res)
103
Collect gear - 2
After having βdrvβ gear to drive data, py gears has included βcollectβ gear as an end-point to the
simple verification environment.
It will be an end point of the data flow that simply collects the data from the DUTβs output interface.
- βvalβ -> input to the collect gear to be used for connecting to the design under test output
- βresultβ -> for collecting the output data
Multiple collect gears can be used if the design has multiple outputs
User can define its own collection or monitoring gears for more complex data collection, monitoring
and checking
* collected data is available only if simulation is executed (described in following slides)
104
Check gear
Instead of just collecting data check gear can automatically check output
based on the given reference
# collect gear definition
@gear
async def check(din, *, ref,
cmp=None):
Check parameters:
- din - input interface for
connection to the design output
- ref - list of referent data used
for checking
- cmp - *optional provide a
custom check function
from pygears import gear
from pygears.typing import Uint
from pygears.sim import sim
from pygears.lib.verif import check, drv
@gear
def dut(din) -> b'din':
return din
ref = [0,1,2,3,4,5,6,7,8,9]
drv(t=Uint[8], seq=range(10)) \
| dut() \
| check(ref=ref)
sim()
105
Logging
PyGears built-in support for
printing messages
from pygears.sim import log
result = [[1,1],[2,2],[3,3]]
log.info(f'Formatted info
message {result})
log.info('Some Info message')
log.debug('Some Debug message')
log.error('Some Error message')
- Used to debug Pygears simulation
- Will not be translated into SystemVerilog
- If in async gear - will include simulation
cycle and gear name
- For gears that are translated into HDL ->
messages are shown only in python βsimβ
mode
0 [INFO]: Formatted info message [[1,1], [2,2], [3,3]]
1 [INFO]: Some Info message
2 [DEBUG]: Some Debug message
3 [ERROR]: Some Error message
File "...", line 143, in <module>
LogException: [0], Some Error message
Info - regular information print
in the console
Debug - lower verbosity (not
printed by default)
Error - causes simulation to fail
Also available: warning, critical,
exception
106
Logging
107
Uses python internal logger, can be used both inside and outside of gears
When debugging pygears design and verification components both standard python βprintβ or included PyGears logging mechanism can be
used.
Pygers includes a custom logger that can be used for printing information messages, debugging and errors.
β Messages in βverifβ gears (drv/check/collect) - will be shown both in βsim' and βcosimβ β can be used for verification debugging and
error reporting
β Messages in 'dataflowβ gears - will be shown both in βsimβ and βcosimβ - used to debug data structures and flow
β Messages in βasync gearsβ will not be translated to SV when doing 'cosimβ - it will be just ignored
β when doing python βsimβ messages will be correctly shown and can be used to debug simulation with timestep info
Logger accepts all standard python formatting options.
By default βdebugβ messages will not be printed:
β Can be turned on with following code line : reg[βlogger/sim/levelβ] = logging.DEBUG
Errors will automatically break the simulation with error message
β Can be modified: reg[βlogger/sim/errorβ] = βdebugβ
Collateral (generated files):
β Path -> Defauly system temp folder OR custom (set through registry as simply by setting sim() parameter res_dir to desired location)
β Logging is not exported into files
β VCD is going to be generated in those files
β If there is cosim() then generated VCD will be tuned for Verilator, same as generated HDL SV files
β Also there is a possability to convert generated VCD to JSON file that can be than uploaded to Anari Platform Web Sim Viewer, moare
about that later
@gear
async def dut(din: Array) -> Queue['din[0]']:
i = Uint[bitw(len(din.dtype) - 1)](0)
async with din as val:
async for i, last in qrange(len(din.dtype)):
yield val[i], last
@gear
async def check(dut_res, *, ref):
for ref_transaction in ref:
for element in ref_transaction:
async with dut_res as (data, eot):
assert element == data
seq=[[1,2,3,4,5,6,7,8],[10,20,30,40,50,60,70,80]]
drv(t=Array[Uint[8], 8], seq=seq) \
| dut \
| check(ref=seq)
HLS Design & Testbench - Example
Design - Device Under Test
Converting Array to Queue
Checker for test
Comparing Array and Queue
element, eot is thrown away
Test bench
Driving dut with 2 sequences,
since only type changed we
used same seq to for checker
108
Final thoughts
109
Conclusion
β PyGears incorporates some useful design patterns that every project could benefit from.
β It gives access to hardware design to people with programming skills without knowledge of HDLs
β It compiles to standard HDLs so it can be integrated seamlessly as a part of the design.
β It facilitates design verification both by forcing the use of a single interface type, ensuring the hardware
system consistency, and by integrating with rich Python ecosystem
110
Additional information
112
How to install PyGears
113
Linux Installation
Tested system: Ubuntu 20.04
Installation steps:
β Install VS Code for Linux
β Open VS Code and all commands enter though VS Codeβs GUI Terminal (Terminal/New Terminal)
β Make sure you are running Python 3.9 or 3.8 or 3.7
β Install PyGears
pip install pygears OR pip3 install pygears
β Install verilator
sudo apt-get install verilator
β Install GTKwave (optional sim viewer)
sudo apt update
sudo apt install gtkwave
114
Windows Installation part 1
Tested system: Windows 10 + WSL
Installation steps:
β Enable Windows Subsystem for Linux and install Ubuntu
β Search for Turn Windows features on or off and click the top result to open the experience.
β Check the βWindows Subsystem for Linuxβ option. Enable WSL on Windows 10.
β Install Ubuntu 20.04 LTS for WSL from windows store
* alternatively from windows powershell type: wsl --install -d Ubuntu-20.04
β Install pygears and verilator on Ubuntu WSL
β Open Start on Windows 10 -> search for 'Ubuntu" and open it for the first run it may finish up
installation and ask for initial username and password
β in the Ubuntu console type following commands
sudo apt update
sudo apt install python3-pip
pip3 install pygears
sudo apt install verilator
β python will be available with command python3
β make sure correct python version (3.7 or 3.8 or 3.9) is installed, type the following command
python3 --version
115
Windows Installation part 2
Vscode from Windows 10 + WSL
β install vscode on windows
β install vscode wsl extension
β Open Start on Windows 10 -> search for 'Ubuntu"
β Type code in Ubuntu console to install vscode server
β the vscode server version should be installed on our WSL Ubuntu system and open up VScode app
automatically connecting into our Ubuntu system
β when Vscode is open make sure that
β It is connected to the Ubuntu wsl in the left-bottom corner
β And that correct python version is active
β In Windows, Ubuntu file system will be available on following path
β for Ubuntu 20.04 'home' dir -> \\wsl$\Ubuntu-20.04\home
β In Ubuntu, windows file system is available on following path
β for C: drive -> /mnt/c
116
MacOS Installation
Tested system: macOS Monterey 12.0.1 / Apple M1
Installation steps:
β Install VS Code for MacOS
β Open VS Code and all commands enter though VS Codeβs GUI Terminal (Terminal/New Terminal)
β Make sure you are running Python 3.9 or 3.8 or 3.7
β Install brew
β Donβt miss that after installation there are two additional steps to make it work, check Terminal output
after brew finishes installation
β Install PyGears
pip install pygears OR pip3 install pygears
β Install verilator
brew install verilator
β Install GTKwave (optional sim viewer)
brew install --cask gtkwave
117
How to get help
118
Reaching-out Anari Support
There are 2 ways to get help:
β Stackoverflow
β When posting question make sure you use tags python and
pygears
β If this is satisfied Anari team will be notified within 15min and we
are going to answer question as soon as possible
β Discord community (invite at last slide)
119