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, and we 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 limit

  • build_new_roms (bool) – indicates whether to create and pass new RomBlocks during __getitem__ to avoid exceeding max_read_ports

  • asynchronous (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