Logic Nets and Blocks#

LogicNets#

class pyrtl.core.LogicNet(op, op_param, args, dests)[source]#

The basic immutable datatype for storing a “net” in a netlist.

This is used for the internal representation that Python stores knowledge of what this is, and how it is used is only required for advanced users of PyRTL.

A ‘net’ is a structure in Python that is representative of hardware logic operations. These include binary operations, such as and or and not, arithmetic operations such as + and -, as well as other operations such as Memory ops, and concat, split, wire, and reg logic.

The details of what is allowed in each of these fields is defined in the comments of Block, and is checked by Block.sanity_check()

Logical Operations:

op

op_param

args

dests

&

None

a1, a2

out

AND two wires together, put result into out

|

None

a1, a2

out

OR two wires together, put result into out

^

None

a1, a2

out

XOR two wires together, put result into out

n

None

a1, a2

out

NAND two wires together, put result into out

~

None

a1

out

invert one wire, put result into out

+

None

a1, a2

out

add a1 and a2, put result into out

len(out) == max(len(a1), len(a2)) + 1

works with both unsigned and two’s complement

-

None

a1, a2

out

subtract a2 from a1, put result into out

len(out) == max(len(a1), len(a2)) + 1

works with both unsigned and two’s complement

*

None

a1, a2

out

multiply a1 & a2, put result into out

len(out) == len(a1) + len(a2)

assumes unsigned, but signed_mult() provides wrapper

=

None

a1, a2

out

check a1 & a2 equal, put result into out (0 | 1)

<

None

a1, a2

out

check a2 greater than a1, put result into out (0 | 1)

>

None

a1, a2

out

check a1 greater than a2, put result into out (0 | 1)

w

None

w1

w2

connects w1 to w2

directional wire with no logical operation

x

None

x,

a1, a2

out

multiplexer:

when x == 0 connect a1 to out

when x == 1 connect a2 to out

x must be one bit and len(a1) == len(a2)

c

None

*args

out

concatenates *args (wires) into single WireVector

puts first arg at MSB, last arg at LSB

s

sel

wire

out

selects bits from wire based on sel (slicing syntax)

puts selected bits into out

r

None

next

r1

on positive clock edge: copies next to r1

m

memid,

mem

addr

data

read address addr of mem (w/ id memid), put it into data

@

memid,

mem

addr

data,

wr_en

write data to mem (w/ id memid) at address addr

request write enable (wr_en)

Blocks#

class pyrtl.core.Block[source]#

Block encapsulates a netlist.

A Block in PyRTL is the class that stores a netlist and provides basic access and error checking members. Each block has well defined inputs and outputs, and contains both the basic logic elements and references to the wires and memories that connect them together.

The logic structure is primarily contained in Block.logic which holds a set of LogicNets. Each LogicNet describes a primitive operation (such as an adder or memory). The primitive is described by a 4-tuple of:

  1. the op (a single character describing the operation such as + or r),

  2. a set of hard-wired op_params (such as the constants to select from the “selection” op).

  3. the tuple args which list the WireVectors hooked up as inputs to this particular net.

  4. the tuple dests which list the WireVectors hooked up as output for this particular net.

Below is a list of the basic operations. These properties (more formally specified) should all be checked by the class method Block.sanity_check().

  • Most logical and arithmetic ops are pretty self explanatory. Each takes exactly two arguments, and they should perform the arithmetic or logical operation specified.

    OPS: &, |, ^, n, ~, +, -, *.

    All inputs must be the same bitwidth. Logical operations produce as many bits as are in the input, while + and - produce n+1 bits, and * produces 2n bits.

  • In addition there are some operations for performing comparisons that should perform the operation specified. The = op is checking to see if the bits of the vectors are equal, while < and > do unsigned arithmetic comparison. All comparisons generate a single bit of output (1 for true, 0 for false).

  • The w operator is simply a directional wire and has no logic function.

  • The x operator is a multiplexer which takes a select bit and two signals. If the value of the select bit is 0 it selects the second argument; if it is 1 it selects the third argument. Select must be a single bit, while the other two arguments must be the same length.

  • The c operator is the concatenation operator and combines any number of WireVectors (a, b, …, z) into a single new WireVector with a in the MSB and z (or whatever is last) in the LSB position.

  • The s operator is the selection operator and chooses, based on the op_param specified, a subset of the logic bits from a WireVector to select. Repeats are accepted.

  • The r operator is a register and on posedge, simply copies the value from the input to the output of the register.

  • The m operator is a memory block read port, which supports async reads (acting like combinational logic). Multiple read (and write) ports are possible to the same memory but each m defines only one of those. The op_param is a tuple containing two references: the mem id, and a reference to the MemBlock containing this port. The MemBlock should only be used for debug and sanity checks. Each read port has one addr (an arg) and one data (a dest).

  • The @ (update) operator is a memory block write port, which supports synchronous writes (writes are “latched” at positive edge). Multiple write (and read) ports are possible to the same memory but each @ defines only one of those. The op_param is a tuple containing two references: the mem id, and a reference to the MemoryBlock. Writes have three args (addr, data, and write enable we_en). The dests should be an empty tuple. You will not see a written value change until the following cycle. If multiple writes happen to the same address in the same cycle the behavior is currently undefined.

The connecting elements (args and dests) should be WireVectors or derived from WireVector, and should be registered with the block using Block.add_wirevector(). Nets should be registered using Block.add_net().

In addition, there is a member Block.legal_ops which defines the set of operations that can be legally added to the block. By default it is set to all of the above defined operations, but it can be useful in certain cases to only allow a subset of operations (such as when transforms are being done that are “lowering” the blocks to more primitive ops).

pyrtl.core.working_block(block=None)[source]#

Convenience function for capturing the current working block.

