Exemple #1
0
class PSL_level0(HW_sim_object):
    def __init__(self, env, period, pd_in_pipe, pu_out_pipe, rm_req_pipe, rm_result_pipe, max_nodes=MAX_NODES, rd_latency=2, wr_latency=1):
        """pd_in_pipe - the pipe on which push down requests will be received
           pu_out_pipe - the pipe on which push up requests will be inserted
        """
        super(PSL_level0, self).__init__(env, period)

        # the pipe on which push down requests will be received 
        self.pd_in_pipe = pd_in_pipe
        # the pipe on which push up requests will be inserted 
        self.pu_out_pipe = pu_out_pipe

        # the pipe on which removal requests will be placed
        self.rm_req_pipe = rm_req_pipe
        # the pipe on which removal results will be written
        self.rm_result_pipe = rm_result_pipe

        self.nodes_r_in_pipe = simpy.Store(env)
        self.nodes_r_out_pipe = simpy.Store(env)
        self.nodes_w_in_pipe = simpy.Store(env)
        # maps: node ptr --> PSL_level0_node object
        self.node_bram = BRAM(env, period, self.nodes_r_in_pipe,
                                           self.nodes_r_out_pipe,
                                           self.nodes_w_in_pipe, depth=max_nodes, write_latency=wr_latency, read_latency=rd_latency)

        self.max_nodes = max_nodes

        # stores free node ptrs
        self.node_free_list = Fifo(max_nodes)
        self.init_free_lists()

        # set up the tail pointer to point to the HEAD node 
        self.tail_ptr = HEAD_PTR

        # The head node is stored in register to provide fall through functionality
        #   If the head node is invalid then that means there is nothing in the level
        left = TAIL_PTR
        self.head_node = PSL_level0_node(0, 0, left, 0, 0, 0, valid=False)

        self.run()

    """
    Initialize free lists
    """
    def init_free_lists(self):
        # Add all segments to node free list 
        for i in range(self.max_nodes):
            self.node_free_list.push(i)

    def run(self):
        """Register the processes with the simulation environment
        """
        self.env.process(self.process_level_sm())

    def process_level_sm(self):
        """This process must read incomming push down requests and removal requests,
           then update the head node and produce any push up requests
        """
        while True:
            pd_req = PSL_push_down_req(0, 0, 0, 0, 0, [], valid=False)
            # check if there are any push down requests
            if len(self.pd_in_pipe.items) > 0:
                pd_req = yield self.pd_in_pipe.get()

            rm_req = None
            # check of there are any removal requests
            if len(self.rm_req_pipe.items) > 0:
                rm_req = yield self.rm_req_pipe.get()

            # now use the push down request and removal request to update head_node and produce a push up request if necessary

            # check if a node needs to be removed
            if rm_req is not None:
                # write out the removal result
                yield self.env.process(self.process_remove(pd_req))

            # check if pd_req still needs to be inserted
            if pd_req.valid:
                yield self.env.process(self.insert_pd_req(pd_req))
                
            yield self.wait_clock()

    def insert_pd_req(self, pd_req):
        """Start looking for where to insert the pd_req.
           Keep track of:
             - The last up ptr we saw that is not None, starting with the up ptr in the pd_req
             - The number of nodes we've traversed: for a 1-2 skip list, should not traverse more than 2.
               If we are on our second node and the pd_req is still smaller then insert the pd_re immediately after this node and submit a push up req for the node we are at
        """



    def process_remove(self, pd_req):
        """A removal request has been asserted. Need to decide whether to remove the current head node
           or the push down request
        """
        if pd_req.valid and self.head_node.valid:
            # either need to return the head node or the pd node depending on which is smaller
            if pd_req.rank < self.head_node.rank:
                # return the pd_request
                rm_result = PSL_rm_result(pd_req.rank, pd_req.timestamp, pd_req.meta_ptrs)
                # no longer need to insert pd_req
                pd_req.valid = False
            else:
                # return the head_node
                rm_result = PSL_rm_result(self.head_node.rank, self.head_node.timestamp, self.head_node.meta_ptrs)
                # head node need to be replaced
                yield self.env.process(self.replace_head(pd_req))
        elif pd_req.valid:
            # return the pd_request
            rm_result = PSL_rm_result(pd_req.rank, pd_req.timestamp, pd_req.meta_ptrs)
            # no longer need to insert pd_req
            pd_req.valid = False
        elif self.head_node.valid:
            # return the head_node
            rm_result = PSL_rm_result(self.head_node.rank, self.head_node.timestamp, self.head_node.meta_ptrs)
            # head node need to be replaced
            yield self.env.process(self.replace_head(pd_req))
        else:
            print "ERROR: removal request received but head node and pd_request are invalid"
            sys.exit(1)
        # write out the removal result
        self.rm_result_pipe.put(rm_result)
        yield self.wait_clock()

    def replace_head(self, pd_req):
        """ We just removed the head node so it must be replaced.
            The pd_req still needs to be processed. If the pd_request's start node == self.head_node.left
            then we just need to replace the head_node with the pd_req :)
        """
        if pd_req.valid and (self.head_node.left == pd_req.start or self.head_node.left == TAIL_PTR):
            # replace head node with pd_req :)
            yield self.env.process(self.replace_head_with_pd_req(pd_req))
        else:
            # replace head with left neighbor
            if (self.head_node.left == TAIL_PTR):
                # this level is now empty
                self.head_node.valid = False
                self.tail_ptr = HEAD_PTR
            else:
                yield self.env.process(self.replace_head_with_neighbor(pd_req))

        yield self.wait_clock()

    def replace_head_with_pd_req(self, pd_req):
        """Replace the head node with the pd_request
        """
        left = self.head_node.left
        right = None
        up = None # no need to add a level above this node
        self.head_node = PSL_level0_node(pd_req.rank, pd_req.timestamp, left, right, up, pd_req.meta_ptrs)
        pd_req.valid = False # no need for further processing
        yield self.wait_clock()

    def replace_head_with_neighbor(self, pd_req):
        """The head node needs to be replaced with it's left neighbor
        """
        # replace head node with its left neighbor
        left_addr = self.head_node.left
        self.nodes_r_in_pipe.put(left_addr)
        self.head_node = yield self.nodes_r_out_pipe.get()
        # return the new head's old address to the free list
        self.node_free_list.push(left_addr)

        # Update the head node's left neighbor appropriately
        #    This may just require updating the left neighbor's right pointer to point to the HEAD
        #    Or we may want to insert the pd_req as the new head's left neighbor
        if self.head_node.left != TAIL_PTR:
            left_ptr = self.head_node.left
            self.nodes_r_in_pipe.put(left_ptr)
            left_neighbor = yield self.nodes_r_out_pipe.get()
            if self.head_node.left == pd_req.start and pd_req.valid:
                # the pd request must be inserted here (between the head's current left and the head)
                new_addr = self.node_free_list.pop()
                left = pd_req.start
                right = HEAD_PTR
                up = None # no need to add level above this yet
                new_node = PSL_level0_node(pd_req.rank, pd_req.timestamp, left, right, up, pd_req.meta_ptrs)
                left_neighbor.right = new_addr
                self.head_node.left = new_addr
                # write the new node
                self.nodes_w_in_pipe.put((new_addr, new_node))
                pd_req.valid = False # no need for further processing
            else:
                # replace the left neighbor's right pointer with the HEAD ptr
                left_neighbor.right = HEAD_PTR
            self.nodes_w_in_pipe.put((left_ptr, left_neighbor))
        else:
            # TODO: insert pd
        yield self.wait_clock()




    def removal_sm(self):
        """
        Receives requests to dequeue pkts and metadata from storage
        Reads:
          - self.ptr_in_pipe
        Writes:
          - self.pkt_out_pipe
        """
        while True:
            # wait for a read request
            (head_seg_ptr, meta_ptr) = yield self.ptr_in_pipe.get()

            # read the metadata
            self.metadata_r_in_pipe.put(meta_ptr) # send read request
            tuser = yield self.metadata_r_out_pipe.get() # wait for response
            self.free_meta_list.push(meta_ptr) # add meta_ptr to free list
   
            # read the packet
            pkt_str = ''
            cur_seg_ptr = head_seg_ptr
            while (cur_seg_ptr is not None):
                # send the read request
                self.segments_r_in_pipe.put(cur_seg_ptr)
                # wait for response
                pkt_seg = yield self.segments_r_out_pipe.get()

                pkt_str += pkt_seg.tdata
                # add segment to free list
                self.free_seg_list.push(cur_seg_ptr)
                cur_seg_ptr = pkt_seg.next_seg
    
            # reconstruct the final scapy packet
            pkt = Ether(pkt_str)
            # Write the final pkt and metadata
            self.pkt_out_pipe.put((pkt, tuser))
