Repository: pymtl/pymtl3 (language: python)

Issue:
bypass queues should now support non-power-of-2 sizes
Creating a pull request for a fix to the bypass queues in the ece5745-2021 version of pymtl3. Before they did not support queue sizes that were not a power of 2.


Discussion / hints:

Fix patch:
diff --git a/pymtl3/datatypes/PythonBits.py b/pymtl3/datatypes/PythonBits.py
index d387e358d..33bd47f23 100644
--- a/pymtl3/datatypes/PythonBits.py
+++ b/pymtl3/datatypes/PythonBits.py
@@ -415,7 +415,23 @@ def __eq__( self, other ):
                           f"Suggestion: 0 <= x <= {hex(_upper[ nbits ])}" )
       return _new_valid_bits( 1, self._uint == other )
 
-  # No need for __ne__
+  def __ne__( self, other ):
+    nbits = self._nbits
+    try:
+      if other.nbits != nbits:
+        raise ValueError( f"Operands of '!=' (ne) operation must have matching bitwidth, "\
+                          f"but here Bits{nbits} != Bits{other.nbits}.\n" )
+      return _new_valid_bits( 1, self._uint != other._uint )
+    except AttributeError:
+      try:
+        other = int(other)
+      except:
+        return _new_valid_bits( 1, 1 )
+
+      if other < 0 or other > _upper[ nbits ]:
+        raise ValueError( f"Integer {hex(other)} is not a valid binop operand with Bits{nbits}!\n"
+                          f"Suggestion: 0 <= x <= {hex(_upper[ nbits ])}" )
+      return _new_valid_bits( 1, self._uint != other )
 
   def __lt__( self, other ):
     nbits = self._nbits
diff --git a/pymtl3/dsl/ComponentLevel2.py b/pymtl3/dsl/ComponentLevel2.py
index 3de3cc622..ad1c6604b 100644
--- a/pymtl3/dsl/ComponentLevel2.py
+++ b/pymtl3/dsl/ComponentLevel2.py
@@ -120,6 +120,8 @@ def extract_obj_from_names( func, names, update_ff=False, is_write=False ):
 
       def expand_array_index( obj, name_depth, node_depth, idx_depth, idx ):
         """ Find s.x[0][*][2], if index is exhausted, jump back to lookup_variable """
+        if obj is None:
+          return
 
         if idx_depth >= len(idx): # exhausted, go to next level of name
           lookup_variable( obj, name_depth+1, node_depth+1 )
diff --git a/pymtl3/passes/PassGroups.py b/pymtl3/passes/PassGroups.py
index efa906baf..a2bbac10c 100644
--- a/pymtl3/passes/PassGroups.py
+++ b/pymtl3/passes/PassGroups.py
@@ -23,15 +23,15 @@ def __call__( s, top ):
     VcdGenerationPass()( top )
     PrintTextWavePass()( top )
 
-    PrepareSimPass(print_line_trace=True)( top )
+    PrepareSimPass(print_line_trace=False)( top )
 
 class DefaultPassGroup( BasePass ):
   def __init__( s, *, vcdwave=None, textwave=False,
-                      print_line_trace=True, reset_active_high=True ):
+                      linetrace=False, reset_active_high=True ):
 
     s.vcdwave = vcdwave
     s.textwave = textwave
-    s.print_line_trace = print_line_trace
+    s.linetrace = linetrace
     s.reset_active_high = reset_active_high
 
   def __call__( s, top ):
@@ -50,7 +50,7 @@ def __call__( s, top ):
     VcdGenerationPass()( top )
     PrintTextWavePass()( top )
 
-    PrepareSimPass(print_line_trace=s.print_line_trace,
+    PrepareSimPass(print_line_trace=s.linetrace,
                    reset_active_high=s.reset_active_high)( top )
 
 class AutoTickSimPass( BasePass ):
diff --git a/pymtl3/passes/backends/verilog/VerilogPlaceholderPass.py b/pymtl3/passes/backends/verilog/VerilogPlaceholderPass.py
index fb2f823c3..285c42df7 100644
--- a/pymtl3/passes/backends/verilog/VerilogPlaceholderPass.py
+++ b/pymtl3/passes/backends/verilog/VerilogPlaceholderPass.py
@@ -410,7 +410,13 @@ def _get_auto_prefix( s, m ):
     if 'placeholder' in pymtl_config and \
        'auto_prefix' in pymtl_config['placeholder'] and \
        pymtl_config.getboolean( 'placeholder', 'auto_prefix' ):
-      return f"{os.path.basename(parent_dir)}_"
+      if cwd == parent_dir:
+        # Just return the base name
+        return f"{os.path.basename(parent_dir)}_"
+      else:
+        # Return an underscore-connected prefix that includes all
+        # directories along the path
+        return f"{parent_dir[len(cwd)+1:].replace(os.path.sep,'_')}_"
     else:
       return ''
 
diff --git a/pymtl3/passes/backends/verilog/import_/VerilogVerilatorImportPass.py b/pymtl3/passes/backends/verilog/import_/VerilogVerilatorImportPass.py
index e9869e9c2..4c5272e5f 100644
--- a/pymtl3/passes/backends/verilog/import_/VerilogVerilatorImportPass.py
+++ b/pymtl3/passes/backends/verilog/import_/VerilogVerilatorImportPass.py
@@ -658,6 +658,7 @@ def serialize_cfg( s, ip_cfg ):
       'vl_W_lint', 'vl_W_style', 'vl_W_fatal', 'vl_Wno_list',
       'vl_xinit', 'vl_trace',
       'vl_trace_timescale', 'vl_trace_cycle_time',
+      'vl_trace_on_demand', 'vl_trace_on_demand_portname',
       'c_flags', 'c_include_path', 'c_srcs',
       'ld_flags', 'ld_libs',
     ]
diff --git a/pymtl3/passes/backends/verilog/translation/structural/VStructuralTranslatorL4.py b/pymtl3/passes/backends/verilog/translation/structural/VStructuralTranslatorL4.py
index faccfdb12..03949fe3c 100644
--- a/pymtl3/passes/backends/verilog/translation/structural/VStructuralTranslatorL4.py
+++ b/pymtl3/passes/backends/verilog/translation/structural/VStructuralTranslatorL4.py
@@ -63,6 +63,9 @@ def rtlir_tr_subcomp_ifc_port_decl( s, m, c_id, c_rtype, c_array_type,
         pmap = lambda x: x
       vname = f'{ifc_id}__{port_id}'
       pyname = vname.replace('__', '.')
+      # `pmap` is a function that maps the _full name_ of a port object to its
+      # Verilog name. `pyname` is simply a name in the local scope. We need to
+      # build up the full name by concatenating `str(obj)` and `pyname`.
       port_full_name = f"{obj}.{pyname}"
       ph_id = vname if pmap(port_full_name) == port_full_name else pmap(port_full_name)
       port_dtype = s.rtlir_data_type_translation( m, port_rtype.get_dtype() )
diff --git a/pymtl3/passes/backends/verilog/util/utility.py b/pymtl3/passes/backends/verilog/util/utility.py
index 6e5022d89..7ab6b9a09 100644
--- a/pymtl3/passes/backends/verilog/util/utility.py
+++ b/pymtl3/passes/backends/verilog/util/utility.py
@@ -25,7 +25,7 @@ def make_indent( src, nindent ):
 
 def get_component_unique_name( c_rtype ):
   full_name = get_component_full_name( c_rtype )
-  special_chars = [' ', '<', '>', '.']
+  special_chars = [' ', '<', '>', '.', '[', ']']
 
   if len( full_name ) < 64 and not any([c in full_name for c in special_chars]):
     return full_name
diff --git a/pymtl3/passes/mamba/UnrollSimPass.py b/pymtl3/passes/mamba/UnrollSimPass.py
index 61fc9964d..181c406cd 100644
--- a/pymtl3/passes/mamba/UnrollSimPass.py
+++ b/pymtl3/passes/mamba/UnrollSimPass.py
@@ -29,6 +29,7 @@ def gen_tick_function( funclist ):
 def compile_unroll( schedule ):
   {}
   def unrolled():
+    pass
     # The code below does the actual calling of update blocks.
     {}
   return unrolled
diff --git a/pymtl3/stdlib/basic_rtl/arithmetics.py b/pymtl3/stdlib/basic_rtl/arithmetics.py
index 85d29d40d..a55a59ba6 100644
--- a/pymtl3/stdlib/basic_rtl/arithmetics.py
+++ b/pymtl3/stdlib/basic_rtl/arithmetics.py
@@ -36,11 +36,13 @@ def up_mux():
 
 class RightLogicalShifter( Component ):
 
-  def construct( s, Type, shamt_nbits=1 ):
+  def construct( s, Type, shamt_nbits=0 ):
+    if shamt_nbits == 0: shamt_nbits = Type.nbits
+    assert shamt_nbits == Type.nbits
+
     s.in_   = InPort( Type )
     s.shamt = InPort( shamt_nbits )
     s.out   = OutPort( Type )
-    assert shamt_nbits == Type.nbits
 
     @update
     def up_rshifter():
@@ -50,7 +52,10 @@ def up_rshifter():
 
 class LeftLogicalShifter( Component ):
 
-  def construct( s, Type, shamt_nbits = 1 ):
+  def construct( s, Type, shamt_nbits=0 ):
+    if shamt_nbits == 0: shamt_nbits = Type.nbits
+    assert shamt_nbits == Type.nbits
+
     s.in_   = InPort( Type )
     s.shamt = InPort( shamt_nbits )
     s.out   = OutPort( Type )
diff --git a/pymtl3/stdlib/basic_rtl/register_files.py b/pymtl3/stdlib/basic_rtl/register_files.py
index 837786c0d..734949171 100644
--- a/pymtl3/stdlib/basic_rtl/register_files.py
+++ b/pymtl3/stdlib/basic_rtl/register_files.py
@@ -6,7 +6,7 @@ class RegisterFile( Component ):
   def construct( s, Type, nregs=32, rd_ports=1, wr_ports=1,
                 const_zero=False ):
 
-    addr_type = mk_bits( clog2( nregs ) )
+    addr_type = mk_bits( max( 1, clog2( nregs ) ) )
 
     s.raddr = [ InPort( addr_type ) for i in range( rd_ports ) ]
     s.rdata = [ OutPort( Type ) for i in range( rd_ports ) ]
diff --git a/pymtl3/stdlib/ifcs/__init__.py b/pymtl3/stdlib/ifcs/__init__.py
index a345b3f99..f68cc595f 100644
--- a/pymtl3/stdlib/ifcs/__init__.py
+++ b/pymtl3/stdlib/ifcs/__init__.py
@@ -9,7 +9,6 @@
     SendIfcFL,
     SendIfcRTL,
 )
