from migen.fhdl.std import *


class ReorderSlot:
    def __init__(self, tag_width, data_width):
        self.wait_data = Signal()
        self.has_data = Signal()
        self.tag = Signal(tag_width)
        self.data = Signal(data_width)


class ReorderBuffer(Module):
    def __init__(self, tag_width, data_width, depth):
        # issue
        self.can_issue = Signal()
        self.issue = Signal()
        self.tag_issue = Signal(tag_width)

        # call
        self.call = Signal()
        self.tag_call = Signal(tag_width)
        self.data_call = Signal(data_width)

        # readback
        self.can_read = Signal()
        self.read = Signal()
        self.data_read = Signal(data_width)

        ###

        empty_count = Signal(max=depth+1, reset=depth)
        produce = Signal(max=depth)
        consume = Signal(max=depth)
        slots = Array(ReorderSlot(tag_width, data_width)
            for n in range(depth))

        # issue
        self.comb += self.can_issue.eq(empty_count != 0)
        self.sync += If(self.issue & self.can_issue,
                empty_count.eq(empty_count - 1),
                If(produce == depth - 1,
                    produce.eq(0)
                ).Else(
                    produce.eq(produce + 1)
                ),
                slots[produce].wait_data.eq(1),
                slots[produce].tag.eq(self.tag_issue)
            )

        # call
        for n, slot in enumerate(slots):
            self.sync += If(self.call & slot.wait_data & (self.tag_call == slot.tag),
                    slot.wait_data.eq(0),
                    slot.has_data.eq(1),
                    slot.data.eq(self.data_call)
                )

        # readback
        self.comb += [
            self.can_read.eq(slots[consume].has_data),
            self.data_read.eq(slots[consume].data)
        ]
        self.sync += [
            If(self.read & self.can_read,
                empty_count.eq(empty_count + 1),
                If(consume == depth - 1,
                    consume.eq(0)
                ).Else(
                    consume.eq(consume + 1)
                ),
                slots[consume].has_data.eq(0)
            )
        ]

        # do not touch empty count when issuing and reading at the same time
        self.sync += If(self.issue & self.can_issue & self.read & self.can_read,
                empty_count.eq(empty_count)
            )