class SkipList(HW_sim_object):
    def __init__(self, env, period, size, outreg_width, enq_fifo_depth,
                 rd_latency, wr_latency, outreg_latency):
        super(SkipList, self).__init__(env, period)

        self.env = env
        self.period = period
        self.outreg_width = outreg_width
        self.outreg_latency = outreg_latency
        self.enq_fifo_depth = enq_fifo_depth

        # Process communication pipes
        self.search_in_pipe = simpy.Store(env)
        self.search_out_pipe = simpy.Store(env)
        self.enq_in_pipe = simpy.Store(env)
        self.enq_out_pipe = simpy.Store(env)
        self.deq_in_pipe = simpy.Store(env)
        self.deq_out_pipe = simpy.Store(env)
        self.outreg_ins_in_pipe = simpy.Store(env)
        self.outreg_ins_out_pipe = simpy.Store(env)
        self.outreg_rem_in_pipe = simpy.Store(env)
        self.outreg_rem_out_pipe = simpy.Store(env)
        self.nodes_r_in_pipe = simpy.Store(env)
        self.nodes_r_out_pipe = simpy.Store(env)
        self.nodes_w_in_pipe = simpy.Store(env)
        self.nodes_w_out_pipe = simpy.Store(env)

        # Block RAM for node memory
        depth = size
        self.nodes = BRAM(self.env, period, self.nodes_r_in_pipe,
                          self.nodes_r_out_pipe, self.nodes_w_in_pipe,
                          self.nodes_w_out_pipe, depth, wr_latency, rd_latency)
        # FIFO for free node list
        self.free_node_list = Fifo(size)
        # Output register on dequeue side
        self.outreg = out_reg(self.env, period, self.outreg_ins_in_pipe,
                              self.outreg_ins_out_pipe,
                              self.outreg_rem_in_pipe,
                              self.outreg_rem_out_pipe, outreg_width,
                              outreg_latency)
        # FIFO for enqueing into the skip list
        self.enq_fifo = Fifo(enq_fifo_depth)

        # Set current size and max level to zero
        self.num_entries = 0
        self.currMaxLevel = 0

        # Push all free nodes in free list FIFO
        for addr in range(size):
            self.free_node_list.push(addr)

        # log2_size is max height skip list will grow to
        self.log2_size = int(math.log(size, 2))

        # Head and tail pointers for each level, representing -inf and +inf
        self.head = (self.log2_size + 1) * [0]
        self.tail = (self.log2_size + 1) * [0]

        # Busy flag
        self.busy = 1

        # Next value to be output
        self.next_val = None

        # Lists to store time measurements
        self.bg_search_nclks_list = []
        self.bg_enq_nclks_list = []
        self.bg_deq_nclks_list = []

        # register processes for simulation
        self.run(env)

    def run(self, env):
        self.env.process(self.initSkipList())
        self.env.process(self.search())
        self.env.process(self.enqueue())
        self.env.process(self.dequeue())
        self.enq_sl_proc = self.env.process(self.enq_sl())
        self.deq_sl_proc = self.env.process(self.deq_sl())

    def __str__(self):
        outStr = ""
        # Level 0 head and tail
        h0 = self.head[0]
        t0 = self.tail[0]

        # Loop through all levels in descending order
        for i in range(self.currMaxLevel, -1, -1):
            val0, hsp0, mdp0, lvl0, r0, l0, u0, d0 = self.nodes.mem[h0]
            # +inf
            outStr += "+oo --"
            # For every node in level 0...
            while r0 != t0:
                val0, hsp0, mtp0, lvl0, r0, l0, u0, d0 = self.nodes.mem[r0]
                # Print value in level i if it exists in level 0
                j = 0
                u = u0
                while j < i:
                    if u == -1:
                        # print dashes if no connection between level 0 and level i
                        if val0 < 10:
                            outStr += "---"
                        elif val0 < 100:
                            outStr += "----"
                        else:
                            outStr += "-----"
                        break
                    else:
                        val, hsp, mdp, lvl, r, l, u, d = self.nodes.mem[u]
                        j += 1
                if i == j:
                    outStr += str(val0) + "--"
            # -inf
            outStr += " -oo\n"
        return outStr

    def initSkipList(self):
        prev_h = -1
        prev_t = -1
        # Initialize head and tail pointers up to log2(maxsize) levels
        for i in range(self.log2_size + 1):
            h = self.free_node_list.pop()
            t = self.free_node_list.pop()
            self.head[i] = h
            self.tail[i] = t

            if i > 0:
                # Read head from lower level
                # Send read request
                self.nodes_r_in_pipe.put(prev_h)
                # Wait for response
                prev_val, prev_hsp, prev_mdp, prev_lvl, prev_r, prev_l, prev_u, prev_d = yield self.nodes_r_out_pipe.get(
                )
                # Write back w/ up ptrs set to this level
                self.nodes_w_in_pipe.put((prev_h, [
                    prev_val, prev_hsp, prev_mdp, prev_lvl, prev_r, prev_l, h,
                    prev_d
                ]))
                yield self.nodes_w_out_pipe.get()
                # Read tail from lower level
                self.nodes_r_in_pipe.put(prev_t)
                prev_val, prev_hsp, prev_mdp, prev_lvl, prev_r, prev_l, prev_u, prev_d = yield self.nodes_r_out_pipe.get(
                )
                # Write back w/ up ptrs set to this level
                self.nodes_w_in_pipe.put((prev_t, [
                    prev_val, prev_hsp, prev_mdp, prev_lvl, prev_r, prev_l, t,
                    prev_d
                ]))
                yield self.nodes_w_out_pipe.get()

            # Write current level's head/tail
            self.nodes_w_in_pipe.put(
                (h, [POS_INF, -1, -1, i, t, -1, -1, prev_h]))
            yield self.nodes_w_out_pipe.get()
            self.nodes_w_in_pipe.put(
                (t, [NEG_INF, -1, -1, i, -1, h, -1, prev_t]))
            yield self.nodes_w_out_pipe.get()

            prev_h = h
            prev_t = t

            self.busy = 0