-from .val_rdy_ifcs import InValRdyIfc, OutValRdyIfc
 from .xcel_ifcs import (
     XcelMasterIfcCL,
     XcelMasterIfcFL,
diff --git a/pymtl3/stdlib/ifcs/val_rdy_ifcs.py b/pymtl3/stdlib/ifcs/val_rdy_ifcs.py
deleted file mode 100644
index 48073e9da..000000000
--- a/pymtl3/stdlib/ifcs/val_rdy_ifcs.py
+++ /dev/null
@@ -1,39 +0,0 @@
-"""
-========================================================================
-ValRdyIfc
-========================================================================
-RTL val/rdy interface.
-
-Author : Shunning Jiang
-  Date : Apr 5, 2019
-"""
-from pymtl3 import *
-
-
-def valrdy_to_str( msg, val, rdy, trace_len=15 ):
-  if     val and not rdy: return "#".ljust( trace_len )
-  if not val and     rdy: return " ".ljust( trace_len )
-  if not val and not rdy: return ".".ljust( trace_len )
-  return f"{msg}".ljust( trace_len ) # val and rdy
-
-class InValRdyIfc( Interface ):
-
-  def construct( s, Type ):
-
-    s.msg = InPort( Type )
-    s.val = InPort( 1 )
-    s.rdy = OutPort( 1 )
-
-  def line_trace( s ):
-    return valrdy_to_str( s.msg, s.val, s.rdy )
-
-class OutValRdyIfc( Interface ):
-
-  def construct( s, Type ):
-
-    s.msg = OutPort( Type )
-    s.val = OutPort( 1 )
-    s.rdy = InPort( 1 )
-
-  def line_trace( s ):
-    return valrdy_to_str( s.msg, s.val, s.rdy )
diff --git a/pymtl3/stdlib/queues/valrdy_queues.py b/pymtl3/stdlib/queues/valrdy_queues.py
index beb037983..2eb97f0a5 100644
--- a/pymtl3/stdlib/queues/valrdy_queues.py
+++ b/pymtl3/stdlib/queues/valrdy_queues.py
@@ -44,8 +44,8 @@ def construct( s, Type ):
     s.buffer = RegEn( Type )
     s.buffer.in_ //= s.enq.msg
 
-    s.next_full = Wire( Bits1 )
-    s.full      = Wire( Bits1 )
+    s.next_full = Wire()
+    s.full      = Wire()
 
     s.byp_mux = m = Mux( Type, 2 )
     m.out    //= s.deq.msg
@@ -86,8 +86,8 @@ def construct( s, Type ):
     m.in_ //= s.enq.msg
     m.out //= s.deq.msg
 
-    s.next_full = Wire( Bits1 )
-    s.full      = Wire( Bits1 )
+    s.next_full = Wire()
+    s.full      = Wire()
     connect( s.full, s.deq.val )
 
     @update_ff
diff --git a/pymtl3/stdlib/stream/SinkRTL.py b/pymtl3/stdlib/stream/SinkRTL.py
new file mode 100644
index 000000000..8cbfbc4d9
--- /dev/null
+++ b/pymtl3/stdlib/stream/SinkRTL.py
@@ -0,0 +1,120 @@
+"""
+========================================================================
+SinkRTL
+========================================================================
+Test sinks with RTL interfaces.
+
+Author : Shunning Jiang
+  Date : Feb 12, 2021
+"""
+
+from pymtl3 import *
+from .ifcs import RecvIfcRTL
+
+
+class PyMTLTestSinkError( Exception ): pass
+
+#-------------------------------------------------------------------------
+# TestSinkRTL
+#-------------------------------------------------------------------------
+
+class SinkRTL( Component ):
+
+  def construct( s, Type, msgs, initial_delay=0, interval_delay=0,
+                 arrival_time=None, cmp_fn=lambda a, b : a == b ):
+
+    # Interface
+
+    s.recv = RecvIfcRTL( Type )
+
+    # Data
+
+    # [msgs] and [arrival_time] must have the same length.
+    if arrival_time is None:
+      s.arrival_time = None
+    else:
+      assert len( msgs ) == len( arrival_time )
+      s.arrival_time = list( arrival_time )
+
+    s.idx          = 0
+    s.count        = 0
+    s.cycle_count  = 0
+    s.msgs         = list( msgs )
+
+    s.error_msg    = ''
+
+    s.received = False
+    s.all_msg_recved = False
+    s.done_flag      = False
+
+    @update_ff
+    def up_sink():
+      # Raise exception at the start of next cycle so that the errored
+      # line trace gets printed out
+      if s.error_msg:
+        raise PyMTLTestSinkError( s.error_msg )
+
+      # Tick one more cycle after all message is received so that the
+      # exception gets thrown
+      if s.all_msg_recved:
+        s.done_flag = True
+
+      if s.idx >= len(s.msgs):
+        s.all_msg_recved = True
+
+      s.received = False
+
+      if s.reset:
+        s.cycle_count = 0
+
+        s.idx = 0
+        s.count = initial_delay
+        s.recv.rdy <<= (s.idx < len(s.msgs)) & (s.count == 0)
+
+      else:
+        s.cycle_count += 1
+
+        # This means at least previous cycle count = 0
+        if s.recv.val & s.recv.rdy:
+          msg = s.recv.msg
+
+          # Sanity check
+          if s.idx >= len(s.msgs):
+            s.error_msg = ( 'Test Sink received more msgs than expected!\n'
+                           f'Received : {msg}' )
+
+          else:
+            # Check correctness first
+            if not cmp_fn( msg, s.msgs[ s.idx ] ):
+              s.error_msg = (
+                f'Test sink {s} received WRONG message!\n'
+                f'Expected : { s.msgs[ s.idx ] }\n'
+                f'Received : { msg }'
+              )
+
+            # Check timing if performance regeression is turned on
+            elif s.arrival_time and s.cycle_count > s.arrival_time[ s.idx ]:
+              s.error_msg = (
+                f'Test sink {s} received message LATER than expected!\n'
+                f'Expected msg : {s.msgs[ s.idx ]}\n'
+                f'Expected at  : {s.arrival_time[ s.idx ]}\n'
+                f'Received msg : {msg}\n'
+                f'Received at  : {s.cycle_count}'
+              )
+
+          s.idx += 1
+          s.count = interval_delay
+
+        if s.count > 0:
+          s.count -= 1
+          s.recv.rdy <<= 0
+        else: # s.count == 0
+          s.recv.rdy <<= (s.idx < len(s.msgs))
+
+  def done( s ):
+    return s.done_flag
+
+  # Line trace
+
+  def line_trace( s ):
+    return f"{s.recv}"
diff --git a/pymtl3/stdlib/stream/SourceRTL.py b/pymtl3/stdlib/stream/SourceRTL.py
new file mode 100644
index 000000000..920d84dc4
--- /dev/null
+++ b/pymtl3/stdlib/stream/SourceRTL.py
@@ -0,0 +1,63 @@
+"""
+========================================================================
+SourceRTL
+========================================================================
+Test sources with RTL interfaces.
+
+Author : Shunning Jiang
+  Date : Feb 12, 2021
+"""
+from collections import deque
+from copy import deepcopy
+
+from pymtl3 import *
+from .ifcs import SendIfcRTL
+
+
+class SourceRTL( Component ):
+
+  def construct( s, Type, msgs, initial_delay=0, interval_delay=0 ):
+
+    # Interface
+
+    s.send = SendIfcRTL( Type )
+
+    # Data
+
+    s.msgs = deepcopy(msgs)
+
+    # TODO: use wires and ROM to make it translatable
+    s.idx = 0
+    s.count = 0
+
+    @update_ff
+    def up_src():
+      if s.reset:
+        s.idx   = 0
+        s.count = initial_delay
+        s.send.val <<= 0
+
+      else:
+        if s.send.val & s.send.rdy:
+          s.idx += 1
+          s.count = interval_delay
+
+        if s.count > 0:
+          s.count -= 1
+          s.send.val <<= 0
+
+        else: # s.count == 0
+          if s.idx < len(s.msgs):
+            s.send.val <<= 1
+            s.send.msg <<= s.msgs[s.idx]
+          else:
+            s.send.val <<= 0
+
+
+  def done( s ):
+    return s.idx >= len(s.msgs)
+
+  # Line trace
+
+  def line_trace( s ):
+    return f"{s.send}"
diff --git a/pymtl3/stdlib/stream/__init__.py b/pymtl3/stdlib/stream/__init__.py
new file mode 100644
index 000000000..64fb7b68d
--- /dev/null
+++ b/pymtl3/stdlib/stream/__init__.py
@@ -0,0 +1,7 @@
+from .SinkRTL import SinkRTL
+from .SourceRTL import SourceRTL
+from . import ifcs
+from .queue_adapters import RecvQueueAdapter, SendQueueAdapter
+from .queues import NormalQueueRTL, PipeQueueRTL, BypassQueueRTL
+from .magic_memory import MagicMemoryRTL
+from . import fl
diff --git a/pymtl3/stdlib/stream/fl.py b/pymtl3/stdlib/stream/fl.py
new file mode 100644
index 000000000..6895c2e2d
--- /dev/null
+++ b/pymtl3/stdlib/stream/fl.py
@@ -0,0 +1,248 @@
+import greenlet
+from pymtl3 import *
+from pymtl3.extra import clone_deepcopy
+from pymtl3.stdlib.mem.MemMsg import MemMsgType
+from .ifcs import RecvIfcRTL, SendIfcRTL, MasterIfcRTL
+
+class RecvQueueAdapter( Component ):
+
+  @blocking
+  def deq( s ):
+    while s.entry is None:
+      greenlet.getcurrent().parent.switch(0)
+    ret = s.entry
+    s.entry = None
+    return ret
+
+  def construct( s, Type ):
+    s.recv = RecvIfcRTL( Type )
+    s.entry = None
+
+    @update_once
+    def up_recv_rdy():
+      s.recv.rdy @= (s.entry is None)
+
+    @update_once
+    def up_recv_msg():
+      if (s.entry is None) & s.recv.val:
+        s.entry = clone_deepcopy( s.recv.msg )
+
+    s.add_constraints( M( s.deq )       < U( up_recv_rdy ), # deq before recv in a cycle -- pipe behavior
+                       U( up_recv_rdy ) < U( up_recv_msg ) )
+
+  def line_trace( s ):
+    return "{}(){}".format( s.recv, s.send )
+
+class SendQueueAdapter( Component ):
+
+  @blocking
+  def enq( s, msg ):
+    while s.entry is not None:
+      greenlet.getcurrent().parent.switch(0)
+
+    s.entry = clone_deepcopy(msg)
+
+  def construct( s, Type ):
+    s.send = SendIfcRTL( Type )
+    s.entry = None
+
+    s.sent = Wire()
+
+    @update
+    def up_send():
+      if s.entry is None:
+        s.send.val @= 0
+      else:
+        s.send.val @= 1
+        s.send.msg @= s.entry
+
+    @update_ff
+    def up_sent():
+      s.sent <<= s.send.val & s.send.rdy
+
+    @update_once
+    def up_clear():
+      if s.sent: # constraints reverse this
+        s.entry = None
+
+    s.add_constraints( U( up_clear ) < M( s.enq ),
+                       M( s.enq )    < U( up_send ) )
+
+  def line_trace( s ):
+    return "{}(){}".format( s.recv, s.send )
+
+class MemMasterAdapter( Component ):
+
+  @blocking
+  def read( s, addr, nbytes ):
+    while s.req_entry is not None:
+      greenlet.getcurrent().parent.switch(0)
+
+    s.req_entry = s.create_req( MemMsgType.READ, 0, addr, nbytes )
+
+    while s.resp_entry is None:
+      greenlet.getcurrent().parent.switch(0)
+
+    ret = s.resp_entry.data[0:nbytes<<3]
+    s.resp_entry = None
+    return ret
+
+  @blocking
+  def write( s, addr, nbytes, data ):
+    while s.req_entry is not None:
+      greenlet.getcurrent().parent.switch(0)
+
+    s.req_entry = s.create_req( MemMsgType.WRITE, 0, addr, nbytes, data )
+
+    while s.resp_entry is None:
+      greenlet.getcurrent().parent.switch(0)
+
+    s.resp_entry = None
+
+  @blocking
+  def amo( s, amo_type, addr, nbytes, data ):
+    while s.req_entry is not None:
+      greenlet.getcurrent().parent.switch(0)
+
+    s.req_entry = s.create_req( amo_type, 0, addr, nbytes, data )
+
+    while s.resp_entry is None:
+      greenlet.getcurrent().parent.switch(0)
+
+    ret = s.resp_entry.data[0:nbytes<<3]
+    s.resp_entry = None
+    return ret
+
+  def construct( s, ReqType, RespType ):
+    s.master = MasterIfcRTL( ReqType, RespType )
+
+    # Use create_req to handle type mismatch
+    Tlen  = ReqType.get_field_type('len')
+    Tdata = ReqType.get_field_type('data')
+    s.create_req = lambda a,b,c,d,e=0: ReqType( a, b, c, Tlen(d, trunc_int=True), Tdata(int(e)) )
+
+    s.req_entry  = None
+    s.resp_entry = None
+
+    # req path
+
+    s.req_sent = Wire()
+
+    @update_ff
+    def up_req_sent():
+      s.req_sent <<= s.master.req.val & s.master.req.rdy
+
+    @update
+    def up_clear_req():
+      if s.req_sent:
+        s.req_entry = None
+
+    @update_once
+    def up_send_req():
+      if s.req_entry is None:
+        s.master.req.val @= 0
+      else:
+        s.master.req.val @= 1
+        s.master.req.msg @= s.req_entry
+
+    # resp path
+    @update_once
+    def up_resp_rdy():
+      s.master.resp.rdy @= (s.resp_entry is None)
+
+    @update_once
+    def up_resp_msg():
+      if (s.resp_entry is None) & s.master.resp.val:
+        s.resp_entry = clone_deepcopy( s.master.resp.msg )
+
+    s.add_constraints( U( up_clear_req ) < M(s.read),
+                       U( up_clear_req ) < M(s.write),
+                       U( up_clear_req ) < M(s.amo),
+
+                       M( s.read )    < U( up_send_req ),
+                       M( s.write )   < U( up_send_req ),
+                       M( s.amo )     < U( up_send_req ),
+
+                       M( s.read )      < U( up_resp_rdy ),
+                       M( s.write )     < U( up_resp_rdy ),
+                       M( s.amo )       < U( up_resp_rdy ),
+                       U( up_resp_rdy ) < U( up_resp_msg ) )
+
+class XcelMasterAdapter( Component ):
+
+  @blocking
+  def read( s, addr ):
+    while s.req_entry is not None:
+      greenlet.getcurrent().parent.switch(0)
+
+    s.req_entry = s.create_req( 0, addr )
+
+    while s.resp_entry is None:
+      greenlet.getcurrent().parent.switch(0)
+
+    ret = s.resp_entry.data
+    s.resp_entry = None
+    return ret
+
+  @blocking
+  def write( s, addr, data ):
+    while s.req_entry is not None:
+      greenlet.getcurrent().parent.switch(0)
+
+    s.req_entry = s.create_req( 1, addr, data )
+
+    while s.resp_entry is None:
+      greenlet.getcurrent().parent.switch(0)
+
+    s.resp_entry = None
+
+  def construct( s, ReqType, RespType ):
+    s.master = MasterIfcRTL( ReqType, RespType )
+
+    # Use create_req to handle type mismatch
+    Tdata = ReqType.get_field_type('data')
+    s.create_req = lambda a,b,c=0: ReqType( a, b, Tdata(int(c)) )
+
+    s.req_entry  = None
+    s.resp_entry = None
+
+    # req path
+
+    s.req_sent = Wire()
+
+    @update_ff
+    def up_req_sent():
+      s.req_sent <<= s.master.req.val & s.master.req.rdy
+
+    @update
+    def up_clear_req():
+      if s.req_sent:
+        s.req_entry = None
+
+    @update_once
+    def up_send_req():
+      if s.req_entry is None:
+        s.master.req.val @= 0
+      else:
+        s.master.req.val @= 1
+        s.master.req.msg @= s.req_entry
+
+    # resp path
+    @update_once
+    def up_resp_rdy():
+      s.master.resp.rdy @= (s.resp_entry is None)
+
+    @update_once
+    def up_resp_msg():
+      if (s.resp_entry is None) & s.master.resp.val:
+        s.resp_entry = clone_deepcopy( s.master.resp.msg )
+
+    s.add_constraints( U( up_clear_req ) < M(s.read),
+                       U( up_clear_req ) < M(s.write),
+
+                       M( s.read )    < U( up_send_req ),
+                       M( s.write )   < U( up_send_req ),
+
+                       M( s.read )      < U( up_resp_rdy ),
+                       M( s.write )     < U( up_resp_rdy ),
+                       U( up_resp_rdy ) < U( up_resp_msg ) )
diff --git a/pymtl3/stdlib/stream/ifcs.py b/pymtl3/stdlib/stream/ifcs.py
new file mode 100644
index 000000000..0912b517b
--- /dev/null
+++ b/pymtl3/stdlib/stream/ifcs.py
@@ -0,0 +1,61 @@
+"""
+========================================================================
+ValRdyIfc
+========================================================================
+RTL val/rdy interface.
+
+Author : Shunning Jiang
+  Date : Apr 5, 2019
+"""
+from pymtl3 import *
+
+
+def valrdy_to_str( msg, val, rdy, trace_len=15 ):
+  if     val and not rdy: return "#".ljust( trace_len )
+  if not val and     rdy: return " ".ljust( trace_len )
+  if not val and not rdy: return ".".ljust( trace_len )
+  return f"{msg}".ljust( trace_len ) # val and rdy
+
+class RecvIfcRTL( Interface ):
+
+  def construct( s, Type ):
+
+    s.msg = InPort( Type )
+    s.val = InPort()
+    s.rdy = OutPort()
+
+    s.trace_len = len(str(Type()))
+
+  def __str__( s ):
+    return valrdy_to_str( s.msg, s.val, s.rdy, s.trace_len )
+
+class SendIfcRTL( Interface ):
+
+  def construct( s, Type ):
+
+    s.msg = OutPort( Type )
+    s.val = OutPort()
+    s.rdy = InPort()
+
+    s.trace_len = len(str(Type()))
+
+  def __str__( s ):
+    return valrdy_to_str( s.msg, s.val, s.rdy, s.trace_len )
+
+class MasterIfcRTL( Interface ):
+  def construct( s, ReqType, RespType ):
+    s.ReqType  = ReqType
+    s.RespType = RespType
+    s.req  = SendIfcRTL( Type=ReqType )
+    s.resp = RecvIfcRTL( Type=RespType )
+  def __str__( s ):
+    return f"{s.req}|{s.resp}"
+
+class MinionIfcRTL( Interface ):
+  def construct( s, ReqType, RespType ):
+    s.ReqType  = ReqType
+    s.RespType = RespType
+    s.req  = RecvIfcRTL( Type=ReqType )
+    s.resp = SendIfcRTL( Type=RespType )
+  def __str__( s ):
+    return f"{s.req}|{s.resp}"
diff --git a/pymtl3/stdlib/stream/magic_memory.py b/pymtl3/stdlib/stream/magic_memory.py
new file mode 100644
index 000000000..9fc9ef7f8
--- /dev/null
+++ b/pymtl3/stdlib/stream/magic_memory.py
@@ -0,0 +1,208 @@
+"""
+========================================================================
+MagicMemoryCL
+========================================================================
+A behavioral magic memory which is parameterized based on the number of
+memory request/response ports. This version is a little different from
+the one in pclib because we actually use the memory messages correctly
+in the interface.
+
+Author : Shunning Jiang
+Date   : Feb 6, 2020
+"""
+from random import Random
+from collections import deque
+
+from pymtl3 import *
+from pymtl3.extra import clone_deepcopy
+from pymtl3.stdlib.delays import DelayPipeDeqCL, DelayPipeSendCL, StallCL
+
+from pymtl3.stdlib.mem.MagicMemoryFL import MagicMemoryFL
+from pymtl3.stdlib.mem.MemMsg import MemMsgType, mk_mem_msg
+from .ifcs import MinionIfcRTL, RecvIfcRTL, SendIfcRTL
+from .queues import NormalQueueRTL, PipeQueueRTL
+
+# BRGTC2 custom MemMsg modified for RISC-V 32
+
+#- - NOTE  - - - NOTE  - - - NOTE  - - - NOTE  - - - NOTE  - - - NOTE  - -
+#-------------------------------------------------------------------------
+# BRGTC2
+#-------------------------------------------------------------------------
+# The AMO implementations (and MemMsg) has been updated to match RISC-V.
+#
+# There is also a small fix to the AMO ops to handle signed ops. The AMO
+# operations act on the bitwidth of the processor architecture, so the
+# read_data from the TestMemory used with AMOs cannot just be the memory
+# request message size (e.g., 128b):
+#
+#         read_data = Bits( s.data_nbits )
+#
+# It must instead be the number of bytes matching the bitwidth in the
+# processor (e.g., 32b):
+#
+#         read_data = Bits( nbytes*8 )
+#
+# Otherwise for example we would be reading 128b from the memory and
+# comparing that to the 32b value from the request message.
+#
+#-------------------------------------------------------------------------
+#- - NOTE  - - - NOTE  - - - NOTE  - - - NOTE  - - - NOTE  - - - NOTE  - -
+
+class RandomStall( Component ):
+  def construct( s, Type, stall_prob=0, stall_seed=0xdeadbeef ):
+
+    s.recv = RecvIfcRTL( Type )
+    s.send = SendIfcRTL( Type )
+
+    s.recv.msg //= s.send.msg
+
+    stall_rgen = Random( stall_seed )
+
+    s.rand_value = 0
+    s.stall_prob = stall_prob
+
+    @update_ff
+    def up_rand():
+      s.rand_value = stall_rgen.random()
+
+    @update
+    def up_stall_rdy():
+      s.recv.rdy @= s.send.rdy & (s.rand_value > stall_prob)
+    @update
+    def up_stall_val():
+      s.send.val @= s.recv.val & (s.rand_value > stall_prob)
+
+  def line_trace( s ):
+    return "[ ]" if s.rand_value > s.stall_prob else "[#]"
+
+# Cuts both val and rdy paths
+class InelasticDelayPipe( Component ):
+
+  def construct( s, Type, delay=1 ):
+
+    s.recv = RecvIfcRTL( Type )
+    s.send = SendIfcRTL( Type )
+
+    assert delay >= 1
+    s.delay = delay
+
+    # FIFO behavior
+    s.delay_pipe = deque( [None]*(delay+1) )
+
+    @update_ff
+    def up_delay():
+
+      if s.recv.rdy & s.recv.val:
+        s.delay_pipe[0] = clone_deepcopy( s.recv.msg )
+
+      # We remove the sent message from the pipe first and then
+      # clone the recv message. This allows us to use one entry for one
+      # delay
+
+      if s.send.val:
+        if s.send.rdy:
+          s.delay_pipe[-1] = None
+          s.delay_pipe.rotate()
+      else: # this cycle invalid send means pipe[-1] is None
+        s.delay_pipe.rotate()
+
+      if s.delay_pipe[-1] is None:
+        s.send.val <<= 0
+      else:
+        s.send.val <<= 1
+        s.send.msg <<= s.delay_pipe[-1]
+
+      s.recv.rdy <<= s.delay_pipe[0] is None
+
+  def line_trace( s ):
+    return f"[{''.join([ ' ' if x is None else '*' for x in s.delay_pipe])}]"
+
+class MagicMemoryRTL( Component ):
+
+  # Magical methods
+
+  def read_mem( s, addr, size ):
+    return s.mem.read_mem( addr, size )
+
+  def write_mem( s, addr, data ):
+    return s.mem.write_mem( addr, data )
+
+  # Actual stuff
+  def construct( s, nports=1, mem_ifc_dtypes=[mk_mem_msg(8,32,32)],
+                    stall_prob=0, extra_latency=0, mem_nbytes=2**20 ):
+
+    # Local constants
+
+    assert len(mem_ifc_dtypes) == nports
+    req_classes  = [ x for (x,y) in mem_ifc_dtypes ]
+    resp_classes = [ y for (x,y) in mem_ifc_dtypes ]
+
+    s.mem = MagicMemoryFL( mem_nbytes )
+
+    # Interface
+
+    s.ifc = [ MinionIfcRTL( req_classes[i], resp_classes[i] ) for i in range(nports) ]
+
+
+    # stall and delays
+    s.req_stalls = [ RandomStall( req_classes[i], stall_prob, i ) for i in range(nports) ]
+    # s.req_qs     = [ NormalQueueRTL( req_classes[i], 2 ) for i in range(nports) ]
+    s.resp_qs    = [ InelasticDelayPipe( resp_classes[i], extra_latency+1 ) for i in range(nports) ]
+
+    for i in range(nports):
+      s.req_stalls[i].recv //= s.ifc[i].req
+      # s.req_stalls[i].send //= s.req_qs[i].recv
+      s.resp_qs[i].send    //= s.ifc[i].resp
+
+      s.req_stalls[i].send.rdy //= s.resp_qs[i].recv.rdy
+      s.req_stalls[i].send.val //= s.resp_qs[i].recv.val
+
+    @update_once
+    def up_mem():
+
+      for i in range(nports):
+
+        if s.req_stalls[i].send.val:
+
+          # Dequeue memory request message
+
+          req = s.req_stalls[i].send.msg
+          len_ = int(req.len)
+          if len_ == 0: len_ = req_classes[i].data_nbits >> 3
+
+          if   req.type_ == MemMsgType.READ:
+            resp = resp_classes[i]( req.type_, req.opaque, 0, req.len,
+                                    zext( s.mem.read( req.addr, len_ ), req_classes[i].data_nbits ) )
+
+          elif  req.type_ == MemMsgType.WRITE:
+            s.mem.write( req.addr, len_, req.data[0:len_<<3] )
+            # FIXME do we really set len=0 in response when doing subword wr?
+            # resp = resp_classes[i]( req.type_, req.opaque, 0, req.len, 0 )
+            resp = resp_classes[i]( req.type_, req.opaque, 0, 0, 0 )
+
+          elif  req.type_ == MemMsgType.AMO_ADD   or \
+                req.type_ == MemMsgType.AMO_AND   or \
+                req.type_ == MemMsgType.AMO_MAX   or \
+                req.type_ == MemMsgType.AMO_MAXU  or \
+                req.type_ == MemMsgType.AMO_MIN   or \
+                req.type_ == MemMsgType.AMO_MINU  or \
+                req.type_ == MemMsgType.AMO_OR    or \
+                req.type_ == MemMsgType.AMO_SWAP  or \
+                req.type_ == MemMsgType.AMO_XOR:
+            resp = resp_classes[i]( req.type_, req.opaque, 0, req.len,
+               s.mem.amo( req.type_, req.addr, len_, req.data ) )
+
+          # Invalid type
+          else:
+            assert False
+
+          s.resp_qs[i].recv.msg @= resp
+
+  #-----------------------------------------------------------------------
+  # line_trace
+  #-----------------------------------------------------------------------
+
+  def line_trace( s ):
+    # print()
+    return "|".join( f"{s.req_stalls[i].line_trace()}{s.ifc[i].req}>{s.ifc[i].resp}{s.resp_qs[i].line_trace()}"
+                        for i in range(len(s.ifc)) )
diff --git a/pymtl3/stdlib/stream/queue_adapters.py b/pymtl3/stdlib/stream/queue_adapters.py
new file mode 100644
index 000000000..991857357
--- /dev/null
+++ b/pymtl3/stdlib/stream/queue_adapters.py
@@ -0,0 +1,65 @@
+from pymtl3 import *
+from pymtl3.extra import clone_deepcopy
+from .ifcs import RecvIfcRTL, SendIfcRTL
+
+class RecvQueueAdapter( Component ):
+
+  @non_blocking( lambda s: s.entry is not None )
+  def deq( s ):
+    ret = s.entry
+    s.entry = None
+    return ret
+
+  def construct( s, Type ):
+    s.recv = RecvIfcRTL( Type )
+    s.entry = None
+
+    @update_once
+    def up_recv_rdy():
+      s.recv.rdy @= (s.entry is None)
+
+    @update_once
+    def up_recv_msg():
+      if (s.entry is None) & s.recv.val:
+        s.entry = clone_deepcopy( s.recv.msg )
+
+    s.add_constraints( M( s.deq )     < U( up_recv_rdy ), # deq before recv in a cycle -- pipe behavior
+                       M( s.deq.rdy ) < U( up_recv_rdy ),
+                       U( up_recv_rdy ) < U( up_recv_msg ) )
+
+
+class SendQueueAdapter( Component ):
+
+  @non_blocking( lambda s: s.entry is None )
+  def enq( s, msg ):
+    s.entry = clone_deepcopy( msg )
+
+  def construct( s, Type ):
+    s.send = SendIfcRTL( Type )
+
+    s.entry = None
+    s.sent  = Wire()
+
+    @update_once
+    def up_send():
+      if s.entry is None:
+        s.send.val @= 0
+      else:
+        s.send.val @= 1
+        s.send.msg @= s.entry
+
+    @update_ff
+    def up_sent():
+      s.sent <<= s.send.val & s.send.rdy
+
+    @update_once
+    def up_clear():
+      if s.sent: # constraints reverse this
+        s.entry = None
+
+    s.add_constraints(
+      U( up_clear )  < M( s.enq ),
+      U( up_clear )  < M( s.enq.rdy ),
+      M( s.enq )     < U( up_send ),
+      M( s.enq.rdy ) < U( up_send )
+    )
diff --git a/pymtl3/stdlib/stream/queues.py b/pymtl3/stdlib/stream/queues.py
new file mode 100644
index 000000000..77004f9f0
--- /dev/null
+++ b/pymtl3/stdlib/stream/queues.py
@@ -0,0 +1,557 @@
+"""
+-------------------------------------------------------------------------
+Library of RTL queues
+-------------------------------------------------------------------------
+
+Author : Yanghui Ou
+  Date : Feb 22, 2021
+"""
+
+
+from pymtl3 import *
+from pymtl3.stdlib.basic_rtl import Mux, RegisterFile
+
+from .ifcs import RecvIfcRTL, SendIfcRTL
+
+#-------------------------------------------------------------------------
+# NormalQueue1EntryRTL
+#-------------------------------------------------------------------------
+
+class NormalQueue1EntryRTL( Component ):
+
+  def construct( s, EntryType ):
+
+    # Interface
+
+    s.recv  = RecvIfcRTL( EntryType )
+    s.send  = SendIfcRTL( EntryType )
+    s.count = OutPort()
+
+    # Components
+
+    s.full  = Wire()
+    s.entry = Wire( EntryType )
+
+    # Logic
+
+    s.count //= s.full
+
+    s.send.msg //= s.entry
+    s.send.val //= s.full
+    s.recv.rdy //= lambda: ~s.full
+
+    @update_ff
+    def ff_normal1():
+      if s.reset:
+        s.full <<= 0
+      else:
+        s.full <<= (s.recv.val & ~s.full) | (s.full & ~s.send.rdy)
+
+      if s.recv.val & ~s.full:
+        s.entry <<= s.recv.msg
+
+  def line_trace( s ):
+    return f"{s.recv}({s.full}){s.send}"
+
+#-------------------------------------------------------------------------
+# Dpath and Ctrl for NormalQueueRTL
+#-------------------------------------------------------------------------
+
+class NormalQueueDpathRTL( Component ):
+
+  def construct( s, EntryType, num_entries=2 ):
+    assert num_entries >= 2
+
+    # Interface
+
+    s.recv_msg = InPort( EntryType )
+    s.send_msg = OutPort( EntryType )
+
+    s.wen   = InPort()
+    s.waddr = InPort( clog2( num_entries ) )
+    s.raddr = InPort( clog2( num_entries ) )
+
+    # Component
+
+    s.rf = m = RegisterFile( EntryType, num_entries )
+    m.raddr[0] //= s.raddr
+    m.rdata[0] //= s.send_msg
+    m.wen[0]   //= s.wen
+    m.waddr[0] //= s.waddr
+    m.wdata[0] //= s.recv_msg
+
+class NormalQueueCtrlRTL( Component ):
+
+  def construct( s, num_entries=2 ):
+    assert num_entries >= 2
+
+    # Constants
+
+    addr_nbits  = clog2( num_entries   )
+    count_nbits = clog2( num_entries+1 )
+
+    # Interface
+
+    s.recv_val = InPort()
+    s.recv_rdy = OutPort()
+    s.send_val = OutPort()
+    s.send_rdy = InPort()
+
+    s.count = OutPort( count_nbits )
+    s.wen   = OutPort()
+    s.waddr = OutPort( addr_nbits )
+    s.raddr = OutPort( addr_nbits )
+
+    # Registers
+
+    s.head = Wire( addr_nbits )
+    s.tail = Wire( addr_nbits )
+
+    # Wires
+
+    s.recv_xfer  = Wire()
+    s.send_xfer  = Wire()
+
+    # Connections
+
+    s.wen   //= s.recv_xfer
+    s.waddr //= s.tail
+    s.raddr //= s.head
+
+    s.recv_rdy  //= lambda: s.count < num_entries
+    s.send_val  //= lambda: s.count > 0
+
+    s.recv_xfer //= lambda: s.recv_val & s.recv_rdy
+    s.send_xfer //= lambda: s.send_val & s.send_rdy
+
+    @update_ff
+    def up_reg():
+
+      if s.reset:
+        s.head  <<= 0
+        s.tail  <<= 0
+        s.count <<= 0
+
+      else:
+        if s.recv_xfer:
+          s.tail <<= s.tail + 1 if ( s.tail < num_entries - 1 ) else 0
+
+        if s.send_xfer:
+          s.head <<= s.head + 1 if ( s.head < num_entries -1 ) else 0
+
+        if s.recv_xfer & ~s.send_xfer:
+          s.count <<= s.count + 1
+        elif ~s.recv_xfer & s.send_xfer:
+          s.count <<= s.count - 1
+
+#-------------------------------------------------------------------------
+# NormalQueueRTL
+#-------------------------------------------------------------------------
+
+class NormalQueueRTL( Component ):
+
+  def construct( s, EntryType, num_entries=2 ):
+
+    # Interface
+
+    s.recv  = RecvIfcRTL( EntryType )
+    s.send  = SendIfcRTL( EntryType )
+    s.count = OutPort( clog2( num_entries+1 ) )
+
+    # Components
+
+    assert num_entries > 0
+
+    if num_entries == 1:
+      s.q = NormalQueue1EntryRTL( EntryType )
+      s.recv  //= s.q.recv
+      s.send  //= s.q.send
+      s.count //= s.q.count
+
+    else:
+      s.ctrl  = NormalQueueCtrlRTL ( num_entries )
+      s.dpath = NormalQueueDpathRTL( EntryType, num_entries )
+
+      # Connect ctrl to data path
+
+      s.ctrl.wen   //= s.dpath.wen
+      s.ctrl.waddr //= s.dpath.waddr
+      s.ctrl.raddr //= s.dpath.raddr
+
+      # Connect to interface
+
+      s.recv.val //= s.ctrl.recv_val
+      s.recv.rdy //= s.ctrl.recv_rdy
+      s.recv.msg //= s.dpath.recv_msg
+
+      s.send.val //= s.ctrl.send_val
+      s.send.rdy //= s.ctrl.send_rdy
+      s.send.msg //= s.dpath.send_msg
+      s.count   //= s.ctrl.count
+
+  # Line trace
+
+  def line_trace( s ):
+    return f"{s.recv}({s.count}){s.send}"
+
+#-------------------------------------------------------------------------
+# PipeQueue1EntryRTL
+#-------------------------------------------------------------------------
+
+class PipeQueue1EntryRTL( Component ):
+
+  def construct( s, EntryType ):
+
+    # Interface
+
+    s.recv  = RecvIfcRTL( EntryType )
+    s.send  = SendIfcRTL( EntryType )
+    s.count = OutPort()
+
+    # Components
+
+    s.full  = Wire()
+    s.entry = Wire( EntryType )
+
+    # Logic
+
+    s.count //= s.full
+
+    s.send.msg //= s.entry
+    s.send.val //= s.full
+
+    # If send is rdy, either the entry will be sent out to free up space
+    # for recv, or entry is already available for send. Then if not full
+    # entry can always buffer up a message. rdy path is elongated
+    s.recv.rdy //= lambda: s.send.rdy | ~s.full
+
+    @update_ff
+    def ff_pipe1():
+      if s.reset:
+        s.full <<= 0
+      else:
+        # The pipe queue is full if in this cycle it cannot receive a
+        # message due to back pressure and the entry is already full.
+        # Otherwise it is not full and it becomes full only if there is
+        # a valid incoming message.
+        s.full <<= ~s.recv.rdy | s.recv.val
+
+      # AND rdy and val to buffer the incoming message
+      if s.recv.rdy & s.recv.val:
+        s.entry <<= s.recv.msg
+
+  def line_trace( s ):
+    return f"{s.recv}({s.full}){s.send}"
+
+#-------------------------------------------------------------------------
+# Ctrl for PipeQueue
+#-------------------------------------------------------------------------
+
+class PipeQueueCtrlRTL( Component ):
+
+  def construct( s, num_entries=2 ):
+    assert num_entries >= 2
+
+    # Constants
+
+    addr_nbits  = clog2( num_entries   )
+    count_nbits = clog2( num_entries+1 )
+
+    # Interface
+
+    s.recv_val = InPort ()
+    s.recv_rdy = OutPort()
+    s.send_val = OutPort()
+    s.send_rdy = InPort ()
+    s.count    = OutPort( count_nbits )
+
+    s.wen      = OutPort()
+    s.waddr    = OutPort( addr_nbits )
+    s.raddr    = OutPort( addr_nbits )
+
+    # Registers
+
+    s.head = Wire( addr_nbits )
+    s.tail = Wire( addr_nbits )
+
+    # Wires
+
+    s.recv_xfer  = Wire()
+    s.send_xfer  = Wire()
+
+    # Connections
+
+    s.wen   //= s.recv_xfer
+    s.waddr //= s.tail
+    s.raddr //= s.head
+
+    s.send_val //= lambda: s.count > 0
+    s.recv_rdy //= lambda: ( s.count < num_entries ) | s.send_rdy
+
+    s.recv_xfer //= lambda: s.recv_val & s.recv_rdy
+    s.send_xfer //= lambda: s.send_val & s.send_rdy
+
+    @update_ff
+    def up_reg():
+
+      if s.reset:
+        s.head  <<= 0
+        s.tail  <<= 0
+        s.count <<= 0
+
+      else:
+        if s.recv_xfer:
+          s.tail <<= s.tail + 1 if ( s.tail < num_entries - 1 ) else 0
+
+        if s.send_xfer:
+          s.head <<= s.head + 1 if ( s.head < num_entries -1 ) else 0
+
+        if s.recv_xfer & ~s.send_xfer:
+          s.count <<= s.count + 1
+        elif ~s.recv_xfer & s.send_xfer:
+          s.count <<= s.count - 1
+
+#-------------------------------------------------------------------------
+# PipeQueueRTL
+#-------------------------------------------------------------------------
+
+class PipeQueueRTL( Component ):
+
+  def construct( s, EntryType, num_entries=2 ):
+
+    # Interface
+
+    s.recv  = RecvIfcRTL( EntryType )
+    s.send  = SendIfcRTL( EntryType )
+    s.count = OutPort( clog2( num_entries+1 ) )
+
+    # Components
+
+    assert num_entries > 0
+    if num_entries == 1:
+      s.q = PipeQueue1EntryRTL( EntryType )
+      s.recv  //= s.q.recv
+      s.send  //= s.q.send
+      s.count //= s.q.count
+
+    else:
+      s.ctrl  = PipeQueueCtrlRTL ( num_entries )
+      s.dpath = NormalQueueDpathRTL( EntryType, num_entries )
+
+      # Connect ctrl to data path
+
+      s.ctrl.wen   //= s.dpath.wen
+      s.ctrl.waddr //= s.dpath.waddr
+      s.ctrl.raddr //= s.dpath.raddr
+
+      # Connect to interface
+
+      s.recv.val //= s.ctrl.recv_val
+      s.recv.rdy //= s.ctrl.recv_rdy
+      s.send.val //= s.ctrl.send_val
+      s.send.rdy //= s.ctrl.send_rdy
+      s.count    //= s.ctrl.count
+      s.recv.msg //= s.dpath.recv_msg
+      s.send.msg //= s.dpath.send_msg
+
+  # Line trace
+
+  def line_trace( s ):
+    return f"{s.recv}({s.count}){s.send}"
+
+#-------------------------------------------------------------------------
+# BypassQueue1EntryRTL
+#-------------------------------------------------------------------------
+
+class BypassQueue1EntryRTL( Component ):
+
+  def construct( s, EntryType ):
+
+    # Interface
+
+    s.recv  = RecvIfcRTL( EntryType )
+    s.send  = SendIfcRTL( EntryType )
+    s.count = OutPort()
+
+    # Components
+
+    s.full  = Wire()
+    s.entry = Wire( EntryType )
+
+    s.bypass_mux = m = Mux( EntryType, 2 )
+    m.in_[0] //= s.recv.msg
+    m.in_[1] //= s.entry
+    m.out    //= s.send.msg
+    m.sel    //= s.full
+
+    # Logic
+
+    s.count //= s.full
+
+    s.send.val //= lambda: s.full | s.recv.val
+    s.recv.rdy //= lambda: ~s.full
+
+    @update_ff
+    def ff_bypass1():
+      if s.reset:
+        s.full <<= 0
+      else:
+        s.full <<= ~s.send.rdy & (s.full | s.recv.val)
+
+      # buffer the incoming message if we cannot directly send it out
+      if ~s.send.rdy & ~s.full & s.recv.val:
+        s.entry <<= s.recv.msg
+
+  def line_trace( s ):
+    return f"{s.recv}({s.full}){s.send}"
+#-------------------------------------------------------------------------
+# Ctrl and Dpath for BypassQueue
+#-------------------------------------------------------------------------
+
+class BypassQueueDpathRTL( Component ):
+
+  def construct( s, EntryType, num_entries=2 ):
+    assert num_entries >= 2
+
+    # Interface
+
+    s.recv_msg = InPort( EntryType )
+    s.send_msg = OutPort( EntryType )
+
+    s.wen     = InPort()
+    s.waddr   = InPort( clog2( num_entries ) )
+    s.raddr   = InPort( clog2( num_entries ) )
+    s.mux_sel = InPort()
+
+    # Component
+
+    s.rf = m = RegisterFile( EntryType, num_entries )
+    m.raddr[0] //= s.raddr
+    m.wen[0]   //= s.wen
+    m.waddr[0] //= s.waddr
+    m.wdata[0] //= s.recv_msg
+
+    s.mux = m = Mux( EntryType, 2 )
+    m.sel    //= s.mux_sel
+    m.in_[0] //= s.rf.rdata[0]
+    m.in_[1] //= s.recv_msg
+    m.out    //= s.send_msg
+
+class BypassQueueCtrlRTL( Component ):
+
+  def construct( s, num_entries=2 ):
+    assert num_entries >= 2
+
+    # Constants
+
+    addr_nbits  = clog2( num_entries   )
+    count_nbits = clog2( num_entries+1 )
+
+    last_idx = num_entries-1
+
+    # Interface
+
+    s.recv_val = InPort ()
+    s.recv_rdy = OutPort()
+    s.send_val = OutPort()
+    s.send_rdy = InPort()
+    s.count    = OutPort( count_nbits )
+
+    s.wen     = OutPort()
+    s.waddr   = OutPort( addr_nbits )
+    s.raddr   = OutPort( addr_nbits )
+    s.mux_sel = OutPort()
+
+    # Registers
+
+    s.head = Wire( addr_nbits )
+    s.tail = Wire( addr_nbits )
+
+    # Wires
+
+    s.recv_xfer  = Wire()
+    s.send_xfer  = Wire()
+
+    # Connections
+
+    s.wen   //= s.recv_xfer
+    s.waddr //= s.tail
+    s.raddr //= s.head
+
+    s.recv_rdy //= lambda: s.count < num_entries
+    s.send_val //= lambda: (s.count > 0) | s.recv_val
+
+    s.mux_sel //= lambda: s.count == 0
+
+    s.recv_xfer //= lambda: s.recv_val & s.recv_rdy
+    s.send_xfer //= lambda: s.send_val & s.send_rdy
+
+    @update_ff
+    def up_reg():
+
+      if s.reset:
+        s.head  <<= 0
+        s.tail  <<= 0
+        s.count <<= 0
+
+      else:
+        if s.recv_xfer:
+          s.tail <<= s.tail + 1 if ( s.tail < num_entries - 1 ) else 0
+
+        if s.send_xfer:
+          s.head <<= s.head + 1 if ( s.head < num_entries -1 ) else 0
+
+        if s.recv_xfer & ~s.send_xfer:
+          s.count <<= s.count + 1
+        if ~s.recv_xfer & s.send_xfer:
+          s.count <<= s.count - 1
+
+#-------------------------------------------------------------------------
+# BypassQueueRTL
+#-------------------------------------------------------------------------
+
+class BypassQueueRTL( Component ):
+
+  def construct( s, EntryType, num_entries=2 ):
+
+    # Interface
+
+    s.recv  = RecvIfcRTL( EntryType )
+    s.send  = SendIfcRTL( EntryType )
+    s.count = OutPort( clog2( num_entries+1 ) )
+
+    # Components
+
+    assert num_entries > 0
+    if num_entries == 1:
+      s.q = BypassQueue1EntryRTL( EntryType )
+      s.recv  //= s.q.recv
+      s.send  //= s.q.send
+      s.count //= s.q.count
+
+    else:
+      s.ctrl  = BypassQueueCtrlRTL ( num_entries )
+      s.dpath = BypassQueueDpathRTL( EntryType, num_entries )
+
+      # Connect ctrl to data path
+
+      s.ctrl.wen     //= s.dpath.wen
+      s.ctrl.waddr   //= s.dpath.waddr
+      s.ctrl.raddr   //= s.dpath.raddr
+      s.ctrl.mux_sel //= s.dpath.mux_sel
+
+      # Connect to interface
+
+      s.recv.val //= s.ctrl.recv_val
+      s.recv.rdy //= s.ctrl.recv_rdy
+      s.send.val //= s.ctrl.send_val
+      s.send.rdy //= s.ctrl.send_rdy
+      s.count    //= s.ctrl.count
+      s.recv.msg //= s.dpath.recv_msg
+      s.send.msg //= s.dpath.send_msg
+
+  # Line trace
+
+  def line_trace( s ):
+    return f"{s.recv}({s.count}){s.send}"
+
diff --git a/pymtl3/stdlib/stream/valrdy_master_minion_ifcs.py b/pymtl3/stdlib/stream/valrdy_master_minion_ifcs.py
new file mode 100644
index 000000000..26397b319
--- /dev/null
+++ b/pymtl3/stdlib/stream/valrdy_master_minion_ifcs.py
@@ -0,0 +1,152 @@
+"""
+==========================================================================
+ master_minion_ifc.py
+==========================================================================
+Master/minion send/recv interface implementations at CL and RTL.
+ Author: Shunning Jiang
+   Date: Jan 28, 2020
+"""
+from pymtl3 import *
+
+from .ifcs import RecvIfcRTL, SendIfcRTL
+from pymtl3.extra import clone_deepcopy
+
+#-------------------------------------------------------------------------
+# CL interfaces
+#-------------------------------------------------------------------------
+
+# There is no custom connect method in CL ifcs.
+# For MasterCL-MinionRTL and MasterRTL-MinionCL connections, we just need
+# to connect by name and leverage the custom connect method of the nested
+# Send/RecvIfc.
+
+class MasterIfcCL( Interface ):
+  def construct( s, ReqType, RespType, *, req=None, req_rdy=None, resp=None, resp_rdy=None ):
+    s.ReqType  = ReqType
+    s.RespType = RespType
+    s.req  = CalleeIfcCL( Type=ReqType,  method=req, rdy=req_rdy )
+    s.resp = CalleeIfcCL( Type=RespType, method=resp, rdy=resp_rdy )
+  def __str__( s ):
+    return f"{s.req}|{s.resp}"
+
+class MinionIfcCL( Interface ):
+  def construct( s, ReqType, RespType ):
+    s.ReqType  = ReqType
+    s.RespType = RespType
+    s.req  = CallerIfcCL( Type=ReqType )
+    s.resp = CallerIfcCL( Type=RespType )
+  def __str__( s ):
+    return f"{s.req}|{s.resp}"
+
+#-------------------------------------------------------------------------
+# RTL interfaces
+#-------------------------------------------------------------------------
+# There is no custom connect method in CL ifcs.
+# For MasterCL-MinionRTL and MasterRTL-MinionCL connections, we just need
+# to connect by name and leverage the custom connect method of the nested
+# Send/RecvIfc.
+
+class MasterIfcRTL( Interface ):
+  def construct( s, ReqType, RespType ):
+    s.ReqType  = ReqType
+    s.RespType = RespType
+    s.req  = RecvIfcRTL( Type=ReqType )
+    s.resp = SendIfcRTL( Type=RespType )
+  def __str__( s ):
+    return f"{s.req}|{s.resp}"
+
+  def connect( s, other, parent ):
+    if isinstance( other, MinionIfcCL ):
+      m = ValRdyMasterMinionRTL2CLAdapter( other.ReqType, other.RespType )
+
+      if hasattr( parent, "ValRdyMasterMinionRTL2CLAdapter_count" ):
+        count = parent.XcelIfcFL2RTL_count
+        setattr( parent, "ValRdyMasterMinionRTL2CLAdapter_count" + str( count ), m )
+      else:
+        parent.ValRdyMasterMinionRTL2CLAdapter_count = 0
+        parent.ValRdyMasterMinionRTL2CLAdapter_0 = m
+
+      s       //= m.left
+      m.right //= other
+      parent.ValRdyMasterMinionRTL2CLAdapter_count += 1
+      return True
+
+    return False
+
+class MinionIfcRTL( Interface ):
+  def construct( s, ReqType, RespType ):
+    s.ReqType  = ReqType
+    s.RespType = RespType
+    s.req  = RecvIfcRTL( Type=ReqType )
+    s.resp = SendIfcRTL( Type=RespType )
+  def __str__( s ):
+    return f"{s.req}|{s.resp}"
+
+class ValRdyMasterMinionRTL2CLAdapter( Component ):
+
+  def req_rdy( s ):
+    return s.req_entry is not None
+
+  def req( s ):
+    assert s.req_entry is not None
+    ret = s.req_entry
+    s.req_entry = None
+    return ret
+
+  def resp_rdy( s ):
+    return s.resp_entry is None
+
+  def resp( s, msg ):
+    s.resp_entry = clone_deepcopy( msg )
+
+  def construct( s, ReqType, RespType ):
+    s.left  = MinionIfcRTL( ReqType, RespType )
+    s.right = MasterIfcCL( ReqType, RespType, req=s.req, req_rdy=s.req_rdy, resp=s.resp, resp_rdy=s.resp_rdy )
+
+    # Req side
+
+    s.req_entry = None
+
+    @update_ff
+    def up_left_req_rdy():
+      s.left.req.rdy <<= (s.req_entry is None)
+
+    @update_once
+    def up_left_req_msg():
+      if s.req_entry is None:
+        if s.left.req.val:
+          s.req_entry = clone_deepcopy( s.left.req.msg )
+
+    s.add_constraints(
+      U( up_left_req_msg ) < M( s.req ),
+      U( up_left_req_msg ) < M( s.req_rdy ),
+    )
+
+    # Resp side
+
+    s.resp_entry = None
+    s.resp_sent  = Wire()
+
+    @update_once
+    def up_right_resp():
+      if s.resp_entry is None:
+        s.left.resp.val @= 0
+      else:
+        s.left.resp.val @= 1
+        s.left.resp.msg @= s.resp_entry
+
+    @update_ff
+    def up_resp_sent():
+      s.resp_sent <<= s.left.resp.val & s.left.resp.rdy
+
+    @update_once
+    def up_clear():
+      if s.resp_sent: # constraints reverse this
+        s.resp_entry = None
+
+    s.add_constraints(
+      U( up_clear )   < M( s.resp ),
+      U( up_clear )   < M( s.resp_rdy ),
+      M( s.resp )     < U( up_right_resp ),
+      M( s.resp_rdy ) < U( up_right_resp )
+    )


Test patch:
diff --git a/examples/ex01_basics/IncrWires_test.py b/examples/ex01_basics/IncrWires_test.py
index 89898245f..a5666cc89 100644
--- a/examples/ex01_basics/IncrWires_test.py
+++ b/examples/ex01_basics/IncrWires_test.py
@@ -64,7 +64,7 @@ def line_trace( s ):
 
 def test_wires():
   incr = IncrWires()
-  incr.apply( DefaultPassGroup(print_line_trace=True) )
+  incr.apply( DefaultPassGroup(linetrace=True) )
 
   # Print out the update block schedule.
   print( "\n==== Schedule ====" )
diff --git a/examples/ex02_cksum/test/ChecksumVRTL_test.py b/examples/ex02_cksum/test/ChecksumVRTL_test.py
index af2dd4c09..ad78c8b36 100644
--- a/examples/ex02_cksum/test/ChecksumVRTL_test.py
+++ b/examples/ex02_cksum/test/ChecksumVRTL_test.py
@@ -111,7 +111,7 @@ def run_sim( s, th ):
     # ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''\/
 
     th = YosysTranslationImportPass()( th )
-    th.apply( DefaultPassGroup(print_line_trace=False) )
+    th.apply( DefaultPassGroup(linetrace=False) )
 
     # ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''/\
 
diff --git a/examples/ex03_proc/test/ProcVRTL_test.py b/examples/ex03_proc/test/ProcVRTL_test.py
index 3daebb84b..ab1362586 100644
--- a/examples/ex03_proc/test/ProcVRTL_test.py
+++ b/examples/ex03_proc/test/ProcVRTL_test.py
@@ -58,7 +58,7 @@ def run_sim( s, th, gen_test ):
     th = YosysTranslationImportPass()( th )
 
     # Create a simulator and run simulation
-    th.apply( DefaultPassGroup(print_line_trace=True) )
+    th.apply( DefaultPassGroup(linetrace=True) )
     th.sim_reset()
 
     while not th.done() and th.sim_cycle_count() < max_cycles:
diff --git a/pymtl3/passes/backends/verilog/test/TranslationImport_stdlib_test.py b/pymtl3/passes/backends/verilog/test/TranslationImport_stdlib_test.py
index eb609bf91..0ca18e513 100644
--- a/pymtl3/passes/backends/verilog/test/TranslationImport_stdlib_test.py
+++ b/pymtl3/passes/backends/verilog/test/TranslationImport_stdlib_test.py
@@ -12,6 +12,8 @@
 finishing each test (no matter it fails or passes).
 """
 
+from pymtl3.passes.PassGroups import DefaultPassGroup
+
 from pymtl3.passes.rtlir.util.test_utility import do_test
 from pymtl3.stdlib.basic_rtl.test.arbiters_test import test_rr_arb_4 as _rr_arb_4
 from pymtl3.stdlib.basic_rtl.test.arbiters_test import test_rr_arb_en_4 as _rr_arb_en_4
@@ -19,16 +21,12 @@
 from pymtl3.stdlib.basic_rtl.test.encoders_test import (
     test_encoder_5_directed as _encoder5,
 )
-from pymtl3.stdlib.queues.test.valrdy_queues_test import test_2entry_normal_Bits as _n2
-from pymtl3.stdlib.queues.test.valrdy_queues_test import test_3entry_normal_Bits as _n3
-from pymtl3.stdlib.queues.test.valrdy_queues_test import (
-    test_bypass_Bits as _bypass_Bits,
-)
-from pymtl3.stdlib.queues.test.valrdy_queues_test import (
-    test_normal_Bits as _normal_Bits,
-)
-from pymtl3.stdlib.queues.test.valrdy_queues_test import test_pipe_Bits as _pipe_Bits
+from pymtl3.stdlib.stream.test.queues_test import test_normal1_simple as _n1
+from pymtl3.stdlib.stream.test.queues_test import test_normal2_simple as _n2
+from pymtl3.stdlib.stream.test.queues_test import test_bypass1_simple as _b1
+from pymtl3.stdlib.stream.test.queues_test import test_pipe1_simple as _p1
 from pymtl3.stdlib.test_utils import TestVectorSimulator
+from pymtl3.stdlib.test_utils import run_sim as stdlib_run_sim
 
 from ..VerilogTranslationImportPass import VerilogTranslationImportPass
 
@@ -126,40 +124,44 @@ def tv_out( model, tv ):
 # Queue tests
 #-------------------------------------------------------------------------
 
-# Swap run_test_queue with modified version that calls the local do_test
-def _run_queue_test( do_test, test_func ):
+# Swap run_test_queue with modified version that calls the local run_sim
+def _run_queue_test( test_func ):
 
-  def _run_test( m, tvs ):
-    def tv_in( model, tv ):
-      model.enq.val @= tv[0]
-      model.enq.msg @= tv[2]
-      model.deq.rdy @= tv[4]
-    def tv_out( model, tv ):
-      if tv[1] != '?': assert model.enq.rdy == tv[1]
-      if tv[3] != '?': assert model.deq.val == tv[3]
-      if tv[5] != '?': assert model.deq.msg == tv[5]
-    m._tvs = tvs
-    m._tv_in, m._tv_out = tv_in, tv_out
-    do_test( m )
+  def _run_sim( th, cmdline_opts, duts ):
+    th.elaborate()
+    dut_objs = []
+
+    for dut in duts:
+      dut_objs.append( eval(f'th.{dut}') )
 
-  original_run_test = test_func.__globals__['run_test_queue']
-  test_func.__globals__['run_test_queue'] = _run_test
+    for obj in dut_objs:
+      obj.set_metadata( VerilogTranslationImportPass.enable, True )
+
+    th = VerilogTranslationImportPass()( th )
+
+    th.apply( DefaultPassGroup() )
+    th.sim_reset()
+
+    while not th.done() and th.sim_cycle_count() < 100:
+      th.sim_tick()
+
+    assert th.sim_cycle_count() < 100
+
+  original_run_sim = test_func.__globals__['run_sim']
+  test_func.__globals__['run_sim'] = _run_sim
   try:
-    test_func()
+    test_func( None )
   finally:
-    test_func.__globals__['run_test_queue'] = original_run_test
-
-def test_bypass_Bits( do_test ):
-  _run_queue_test( do_test, _bypass_Bits )
+    test_func.__globals__['run_sim'] = original_run_sim
 
-def test_pipe_Bits( do_test ):
-  _run_queue_test( do_test, _pipe_Bits )
+def test_normal1_simple():
+  _run_queue_test( _n1 )
 
-def test_normal_Bits( do_test ):
-  _run_queue_test( do_test, _normal_Bits )
+def test_normal2_simple():
+  _run_queue_test( _n2 )
 
-def test_2entry_normal_Bits( do_test ):
-  _run_queue_test( do_test, _n2 )
+def test_bypass1_simple():
+  _run_queue_test( _b1 )
 
-def test_3entry_normal_Bits( do_test ):
-  _run_queue_test( do_test, _n3 )
+def test_pipe1_simple():
+  _run_queue_test( _p1 )
diff --git a/pymtl3/passes/backends/yosys/test/TranslationImport_stdlib_test.py b/pymtl3/passes/backends/yosys/test/TranslationImport_stdlib_test.py
index 7d3edf0e2..a5adc8d0a 100644
--- a/pymtl3/passes/backends/yosys/test/TranslationImport_stdlib_test.py
+++ b/pymtl3/passes/backends/yosys/test/TranslationImport_stdlib_test.py
@@ -12,20 +12,22 @@
 finishing each test (no matter it fails or passes).
 """
 
+from pymtl3.passes.PassGroups import DefaultPassGroup
+
 from pymtl3.passes.backends.verilog.test.TranslationImport_stdlib_test import (
-    test_2entry_normal_Bits,
-    test_3entry_normal_Bits,
     test_arbiter_rr_arb_4,
     test_arbiter_rr_arb_en_4,
-    test_bypass_Bits,
     test_crossbar3,
     test_encoder_5_directed,
-    test_normal_Bits,
-    test_pipe_Bits,
 )
 from pymtl3.passes.rtlir.util.test_utility import do_test
 from pymtl3.stdlib.test_utils import TestVectorSimulator
 
+from pymtl3.stdlib.stream.test.queues_test import test_normal1_simple as _n1
+from pymtl3.stdlib.stream.test.queues_test import test_normal2_simple as _n2
+from pymtl3.stdlib.stream.test.queues_test import test_bypass1_simple as _b1
+from pymtl3.stdlib.stream.test.queues_test import test_pipe1_simple as _p1
+
 from ..YosysTranslationImportPass import YosysTranslationImportPass
 
 
@@ -36,3 +38,49 @@ def local_do_test( _m ):
   m = YosysTranslationImportPass()( _m )
   sim = TestVectorSimulator( m, _m._tvs, _m._tv_in, _m._tv_out )
   sim.run_test()
+
+#-------------------------------------------------------------------------
+# Queue tests
+#-------------------------------------------------------------------------
+
+# Swap run_test_queue with modified version that calls the local run_sim
+def _run_queue_test( test_func ):
+
+  def _run_sim( th, cmdline_opts, duts ):
+    th.elaborate()
+    dut_objs = []
+
+    for dut in duts:
+      dut_objs.append( eval(f'th.{dut}') )
+
+    for obj in dut_objs:
+      obj.set_metadata( YosysTranslationImportPass.enable, True )
+
+    th = YosysTranslationImportPass()( th )
+
+    th.apply( DefaultPassGroup() )
+    th.sim_reset()
+
+    while not th.done() and th.sim_cycle_count() < 100:
+      th.sim_tick()
+
+    assert th.sim_cycle_count() < 100
+
+  original_run_sim = test_func.__globals__['run_sim']
+  test_func.__globals__['run_sim'] = _run_sim
+  try:
+    test_func( None )
+  finally:
+    test_func.__globals__['run_sim'] = original_run_sim
+
+def test_normal1_simple():
+  _run_queue_test( _n1 )
+
+def test_normal2_simple():
+  _run_queue_test( _n2 )
+
+def test_bypass1_simple():
+  _run_queue_test( _b1 )
+
+def test_pipe1_simple():
+  _run_queue_test( _p1 )
diff --git a/pymtl3/stdlib/queues/test/valrdy_queues_test.py b/pymtl3/stdlib/queues/test/valrdy_queues_test.py
deleted file mode 100644
index 56e2d95cd..000000000
--- a/pymtl3/stdlib/queues/test/valrdy_queues_test.py
+++ /dev/null
@@ -1,123 +0,0 @@
-from pymtl3 import *
-from pymtl3.stdlib.ifcs import InValRdyIfc, OutValRdyIfc
-from pymtl3.stdlib.test_utils import TestVectorSimulator
-
-from ..valrdy_queues import *
-
-
-def run_test_queue( model, test_vectors ):
-
-  # Define functions mapping the test vector to ports in model
-
-  def tv_in( model, tv ):
-    model.enq.val @= tv[0]
-    model.enq.msg @= tv[2]
-    model.deq.rdy @= tv[4]
-
-  def tv_out( model, tv ):
-    if tv[1] != '?': assert model.enq.rdy == tv[1]
-    if tv[3] != '?': assert model.deq.val == tv[3]
-    if tv[5] != '?': assert model.deq.msg == tv[5]
-
-  # Run the test
-
-  sim = TestVectorSimulator( model, test_vectors, tv_in, tv_out )
-  sim.run_test()
-
-def test_bypass_Bits():
-
-  B1  = mk_bits(1)
-  B32 = mk_bits(32)
-  run_test_queue( BypassQueue1RTL( Bits32 ), [
-    # enq.val enq.rdy enq.msg  deq.val deq.rdy deq.msg
-    [  B1(1) , B1(1) ,B32(123), B1(1) , B1(1) ,B32(123) ],
-    [  B1(1) , B1(1) ,B32(345), B1(1) , B1(0) ,B32(345) ],
-    [  B1(1) , B1(0) ,B32(567), B1(1) , B1(0) ,B32(345) ],
-    [  B1(1) , B1(0) ,B32(567), B1(1) , B1(1) ,B32(345) ],
-    [  B1(1) , B1(1) ,B32(567), B1(1) , B1(1) ,B32(567) ],
-    [  B1(0) , B1(1) ,B32(0  ), B1(0) , B1(1) ,  '?'    ],
-    [  B1(0) , B1(1) ,B32(0  ), B1(0) , B1(0) ,  '?'    ],
-  ] )
-
-def test_pipe_Bits():
-
-  B1  = mk_bits(1)
-  B32 = mk_bits(32)
-  run_test_queue( PipeQueue1RTL( Bits32 ), [
-    # enq.val enq.rdy enq.msg  deq.val deq.rdy deq.msg
-    [  B1(1) , B1(1) ,B32(123), B1(0) , B1(1) ,  '?'    ],
-    [  B1(1) , B1(0) ,B32(345), B1(1) , B1(0) ,B32(123) ],
-    [  B1(1) , B1(0) ,B32(567), B1(1) , B1(0) ,B32(123) ],
-    [  B1(1) , B1(1) ,B32(567), B1(1) , B1(1) ,B32(123) ],
-    [  B1(1) , B1(1) ,B32(789), B1(1) , B1(1) ,B32(567) ],
-    [  B1(0) , B1(1) ,B32(0  ), B1(1) , B1(1) ,B32(789) ],
-    [  B1(0) , B1(1) ,B32(0  ), B1(0) , B1(0) ,  '?'    ],
-  ] )
-
-def test_normal_Bits():
-
-  B1  = mk_bits(1)
-  B32 = mk_bits(32)
-  run_test_queue( NormalQueue1RTL( Bits32 ), [
-    # enq.val enq.rdy enq.msg  deq.val deq.rdy deq.msg
-    [  B1(1) , B1(1) ,B32(123), B1(0) , B1(1) ,  '?'    ],
-    [  B1(1) , B1(0) ,B32(345), B1(1) , B1(0) ,B32(123) ],
-    [  B1(1) , B1(0) ,B32(567), B1(1) , B1(0) ,B32(123) ],
-    [  B1(1) , B1(0) ,B32(567), B1(1) , B1(1) ,B32(123) ],
-    [  B1(1) , B1(1) ,B32(567), B1(0) , B1(1) ,B32(123) ],
-    [  B1(0) , B1(0) ,B32(0  ), B1(1) , B1(1) ,B32(567) ],
-    [  B1(0) , B1(1) ,B32(0  ), B1(0) , B1(0) ,  '?'    ],
-  ] )
-
-def test_2entry_normal_Bits():
-  """Two Element Normal Queue."""
-  B1  = mk_bits(1)
-  B32 = mk_bits(32)
-  run_test_queue( NormalQueueRTL( 2, Bits32 ), [
-    # Enqueue one element and then dequeue it
-    # enq_val enq_rdy enq_bits deq_val deq_rdy deq_bits
-    [ B1(1), B1(1), B32(0x0001), B1(0), B1(1),      '?'    ],
-    [ B1(0), B1(1), B32(0x0000), B1(1), B1(1), B32(0x0001) ],
-    [ B1(0), B1(1), B32(0x0000), B1(0), B1(0),      '?'    ],
-
-    # Fill in the queue and enq/deq at the same time
-    # enq_val enq_rdy enq_bits deq_val deq_rdy deq_bits
-    [ B1(1), B1(1), B32(0x0002), B1(0), B1(0),      '?'    ],
-    [ B1(1), B1(1), B32(0x0003), B1(1), B1(0), B32(0x0002) ],
-    [ B1(0), B1(0), B32(0x0003), B1(1), B1(0), B32(0x0002) ],
-    [ B1(1), B1(0), B32(0x0003), B1(1), B1(0), B32(0x0002) ],
-    [ B1(1), B1(0), B32(0x0003), B1(1), B1(1), B32(0x0002) ],
-    [ B1(1), B1(1), B32(0x0004), B1(1), B1(0),      '?'    ],
-    [ B1(1), B1(0), B32(0x0004), B1(1), B1(1), B32(0x0003) ],
-    [ B1(1), B1(1), B32(0x0005), B1(1), B1(0),      '?'    ],
-    [ B1(0), B1(0), B32(0x0005), B1(1), B1(1), B32(0x0004) ],
-    [ B1(0), B1(1), B32(0x0005), B1(1), B1(1), B32(0x0005) ],
-    [ B1(0), B1(1), B32(0x0005), B1(0), B1(1),      '?'    ],
-  ])
-
-def test_3entry_normal_Bits():
-  """Three Element Queue."""
-  B1  = mk_bits(1)
-  B32 = mk_bits(32)
-  run_test_queue( NormalQueueRTL( 3, Bits32 ), [
-    # Enqueue one element and then dequeue it
-    # enq_val enq_rdy enq_bits deq_val deq_rdy deq_bits
-    [ B1(1), B1(1), B32(0x0001), B1(0), B1(1),      '?'    ],
-    [ B1(0), B1(1), B32(0x0000), B1(1), B1(1), B32(0x0001) ],
-    [ B1(0), B1(1), B32(0x0000), B1(0), B1(0),      '?'    ],
-
-    # Fill in the queue and enq/deq at the same time
-    # enq_val enq_rdy enq_bits deq_val deq_rdy deq_bits
-    [ B1(1), B1(1), B32(0x0002), B1(0), B1(0),      '?'    ],
-    [ B1(1), B1(1), B32(0x0003), B1(1), B1(0), B32(0x0002) ],
-    [ B1(1), B1(1), B32(0x0004), B1(1), B1(0), B32(0x0002) ],
-    [ B1(1), B1(0), B32(0x0005), B1(1), B1(0), B32(0x0002) ],
-    [ B1(0), B1(0), B32(0x0005), B1(1), B1(0), B32(0x0002) ],
-    [ B1(1), B1(0), B32(0x0005), B1(1), B1(1), B32(0x0002) ],
-    [ B1(1), B1(1), B32(0x0005), B1(1), B1(1), B32(0x0003) ],
-    [ B1(1), B1(1), B32(0x0006), B1(1), B1(1), B32(0x0004) ],
-    [ B1(1), B1(1), B32(0x0007), B1(1), B1(1), B32(0x0005) ],
-    [ B1(0), B1(1), B32(0x0000), B1(1), B1(1), B32(0x0006) ],
-    [ B1(0), B1(1), B32(0x0000), B1(1), B1(1), B32(0x0007) ],
-    [ B1(0), B1(1), B32(0x0000), B1(0), B1(1),      '?'    ],
-  ])
diff --git a/pymtl3/stdlib/stream/test/__init__.py b/pymtl3/stdlib/stream/test/__init__.py
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/pymtl3/stdlib/stream/test/__init__.py
@@ -0,0 +1,1 @@
+
diff --git a/pymtl3/stdlib/stream/test/magic_memory_test.py b/pymtl3/stdlib/stream/test/magic_memory_test.py
new file mode 100644
index 000000000..8204a0cb7
--- /dev/null
+++ b/pymtl3/stdlib/stream/test/magic_memory_test.py
@@ -0,0 +1,333 @@
+#=========================================================================
+# TestMemory_test.py
+#=========================================================================
+
+import random
+import struct
+
+import pytest
+
+from pymtl3 import *
+from pymtl3.stdlib.test_utils import mk_test_case_table, run_sim
+from pymtl3.stdlib.mem.MemMsg import MemMsgType, mk_mem_msg
+
+from ..SourceRTL import SourceRTL
+from ..SinkRTL import SinkRTL
+
+from ..magic_memory import MagicMemoryRTL
+
+#-------------------------------------------------------------------------
+# TestHarness
+#-------------------------------------------------------------------------
+
+class TestHarness( Component ):
+
+  def construct( s, cls, nports, PortTypes, src_msgs, sink_msgs,
+                 stall_prob, mem_extra_latency, src_initial,  src_interval, sink_initial, sink_interval,
+                 arrival_time=None ):
+    assert len(PortTypes) == nports
+    s.srcs = [ SourceRTL( PortTypes[i][0], src_msgs[i], src_initial, src_interval )
+                for i in range(nports) ]
+    s.mem  = cls( nports, PortTypes, stall_prob, mem_extra_latency )
+    s.sinks = [ SinkRTL( PortTypes[i][1], sink_msgs[i], sink_initial, sink_interval,
+                            arrival_time ) for i in range(nports) ]
+
+    # Connections
+    for i in range(nports):
+      s.srcs[i].send    //= s.mem.ifc[i].req
+      s.mem.ifc[i].resp //= s.sinks[i].recv
+
+  def done( s ):
+    return all([x.done() for x in s.srcs] + [x.done() for x in s.sinks])
+
+  def line_trace( s ):
+    return "{} >>>  {}  >>> {}".format(
+      "|".join( [ x.line_trace() for x in s.srcs ] ),
+      s.mem.line_trace(),
+      "|".join( [ x.line_trace() for x in s.sinks ] ) )
+
+#-------------------------------------------------------------------------
+# make messages
+#-------------------------------------------------------------------------
+
+req_type_dict = {
+  'rd': MemMsgType.READ,
+  'wr': MemMsgType.WRITE,
+  'ad': MemMsgType.AMO_ADD,
+  'an': MemMsgType.AMO_AND,
+  'or': MemMsgType.AMO_OR,
+  'xg': MemMsgType.AMO_SWAP,
+  'mn': MemMsgType.AMO_MIN,
+}
+
+resp_type_dict = {
+  'rd': MemMsgType.READ,
+  'wr': MemMsgType.WRITE,
+  'ad': MemMsgType.AMO_ADD,
+  'an': MemMsgType.AMO_AND,
+  'or': MemMsgType.AMO_OR,
+  'xg': MemMsgType.AMO_SWAP,
+  'mn': MemMsgType.AMO_MIN,
+}
+
+req_cls, resp_cls = mk_mem_msg( 8, 32, 32 )
+
+def req( type_, opaque, addr, len, data ):
+  return req_cls( req_type_dict[type_], opaque, addr, len, b32(data) )
+
+def resp( type_, opaque, len, data ):
+  return resp_cls( resp_type_dict[type_], opaque, 0, len, b32(data) )
+
+#----------------------------------------------------------------------
+# Test Case: basic
+#----------------------------------------------------------------------
+
+def basic_msgs( base_addr ):
+  return [
+    req( 'wr', 0x0, base_addr, 0, 0xdeadbeef ), resp( 'wr', 0x0, 0, 0          ),
+    req( 'rd', 0x1, base_addr, 0, 0          ), resp( 'rd', 0x1, 0, 0xdeadbeef ),
+  ]
+
+#----------------------------------------------------------------------
+# Test Case: stream
+#----------------------------------------------------------------------
+
+def stream_msgs( base_addr ):
+
+  msgs = []
+  for i in range(20):
+    msgs.extend([
+      req( 'wr', i, base_addr+4*i, 0, i ), resp( 'wr', i, 0, 0 ),
+      req( 'rd', i, base_addr+4*i, 0, 0 ), resp( 'rd', i, 0, i ),
+    ])
+
+  return msgs
+
+#----------------------------------------------------------------------
+# Test Case: subword reads
+#----------------------------------------------------------------------
+
+def subword_rd_msgs( base_addr ):
+  return [
+
+    req( 'wr', 0x0, base_addr+0, 0, 0xdeadbeef ), resp( 'wr', 0x0, 0, 0          ),
+
+    req( 'rd', 0x1, base_addr+0, 1, 0          ), resp( 'rd', 0x1, 1, 0x000000ef ),
+    req( 'rd', 0x2, base_addr+1, 1, 0          ), resp( 'rd', 0x2, 1, 0x000000be ),
+    req( 'rd', 0x3, base_addr+2, 1, 0          ), resp( 'rd', 0x3, 1, 0x000000ad ),
+    req( 'rd', 0x4, base_addr+3, 1, 0          ), resp( 'rd', 0x4, 1, 0x000000de ),
+
+    req( 'rd', 0x5, base_addr+0, 2, 0          ), resp( 'rd', 0x5, 2, 0x0000beef ),
+    req( 'rd', 0x6, base_addr+1, 2, 0          ), resp( 'rd', 0x6, 2, 0x0000adbe ),
+    req( 'rd', 0x7, base_addr+2, 2, 0          ), resp( 'rd', 0x7, 2, 0x0000dead ),
+
+    req( 'rd', 0x8, base_addr+0, 3, 0          ), resp( 'rd', 0x8, 3, 0x00adbeef ),
+    req( 'rd', 0x9, base_addr+1, 3, 0          ), resp( 'rd', 0x9, 3, 0x00deadbe ),
+
+    req( 'rd', 0xa, base_addr+0, 0, 0          ), resp( 'rd', 0xa, 0, 0xdeadbeef ),
+
+  ]
+
+#----------------------------------------------------------------------
+# Test Case: subword writes
+#----------------------------------------------------------------------
+
+def subword_wr_msgs( base_addr ):
+  return [
+
+    req( 'wr', 0x0, base_addr+0, 1, 0x000000ef ), resp( 'wr', 0x0, 0, 0          ),
+    req( 'wr', 0x1, base_addr+1, 1, 0x000000be ), resp( 'wr', 0x1, 0, 0          ),
+    req( 'wr', 0x2, base_addr+2, 1, 0x000000ad ), resp( 'wr', 0x2, 0, 0          ),
+    req( 'wr', 0x3, base_addr+3, 1, 0x000000de ), resp( 'wr', 0x3, 0, 0          ),
+    req( 'rd', 0x4, base_addr+0, 0, 0          ), resp( 'rd', 0x4, 0, 0xdeadbeef ),
+
+    req( 'wr', 0x5, base_addr+0, 2, 0x0000abcd ), resp( 'wr', 0x5, 0, 0          ),
+    req( 'wr', 0x6, base_addr+2, 2, 0x0000ef01 ), resp( 'wr', 0x6, 0, 0          ),
+    req( 'rd', 0x7, base_addr+0, 0, 0          ), resp( 'rd', 0x7, 0, 0xef01abcd ),
+
+    req( 'wr', 0x8, base_addr+1, 2, 0x00002345 ), resp( 'wr', 0x8, 0, 0          ),
+    req( 'rd', 0xa, base_addr+0, 0, 0          ), resp( 'rd', 0xa, 0, 0xef2345cd ),
+
+    req( 'wr', 0xb, base_addr+0, 3, 0x00cafe02 ), resp( 'wr', 0xb, 0, 0          ),
+    req( 'rd', 0xc, base_addr+0, 0, 0          ), resp( 'rd', 0xc, 0, 0xefcafe02 ),
+
+  ]
+
+#----------------------------------------------------------------------
+# Test Case: amos
+#----------------------------------------------------------------------
+
+def amo_msgs( base_addr ):
+  return [
+    # load some initial data
+    req( 'wr', 0x0, base_addr   , 0, 0x01234567 ), resp( 'wr', 0x0, 0, 0          ),
+    req( 'wr', 0x0, base_addr+4 , 0, 0x98765432 ), resp( 'wr', 0x0, 0, 0          ),
+    req( 'wr', 0x0, base_addr+8 , 0, 0x22002200 ), resp( 'wr', 0x0, 0, 0          ),
+    req( 'wr', 0x0, base_addr+12, 0, 0x00112233 ), resp( 'wr', 0x0, 0, 0          ),
+    req( 'wr', 0x0, base_addr+16, 0, 0x44556677 ), resp( 'wr', 0x0, 0, 0          ),
+    req( 'wr', 0x0, base_addr+20, 0, 0x01230123 ), resp( 'wr', 0x0, 0, 0          ),
+    # amo.add
+    req( 'ad', 0x1, base_addr   , 0, 0x12345678 ), resp( 'ad', 0x1, 0, 0x01234567 ),
+    req( 'rd', 0x2, base_addr   , 0, 0          ), resp( 'rd', 0x2, 0, 0x13579bdf ),
+    # amo.and
+    req( 'an', 0x3, base_addr+4 , 0, 0x23456789 ), resp( 'an', 0x3, 0, 0x98765432 ),
+    req( 'rd', 0x4, base_addr+4 , 0, 0          ), resp( 'rd', 0x4, 0, 0x00444400 ),
+    # amo.or
+    req( 'or', 0x5, base_addr+8 , 0, 0x01230123 ), resp( 'or', 0x5, 0, 0x22002200 ),
+    req( 'rd', 0x6, base_addr+8 , 0, 0          ), resp( 'rd', 0x6, 0, 0x23232323 ),
+    # amo.xchg
+    req( 'xg', 0x5, base_addr+12, 0, 0xdeadbeef ), resp( 'xg', 0x5, 0, 0x00112233 ),
+    req( 'rd', 0x6, base_addr+12, 0, 0          ), resp( 'rd', 0x6, 0, 0xdeadbeef ),
+    # amo.min -- mem is smaller
+    req( 'mn', 0x7, base_addr+16, 0, 0xcafebabe ), resp( 'mn', 0x7, 0, 0x44556677 ),
+    req( 'rd', 0x8, base_addr+16, 0, 0          ), resp( 'rd', 0x8, 0, 0xcafebabe ),
+    # amo.min -- arg is smaller
+    req( 'mn', 0x9, base_addr+20, 0, 0x01201234 ), resp( 'mn', 0x9, 0, 0x01230123 ),
+    req( 'rd', 0xa, base_addr+20, 0, 0          ), resp( 'rd', 0xa, 0, 0x01201234 ),
+  ]
+
+#----------------------------------------------------------------------
+# Test Case: random
+#----------------------------------------------------------------------
+
+def random_msgs( base_addr ):
+
+  rgen = random.Random()
+  rgen.seed(0xa4e28cc2)
+
+  vmem = [ rgen.randint(0,0xffffffff) for _ in range(20) ]
+  msgs = []
+
+  for i in range(20):
+    msgs.extend([
+      req( 'wr', i, base_addr+4*i, 0, vmem[i] ), resp( 'wr', i, 0, 0 ),
+    ])
+
+  for i in range(1):
+    idx = rgen.randint(0,19)
+
+    if rgen.randint(0,1):
+
+      correct_data = vmem[idx]
+      msgs.extend([
+        req( 'rd', i, base_addr+4*idx, 0, 0 ), resp( 'rd', i, 0, correct_data ),
+      ])
+
+    else:
+
+      new_data = rgen.randint(0,0xffffffff)
+      vmem[idx] = new_data
+      msgs.extend([
+        req( 'wr', i, base_addr+4*idx, 0, new_data ), resp( 'wr', i, 0, 0 ),
+      ])
+
+  return msgs
+
+#-------------------------------------------------------------------------
+# Test Case Table
+#-------------------------------------------------------------------------
+
+test_case_table = mk_test_case_table([
+  (                                  "msg_func          stall extra_lat src_init src_intv sink_init sink_intv"),
+  [ "basic",                          basic_msgs,       0.0,  0,        0,       0,       0,        0         ],
+  [ "stream",                         stream_msgs,      0.0,  0,        0,       0,       0,        0         ],
+  [ "subword_rd",                     subword_rd_msgs,  0.0,  0,        0,       0,       0,        0         ],
+  [ "subword_wr",                     subword_wr_msgs,  0.0,  0,        0,       0,       0,        0         ],
+  [ "amo",                            amo_msgs,         0.0,  0,        0,       0,       0,        0         ],
+  [ "random",                         random_msgs,      0.0,  0,        0,       0,       0,        0         ],
+  [ "random_3x14",                    random_msgs,      0.0,  0,        5,       3,       7,        14        ],
+  [ "stream_stall0.5_extralat0",      stream_msgs,      0.5,  0,        0,       0,       0,        0         ],
+  [ "stream_stall0.0_extralat4",      stream_msgs,      0.0,  4,        0,       0,       0,        0         ],
+  [ "stream_stall0.5_extralat4",      stream_msgs,      0.5,  4,        0,       0,       0,        0         ],
+  [ "random_stall0.5_extralat4_3x14", random_msgs,      0.5,  4,        5,       14,      7,        14        ],
+])
+
+#-------------------------------------------------------------------------
+# Test cases for 1 port
+#-------------------------------------------------------------------------
+
+@pytest.mark.parametrize( **test_case_table )
+def test_1port( test_params, cmdline_opts ):
+  msgs = test_params.msg_func(0x1000)
+  run_sim( TestHarness( MagicMemoryRTL, 1, [(req_cls, resp_cls)],
+                        [ msgs[::2] ],
+                        [ msgs[1::2] ],
+                        test_params.stall, test_params.extra_lat,
+                        test_params.src_init, test_params.src_intv,
+                        test_params.sink_init, test_params.sink_intv ), cmdline_opts )
+
+#-------------------------------------------------------------------------
+# Test cases for 2 port
+#-------------------------------------------------------------------------
+
+@pytest.mark.parametrize( **test_case_table )
+def test_2port( test_params, cmdline_opts ):
+  msgs0 = test_params.msg_func(0x1000)
+  msgs1 = test_params.msg_func(0x2000)
+  run_sim( TestHarness( MagicMemoryRTL, 2, [(req_cls, resp_cls)]*2,
+                        [ msgs0[::2],  msgs1[::2]  ],
+                        [ msgs0[1::2], msgs1[1::2] ],
+                        test_params.stall, test_params.extra_lat,
+                        test_params.src_init, test_params.src_intv,
+                        test_params.sink_init, test_params.sink_intv ), cmdline_opts )
+
+@pytest.mark.parametrize( **test_case_table )
+def test_20port( test_params, cmdline_opts ):
+  msgs = [ test_params.msg_func(0x1000*i) for i in range(20) ]
+  run_sim( TestHarness( MagicMemoryRTL, 20, [(req_cls, resp_cls)]*20,
+                        [ x[::2]  for x in msgs ],
+                        [ x[1::2] for x in msgs ],
+                        test_params.stall, test_params.extra_lat,
+                        test_params.src_init, test_params.src_intv,
+                        test_params.sink_init, test_params.sink_intv ), cmdline_opts )
+
+#-------------------------------------------------------------------------
+# Test Read/Write Mem
+#-------------------------------------------------------------------------
+
+def test_read_write_mem( cmdline_opts ):
+
+  rgen = random.Random()
+  rgen.seed(0x05a3e95b)
+
+  # Test data we want to write into memory
+
+  data = [ rgen.randrange(-(2**31),2**31) for _ in range(20) ]
+
+  # Convert test data into byte array
+
+  data_bytes = struct.pack("<{}i".format(len(data)),*data)
+
+  # Create memory messages to read and verify memory contents
+
+  msgs = []
+  for i, item in enumerate(data):
+    msgs.extend([
+      req( 'rd', 0x1, 0x1000+4*i, 0, 0 ), resp( 'rd', 0x1, 0, item ),
+    ])
+
+  # Create test harness with above memory messages
+
+  th = TestHarness( MagicMemoryRTL, 2, [(req_cls, resp_cls)]*2, [msgs[::2], []], [msgs[1::2], []],
+                    0, 0, 0, 0, 0, 0 )
+  th.elaborate()
+
+  # Write the data into the test memory
+
+  th.mem.write_mem( 0x1000, data_bytes )
+
+  # Run the test
+
+  run_sim( th )
+
+  # Read the data back out of the test memory
+
+  result_bytes = th.mem.read_mem( 0x1000, len(data_bytes) )
+
+  # Convert result bytes into list of ints
+
+  result = list(struct.unpack("<{}i".format(len(data)),result_bytes))
+
+  # Compare result to original data
+
+  assert result == data
diff --git a/pymtl3/stdlib/stream/test/queues_test.py b/pymtl3/stdlib/stream/test/queues_test.py
new file mode 100644
index 000000000..59cc71f08
--- /dev/null
+++ b/pymtl3/stdlib/stream/test/queues_test.py
@@ -0,0 +1,205 @@
+"""
+========================================================================
+Tests for queues
+========================================================================
+
+Author: Shunning Jiang, Yanghui Ou
+  Date: Feb 22, 2021
+"""
+from itertools import product
+
+import pytest
+
+from pymtl3 import *
+from pymtl3.stdlib.test_utils import TestVectorSimulator, run_sim
+from ..SourceRTL import SourceRTL
+from ..SinkRTL import SinkRTL
+
+from ..queues import (
+    BypassQueue1EntryRTL,
+    BypassQueueRTL,
+    NormalQueue1EntryRTL,
+    NormalQueueRTL,
+    PipeQueue1EntryRTL,
+    PipeQueueRTL,
+)
+
+#-------------------------------------------------------------------------
+# TestVectorSimulator test
+#-------------------------------------------------------------------------
+
+def run_tv_test( dut, test_vectors, cmdline_opts  ):
+
+  # Define input/output functions
+
+  def tv_in( dut, tv ):
+    dut.recv.val @= tv[0]
+    dut.recv.msg @= tv[2]
+    dut.send.rdy @= tv[4]
+
+  def tv_out( dut, tv ):
+    if tv[1] != '?': assert dut.recv.rdy == tv[1]
+    if tv[3] != '?': assert dut.send.val == tv[3]
+    if tv[5] != '?': assert dut.send.msg == tv[5]
+
+  # Run the test
+
+  sim = TestVectorSimulator( dut, test_vectors, tv_in, tv_out )
+  sim.run_test( cmdline_opts )
+
+def test_normal_behavior( cmdline_opts ):
+
+  B1  = mk_bits(1)
+  B32 = mk_bits(32)
+  run_tv_test( NormalQueueRTL( Bits32, 2 ), [
+    #  recv.val recv.rdy recv.msg  send.val send.rdy send.msg
+    [  1,       1,       0x123,    0,       0,       '?'   ],
+    [  1,       1,       0x345,    1,       0,       0x123 ],
+    [  1,       0,       0x567,    1,       0,       0x123 ],
+    [  1,       0,       0x567,    1,       1,       0x123 ],
+    [  1,       1,       0x567,    1,       1,       0x345 ],
+    [  1,       1,       0x890,    1,       0,       '?'   ],
+    [  1,       0,       0x0  ,    1,       1,       0x567 ],
+    [  0,       1,       0x0  ,    1,       1,       0x890 ],
+    [  1,       1,       0x2  ,    0,       1,       '?'   ],
+    [  1,       1,       0x3  ,    1,       1,       0x2   ],
+    [  1,       1,       0x4  ,    1,       1,       0x3   ],
+  ], cmdline_opts )
+
+#-------------------------------------------------------------------------
+# TestHarness
+#-------------------------------------------------------------------------
+
+class TestHarness( Component ):
+
+  def construct( s, MsgType, QueueType, src_msgs, sink_msgs ):
+
+    s.src  = SourceRTL( MsgType, src_msgs )
+    s.dut  = QueueType( MsgType )
+    s.sink = SinkRTL( MsgType, sink_msgs )
+
+    s.src.send //= s.dut.recv
+    s.dut.send //= s.sink.recv
+
+  def done( s ):
+    return s.src.done() and s.sink.done()
+
+  def line_trace( s ):
+    return "{} ({}) {}".format(
+      s.src.line_trace(), s.dut.line_trace(), s.sink.line_trace() )
+
+#-------------------------------------------------------------------------
+# Test cases
+#-------------------------------------------------------------------------
+
+test_msgs = [ Bits16( 4 ), Bits16( 1 ), Bits16( 2 ), Bits16( 3 ) ]
+
+arrival_normal = [ 3, 5, 7, 9 ]
+arrival_pipe   = [ 3, 4, 5, 6 ]
+arrival_bypass = [ 2, 3, 4, 5 ]
+
+
+def test_normal1_simple( cmdline_opts ):
+  th = TestHarness( Bits16, NormalQueueRTL, test_msgs, test_msgs )
+  th.set_param( "top.sink.construct", arrival_time = arrival_normal )
+  th.set_param( "top.dut.construct", num_entries = 1 )
+  run_sim( th, cmdline_opts, duts=['dut'] )
+
+def test_normal2_simple( cmdline_opts ):
+  th = TestHarness( Bits16, NormalQueueRTL, test_msgs, test_msgs )
+  th.set_param( "top.sink.construct", arrival_time = arrival_pipe )
+  th.set_param( "top.dut.construct", num_entries = 2 )
+  run_sim( th, cmdline_opts, duts=['dut'] )
+
+def test_normal3_simple( cmdline_opts ):
+  th = TestHarness( Bits16, NormalQueueRTL, test_msgs, test_msgs )
+  th.set_param( "top.sink.construct", arrival_time = arrival_pipe )
+  th.set_param( "top.dut.construct", num_entries = 3 )
+  run_sim( th, cmdline_opts, duts=['dut'] )
+
+def test_pipe1_simple( cmdline_opts ):
+  th = TestHarness( Bits16, PipeQueueRTL, test_msgs, test_msgs )
+  th.set_param( "top.sink.construct", arrival_time = arrival_pipe )
+  th.set_param( "top.dut.construct", num_entries = 1 )
+  run_sim( th, cmdline_opts, duts=['dut'] )
+
+def test_pipe1_backpressure( cmdline_opts ):
+  th = TestHarness( Bits16, PipeQueueRTL, test_msgs, test_msgs )
+  th.set_param( "top.sink.construct", initial_delay = 20 )
+  th.set_param( "top.dut.construct", num_entries = 1 )
+  run_sim( th, cmdline_opts, duts=['dut'] )
+
+def test_pipe2_backpressure( cmdline_opts ):
+  th = TestHarness( Bits16, PipeQueueRTL, test_msgs, test_msgs )
+  th.set_param( "top.sink.construct", initial_delay = 20 )
+  th.set_param( "top.dut.construct", num_entries = 2 )
+  run_sim( th, cmdline_opts, duts=['dut'] )
+
+def test_pipe3_backpressure( cmdline_opts ):
+  th = TestHarness( Bits16, PipeQueueRTL, test_msgs, test_msgs )
+  th.set_param( "top.sink.construct", initial_delay = 20 )
+  th.set_param( "top.dut.construct", num_entries = 3 )
+  run_sim( th, cmdline_opts, duts=['dut'] )
+
+def test_bypass1_simple( cmdline_opts ):
+  th = TestHarness( Bits16, BypassQueueRTL, test_msgs, test_msgs )
+  th.set_param( "top.sink.construct", arrival_time = arrival_bypass )
+  th.set_param( "top.dut.construct", num_entries = 1 )
+  run_sim( th, cmdline_opts, duts=['dut'] )
+
+def test_bypass1_backpressure( cmdline_opts ):
+  th = TestHarness( Bits16, BypassQueueRTL, test_msgs, test_msgs )
+  th.set_param( "top.sink.construct", initial_delay = 20 )
+  th.set_param( "top.dut.construct", num_entries = 1 )
+  run_sim( th, cmdline_opts, duts=['dut'] )
+
+def test_bypass2_sparse( cmdline_opts ):
+  th = TestHarness( Bits16, BypassQueueRTL, test_msgs, test_msgs )
+  th.set_param( "top.src.construct", interval_delay = 3 )
+  th.set_param( "top.dut.construct", num_entries = 2 )
+  run_sim( th, cmdline_opts, duts=['dut'] )
+
+def test_bypass3_sparse( cmdline_opts ):
+  th = TestHarness( Bits16, BypassQueueRTL, test_msgs, test_msgs )
+  th.set_param( "top.src.construct", interval_delay = 3 )
+  th.set_param( "top.dut.construct", num_entries = 3 )
+  run_sim( th, cmdline_opts, duts=['dut'] )
+
+@pytest.mark.parametrize(
+  'QType, num_entries',
+  product( [ NormalQueueRTL, PipeQueueRTL, BypassQueueRTL ], [ 8, 10, 12, 16 ] )
+)
+def test_large_backpressure( QType, num_entries, cmdline_opts ):
+  msgs = test_msgs * 8
+  th = TestHarness( Bits16, QType, msgs, msgs )
+  th.set_param( "top.sink.construct", initial_delay = 20 )
+  th.set_param( "top.dut.construct", num_entries = 16 )
+  run_sim( th, cmdline_opts, duts=['dut'] )
+
+@pytest.mark.parametrize(
+  'QType', [ NormalQueue1EntryRTL, PipeQueue1EntryRTL, BypassQueue1EntryRTL ]
+)
+def test_single_simple( QType, cmdline_opts ):
+  th = TestHarness( Bits16, QType, test_msgs, test_msgs )
+  run_sim( th, cmdline_opts, duts=['dut'] )
+
+@pytest.mark.parametrize(
+  'QType', [ NormalQueue1EntryRTL, PipeQueue1EntryRTL, BypassQueue1EntryRTL ]
+)
+def test_single_backpressure( QType, cmdline_opts ):
+  th = TestHarness( Bits16, QType, test_msgs, test_msgs )
+  th.set_param( "top.sink.construct", initial_delay = 10, interval_delay=2 )
+  run_sim( th, cmdline_opts, duts=['dut'] )
+
+
+@pytest.mark.parametrize(
+  'QType, num_entries',
+  product( [ NormalQueueRTL, PipeQueueRTL, BypassQueueRTL ], [ 8, 10, 12, 16 ] )
+)
+def test_large_delay( QType, num_entries, cmdline_opts ):
+  msgs = test_msgs * 8
+  th = TestHarness( Bits16, QType, msgs, msgs )
+  th.set_param( "top.src.construct",  initial_delay=5,  interval_delay = 3)
+  th.set_param( "top.sink.construct", initial_delay=20, interval_delay = 7)
+  th.set_param( "top.dut.construct", num_entries = 16 )
+  run_sim( th, cmdline_opts, duts=['dut'] )
diff --git a/pymtl3/stdlib/stream/valrdy_test_masters.py b/pymtl3/stdlib/stream/valrdy_test_masters.py
new file mode 100644
index 000000000..e6dc2c31c
--- /dev/null
+++ b/pymtl3/stdlib/stream/valrdy_test_masters.py
@@ -0,0 +1,30 @@
+"""
+==========================================================================
+ test_masters.py
+==========================================================================
+
+ Author: Shunning Jiang
+   Date: May 27, 2020
+"""
+from pymtl3 import *
+from pymtl3.stdlib.ifcs.valrdy_master_minion_ifcs import MasterIfcRTL
+
+from .valrdy_test_sinks import TestSinkRTL
+from .valrdy_test_srcs import TestSrcRTL
+
+
+class TestMasterRTL( Component ):
+
+  def construct( s, ReqType, RespType, MasterIfc=MasterIfcRTL ):
+    s.master = MasterIfc( ReqType, RespType )
+    s.src  = TestSrcRTL( ReqType )
+    s.sink = TestSinkRTL( RespType )
+
+    s.src.out  //= s.master.req
+    s.sink.in_ //= s.master.resp
+
+  def done( s ):
+    return s.src.done() and s.sink.done()
+
+  def line_trace( s ):
+    return str(s.master)
diff --git a/pymtl3/stdlib/test_utils/__init__.py b/pymtl3/stdlib/test_utils/__init__.py
index 89b40c54c..d626c7e41 100644
--- a/pymtl3/stdlib/test_utils/__init__.py
+++ b/pymtl3/stdlib/test_utils/__init__.py
@@ -1,4 +1,5 @@
 from .test_helpers import (
+    RunTestVectorSimError,
     TestVectorSimulator,
     config_model_with_cmdline_opts,
     mk_test_case_table,
diff --git a/pymtl3/stdlib/test_utils/test/run_test_vector_sim_test.py b/pymtl3/stdlib/test_utils/test/run_test_vector_sim_test.py
new file mode 100644
index 000000000..51e6a9005
--- /dev/null
+++ b/pymtl3/stdlib/test_utils/test/run_test_vector_sim_test.py
@@ -0,0 +1,215 @@
+#=========================================================================
+# run_test_vector_sim_test
+#=========================================================================
+
+import pytest
+
+from pymtl3 import *
+from pymtl3.stdlib.test_utils import run_test_vector_sim
+from pymtl3.stdlib.test_utils import RunTestVectorSimError
+
+#-------------------------------------------------------------------------
+# Test component with 1 input and 1 output port
+#-------------------------------------------------------------------------
+
+class TestComponent1i1o( Component ):
+  def construct( s ):
+    s.in_ = InPort(2)
+    s.out = OutPort(2)
+
+    @update
+    def up():
+      s.out @= s.in_ + 1
+
+  def line_trace(s):
+    return f"{s.in_}(){s.out}"
+
+def test_1i1o_basic( cmdline_opts ):
+  run_test_vector_sim( TestComponent1i1o(), [
+    ('in_ out*' ),
+    [ 0,  1     ],
+    [ 1,  2     ],
+    [ 0,  1     ],
+  ], cmdline_opts )
+
+# Verify that using a don't care works
+
+def test_1i1o_dontcare( cmdline_opts ):
+  run_test_vector_sim( TestComponent1i1o(), [
+    ('in_ out*' ),
+    [ 0,  '?'   ],
+    [ 1,  2     ],
+    [ 0,  1     ],
+  ], cmdline_opts )
+
+# Verify that an exception is thrown if the output is incorrect
+
+def test_1i1o_incorrect_output( cmdline_opts ):
+  with pytest.raises(RunTestVectorSimError):
+    run_test_vector_sim( TestComponent1i1o(), [
+      ('in_ out*' ),
+      [ 0,  0     ],
+      [ 1,  1     ],
+      [ 0,  0     ],
+    ], cmdline_opts )
+
+# Verify that an exception is thrown if input port name is wrong
+
+def test_1i1o_incorrect_inport( cmdline_opts ):
+  with pytest.raises(RunTestVectorSimError):
+    run_test_vector_sim( TestComponent1i1o(), [
+      ('inX out*' ),
+      [ 0,  0     ],
+      [ 1,  2     ],
+      [ 0,  0     ],
+    ], cmdline_opts )
+
+# Verify that an exception is thrown if output port name is wrong
+
+def test_1i1o_incorrect_outport( cmdline_opts ):
+  with pytest.raises(RunTestVectorSimError):
+    run_test_vector_sim( TestComponent1i1o(), [
+      ('in_ outX*' ),
+      [ 0,  0      ],
+      [ 1,  2      ],
+      [ 0,  0      ],
+    ], cmdline_opts )
+
+#-------------------------------------------------------------------------
+# Test component with 2 input and 2 output port
+#-------------------------------------------------------------------------
+
+class TestComponent2i2o( Component ):
+  def construct( s ):
+    s.in0  = InPort(2)
+    s.in1  = InPort(2)
+    s.out0 = OutPort(2)
+    s.out1 = OutPort(2)
+
+    @update
+    def up():
+      s.out0 @= s.in0 + 1
+      s.out1 @= s.in1 + 2
+
+  def line_trace(s):
+    return f"{s.in0}|{s.in1}(){s.out0}|{s.out1}"
+
+def test_2i2o_basic( cmdline_opts ):
+  run_test_vector_sim( TestComponent2i2o(), [
+    ('in0 in1 out0* out1*' ),
+    [ 0,  0,  1,    2      ],
+    [ 1,  0,  2,    2      ],
+    [ 0,  1,  1,    3      ],
+  ], cmdline_opts )
+
+# Verify that using a don't care works
+
+def test_2i2o_dontcare( cmdline_opts ):
+  run_test_vector_sim( TestComponent2i2o(), [
+    ('in0 in1 out0* out1*' ),
+    [ 0,  0,  '?',  '?'    ],
+    [ 1,  0,  2,    '?'    ],
+    [ 0,  1,  1,    3      ],
+  ], cmdline_opts )
+
+# Verify that an exception is thrown if the output is incorrect
+
+def test_2i2o_incorrect_output( cmdline_opts ):
+  with pytest.raises(RunTestVectorSimError):
+    run_test_vector_sim( TestComponent2i2o(), [
+      ('in0 in1 out0* out1*' ),
+      [ 0,  0,  1,    2      ],
+      [ 1,  0,  3,    2      ],
+      [ 0,  1,  1,    3      ],
+    ], cmdline_opts )
+
+# Verify that an exception is thrown if input port name is wrong
+
+def test_2i2o_incorrect_inport( cmdline_opts ):
+  with pytest.raises(RunTestVectorSimError):
+    run_test_vector_sim( TestComponent2i2o(), [
+      ('in0 inX out0* out1*' ),
+      [ 0,  0,  1,    2      ],
+      [ 1,  0,  2,    2      ],
+      [ 0,  1,  1,    3      ],
+    ], cmdline_opts )
+
+# Verify that an exception is thrown if output port name is wrong
+
+def test_2i2o_incorrect_outport( cmdline_opts ):
+  with pytest.raises(RunTestVectorSimError):
+    run_test_vector_sim( TestComponent2i2o(), [
+      ('in0 in1 outX* out1*' ),
+      [ 0,  0,  1,    2      ],
+      [ 1,  0,  2,    2      ],
+      [ 0,  1,  1,    3      ],
+    ], cmdline_opts )
+
+#-------------------------------------------------------------------------
+# Test component with input and output array ports
+#-------------------------------------------------------------------------
+
+class TestComponentArray2i2o( Component ):
+  def construct( s ):
+    s.in_  = [ InPort(2)  for _ in range(2) ]
+    s.out  = [ OutPort(2) for _ in range(2) ]
+
+    @update
+    def up():
+      s.out[0] @= s.in_[0] + 1
+      s.out[1] @= s.in_[1] + 2
+
+  def line_trace(s):
+    return f"{s.in_[0]}|{s.in_[1]}(){s.out[0]}|{s.out[1]}"
+
+def test_a2i2o_basic( cmdline_opts ):
+  run_test_vector_sim( TestComponentArray2i2o(), [
+    ('in_[0] in_[1] out[0]* out[1]*' ),
+    [ 0,     0,     1,      2        ],
+    [ 1,     0,     2,      2        ],
+    [ 0,     1,     1,      3        ],
+  ], cmdline_opts )
+
+# Verify that using a don't care works
+
+def test_a2i2o_dontcare( cmdline_opts ):
+  run_test_vector_sim( TestComponentArray2i2o(), [
+    ('in_[0] in_[1] out[0]* out[1]*' ),
+    [ 0,     0,     '?',    '?'      ],
+    [ 1,     0,     2,      '?'      ],
+    [ 0,     1,     1,      3        ],
+  ], cmdline_opts )
+
+# Verify that an exception is thrown if the output is incorrect
+
+def test_a2i2o_incorrect_output( cmdline_opts ):
+  with pytest.raises(RunTestVectorSimError):
+    run_test_vector_sim( TestComponentArray2i2o(), [
+      ('in_[0] in_[1] out[0]* out[1]*' ),
+      [ 0,     0,     1,      2        ],
+      [ 1,     0,     3,      2        ],
+      [ 0,     1,     1,      3        ],
+    ], cmdline_opts )
+
+# Verify that an exception is thrown if input port name is wrong
+
+def test_a2i2o_incorrect_inport( cmdline_opts ):
+  with pytest.raises(RunTestVectorSimError):
+    run_test_vector_sim( TestComponentArray2i2o(), [
+      ('in_[0] inX    out[0]* out[1]*' ),
+      [ 0,     0,     1,      2        ],
+      [ 1,     0,     2,      2        ],
+      [ 0,     1,     1,      3        ],
+    ], cmdline_opts )
+
+# Verify that an exception is thrown if output port name is wrong
+
+def test_a2i2o_incorrect_outport( cmdline_opts ):
+  with pytest.raises(RunTestVectorSimError):
+    run_test_vector_sim( TestComponentArray2i2o(), [
+      ('in_[0] in_[1] outX*   out[1]*' ),
+      [ 0,     0,     1,      2        ],
+      [ 1,     0,     2,      2        ],
+      [ 0,     1,     1,      3        ],
+    ], cmdline_opts )
+
diff --git a/pymtl3/stdlib/test_utils/test_helpers.py b/pymtl3/stdlib/test_utils/test_helpers.py
index 638b098e8..19caf8509 100644
--- a/pymtl3/stdlib/test_utils/test_helpers.py
+++ b/pymtl3/stdlib/test_utils/test_helpers.py
@@ -67,6 +67,11 @@ def config_model_with_cmdline_opts( top, cmdline_opts, duts ):
   dump_vcd     = cmdline_opts[ 'dump_vcd'     ]
   dump_vtb     = cmdline_opts[ 'dump_vtb'     ]
 
+  if 'on_demand_vcd_portname' in cmdline_opts:
+    on_demand_vcd_portname = cmdline_opts['on_demand_vcd_portname']
+  else:
+    on_demand_vcd_portname = ""
+
   # Setup the model
 
   top.elaborate()
@@ -74,6 +79,9 @@ def config_model_with_cmdline_opts( top, cmdline_opts, duts ):
   if dump_vcd:
     _recursive_set_vl_trace( top, dump_vcd )
 
+  if on_demand_vcd_portname:
+    assert test_verilog, "--test-verilog has to be present to enable on-demand VCD!"
+
   if duts:
     dut_objs = []
     for i, dut in enumerate(duts):
@@ -90,6 +98,10 @@ def config_model_with_cmdline_opts( top, cmdline_opts, duts ):
         dut.set_metadata( VerilogVerilatorImportPass.vl_trace, True )
         dut.set_metadata( VerilogVerilatorImportPass.vl_trace_filename, dump_vcd )
 
+      if on_demand_vcd_portname:
+        dut.set_metadata( VerilogVerilatorImportPass.vl_trace_on_demand, True )
+        dut.set_metadata( VerilogVerilatorImportPass.vl_trace_on_demand_portname, on_demand_vcd_portname )
+
   top.apply( VerilogPlaceholderPass() )
   top = VerilogTranslationImportPass()( top )
 
@@ -140,7 +152,7 @@ def run_test( self, cmdline_opts=None ):
     self.model = config_model_with_cmdline_opts( self.model, cmdline_opts, [] )
 
     try:
-      self.model.apply( DefaultPassGroup(print_line_trace=True) )
+      self.model.apply( DefaultPassGroup(linetrace=True) )
       self.model.sim_reset()
 
       for test_vector in self.test_vectors:
@@ -160,7 +172,7 @@ def run_test( self, cmdline_opts=None ):
     finally:
       finalize_verilator( self.model )
 
-def run_sim( model, cmdline_opts=None, line_trace=True, duts=None ):
+def run_sim( model, cmdline_opts=None, print_line_trace=True, duts=None ):
 
   cmdline_opts = cmdline_opts or {'dump_vcd': False, 'test_verilog': False, 'max_cycles': None, 'dump_vtb': ''}
 
@@ -172,7 +184,7 @@ def run_sim( model, cmdline_opts=None, line_trace=True, duts=None ):
 
   try:
     # Create a simulator
-    model.apply( DefaultPassGroup(print_line_trace=line_trace) )
+    model.apply( DefaultPassGroup(linetrace=print_line_trace) )
     # Reset model
     model.sim_reset()
 
@@ -194,7 +206,7 @@ def run_sim( model, cmdline_opts=None, line_trace=True, duts=None ):
 class RunTestVectorSimError( Exception ):
   pass
 
-def run_test_vector_sim( model, test_vectors, cmdline_opts=None, line_trace=True ):
+def run_test_vector_sim( model, test_vectors, cmdline_opts=None, print_line_trace=True ):
   cmdline_opts = cmdline_opts or {'dump_vcd': False, 'test_verilog': False, 'dump_vtb': ''}
 
   # First row in test vectors contains port names
@@ -214,7 +226,7 @@ def run_test_vector_sim( model, test_vectors, cmdline_opts=None, line_trace=True
 
   try:
     # Create a simulator
-    model.apply( DefaultPassGroup(print_line_trace=line_trace) )
+    model.apply( DefaultPassGroup(linetrace=print_line_trace) )
     # Reset model
     model.sim_reset()
 
@@ -246,12 +258,19 @@ def run_test_vector_sim( model, test_vectors, cmdline_opts=None, line_trace=True
 
         groups[i] = g = ( True, m.group(1), int(m.group(2)) )
 
+        if not hasattr( model, g[1] ):
+          raise RunTestVectorSimError(f"Invalid port name: {g[1]}")
+
         # Get type of all the ports
         t = type( getattr( model, g[1] )[ int(g[2]) ] )
         types[i] = None if is_bitstruct_class( t ) else t
 
       else:
         groups[i] = ( False, port_name )
+
+        if not hasattr( model, port_name ):
+          raise RunTestVectorSimError(f"Invalid port name: {port_name}")
+
         t = type( getattr( model, port_name ) )
         types[i] = None if is_bitstruct_class( t ) else t
 
@@ -290,8 +309,13 @@ def run_test_vector_sim( model, test_vectors, cmdline_opts=None, line_trace=True
         else:     out_value = getattr( model, g[1] )
 
         if out_value != ref_value:
-          if line_trace:
+          if print_line_trace:
             model.print_line_trace()
+
+          port_name = g[1]
+          if g[0]:
+            port_name = f"{g[1]}[{g[2]}]"
+
           error_msg = """
 run_test_vector_sim received an incorrect value!
 - row number     : {row_number}
diff --git a/pymtl3/stdlib/test_utils/test_srcs.py b/pymtl3/stdlib/test_utils/test_srcs.py
index d14f87dbb..68df842bf 100644
--- a/pymtl3/stdlib/test_utils/test_srcs.py
+++ b/pymtl3/stdlib/test_utils/test_srcs.py
@@ -1,6 +1,6 @@
 """
 ========================================================================
-Test sources
+SrcRTL
 ========================================================================
 Test sources with CL or RTL interfaces.
 
diff --git a/pymtl3/stdlib/test_utils/valrdy_test_srcs.py b/pymtl3/stdlib/test_utils/valrdy_test_srcs.py
new file mode 100644
index 000000000..b859806d5
--- /dev/null
+++ b/pymtl3/stdlib/test_utils/valrdy_test_srcs.py
@@ -0,0 +1,64 @@
+"""
+========================================================================
+Test sources
+========================================================================
+Test sources with CL or RTL interfaces.
+
+Author : Yanghui Ou
+  Date : Mar 11, 2019
+"""
+from collections import deque
+from copy import deepcopy
+
+from pymtl3 import *
+from pymtl3.stdlib.ifcs import OutValRdyIfc
+
+
+class TestSrcRTL( Component ):
+
+  def construct( s, Type, msgs, initial_delay=0, interval_delay=0 ):
+
+    # Interface
+
+    s.out = OutValRdyIfc( Type )
+
+    # Data
+
+    s.msgs = deepcopy(msgs)
+
+    # TODO: use wires and ROM to make it translatable
+    s.idx = 0
+    s.num_msgs = len(s.msgs)
+    s.count = 0
+
+    @update_ff
+    def up_src():
+      if s.reset:
+        s.idx   = 0
+        s.count = initial_delay
+        s.out.val <<= 0
+
+      else:
+        if s.out.val & s.out.rdy:
+          s.idx += 1
+          s.count = interval_delay
+
+        if s.count > 0:
+          s.count -= 1
+          s.out.val <<= 0
+
+        else: # s.count == 0
+          if s.idx < s.num_msgs:
+            s.out.val <<= 1
+            s.out.msg <<= s.msgs[s.idx]
+          else:
+            s.out.val <<= 0
+
+
+  def done( s ):
+    return s.idx >= s.num_msgs
+
+  # Line trace
+
+  def line_trace( s ):
+    return f"{s.out}"