Registers and Memories#
Registers#
The class Register
is derived from WireVector
, and so can
be used just like any other WireVector
. Reading a register produces
the stored value available in the current cycle. The stored value for the
following cycle can be set by assigning to property
next
with the
<<=
(__ilshift__()
) operator. Registers reset
to zero by default, and reside in the same clock domain.
- class pyrtl.wire.Register(bitwidth, name='', reset_value=None, block=None)[source]#
Bases:
WireVector
A WireVector with a register state element embedded.
Registers only update their outputs on posedge of an implicit clock signal. The “value” in the current cycle can be accessed by just referencing the Register itself. To set the value for the next cycle (after the next posedge) you write to the property
next
with the<<=
operator. For example, if you want to specify a counter it would look like:a.next <<= a + 1
- __init__(bitwidth, name='', reset_value=None, block=None)[source]#
Construct a register.
- Parameters:
bitwidth (int) – Number of bits to represent this register.
name (str) – The name of the wire. Must be unique. If none is provided, one will be autogenerated.
reset_value – Value to initialize this register to during simulation and in any code (e.g. Verilog) that is exported. Defaults to 0, but can be explicitly overridden at simulation time.
block – The block under which the wire should be placed. Defaults to the working block.
- Returns:
a WireVector object representing a register.
It is an error if the
reset_value
cannot fit into the specified bitwidth for this register.
- property next#
This property is the way to set what the WireVector will be the next cycle (aka, it is before the D-Latch)
Memories#
- class pyrtl.memory.MemBlock(bitwidth, addrwidth, name='', max_read_ports=2, max_write_ports=1, asynchronous=False, block=None)[source]#
MemBlock is the object for specifying block memories. It can be indexed like an array for both reading and writing. Writes under a conditional are automatically converted to enabled writes. For example, consider the following examples where
addr
,data
, andwe
are all WireVectors:data = memory[addr] # infer read port memory[addr] <<= data # infer write port mem[address] <<= MemBlock.EnabledWrite(data, enable=we)
When the address of a memory is assigned to using an
EnabledWrite
object items will only be written to the memory when the enable WireVector is set to high (1).- class EnabledWrite(data, enable)#
Allows for an enable bit for each write port, where data (the first field in the tuple) is the normal data address, and enable (the second field) is a one bit signal specifying that the write should happen (i.e. active high).
- data#
Alias for field number 0
- enable#
Alias for field number 1
- __init__(bitwidth, addrwidth, name='', max_read_ports=2, max_write_ports=1, asynchronous=False, block=None)[source]#
Create a PyRTL read-write memory.
- Parameters:
bitwidth (int) – Defines the bitwidth of each element in the memory
addrwidth (int) – The number of bits used to address an element of the memory. This also defines the size of the memory
name (basestring) – The identifier for the memory
max_read_ports – limits the number of read ports each block can create; passing None indicates there is no limit
max_write_ports – limits the number of write ports each block can create; passing None indicates there is no limit
asynchronous (bool) – If false make sure that memory reads are only done using values straight from a register. (aka make sure that the read is synchronous)
name – Name of the memory. Defaults to an autogenerated name
block – The block to add it to, defaults to the working block
It is best practice to make sure your block memory/fifos read/write operations start on a clock edge if you want them to synthesize into efficient hardware. MemBlocks will enforce this by making sure that you only address them with a register or input, unless you explicitly declare the memory as asynchronous with
asynchronous=True
flag. Note that asynchronous mems are, while sometimes very convenient and tempting, rarely a good idea. They can’t be mapped to block RAMs in FPGAs and will be converted to registers by most design tools even though PyRTL can handle them with no problem. For any memory beyond a few hundred entries it is not a realistic option.Each read or write to the memory will create a new port (either a read port or write port respectively). By default memories are limited to 2-read and 1-write port, but to keep designs efficient by default, but those values can be set as options. Note that memories with high numbers of ports may not be possible to map to physical memories such as block RAMs or existing memory hardware macros.
ROMs#
- class pyrtl.memory.RomBlock(bitwidth, addrwidth, romdata, name='', max_read_ports=2, build_new_roms=False, asynchronous=False, pad_with_zeros=False, block=None)[source]#
Bases:
MemBlock
PyRTL Read Only Memory.
RomBlocks are the read only memory block for PyRTL. They support the same read interface and normal memories, but they are cannot be written to (i.e. there are no write ports). The ROM must be initialized with some values and construction through the use of the
romdata
which is the memory for the system.- __init__(bitwidth, addrwidth, romdata, name='', max_read_ports=2, build_new_roms=False, asynchronous=False, pad_with_zeros=False, block=None)[source]#
Create a Python Read Only Memory.
- Parameters:
bitwidth (int) – The bitwidth of each item stored in the ROM
addrwidth (int) – The bitwidth of the address bus (determines number of addresses)
romdata – This can either be a function or an array (iterable) that maps an address as an input to a result as an output
name (str) – The identifier for the memory
max_read_ports – limits the number of read ports each block can create; passing
None
indicates there is no limitbuild_new_roms (bool) – indicates whether to create and pass new RomBlocks during
__getitem__
to avoid exceeding max_read_portsasynchronous (bool) – If false make sure that memory reads are only done using values straight from a register. (aka make sure that reads are synchronous)
pad_with_zeros (bool) – If true, extend any missing romdata with zeros out until the size of the romblock so that any access to the rom is well defined. Otherwise, the simulation should throw an error on access of unintialized data. If you are generating verilog from the rom, you will need to specify a value for every address (in which case setting this to True will help), however for testing and simulation it useful to know if you are off the end of explicitly specified values (which is why it is False by default)
block – The block to add to, defaults to the working block