#            print ("sl init done @", self.env.now)

# Search for insertion point for new value

    def search(self):
        while True:
            # wait for search command
            value = yield self.search_in_pipe.get()
            t1 = self.env.now
            level = self.currMaxLevel
            n = self.head[level]
            u = self.head[level + 1]
            # Loop until bottom level
            while level >= 0:
                print("level:", level)
                cons_nodes = 0
                # Read node n
                self.nodes_r_in_pipe.put(n)
                nVal, nHsp, nMdp, nLvl, nR, nL, nU, nD = yield self.nodes_r_out_pipe.get(
                )
                d, dVal, dHsp, dMdp, dLvl, dR, dL, dU, dD = n, nVal, nHsp, nMdp, nLvl, nR, nL, nU, nD
                # While traversing this level searcing for consecutive nodes...
                while nR != -1:
                    # Store current node in l
                    l, lVal, lHsp, lMdp, lLvl, lR, lL, lU, lD = n, nVal, nHsp, nMdp, nLvl, nR, nL, nU, nD
                    print("curr node: @", n, "val:", nVal, "level", nLvl)
                    # Move right
                    n = nR
                    self.nodes_r_in_pipe.put(n)
                    nVal, nHsp, nMdp, nLvl, nR, nL, nU, nD = yield self.nodes_r_out_pipe.get(
                    )
                    # Save the node at which we will drop down
                    if nVal > value:
                        d, dVal, dHsp, dMdp, dLvl, dR, dL, dU, dD = n, nVal, nHsp, nMdp, nLvl, nR, nL, nU, nD
                    # Exit if reached a higher node stack
                    if nU != -1:
                        break
                    # Count consecutive nodes except for head
                    cons_nodes += 1
                    print("cons_nodes:", cons_nodes)
                    # If max number of consecutive nodes found
                    if cons_nodes == MAX_CONS_NODES:
                        # Insert new node one level above
                        # Read node in level above
                        self.nodes_r_in_pipe.put(u)
                        uVal, uHsp, uMdp, uLvl, uR, uL, uU, uD = yield self.nodes_r_out_pipe.get(
                        )
                        print("read u: @", u, "val:", uVal)
                        # Get new node
                        m = self.free_node_list.pop()
                        # Connect new node
                        self.nodes_w_in_pipe.put(
                            (m, [lVal, lHsp, lMdp, level + 1, uR, u, -1, l]))
                        yield self.nodes_w_out_pipe.get()
                        print("adding node: @", m, "val:", lVal, "level:",
                              level + 1, "r:", uR, "l:", u, "u:", -1, "d:", l)
                        # Connect right neighbor to new node
                        # Read right neighbor of upper node
                        self.nodes_r_in_pipe.put(uR)
                        uRVal, uRHsp, uRMdp, uRLvl, uRR, uRL, uRU, uRD = yield self.nodes_r_out_pipe.get(
                        )
                        print("read uR: @", uR, "val:", uRVal)
                        # Write back
                        self.nodes_w_in_pipe.put(
                            (uR,
                             [uRVal, uRHsp, uRMdp, uRLvl, uRR, m, uRU, uRD]))
                        yield self.nodes_w_out_pipe.get()
                        print("write uR: @", uR, "val:", uRVal, "l:", m)

                        # Connect left neighbor to new node
                        self.nodes_w_in_pipe.put(
                            (u, [uVal, uHsp, uMdp, uLvl, m, uL, uU, uD]))
                        yield self.nodes_w_out_pipe.get()
                        print("write u: @", u, "val:", uVal, "r:", m)

                        # Connect node below to new node
                        self.nodes_w_in_pipe.put(
                            (l, [lVal, lHsp, lMdp, lLvl, lR, lL, m, lD]))
                        yield self.nodes_w_out_pipe.get()
                        print("write l: @", l, "val:", lVal, "u:", m)

                        # Increment current level if we added a new level
                        if level + 1 > self.currMaxLevel:
                            self.currMaxLevel += 1
                            print("incr currMaxLev:", self.currMaxLevel)
                            break

                u = d
                n = dD
                level -= 1
            # Output result
            nclks = self.env.now - t1
            self.search_out_pipe.put(
                ((d, dVal, dHsp, dMdp, dLvl, dR, dL, dU, dD), nclks))

    def enq_sl(self):
        while True:
            try:
                yield self.env.timeout(self.period)
                # If enq_fifo not empty and there's room in skip list, process entry
                if self.enq_fifo.fill_level(
                ) > 0 and self.free_node_list.fill_level() >= (
                        self.currMaxLevel + 1) and self.busy == 0:
                    #print ("enq_sl:", self.env.now)
                    self.busy = 1
                    t1 = self.env.now
                    (value, (hsp, mdp)) = self.enq_fifo.pop()

                    # Find insertion point
                    self.search_in_pipe.put(value)
                    ((n, nVal, nHsp, nMdp, nLvl, nR, nL, nU, nD),
                     search_nclks) = yield self.search_out_pipe.get()

                    # Insert new node at level 0
                    m = self.free_node_list.pop()
                    # Connect new node to neighbors on same level
                    self.nodes_w_in_pipe.put(
                        (m, [value, hsp, mdp, 0, nR, n, -1, -1]))
                    yield self.nodes_w_out_pipe.get()

                    # Connect right neighbor to new node
                    # Read right neighbor
                    self.nodes_r_in_pipe.put(nR)
                    nRVal, nRHsp, nRMdp, nRLvl, nRR, nRL, nRU, nRD = yield self.nodes_r_out_pipe.get(
                    )
                    # Write back
                    self.nodes_w_in_pipe.put(
                        (nR, [nRVal, nRHsp, nRMdp, nRLvl, nRR, m, nRU, nRD]))
                    yield self.nodes_w_out_pipe.get()

                    # Connect left neighbor
                    self.nodes_w_in_pipe.put(
                        (n, [nVal, nHsp, nMdp, nLvl, m, nL, nU, nD]))
                    yield self.nodes_w_out_pipe.get()

                    # Write time measurements to lists
                    self.bg_search_nclks_list.append(search_nclks)
                    enq_nclks = self.env.now - t1 - search_nclks
                    self.bg_enq_nclks_list.append(enq_nclks)
                    self.busy = 0

            except simpy.Interrupt as i:
                #                print ("enq_sl stopped")
                break

    def enqueue(self):
        while True:
            # Wait for enqueue command
            (value, hsp, mdp) = yield self.enq_in_pipe.get()
            t1 = self.env.now
            # Wait if out reg and enqueue FIFO are full
            while (self.outreg.num_entries == self.outreg.width
                   and self.enq_fifo.fill_level()
                   == self.enq_fifo_depth) or self.outreg.busy == 1:
                yield self.env.timeout(self.period)
            # Insert into output reg
            self.outreg_ins_in_pipe.put((value, [hsp, mdp]))
            (out_reg_val, out_reg_ptrs) = yield self.outreg_ins_out_pipe.get()
            if out_reg_val != -1:
                # out reg insert returned an entry (either same new entry or one that was evicted from out reg)
                # push entry into enqueue FIFO
                self.enq_fifo.push((out_reg_val, out_reg_ptrs))

            enq_nclks = self.env.now - t1
            self.enq_out_pipe.put((0, enq_nclks))
            self.num_entries += 1

    def deq_sl(self):
        while True:
            try:
                # Wait one clock
                yield self.env.timeout(self.period)
                # If there's room in out reg and there are entries in skip list and it's not busy
                if (
                        self.outreg.num_entries < self.outreg.width
                ) and self.num_entries > self.outreg.num_entries and self.busy == 0:
                    t1 = self.env.now
                    self.busy = 1
                    # Point to tail node in level 0
                    t = self.tail[0]
                    # Read tail
                    self.nodes_r_in_pipe.put(t)
                    tVal, tHsp, tMdp, tLvl, tR, tL, tU, tD = yield self.nodes_r_out_pipe.get(
                    )
                    # Read node to dequeue
                    self.nodes_r_in_pipe.put(tL)
                    dqVal, dqHsp, dqMdp, dqLvl, dqR, dqL, dqU, dqD = yield self.nodes_r_out_pipe.get(
                    )

                    # Send dequeued value to out reg
                    self.outreg.ins_in_pipe.put((dqVal, [dqHsp, dqMdp]))
                    (tmpVal, tmpPtrs) = yield self.outreg_ins_out_pipe.get()
                    # tmpVal should be -1 because there was room available in out reg
                    if tmpVal != -1:
                        print(
                            "Dequeue Error!: Received non-null value from out reg:",
                            tmpVal, tmpPtrs)

                    # Read left neighbor
                    self.nodes_r_in_pipe.put(dqL)
                    llVal, llHsp, llMdp, llLvl, llR, llL, llU, llD = yield self.nodes_r_out_pipe.get(
                    )
                    # Connect left neighbor to tail
                    self.nodes_w_in_pipe.put(
                        (dqL, [llVal, llHsp, llMdp, llLvl, t, llL, llU, llD]))
                    yield self.nodes_w_out_pipe.get()
                    self.nodes_w_in_pipe.put(
                        (t, [tVal, tHsp, tMdp, tLvl, tR, dqL, tU, tD]))
                    yield self.nodes_w_out_pipe.get()

                    # Clear node and return it to free list
                    self.nodes_w_in_pipe.put(
                        (tL, [-1, -1, -1, -1, -1, -1, -1, -1]))
                    yield self.nodes_w_out_pipe.get()
                    self.free_node_list.push(tL)

                    # Loop to free any nodes above
                    while dqU != -1:
                        # Read up neighbor
                        self.nodes_r_in_pipe.put(dqU)
                        uVal, uHsp, uMdp, uLvl, uR, uL, uU, uD = yield self.nodes_r_out_pipe.get(
                        )
                        # Read tail connected to this node
                        self.nodes_r_in_pipe.put(uR)
                        tVal, tHsp, tMdp, tLvl, tR, tL, tU, tD = yield self.nodes_r_out_pipe.get(
                        )
                        # Read left neighbor
                        self.nodes_r_in_pipe.put(uL)
                        lVal, lHsp, lMdp, lLvl, lR, lL, lU, lD = yield self.nodes_r_out_pipe.get(
                        )
                        # Connect left neighbor to tail
                        self.nodes_w_in_pipe.put(
                            (uL, [lVal, lHsp, lMdp, lLvl, uR, lL, lU, lD]))
                        self.nodes_w_out_pipe.get()
                        self.nodes_w_in_pipe.put(
                            (uR, [tVal, tHsp, tMdp, tLvl, tR, uL, tU, tD]))
                        self.nodes_w_out_pipe.get()
                        # If level is empty, decrement current max level
                        if tLvl == self.currMaxLevel and uL == self.head[
                                self.currMaxLevel]:
                            self.currMaxLevel -= 1
                        # Clear node and return it to free list
                        self.nodes_w_in_pipe.put(
                            (dqU, [-1, -1, -1, -1, -1, -1, -1, -1]))
                        yield self.nodes_w_out_pipe.get()
                        self.free_node_list.push(dqU)
                        # Move up
                        dqU = uU

                    deq_nclks = self.env.now - t1
                    self.bg_deq_nclks_list.append(deq_nclks)
                    self.busy = 0

            except simpy.Interrupt as i:
                #                print ("deq_sl stopped")
                break

    def dequeue(self):
        while True:
            # Wait for dequeue command
            yield self.deq_in_pipe.get()
            t1 = self.env.now
            # Send remove request to out reg
            self.outreg_rem_in_pipe.put(True)
            (retVal, (retHsp, retMdp)) = yield self.outreg_rem_out_pipe.get()
            self.num_entries -= 1
            # Output deq result
            deq_nclks = self.env.now - t1
            self.deq_out_pipe.put((retVal, retHsp, retMdp, deq_nclks))
