Quick Start

python -m pip install pyhgl

Syntax

PyHGL adds some syntactic sugar to Python and uses a parser generated by Pegen. PyHGL source files (with .pyh suffix) should be imported by a normal python file that has imported pyhgl.

# in Adder.pyh 
from pyhgl.logic import *
...
# in main.py 
import pyhgl  
import Adder 

new operators and statements are listed below.

Operator

Operator

Description

!x

LogicNot

x && y

LogicAnd

x || y

LogicOr

x |-> y

Imply(Assertion)

x >>> y

Sequential(Assertion)

x <== y

Assignment

x[idx] <== y

Partial Assignment

x <=> y

Connect

One-line Decorator

# pyhgl
@decorator NAME:
    ...  
# python 
@decorator 
def NAME():
    ...
    return locals()

When Statement

when signal:
    ... 
elsewhen signal:
    ... 
otherwise:
    ... 

Switch Statement

switch signal:
    once a, b:
        ... 
    once c: 
        ... 
    once ...:
        ...

Example

a Game of Life example comes from Chisel Tutorials.

in life.pyh

from pyhgl.logic import *

@module Life(rows, cols):                               # declares a module `Life`
    cells     = Reg(Array.zeros(rows, cols)) @ Output   # a rows*cols array of 1-bit regs
    running   = UInt(0)                      @ Input    # control signal. if 1, run; if 0, write data
    wdata     = UInt(0)                      @ Input    # data to write
    waddr_row = UInt(rows)                   @ Input    # write address
    waddr_col = UInt(cols)                   @ Input    # write address

    def make_cell(row, col):                            # row and col are indexes
        neighbors = cells[[row-1,row,(row+1)%rows],     # select a 3*3 range circularly
                          [col-1,col,(col+1)%cols]]
        cell = neighbors[1,1]                           # select current cell
        neighbors[1,1] = UInt('3:b0')                   # 3 bits to count 8 neighbors
        count_neighbors = Add(neighbors, axis=(0,1))    # sum up the 3*3 array, return a 3-bit uint
        when !running:                                  # write data
            when waddr_row == row && waddr_col == col:  
                cell <== wdata 
        otherwise:                                      # next state
            when count_neighbors == 3:                  # n=3, becomes a live cell
                cell <== 1 
            elsewhen count_neighbors != 2:              # n!=3 and n!=2, dies
                cell <== 0 
                                                        # otherwise keep the state
    for i in range(rows):
        for j in range(cols):
            make_cell(i,j)                              # map rules on each cell


@task test_life(self, dut):                             # coroutine-based simulation tasks
                                                        # dut is a `Life` module instance
    @task set_mode(self, run: bool):                    # declares a task 
        setv(dut.running, run)                          # `setv` set value to signal/signals
        yield self.clock_n()                            # wait until next negedge of default clock
    @task write(self, row, col):                        # write `1` to a specific cell
        setv(dut.wdata, 1)
        setv(dut.waddr_row, row)        
        setv(dut.waddr_col, col)
        yield self.clock_n()
    @task init_glider(self):      
        # wait and execute these tasks sequentially                      
        yield write(3,3), write(3,5), write(4,4), write(4,5), write(5,4)
    @task show(self):
        cells_str = Map(lambda v: '*' if v==1 else ' ', getv(dut.cells))   
        for row in cells_str:
            print(' '.join(row)) 
        yield self.clock_n()
        
    yield set_mode(0), init_glider(), set_mode(1)
    for _ in range(20):
        yield show()


with Session() as sess:                                 # enter a `Session` before initialization
    life = Life(10,10)                                  # a 10*10 game of life
    sess.track(life)                                    # track all singals in the module
    sess.join(test_life(life))                          # wait until the task finishes
    sess.dumpVCD('life.vcd')                            # generate waveform to ./build/life.vcd
    sess.dumpVerilog('life.sv')                         # generate RTL to ./build/life.sv
    print(sess)                                         # a summary 

in main.py

import pyhgl 
import life

run: python main.py