#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from hwt.code import If, Concat
from hwt.hdl.constants import READ_WRITE, WRITE, READ
from hwt.hdl.types.bits import Bits
from hwt.interfaces.std import BramPort, Clk, BramPort_withoutClk
from hwt.serializer.mode import serializeParamsUniq
from hwt.synthesizer.hObjList import HObjList
from hwt.synthesizer.param import Param
from hwt.synthesizer.rtlLevel.rtlSignal import RtlSignal
from hwt.synthesizer.unit import Unit
[docs]@serializeParamsUniq
class RamSingleClock(Unit):
"""
RAM/ROM with only one clock signal.
It can be configured to have arbitrary number of ports.
It can also be configured to have write mask or to be composed from multiple smaller memories.
:note: This memory may not be mapped to RAM
if synthesis tool consider it to be too small.
:ivar PORT_CNT: Param which specifies number of ram ports,
it can be int or tuple of READ_WRITE, WRITE, READ
to specify rw access for each port separately
:ivar HAS_BE: Param, if True the write ports will have byte enable signal
.. hwt-autodoc::
"""
PORT_CLS = BramPort_withoutClk
def _config(self):
self.ADDR_WIDTH = Param(10)
self.DATA_WIDTH = Param(64)
self.PORT_CNT = Param(1)
self.HAS_BE = Param(False)
self.MAX_BLOCK_DATA_WIDTH = Param(None)
self.INIT_DATA = Param(None)
[docs] def _declr_ports(self):
PORTS = self.PORT_CNT
with self._paramsShared():
ports = HObjList()
if isinstance(PORTS, int):
for _ in range(PORTS):
p = self.PORT_CLS()
ports.append(p)
else:
for access_mode in PORTS:
p = self.PORT_CLS()
if access_mode == READ_WRITE:
pass
elif access_mode == READ:
p.HAS_W = False
elif access_mode == WRITE:
p.HAS_R = False
else:
raise ValueError(access_mode)
ports.append(p)
self.port = ports
[docs] def _declr_children(self):
# for the case where this memory will be relized using multiple memory blocks
children = HObjList()
MAX_DW = self.MAX_BLOCK_DATA_WIDTH
if MAX_DW is not None and MAX_DW < self.DATA_WIDTH:
DW = self.DATA_WIDTH
while DW > 0:
c = self.__class__()
c._updateParamsFrom(self, exclude=({"DATA_WIDTH"}, {}))
c.DATA_WIDTH = min(DW, MAX_DW)
if self.INIT_DATA is not None:
raise NotImplementedError()
children.append(c)
DW -= MAX_DW
self.children = children
def _declr(self):
self.clk = Clk()
self._declr_ports()
self._declr_children()
[docs] @staticmethod
def mem_write(mem, port: BramPort_withoutClk):
drive = []
if port.HAS_BE:
assert port.DATA_WIDTH % 8 == 0, port.DATA_WIDTH
# we for each byte separate
#drive.append(
# mem[port.addr](apply_write_with_mask(mem[port.addr], port.din, port.we))
#)
for b_i, be in enumerate(port.we):
low = b_i * 8
drive.append(
If(be,
mem[port.addr][low + 8: low](port.din[low + 8: low])
)
)
elif port.HAS_R and port.HAS_W:
# explicit we
drive.append(
If(port.we,
mem[port.addr](port.din)
)
)
else:
# en used as we
drive.append(mem[port.addr](port.din))
return drive
[docs] @staticmethod
def connect_port(clk: RtlSignal, port: BramPort_withoutClk, mem: RtlSignal):
if port.HAS_R and port.HAS_W:
If(clk._onRisingEdge(),
If(port.en,
*RamSingleClock.mem_write(mem, port),
port.dout(mem[port.addr]),
).Else(
port.dout(None),
),
)
elif port.HAS_R:
If(clk._onRisingEdge(),
If(port.en,
port.dout(mem[port.addr])
)
)
elif port.HAS_W:
If(clk._onRisingEdge(),
If(port.en,
*RamSingleClock.mem_write(mem, port),
)
)
else:
raise AssertionError("Bram port has to have at least write or read part")
[docs] def delegate_to_children(self):
MAX_DW = self.MAX_BLOCK_DATA_WIDTH
DW = self.DATA_WIDTH
for ports in zip(self.port, *[c.port for c in self.children]):
dout = []
p = ports[0]
clk = getattr(p, "clk", None)
for i, cp in enumerate(ports[1:]):
cp.en(p.en)
cp.addr(p.addr)
if p.HAS_R:
dout.append(cp.dout)
if p.HAS_W:
if p.HAS_BE:
cp.we(p.we[min((i + 1) * (MAX_DW // 8), DW // 8):i * (MAX_DW // 8)])
elif p.HAS_R:
cp.we(p.we)
cp.din(p.din[min((i + 1) * (MAX_DW), DW):i * (MAX_DW)])
if clk is not None:
cp.clk(clk)
if dout:
p.dout(Concat(*reversed(dout)))
clk = getattr(self, "clk", None)
for c in self.children:
c.clk(clk)
def _impl(self):
if self.children:
self.delegate_to_children()
else:
dt = Bits(self.DATA_WIDTH)[2 ** self.ADDR_WIDTH]
self._mem = self._sig("ram_memory", dt, def_val=self.INIT_DATA)
for p in self.port:
self.connect_port(self.clk, p, self._mem)
[docs]@serializeParamsUniq
class RamMultiClock(Unit):
"""
RAM where each port has an independet clock.
It can be configured to true dual port RAM etc.
It can also be configured to have write mask or to be composed from multiple smaller memories.
:note: write-first variant
.. hwt-autodoc::
"""
PORT_CLS = BramPort
def _config(self):
RamSingleClock._config(self)
self.PORT_CNT = 2
def _declr(self):
RamSingleClock._declr_ports(self)
RamSingleClock._declr_children(self)
def _impl(self):
if self.children:
self.delegate_to_children()
else:
dt = Bits(self.DATA_WIDTH)[2 ** self.ADDR_WIDTH]
self._mem = self._sig("ram_memory", dt, self.INIT_DATA)
for p in self.port:
RamSingleClock.connect_port(p.clk, p, self._mem)
if __name__ == "__main__":
from hwt.synthesizer.utils import to_rtl_str
u = RamSingleClock()
u.HAS_BE = True
u.ADDR_WIDTH = 10
u.DATA_WIDTH = 17*8
print(to_rtl_str(u))