class SkipList(HW_sim_object):
    def __init__(self, env, period, size, outreg_width, enq_fifo_depth,
                 rd_latency, wr_latency, outreg_latency):
        super(SkipList, self).__init__(env, period)

        self.env = env
        self.period = period
        self.outreg_width = outreg_width
        self.outreg_latency = outreg_latency
        self.enq_fifo_depth = enq_fifo_depth

        # Process communication pipes
        self.search_in_pipe = simpy.Store(env)
        self.search_out_pipe = simpy.Store(env)
        self.enq_in_pipe = simpy.Store(env)
        self.enq_out_pipe = simpy.Store(env)
        self.deq_in_pipe = simpy.Store(env)
        self.deq_out_pipe = simpy.Store(env)
        self.outreg_ins_in_pipe = simpy.Store(env)
        self.outreg_ins_out_pipe = simpy.Store(env)
        self.outreg_rem_in_pipe = simpy.Store(env)
        self.outreg_rem_out_pipe = simpy.Store(env)
        self.nodes_r_in_pipe = simpy.Store(env)
        self.nodes_r_out_pipe = simpy.Store(env)
        self.nodes_w_in_pipe = simpy.Store(env)
        self.nodes_w_out_pipe = simpy.Store(env)

        # Block RAM for node memory
        depth = size
        self.nodes = BRAM(self.env, period, self.nodes_r_in_pipe,
                          self.nodes_r_out_pipe, self.nodes_w_in_pipe,
                          self.nodes_w_out_pipe, depth, wr_latency, rd_latency)
        # FIFO for free node list
        self.free_node_list = Fifo(size)
        # Output register on dequeue side
        self.outreg = out_reg(self.env, period, self.outreg_ins_in_pipe,
                              self.outreg_ins_out_pipe,
                              self.outreg_rem_in_pipe,
                              self.outreg_rem_out_pipe, outreg_width,
                              outreg_latency)
        # FIFO for enqueing into the skip list
        self.enq_fifo = Fifo(enq_fifo_depth)

        # Set current size and max level to zero
        self.num_entries = 0
        self.currMaxLevel = 0

        # Push all free nodes in free list FIFO
        for addr in range(size):
            self.free_node_list.push(addr)

        # log2_size is max height skip list will grow to
        self.log2_size = int(math.log(size, 2))

        # Head and tail pointers for each level, representing -inf and +inf
        self.head = self.log2_size * [0]
        self.tail = self.log2_size * [0]

        # Busy flag
        self.busy = 1

        # Next value to be output
        self.next_val = None

        # Lists to store time measurements
        self.bg_search_nclks_list = []
        self.bg_enq_nclks_list = []
        self.bg_deq_nclks_list = []

        # register processes for simulation
        self.run(env)

    def run(self, env):
        self.env.process(self.initSkipList())
        self.env.process(self.search())
        self.env.process(self.enqueue())
        self.env.process(self.dequeue())
        self.enq_sl_proc = self.env.process(self.enq_sl())
        self.deq_sl_proc = self.env.process(self.deq_sl())

    def __str__(self):
        outStr = ""
        # Level 0 head and tail
        h0 = self.head[0]
        t0 = self.tail[0]

        # Loop through all levels in descending order
        for i in range(self.currMaxLevel, -1, -1):
            val0, hsp0, mdp0, lvl0, r0, l0, u0, d0 = self.nodes.mem[h0]
            # -inf
            outStr += "-oo--"
            # For every node in level 0...
            while r0 != t0:
                val0, hsp0, mtp0, lvl0, r0, l0, u0, d0 = self.nodes.mem[r0]
                # Print value in level i if it exists in level 0
                j = 0
                u = u0
                while j < i:
                    if u == -1:
                        # print dashes if no connection between level 0 and level i
                        if val0 < 10:
                            outStr += "---"
                        elif val0 < 100:
                            outStr += "----"
                        else:
                            outStr += "-----"
                        break
                    else:
                        val, hsp, mdp, lvl, r, l, u, d = self.nodes.mem[u]
                        j += 1
                if i == j:
                    outStr += str(val0) + "--"
            # +inf
            outStr += "+oo\n"
        return outStr

    def initSkipList(self):
        prev_h = -1
        prev_t = -1
        # Initialize head and tail pointers up to log2(maxsize) levels
        for i in range(self.log2_size):
            h = self.free_node_list.pop()
            t = self.free_node_list.pop()
            self.head[i] = h
            self.tail[i] = t

            if i > 0:
                # Read head from lower level
                # Send read request
                self.nodes_r_in_pipe.put(prev_h)
                # Wait for response
                prev_val, prev_hsp, prev_mdp, prev_lvl, prev_r, prev_l, prev_u, prev_d = yield self.nodes_r_out_pipe.get(
                )
                # Write back w/ up ptrs set to this level
                self.nodes_w_in_pipe.put((prev_h, [
                    prev_val, prev_hsp, prev_mdp, prev_lvl, prev_r, prev_l, h,
                    prev_d
                ]))
                yield self.nodes_w_out_pipe.get()
                # Read tail from lower level
                self.nodes_r_in_pipe.put(prev_t)
                prev_val, prev_hsp, prev_mdp, prev_lvl, prev_r, prev_l, prev_u, prev_d = yield self.nodes_r_out_pipe.get(
                )
                # Write back w/ up ptrs set to this level
                self.nodes_w_in_pipe.put((prev_t, [
                    prev_val, prev_hsp, prev_mdp, prev_lvl, prev_r, prev_l, t,
                    prev_d
                ]))
                yield self.nodes_w_out_pipe.get()

            # Write current level's head/tail
            self.nodes_w_in_pipe.put(
                (h, [POS_INF, -1, -1, i, t, -1, -1, prev_h]))
            yield self.nodes_w_out_pipe.get()
            self.nodes_w_in_pipe.put(
                (t, [NEG_INF, -1, -1, i, -1, h, -1, prev_t]))
            yield self.nodes_w_out_pipe.get()

            prev_h = h
            prev_t = t

        self.busy = 0
