diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,46 @@
+## v0.0.10
+
+### Breaking Changes
+
+* `--write adjacent` no longer forbids overwriting existing generated files
+
+### New Features
+
+* Added support for assignments within expressions (e.g., `x = ++y;`)
+* Added support for excluding the conversion of unbased unsized literals (e.g.,
+  `'1`, `'x`) via `--exclude UnbasedUniszed`
+* Added support for enumerated type ranges (e.g., `enum { X[3:5] }`)
+* Added support for complex event expressions (e.g., `@(x ^ y)`)
+* Added support for the SystemVerilog `edge` event
+* Added support for cycle delay ranges in assertion sequence expressions
+* Added support for procedural continuous assignments (`assign`/`deassign` and
+  `force`/`release`)
+* Added conversion for `do` `while` loops
+* Added support for hierarchical calls to functions with no inputs
+* Added support for passing through DPI imports and exports
+* Added support for passing through functions with output ports
+* Extended applicability of simplified Yosys-compatible `for` loop elaboration
+
+### Other Enhancements
+
+* Certain errors raised during conversion now also provide hierarchical and
+  approximate source location information to help locate the error
+
+### Bug Fixes
+
+* Fixed inadvertent design behavior changes caused by constant folding removing
+  intentional width-extending operations such as `+ 0` and `* 1`
+* Fixed forced conversion to `reg` of data sensed in an edge-controlled
+  procedural assignment
+* `always_comb` and `always_latch` now generate explicit sensitivity lists where
+  necessary because of calls to functions which reference non-local data
+* Fixed signed `struct` fields being converted to unsigned expressions when
+  accessed directly
+* Fixed conversion of casts using structs containing multi-dimensional fields
+* Fixed incorrect name resolution conflicts raised during interface inlining
+* Fixed handling of interface instances which shadow other declarations
+* Fixed names like `<pkg>_<name>` being shadowed by elaborated packages
+
 ## v0.0.9
 
 ### Breaking Changes
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 BSD 3-Clause License
 
-Copyright 2019-2021 Zachary Snow
+Copyright 2019-2022 Zachary Snow
 Copyright 2011-2015 Tom Hawkins
 
 All rights reserved.
diff --git a/NOTICE b/NOTICE
--- a/NOTICE
+++ b/NOTICE
@@ -1,26 +1,26 @@
 ================================================================================
-Dependency: alex-3.2.6
+Dependency: alex-3.2.7.1
 ================================================================================
 
   Copyright (c) 1995-2011, Chris Dornan and Simon Marlow
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are
   met:
-  
+
       * Redistributions of source code must retain the above copyright
         notice, this list of conditions and the following disclaimer.
-  
+
       * Redistributions in binary form must reproduce the above
         copyright notice, this list of conditions and the following
         disclaimer in the documentation and/or other materials provided
         with the distribution.
-  
+
       * Neither the name of the copyright holders, nor the names of the
         contributors may be used to endorse or promote products derived
         from this software without specific prior written permission.
-  
+
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@@ -38,43 +38,43 @@
 ================================================================================
 
   This library (libraries/base) is derived from code from several
-  sources: 
-  
+  sources:
+
     * Code from the GHC project which is largely (c) The University of
       Glasgow, and distributable under a BSD-style license (see below),
-  
+
     * Code from the Haskell 98 Report which is (c) Simon Peyton Jones
       and freely redistributable (but see the full license for
       restrictions).
-  
+
     * Code from the Haskell Foreign Function Interface specification,
       which is (c) Manuel M. T. Chakravarty and freely redistributable
       (but see the full license for restrictions).
-  
+
   The full text of these licenses is reproduced below.  All of the
   licenses are BSD-style or compatible.
-  
+
   -----------------------------------------------------------------------------
-  
+
   The Glasgow Haskell Compiler License
-  
-  Copyright 2004, The University Court of the University of Glasgow. 
+
+  Copyright 2004, The University Court of the University of Glasgow.
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
   - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
-   
+
   - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
-   
+
   - Neither name of the University nor the names of its contributors may be
   used to endorse or promote products derived from this software without
-  specific prior written permission. 
-  
+  specific prior written permission.
+
   THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF
   GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
@@ -87,14 +87,14 @@
   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
   DAMAGE.
-  
+
   -----------------------------------------------------------------------------
-  
+
   Code derived from the document "Report on the Programming Language
   Haskell 98", is distributed under the following license:
-  
+
     Copyright (c) 2002 Simon Peyton Jones
-  
+
     The authors intend this Report to belong to the entire Haskell
     community, and so we grant permission to copy and distribute it for
     any purpose, provided that it is reproduced in its entirety,
@@ -102,15 +102,15 @@
     copied and distributed for any purpose, provided that the modified
     version is clearly presented as such, and that it does not claim to
     be a definition of the Haskell 98 Language.
-  
+
   -----------------------------------------------------------------------------
-  
+
   Code derived from the document "The Haskell 98 Foreign Function
   Interface, An Addendum to the Haskell 98 Report" is distributed under
   the following license:
-  
+
     Copyright (c) 2002 Manuel M. T. Chakravarty
-  
+
     The authors intend this Report to belong to the entire Haskell
     community, and so we grant permission to copy and distribute it for
     any purpose, provided that it is reproduced in its entirety,
@@ -118,52 +118,52 @@
     copied and distributed for any purpose, provided that the modified
     version is clearly presented as such, and that it does not claim to
     be a definition of the Haskell 98 Foreign Function Interface.
-  
+
   -----------------------------------------------------------------------------
-  
 
+
 ================================================================================
-Dependency: base-4.14.1.0
+Dependency: base-4.14.3.0
 ================================================================================
 
   This library (libraries/base) is derived from code from several
-  sources: 
-  
+  sources:
+
     * Code from the GHC project which is largely (c) The University of
       Glasgow, and distributable under a BSD-style license (see below),
-  
+
     * Code from the Haskell 98 Report which is (c) Simon Peyton Jones
       and freely redistributable (but see the full license for
       restrictions).
-  
+
     * Code from the Haskell Foreign Function Interface specification,
       which is (c) Manuel M. T. Chakravarty and freely redistributable
       (but see the full license for restrictions).
-  
+
   The full text of these licenses is reproduced below.  All of the
   licenses are BSD-style or compatible.
-  
+
   -----------------------------------------------------------------------------
-  
+
   The Glasgow Haskell Compiler License
-  
-  Copyright 2004, The University Court of the University of Glasgow. 
+
+  Copyright 2004, The University Court of the University of Glasgow.
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
   - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
-   
+
   - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
-   
+
   - Neither name of the University nor the names of its contributors may be
   used to endorse or promote products derived from this software without
-  specific prior written permission. 
-  
+  specific prior written permission.
+
   THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF
   GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
@@ -176,14 +176,14 @@
   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
   DAMAGE.
-  
+
   -----------------------------------------------------------------------------
-  
+
   Code derived from the document "Report on the Programming Language
   Haskell 98", is distributed under the following license:
-  
+
     Copyright (c) 2002 Simon Peyton Jones
-  
+
     The authors intend this Report to belong to the entire Haskell
     community, and so we grant permission to copy and distribute it for
     any purpose, provided that it is reproduced in its entirety,
@@ -191,15 +191,15 @@
     copied and distributed for any purpose, provided that the modified
     version is clearly presented as such, and that it does not claim to
     be a definition of the Haskell 98 Language.
-  
+
   -----------------------------------------------------------------------------
-  
+
   Code derived from the document "The Haskell 98 Foreign Function
   Interface, An Addendum to the Haskell 98 Report" is distributed under
   the following license:
-  
+
     Copyright (c) 2002 Manuel M. T. Chakravarty
-  
+
     The authors intend this Report to belong to the entire Haskell
     community, and so we grant permission to copy and distribute it for
     any purpose, provided that it is reproduced in its entirety,
@@ -207,7 +207,7 @@
     copied and distributed for any purpose, provided that the modified
     version is clearly presented as such, and that it does not claim to
     be a definition of the Haskell 98 Foreign Function Interface.
-  
+
   -----------------------------------------------------------------------------
 
 ================================================================================
@@ -215,24 +215,24 @@
 ================================================================================
 
   Copyright (c) Lennart Kolmodin
-  
+
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:
-  
+
   1. Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
-  
+
   2. Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
-  
+
   3. Neither the name of the author nor the names of his contributors
      may be used to endorse or promote products derived from this software
      without specific prior written permission.
-  
+
   THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
   OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@@ -253,9 +253,9 @@
             (c) Duncan Coutts 2006-2015
             (c) David Roundy 2003-2005
             (c) Simon Meier 2010-2011
-  
+
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:
@@ -267,7 +267,7 @@
   3. Neither the name of the author nor the names of his contributors
      may be used to endorse or promote products derived from this software
      without specific prior written permission.
-  
+
   THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
@@ -286,23 +286,23 @@
 
   Copyright Neil Mitchell 2009-2021.
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are
   met:
-  
+
       * Redistributions of source code must retain the above copyright
         notice, this list of conditions and the following disclaimer.
-  
+
       * Redistributions in binary form must reproduce the above
         copyright notice, this list of conditions and the following
         disclaimer in the documentation and/or other materials provided
         with the distribution.
-  
+
       * Neither the name of Neil Mitchell nor the names of other
         contributors may be used to endorse or promote products derived
         from this software without specific prior written permission.
-  
+
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@@ -316,28 +316,28 @@
   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 ================================================================================
-Dependency: containers-0.6.2.1
+Dependency: containers-0.6.5.1
 ================================================================================
 
   The Glasgow Haskell Compiler License
-  
+
   Copyright 2004, The University Court of the University of Glasgow.
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
   - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
-  
+
   - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
-  
+
   - Neither name of the University nor the names of its contributors may be
   used to endorse or promote products derived from this software without
   specific prior written permission.
-  
+
   THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF
   GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
@@ -358,28 +358,28 @@
   This library (deepseq) is derived from code from the GHC project which
   is largely (c) The University of Glasgow, and distributable under a
   BSD-style license (see below).
-  
+
   -----------------------------------------------------------------------------
-  
+
   The Glasgow Haskell Compiler License
-  
-  Copyright 2001-2009, The University Court of the University of Glasgow. 
+
+  Copyright 2001-2009, The University Court of the University of Glasgow.
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
   - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
-   
+
   - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
-   
+
   - Neither name of the University nor the names of its contributors may be
   used to endorse or promote products derived from this software without
-  specific prior written permission. 
-  
+  specific prior written permission.
+
   THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF
   GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
@@ -392,7 +392,7 @@
   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
   DAMAGE.
-  
+
   -----------------------------------------------------------------------------
 
 ================================================================================
@@ -400,39 +400,39 @@
 ================================================================================
 
   This library (libraries/base) is derived from code from two
-  sources: 
-  
+  sources:
+
     * Code from the GHC project which is largely (c) The University of
       Glasgow, and distributable under a BSD-style license (see below),
-  
+
     * Code from the Haskell 98 Report which is (c) Simon Peyton Jones
       and freely redistributable (but see the full license for
       restrictions).
-  
+
   The full text of these licenses is reproduced below.  Both of the
   licenses are BSD-style or compatible.
-  
+
   -----------------------------------------------------------------------------
-  
+
   The Glasgow Haskell Compiler License
-  
-  Copyright 2004, The University Court of the University of Glasgow. 
+
+  Copyright 2004, The University Court of the University of Glasgow.
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
   - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
-   
+
   - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
-   
+
   - Neither name of the University nor the names of its contributors may be
   used to endorse or promote products derived from this software without
-  specific prior written permission. 
-  
+  specific prior written permission.
+
   THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF
   GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
@@ -445,14 +445,14 @@
   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
   DAMAGE.
-  
+
   -----------------------------------------------------------------------------
-  
+
   Code derived from the document "Report on the Programming Language
   Haskell 98", is distributed under the following license:
-  
+
     Copyright (c) 2002 Simon Peyton Jones
-  
+
     The authors intend this Report to belong to the entire Haskell
     community, and so we grant permission to copy and distribute it for
     any purpose, provided that it is reproduced in its entirety,
@@ -460,7 +460,7 @@
     copied and distributed for any purpose, provided that the modified
     version is clearly presented as such, and that it does not claim to
     be a definition of the Haskell 98 Language.
-  
+
   -----------------------------------------------------------------------------
 
 ================================================================================
@@ -469,23 +469,23 @@
 
   Copyright Neil Mitchell 2005-2018.
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are
   met:
-  
+
       * Redistributions of source code must retain the above copyright
         notice, this list of conditions and the following disclaimer.
-  
+
       * Redistributions in binary form must reproduce the above
         copyright notice, this list of conditions and the following
         disclaimer in the documentation and/or other materials provided
         with the distribution.
-  
+
       * Neither the name of Neil Mitchell nor the names of other
         contributors may be used to endorse or promote products derived
         from this software without specific prior written permission.
-  
+
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@@ -503,39 +503,39 @@
 ================================================================================
 
   This library (libraries/ghc-prim) is derived from code from several
-  sources: 
-  
+  sources:
+
     * Code from the GHC project which is largely (c) The University of
       Glasgow, and distributable under a BSD-style license (see below),
-  
+
     * Code from the Haskell 98 Report which is (c) Simon Peyton Jones
       and freely redistributable (but see the full license for
       restrictions).
-  
+
   The full text of these licenses is reproduced below.  All of the
   licenses are BSD-style or compatible.
-  
+
   -----------------------------------------------------------------------------
-  
+
   The Glasgow Haskell Compiler License
-  
-  Copyright 2004, The University Court of the University of Glasgow. 
+
+  Copyright 2004, The University Court of the University of Glasgow.
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
   - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
-   
+
   - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
-   
+
   - Neither name of the University nor the names of its contributors may be
   used to endorse or promote products derived from this software without
-  specific prior written permission. 
-  
+  specific prior written permission.
+
   THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF
   GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
@@ -548,14 +548,14 @@
   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
   DAMAGE.
-  
+
   -----------------------------------------------------------------------------
-  
+
   Code derived from the document "Report on the Programming Language
   Haskell 98", is distributed under the following license:
-  
+
     Copyright (c) 2002 Simon Peyton Jones
-  
+
     The authors intend this Report to belong to the entire Haskell
     community, and so we grant permission to copy and distribute it for
     any purpose, provided that it is reproduced in its entirety,
@@ -563,29 +563,29 @@
     copied and distributed for any purpose, provided that the modified
     version is clearly presented as such, and that it does not claim to
     be a definition of the Haskell 98 Language.
-  
 
+
 ================================================================================
-Dependency: githash-0.1.6.1
+Dependency: githash-0.1.6.2
 ================================================================================
 
   Copyright (c) 2018, Michael Snoyman, 2015, Adam C. Foltzer
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
   * Redistributions of source code must retain the above copyright notice, this
     list of conditions and the following disclaimer.
-  
+
   * Redistributions in binary form must reproduce the above copyright notice,
     this list of conditions and the following disclaimer in the documentation
     and/or other materials provided with the distribution.
-  
+
   * Neither the name of githash nor the names of its
     contributors may be used to endorse or promote products derived from
     this software without specific prior written permission.
-  
+
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@@ -603,23 +603,23 @@
 
   The Happy License
   -----------------
-  
+
   Copyright 2001, Simon Marlow and Andy Gill.  All rights reserved.
-  
+
   Extensions to implement Tomita's Generalized LR parsing:
     Copyright 2004, University of Durham, Paul Callaghan and Ben Medlock.
     All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
   - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
-   
+
   - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
-   
+
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY
   EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
@@ -637,24 +637,24 @@
 ================================================================================
 
   Copyright Milan Straka 2010
-  
+
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
       * Redistributions of source code must retain the above copyright
         notice, this list of conditions and the following disclaimer.
-  
+
       * Redistributions in binary form must reproduce the above
         copyright notice, this list of conditions and the following
         disclaimer in the documentation and/or other materials provided
         with the distribution.
-  
+
       * Neither the name of Milan Straka nor the names of other
         contributors may be used to endorse or promote products derived
         from this software without specific prior written permission.
-  
+
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@@ -672,24 +672,24 @@
 ================================================================================
 
   Copyright (c) 2014, Herbert Valerio Riedel
-  
+
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
       * Redistributions of source code must retain the above copyright
         notice, this list of conditions and the following disclaimer.
-  
+
       * Redistributions in binary form must reproduce the above
         copyright notice, this list of conditions and the following
         disclaimer in the documentation and/or other materials provided
         with the distribution.
-  
+
       * Neither the name of Herbert Valerio Riedel nor the names of other
         contributors may be used to endorse or promote products derived
         from this software without specific prior written permission.
-  
+
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@@ -707,24 +707,24 @@
 ================================================================================
 
   The Glasgow Haskell Compiler License
-  
+
   Copyright 2004, The University Court of the University of Glasgow.
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
   - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
-  
+
   - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
-  
+
   - Neither name of the University nor the names of its contributors may be
   used to endorse or promote products derived from this software without
   specific prior written permission.
-  
+
   THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF
   GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
@@ -745,28 +745,28 @@
   This library (libraries/pretty) is derived from code from
   the GHC project which is largely (c) The University of
   Glasgow, and distributable under a BSD-style license (see below).
-  
+
   -----------------------------------------------------------------------------
-  
+
   The Glasgow Haskell Compiler License
-  
-  Copyright 2004, The University Court of the University of Glasgow. 
+
+  Copyright 2004, The University Court of the University of Glasgow.
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
   - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
-   
+
   - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
-   
+
   - Neither name of the University nor the names of its contributors may be
   used to endorse or promote products derived from this software without
-  specific prior written permission. 
-  
+  specific prior written permission.
+
   THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF
   GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
@@ -779,30 +779,30 @@
   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
   DAMAGE.
-  
+
   -----------------------------------------------------------------------------
 
 ================================================================================
-Dependency: primitive-0.7.1.0
+Dependency: primitive-0.7.3.0
 ================================================================================
 
   Copyright (c) 2008-2009, Roman Leshchinskiy
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
   - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
-   
+
   - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
-   
+
   - Neither name of the University nor the names of its contributors may be
   used to endorse or promote products derived from this software without
-  specific prior written permission. 
-  
+  specific prior written permission.
+
   THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF
   GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
@@ -815,46 +815,46 @@
   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
   DAMAGE.
-  
 
+
 ================================================================================
-Dependency: process-1.6.9.0
+Dependency: process-1.6.13.2
 ================================================================================
 
   This library (libraries/process) is derived from code from two
-  sources: 
-  
+  sources:
+
     * Code from the GHC project which is largely (c) The University of
       Glasgow, and distributable under a BSD-style license (see below),
-  
+
     * Code from the Haskell 98 Report which is (c) Simon Peyton Jones
       and freely redistributable (but see the full license for
       restrictions).
-  
+
   The full text of these licenses is reproduced below.  Both of the
   licenses are BSD-style or compatible.
-  
+
   -----------------------------------------------------------------------------
-  
+
   The Glasgow Haskell Compiler License
-  
-  Copyright 2004, The University Court of the University of Glasgow. 
+
+  Copyright 2004, The University Court of the University of Glasgow.
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
   - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
-   
+
   - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
-   
+
   - Neither name of the University nor the names of its contributors may be
   used to endorse or promote products derived from this software without
-  specific prior written permission. 
-  
+  specific prior written permission.
+
   THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF
   GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
@@ -867,14 +867,14 @@
   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
   DAMAGE.
-  
+
   -----------------------------------------------------------------------------
-  
+
   Code derived from the document "Report on the Programming Language
   Haskell 98", is distributed under the following license:
-  
+
     Copyright (c) 2002 Simon Peyton Jones
-  
+
     The authors intend this Report to belong to the entire Haskell
     community, and so we grant permission to copy and distribute it for
     any purpose, provided that it is reproduced in its entirety,
@@ -882,33 +882,33 @@
     copied and distributed for any purpose, provided that the modified
     version is clearly presented as such, and that it does not claim to
     be a definition of the Haskell 98 Language.
-  
+
   -----------------------------------------------------------------------------
 
 ================================================================================
 Dependency: template-haskell-2.16.0.0
 ================================================================================
 
-  
+
   The Glasgow Haskell Compiler License
-  
+
   Copyright 2002-2007, The University Court of the University of Glasgow.
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
   - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
-  
+
   - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
-  
+
   - Neither name of the University nor the names of its contributors may be
   used to endorse or promote products derived from this software without
   specific prior written permission.
