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))