#        print ("sl init done @", self.env.now)

# Search for value starting at startNode and stopping at stopLevel

    def search(self):
        while True:
            # wait for search command
            (startNode, stopLevel, value) = yield self.search_in_pipe.get()
            t1 = self.env.now
            n = startNode
            self.nodes_r_in_pipe.put(n)
            val, hsp, mdp, lvl, r, l, u, d = yield self.nodes_r_out_pipe.get()
            dn = d
            while True:
                # Move right as long as value is smaller than nodes on this level
                if value < val:
                    if r != -1:
                        dn = d
                        n = r
                        self.nodes_r_in_pipe.put(n)
                        val, hsp, mdp, lvl, r, l, u, d = yield self.nodes_r_out_pipe.get(
                        )
                else:
                    # Backtrack one
                    n = l
                    # Stop if stopLevel reached
                    if lvl == stopLevel:
                        break
                    else:
                        # Otherwise, go down
                        self.nodes_r_in_pipe.put(dn)
                        val, hsp, mdp, lvl, r, l, u, d = yield self.nodes_r_out_pipe.get(
                        )
            # Output result
            nclks = self.env.now - t1
            self.search_out_pipe.put((n, dn, nclks))

    def enq_sl(self):
        while True:
            try:
                yield self.env.timeout(self.period)
                # If enq_fifo not empty and there's room in skip list, process entry
                if self.enq_fifo.fill_level(
                ) > 0 and self.free_node_list.fill_level() >= (
                        self.currMaxLevel + 1) and self.busy == 0:
                    #print ("enq_sl:", self.env.now)
                    self.busy = 1
                    t1 = self.env.now
                    (value, (hsp, mdp)) = self.enq_fifo.pop()

                    # Update max level
                    self.currMaxLevel = int(
                        math.log(self.num_entries - self.outreg.num_entries,
                                 2))
                    # Generate random number between 0 and current max level (inclusive)
                    level = random.randint(0, self.currMaxLevel)
                    # Start search from head of skip list
                    startNode = self.head[self.currMaxLevel]
                    uNode = -1
                    search_nclks_tot = 0
                    # Insert new nodes at each level starting at randomly selected level and descending to level zero
                    while level >= 0:
                        # Find insertion point at this level starting from the closest preceding node
                        self.search_in_pipe.put((startNode, level, value))
                        (n, startNode,
                         search_nclks) = yield self.search_out_pipe.get()
                        search_nclks_tot += search_nclks
                        # Read node at insertion point
                        self.nodes_r_in_pipe.put(n)
                        lVal, lHsp, lMdp, lLvl, lR, lL, lU, lD = yield self.nodes_r_out_pipe.get(
                        )
                        # Get new node from free list
                        newNode = self.free_node_list.pop()
                        # Connect left neighbor to new node
                        self.nodes_w_in_pipe.put(
                            (n, [lVal, lHsp, lMdp, lLvl, newNode, lL, lU, lD]))
                        yield self.nodes_w_out_pipe.get()
                        # Connect right neighbor
                        self.nodes_r_in_pipe.put(lR)
                        rVal, rHsp, rMdp, rLvl, rR, rL, rU, rD = yield self.nodes_r_out_pipe.get(
                        )
                        self.nodes_w_in_pipe.put(
                            (lR, [rVal, rHsp, rMdp, rLvl, rR, newNode, rU,
                                  rD]))
                        yield self.nodes_w_out_pipe.get()
                        # Connect with level above if any
                        if uNode != -1:
                            self.nodes_r_in_pipe.put(uNode)
                            uVal, uHsp, uMdp, uLvl, uR, uL, uU, uD = yield self.nodes_r_out_pipe.get(
                            )
                            self.nodes_w_in_pipe.put(
                                (uNode,
                                 [uVal, uHsp, uMdp, uLvl, uR, uL, uU,
                                  newNode]))
                            yield self.nodes_w_out_pipe.get()
                        # Connect new node to l/r neighbors on same level and up.  Down ptr is connected in next cycle
                        newVal, newHsp, newMdp, newLvl, newR, newL = value, hsp, mdp, level, lR, n
                        self.nodes_w_in_pipe.put((newNode, [
                            newVal, newHsp, newMdp, newLvl, newR, newL, uNode,
                            -1
                        ]))
                        yield self.nodes_w_out_pipe.get()
                        uNode = newNode
                        uVal, uHsp, uMdp, uLvl, uR, uL, uU = newVal, newHsp, newMdp, newLvl, newR, newL, newNode
                        # Next level down
                        level -= 1
                    # Write time measurements to lists
                    self.bg_search_nclks_list.append(search_nclks_tot)
                    enq_nclks = self.env.now - t1 - search_nclks_tot
                    self.bg_enq_nclks_list.append(enq_nclks)
                    self.busy = 0

            except simpy.Interrupt as i:
                #                print ("enq_sl stopped")
                break

    def enqueue(self):
        while True:
            # Wait for enqueue command
            (value, hsp, mdp) = yield self.enq_in_pipe.get()
            t1 = self.env.now
            # Wait if out reg and enqueue FIFO are full
            while (self.outreg.num_entries == self.outreg.width
                   and self.enq_fifo.fill_level()
                   == self.enq_fifo_depth) or self.outreg.busy == 1:
                yield self.env.timeout(self.period)
            # Insert into output reg
            self.outreg_ins_in_pipe.put((value, [hsp, mdp]))
            (out_reg_val, out_reg_ptrs) = yield self.outreg_ins_out_pipe.get()
            if out_reg_val != -1:
                # out reg insert returned an entry (either same new entry or one that was evicted from out reg)
                # push entry into enqueue FIFO
                self.enq_fifo.push((out_reg_val, out_reg_ptrs))

            enq_nclks = self.env.now - t1
            self.enq_out_pipe.put((0, enq_nclks))
            self.num_entries += 1

    def deq_sl(self):
        while True:
            try:
                # Wait one clock
                yield self.env.timeout(self.period)
                # If there's room in out reg and there are entries in skip list and it's not busy
                if (
                        self.outreg.num_entries < self.outreg.width
                ) and self.num_entries > self.outreg.num_entries and self.busy == 0:
                    t1 = self.env.now
                    self.busy = 1
                    # Point to tail node in level 0
                    t = self.tail[0]
                    # Read tail
                    self.nodes_r_in_pipe.put(t)
                    tVal, tHsp, tMdp, tLvl, tR, tL, tU, tD = yield self.nodes_r_out_pipe.get(
                    )
                    # Read node to dequeue
                    self.nodes_r_in_pipe.put(tL)
                    dqVal, dqHsp, dqMdp, dqLvl, dqR, dqL, dqU, dqD = yield self.nodes_r_out_pipe.get(
                    )

                    # Send dequeued value to out reg
                    self.outreg.ins_in_pipe.put((dqVal, [dqHsp, dqMdp]))
                    (tmpVal, tmpPtrs) = yield self.outreg_ins_out_pipe.get()
                    # tmpVal should be -1 because there was room available in out reg
                    if tmpVal != -1:
                        print(
                            "Dequeue Error!: Received non-null value from out reg:",
                            tmpVal, tmpPtrs)

                    # Read left neighbor
                    self.nodes_r_in_pipe.put(dqL)
                    llVal, llHsp, llMdp, llLvl, llR, llL, llU, llD = yield self.nodes_r_out_pipe.get(
                    )
                    # Connect left neighbor to tail
                    self.nodes_w_in_pipe.put(
                        (dqL, [llVal, llHsp, llMdp, llLvl, t, llL, llU, llD]))
                    yield self.nodes_w_out_pipe.get()
                    self.nodes_w_in_pipe.put(
                        (t, [tVal, tHsp, tMdp, tLvl, tR, dqL, tU, tD]))
                    yield self.nodes_w_out_pipe.get()

                    # Clear node and return it to free list
                    self.nodes_w_in_pipe.put(
                        (tL, [-1, -1, -1, -1, -1, -1, -1, -1]))
                    yield self.nodes_w_out_pipe.get()
                    self.free_node_list.push(tL)

                    # Loop to free any nodes above
                    while dqU != -1 and dqLvl <= self.currMaxLevel:
                        # Read up neighbor
                        self.nodes_r_in_pipe.put(dqU)
                        uVal, uHsp, uMdp, uLvl, uR, uL, uU, uD = yield self.nodes_r_out_pipe.get(
                        )
                        # Read tail connected to this node
                        self.nodes_r_in_pipe.put(uR)
                        tVal, tHsp, tMdp, tLvl, tR, tL, tU, tD = yield self.nodes_r_out_pipe.get(
                        )
                        # Read left neighbor
                        self.nodes_r_in_pipe.put(uL)
                        lVal, lHsp, lMdp, lLvl, lR, lL, lU, lD = yield self.nodes_r_out_pipe.get(
                        )
                        # Connect left neighbor to tail
                        self.nodes_w_in_pipe.put(
                            (uL, [lVal, lHsp, lMdp, lLvl, uR, lL, lU, lD]))
                        self.nodes_w_out_pipe.get()
                        self.nodes_w_in_pipe.put(
                            (uR, [tVal, tHsp, tMdp, tLvl, tR, uL, tU, tD]))
                        self.nodes_w_out_pipe.get()
                        # Clear node and return it to free list
                        self.nodes_w_in_pipe.put(
                            (dqU, [-1, -1, -1, -1, -1, -1, -1, -1]))
                        yield self.nodes_w_out_pipe.get()
                        self.free_node_list.push(dqU)
                        # Move up
                        dqU = uU

                    # Adjust max level
                    maxLevel = int(
                        math.log(
                            self.num_entries - self.outreg.num_entries + 1, 2))
                    # if levels decreased, remove any nodes left in the top level
                    if maxLevel < self.currMaxLevel:
                        h = self.head[self.currMaxLevel]
                        t = self.tail[self.currMaxLevel]
                        self.nodes_r_in_pipe.put(h)
                        val, hsp, mdp, lvl, r, l, u, d = yield self.nodes_r_out_pipe.get(
                        )
                        # Connect head and tail in vacated level
                        self.nodes_w_in_pipe.put((h, [
                            POS_INF, -1, -1, lvl, t, -1, -1, self.head[lvl - 1]
                        ]))
                        yield self.nodes_w_out_pipe.get()
                        self.nodes_w_in_pipe.put((t, [
                            NEG_INF, -1, -1, lvl, -1, h, -1, self.tail[lvl - 1]
                        ]))
                        yield self.nodes_w_out_pipe.get()
                        self.currMaxLevel = maxLevel
                        # Walk through all nodes at that level and free them
                        while (r != t):
                            # Read right node
                            self.nodes_r_in_pipe.put(r)
                            rVal, rHsp, rMdp, rLvl, rR, rL, rU, rD = yield self.nodes_r_out_pipe.get(
                            )

                            # Null out up ptrs in nodes below
                            self.nodes_r_in_pipe.put(rD)
                            dVal, dHsp, dMdp, dLvl, dR, dL, dU, dD = yield self.nodes_r_out_pipe.get(
                            )
                            self.nodes_w_in_pipe.put(
                                (rD, [dVal, dHsp, dMdp, dLvl, dR, dL, -1, dD]))
                            yield self.nodes_w_out_pipe.get()

                            # Clear node and free it
                            self.nodes_w_in_pipe.put(
                                (r, [-1, -1, -1, -1, -1, -1, -1, -1]))
                            self.nodes_w_out_pipe.get()
                            self.free_node_list.push(r)
                            # Move right
                            r = rR

                    deq_nclks = self.env.now - t1
                    self.bg_deq_nclks_list.append(deq_nclks)
                    self.busy = 0

            except simpy.Interrupt as i:
                #                print ("deq_sl stopped")
                break

    def dequeue(self):
        while True:
            # Wait for dequeue command
            yield self.deq_in_pipe.get()
            t1 = self.env.now
            # Send remove request to out reg
            self.outreg_rem_in_pipe.put(True)
            (retVal, (retHsp, retMdp)) = yield self.outreg_rem_out_pipe.get()
            self.num_entries -= 1
            # Output deq result
            deq_nclks = self.env.now - t1
            self.deq_out_pipe.put((retVal, retHsp, retMdp, deq_nclks))
