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 byBlock.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 ofLogicNets
. EachLogicNet
describes a primitive operation (such as an adder or memory). The primitive is described by a 4-tuple of:the op (a single character describing the operation such as
+
orr
),a set of hard-wired op_params (such as the constants to select from the “selection” op).
the tuple args which list the WireVectors hooked up as inputs to this particular net.
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 witha
in the MSB andz
(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 eachm
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 usingBlock.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.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.