-  
+
   THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF
   GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
@@ -921,27 +921,27 @@
   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
   DAMAGE.
-  
 
+
 ================================================================================
 Dependency: text-1.2.4.1
 ================================================================================
 
   Copyright (c) 2008-2009, Tom Harper
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:
-  
+
       * Redistributions of source code must retain the above copyright
         notice, this list of conditions and the following disclaimer.
-  
+
       * Redistributions in binary form must reproduce the above
         copyright notice, this list of conditions and the following
         disclaimer in the documentation and/or other materials provided
         with the distribution.
-  
+
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@@ -955,28 +955,28 @@
   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 ================================================================================
-Dependency: th-compat-0.1.2
+Dependency: th-compat-0.1.3
 ================================================================================
 
   Copyright (c) 2020, Ryan Scott
-  
+
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
       * Redistributions of source code must retain the above copyright
         notice, this list of conditions and the following disclaimer.
-  
+
       * Redistributions in binary form must reproduce the above
         copyright notice, this list of conditions and the following
         disclaimer in the documentation and/or other materials provided
         with the distribution.
-  
+
       * Neither the name of Ryan Scott nor the names of other
         contributors may be used to endorse or promote products derived
         from this software without specific prior written permission.
-  
+
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@@ -995,13 +995,13 @@
 
   TimeLib is Copyright (c) Ashley Yakeley, 2004-2014. All rights reserved.
   Certain sections are Copyright 2004, The University Court of the University of Glasgow. All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-  
+
   - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-  
+
   - Neither name of the copyright holders nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-  
+
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 ================================================================================
@@ -1009,24 +1009,24 @@
 ================================================================================
 
   The Glasgow Haskell Compiler License
-  
+
   Copyright 2004, The University Court of the University of Glasgow.
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
   - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
-  
+
   - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
-  
+
   - Neither name of the University nor the names of its contributors may be
   used to endorse or promote products derived from this software without
   specific prior written permission.
-  
+
   THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF
   GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
@@ -1045,24 +1045,24 @@
 ================================================================================
 
   The Glasgow Haskell Compiler License
-  
-  Copyright 2004, The University Court of the University of Glasgow. 
+
+  Copyright 2004, The University Court of the University of Glasgow.
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
   - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
-   
+
   - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
-   
+
   - Neither name of the University nor the names of its contributors may be
   used to endorse or promote products derived from this software without
-  specific prior written permission. 
-  
+  specific prior written permission.
+
   THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF
   GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
@@ -1077,26 +1077,26 @@
   DAMAGE.
 
 ================================================================================
-Dependency: vector-0.12.3.0
+Dependency: vector-0.12.3.1
 ================================================================================
 
   Copyright (c) 2008-2012, Roman Leshchinskiy
   All rights reserved.
-  
+
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
-  
+
   - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
-   
+
   - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
-   
+
   - Neither name of the University nor the names of its contributors may be
   used to endorse or promote products derived from this software without
-  specific prior written permission. 
-  
+  specific prior written permission.
+
   THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF
   GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
@@ -1109,5 +1109,5 @@
   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
   DAMAGE.
-  
+
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -94,8 +94,8 @@
      --skip-preprocessor    Disable preprocessor
 Conversion:
      --pass-through         Dump input without converting
-  -E --exclude=CONV         Exclude a particular conversion (always, assert,
-                            interface, or logic)
+  -E --exclude=CONV         Exclude a particular conversion (Always, Assert,
+                            Interface, Logic, or UnbasedUnsized)
   -v --verbose              Retain certain conversion artifacts
   -w --write=MODE/FILE      How to write output; default is 'stdout'; use
                             'adjacent' to create a .v file next to each input;
diff --git a/src/Convert.hs b/src/Convert.hs
--- a/src/Convert.hs
+++ b/src/Convert.hs
@@ -17,9 +17,12 @@
 import qualified Convert.BlockDecl
 import qualified Convert.Cast
 import qualified Convert.DimensionQuery
+import qualified Convert.DoWhile
 import qualified Convert.DuplicateGenvar
 import qualified Convert.EmptyArgs
 import qualified Convert.Enum
+import qualified Convert.EventEdge
+import qualified Convert.ExprAsgn
 import qualified Convert.ForAsgn
 import qualified Convert.Foreach
 import qualified Convert.FuncRet
@@ -64,6 +67,7 @@
     [ Convert.NamedBlock.convert
     , Convert.DuplicateGenvar.convert
     , Convert.AsgnOp.convert
+    , Convert.EmptyArgs.convert
     , Convert.FuncRet.convert
     , Convert.TFBlock.convert
     ]
@@ -76,7 +80,7 @@
     , Convert.Inside.convert
     , Convert.IntTypes.convert
     , Convert.MultiplePacked.convert
-    , Convert.UnbasedUnsized.convert
+    , selectExclude Job.UnbasedUnsized Convert.UnbasedUnsized.convert
     , Convert.Cast.convert
     , Convert.ParamType.convert
     , Convert.HierConst.convert
@@ -99,10 +103,12 @@
 initialPhases selectExclude =
     [ Convert.ForAsgn.convert
     , Convert.Jump.convert
+    , Convert.ExprAsgn.convert
     , Convert.KWArgs.convert
     , Convert.Unique.convert
+    , Convert.EventEdge.convert
     , Convert.LogOp.convert
-    , Convert.EmptyArgs.convert
+    , Convert.DoWhile.convert
     , Convert.Foreach.convert
     , Convert.FuncRoutine.convert
     , selectExclude Job.Assert Convert.Assertion.convert
diff --git a/src/Convert/AlwaysKW.hs b/src/Convert/AlwaysKW.hs
--- a/src/Convert/AlwaysKW.hs
+++ b/src/Convert/AlwaysKW.hs
@@ -3,24 +3,224 @@
  -
  - Conversion for `always_latch`, `always_comb`, and `always_ff`
  -
- - `always_latch` -> `always @*`
- - `always_comb` -> `always @*`
- - `always_ff` -> `always`
+ - `always_comb` and `always_latch` become `always @*`, or produce an explicit
+ - sensitivity list if they need to pick up sensitivities from the functions
+ - they call. `always_ff` simply becomes `always`.
+ -
+ - TODO: `always_comb` blocks do not run at time zero
  -}
 
 module Convert.AlwaysKW (convert) where
 
+import Control.Monad.State.Strict
+import Control.Monad.Writer.Strict
+import Data.Maybe (fromMaybe, mapMaybe)
+
+import Convert.Scoper
 import Convert.Traverse
 import Language.SystemVerilog.AST
 
 convert :: [AST] -> [AST]
-convert = map $ traverseDescriptions $ traverseModuleItems replaceAlwaysKW
+convert = map $ traverseDescriptions traverseDescription
 