class Pkt_storage(HW_sim_object):
    def __init__(self, env, period, pkt_in_pipe, pkt_out_pipe, ptr_in_pipe, ptr_out_pipe, max_segments=MAX_SEGMENTS, max_pkts=MAX_PKTS, rd_latency=1, wr_latency=1):
        super(Pkt_storage, self).__init__(env, period)

        # read the incomming pkt and metadata from here
        self.pkt_in_pipe = pkt_in_pipe
        # write the outgoing pkt and metadata into here
        self.pkt_out_pipe = pkt_out_pipe

        # read the incoming head_seg_ptr and metadata_ptr from here
        self.ptr_in_pipe = ptr_in_pipe
        # write the outgoing head_seg_ptr and metadata_ptr into here
        self.ptr_out_pipe = ptr_out_pipe

        self.segments_r_in_pipe = simpy.Store(env)
        self.segments_r_out_pipe = simpy.Store(env)
        self.segments_w_in_pipe = simpy.Store(env)
        # maps: segment ID --> Pkt_seg object
        self.segments = BRAM(env, period, self.segments_r_in_pipe, self.segments_r_out_pipe, self.segments_w_in_pipe, depth=max_segments, write_latency=wr_latency, read_latency=rd_latency)

        self.metadata_r_in_pipe = simpy.Store(env)
        self.metadata_r_out_pipe = simpy.Store(env)
        self.metadata_w_in_pipe = simpy.Store(env)
        # maps: metadata ptr --> tuser object
        self.metadata = BRAM(env, period, self.metadata_r_in_pipe, self.metadata_r_out_pipe, self.metadata_w_in_pipe, depth=max_pkts, write_latency=wr_latency, read_latency=rd_latency)

        self.max_segments = max_segments
        self.max_pkts = max_pkts

        # stores ID of free segments
        self.free_seg_list = Fifo(max_segments)
        # stores ID of free tuser blocks
        self.free_meta_list = Fifo(max_pkts)

        self.init_free_lists()

        self.run()

    """
    Initialize free lists
    """
    def init_free_lists(self):
        # Add all segments to free_seg_list
        for i in range(self.max_segments):
            self.free_seg_list.push(i)

        # Add all metadata blocks to free_meta_list
        for i in range(self.max_pkts):
            self.free_meta_list.push(i)

    def run(self):
        """Register the processes with the simulation environment
        """
        self.env.process(self.insertion_sm())
        self.env.process(self.removal_sm())


    def insertion_sm(self):
        """Constantly read the in_pipe and write incomming data into packet storage
           Items that come out of the in_pipe should be of the form: (scapy pkt, Tuser object)
           Reads:
             - self.pkt_in_pipe
           Writes:
             - self.ptr_out_pipe
        """
        while True:
            # wait for a pkt to come in
            (pkt, tuser) = yield self.pkt_in_pipe.get() 

            # get a free metadata block
            meta_ptr = self.free_meta_list.pop()
            # get a free segment
            cur_seg_ptr = self.free_seg_list.pop()

            head_seg_ptr = cur_seg_ptr
            # write the head_seg_ptr and meta_ptr so skip list can start insertion ASAP
            self.ptr_out_pipe.put((head_seg_ptr, meta_ptr))

            # write the metadata block into BRAM
            self.metadata_w_in_pipe.put((meta_ptr, tuser))

            # write the pkt into segments
            pkt_str = str(pkt)
            while len(pkt_str) > SEG_SIZE:
                tdata = pkt_str[0:SEG_SIZE]
                next_seg_ptr = self.free_seg_list.pop()
                # create the new segment
                self.segments_w_in_pipe.put((cur_seg_ptr, Pkt_segment(tdata, next_seg_ptr)))
                pkt_str = pkt_str[SEG_SIZE:]
                cur_seg_ptr = next_seg_ptr 
            tdata = pkt_str
            next_seg_ptr = None
            # create the final segment for the packet
            self.segments_w_in_pipe.put((cur_seg_ptr, Pkt_segment(tdata, next_seg_ptr)))


    def removal_sm(self):
        """
        Receives requests to dequeue pkts and metadata from storage
        Reads:
          - self.ptr_in_pipe
        Writes:
          - self.pkt_out_pipe
        """
        while True:
            # wait for a read request
            (head_seg_ptr, meta_ptr) = yield self.ptr_in_pipe.get()

            # read the metadata
            self.metadata_r_in_pipe.put(meta_ptr) # send read request
            tuser = yield self.metadata_r_out_pipe.get() # wait for response
            self.free_meta_list.push(meta_ptr) # add meta_ptr to free list
   
            # read the packet
            pkt_str = ''
            cur_seg_ptr = head_seg_ptr
            while (cur_seg_ptr is not None):
                # send the read request
                self.segments_r_in_pipe.put(cur_seg_ptr)
                # wait for response
                pkt_seg = yield self.segments_r_out_pipe.get()

                pkt_str += pkt_seg.tdata
                # add segment to free list
                self.free_seg_list.push(cur_seg_ptr)
                cur_seg_ptr = pkt_seg.next_seg
    
            # reconstruct the final scapy packet
            pkt = Ether(pkt_str)
            # Write the final pkt and metadata
            self.pkt_out_pipe.put((pkt, tuser))