If a block is not passed, or if the block passed is None, then this will return the “current working block”. However, if a block is passed in it will simply return that block instead. This feature is useful in allowing functions to “override” the current working block.

pyrtl.core.reset_working_block()[source]#

Reset the working block to be empty.

pyrtl.core.set_working_block(block, no_sanity_check=False)[source]#

Set the working block to be the block passed as argument. Compatible with the with statement.

Sanity checks will only be run if the new block is different from the original block.

pyrtl.core.temp_working_block()[source]#

Set the working block to be new temporary block.

If used with the with statement the block will be reset to the original value (at the time of call) at exit of the context.

pyrtl.core.Block.add_wirevector(self, wirevector)#

Add a WireVector object to the block.

Parameters:

wirevector (WireVector) – WireVector object added to block

pyrtl.core.Block.remove_wirevector(self, wirevector)#

Remove a WireVector object from the block.

Parameters:

wirevector (WireVector) – WireVector object removed from block

pyrtl.core.Block.add_net(self, net)#

Add a net to the logic of the block.

Parameters:

net (LogicNet) – LogicNet object added to block

The passed net, which must be of type LogicNet, is checked and then added to the block. No wires are added by this member, they must be added seperately with Block.add_wirevector().

pyrtl.core.Block.get_memblock_by_name(self, name, strict=False)#

Get a reference to a memory stored in this block by name.

Parameters:
  • name (str) – name of MemBlock object

  • strict (bool) – Determines if PyrtlError or None is thrown on no match. Defaults to False.

Returns:

a MemBlock object with specified name

By fallthrough, if a matching MemBlock cannot be found the value None is returned. However, if the argument strict is set to True, then this will instead throw a PyrtlError when no match is found.

This useful for when a block defines its own internal memory block, and during simulation you want to instantiate that memory with certain values for testing. Since the Simulation constructor requires a reference to the memory object itself, but the block you’re testing defines the memory internally, this allows you to get the object reference.

Note that this requires you know the name of the memory block, meaning that you most likely need to have named it yourself.

Example:

def special_memory(read_addr, write_addr, data, wen):
    mem = pyrtl.MemBlock(bitwidth=32, addrwidth=5, name='special_mem')
    mem[write_addr] <<= pyrtl.MemBlock.EnabledWrite(data, wen & (write_addr > 0))
    return mem[read_addr]

read_addr = pyrtl.Input(5, 'read_addr')
write_addr = pyrtl.Input(5, 'write_addr')
data = pyrtl.Input(32, 'data')
wen = pyrtl.Input(1, 'wen')
res = pyrtl.Output(32, 'res')

res <<= special_memory(read_addr, write_addr, data, wen)

# Can only access it after the `special_memory` block has been instantiated/called
special_mem = pyrtl.working_block().get_memblock_by_name('special_mem')

sim = pyrtl.Simulation(memory_value_map={
    special_mem: {
        0: 5,
        1: 6,
        2: 7,
    }
})

inputs = {
    'read_addr':  '012012',
    'write_addr': '012012',
    'data':       '890333',
    'wen':        '111000',
}
expected = {
    'res': '567590',
}
sim.step_multiple(inputs, expected)
pyrtl.core.Block.wirevector_subset(self, cls=None, exclude=())#

Return set of WireVectors, filtered by the type or tuple of types provided as cls.

Parameters:
  • cls – Type of returned WireVector objects

  • exclude – Type of WireVector objects to exclude

Returns:

Set of WireVector objects that are both a cls type and not a excluded type

If no cls is specified, the full set of WireVectors associated with the Block are returned. If cls is a single type, or a tuple of types, only those WireVectors of the matching types will be returned. This is helpful for getting all inputs, outputs, or registers of a block for example.

Examples:

inputs = pyrtl.working_block().wirevector_subset(pyrtl.Input)
outputs = pyrtl.working_block().wirevector_subset(pyrtl.Output)

# returns set of all non-input WireVectors
non_inputs = pyrtl.working_block().wirevector_subset(exclude=pyrtl.Input)
pyrtl.core.Block.logic_subset(self, op=None)#

Return set of LogicNets, filtered by the type(s) of logic op provided as op.

Parameters:

op – Operation of LogicNet to filter by. Defaults to None.

Returns:

set of LogicNets with corresponding op

If no op is specified, the full set of LogicNets associated with the Block are returned. This is helpful for getting all memories of a block for example.

pyrtl.core.Block.get_wirevector_by_name(self, name, strict=False)#

Return the WireVector matching name.

Parameters:
  • name (str) – name of WireVector object

  • strict (bool) – Determines if PyrtlError or None is thrown on no match. Defaults to False.

Returns:

a WireVector object with specified name

By fallthrough, if a matching WireVector cannot be found the value None is returned. However, if the argument strict is set to True, then this will instead throw a PyrtlError when no match is found.

pyrtl.core.Block.net_connections(self, include_virtual_nodes=False)#

Returns a representation of the current block useful for creating a graph.

Parameters:

include_virtual_nodes (bool) – if enabled, the wire itself will be used to signal an external source or sink (such as the source for an Input net). If disabled, these nodes will be excluded from the adjacency dictionaries

Returns:

Two dictionaries: one that maps WireVectors to the logic net that creates their signal (wire_src_dict) and one that maps WireVectors to a list of logic nets that use the signal (wire_sink_dict).

These dictionaries make the creation of a graph much easier, as well as facilitate other places in which one would need wire source and wire sink information.

Look at net_graph() for one such graph that uses the information from this function.

pyrtl.core.Block.sanity_check(self)#

Check block and throw PyrtlError or PyrtlInternalError if there is an issue.

Should not modify anything, only check data structures to make sure they have been built according to the assumptions stated in the Block comments.