-replaceAlwaysKW :: ModuleItem -> ModuleItem
-replaceAlwaysKW (AlwaysC AlwaysLatch stmt) =
-    AlwaysC Always $ Timing (Event SenseStar) stmt
-replaceAlwaysKW (AlwaysC AlwaysComb stmt) =
-    AlwaysC Always $ Timing (Event SenseStar) stmt
-replaceAlwaysKW (AlwaysC AlwaysFF stmt) =
-    AlwaysC Always stmt
-replaceAlwaysKW other = other
+traverseDescription :: Description -> Description
+traverseDescription description@Part{} =
+    evalState (evalScoperT $ scopePart op description) mempty
+    where op = traverseModuleItem >=> scoper
+traverseDescription description = description
+
+type SC = ScoperT Kind (State (Any, [Expr]))
+
+type PortDir = (Identifier, Direction)
+
+data Kind
+    = Const Expr
+    | Var
+    | Proc [Expr] [PortDir]
+    deriving Eq
+
+scoper :: ModuleItem -> SC ModuleItem
+scoper = scopeModuleItem traverseDecl return traverseGenItem traverseStmt
+
+-- track declarations and visit expressions they contain
+traverseDecl :: Decl -> SC Decl
+traverseDecl decl = do
+    case decl of
+        Param    s _ x e   -> do
+            -- handle references to local constants
+            e' <- if s == Localparam
+                    then scopeExpr e
+                    else return Nil
+            insertElem x $ Const e'
+        ParamType  _ x _   -> insertElem x $ Const Nil
+        Variable _ _ x _ _ -> do
+            -- don't let the second visit of a function or task overwrite the
+            -- Proc entry that was just generated
+            details <- lookupLocalIdentM x
+            case details of
+                Just (_, _, Proc{}) -> return ()
+                _ -> insertElem x Var
+        Net  _ _ _ _ x _ _ -> insertElem x Var
+        CommentDecl{} -> return ()
+    traverseDeclExprsM traverseExpr decl
+
+-- track expressions and subroutines in a statement
+traverseStmt :: Stmt -> SC Stmt
+traverseStmt (Subroutine expr args) =
+    traverseCall Subroutine expr args
+traverseStmt stmt = traverseStmtExprsM traverseExpr stmt
+
+-- visit tasks, functions, and always blocks in generate scopes
+traverseGenItem :: GenItem -> SC GenItem
+traverseGenItem (GenModuleItem item) =
+    traverseModuleItem item >>= return . GenModuleItem
+traverseGenItem other = return other
+
+-- identify variables referenced within an expression
+traverseExpr :: Expr -> SC Expr
+traverseExpr (Call expr args) =
+    traverseCall Call expr args
+traverseExpr expr = do
+    prefix <- embedScopes longestStaticPrefix expr
+    case prefix of
+        Just expr' -> push (Any False, [expr']) >> return expr
+        _ -> traverseSinglyNestedExprsM traverseExpr expr
+
+-- turn a reference to a variable into a canonicalized longest static prefix, if
+-- possible, per IEEE 1800-2017 Section 11.5.3
+longestStaticPrefix :: Scopes Kind -> Expr -> Maybe Expr
+longestStaticPrefix scopes expr@Ident{} =
+    asVar scopes expr
+longestStaticPrefix scopes (Range expr mode (l, r)) = do
+    expr' <- longestStaticPrefix scopes expr
+    l' <- asConst scopes l
+    r' <- asConst scopes r
+    Just $ Range expr' mode (l', r')
+longestStaticPrefix scopes (Bit expr idx) = do
+    expr' <- longestStaticPrefix scopes expr
+    idx' <- asConst scopes idx
+    Just $ Bit expr' idx'
+longestStaticPrefix scopes orig@(Dot expr field) =
+    case asVar scopes orig of
+        Just orig' -> Just orig'
+        _ -> do
+            expr' <- longestStaticPrefix scopes expr
+            Just $ Dot expr' field
+longestStaticPrefix _ _ =
+    Nothing
+
+-- lookup an expression as an outwardly-visible variable
+asVar :: Scopes Kind -> Expr -> Maybe Expr
+asVar scopes expr = do
+    (accesses, _, Var) <- lookupElem scopes expr
+    if visible accesses
+        then Just $ accessesToExpr accesses
+        else Nothing
+
+visible :: [Access] -> Bool
+visible = not . elem (Access "" Nil)
+
+-- lookup an expression as a hoist-able constant
+asConst :: Scopes Kind -> Expr -> Maybe Expr
+asConst scopes expr =
+    case runWriter $ asConstRaw scopes expr of
+        (expr', Any False) -> Just expr'
+        _ -> Nothing
+
+asConstRaw :: Scopes Kind -> Expr -> Writer Any Expr
+asConstRaw scopes expr =
+    case lookupElem scopes expr of
+        Just (_, _, Const Nil) -> recurse
+        Just (_, _, Const expr') -> asConstRaw scopes expr'
+        Just{} -> tell (Any True) >> return Nil
+        Nothing -> recurse
+    where
+        recurse = traverseSinglyNestedExprsM (asConstRaw scopes) expr
+
+-- special handling for subroutine invocations and function calls
+traverseCall :: (Expr -> Args -> a) -> Expr -> Args -> SC a
+traverseCall constructor expr args = do
+    details <- lookupElemM expr
+    expr' <- traverseExpr expr
+    args' <- case details of
+        Just (_, _, Proc exprs ps) -> do
+            when (not $ null exprs) $
+                push (Any True, exprs)
+            traverseArgs ps args
+        _ -> traverseArgs [] args
+    return $ constructor expr' args'
+
+-- treats output ports as assignment-like contexts
+traverseArgs :: [PortDir] -> Args -> SC Args
+traverseArgs ps (Args pnArgs kwArgs) = do
+    pnArgs' <- zipWithM usingPN [0..] pnArgs
+    kwArgs' <- mapM usingKW kwArgs
+    return (Args pnArgs' kwArgs')
+    where
+
+        usingPN :: Int -> Expr -> SC Expr
+        usingPN key val = do
+            if dir == Output
+                then return val
+                else traverseExpr val
+            where dir = if key < length ps
+                            then snd $ ps !! key
+                            else Input
+
+        usingKW :: (Identifier, Expr) -> SC (Identifier, Expr)
+        usingKW (key, val) = do
+            val' <- if dir == Output
+                        then return val
+                        else traverseExpr val
+            return (key, val')
+            where dir = fromMaybe Input $ lookup key ps
+
+-- append to the non-local expression state
+push :: (Any, [Expr]) -> SC ()
+push x = lift $ modify' (x <>)
+
+-- custom traversal which converts SystemVerilog `always` keywords and tracks
+-- information about task and functions
+traverseModuleItem :: ModuleItem -> SC ModuleItem
+traverseModuleItem (AlwaysC AlwaysLatch stmt) = do
+    e <- fmap toEvent $ findNonLocals $ Initial stmt
+    return $ AlwaysC Always $ Timing (Event e) stmt
+traverseModuleItem (AlwaysC AlwaysComb stmt) = do
+    e <- fmap toEvent $ findNonLocals $ Initial stmt
+    return $ AlwaysC Always $ Timing (Event e) stmt
+traverseModuleItem (AlwaysC AlwaysFF stmt) =
+    return $ AlwaysC Always stmt
+traverseModuleItem item@(MIPackageItem (Function _ _ x decls _)) = do
+    (_, s) <- findNonLocals item
+    insertElem x $ Proc s (ports decls)
+    return item
+traverseModuleItem item@(MIPackageItem (Task _ x decls _)) = do
+    insertElem x $ Proc [] (ports decls)
+    return item
+traverseModuleItem other = return other
+
+toEvent :: (Bool, [Expr]) -> Event
+toEvent (False, _) = EventStar
+toEvent (True, exprs) =
+    EventExpr $ foldl1 EventExprOr $ map (EventExprEdge NoEdge) exprs
+
+-- turn a list of port declarations into a port direction map
+ports :: [Decl] -> [PortDir]
+ports = filter ((/= Local) . snd) . map port
+
+port :: Decl -> PortDir
+port (Variable d _ x _ _) = (x, d)
+port (Net  d _ _ _ x _ _) = (x, d)
+port _ = ("", Local)
+
+-- get a list of non-local variables referenced within a module item, and
+-- whether or not this module item references any functions which themselves
+-- reference non-local variables
+findNonLocals :: ModuleItem -> SC (Bool, [Expr])
+findNonLocals item = do
+    scopes <- get
+    lift $ put mempty
+    _ <- scoper item
+    (anys, exprs) <- lift get
+    let nonLocals = mapMaybe (longestStaticPrefix scopes) exprs
+    return (getAny anys, nonLocals)
diff --git a/src/Convert/Cast.hs b/src/Convert/Cast.hs
--- a/src/Convert/Cast.hs
+++ b/src/Convert/Cast.hs
@@ -115,7 +115,8 @@
     where
         maybeInt = numberToInteger size
         Just int = maybeInt
-        illegal s = error $ "size cast width " ++ show size ++ " is not " ++ s
+        illegal = scopedErrorM . msg
+        msg s = "size cast width " ++ show size ++ " is not " ++ s
 convertCastM (Number size) (Number value) signed =
     return $ Number $
         numberCast signed (fromIntegral size') value
diff --git a/src/Convert/DoWhile.hs b/src/Convert/DoWhile.hs
new file mode 100644
--- /dev/null
+++ b/src/Convert/DoWhile.hs
@@ -0,0 +1,31 @@
+{- sv2v
+ - Author: Zachary Snow <zach@zachjs.com>
+ -
+ - Conversion for `do` `while` loops.
+ -
+ - These are converted into while loops with an extra condition which is
+ - initially true and immediately set to false in the body. This strategy is
+ - preferrable to simply duplicating the loop body as it could contain jumps.
+ -}
+
+module Convert.DoWhile (convert) where
+
+import Convert.Traverse
+import Language.SystemVerilog.AST
+
+convert :: [AST] -> [AST]
+convert =
+    map $ traverseDescriptions $ traverseModuleItems $
+    traverseStmts $ traverseNestedStmts convertStmt
+
+convertStmt :: Stmt -> Stmt
+convertStmt (DoWhile cond body) =
+    Block Seq "" [decl] [While cond' body']
+    where
+        ident = "sv2v_do_while"
+        typ = IntegerVector TLogic Unspecified []
+        decl = Variable Local typ ident [] (RawNum 1)
+        cond' = BinOp LogOr (Ident ident) cond
+        asgn = Asgn AsgnOpEq Nothing (LHSIdent ident) (RawNum 0)
+        body' = Block Seq "" [] [asgn, body]
+convertStmt other = other
diff --git a/src/Convert/EmptyArgs.hs b/src/Convert/EmptyArgs.hs
--- a/src/Convert/EmptyArgs.hs
+++ b/src/Convert/EmptyArgs.hs
@@ -8,48 +8,77 @@
 
 module Convert.EmptyArgs (convert) where
 
-import Control.Monad.Writer.Strict
-import qualified Data.Set as Set
-
+import Convert.Scoper
 import Convert.Traverse
 import Language.SystemVerilog.AST
 
-type Idents = Set.Set Identifier
+type SC = Scoper ()
 
 convert :: [AST] -> [AST]
-convert = map $ traverseDescriptions convertDescription
+convert = map $ traverseDescriptions traverseDescription
 
-convertDescription :: Description -> Description
-convertDescription description@Part{} =
-    traverseModuleItems
-        (traverseExprs $ traverseNestedExprs $ convertExpr functions)
-        description'
-    where
-        (description', functions) =
-            runWriter $ traverseModuleItemsM traverseFunctionsM description
-convertDescription other = other
+traverseDescription :: Description -> Description
+traverseDescription =
+    evalScoper . scopePart scoper .
+    traverseModuleItems addDummyArg
+    where scoper = scopeModuleItem
+            traverseDecl traverseModuleItem traverseGenItem traverseStmt
 
-traverseFunctionsM :: ModuleItem -> Writer Idents ModuleItem
-traverseFunctionsM (MIPackageItem (Function ml t f decls stmts)) = do
-    decls' <-
-        if any isInput decls
-            then return decls
-            else do
-                tell $ Set.singleton f
-                return $ dummyDecl : decls
-    return $ MIPackageItem $ Function ml t f decls' stmts
-    where
-        dummyType = IntegerVector TReg Unspecified []
-        dummyDecl = Variable Input dummyType "_sv2v_unused" [] Nil
-        isInput :: Decl -> Bool
-        isInput (Variable Input _ _ _ _) = True
-        isInput _ = False
-traverseFunctionsM other = return other
+-- add a dummy argument to functions with no input ports
+addDummyArg :: ModuleItem -> ModuleItem
+addDummyArg (MIPackageItem (Function l t f decls stmts))
+    | all (not . isInput) decls =
+        MIPackageItem $ Function l t f (dummyDecl : decls) stmts
+addDummyArg other = other
 
-convertExpr :: Idents -> Expr -> Expr
-convertExpr functions (Call (Ident func) (Args [] [])) =
-    Call (Ident func) (Args args [])
-    where args = if Set.member func functions
-            then [RawNum 0]
-            else []
-convertExpr _ other = other
+isInput :: Decl -> Bool
+isInput (Variable Input _ _ _ _) = True
+isInput _ = False
+
+-- write down all declarations so we can look up the dummy arg
+traverseDecl :: Decl -> SC Decl
+traverseDecl decl = do
+    decl' <- case decl of
+        Param    _ _ x _   -> insertElem x () >> return decl
+        ParamType  _ x _   -> insertElem x () >> return decl
+        Variable d t x a e -> do
+            insertElem x ()
+            -- new dummy args have a special name for idempotence
+            return $ if x == dummyIdent
+                then Variable d t dummyIdentFinal a e
+                else decl
+        Net  _ _ _ _ x _ _ -> insertElem x () >> return decl
+        CommentDecl{} -> return decl
+    traverseDeclExprsM traverseExpr decl'
+
+traverseModuleItem :: ModuleItem -> SC ModuleItem
+traverseModuleItem = traverseExprsM traverseExpr
+
+traverseGenItem :: GenItem -> SC GenItem
+traverseGenItem = traverseGenItemExprsM traverseExpr
+
+traverseStmt :: Stmt -> SC Stmt
+traverseStmt = traverseStmtExprsM traverseExpr
+
+-- pass a dummy value to functions which had no inputs
+traverseExpr :: Expr -> SC Expr
+traverseExpr (Call func (Args args [])) = do
+    details <- lookupElemM $ Dot func dummyIdent
+    let args' = if details /= Nothing
+                    then RawNum 0 : args
+                    else args
+    return $ Call func (Args args' [])
+traverseExpr expr =
+    traverseSinglyNestedExprsM traverseExpr expr
+
+dummyIdent :: Identifier
+dummyIdent = '?' : dummyIdentFinal
+
+dummyIdentFinal :: Identifier
+dummyIdentFinal = "_sv2v_unused"
+
+dummyType :: Type
+dummyType = IntegerVector TReg Unspecified []
+
+dummyDecl :: Decl
+dummyDecl = Variable Input dummyType dummyIdent [] Nil
diff --git a/src/Convert/Enum.hs b/src/Convert/Enum.hs
--- a/src/Convert/Enum.hs
+++ b/src/Convert/Enum.hs
@@ -94,7 +94,7 @@
     -- check for obviously duplicate values
     if noDuplicates
         then zipWithM_ insertEnumItem keys vals
-        else error $ "enum conversion has duplicate vals: "
+        else scopedErrorM $ "enum conversion has duplicate vals: "
                 ++ show (zip keys vals)
     where
         insertEnumItem :: Identifier -> Expr -> SC ()
diff --git a/src/Convert/EventEdge.hs b/src/Convert/EventEdge.hs
new file mode 100644
--- /dev/null
+++ b/src/Convert/EventEdge.hs
@@ -0,0 +1,41 @@
+{- sv2v
+ - Author: Zachary Snow <zach@zachjs.com>
+ -
+ - Conversion for `edge` sensitivity
+ -
+ - IEEE 1800-2017 Section 9.4.2 defines `edge` as either `posedge` or `negedge`.
+ - This does not convert senses in assertions as they are likely either removed
+ - or fully supported downstream.
+ -}
+
+module Convert.EventEdge (convert) where
+
+import Convert.Traverse
+import Language.SystemVerilog.AST
+
+convert :: [AST] -> [AST]
+convert =
+    map $ traverseDescriptions $ traverseModuleItems $ traverseStmts $
+        traverseNestedStmts convertStmt
+
+convertStmt :: Stmt -> Stmt
+convertStmt (Asgn op (Just timing) lhs expr) =
+    Asgn op (Just $ convertTiming timing) lhs expr
+convertStmt (Timing timing stmt) =
+    Timing (convertTiming timing) stmt
+convertStmt other = other
+
+convertTiming :: Timing -> Timing
+convertTiming (Event event) = Event $ convertEvent event
+convertTiming other = other
+
+convertEvent :: Event -> Event
+convertEvent EventStar = EventStar
+convertEvent (EventExpr e) = EventExpr $ convertEventExpr e
+
+convertEventExpr :: EventExpr -> EventExpr
+convertEventExpr (EventExprOr v1 v2) =
+    EventExprOr (convertEventExpr v1) (convertEventExpr v2)
+convertEventExpr (EventExprEdge Edge lhs) =
+    EventExprOr (EventExprEdge Posedge lhs) (EventExprEdge Negedge lhs)
+convertEventExpr other@EventExprEdge{} = other
diff --git a/src/Convert/ExprAsgn.hs b/src/Convert/ExprAsgn.hs
new file mode 100644
--- /dev/null
+++ b/src/Convert/ExprAsgn.hs
@@ -0,0 +1,113 @@
+{-# LANGUAGE PatternSynonyms #-}
+{- sv2v
+ - Author: Zachary Snow <zach@zachjs.com>
+ -
+ - Conversion for assignments within expressions
+ -
+ - IEEE 1800-2017 Section 11.3.6 states that assignments within expressions can
+ - only appear in procedural statements, though some tools support them in other
+ - contexts. We do not currently raise an error for assignments within
+ - expressions in unsupported contexts.
+ -
+ - Assignment expressions are replaced with the LHS, with the LHS being updated
+ - in a preceding statement. For post-increment operations, a pre-increment is
+ - performed and then the increment is reversed in the expression.
+ -
+ - This conversion occurs after the elaboration of `do` `while` loops and
+ - `foreach` loops, but before the elaboration of jumps and extra `for` loop
+ - initializations.
+ -}
+
+module Convert.ExprAsgn (convert) where
+
+import Control.Monad.Writer.Strict
+import Data.Bitraversable (bimapM)
+
+import Convert.Traverse
+import Language.SystemVerilog.AST
+
+convert :: [AST] -> [AST]
+convert = map $ traverseDescriptions $ traverseModuleItems $
+    traverseStmts convertStmt
+
+convertStmt :: Stmt -> Stmt
+
+-- assignment expressions in a loop guard
+convertStmt (While cond stmt) =
+    if null add
+        then While cond stmt'
+        else block $ add ++ [While cond' $ injectLoopIter add stmt']
+    where
+        (cond', add) = runWriter $ convertExpr cond
+        stmt' = convertStmt stmt
+
+-- assignment expressions can appear in any part of the for loop
+convertStmt (For inits cond incrs stmt) =
+    if null initsAdd && null condAdd && null incrsAdd
+        then For inits cond incrs stmt'
+        else For initsCombined cond' incrs' $
+            injectLoopIter (incrsAdd ++ condAdd) stmt'
+    where
+        (inits', initsAdd) = runWriter $ mapM convertInit inits
+        (cond', condAdd) = runWriter $ convertExpr cond
+        (incrs', incrsAdd) = runWriter $ mapM convertIncr incrs
+        stmt' = convertStmt stmt
+        initsCombined = map toInit initsAdd ++ inits' ++ map toInit condAdd
+
+-- assignment expressions in other statements are added in front
+convertStmt stmt =
+    traverseSinglyNestedStmts convertStmt $
+        if null add
+            then stmt
+            else block $ add ++ [stmt']
+    where (stmt', add) = runWriter $ traverseStmtExprsM convertExpr stmt
+
+-- helper for creating simple blocks
+block :: [Stmt] -> Stmt
+block = Block Seq "" []
+
+-- add statements before the loop guard is checked
+injectLoopIter :: [Stmt] -> Stmt -> Stmt
+injectLoopIter add stmt = block $ beforeContinue add stmt : add
+
+-- add statements before every `continue` in the loop
+beforeContinue :: [Stmt] -> Stmt -> Stmt
+beforeContinue _ stmt@While{} = stmt
+beforeContinue _ stmt@For{} = stmt
+beforeContinue add Continue =
+    block $ add ++ [Continue]
+beforeContinue add stmt =
+    traverseSinglyNestedStmts (beforeContinue add) stmt
+
+-- reversible pattern to unwrap in for loops
+pattern AsgnStmt :: LHS -> Expr -> Stmt
+pattern AsgnStmt lhs expr = Asgn AsgnOpEq Nothing lhs expr
+
+toInit :: Stmt -> (LHS, Expr)
+toInit stmt = (lhs, expr)
+    where AsgnStmt lhs expr = stmt
+
+-- functions which convert and collect assignment expressions
+type Converter t = t -> Writer [Stmt] t
+
+convertExpr :: Converter Expr
+convertExpr (ExprAsgn l r) = do
+    l' <- convertExpr l
+    r' <- convertExpr r
+    let Just lhs = exprToLHS l' -- checked by parser
+    tell [AsgnStmt lhs r']
+    return l'
+convertExpr expr =
+    traverseSinglyNestedExprsM convertExpr expr
+
+convertLHS :: Converter LHS
+convertLHS = traverseNestedLHSsM $ traverseLHSExprsM convertExpr
+
+convertInit :: Converter (LHS, Expr)
+convertInit = bimapM convertLHS convertExpr
+
+convertIncr :: Converter (LHS, AsgnOp, Expr)
+convertIncr (lhs, op, expr) = do
+    lhs' <- convertLHS lhs
+    expr' <- convertExpr expr
+    return (lhs', op, expr')
diff --git a/src/Convert/ExprUtils.hs b/src/Convert/ExprUtils.hs
--- a/src/Convert/ExprUtils.hs
+++ b/src/Convert/ExprUtils.hs
@@ -76,14 +76,10 @@
 
 simplifyBinOp :: BinOp -> Expr -> Expr -> Expr
 
-simplifyBinOp Add (Dec 0) e = e
-simplifyBinOp Add e (Dec 0) = e
 simplifyBinOp Sub e (Dec 0) = e
 simplifyBinOp Sub (Dec 0) e = UniOp UniSub e
 simplifyBinOp Mul (Dec 0) _ = toDec 0
-simplifyBinOp Mul (Dec 1) e = e
 simplifyBinOp Mul _ (Dec 0) = toDec 0
-simplifyBinOp Mul e (Dec 1) = e
 simplifyBinOp Mod _ (Dec 1) = toDec 0
 
 simplifyBinOp Add e1 (UniOp UniSub e2) = BinOp Sub e1 e2
diff --git a/src/Convert/FuncRet.hs b/src/Convert/FuncRet.hs
--- a/src/Convert/FuncRet.hs
+++ b/src/Convert/FuncRet.hs
@@ -1,7 +1,8 @@
 {- sv2v
  - Author: Zachary Snow <zach@zachjs.com>
  -
- - Conversion which makes function `logic` and `reg` return types implicit
+ - Conversion which makes function `logic` and `reg` return types implicit and
+ - converts `void` functions to tasks
  -
  - Verilog-2005 restricts function return types to `integer`, `real`,
  - `realtime`, `time`, and implicit signed/dimensioned types.
@@ -16,6 +17,8 @@
 convert = map $ traverseDescriptions $ traverseModuleItems convertFunction
 
 convertFunction :: ModuleItem -> ModuleItem
+convertFunction (MIPackageItem (Function ml Void f decls stmts)) =
+    MIPackageItem $ Task ml f decls stmts
 convertFunction (MIPackageItem (Function ml t f decls stmts)) =
     MIPackageItem $ Function ml t' f decls stmts
     where
diff --git a/src/Convert/FuncRoutine.hs b/src/Convert/FuncRoutine.hs
--- a/src/Convert/FuncRoutine.hs
+++ b/src/Convert/FuncRoutine.hs
@@ -32,6 +32,7 @@
 convertDescription other = other
 
 collectFunctionsM :: ModuleItem -> Writer Idents ()
+collectFunctionsM (MIPackageItem (Function _ Void _ _ _)) = return ()
 collectFunctionsM (MIPackageItem (Function _ _ f _ _)) =
     tell $ Set.singleton f
 collectFunctionsM _ = return ()
diff --git a/src/Convert/Interface.hs b/src/Convert/Interface.hs
--- a/src/Convert/Interface.hs
+++ b/src/Convert/Interface.hs
@@ -7,6 +7,7 @@
 
 module Convert.Interface (convert) where
 
+import Data.List (intercalate, (\\))
 import Data.Maybe (isJust, isNothing, mapMaybe)
 import Control.Monad.Writer.Strict
 import qualified Data.Map.Strict as Map
@@ -57,7 +58,7 @@
 convertDescription _ (Part _ _ Interface _ name _ _) =
     PackageItem $ Decl $ CommentDecl $ "removed interface: " ++ name
 convertDescription parts (Part attrs extern Module lifetime name ports items) =
-    if null $ extractModportInstances $ PartInfo Module ports items then
+    if null $ extractModportInstances name $ PartInfo Module ports items then
         Part attrs extern Module lifetime name ports items'
     else
         PackageItem $ Decl $ CommentDecl $
@@ -99,12 +100,12 @@
             else do
                 -- inline instantiation of a module
                 let modportBindings = getModportBindings modports
-                if length modportInstances /= length modportBindings
-                    then
-                        error $ "instance " ++ instanceName ++ " of " ++ part
-                            ++ " has interface ports "
-                            ++ showKeys modportInstances ++ ", but only "
-                            ++ showKeys modportBindings ++ " are connected"
+                let unconnected = map fst modportInstances \\
+                                    map fst modportBindings
+                if not (null unconnected)
+                    then scopedErrorM $ "instance " ++ instanceName ++ " of "
+                            ++ part ++ " has unconnected interface ports: "
+                            ++ intercalate ", " unconnected
                     else scoper $ Generate $ map GenModuleItem $
                             inlineInstance modports rs modportBindings partItems
                             part instanceName paramBindings portBindings
@@ -115,12 +116,11 @@
                 Just partInfo = maybePartInfo
                 PartInfo partKind _ partItems = partInfo
 
-                modportInstances = extractModportInstances partInfo
+                modportInstances = extractModportInstances part partInfo
                 getModportBindings modports = mapMaybe
                     (inferModportBinding modports modportInstances) $
                     map (second $ addImpliedSlice modports) portBindings
                 second f = \(a, b) -> (a, f b)
-                showKeys = show . map fst
 
         traverseModuleItemM other = return other
 
@@ -195,8 +195,8 @@
                 -- given entire interface, but just bound to a modport
                 foundModport $ Dot expr modportName
             else if modportInstance /= Nothing then
-                error $ "could not resolve modport binding " ++ show expr
-                    ++ " for port " ++ portName ++ " of type "
+                scopedError modports $ "could not resolve modport binding "
+                    ++ show expr ++ " for port " ++ portName ++ " of type "
                     ++ showModportType interfaceName modportName
             else
                 Nothing
@@ -208,14 +208,15 @@
                 (interfaceName, modportName) =
                     case modportInstance of
                         Just x -> x
-                        Nothing -> error $ "can't deduce modport for interface "
-                            ++ show expr ++ " bound to port " ++ portName
+                        Nothing -> scopedError modports $
+                            "can't deduce modport for interface " ++ show expr
+                            ++ " bound to port " ++ portName
 
                 foundModport modportE =
                     if (null interfaceName || bInterfaceName == interfaceName)
                         && (null modportName || bModportName == modportName)
                         then Just (instanceE, qualifyModport modportE)
-                        else error msg
+                        else scopedError modports msg
                     where
                         bModportName =
                             case modportE of
@@ -246,8 +247,8 @@
                         Nothing ->
                             case lookupIntfElem modports (Dot e "") of
                                 Just (accesses, _, _) -> init accesses
-                                Nothing ->
-                                    error $ "could not find modport " ++ show e
+                                Nothing -> scopedError modports $
+                                    "could not find modport " ++ show e
 
         showModportType :: Identifier -> Identifier -> String
         showModportType "" "" = "generic interface"
@@ -271,23 +272,26 @@
                         e' = replaceInExpr replacements e
 
         -- association list of modport instances in the given module body
-        extractModportInstances :: PartInfo -> ModportInstances
-        extractModportInstances partInfo =
-            execWriter $ mapM (collectDeclsM collectDecl) (pItems partInfo)
+        extractModportInstances :: Identifier -> PartInfo -> ModportInstances
+        extractModportInstances part partInfo =
+            execWriter $ runScoperT $ scopeModuleItems collector part decls
             where
-                collectDecl :: Decl -> Writer ModportInstances ()
-                collectDecl (Variable _ t x _ _) =
+                collector = scopeModuleItem checkDecl return return return
+                decls = filter isDecl $ pItems partInfo
+                checkDecl :: Decl -> ScoperT () (Writer ModportInstances) Decl
+                checkDecl decl@(Variable _ t x _ _) =
                     if maybeInfo == Nothing then
-                        return ()
+                        return decl
                     else if elem x (pPorts partInfo) then
-                        tell [(x, info)]
+                        tell [(x, info)] >> return decl
                     else
-                        error $ "Modport not in port list: " ++ show (t, x)
+                        scopedErrorM $
+                            "Modport not in port list: " ++ show t ++ " " ++ x
                             ++ ". Is this an interface missing a port list?"
                     where
                         maybeInfo = extractModportInfo t
                         Just info = maybeInfo
-                collectDecl _ = return ()
+                checkDecl decl = return decl
 
         extractModportInfo :: Type -> Maybe (Identifier, Identifier)
         extractModportInfo (InterfaceT "" "" _) = Just ("", "")
@@ -309,6 +313,10 @@
 
 convertDescription _ other = other
 
+isDecl :: ModuleItem -> Bool
+isDecl (MIPackageItem Decl{}) = True
+isDecl _ = False
+
 -- produce the implicit modport decls for an interface bundle
 impliedModport :: [ModuleItem] -> [ModportDecl]
 impliedModport =
@@ -338,8 +346,10 @@
         items' = evalScoper $ scopeModuleItems scoper partName $
             map (traverseNestedModuleItems rewriteItem) $
             if null modportBindings
-                then items ++ [typeModport, dimensionModport, bundleModport]
-                else items
+                then itemsChecked ++ infoModports
+                else itemsChecked
+        itemsChecked = checkBeforeInline global partName items checkErrMsg
+        infoModports = [typeModport, dimensionModport, bundleModport]
         scoper = scopeModuleItem
             traverseDeclM traverseModuleItemM traverseGenItemM traverseStmtM
 
@@ -422,8 +432,8 @@
         -- LHSs are replaced using simple substitutions
         traverseLHSM :: LHS -> Scoper () LHS
         traverseLHSM =
-            embedScopes tagLHS >=>
-            embedScopes replaceLHS
+            fmap replaceLHS .
+            embedScopes tagLHS
         tagLHS :: Scopes () -> LHS -> LHS
         tagLHS scopes lhs
             | lookupElem scopes lhs /= Nothing =
@@ -438,25 +448,24 @@
                 then LHSDot scopedInstanceLHS y
                 else LHSDot (LHSIdent x) y
         renamePartLHS lhs = traverseSinglyNestedLHSs renamePartLHS lhs
-        replaceLHS :: Scopes () -> LHS -> LHS
-        replaceLHS _ (LHSDot lhs "@") = lhs
-        replaceLHS local (LHSDot (LHSBit lhs elt) field) =
+        replaceLHS :: LHS -> LHS
+        replaceLHS (LHSDot lhs "@") = lhs
+        replaceLHS (LHSDot (LHSBit lhs elt) field) =
             case lookup (LHSDot (LHSBit lhs Tag) field) lhsReplacements of
                 Just resolved -> replaceLHSArrTag elt resolved
-                Nothing -> LHSDot (replaceLHS local $ LHSBit lhs elt) field
-        replaceLHS local lhs =
+                Nothing -> LHSDot (replaceLHS $ LHSBit lhs elt) field
+        replaceLHS lhs =
             case lookup lhs lhsReplacements of
                 Just lhs' -> lhs'
-                Nothing -> checkExprResolution local (lhsToExpr lhs) $
-                    traverseSinglyNestedLHSs (replaceLHS local) lhs
+                Nothing -> traverseSinglyNestedLHSs replaceLHS lhs
         replaceLHSArrTag :: Expr -> LHS -> LHS
         replaceLHSArrTag =
             traverseNestedLHSs . (traverseLHSExprs . replaceArrTag)
         -- top-level expressions may be modports bound to other modports
         traverseExprM :: Expr -> Scoper () Expr
         traverseExprM =
-            embedScopes tagExpr >=>
-            embedScopes replaceExpr
+            fmap replaceExpr .
+            embedScopes tagExpr
         tagExpr :: Scopes () -> Expr -> Expr
         tagExpr scopes expr
             | lookupElem scopes expr /= Nothing =
@@ -471,38 +480,35 @@
                 then Dot scopedInstanceExpr y
                 else Dot (Ident x) y
         renamePartExpr expr = visitExprsStep renamePartExpr expr
-        replaceExpr :: Scopes () -> Expr -> Expr
-        replaceExpr _ (Dot expr "@") = expr
-        replaceExpr local (Ident x) =
+        replaceExpr :: Expr -> Expr
+        replaceExpr (Dot expr "@") = expr
+        replaceExpr (Ident x) =
             case lookup x modportBindings of
                 Just (_, m) -> m
-                Nothing -> checkExprResolution local (Ident x) (Ident x)
-        replaceExpr local expr =
-            replaceExpr' local expr
-        replaceExpr' :: Scopes () -> Expr -> Expr
-        replaceExpr' _ (Dot expr "@") = expr
-        replaceExpr' local (Dot (Bit expr elt) field) =
+                Nothing -> Ident x
+        replaceExpr expr =
+            replaceExpr' expr
+        replaceExpr' :: Expr -> Expr
+        replaceExpr' (Dot expr "@") = expr
+        replaceExpr' (Dot (Bit expr elt) field) =
             case lookup (Dot (Bit expr Tag) field) exprReplacements of
-                Just resolved -> replaceArrTag (replaceExpr' local elt) resolved
-                Nothing -> Dot (replaceExpr' local $ Bit expr elt) field
-        replaceExpr' local (Bit expr elt) =
+                Just resolved -> replaceArrTag (replaceExpr' elt) resolved
+                Nothing -> Dot (replaceExpr' $ Bit expr elt) field
+        replaceExpr' (Bit expr elt) =
             case lookup (Bit expr Tag) exprReplacements of
-                Just resolved -> replaceArrTag (replaceExpr' local elt) resolved
-                Nothing -> Bit (replaceExpr' local expr) (replaceExpr' local elt)
-        replaceExpr' local expr@(Dot Ident{} _) =
+                Just resolved -> replaceArrTag (replaceExpr' elt) resolved
+                Nothing -> Bit (replaceExpr' expr) (replaceExpr' elt)
+        replaceExpr' expr@(Dot Ident{} _) =
             case lookup expr exprReplacements of
                 Just expr' -> expr'
-                Nothing -> checkExprResolution local expr $
-                    visitExprsStep (replaceExprAny local) expr
-        replaceExpr' local (Ident x) =
-            checkExprResolution local (Ident x) (Ident x)
-        replaceExpr' local expr = replaceExprAny local expr
-        replaceExprAny :: Scopes () -> Expr -> Expr
-        replaceExprAny local expr =
+                Nothing -> visitExprsStep replaceExprAny expr
+        replaceExpr' (Ident x) = Ident x
+        replaceExpr' expr = replaceExprAny expr
+        replaceExprAny :: Expr -> Expr
+        replaceExprAny expr =
             case lookup expr exprReplacements of
                 Just expr' -> expr'
-                Nothing -> checkExprResolution local expr $
-                    visitExprsStep (replaceExpr' local) expr
+                Nothing -> visitExprsStep replaceExpr' expr
         replaceArrTag :: Expr -> Expr -> Expr
         replaceArrTag replacement Tag = replacement
         replaceArrTag replacement expr =
@@ -521,22 +527,11 @@
             . traverseExprTypes (traverseNestedTypes typeMapper)
             where typeMapper = traverseTypeExprs exprMapper
 
-        checkExprResolution :: Scopes () -> Expr -> a -> a
-        checkExprResolution local expr =
-            if not (exprResolves local expr) && exprResolves global expr
-                then
-                    error $ "inlining instance \"" ++ instanceName ++ "\" of "
-                        ++ inlineKind ++ " \"" ++ partName
-                        ++ "\" would make expression \"" ++ show expr
-                        ++ "\" used in \"" ++ instanceName
-                        ++ "\" resolvable when it wasn't previously"
-                else id
-
-        exprResolves :: Scopes a -> Expr -> Bool
-        exprResolves local (Ident x) =
-            isJust (lookupElem local x) || isLoopVar local x
-        exprResolves local expr =
-            isJust (lookupElem local expr)
+        checkErrMsg :: String -> String
+        checkErrMsg exprStr = "inlining instance \"" ++ instanceName
+            ++ "\" of " ++ inlineKind ++ " \"" ++ partName
+            ++ "\" would make expression \"" ++ exprStr ++ "\" used in \""
+            ++ instanceName ++ "\" resolvable when it wasn't previously"
 
         -- unambiguous reference to the current instance
         scopedInstanceRaw = accessesToExpr $ localAccesses global instanceName
@@ -715,3 +710,74 @@
 sliceLo NonIndexed (l, r) = endianCondExpr (l, r) r l
 sliceLo IndexedPlus (base, _) = base
 sliceLo IndexedMinus (base, len) = BinOp Add (BinOp Sub base len) (RawNum 1)
+
+-- check for cases where an expression in an inlined part only resolves after
+-- inlining, potentially hiding a design error
+checkBeforeInline :: Scopes a -> Identifier -> [ModuleItem]
+    -> (String -> String) -> [ModuleItem]
+checkBeforeInline global partName items checkErrMsg =
+    evalScoper $ scopeModuleItems scoper partName $ items
+    where
+        scoper = scopeModuleItem
+            checkDecl checkModuleItem checkGenItem checkStmt
+
+        checkDecl :: Decl -> Scoper () Decl
+        checkDecl decl = do
+            case decl of
+                Variable  _ _ x _ _ -> insertElem x ()
+                Net   _ _ _ _ x _ _ -> insertElem x ()
+                Param     _ _ x _   -> insertElem x ()
+                ParamType _   x _   -> insertElem x ()
+                CommentDecl{} -> return ()
+            traverseDeclExprsM checkExpr decl
+
+        checkModuleItem :: ModuleItem -> Scoper () ModuleItem
+        checkModuleItem item@(Instance _ _ x _ _) =
+            insertElem x () >> traverseExprsM checkExpr item
+        checkModuleItem item =
+            traverseExprsM checkExpr item >>=
+            traverseLHSsM  checkLHS
+
+        checkGenItem :: GenItem -> Scoper () GenItem
+        checkGenItem = traverseGenItemExprsM checkExpr
+
+        checkStmt :: Stmt -> Scoper () Stmt
+        checkStmt =
+            traverseStmtExprsM checkExpr >=>
+            traverseStmtLHSsM  checkLHS
+
+        checkExpr :: Expr -> Scoper () Expr
+        checkExpr = embedScopes checkExprResolutionId
+
+        checkLHS :: LHS -> Scoper () LHS
+        checkLHS = embedScopes checkLHSResolutionId
+
+        checkLHSResolutionId :: Scopes () -> LHS -> LHS
+        checkLHSResolutionId local lhs = checkExprResolution local expr lhs
+            where expr = lhsToExpr lhs
+
+        checkExprResolutionId :: Scopes () -> Expr -> Expr
+        checkExprResolutionId local expr = checkExprResolution local expr expr
+
+        -- error if the given expression resolves globally but not locally
+        checkExprResolution :: Scopes () -> Expr -> a -> a
+        checkExprResolution local expr =
+            if exprResolves global expr && not (anyPrefixResolves local expr)
+                then scopedError local $ checkErrMsg $ show expr
+                else id
+
+        -- check if hierarchical prefix of an expr exists in the given scope
+        anyPrefixResolves :: Scopes () -> Expr -> Bool
+        anyPrefixResolves local expr =
+            exprResolves local expr ||
+            case expr of
+                Dot inner _ -> anyPrefixResolves local inner
+                Bit inner _ -> anyPrefixResolves local inner
+                _ -> False
+
+        -- check if expr exists in the given scope
+        exprResolves :: Scopes a -> Expr -> Bool
+        exprResolves local (Ident x) =
+            isJust (lookupElem local x) || isLoopVar local x
+        exprResolves local expr =
+            isJust (lookupElem local expr)
diff --git a/src/Convert/Jump.hs b/src/Convert/Jump.hs
--- a/src/Convert/Jump.hs
+++ b/src/Convert/Jump.hs
@@ -121,12 +121,25 @@
 pattern SimpleLoopInits :: Identifier -> [(LHS, Expr)]
 pattern SimpleLoopInits var <- [(LHSIdent var, _)]
 
-pattern SimpleLoopGuard :: Identifier -> Expr
-pattern SimpleLoopGuard var <- BinOp _ (Ident var) _
-
 pattern SimpleLoopIncrs :: Identifier -> [(LHS, AsgnOp, Expr)]
 pattern SimpleLoopIncrs var <- [(LHSIdent var, _, _)]
 
+-- check if an expression contains a reference to the given identifier
+usesIdent :: Identifier -> Expr -> Writer Any ()
+usesIdent x (Ident y)
+    | x == y = tell $ Any True
+    | otherwise = return ()
+usesIdent x expr =
+    collectSinglyNestedExprsM (usesIdent x) expr
+
+-- identifies loops which could likely be statically unrolled, and so may
+-- benefit from avoiding complicating the loop guard with jump state, assuming
+-- the guard and incrementation do not have side effects
+simpleLoopVar :: [(LHS, Expr)] -> Expr -> [(LHS, AsgnOp, Expr)] -> Identifier
+simpleLoopVar (SimpleLoopInits var1) comp (SimpleLoopIncrs var3)
+    | var1 == var3, getAny $ execWriter $ usesIdent var1 comp = var1
+simpleLoopVar _ _ _ = ""
+
 -- rewrites the given statement, and returns the type of any unfinished jump
 convertStmt :: Stmt -> State Info Stmt
 
@@ -140,20 +153,12 @@
 
 convertStmt (Block Seq ""
     decls@[CommentDecl{}, Variable Local _ var0 [] Nil]
-    [ comment@CommentStmt{}
-    , For
-        inits@(SimpleLoopInits var1)
-        comp@(SimpleLoopGuard var2)
-        incr@(SimpleLoopIncrs var3)
-        stmt
-    ]) =
-    convertLoop localInfo loop comp incr stmt
+    [comment@CommentStmt{}, For inits comp incr stmt])
+    | var1@(_ : _) <- simpleLoopVar inits comp incr, var0 == var1 =
+    convertLoop (Just var1) loop comp incr stmt
         >>= return . Block Seq "" decls . (comment :) . pure
     where
         loop c i s = For inits c i s
-        localInfo = if var0 /= var1 || var1 /= var2 || var2 /= var3
-                        then Nothing
-                        else Just ""
 
 convertStmt (Block Seq x decls stmts) =
     step stmts >>= return . Block Seq x decls
@@ -192,25 +197,17 @@
     modify $ \s -> s { sHasJump = hasJump }
     return $ Case unique kw expr cases'
 
-convertStmt (For
-    inits@(SimpleLoopInits var1)
-    comp@(SimpleLoopGuard var2)
-    incr@(SimpleLoopIncrs var3) stmt) =
-    convertLoop localInfo loop comp incr stmt
+convertStmt (For inits comp incr stmt)
+    | var@(_ : _) <- simpleLoopVar inits comp incr =
+    convertLoop (Just var) loop comp incr stmt
     where
         loop c i s = For inits c i s
-        localInfo = if var1 /= var2 || var2 /= var3
-                        then Nothing
-                        else Just var1
 convertStmt (For inits comp incr stmt) =
     convertLoop Nothing loop comp incr stmt
     where loop c i s = For inits c i s
 convertStmt (While comp stmt) =
     convertLoop Nothing loop comp [] stmt
     where loop c _ s = While c s
-convertStmt (DoWhile comp stmt) =
-    convertLoop Nothing loop comp [] stmt
-    where loop c _ s = DoWhile c s
 
 convertStmt (Continue) = do
     loopDepth <- gets sLoopDepth
diff --git a/src/Convert/Logic.hs b/src/Convert/Logic.hs
--- a/src/Convert/Logic.hs
+++ b/src/Convert/Logic.hs
@@ -94,7 +94,7 @@
                         Just (_, _, t) -> tell [isRegType t]
                         _ -> tell [False]
 
-        always_comb = AlwaysC Always . Timing (Event SenseStar)
+        always_comb = AlwaysC Always . Timing (Event EventStar)
 
         fixModuleItem :: ModuleItem -> ModuleItem
         -- rewrite bad continuous assignments to use procedural assignments
@@ -141,12 +141,7 @@
                         items =
                             [ MIPackageItem $ Decl decl
                             , always_comb $ Asgn AsgnOpEq Nothing lhs tmpExpr]
-                        lhs = case exprToLHS expr of
-                            Just l -> l
-                            Nothing ->
-                                error $ "bad non-lhs, non-net expr "
-                                    ++ show expr ++ " connected to output port "
-                                    ++ portName ++ " of " ++ instanceName
+                        Just lhs = exprToLHS expr
                 maybeModulePorts = Map.lookup moduleName ports
         fixModuleItem other = other
 
@@ -188,9 +183,9 @@
 rewriteDeclM _ decl = return decl
 
 traverseStmtM :: Stmt -> ST Stmt
-traverseStmtM stmt@Timing{} =
+traverseStmtM (Asgn op Just{} lhs expr) =
     -- ignore the timing LHSs
-    return stmt
+    traverseStmtM $ Asgn op Nothing lhs expr
 traverseStmtM stmt@(Subroutine (Ident f) (Args (_ : Ident x : _) [])) =
     when (f == "$readmemh" || f == "$readmemb") (collectLHSM $ LHSIdent x)
         >> return stmt
diff --git a/src/Convert/MultiplePacked.hs b/src/Convert/MultiplePacked.hs
--- a/src/Convert/MultiplePacked.hs
+++ b/src/Convert/MultiplePacked.hs
@@ -5,7 +5,7 @@
  - Conversion for flattening variables with multiple packed dimensions
  -
  - This removes one packed dimension per identifier per pass. This works fine
- - because all conversions are repeatedly applied.
+ - because this conversion is repeatedly applied.
  -
  - We previously had a very complex conversion which used `generate` to make
  - flattened and unflattened versions of the array as necessary. This has now
@@ -28,6 +28,7 @@
 
 import Convert.ExprUtils
 import Control.Monad ((>=>))
+import Data.Bifunctor (first)
 import Data.Tuple (swap)
 import Data.Maybe (isJust)
 import qualified Data.Map.Strict as Map
@@ -59,31 +60,35 @@
     traverseDeclExprsM traverseExprM $ Param s t' ident e
 traverseDeclM other = traverseDeclExprsM traverseExprM other
 
+-- write down the given declaration and then flatten it
 traverseTypeM :: Type -> [Range] -> Identifier -> Scoper TypeInfo Type
 traverseTypeM t a ident = do
     tScoped <- scopeType t
     insertElem ident (tScoped, a)
-    t' <- case t of
-        Struct pk fields rs -> do
-            fields' <- flattenFields fields
-            return $ Struct pk fields' rs
-        Union  pk fields rs -> do
-            fields' <- flattenFields fields
-            return $ Union  pk fields' rs
-        _ -> return t
-    let (tf, rs) = typeRanges t'
-    if length rs <= 1
-        then return t'
-        else do
-            let r1 : r2 : rest = rs
-            let rs' = (combineRanges r1 r2) : rest
-            return $ tf rs'
+    return $ flattenType t
+
+-- flatten the innermost dimension of the given type, and any types it contains
+flattenType :: Type -> Type
+flattenType t =
+    tf $ if length ranges <= 1
+        then ranges
+        else rangesFlat
     where
-        flattenFields fields = do
-            let (fieldTypes, fieldNames) = unzip fields
-            fieldTypes' <- mapM (\x -> traverseTypeM x [] "") fieldTypes
-            return $ zip fieldTypes' fieldNames
+        (tf, ranges) = case t of
+            Struct pk fields rs ->
+                (Struct pk fields', rs)
+                where fields' = flattenFields fields
+            Union  pk fields rs ->
+                (Union  pk fields', rs)
+                where fields' = flattenFields fields
+            _ -> typeRanges t
+        r1 : r2 : rest = ranges
+        rangesFlat = combineRanges r1 r2 : rest
 
+-- flatten the types in a given list of struct/union fields
+flattenFields :: [Field] -> [Field]
+flattenFields = map $ first flattenType
+
 traverseModuleItemM :: ModuleItem -> Scoper TypeInfo ModuleItem
 traverseModuleItemM (Instance m p x rs l) = do
     -- converts multi-dimensional instances
@@ -118,9 +123,9 @@
             where
                 size1 = rangeSizeHiLo (s1, e1)
                 size2 = rangeSizeHiLo (s2, e2)
-                lower = BinOp Add e2 (BinOp Mul e1 size2)
-                upper = BinOp Add (BinOp Mul size1 size2)
-                            (BinOp Sub lower (RawNum 1))
+                lower = binOp Add e2 (binOp Mul e1 size2)
+                upper = binOp Add (binOp Mul size1 size2)
+                            (binOp Sub lower (RawNum 1))
 
 traverseStmtM :: Stmt -> Scoper TypeInfo Stmt
 traverseStmtM =
@@ -144,10 +149,8 @@
         traverseLHSSingleM lhs = do
             let expr = lhsToExpr lhs
             expr' <- convertExprM expr
-            case exprToLHS expr' of
-                Just lhs' -> return lhs'
-                Nothing -> error $ "multi-packed conversion created non-LHS from "
-                    ++ (show expr) ++ " to " ++ (show expr')
+            let Just lhs' = exprToLHS expr'
+            return lhs'
 
 convertExprM :: Expr -> Scoper TypeInfo Expr
 convertExprM = embedScopes convertExpr
@@ -158,9 +161,9 @@
     where
         -- removes the innermost dimensions of the given type information, and
         -- applies the given transformation to the expression
-        dropLevel :: (Expr -> Expr) -> (TypeInfo, Expr) -> (TypeInfo, Expr)
-        dropLevel nest ((t, a), expr) =
-            ((tf rs', a'), nest expr)
+        dropLevel :: TypeInfo -> TypeInfo
+        dropLevel (t, a) =
+            (tf rs', a')
             where
                 (tf, rs) = typeRanges t
                 (rs', a') = case (rs, a) of
@@ -168,48 +171,46 @@
                     (packed, []) -> (tail packed, [])
                     (packed, unpacked) -> (packed, tail unpacked)
 
-        -- given an expression, returns its type information and a tagged
-        -- version of the expression, if possible
-        levels :: Expr -> Maybe (TypeInfo, Expr)
+        -- given an expression, returns its type information, if possible
+        levels :: Expr -> Maybe TypeInfo
         levels (Bit expr a) =
             case levels expr of
-                Just info -> Just $ dropLevel (\expr' -> Bit expr' a) info
+                Just info -> Just $ dropLevel info
                 Nothing -> fallbackLevels $ Bit expr a
-        levels (Range expr a b) =
-            fmap (dropLevel $ \expr' -> Range expr' a b) (levels expr)
+        levels (Range expr _ _) =
+            fmap dropLevel $ levels expr
         levels (Dot expr x) =
             case levels expr of
-                Just ((Struct _ fields [], []), expr') -> dropDot fields expr'
-                Just ((Union  _ fields [], []), expr') -> dropDot fields expr'
+                Just (Struct _ fields [], []) -> dropDot fields
+                Just (Union  _ fields [], []) -> dropDot fields
                 _ -> fallbackLevels $ Dot expr x
             where
-                dropDot :: [Field] -> Expr -> Maybe (TypeInfo, Expr)
-                dropDot fields expr' =
+                dropDot :: [Field] -> Maybe TypeInfo
+                dropDot fields =
                     if Map.member x fieldMap
-                        then Just ((fieldType, []), Dot expr' x)
+                        then Just (fieldType, [])
                         else Nothing
                     where
                         fieldMap = Map.fromList $ map swap fields
                         fieldType = fieldMap Map.! x
         levels expr = fallbackLevels expr
 
-        fallbackLevels :: Expr -> Maybe (TypeInfo, Expr)
+        fallbackLevels :: Expr -> Maybe TypeInfo
         fallbackLevels expr =
-            fmap ((, expr) . thd3) res
+            fmap thd3 res
             where
                 res = lookupElem scopes expr
                 thd3 (_, _, c) = c
 
         -- given an expression, returns the two most significant (innermost,
-        -- leftmost) packed dimensions and a tagged version of the expression,
-        -- if possible
-        dims :: Expr -> Maybe (Range, Range, Expr)
+        -- leftmost) packed dimensions
+        dims :: Expr -> Maybe (Range, Range)
         dims expr =
             case levels expr of
-                Just ((t, []), expr') ->
+                Just (t, []) ->
                     case snd $ typeRanges t of
                         dimInner : dimOuter : _ ->
-                            Just (dimInner, dimOuter, expr')
+                            Just (dimInner, dimOuter)
                         _ -> Nothing
                 _ -> Nothing
 
@@ -219,31 +220,26 @@
         orientIdx r e =
             endianCondExpr r e eSwapped
             where
-                eSwapped = BinOp Sub (snd r) (BinOp Sub e (fst r))
+                eSwapped = binOp Sub (snd r) (binOp Sub e (fst r))
 
         -- Converted idents are prefixed with an invalid character to ensure
         -- that are not converted twice when the traversal steps downward. When
         -- the prefixed identifier is encountered at the lowest level, it is
         -- removed.
 
-        tag = ':'
-
         rewriteExpr :: Expr -> Expr
-        rewriteExpr (Ident x) =
-            if head x == tag
-                then Ident $ tail x
-                else Ident x
+        rewriteExpr expr@Ident{} = expr
         rewriteExpr orig@(Bit (Bit expr idxInner) idxOuter) =
             if isJust maybeDims && expr == rewriteExpr expr
-                then Bit expr' idx'
+                then Bit expr idx'
                 else rewriteExprLowPrec orig
             where
                 maybeDims = dims expr
-                Just (dimInner, dimOuter, expr') = maybeDims
+                Just (dimInner, dimOuter) = maybeDims
                 idxInner' = orientIdx dimInner idxInner
                 idxOuter' = orientIdx dimOuter idxOuter
-                base = BinOp Mul idxInner' (rangeSize dimOuter)
-                idx' = simplify $ BinOp Add base idxOuter'
+                base = binOp Mul idxInner' (rangeSize dimOuter)
+                idx' = simplify $ binOp Add base idxOuter'
         rewriteExpr orig@(Range (Bit expr idxInner) NonIndexed rangeOuter) =
             if isJust maybeDims && expr == rewriteExpr expr
                 then rewriteExpr $ Range exprOuter IndexedMinus range
@@ -252,44 +248,46 @@
                 maybeDims = dims expr
                 exprOuter = Bit expr idxInner
                 baseDec = fst rangeOuter
-                baseInc = BinOp Sub (BinOp Add baseDec len) (RawNum 1)
+                baseInc = binOp Sub (binOp Add baseDec len) (RawNum 1)
                 base = endianCondExpr rangeOuter baseDec baseInc
                 len = rangeSize rangeOuter
                 range = (base, len)
         rewriteExpr orig@(Range (Bit expr idxInner) modeOuter rangeOuter) =
             if isJust maybeDims && expr == rewriteExpr expr
-                then Range expr' modeOuter range'
+                then Range expr modeOuter range'
                 else rewriteExprLowPrec orig
             where
                 maybeDims = dims expr
-                Just (dimInner, dimOuter, expr') = maybeDims
+                Just (dimInner, dimOuter) = maybeDims
                 idxInner' = orientIdx dimInner idxInner
                 (baseOuter, lenOuter) = rangeOuter
                 baseOuter' = orientIdx dimOuter baseOuter
-                start = BinOp Mul idxInner' (rangeSize dimOuter)
-                baseDec = BinOp Add start baseOuter'
+                start = binOp Mul idxInner' (rangeSize dimOuter)
+                baseDec = binOp Add start baseOuter'
                 baseInc = if modeOuter == IndexedPlus
-                    then BinOp Add (BinOp Sub baseDec len) one
-                    else BinOp Sub (BinOp Add baseDec len) one
+                    then binOp Add (binOp Sub baseDec len) one
+                    else binOp Sub (binOp Add baseDec len) one
                 base = endianCondExpr dimOuter baseDec baseInc
                 len = lenOuter
                 range' = (base, len)
                 one = RawNum 1
+        rewriteExpr (Cast (Left t) expr) =
+            Cast (Left $ flattenType t) expr
         rewriteExpr other =
             rewriteExprLowPrec other
 
         rewriteExprLowPrec :: Expr -> Expr
         rewriteExprLowPrec orig@(Bit expr idx) =
             if isJust maybeDims && expr == rewriteExpr expr
-                then Range expr' mode' range'
+                then Range expr mode' range'
                 else orig
             where
                 maybeDims = dims expr
-                Just (dimInner, dimOuter, expr') = maybeDims
+                Just (dimInner, dimOuter) = maybeDims
                 mode' = IndexedPlus
                 idx' = orientIdx dimInner idx
                 len = rangeSize dimOuter
-                base = BinOp Add (endianCondExpr dimOuter (snd dimOuter) (fst dimOuter)) (BinOp Mul idx' len)
+                base = binOp Add (endianCondExpr dimOuter (snd dimOuter) (fst dimOuter)) (binOp Mul idx' len)
                 range' = (simplify base, simplify len)
         rewriteExprLowPrec orig@(Range expr NonIndexed range) =
             if isJust maybeDims && expr == rewriteExpr expr
@@ -298,34 +296,55 @@
             where
                 maybeDims = dims expr
                 baseDec = fst range
-                baseInc = BinOp Sub (BinOp Add baseDec len) (RawNum 1)
+                baseInc = binOp Sub (binOp Add baseDec len) (RawNum 1)
                 base = endianCondExpr range baseDec baseInc
                 len = rangeSize range
                 range' = (base, len)
         rewriteExprLowPrec orig@(Range expr mode range) =
             if isJust maybeDims && expr == rewriteExpr expr
-                then Range expr' mode' range'
+                then Range expr mode' range'
                 else orig
             where
                 maybeDims = dims expr
-                Just (dimInner, dimOuter, expr') = maybeDims
+                Just (dimInner, dimOuter) = maybeDims
                 sizeOuter = rangeSize dimOuter
                 offsetOuter = uncurry (endianCondExpr dimOuter) $ swap dimOuter
                 (baseOrig, lenOrig) = range
-                lenOrigMinusOne = BinOp Sub lenOrig (RawNum 1)
+                lenOrigMinusOne = binOp Sub lenOrig (RawNum 1)
                 baseSwapped =
                     orientIdx dimInner $
                     if mode == IndexedPlus
                         then
                             endianCondExpr dimInner
                             baseOrig
-                            (BinOp Add baseOrig lenOrigMinusOne)
+                            (binOp Add baseOrig lenOrigMinusOne)
                         else
                             endianCondExpr dimInner
-                            (BinOp Sub baseOrig lenOrigMinusOne)
+                            (binOp Sub baseOrig lenOrigMinusOne)
                             baseOrig
-                base = BinOp Add offsetOuter (BinOp Mul sizeOuter baseSwapped)
+                base = binOp Add offsetOuter (binOp Mul sizeOuter baseSwapped)
                 mode' = IndexedPlus
-                len = BinOp Mul sizeOuter lenOrig
+                len = binOp Mul sizeOuter lenOrig
                 range' = (base, len)
         rewriteExprLowPrec other = other
+
+-- Traditional identity operations like `+ 0` and `* 1` are not no-ops in
+-- SystemVerilog because they may implicitly extend the width of the other
+-- operand. Encouraged by the official language specifications (e.g., Section
+-- 11.6.2 of IEEE 1800-2017), these operations are used in real designs as
+-- workarounds for the standard expression evaluation semantics.
+--
+-- The process of flattening arrays in this conversion can naturally lead to
+-- unnecessary identity operations. Previously, `simplifyStep` was responsible
+-- for cleaning up the below unnecessary operations produced by this conversion,
+-- but it inadvertently changed the behavior of legitimate input designs.
+--
+-- Rather than applying these specific simplifications to all expressions, they
+-- are now only considered when constructing a new binary operation expression
+-- as part of array flattening.
+binOp :: BinOp -> Expr -> Expr -> Expr
+binOp Add (RawNum 0) e = e
+binOp Add e (RawNum 0) = e
+binOp Mul (RawNum 1) e = e
+binOp Mul e (RawNum 1) = e
+binOp op a b = BinOp op a b
diff --git a/src/Convert/Package.hs b/src/Convert/Package.hs
--- a/src/Convert/Package.hs
+++ b/src/Convert/Package.hs
@@ -26,7 +26,7 @@
 
 import Control.Monad.State.Strict
 import Control.Monad.Writer.Strict
-import Data.List (insert, intercalate)
+import Data.List (insert, intercalate, isPrefixOf)
 import Data.Maybe (mapMaybe)
 import qualified Data.Map.Strict as Map
 import qualified Data.Set as Set
@@ -74,7 +74,7 @@
 prefixItems :: Identifier -> [ModuleItem] -> [ModuleItem]
 prefixItems prefix items =
     snd $ evalState (processItems "" prefix items) initialState
-    where initialState = ([], Map.empty, Map.empty)
+    where initialState = PK [] Map.empty Map.empty Set.empty
 
 -- collect packages and global package items
 collectPackageM :: Description -> Writer (Packages, Classes, [PackageItem]) ()
@@ -107,14 +107,55 @@
 convertPackages files =
     (files', packages')
     where
-        (files', ([], packages', _)) = runState op ([], packages, classes)
+        (files', PK [] packages' _ _) = runState op initialState
+        initialState = PK [] packages classes conflicts
         op = mapM (traverseDescriptionsM traverseDescriptionM) files
         packages = Map.insert "" (Map.empty, globalItems) realPackages
         (realPackages, classes, globalItems) =
             execWriter $ mapM (collectDescriptionsM collectPackageM) files
+        prefixes = Set.union (Map.keysSet classes) (Map.keysSet realPackages)
+        conflicts =
+            if Set.null prefixes
+                then Set.empty
+                else execWriter $ mapM (collectIdentConflicts prefixes) files
 
-type PackagesState = State ([Identifier], Packages, Classes)
+-- write down identifiers that might conflict with the generated names for
+-- injected package items
+collectIdentConflicts :: Idents -> AST -> Writer Idents ()
+collectIdentConflicts prefixes =
+    mapM_ $ collectModuleItemsM collectModuleItem
+    where
+        collectModuleItem =
+            evalScoperT . scoper >=>
+            collectify traverseIdentsM ident
+        scoper = scopeModuleItem collectDecl return return return
+        collectDecl decl = do
+            case decl of
+                Variable _ _ x _ _ -> lift $ ident x
+                Net  _ _ _ _ x _ _ -> lift $ ident x
+                Param    _ _ x   _ -> lift $ ident x
+                ParamType  _ x   _ -> lift $ ident x
+                CommentDecl{} -> return ()
+            return decl
+        ident = collectIdent prefixes
 
+-- write down identifiers that have a package name as a prefix
+collectIdent :: Idents -> Identifier -> Writer Idents ()
+collectIdent prefixes ident =
+    case Set.lookupLE ident prefixes of
+        Just prefix -> when (prefix `isPrefixOf` ident) found
+            where found = tell $ Set.singleton ident
+        Nothing -> return ()
+
+data PK = PK
+    { pkStack :: [Identifier]
+    , pkPackages :: Packages
+    , pkClasses :: Classes
+    , pkConflicts :: Idents
+    }
+
+type PackagesState = State PK
+
 traverseDescriptionM :: Description -> PackagesState Description
 traverseDescriptionM (PackageItem item) = do
     return $ PackageItem $ case piNames item of
@@ -264,7 +305,7 @@
                     insertElem x Declared
                     if inProcedure || null packageName
                         then return x
-                        else return $ packageName ++ '_' : x
+                        else lift $ makeIdent packageName x
 
         -- check the global scope for declarations or imports
         resolveGlobalIdent :: Identifier -> Scope Identifier
@@ -293,13 +334,13 @@
                 Just ([_, _], _, Declared) ->
                     if null packageName
                         then return x
-                        else return $ packageName ++ '_' : x
+                        else lift $ makeIdent packageName x
                 Just (_, _, Declared) -> return x
                 Just (_, _, Imported rootPkg) ->
-                    return $ rootPkg ++ '_' : x
+                    lift $ makeIdent rootPkg x
                 Just (accesses, _, Available [rootPkg]) -> do
                     insertElem accesses $ Imported rootPkg
-                    return $ rootPkg ++ '_' : x
+                    lift $ makeIdent rootPkg x
                 Just (_, _, Available rootPkgs) ->
                     error $ "identifier " ++ show x
                         ++ " ambiguously refers to the definitions in any of "
@@ -404,9 +445,11 @@
                 lift $ resolvePSIdent p x
             else do
                 details <- lookupElemM $ Dot (Ident p) x
-                return $ case details of
-                    Just ([_, _], _, Declared) -> p ++ '_' : x
-                    Just ([_, _], _, Imported rootPkg) -> rootPkg ++ '_' : x
+                case details of
+                    Just ([_, _], _, Declared) ->
+                        lift $ makeIdent p x
+                    Just ([_, _], _, Imported rootPkg) ->
+                        lift $ makeIdent rootPkg x
                     _ -> error $ "package " ++ show p ++ " references"
                             ++ " undeclared local \"" ++ p ++ "::" ++ x ++ "\""
 
@@ -425,7 +468,7 @@
         -- inject the given class item and its dependencies into the local scope
         classScopeInject :: Identifier -> Identifier -> Scope ()
         classScopeInject rootPkg fullName = do
-            (_, packages, _) <- lift get
+            packages <- lift $ gets pkPackages
             let (_, packageItems) = packages Map.! rootPkg
             let localPIs = Map.fromList $ concatMap toPIElem packageItems
             mapM_ injectIfMissing $
@@ -451,7 +494,7 @@
 -- locate a package by name, processing its contents if necessary
 findPackage :: Identifier -> PackagesState Package
 findPackage packageName = do
-    (stack, packages, classes) <- get
+    PK { pkStack = stack, pkPackages = packages } <- get
     let maybePackage = Map.lookup packageName packages
     assertMsg (maybePackage /= Nothing) $
         "could not find package " ++ show packageName
@@ -465,10 +508,12 @@
     if Map.null exports
         then do
             -- process and resolve this package
-            put (packageName : stack, packages, classes)
+            modify' $ \pk -> pk { pkStack = packageName : pkStack pk }
             package' <- processPackage packageName $ snd package
-            (_, packages', _) <- get
-            put (stack, Map.insert packageName package' packages', classes)
+            pk <- get
+            let stack' = tail $ pkStack pk
+            let packages' = Map.insert packageName package' $ pkPackages pk
+            put $ pk { pkStack = stack', pkPackages = packages' }
             return package'
         else return package
 
@@ -490,11 +535,11 @@
 -- resolve a package scoped identifier to its unique global name
 resolvePSIdent :: Identifier -> Identifier -> PackagesState Identifier
 resolvePSIdent packageName itemName = do
-    (_, _, classes) <- get
+    classes <- gets pkClasses
     case Map.lookup packageName classes of
         Nothing -> do
             rootPkg <- resolveRootPackage packageName itemName
-            return $ rootPkg ++ '_' : itemName
+            makeIdent rootPkg itemName
         Just ([], _) -> resolveCSIdent packageName [] Set.empty itemName
         Just _ -> error $ "reference to " ++ show itemName
             ++ " in parameterized class " ++ show packageName
@@ -532,7 +577,7 @@
 resolveCSIdent :: Identifier -> [ParamBinding] -> Idents -> Identifier -> PackagesState Identifier
 resolveCSIdent className paramBindings scopeKeys itemName = do
     -- find the specified class
-    (_, _, classes) <- get
+    classes <- gets pkClasses
     let maybeClass = Map.lookup className classes
     assertMsg (maybeClass /= Nothing) $
         "could not find class " ++ show className
@@ -550,13 +595,13 @@
     let classItems'' = map overrider classItems'
     -- add the synthetic package to the state
     let package = (exports, classItems'')
-    (stack, packages, _) <- get
-    put (stack, Map.insert packageName package packages, classes)
+    packages' <- gets $ Map.insert packageName package . pkPackages
+    modify' $ \pk -> pk { pkPackages = packages' }
     -- ensure the item actually exists
     let maybeIdentState = Map.lookup itemName exports
     assertMsg (maybeIdentState /= Nothing) $
         "could not find " ++ show itemName ++ " in class " ++ show className
-    return $ packageName ++ '_' : itemName
+    makeIdent packageName itemName
     where
         extractParameterName :: Decl -> Maybe Identifier
         extractParameterName (Param Parameter _ x _) = Just x
@@ -600,6 +645,19 @@
             where x' = drop (1 + length packageName) x
         overrideParam _ _ other = other
 
+-- construct a new identifier for a package scoped identifier
+makeIdent :: Identifier -> Identifier -> PackagesState Identifier
+makeIdent x y = do
+    conflicts <- gets pkConflicts
+    return $ uniqueIdent conflicts $ x ++ '_' : y
+
+-- prepend underscores until the name is unique
+uniqueIdent :: Idents -> Identifier -> Identifier
+uniqueIdent conflicts ident =
+    if Set.member ident conflicts
+        then uniqueIdent conflicts $ '_' : ident
+        else ident
+
 -- errors with the given message when the check is false
 assertMsg :: Monad m => Bool -> String -> m ()
 assertMsg check msg = when (not check) $ error msg
@@ -652,7 +710,7 @@
 -- iteratively inserts missing package items exactly where they are needed
 addItems :: PIs -> Idents -> [(ModuleItem, Idents)] -> [ModuleItem]
 addItems pis existingPIs ((item, usedPIs) : items) =
-    if not $ Set.disjoint existingPIs thisPI then
+    if not $ forceKeep || Set.disjoint existingPIs thisPI then
         -- this item was re-imported earlier in the module
         addItems pis existingPIs items
     else if Map.null itemsToAdd then
@@ -666,7 +724,11 @@
         thisPI = case item of
             MIPackageItem packageItem ->
                 Set.fromList $ piNames packageItem
+            Instance _ _ x _ _ -> Set.singleton x
             _ -> Set.empty
+        forceKeep = case item of
+            Instance{} -> True
+            _ -> False
         neededPIs = Set.difference (Set.difference usedPIs existingPIs) thisPI
         itemsToAdd = Map.restrictKeys pis neededPIs
         (chosenName, chosenPI) = Map.findMin itemsToAdd
@@ -778,6 +840,8 @@
 piNames (Decl (Param    _ _ ident   _)) = [ident]
 piNames (Decl (ParamType  _ ident   _)) = [ident]
 piNames (Decl (CommentDecl          _)) = []
-piNames (Import x y) = [show $ Import x y]
-piNames (Export x y) = [show $ Export x y]
+piNames item@DPIImport{} = [show item]
+piNames item@DPIExport{} = [show item]
+piNames item@Import{} = [show item]
+piNames item@Export{} = [show item]
 piNames (Directive _) = []
diff --git a/src/Convert/RemoveComments.hs b/src/Convert/RemoveComments.hs
--- a/src/Convert/RemoveComments.hs
+++ b/src/Convert/RemoveComments.hs
@@ -39,6 +39,9 @@
     where
         decls' = convertDecls decls
         stmts' = convertStmts stmts
+convertPackageItem (DPIImport spec prop alias typ name decls) =
+    DPIImport spec prop alias typ name decls'
+    where decls' = convertDecls decls
 convertPackageItem other = other
 
 convertStmt :: Stmt -> Stmt
diff --git a/src/Convert/Scoper.hs b/src/Convert/Scoper.hs
--- a/src/Convert/Scoper.hs
+++ b/src/Convert/Scoper.hs
@@ -57,6 +57,8 @@
     , withinProcedureM
     , procedureLoc
     , procedureLocM
+    , scopedError
+    , scopedErrorM
     , isLoopVar
     , isLoopVarM
     , loopVarDepth
@@ -68,7 +70,7 @@
     ) where
 
 import Control.Monad.State.Strict
-import Data.List (findIndices, partition)
+import Data.List (findIndices, intercalate, isPrefixOf, partition)
 import Data.Maybe (isNothing)
 import qualified Data.Map.Strict as Map
 
@@ -105,6 +107,7 @@
     , sProcedureLoc :: [Access]
     , sInjectedItems :: [(Bool, ModuleItem)]
     , sInjectedDecls :: [Decl]
+    , sLatestTrace :: String
     } deriving Show
 
 extractMapping :: Scopes a -> Map.Map Identifier a
@@ -353,6 +356,26 @@
 procedureLoc :: Scopes a -> [Access]
 procedureLoc = sProcedureLoc
 
+debugLocation :: Scopes a -> String
+debugLocation s =
+    hierarchy ++
+    if null latestTrace
+        then " (use -v to get approximate source location)"
+        else ", near " ++ latestTrace
+    where
+        hierarchy = intercalate "." $ map tierToStr $ sCurrent s
+        latestTrace = sLatestTrace s
+        tierToStr :: Tier -> String
+        tierToStr (Tier "" _) = "<unnamed_block>"
+        tierToStr (Tier x "") = x
+        tierToStr (Tier x y) = x ++ '[' : y ++ "]"
+
+scopedErrorM :: Monad m => String -> ScoperT a m x
+scopedErrorM msg = get >>= flip scopedError msg
+
+scopedError :: Scopes a -> String -> x
+scopedError scopes = error . (++ ", within scope " ++ debugLocation scopes)
+
 isLoopVar :: Scopes a -> Identifier -> Bool
 isLoopVar scopes x = any matches $ sCurrent scopes
     where matches = (== x) . tierIndex
@@ -411,8 +434,11 @@
 runScoperT = flip runStateT initialState
 
 initialState :: Scopes a
-initialState = Scopes [] Map.empty [] [] []
+initialState = Scopes [] Map.empty [] [] [] ""
 
+tracePrefix :: String
+tracePrefix = "Trace: "
+
 scopeModuleItem
     :: forall a m. Monad m
     => MapperM (ScoperT a m) Decl
@@ -420,7 +446,7 @@
     -> MapperM (ScoperT a m) GenItem
     -> MapperM (ScoperT a m) Stmt
     -> MapperM (ScoperT a m) ModuleItem
-scopeModuleItem declMapper moduleItemMapper genItemMapper stmtMapper =
+scopeModuleItem declMapperRaw moduleItemMapper genItemMapper stmtMapperRaw =
     wrappedModuleItemMapper
     where
         fullStmtMapper :: Stmt -> ScoperT a m Stmt
@@ -438,6 +464,21 @@
                 then traverseSinglyNestedStmtsM fullStmtMapper stmt'
                 else fullStmtMapper $ Block Seq "" injected [stmt']
 
+        declMapper :: Decl -> ScoperT a m Decl
+        declMapper decl@(CommentDecl c) =
+            consumeComment c >> return decl
+        declMapper decl = declMapperRaw decl
+
+        stmtMapper :: Stmt -> ScoperT a m Stmt
+        stmtMapper stmt@(CommentStmt c) =
+            consumeComment c >> return stmt
+        stmtMapper stmt = stmtMapperRaw stmt
+
+        consumeComment :: String -> ScoperT a m ()
+        consumeComment c =
+            when (tracePrefix `isPrefixOf` c) $
+                modify' $ \s -> s { sLatestTrace = drop (length tracePrefix) c }
+
         -- converts a decl and adds decls injected during conversion
         declMapper' :: Decl -> ScoperT a m [Decl]
         declMapper' decl = do
@@ -479,7 +520,11 @@
         redirectTFDecl :: Type -> Identifier -> ScoperT a m (Type, Identifier)
         redirectTFDecl typ ident = do
             res <- declMapper $ Variable Local typ ident [] Nil
-            let Variable Local newType newName newRanges Nil = res
+            (newType, newName, newRanges) <-
+                return $ case res of
+                    Variable Local t x r Nil -> (t, x, r)
+                    Net Local TWire DefaultStrength t x r Nil -> (t, x, r)
+                    _ -> error "redirectTFDecl invariant violated"
             return $ if null newRanges
                 then (newType, newName)
                 else
@@ -516,6 +561,14 @@
             return $ MIPackageItem $ Task ml x' decls' stmts'
         fullModuleItemMapper (MIPackageItem (Decl decl)) =
             declMapper decl >>= return . MIPackageItem . Decl
+        fullModuleItemMapper (MIPackageItem item@DPIImport{}) = do
+            let DPIImport spec prop alias typ name decls = item
+            (typ', name') <- redirectTFDecl typ name
+            decls' <- mapM declMapper decls
+            let item' = DPIImport spec prop alias typ' name' decls'
+            return $ MIPackageItem item'
+        fullModuleItemMapper (MIPackageItem (DPIExport spec alias kw name)) =
+            return $ MIPackageItem $ DPIExport spec alias kw name
         fullModuleItemMapper (AlwaysC kw stmt) = do
             enterProcedure
             stmt' <- fullStmtMapper stmt
diff --git a/src/Convert/Struct.hs b/src/Convert/Struct.hs
--- a/src/Convert/Struct.hs
+++ b/src/Convert/Struct.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE PatternSynonyms #-}
 {-# LANGUAGE TupleSections #-}
 {- sv2v
  - Author: Zachary Snow <zach@zachjs.com>
@@ -8,6 +9,7 @@
 module Convert.Struct (convert) where
 
 import Control.Monad ((>=>), when)
+import Control.Monad.State.Strict (get)
 import Data.Either (isLeft)
 import Data.List (elemIndex, find, partition, (\\))
 import Data.Maybe (fromJust)
@@ -111,12 +113,14 @@
             let (tf, rs) = typeRanges t
             when (isRangeable t) $
                 scopeType (tf $ a ++ rs) >>= insertElem x
-            let e' = convertExpr t e
+            scopes <- get
+            let e' = convertExpr scopes t e
             let t' = convertType t
             return $ Variable d t' x a e'
         Param s t x e -> do
             scopeType t >>= insertElem x
-            let e' = convertExpr t e
+            scopes <- get
+            let e' = convertExpr scopes t e
             let t' = convertType t
             return $ Param s t' x e'
         _ -> return decl
@@ -182,24 +186,27 @@
     (typ, lhs') <- convertLHS lhs
     -- convert the RHS using the LHS type information, and then the innermost
     -- type information on the resulting RHS
-    (_, expr') <- embedScopes convertSubExpr $ convertExpr typ expr
+    scopes <- get
+    let (_, expr') =
+            convertSubExpr scopes $
+            convertExpr scopes typ expr
     return (lhs', expr')
 
 structIsntReady :: Type -> Bool
 structIsntReady = (Nothing ==) . convertStruct
 
 -- try expression conversion by looking at the *outermost* type first
-convertExpr :: Type -> Expr -> Expr
-convertExpr _ Nil = Nil
-convertExpr t (Mux c e1 e2) =
+convertExpr :: Scopes a -> Type -> Expr -> Expr
+convertExpr _ _ Nil = Nil
+convertExpr scopes t (Mux c e1 e2) =
     Mux c e1' e2'
     where
-        e1' = convertExpr t e1
-        e2' = convertExpr t e2
+        e1' = convertExpr scopes t e1
+        e2' = convertExpr scopes t e2
 
-convertExpr struct@(Struct _ fields []) (Pattern itemsOrig) =
+convertExpr scopes struct@(Struct _ fields []) (Pattern itemsOrig) =
     if not (null extraNames) then
-        error $ "pattern " ++ show (Pattern itemsOrig) ++
+        scopedError scopes $ "pattern " ++ show (Pattern itemsOrig) ++
             " has extra named fields " ++ show extraNames ++
             " that are not in " ++ show struct
     else if structIsntReady struct then
@@ -215,7 +222,8 @@
                 itemsOrig
             -- position-based patterns should cover every field
             else if length itemsOrig /= length fields then
-                error $ "struct pattern " ++ show (Pattern itemsOrig) ++
+                scopedError scopes $ "struct pattern " ++
+                    show (Pattern itemsOrig) ++
                     " doesn't have the same number of items as " ++ show struct
             -- if the pattern does not use identifiers, use the
             -- identifiers from the struct type definition in order
@@ -229,9 +237,9 @@
         isNumbered :: TypeOrExpr -> Bool
         isNumbered (Right (Number n)) =
             if maybeIndex == Nothing
-                then error msgNonInteger
+                then scopedError scopes msgNonInteger
                 else 0 <= index && index < length fieldNames
-                        || error msgOutOfBounds
+                        || scopedError scopes msgOutOfBounds
             where
                 maybeIndex = fmap fromIntegral $ numberToInteger n
                 Just index = maybeIndex
@@ -245,7 +253,7 @@
         right = \(Right x) -> x
         getName :: Expr -> Identifier
         getName (Ident x) = x
-        getName e = error $ "invalid pattern key " ++ show e
+        getName e = scopedError scopes $ "invalid pattern key " ++ show e
                         ++ " is not a type, field name, or index"
 
         items = zip
@@ -253,7 +261,7 @@
             (map resolveField fieldNames)
         resolveField :: Identifier -> Expr
         resolveField fieldName =
-            convertExpr fieldType $
+            convertExpr scopes fieldType $
             -- look up by name
             if valueByName /= Nothing then
                 fromJust valueByName
@@ -269,7 +277,7 @@
             else if valueByIndex /= Nothing then
                 fromJust valueByIndex
             else
-                error $ "couldn't find field '" ++ fieldName ++
+                scopedError scopes $ "couldn't find field '" ++ fieldName ++
                     "' from struct definition " ++ show struct ++
                     " in struct pattern " ++ show (Pattern itemsOrig)
             where
@@ -292,21 +300,21 @@
                         Just value = numberToInteger n
                         Right (Number n) = item
 
-convertExpr _ (Cast (Left t) expr) =
-    Cast (Left t') $ convertExpr t expr
+convertExpr scopes _ (Cast (Left t) expr) =
+    Cast (Left t') $ convertExpr scopes t expr
     where t' = convertType t
 
-convertExpr (Implicit _ []) expr =
-    traverseSinglyNestedExprs (convertExpr UnknownType) expr
-convertExpr (Implicit sg rs) expr =
-    convertExpr (IntegerVector TBit sg rs) expr
+convertExpr scopes (Implicit _ []) expr =
+    traverseSinglyNestedExprs (convertExpr scopes UnknownType) expr
+convertExpr scopes (Implicit sg rs) expr =
+    convertExpr scopes (IntegerVector TBit sg rs) expr
 
 -- TODO: This is a conversion for concat array literals with elements
 -- that are unsized numbers. This probably belongs somewhere else.
-convertExpr t@IntegerVector{} (Concat exprs) =
+convertExpr scopes t@IntegerVector{} (Concat exprs) =
     if all isUnsizedNumber exprs
         then Concat $ map (Cast $ Left t') exprs
-        else Concat $ map (convertExpr t') exprs
+        else Concat $ map (convertExpr scopes t') exprs
     where
         t' = dropInnerTypeRange t
         isUnsizedNumber :: Expr -> Bool
@@ -318,35 +326,35 @@
 
 -- TODO: This is really a conversion for using default patterns to
 -- populate arrays. Maybe this should be somewhere else?
-convertExpr t orig@(Pattern [(Left UnknownType, expr)]) =
+convertExpr scopes t orig@(Pattern [(Left UnknownType, expr)]) =
     if null rs
         then orig
         else Repeat count [expr']
     where
         count = rangeSize $ head rs
-        expr' = Cast (Left t') $ convertExpr t' expr
+        expr' = Cast (Left t') $ convertExpr scopes t' expr
         (_, rs) = typeRanges t
         t' = dropInnerTypeRange t
 
 -- pattern syntax used for simple array literals
-convertExpr t (Pattern items) =
+convertExpr scopes t (Pattern items) =
     if all (== Right Nil) names
-        then convertExpr t $ Concat exprs'
+        then convertExpr scopes t $ Concat exprs'
         else Pattern items
     where
         (names, exprs) = unzip items
         t' = dropInnerTypeRange t
-        exprs' = map (convertExpr t') exprs
+        exprs' = map (convertExpr scopes t') exprs
 
 -- propagate types through concatenation expressions
-convertExpr t (Concat exprs) =
+convertExpr scopes t (Concat exprs) =
     Concat exprs'
     where
         t' = dropInnerTypeRange t
-        exprs' = map (convertExpr t') exprs
+        exprs' = map (convertExpr scopes t') exprs
 
-convertExpr _ expr =
-    traverseSinglyNestedExprs (convertExpr UnknownType) expr
+convertExpr scopes _ expr =
+    traverseSinglyNestedExprs (convertExpr scopes UnknownType) expr
 
 fallbackType :: Scopes Type -> Expr -> (Type, Expr)
 fallbackType scopes e =
@@ -356,12 +364,19 @@
                 Nothing -> UnknownType
                 Just (_, _, typ) -> typ
 
+pattern MakeSigned :: Expr -> Expr
+pattern MakeSigned e = Call (Ident "$signed") (Args [e] [])
+
 -- converting LHSs by looking at the innermost types first
 convertLHS :: LHS -> Scoper Type (Type, LHS)
 convertLHS l = do
     let e = lhsToExpr l
     (t, e') <- embedScopes convertSubExpr e
-    let Just l' = exprToLHS e'
+    -- per IEEE 1800-2017 sections 10.7 and 11.8, the signedness of the LHS does
+    -- not affect the evaluation and sign-extension of the RHS
+    let Just l' = exprToLHS $ case e' of
+                        MakeSigned e'' -> e''
+                        _ -> e'
     return (t, l')
 
 -- try expression conversion by looking at the *innermost* type first
@@ -372,23 +387,32 @@
     else if structIsntReady subExprType then
         (fieldType, Dot e' x)
     else
-        (fieldType, undotted)
+        (fieldType, undottedWithSign)
     where
         (subExprType, e') = convertSubExpr scopes e
-        (fieldType, bounds, dims) = lookupFieldInfo subExprType x
+        (fieldType, bounds, dims) = lookupFieldInfo scopes subExprType e' x
         base = fst bounds
         len = rangeSize bounds
         undotted = if null dims || rangeSize (head dims) == RawNum 1
             then Bit e' (fst bounds)
             else Range e' IndexedMinus (base, len)
+        -- retain signedness of fields which would otherwise be lost via the
+        -- resulting bit or range selection
+        IntegerVector _ fieldSg _ = fieldType
+        undottedWithSign =
+            if fieldSg == Signed
+                then MakeSigned undotted
+                else undotted
+
 convertSubExpr scopes (Range (Dot e x) NonIndexed rOuter) =
     if isntStruct subExprType then
         (UnknownType, orig')
     else if structIsntReady subExprType then
         (replaceInnerTypeRange NonIndexed rOuter' fieldType, orig')
     else if null dims then
-        error $ "illegal access to range " ++ show (Range Nil NonIndexed rOuter)
-            ++ " of " ++ show (Dot e x) ++ ", which has type " ++ show fieldType
+        scopedError scopes $ "illegal access to range "
+            ++ show (Range Nil NonIndexed rOuter) ++ " of " ++ show (Dot e x)
+            ++ ", which has type " ++ show fieldType
     else
         (replaceInnerTypeRange NonIndexed rOuter' fieldType, undotted)
     where
@@ -398,7 +422,7 @@
         (_, roRight') = convertSubExpr scopes roRight
         rOuter' = (roLeft', roRight')
         orig' = Range (Dot e' x) NonIndexed rOuter'
-        (fieldType, bounds, dims) = lookupFieldInfo subExprType x
+        (fieldType, bounds, dims) = lookupFieldInfo scopes subExprType e' x
         [dim] = dims
         rangeLeft = ( BinOp Sub (fst bounds) $ BinOp Sub (fst dim) roLeft'
                     , BinOp Sub (fst bounds) $ BinOp Sub (fst dim) roRight' )
@@ -412,8 +436,9 @@
     else if structIsntReady subExprType then
         (replaceInnerTypeRange mode (baseO', lenO') fieldType, orig')
     else if null dims then
-        error $ "illegal access to range " ++ show (Range Nil mode (baseO, lenO))
-            ++ " of " ++ show (Dot e x) ++ ", which has type " ++ show fieldType
+        scopedError scopes $ "illegal access to range "
+            ++ show (Range Nil mode (baseO, lenO)) ++ " of " ++ show (Dot e x)
+            ++ ", which has type " ++ show fieldType
     else
         (replaceInnerTypeRange mode (baseO', lenO') fieldType, undotted)
     where
@@ -421,7 +446,7 @@
         (_, baseO') = convertSubExpr scopes baseO
         (_, lenO') = convertSubExpr scopes lenO
         orig' = Range (Dot e' x) mode (baseO', lenO')
-        (fieldType, bounds, dims) = lookupFieldInfo subExprType x
+        (fieldType, bounds, dims) = lookupFieldInfo scopes subExprType e' x
         [dim] = dims
         baseLeft  = BinOp Sub (fst bounds) $ BinOp Sub (fst dim) baseO'
         baseRight = BinOp Add (snd bounds) $ BinOp Sub (snd dim) baseO'
@@ -445,8 +470,8 @@
     else if structIsntReady subExprType then
         (dropInnerTypeRange fieldType, orig')
     else if null dims then
-        error $ "illegal access to bit " ++ show i ++ " of " ++ show (Dot e x)
-            ++ ", which has type " ++ show fieldType
+        scopedError scopes $ "illegal access to bit " ++ show i ++ " of "
+            ++ show (Dot e x) ++ ", which has type " ++ show fieldType
     else
         (dropInnerTypeRange fieldType, Bit e' iFlat)
     where
@@ -454,7 +479,7 @@
         (_, i') = convertSubExpr scopes i
         (backupType, _) = fallbackType scopes $ Dot e' x
         orig' = Bit (Dot e' x) i'
-        (fieldType, bounds, dims) = lookupFieldInfo subExprType x
+        (fieldType, bounds, dims) = lookupFieldInfo scopes subExprType e' x
         [dim] = dims
         left  = BinOp Sub (fst bounds) $ BinOp Sub (fst dim) i'
         right = BinOp Add (snd bounds) $ BinOp Sub (snd dim) i'
@@ -508,11 +533,14 @@
 isntStruct :: Type -> Bool
 isntStruct = (== Nothing) . getFields
 
--- get the field type, flattended bounds, and original type dimensions
-lookupFieldInfo :: Type -> Identifier -> (Type, Range, [Range])
-lookupFieldInfo struct fieldName =
+-- get the field type, flattened bounds, and original type dimensions
+lookupFieldInfo :: Scopes Type -> Type -> Expr -> Identifier
+    -> (Type, Range, [Range])
+lookupFieldInfo scopes struct base fieldName =
     if maybeFieldType == Nothing
-        then error $ "field '" ++ fieldName ++ "' not found in " ++ show struct
+        then scopedError scopes $ "field '" ++ fieldName ++ "' not found in "
+                ++ show struct ++ ", in expression "
+                ++ show (Dot base fieldName)
         else (fieldType, bounds, dims)
     where
         Just fields = getFields struct
@@ -538,4 +566,4 @@
                 details = lookupElem scopes $ LHSDot lhs x
                 typ = maybe UnknownType thd3 details
                 thd3 (_, _, c) = c
-                (_, e') = convertSubExpr scopes $ convertExpr typ e
+                (_, e') = convertSubExpr scopes $ convertExpr scopes typ e
diff --git a/src/Convert/Traverse.hs b/src/Convert/Traverse.hs
--- a/src/Convert/Traverse.hs
+++ b/src/Convert/Traverse.hs
@@ -255,6 +255,7 @@
         cs (Return expr) = return $ Return expr
         cs (Subroutine expr exprs) = return $ Subroutine expr exprs
         cs (Trigger blocks x) = return $ Trigger blocks x
+        cs stmt@Force{} = return stmt
         cs (Assertion a) =
             traverseAssertionStmtsM fullMapper a >>= return . Assertion
         cs (Continue) = return Continue
@@ -300,13 +301,13 @@
             e' <- mapper e
             s' <- seqExprMapper s
             return $ SeqExprThroughout e' s'
-        seqExprMapper (SeqExprDelay ms e s) = do
+        seqExprMapper (SeqExprDelay ms r s) = do
             ms' <- case ms of
                 Nothing -> return Nothing
                 Just x -> seqExprMapper x >>= return . Just
-            e' <- mapper e
+            r' <- mapBothM mapper r
             s' <- seqExprMapper s
-            return $ SeqExprDelay ms' e' s'
+            return $ SeqExprDelay ms' r' s'
         seqExprMapper (SeqExprFirstMatch s items) = do
             s' <- seqExprMapper s
             items' <- mapM seqMatchItemMapper items
@@ -343,10 +344,11 @@
             spMapper PropExprFollowsNO se pe
         propExprMapper (PropExprIff p1 p2) =
             ppMapper PropExprIff p1 p2
-        propSpecMapper (PropertySpec ms e pe) = do
+        propSpecMapper (PropertySpec mv e pe) = do
+            mv' <- mapM (traverseEventExprsM mapper) mv
             e' <- mapper e
             pe' <- propExprMapper pe
-            return $ PropertySpec ms e' pe'
+            return $ PropertySpec mv' e' pe'
         assertionExprMapper (Concurrent e) =
             propSpecMapper e >>= return . Concurrent
         assertionExprMapper (Immediate d e) =
@@ -365,13 +367,6 @@
 traverseStmtLHSsM mapper = stmtMapper
     where
         fullMapper = mapper
-        stmtMapper (Timing (Event sense) stmt) = do
-            sense' <- senseMapper sense
-            return $ Timing (Event sense') stmt
-        stmtMapper (Asgn op (Just (Event sense)) lhs expr) = do
-            lhs' <- fullMapper lhs
-            sense' <- senseMapper sense
-            return $ Asgn op (Just $ Event sense') lhs' expr
         stmtMapper (Asgn op mt lhs expr) =
             fullMapper lhs >>= \lhs' -> return $ Asgn op mt lhs' expr
         stmtMapper (For inits me incrs stmt) = do
@@ -380,30 +375,9 @@
             lhss' <- mapM fullMapper lhss
             let incrs' = zip3 lhss' asgnOps exprs
             return $ For inits' me incrs' stmt
-        stmtMapper (Assertion a) =
-            assertionMapper a >>= return . Assertion
+        stmtMapper (Force kw l e) =
+            fullMapper l >>= \l' -> return $ Force kw l' e
         stmtMapper other = return other
-        senseMapper (Sense        lhs) = fullMapper lhs >>= return . Sense
-        senseMapper (SensePosedge lhs) = fullMapper lhs >>= return . SensePosedge
-        senseMapper (SenseNegedge lhs) = fullMapper lhs >>= return . SenseNegedge
-        senseMapper (SenseOr    s1 s2) = do
-            s1' <- senseMapper s1
-            s2' <- senseMapper s2
-            return $ SenseOr s1' s2'
-        senseMapper (SenseStar       ) = return SenseStar
-        assertionExprMapper (Concurrent (PropertySpec (Just sense) me pe)) = do
-            sense' <- senseMapper sense
-            return $ Concurrent $ PropertySpec (Just sense') me pe
-        assertionExprMapper other = return other
-        assertionMapper (Assert e ab) = do
-            e' <- assertionExprMapper e
-            return $ Assert e' ab
-        assertionMapper (Assume e ab) = do
-            e' <- assertionExprMapper e
-            return $ Assume e' ab
-        assertionMapper (Cover e stmt) = do
-            e' <- assertionExprMapper e
-            return $ Cover e' stmt
 
 traverseStmtLHSs :: Mapper LHS -> Mapper Stmt
 traverseStmtLHSs = unmonad traverseStmtLHSsM
@@ -496,6 +470,10 @@
             e2' <- exprMapper e2
             e3' <- exprMapper e3
             return $ MinTypMax e1' e2' e3'
+        em (ExprAsgn e1 e2) = do
+            e1' <- exprMapper e1
+            e2' <- exprMapper e2
+            return $ ExprAsgn e1' e2'
         em (Nil) = return Nil
 
 traverseSinglyNestedExprs :: Mapper Expr -> Mapper Expr
@@ -618,6 +596,14 @@
         return $ MIPackageItem $ Import x y
     moduleItemMapper (MIPackageItem (Export x y)) =
         return $ MIPackageItem $ Export x y
+    moduleItemMapper (MIPackageItem item@DPIImport{}) = do
+        let DPIImport spec prop alias typ name decls = item
+        typ' <- typeMapper typ
+        decls' <- mapM declMapper decls
+        let item' = DPIImport spec prop alias typ' name decls'
+        return $ MIPackageItem item'
+    moduleItemMapper (MIPackageItem (DPIExport spec alias kw name)) =
+        return $ MIPackageItem $ DPIExport spec alias kw name
     moduleItemMapper (AssertionItem (mx, a)) = do
         a' <- traverseAssertionStmtsM stmtMapper a
         a'' <- traverseAssertionExprsM exprMapper a'
@@ -659,7 +645,6 @@
     caseMapper (exprs, stmt) = do
         exprs' <- mapM exprMapper exprs
         return (exprs', stmt)
-    stmtMapper = traverseNestedStmtsM flatStmtMapper
     flatStmtMapper (StmtAttr attr stmt) =
         -- note: we exclude expressions in attributes from conversion
         return $ StmtAttr attr stmt
@@ -689,7 +674,8 @@
     flatStmtMapper (Foreach x vars stmt) = return $ Foreach x vars stmt
     flatStmtMapper (If u cc s1 s2) =
         exprMapper cc >>= \cc' -> return $ If u cc' s1 s2
-    flatStmtMapper (Timing event stmt) = return $ Timing event stmt
+    flatStmtMapper (Timing timing stmt) =
+        timingMapper timing >>= \timing' -> return $ Timing timing' stmt
     flatStmtMapper (Subroutine e (Args l p)) = do
         e' <- exprMapper e
         l' <- mapM exprMapper l
@@ -699,10 +685,12 @@
     flatStmtMapper (Return expr) =
         exprMapper expr >>= return . Return
     flatStmtMapper (Trigger blocks x) = return $ Trigger blocks x
-    flatStmtMapper (Assertion a) = do
-        a' <- traverseAssertionStmtsM stmtMapper a
-        a'' <- traverseAssertionExprsM exprMapper a'
-        return $ Assertion a''
+    flatStmtMapper (Force kw l e) = do
+        l' <- lhsMapper l
+        e' <- exprMapper e
+        return $ Force kw l' e'
+    flatStmtMapper (Assertion a) =
+        traverseAssertionExprsM exprMapper a >>= return . Assertion
     flatStmtMapper (Continue) = return Continue
     flatStmtMapper (Break) = return Break
     flatStmtMapper (Null) = return Null
@@ -710,6 +698,22 @@
 
     asgnMapper (l, op, e) = exprMapper e >>= \e' -> return $ (l, op, e')
 
+    timingMapper (Event e) = eventMapper e >>= return . Event
+    timingMapper (Delay e) = exprMapper e >>= return . Delay
+    timingMapper (Cycle e) = exprMapper e >>= return . Cycle
+
+    eventMapper EventStar = return EventStar
+    eventMapper (EventExpr e) =
+        traverseEventExprsM exprMapper e >>= return . EventExpr
+
+traverseEventExprsM :: Monad m => MapperM m Expr -> MapperM m EventExpr
+traverseEventExprsM mapper (EventExprEdge edge expr) =
+    mapper expr >>= return . EventExprEdge edge
+traverseEventExprsM mapper (EventExprOr e1 e2) = do
+    e1' <- traverseEventExprsM mapper e1
+    e2' <- traverseEventExprsM mapper e2
+    return $ EventExprOr e1' e2'
+
 traverseStmtExprs :: Mapper Expr -> Mapper Stmt
 traverseStmtExprs = unmonad traverseStmtExprsM
 collectStmtExprsM :: Monad m => CollectorM m Expr -> CollectorM m Stmt
@@ -837,6 +841,7 @@
         tm (UnpackedType t r) = do
             t' <- mapper t
             return $ UnpackedType t' r
+        tm Void = return Void
 
 traverseSinglyNestedTypes :: Mapper Type -> Mapper Type
 traverseSinglyNestedTypes = unmonad traverseSinglyNestedTypesM
@@ -1077,6 +1082,9 @@
         stmtMapper (Asgn op mt lhs expr) = do
             (lhs', expr') <- mapper (lhs, expr)
             return $ Asgn op mt lhs' expr'
+        stmtMapper (Force kw lhs expr) | expr /= Nil = do
+            (lhs', expr') <- mapper (lhs, expr)
+            return $ Force kw lhs' expr'
         stmtMapper other = return other
 
 traverseStmtAsgns :: Mapper (LHS, Expr) -> Mapper Stmt
diff --git a/src/Convert/TypeOf.hs b/src/Convert/TypeOf.hs
--- a/src/Convert/TypeOf.hs
+++ b/src/Convert/TypeOf.hs
@@ -130,7 +130,8 @@
 elaborateSizeCast :: Expr -> Expr -> ST Expr
 elaborateSizeCast (Number size) _ | Just 0 == numberToInteger size =
     -- special case because zero-width ranges cannot be represented
-    error $ "size cast width " ++ show size ++ " is not a positive integer"
+    scopedErrorM $ "size cast width " ++ show size
+        ++ " is not a positive integer"
 elaborateSizeCast size value = do
     t <- typeof value
     force <- isStringParam value
diff --git a/src/Job.hs b/src/Job.hs
--- a/src/Job.hs
+++ b/src/Job.hs
@@ -24,6 +24,7 @@
     | Interface
     | Logic
     | Succinct
+    | UnbasedUnsized
     deriving (Typeable, Data, Eq)
 
 data Write
@@ -65,8 +66,8 @@
     , passThrough = nam_ "pass-through" &= help "Dump input without converting"
         &= groupname "Conversion"
     , exclude = nam_ "exclude" &= name "E" &= typ "CONV"
-        &= help ("Exclude a particular conversion (always, assert, interface,"
-            ++ " or logic)")
+        &= help ("Exclude a particular conversion (Always, Assert, Interface,"
+            ++ " Logic, or UnbasedUnsized)")
     , verbose = nam "verbose" &= help "Retain certain conversion artifacts"
     , write = Stdout &= ignore -- parsed from the flexible flag below
     , writeRaw = "s" &= name "write" &= name "w" &= explicit &= typ "MODE/FILE"
@@ -85,7 +86,7 @@
     &= summary ("sv2v " ++ version)
     &= details [ "sv2v converts SystemVerilog to Verilog."
                , "More info: https://github.com/zachjs/sv2v"
-               , "(C) 2019-2021 Zachary Snow, 2011-2015 Tom Hawkins" ]
+               , "(C) 2019-2022 Zachary Snow, 2011-2015 Tom Hawkins" ]
     &= helpArg [explicit, name "help"]
     &= versionArg [explicit, name "version"]
     &= verbosityArgs [ignore] [ignore]
diff --git a/src/Language/SystemVerilog/AST/Description.hs b/src/Language/SystemVerilog/AST/Description.hs
--- a/src/Language/SystemVerilog/AST/Description.hs
+++ b/src/Language/SystemVerilog/AST/Description.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE PatternSynonyms #-}
 {- sv2v
  - Author: Zachary Snow <zach@zachjs.com>
  - Initial Verilog AST Author: Tom Hawkins <tomahawkins@gmail.com>
@@ -12,6 +13,8 @@
     , Lifetime    (..)
     , Qualifier   (..)
     , ClassItem
+    , DPIImportProperty (..)
+    , DPIExportKW (..)
     ) where
 
 import Data.List (intercalate)
@@ -22,7 +25,7 @@
 import Language.SystemVerilog.AST.Attr (Attr)
 import Language.SystemVerilog.AST.Decl (Decl(CommentDecl))
 import Language.SystemVerilog.AST.Stmt (Stmt)
-import Language.SystemVerilog.AST.Type (Type, Identifier)
+import Language.SystemVerilog.AST.Type (Type, Identifier, pattern UnknownType)
 import {-# SOURCE #-} Language.SystemVerilog.AST.ModuleItem (ModuleItem)
 
 data Description
@@ -82,6 +85,8 @@
     | Export Identifier Identifier
     | Decl Decl
     | Directive String
+    | DPIImport String DPIImportProperty Identifier Type Identifier [Decl]
+    | DPIExport String Identifier DPIExportKW Identifier
     deriving Eq
 
 instance Show PackageItem where
@@ -95,6 +100,22 @@
     show (Export x y) = printf "export %s::%s;" (showWildcard x) (showWildcard y)
     show (Decl decl) = show decl
     show (Directive str) = str
+    show (DPIImport spec prop alias typ name decls) =
+        printf "import %s %s%s %s %s(%s);"
+            (show spec) (showPad prop) aliasStr protoStr name declsStr
+        where
+            aliasStr = if null alias then "" else alias ++ " = "
+            protoStr =
+                if typ == UnknownType
+                    then "task"
+                    else "function " ++ show typ
+            declsStr =
+                if null decls
+                    then ""
+                    else "\n\t" ++ showDecls decls ++ "\n"
+    show (DPIExport spec alias kw name) =
+        printf "export %s %s%s %s;" (show spec) aliasStr (show kw) name
+        where aliasStr = if null alias then "" else alias ++ " = "
 
 showWildcard :: Identifier -> String
 showWildcard "" = "*"
@@ -137,3 +158,23 @@
     show QStatic    = "static"
     show QLocal     = "local"
     show QProtected = "protected"
+
+data DPIImportProperty
+    = DPIContext
+    | DPIPure
+    | DPINone
+    deriving Eq
+
+instance Show DPIImportProperty where
+    show DPIContext = "context"
+    show DPIPure    = "pure"
+    show DPINone    = ""
+
+data DPIExportKW
+    = DPIExportTask
+    | DPIExportFunction
+    deriving Eq
+
+instance Show DPIExportKW where
+    show DPIExportTask     = "task"
+    show DPIExportFunction = "function"
diff --git a/src/Language/SystemVerilog/AST/Expr.hs b/src/Language/SystemVerilog/AST/Expr.hs
--- a/src/Language/SystemVerilog/AST/Expr.hs
+++ b/src/Language/SystemVerilog/AST/Expr.hs
@@ -15,6 +15,7 @@
     , DimsFn (..)
     , DimFn (..)
     , showAssignment
+    , showRange
     , showRanges
     , ParamBinding
     , showParams
@@ -60,6 +61,7 @@
     | Pattern [(TypeOrExpr, Expr)]
     | Inside  Expr [Expr]
     | MinTypMax Expr Expr Expr
+    | ExprAsgn Expr Expr
     | Nil
     deriving Eq
 
@@ -91,6 +93,7 @@
             showPatternItem (Left t, v) = printf "%s: %s" tStr (show v)
                 where tStr = if null (show t) then "default" else show t
     show (MinTypMax a b c) = printf "(%s : %s : %s)" (show a) (show b) (show c)
+    show (ExprAsgn l r) = printf "(%s = %s)" (show l) (show r)
     show e@UniOp{} = showsPrec 0 e ""
     show e@BinOp{} = showsPrec 0 e ""
     show e@Dot  {} = showsPrec 0 e ""
diff --git a/src/Language/SystemVerilog/AST/Stmt.hs b/src/Language/SystemVerilog/AST/Stmt.hs
--- a/src/Language/SystemVerilog/AST/Stmt.hs
+++ b/src/Language/SystemVerilog/AST/Stmt.hs
@@ -8,7 +8,9 @@
 module Language.SystemVerilog.AST.Stmt
     ( Stmt   (..)
     , Timing (..)
-    , Sense  (..)
+    , Event        (..)
+    , EventExpr    (..)
+    , Edge         (..)
     , CaseKW (..)
     , Case
     , ActionBlock  (..)
@@ -29,7 +31,7 @@
 import Language.SystemVerilog.AST.ShowHelp (commas, indent, unlines', showPad, showBlock)
 import Language.SystemVerilog.AST.Attr (Attr)
 import Language.SystemVerilog.AST.Decl (Decl)
-import Language.SystemVerilog.AST.Expr (Expr(Call, Ident, Nil), Args(..))
+import Language.SystemVerilog.AST.Expr (Expr(Call, Ident, Nil), Args(..), Range, showRange, showAssignment)
 import Language.SystemVerilog.AST.LHS (LHS)
 import Language.SystemVerilog.AST.Op (AsgnOp(AsgnOpEq))
 import Language.SystemVerilog.AST.Type (Identifier)
@@ -51,6 +53,7 @@
     | Subroutine Expr Args
     | Trigger Bool Identifier
     | Assertion Assertion
+    | Force Bool LHS Expr
     | Continue
     | Break
     | Null
@@ -96,6 +99,13 @@
     show (Timing    t s) = printf "%s%s" (show t) (showShortBranch s)
     show (Trigger   b x) = printf "->%s %s;" (if b then "" else ">") x
     show (Assertion   a) = show a
+    show (Force  kw l e) = printf "%s %s%s;" kwStr (show l) (showAssignment e)
+        where
+            kwStr = case (kw, e /= Nil) of
+                (True , True ) -> "force"
+                (True , False) -> "release"
+                (False, True ) -> "assign"
+                (False, False) -> "deassign"
     show (Continue     ) = "continue;"
     show (Break        ) = "break;"
     show (Null         ) = ";"
@@ -160,31 +170,47 @@
 type Case = ([Expr], Stmt)
 
 data Timing
-    = Event Sense
+    = Event Event
     | Delay Expr
     | Cycle Expr
     deriving Eq
 
 instance Show Timing where
-    show (Event s) = printf  "@(%s)" (show s)
+    show (Event e) = printf  "@(%s)" (show e)
     show (Delay e) = printf  "#(%s)" (show e)
     show (Cycle e) = printf "##(%s)" (show e)
 
-data Sense
-    = Sense        LHS
-    | SenseOr      Sense Sense
-    | SensePosedge LHS
-    | SenseNegedge LHS
-    | SenseStar
+data Event
+    = EventStar
+    | EventExpr EventExpr
     deriving Eq
 
-instance Show Sense where
-    show (Sense        a  ) = show a
-    show (SenseOr      a b) = printf "%s or %s" (show a) (show b)
-    show (SensePosedge a  ) = printf "posedge %s" (show a)
-    show (SenseNegedge a  ) = printf "negedge %s" (show a)
-    show (SenseStar       ) = "*"
+instance Show Event where
+    show EventStar = "*"
+    show (EventExpr e) = show e
 
+data EventExpr
+    = EventExprEdge Edge Expr
+    | EventExprOr EventExpr EventExpr
+    deriving Eq
+
+instance Show EventExpr where
+    show (EventExprEdge g e) = printf "%s%s" (showPad g) (show e)
+    show (EventExprOr   a b) = printf "%s or %s" (show a) (show b)
+
+data Edge
+    = Posedge
+    | Negedge
+    | Edge
+    | NoEdge
+    deriving Eq
+
+instance Show Edge where
+    show Posedge = "posedge"
+    show Negedge = "negedge"
+    show Edge    = "edge"
+    show NoEdge  = ""
+
 data ActionBlock
     = ActionBlock Stmt Stmt
     deriving Eq
@@ -222,7 +248,7 @@
     | SeqExprIntersect  SeqExpr SeqExpr
     | SeqExprThroughout Expr    SeqExpr
     | SeqExprWithin     SeqExpr SeqExpr
-    | SeqExprDelay (Maybe SeqExpr) Expr SeqExpr
+    | SeqExprDelay (Maybe SeqExpr) Range SeqExpr
     | SeqExprFirstMatch SeqExpr [SeqMatchItem]
     deriving Eq
 instance Show SeqExpr where
@@ -232,9 +258,14 @@
     show (SeqExprIntersect  a b) = printf "(%s %s %s)" (show a) "intersect"  (show b)
     show (SeqExprThroughout a b) = printf "(%s %s %s)" (show a) "throughout" (show b)
     show (SeqExprWithin     a b) = printf "(%s %s %s)" (show a) "within"     (show b)
-    show (SeqExprDelay   me e s) = printf "%s##%s %s" (maybe "" showPad me) (show e) (show s)
+    show (SeqExprDelay   me r s) = printf "%s##%s %s" (maybe "" showPad me) (showCycleDelayRange r) (show s)
     show (SeqExprFirstMatch e a) = printf "first_match(%s, %s)" (show e) (commas $ map show a)
 
+showCycleDelayRange :: Range -> String
+showCycleDelayRange (Nil, e) = printf "(%s)" (show e)
+showCycleDelayRange (e, Nil) = printf "[%s:$]" (show e)
+showCycleDelayRange r = showRange r
+
 type AssertionItem = (Identifier, Assertion)
 
 data Assertion
@@ -266,15 +297,15 @@
     show FinalDeferred = "final"
 
 data PropertySpec
-    = PropertySpec (Maybe Sense) Expr PropExpr
+    = PropertySpec (Maybe EventExpr) Expr PropExpr
     deriving Eq
 instance Show PropertySpec where
-    show (PropertySpec ms e pe) =
-        printf "%s%s\n\t%s" msStr eStr (show pe)
+    show (PropertySpec mv e pe) =
+        printf "%s%s\n\t%s" mvStr eStr (show pe)
         where
-            msStr = case ms of
+            mvStr = case mv of
                 Nothing -> ""
-                Just s -> printf "@(%s) " (show s)
+                Just v -> printf "@(%s) " (show v)
             eStr = case e of
                 Nil -> ""
                 _ -> printf "disable iff (%s)" (show e)
diff --git a/src/Language/SystemVerilog/AST/Type.hs b/src/Language/SystemVerilog/AST/Type.hs
--- a/src/Language/SystemVerilog/AST/Type.hs
+++ b/src/Language/SystemVerilog/AST/Type.hs
@@ -9,6 +9,7 @@
 
 module Language.SystemVerilog.AST.Type
     ( Identifier
+    , EnumItem
     , Field
     , Type               (..)
     , Signing            (..)
@@ -33,7 +34,7 @@
 
 type Identifier = String
 
-type Item = (Identifier, Expr)
+type EnumItem = (Identifier, Expr)
 type Field = (Type, Identifier)
 
 data Type
@@ -44,13 +45,14 @@
     | Alias               Identifier           [Range]
     | PSAlias  Identifier Identifier           [Range]
     | CSAlias  Identifier [ParamBinding] Identifier [Range]
-    | Enum     Type         [Item]             [Range]
+    | Enum     Type         [EnumItem]         [Range]
     | Struct   Packing      [Field]            [Range]
     | Union    Packing      [Field]            [Range]
     | InterfaceT Identifier Identifier         [Range]
     | TypeOf Expr
     | TypedefRef Expr
     | UnpackedType Type [Range] -- used internally
+    | Void
     deriving Eq
 
 instance Show Type where
@@ -66,13 +68,14 @@
     show (Enum t vals r) = printf "enum %s{%s}%s" tStr (commas $ map showVal vals) (showRanges r)
         where
             tStr = showPad t
-            showVal :: (Identifier, Expr) -> String
+            showVal :: EnumItem -> String
             showVal (x, e) = x ++ (showAssignment e)
     show (Struct p items r) = printf "struct %s{\n%s\n}%s" (showPad p) (showFields items) (showRanges r)
     show (Union  p items r) = printf  "union %s{\n%s\n}%s" (showPad p) (showFields items) (showRanges r)
     show (TypeOf expr) = printf "type(%s)" (show expr)
     show (UnpackedType t rs) = printf "UnpackedType(%s, %s)" (show t) (showRanges rs)
     show (TypedefRef e) = show e
+    show Void = "void"
 
 showFields :: [Field] -> String
 showFields items = itemsStr
@@ -101,6 +104,7 @@
         NonInteger    kw       -> (nullRange $ NonInteger  kw   , [])
         TypeOf            expr -> (nullRange $ TypeOf       expr, [])
         TypedefRef        expr -> (nullRange $ TypedefRef   expr, [])
+        Void                   -> (nullRange Void               , [])
 
 nullRange :: Type -> ([Range] -> Type)
 nullRange t [] = t
@@ -180,6 +184,7 @@
     | TRealtime
     | TString
     | TEvent
+    | TChandle
     deriving Eq
 
 instance Show NetType where
@@ -212,6 +217,7 @@
     show TRealtime  = "realtime"
     show TString    = "string"
     show TEvent     = "event"
+    show TChandle   = "chandle"
 
 data Packing
     = Unpacked
diff --git a/src/Language/SystemVerilog/Parser/Parse.y b/src/Language/SystemVerilog/Parser/Parse.y
--- a/src/Language/SystemVerilog/Parser/Parse.y
+++ b/src/Language/SystemVerilog/Parser/Parse.y
@@ -406,7 +406,7 @@
 %nonassoc "else"
 %right  "|->" "|=>" "#-#" "#=#"
 %right "iff"
-%left "or"
+%left "or" ","
 %left "and"
 %left "intersect"
 %left "within"
@@ -518,9 +518,19 @@
   | "realtime"  { withPos $1 TRealtime  }
   | "string"    { withPos $1 TString    }
   | "event"     { withPos $1 TEvent     }
+  | "chandle"   { withPos $1 TChandle   }
 
-EnumItems :: { [(Identifier, Expr)] }
-  : VariablePortIdentifiers { $1 }
+EnumItems :: { [EnumItem] }
+  : EnumItem               { $1 }
+  | EnumItem "," EnumItems { $1 ++ $3 }
+EnumItem :: { [EnumItem] }
+  : Identifier                    OptAsgn { [($1, $2)] }
+  | IdentifierP IntegralDimension OptAsgn {% makeEnumItems $1 $2 $3 }
+IntegralDimension :: { (Integer, Integer) }
+  : "[" IntegralNumber                    "]" { ( 0, $2 - 1) }
+  | "[" IntegralNumber ":" IntegralNumber "]" { ($2,     $4) }
+IntegralNumber :: { Integer }
+  : number {% readIntegralNumber (tokenPosition $1) (tokenString $1) }
 
 StructItems :: { [(Type, Identifier)] }
   : StructItem             { $1 }
@@ -672,12 +682,6 @@
   | "type" IdentifierP { [DTTypeDecl (tokenPosition $1), uncurry DTIdent $2] }
   | ParameterDeclKW    { [uncurry DTParamKW $1] }
 
-VariablePortIdentifiers :: { [(Identifier, Expr)] }
-  : VariablePortIdentifier                             { [$1] }
-  | VariablePortIdentifiers "," VariablePortIdentifier { $1 ++ [$3] }
-VariablePortIdentifier :: { (Identifier, Expr) }
-  : Identifier OptAsgn { ($1,$2) }
-
 Direction :: { Direction }
   : DirectionP { snd $1 }
 DirectionP :: { (Position, Direction) }
@@ -762,7 +766,7 @@
 PropertySpec :: { PropertySpec }
   : OptClockingEvent "disable" "iff" "(" Expr ")" PropExpr { PropertySpec $1 $5  $7 }
   | OptClockingEvent                              PropExpr { PropertySpec $1 Nil $2 }
-OptClockingEvent :: { Maybe Sense }
+OptClockingEvent :: { Maybe EventExpr }
   : ClockingEvent { Just $1 }
   | {- empty -}   { Nothing }
 
@@ -786,8 +790,8 @@
   | SeqExpr "intersect"  SeqExpr { SeqExprIntersect  $1 $3 }
   | Expr    "throughout" SeqExpr { SeqExprThroughout $1 $3 }
   | SeqExpr "within"     SeqExpr { SeqExprWithin     $1 $3 }
-  | SeqExpr "##" Number  SeqExpr { SeqExprDelay (Just $1) (Number $3) $4 }
-  |         "##" Number  SeqExpr { SeqExprDelay (Nothing) (Number $2) $3 }
+  | SeqExpr "##" CycleDelayRange SeqExpr { SeqExprDelay (Just $1) $3 $4 }
+  |         "##" CycleDelayRange SeqExpr { SeqExprDelay (Nothing) $2 $3 }
   | "first_match" "(" SeqExpr SeqMatchItems ")" { SeqExprFirstMatch $3 $4 }
 SeqMatchItems :: { [SeqMatchItem] }
   : "," SeqMatchItem               { [$2] }
@@ -796,6 +800,16 @@
   : ForStepAssignment   { SeqMatchAsgn $1 }
   | Identifier CallArgs { SeqMatchCall $1 $2 }
 
+CycleDelayRange :: { Range }
+  : Range { $1 }
+  | Number       { (Nil, Number $1) }
+  | Identifier   { (Nil, Ident  $1) }
+  | "(" Expr ")" { (Nil,        $2) }
+  | "[" "+" "]"  { (RawNum 1, Nil) }
+  | "[" "*" "]"  { (RawNum 0, Nil) }
+  | "[*"    "]"  { (RawNum 0, Nil) }
+  | "[" Expr ":" "$" "]" { ($2, Nil) }
+
 ActionBlock :: { ActionBlock }
   : Stmt %prec NoElse { ActionBlock $1 Null }
   |      "else" Stmt  { ActionBlock Null $2 }
@@ -901,6 +915,7 @@
   | ImportOrExport       { $1 }
   | ForwardTypedef ";"   { $1 }
   | TimeunitsDeclaration { $1 }
+  | DPIImportExport      { [$1] }
   | Directive            { [Directive $1] }
 
 ImportOrExport :: { [PackageItem] }
@@ -909,7 +924,6 @@
   | "export" "*" "::" "*" ";"       { [Export "" ""] }
 TaskOrFunction :: { PackageItem }
   : "function" Lifetime FuncRetAndName    TFItems DeclsAndStmts endfunction StrTag {% checkTag (snd $3) $7 $ Function $2 (fst $3) (snd $3) (map makeInput $4 ++ fst $5) (snd $5) }
-  | "function" Lifetime "void" Identifier TFItems DeclsAndStmts endfunction StrTag {% checkTag $4       $8 $ Task     $2 $4                ($5 ++ fst $6) (snd $6) }
   | "task"     Lifetime Identifier        TFItems DeclsAndStmts endtask     StrTag {% checkTag $3       $7 $ Task     $2 $3                ($4 ++ fst $5) (snd $5) }
 Typedef :: { Decl }
   : "typedef" Type Identifier ";" { ParamType Localparam $3 $2 }
@@ -928,6 +942,30 @@
   | "timeunit" Time "/" Time ";" { [] }
   | "timeprecision" Time     ";" { [] }
 
+DPIImportExport :: { PackageItem }
+  : "import" DPISpecString OptDPIImportProperty OptDPIAlias DPITFProto     { makeDPIImport $2 $3 $4 $5 }
+  | "export" DPISpecString OptDPIAlias          DPIExportKW Identifier ";" { DPIExport $2 $3 $4 $5 }
+
+DPIExportKW :: { DPIExportKW }
+  : "task"     { DPIExportTask }
+  | "function" { DPIExportFunction }
+
+DPISpecString :: { String }
+  : String { $1 }
+
+OptDPIAlias :: { Identifier }
+  : Identifier "=" { $1 }
+  | {- empty -}    { "" }
+
+OptDPIImportProperty :: { DPIImportProperty }
+  : "context"   { DPIContext }
+  | "pure"      { DPIPure }
+  | {- empty -} { DPINone }
+
+DPITFProto :: { (Type, Identifier, [Decl]) }
+  : "function" FuncRetAndName    TFItems { (fst $2     , snd $2, $3) }
+  | "task"     Identifier        TFItems { (UnknownType,     $2, $3) }
+
 Directive :: { String }
   : "`celldefine"          { tokenString $1 }
   | "`endcelldefine"       { tokenString $1 }
@@ -955,6 +993,7 @@
   | Signing                    Identifier { (Implicit $1          [], $2) }
   |         DimensionsNonEmpty Identifier { (Implicit Unspecified $1, $2) }
   | Signing DimensionsNonEmpty Identifier { (Implicit $1          $2, $3) }
+  | "void"                     Identifier { (Void                   , $2) }
 
 AlwaysKW :: { AlwaysKW }
   : "always"       { Always      }
@@ -1095,6 +1134,10 @@
   | AttributeInstance Stmt                     { StmtAttr $1 $2 }
   | ProceduralAssertionStatement               { Assertion $1 }
   | "void" "'" "(" Expr CallArgs ")" ";"       { Subroutine $4 $5 }
+  | "assign"   LHS "=" Expr ";"                { Force False $2  $4 }
+  | "deassign" LHS          ";"                { Force False $2 Nil }
+  | "force"    LHS "=" Expr ";"                { Force True  $2  $4 }
+  | "release"  LHS          ";"                { Force True  $2 Nil }
 
 OptDelayOrEvent :: { Maybe Timing }
   : DelayOrEvent { Just $1 }
@@ -1168,8 +1211,8 @@
   | Identifier          { ($1, UnknownType) }
 
 -- TODO: This does not allow for @identifier
-ClockingEvent :: { Sense }
-  : "@" "(" Senses ")" { $3 }
+ClockingEvent :: { EventExpr }
+  : "@" "(" EventExpr ")" { $3 }
 
 TimingControl :: { Timing }
   : DelayOrEvent { $1 }
@@ -1188,26 +1231,27 @@
   | "#" Identifier ParamBindings "::" Identifier { CSIdent $2 $3 $5 }
 CycleDelay :: { Expr }
   : "##" Expr { $2 }
-EventControl :: { Sense }
-  : "@" "(" Senses ")" { $3 }
-  | "@" "(*)"          { SenseStar }
-  | "@" "(" "*" ")"    { SenseStar }
-  | "@" "(*" ")"       { SenseStar }
-  | "@" "(" "*)"       { SenseStar }
-  | "@" "*"            { SenseStar }
-  | "@*"               { SenseStar }
-  | "@" Identifier     { Sense $ LHSIdent $2 }
-Senses :: { Sense }
-  : Sense             { $1 }
-  | Senses "or" Sense { SenseOr $1 $3 }
-  | Senses ","  Sense { SenseOr $1 $3 }
-Sense :: { Sense }
-  : "(" Sense ")"         {              $2 }
-  |               LHS     { Sense        $1 }
-  | "posedge"     LHS     { SensePosedge $2 }
-  | "negedge"     LHS     { SenseNegedge $2 }
-  | "posedge" "(" LHS ")" { SensePosedge $3 }
-  | "negedge" "(" LHS ")" { SenseNegedge $3 }
+EventControl :: { Event }
+  : "@*"            { EventStar }
+  | "@" "(*)"       { EventStar }
+  | "@" "(" "*" ")" { EventStar }
+  | "@" "(*" ")"    { EventStar }
+  | "@" "(" "*)"    { EventStar }
+  | "@" "*"         { EventStar }
+  | "@" "(" EventExpr ")" { EventExpr $3 }
+  | "@" Identifier        { EventExpr $ EventExprEdge NoEdge $ Ident $2 }
+EventExpr :: { EventExpr }
+  : Expr { EventExprEdge NoEdge $1 }
+  | EventExprComplex { $1 }
+EventExprComplex :: { EventExpr }
+  : "(" EventExprComplex ")" { $2 }
+  | Edge Expr { EventExprEdge $1 $2 }
+  | EventExpr "or" EventExpr { EventExprOr $1 $3 }
+  | EventExpr ","  EventExpr { EventExprOr $1 $3 }
+Edge :: { Edge }
+  : "posedge" { Posedge }
+  | "negedge" { Negedge }
+  | "edge"    { Edge    }
 
 CaseKW :: { CaseKW }
   : "case"  { CaseN }
@@ -1343,6 +1387,11 @@
   | "^"  Expr %prec REDUCE_OP { UniOp RedXor  $2 }
   | "~^" Expr %prec REDUCE_OP { UniOp RedXnor $2 }
   | "^~" Expr %prec REDUCE_OP { UniOp RedXnor $2 }
+  -- assignments within expressions
+  | "(" Expr "="        Expr ")" {% makeExprAsgn (tokenPosition $3, AsgnOpEq) $2 $4 }
+  | "(" Expr AsgnBinOpP Expr ")" {% makeExprAsgn $3                           $2 $4 }
+  | Expr IncOrDecOperatorP            {% makeIncrExprAsgn True  $2 $1 }
+  | IncOrDecOperatorP Expr %prec "++" {% makeIncrExprAsgn False $1 $2 }
 
 ExprOrNil :: { Expr }
   : Expr        { $1 }
@@ -1541,9 +1590,10 @@
 makeInput :: Decl -> Decl
 makeInput (Variable Local t x a e) = Variable Input t x a e
 makeInput (Variable Input t x a e) = Variable Input t x a e
+makeInput (Variable Output t x a e) = Variable Output t x a e
 makeInput (CommentDecl c) = CommentDecl c
 makeInput other =
-  error $ "unexpected non-var or non-input decl: " ++ (show other)
+  error $ "unexpected non-var or non-port function decl: " ++ (show other)
 
 checkTag :: String -> String -> a -> ParseState a
 checkTag _ "" x = return x
@@ -1713,5 +1763,54 @@
     Decimal (-32) True 0 -> return a
     _ -> parseError pos $ "expected 0 after #, but found " ++ str
   where Token { tokenString = str, tokenPosition = pos } = tok
+
+readIntegralNumber :: Position -> String -> ParseState Integer
+readIntegralNumber pos str = do
+  num <- readNumber pos str
+  case numberToInteger num of
+    Nothing ->
+      parseError pos $ "expected an integral number, but found " ++ str
+    Just integer | integer < 0 ->
+      parseError pos $ "expected a non-negative number, but found " ++ str
+    Just integer ->
+      return integer
+
+makeEnumItems :: (Position, Identifier) -> (Integer, Integer) -> Expr
+  -> ParseState [EnumItem]
+makeEnumItems (pos, _) (0, -1) _ =
+  parseError pos "expected a positive number, but found zero"
+makeEnumItems (_, root) (l, r) base =
+  return $ (name, base) : map (, Nil) names
+  where
+    name : names =
+      map (root ++) $
+      map show $
+      if l >= r
+        then reverse [r..l]
+        else [l..r]
+
+makeDPIImport :: String -> DPIImportProperty -> Identifier
+  -> (Type, Identifier, [Decl]) -> PackageItem
+makeDPIImport a b c (d, e, f) = DPIImport a b c d e f
+
+makeExprAsgn :: (Position, AsgnOp) -> Expr -> Expr -> ParseState Expr
+makeExprAsgn (pos, AsgnOpEq) l r = do
+  case exprToLHS l of
+    Just{} -> return ()
+    Nothing -> parseError pos $ "cannot convert expression to LHS: " ++ show l
+  return $ ExprAsgn l r
+makeExprAsgn (pos, op) l r =
+  makeExprAsgn (pos, AsgnOpEq) l r'
+  where
+    AsgnOp binop = op
+    r' = BinOp binop l r
+
+makeIncrExprAsgn :: Bool -> (Position, BinOp) -> Expr -> ParseState Expr
+makeIncrExprAsgn False (pos, op) expr =
+  makeExprAsgn (pos, AsgnOp op) expr (RawNum 1)
+makeIncrExprAsgn True (pos, op) expr = do
+  expr' <- makeIncrExprAsgn False (pos, op) expr
+  return $ BinOp op expr' minusOne
+  where minusOne = Number $ Decimal 1 True 1
 
 }
diff --git a/src/sv2v.hs b/src/sv2v.hs
--- a/src/sv2v.hs
+++ b/src/sv2v.hs
@@ -4,13 +4,11 @@
  - conversion entry point
  -}
 
-import System.Directory (doesFileExist)
 import System.IO (hPrint, hPutStrLn, stderr, stdout)
 import System.Exit (exitFailure, exitSuccess)
 
-import Control.Monad (filterM, when, zipWithM_)
+import Control.Monad (when, zipWithM_)
 import Control.Monad.Except (runExceptT)
-import Data.List (intercalate)
 
 import Convert (convert)
 import Job (readJob, Job(..), Write(..))
@@ -63,11 +61,6 @@
     writeFile f $ show $ concat asts
 writeOutput Adjacent inPaths asts = do
     outPaths <- mapM rewritePath inPaths
-    badPaths <- filterM doesFileExist outPaths
-    when (not $ null badPaths) $ do
-        hPutStrLn stderr $ "Refusing to write output because the following"
-            ++ " files would be overwritten: " ++ intercalate ", " badPaths
-        exitFailure
     let results = map (++ "\n") $ map show asts
     zipWithM_ writeFile outPaths results
 
diff --git a/sv2v.cabal b/sv2v.cabal
--- a/sv2v.cabal
+++ b/sv2v.cabal
@@ -1,6 +1,6 @@
 cabal-version:   2.4
 name:            sv2v
-version:         0.0.9
+version:         0.0.10
 license:         BSD-3-Clause
 license-file:    LICENSE NOTICE
 maintainer:      Zachary Snow <zach@zachjs.com>
@@ -55,9 +55,12 @@
         Convert.BlockDecl
         Convert.Cast
         Convert.DimensionQuery
+        Convert.DoWhile
         Convert.DuplicateGenvar
         Convert.EmptyArgs
         Convert.Enum
+        Convert.EventEdge
+        Convert.ExprAsgn
         Convert.ExprUtils
         Convert.ForAsgn
         Convert.Foreach
@@ -106,12 +109,12 @@
 
     build-depends:
         array >=0.5.4.0 && <0.6,
-        base >=4.14.1.0 && <4.15,
+        base >=4.14.3.0 && <4.15,
         cmdargs >=0.10.21 && <0.11,
-        containers >=0.6.2.1 && <0.7,
+        containers >=0.6.5.1 && <0.7,
         directory >=1.3.6.0 && <1.4,
         filepath >=1.4.2.1 && <1.5,
-        githash >=0.1.6.1 && <0.2,
+        githash >=0.1.6.2 && <0.2,
         hashable >=1.3.0.0 && <1.4,
         mtl >=2.2.2 && <2.3,
-        vector >=0.12.3.0 && <0.13
+        vector >=0.12.3.1 && <0.13
