from amaranth import *
from amaranth_soc.wishbone import *
from amaranth_soc.memory import *


# We sub-class wishbone.Interface here because it needs to be a bus object to be added as a window to Wishbone stuff
class ROM(Elaboratable, Interface):
    def __init__(self, data=None):
        #self.size = len(data)
        self.data = Memory(width=32, depth=(4096 >> 2), init=data)
        self.r = self.data.read_port()

        # Need to init Interface
        Interface.__init__(self, addr_width=10, data_width=32)

        # This is effectively a "window", and it has a certain set of resources
        # 12 = log2(4096)
        memory_map = MemoryMap(addr_width=10, data_width=32)
        # TODO need to unify how I deal with size
        # In this case, one resource, which is out memory
        memory_map.add_resource(self.data, name="rom_data", size=(4096 >> 2))

        self.memory_map = memory_map

    # Connects memory port signals to wishbone interface
    def elaborate(self, platform):
        # Stolen from https://vivonomicon.com/2020/04/14/learning-fpga-design-with-nmigen/
        m = Module()
        # Register the read port submodule.
        m.submodules.r = self.r

        # 'ack' signal should rest at 0.
        m.d.sync += self.ack.eq( 0 )
        # Simulated reads only take one cycle, but only acknowledge
        # them after 'cyc' and 'stb' are asserted.
        with m.If( self.cyc ):
            m.d.sync += self.ack.eq( self.stb )

        # Set 'dat_r' bus signal to the value in the
        # requested 'data' array index.
        m.d.comb += [
            self.r.addr.eq( self.adr ),
            self.dat_r.eq( self.r.data )
        ]

        # End of simulated memory module.
        return m


# TODO support read segmentation or whatever it's called, where you read/write certian bytes from memory
# Otherwise we can't store individual bytes, and this will wreck shit in weird ways.
class RAM(Elaboratable, Interface):
    def __init__(self):
        #self.size = len(data)
        self.data = Memory(width=32, depth=(4096 >> 2))
        self.r = self.data.read_port()
        self.w = self.data.write_port()

        # Need to init Interface
        Interface.__init__(self, addr_width=10, data_width=32)

        # This is effectively a "window", and it has a certain set of resources
        # 12 = log2(4096)
        memory_map = MemoryMap(addr_width=10, data_width=32)
        # TODO need to unify how I deal with size
        # In this case, one resource, which is out memory
        memory_map.add_resource(self.data, name="ram_data", size=(4096 >> 2))

        self.memory_map = memory_map

    # Connects memory port signals to wishbone interface
    def elaborate(self, platform):
        # Stolen from https://vivonomicon.com/2020/04/14/learning-fpga-design-with-nmigen/
        m = Module()
        # Register the read port submodule.
        m.submodules.r = self.r
        m.submodules.w = self.w

        # 'ack' signal should rest at 0.
        m.d.sync += self.ack.eq(0)
        # Simulated reads only take one cycle, but only acknowledge
        # them after 'cyc' and 'stb' are asserted.
        with m.If( self.cyc & self.stb):
            m.d.sync += self.ack.eq(1)

            # Write to address if we are writing
            with m.If(self.we):
                m.d.sync += self.w.en.eq(1)

        # Set 'dat_r' bus signal to the value in the
        # requested 'data' array index.
        m.d.comb += [
            self.r.addr.eq( self.adr ),
            self.dat_r.eq( self.r.data ),
            self.w.addr.eq(self.adr),
            self.w.data.eq(self.dat_w),
        ]

        # End of simulated memory module.
        return m
