; VCD Generator for ESIM
; Copyright (C) 2010-2012 Centaur Technology
;
; Contact:
;   Centaur Technology Formal Verification Group
;   7600-C N. Capital of Texas Highway, Suite 300, Austin, TX 78731, USA.
;   http://www.centtech.com/
;
; This program is free software; you can redistribute it and/or modify it under
; the terms of the GNU General Public License as published by the Free Software
; Foundation; either version 2 of the License, or (at your option) any later
; version.  This program is distributed in the hope that it will be useful but
; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
; FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
; more details.  You should have received a copy of the GNU General Public
; License along with this program; if not, write to the Free Software
; Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
;
; Original author: Jared Davis <jared@centtech.com>

(in-package "VL")
(include-book "esim-vl")
(include-book "esim-paths")
(include-book "centaur/misc/load-stobj" :dir :system)
(include-book "centaur/vl/mlib/writer" :dir :system)
(include-book "centaur/vl/util/cwtime" :dir :system)
(include-book "centaur/vl/util/string-alists" :dir :system)
(local (include-book "centaur/vl/util/arithmetic" :dir :system))
(local (include-book "centaur/vl/util/osets" :dir :system))
(set-state-ok t)


;; This really ought to be disabled
(local (in-theory (disable good-esim-modulep)))


; Implementation Notes
;
; ESIM has a certain notion of paths, see esim-paths.lisp; in particlar see the
; documentation for mod-internal-paths and note the difference between
; canonical and non-canonical paths.
;
; We want to write VCD files that have all paths, not just canonical paths.
; Otherwise it's hard to find the right signals when you are digging through
; the waveform: you often want to look at a module's inputs and outputs, but
; they don't show up because they aren't canonical.  HOWEVER, there can be many
; times more non-canonical paths than canonical paths, so writing out all the
; paths is somewhat expensive, and to make VCD dumping as fast as possible we
; try to stick with canonical paths as much as we can.
;
; In this code, a SNAPSHOT is an alist that binds canonical paths to their
; values at a particular time.  We expect snapshots to be built from
; ESIM/new-probe, so the internal signals alist at each time just has canonical
; paths.  Note that the output/internal alists generated by the new-probe stuff
; are always in the same order.  We exploit this and assume snapshots are
; always equally-ordered, so we can DIFF two snapshots in a single O(n) that
; just marches down both snapshots and don't have to do any hashing to line up
; signals.
;
; A VCD file basically contains two parts: (1) a definitions section that
; explains what the module hierarchy looks like and defines short identifier
; codes for each of the wires involved, and (2) a list of "diffs" that say what
; new values signals get at each time step.
;
; We start with some code that generates the definitions section from a
; particular E module, and which also prepares some data structures that we
; will use to speed up the diff computations.  This code is memoized so it only
; needs to be run once per module, no matter how many snapshots there are or
; how many VCD dumps we want to generate.  Because of this, it doesn't need to
; be especially fast.


; ----------------------------------------------------------------------------
;
;                     Hierarchy Collection and Printing
;
; ----------------------------------------------------------------------------

; While we gather up the vectors, we also "print" the module hierarchy stuff
; for the VCD file.  We use a similar approach to the VL Printer, and generate
; a VL-PRINTEDLIST-P (a mixed list of characters and strings), in reverse
; order, so we can cons onto the front to print more stuff.
;
; We basically need to print something like this:
;
;    module foo {
;       $var ... $end
;       $var ... $end
;       module bar_inst1 {
;          $var ... $end
;          $var ... $end
;       }
;       module bar_inst2 {
;          $var ... $end
;          $var ... $end
;       }
;   }
;
; Except that the syntax is ten times uglier:
;
;     "module foo {"        -->   $scope module foo $end
;     "}"                   -->   $upscope $end
;
; Here, the the individual variable declarations look like:
;
;   $var  var_type  size   identifier_code   reference   $end
;           ^         ^           ^              ^
;           |         |           |              |
;          wire       |    93-bit encoded name   |
;                     |                          |
;                 width of wire           original name, can be
;                                            foo, foo[i], or
;                                           even foo[msb:lsb]
;
; Each variable is given a code which is made out of printable ASCII characters
; from ! to ~ (i.e., character codes 33 to 126).  There are 93 of these
; numbers, so we basically do a base-93 encoding of our input, using (code-char
; 33) to represent 0, (code-char 34) for 1, and so forth, up to (code-char 126)
; for the 92nd digit.

(defsection vcd-93-bit-encode

  (local (include-book "arithmetic-3/floor-mod/floor-mod" :dir :system))
  (local (in-theory (enable acl2-count)))

  (defund vcd-93-bit-encode-aux (n acc)
    (declare (xargs :guard (natp n)))
    (if (zp n)
        acc
      (vcd-93-bit-encode-aux
       (truncate n 93)
       (cons (code-char (the (unsigned-byte 8)
                             (+ (the (unsigned-byte 8) 33)
                                (the (unsigned-byte 8)
                                     (rem (the integer n) 93)))))
             acc))))

  (definlined vcd-93-bit-encode-chars (n)
    (declare (xargs :guard (natp n)))
    (or (vcd-93-bit-encode-aux n nil)
        (list (code-char 33))))

  (local (in-theory (enable vcd-93-bit-encode-aux
                            vcd-93-bit-encode-chars)))

  (defthm character-listp-of-vcd-93-bit-encode-aux
    (implies (character-listp acc)
             (character-listp (vcd-93-bit-encode-aux n acc))))

  (defthm character-listp-of-vcd-93-bit-encode-chars
    (character-listp (vcd-93-bit-encode-chars n)))

  (defund vcd-93-bit-encode (n)
    (declare (xargs :guard (natp n)))
    (coerce (vcd-93-bit-encode-chars n) 'string))

  (defthm stringp-of-vcd-93-bit-encode
    (stringp (vcd-93-bit-encode n)))

  ;; A warm-fuzzy is that this returns T.
  ;;
  ;;   (uniquep (loop for i from 1 to 100000 collect (vl::vcd-93-bit-encode i)))

  )


(defsection vcd-startmodule

  (defund vcd-startmodule (instname ostream)
    (declare (xargs :guard (and (vl-emodwire-p instname)
                                (vl-printedlist-p ostream))))
    (b* (;; I'm not exactly sure what the right encoding is for names in the
         ;; VCD file, but I think this might be right:
         (basename (vl-emodwire->basename instname))
         (index    (vl-emodwire->index instname))
         (plain    (if index
                       (str::cat basename "[" (str::natstr index) "]")
                     basename))
         (encoded  (vl-maybe-escape-identifier plain))
         (ostream  (cons "$scope module " ostream))
         (ostream  (cons encoded ostream))
         (ostream  (cons " $end
" ostream)))
      ostream))

  (defthm vl-printedlist-p-of-vcd-startmodule
    (implies (force (vl-printedlist-p ostream))
             (vl-printedlist-p (vcd-startmodule instname ostream)))
    :hints(("Goal" :in-theory (enable vcd-startmodule)))))


(defsection vcd-endmodule

  (defund vcd-endmodule (ostream)
    (declare (xargs :guard (vl-printedlist-p ostream)))
    (cons "$upscope $end
" ostream))

  (defthm vl-printedlist-p-of-vcd-endmodule
    (implies (force (vl-printedlist-p ostream))
             (vl-printedlist-p (vcd-endmodule ostream)))
    :hints(("Goal" :in-theory (enable vcd-endmodule)))))


(defsection vcd-vardecl

  (defund vcd-vardecl (name msb-wires idcode ostream)
    (declare (xargs :guard (and (stringp name)
                                (vl-emodwirelist-p msb-wires)
                                (stringp idcode)
                                (vl-printedlist-p ostream))))
    (b* ((ostream (cons "$var wire " ostream))
         (ostream (revappend (str::natchars (len msb-wires)) ostream))
         (ostream (cons #\Space ostream))
         (ostream (cons idcode ostream))
         (ostream (cons #\Space ostream))
         (ostream (cons (vl-maybe-escape-identifier name) ostream))

         (ostream
          ;; Now perhaps print the range part of the reference, i.e., this
          ;; could be nothing, or it could be [3] or [5:3] or similar.
          (b* (((when (atom msb-wires))
                (er hard? 'vcd-vardecl "No wires for ~s0?" name))
               (msb-wire (first msb-wires))
               (msb-idx  (vl-emodwire->index msb-wire))
               ((when (atom (cdr msb-wires)))
                (if msb-idx
                    ;; Only a single wire, but there's an index.
                    (b* ((ostream (cons #\[ ostream))
                         (ostream (revappend (str::natchars msb-idx) ostream))
                         (ostream (cons #\] ostream)))
                      ostream)
                  ;; Else, a single wire with no index -- don't print any
                  ;; range part.
                  ostream))
               ;; Else, should be msb and lsb.
               (lsb-wire (car (last msb-wires)))
               (lsb-idx  (vl-emodwire->index lsb-wire))
               ((unless (and msb-idx lsb-idx))
                (er hard? 'vcd-vardecl "Multiple wires but not indices: ~x0" msb-wires))
               (ostream (cons #\[ ostream))
               (ostream (revappend (str::natchars msb-idx) ostream))
               (ostream (cons #\: ostream))
               (ostream (revappend (str::natchars lsb-idx) ostream))
               (ostream (cons #\] ostream)))
            ostream))

         (ostream (cons " $end
" ostream)))
      ostream))

  (defthm vl-printedlist-p-of-vcd-vardecl
    (implies (and (force (stringp idcode))
                  (force (vl-printedlist-p ostream)))
             (vl-printedlist-p (vcd-vardecl name msb-wires idcode ostream)))
    :hints(("Goal" :in-theory (enable vcd-vardecl)))))



; The idea now is to walk through the module hierarchy and identify the wires
; that are visible in the actual Verilog design.  That is, if there is a wire
; like "wire [3:0] w" in the Verilog file, then we want to include it.  But if
; it's a wire that VL just generated when it split up assignments or something,
; then we don't want to put it in the VCD dump.
;
; For each design-visible wire, we
;   (1) assign a base-93 identifier code, and
;   (2) construct a VCD-VECTOR that associates this code with the canonical
;       paths that represent this wire's value
;
; Meanwhile, we "print" the module hierarchy and $var declarations for the
; definition section of the VCD file.

(defaggregate vcd-vector
  (bits       ;; MSB-first canonical paths making up this vector
   idcode     ;; base-93 encoded identifier code for this net, as a string
   )
  :tag :vcd-vector
  :legiblep nil
  :require ((stringp-of-vcd-vector->idcode
             (stringp idcode)
             :rule-classes :type-prescription)))

(deflist vcd-vectorlist-p (x)
  (vcd-vector-p x)
  :guard t
  :elementp-of-nil nil)


(defund vcd-wires-to-canonical-paths
  (base-path ; hierarchical path to this module, e.g., '(|foo| |bar|)
   msb-wires ; wires from a vector in msb-first order, e.g., '(|baz[2]| |baz[1]| |baz[0]|)
   top-mod   ; top-level module so we can canonicalize paths
   )
  (declare (xargs :guard (and (vl-emodwirelist-p base-path)
                              (true-listp base-path)
                              (vl-emodwirelist-p msb-wires)
                              (good-esim-modulep top-mod))))
  (b* (((when (atom msb-wires))
        nil)
       ;; I tried to avoid canonicalizing paths when they were not to
       ;; inputs/outputs, by using a fast alist that captured the :i and :o for
       ;; a module.  But it didn't seem to save any time, so now to keep things
       ;; simpler I just canonicalize everything with fast-canonicalize-path.
       (path (append base-path (car msb-wires)))
       ((mv okp cpath) (acl2::fast-canonicalize-path path top-mod))
       (- (or okp
              (er hard? 'vcd-wires-to-canonical-paths
                  "Failed to canonicalize path ~x0 within module ~x1~%"
                  path (gpl :n top-mod)))))
    (cons cpath
          (vcd-wires-to-canonical-paths base-path (cdr msb-wires) top-mod))))

(defsection vcd-walist-to-vectors

  (defund vcd-walist-to-vectors
    (walist      ; wirealist to convert into vcd vectors
     dwires-fal  ; fast alist binding design-visible wires (emodwires) to nil
     base-path   ; hierarchical path down to this module, e.g., '(|foo| |bar|)
     top-mod     ; top-level module so we can canonicalize paths
     n           ; name index for id-code generation
     acc         ; answer being accumulated, list of vcd vectors
     ostream     ; printed characters for the hierarchy (reverse order)
     )
    "Returns (MV ACC N OSTREAM)"
    (declare (xargs :guard (and (vl-wirealist-p walist)
                                (vl-emodwirelist-p base-path)
                                (true-listp base-path)
                                (good-esim-modulep top-mod)
                                (natp n)
                                (vl-printedlist-p ostream))))
    (b* (((when (atom walist))
          (mv acc (lnfix n) ostream))
         (name   (caar walist))
         (wires  (cdar walist))

         (visible-p
          ;; This is a little goofy.  I might have instead written something like
          ;; (keys-boundp-fal wires dwires-fal) here.  But, I think due to the
          ;; way they are constructed, the design-wires should either include all
          ;; of the wires or none of them.  If that's the case, it should suffice
          ;; to just check the first wire, for speed.
          (hons-get (car wires) dwires-fal))
         ((unless visible-p)
          (vcd-walist-to-vectors (cdr walist) dwires-fal base-path top-mod n acc ostream))

         ;; It's visible, so assign it an ID code and construct the vector for it.
         (idcode  (vcd-93-bit-encode n))
         (n       (+ 1 n))
         (ostream (vcd-vardecl name wires idcode ostream))
         (bits    (vcd-wires-to-canonical-paths base-path wires top-mod))
         (vec     (make-vcd-vector :bits bits :idcode idcode))
         (acc     (cons vec acc)))
      (vcd-walist-to-vectors (cdr walist) dwires-fal base-path top-mod n acc ostream)))

  (local (in-theory (enable vcd-walist-to-vectors)))

  (defmvtypes vcd-walist-to-vectors (nil natp nil))

  (defthm vcd-vectorlist-p-of-vcd-walist-to-vectors
    (let ((ret (vcd-walist-to-vectors walist dwires-fal base-path top-mod n acc ostream)))
      (implies (and (force (vl-wirealist-p walist))
                    (force (vl-emodwirelist-p base-path))
                    (force (vcd-vectorlist-p acc)))
               (vcd-vectorlist-p (mv-nth 0 ret)))))

  (defthm vl-printedlist-p-of-vcd-walist-to-vectors
    (let ((ret (vcd-walist-to-vectors walist dwires-fal base-path top-mod n acc ostream)))
      (implies (force (vl-printedlist-p ostream))
               (vl-printedlist-p (mv-nth 2 ret))))))


(defsection vcd-vectors-for-mod

  (mutual-recursion

   (defund vcd-vectors-for-mod
     (base-path ; Current path of instance names we're down in, e.g., '(|foo| |bar|)
      mod       ; E module we're collecting vectors for (at base-path)
      top-mod   ; Top-level E module (so we can canonicalize paths)
      n         ; Next free index for name generation
      acc       ; answer being accumulated, list of vcd vectors
      ostream
      )
     "Returns (MV ACC N OSTREAM)"
     (declare (xargs :guard (and (vl-emodwirelist-p base-path)
                                 (true-listp base-path)
                                 (good-esim-modulep top-mod)
                                 (natp n)
                                 (vl-printedlist-p ostream))
                     :verify-guards nil
                     :measure (two-nats-measure (acl2-count mod) 2)))
     (b* ((dwires     (esim-vl-designwires mod))
          (dwires-fal (pairlis$ (redundant-list-fix dwires) nil))
          (walist     (esim-vl-wirealist mod))
          ((with-fast walist dwires-fal))
          ((mv acc n ostream)
           (vcd-walist-to-vectors walist dwires-fal base-path top-mod n acc ostream))
          (occs       (gpl :occs mod)))
       (vcd-vectors-for-occs base-path occs top-mod n acc ostream)))

   (defund vcd-vectors-for-occs (base-path occs top-mod n acc ostream)
     "Returns (MV ACC N OSTREAM)"
     (declare (xargs :guard (and (vl-emodwirelist-p base-path)
                                 (true-listp base-path)
                                 (good-esim-modulep top-mod)
                                 (natp n)
                                 (vl-printedlist-p ostream))
                     :measure (two-nats-measure (acl2-count occs) 1)))
     (b* (((when (atom occs))
           (mv acc (lnfix n) ostream))
          ((mv acc n ostream)
           (vcd-vectors-for-occ base-path (car occs) top-mod n acc ostream)))
       (vcd-vectors-for-occs base-path (cdr occs) top-mod n acc ostream)))

   (defund vcd-vectors-for-occ (base-path occ top-mod n acc ostream)
     "Returns (MV ACC N OSTREAM)"
     (declare (xargs :guard (and (vl-emodwirelist-p base-path)
                                 (true-listp base-path)
                                 (good-esim-modulep top-mod)
                                 (natp n)
                                 (vl-printedlist-p ostream))
                     :measure (two-nats-measure (acl2-count occ) 0)))
     (b* ((op       (gpl :op occ))
          (instname (gpl :u occ))
          ((unless (vl-emodwire-p instname))
           (er hard? 'vcd-vectors-for-occ
               "At path ~x0, instance name ~x1 is not an emodwire?"
               base-path instname)
           (mv acc (lnfix n) ostream))

          ((unless (esim-vl-designwires op))
           ;; If we don't do something special here, we end up with a ton of
           ;; empty scopes for VL-generated modules.  So, look and see if there
           ;; are actually any design-visible wires in the submodule, and if
           ;; not just exit without printing scope stuff.
           (mv acc (lnfix n) ostream))

          (ostream (vcd-startmodule instname ostream))
          (base-path (append base-path (list instname)))
          ((mv acc n ostream)
           (vcd-vectors-for-mod base-path op top-mod n acc ostream))
          (ostream (vcd-endmodule ostream)))
       (mv acc (lnfix n) ostream))))

  (flag::make-flag flag-vcd-vectors-for-mod
                   vcd-vectors-for-mod
                   :flag-mapping ((vcd-vectors-for-mod . mod)
                                  (vcd-vectors-for-occs . occs)
                                  (vcd-vectors-for-occ . occ)))

  (defthm-flag-vcd-vectors-for-mod

    (defthm natp-vcd-vectors-for-mod
      (natp (mv-nth 1 (vcd-vectors-for-mod base-path mod top-mod n acc ostream)))
      :rule-classes :type-prescription
      :flag mod)

    (defthm natp-vcd-vectors-for-occs
      (natp (mv-nth 1 (vcd-vectors-for-occs base-path occs top-mod n acc ostream)))
      :rule-classes :type-prescription
      :flag occs)

    (defthm natp-vcd-vectors-for-occ
      (natp (mv-nth 1 (vcd-vectors-for-occ base-path occ top-mod n acc ostream)))
      :rule-classes :type-prescription
      :flag occ)

    :hints(("Goal"
            :expand ((vcd-vectors-for-mod base-path mod top-mod n acc ostream)
                     (vcd-vectors-for-occs base-path occs top-mod n acc ostream)
                     (vcd-vectors-for-occ base-path occ top-mod n acc ostream)))))


  (defthm-flag-vcd-vectors-for-mod

    (defthm vcd-vectorlist-p-vcd-vectors-for-mod
      (implies (and (force (vl-emodwirelist-p base-path))
                    (force (vcd-vectorlist-p acc)))
               (vcd-vectorlist-p
                (mv-nth 0 (vcd-vectors-for-mod base-path mod top-mod n acc ostream))))
      :flag mod)

    (defthm vcd-vectorlist-p-vcd-vectors-for-occs
      (implies (and (force (vl-emodwirelist-p base-path))
                    (force (vcd-vectorlist-p acc)))
               (vcd-vectorlist-p
                (mv-nth 0 (vcd-vectors-for-occs base-path occs top-mod n acc ostream))))
      :flag occs)

    (defthm vcd-vectorlist-p-vcd-vectors-for-occ
      (implies (and (force (vl-emodwirelist-p base-path))
                    (force (vcd-vectorlist-p acc)))
               (vcd-vectorlist-p
                (mv-nth 0 (vcd-vectors-for-occ base-path occ top-mod n acc ostream))))
      :flag occ)

    :hints(("Goal"
            :expand ((vcd-vectors-for-mod base-path mod top-mod n acc ostream)
                     (vcd-vectors-for-occs base-path occs top-mod n acc ostream)
                     (vcd-vectors-for-occ base-path occ top-mod n acc ostream)))))

  (defthm-flag-vcd-vectors-for-mod

    (defthm vl-printedlist-p-vcd-vectors-for-mod
      (implies (force (vl-printedlist-p ostream))
               (vl-printedlist-p
                (mv-nth 2 (vcd-vectors-for-mod base-path mod top-mod n acc ostream))))
      :flag mod)

    (defthm vl-printedlist-p-vcd-vectors-for-occs
      (implies (force (vl-printedlist-p ostream))
               (vl-printedlist-p
                (mv-nth 2 (vcd-vectors-for-occs base-path occs top-mod n acc ostream))))
      :flag occs)

    (defthm vl-printedlist-p-vcd-vectors-for-occ
      (implies (force (vl-printedlist-p ostream))
               (vl-printedlist-p
                (mv-nth 2 (vcd-vectors-for-occ base-path occ top-mod n acc ostream))))
      :flag occ)

    :hints(("Goal"
            :expand ((vcd-vectors-for-mod base-path mod top-mod n acc ostream)
                     (vcd-vectors-for-occs base-path occs top-mod n acc ostream)
                     (vcd-vectors-for-occ base-path occ top-mod n acc ostream)))))

  (verify-guards vcd-vectors-for-mod))


(defsection vl-printedlist-to-string

  (defund vl-printedlist-to-string (x)
    (declare (xargs :guard (vl-printedlist-p x)))
    ;; This taps into the VL printer's optimized routine for turning printedlists
    ;; into strings.
    (with-local-ps (vl-ps-update-rchars x)))

  (defthm stringp-of-vl-printedlist-to-string
    (stringp (vl-printedlist-to-string x))
    :hints(("Goal" :in-theory (enable vl-printedlist-to-string)))))


(defsection vcd-gather-vectors

  (defund vcd-gather-vectors (mod)
    (declare (xargs :guard (good-esim-modulep mod)))
    (b* ((ostream nil)
         (ostream (vcd-startmodule 'ACL2::|esim_top| ostream))
         ((mv vecs ?n ostream)
          (vcd-vectors-for-mod nil mod mod 0 nil ostream))
         (ostream (vcd-endmodule ostream)))
      ;; We go ahead and turn the hierarchy stuff into a single string, because
      ;; we're going to memoize this anyway and then it can get dumped out very
      ;; quickly.
      (mv vecs (vl-printedlist-to-string ostream))))

  (local (in-theory (enable vcd-gather-vectors)))

  (defthm vcd-vectorlist-p-of-vcd-gather-vectors
    (vcd-vectorlist-p (mv-nth 0 (vcd-gather-vectors mod))))

  (defthm stringp-of-vcd-gather-vectors
    (stringp (mv-nth 1 (vcd-gather-vectors mod))))

  ;; this is pretty important, and lets us avoid the cost of gathering the
  ;; vectors when we make subsequent snapshots.
  (memoize 'vcd-gather-vectors))




; ----------------------------------------------------------------------------
;
;                           Vector Compression
;
; ----------------------------------------------------------------------------

; The whole canonical versus non-canonical thing means we can end up with lots
; of vectors that are identical.  We now compress the vectors we've gathered
; into MULTIVECTORS.
;
; Whereas an ordinary VCD-VECTOR associates a single ID code with its list of
; MSB bits, a VCD-MULTIVECTOR associates a list of ID codes with their list
; of MSB bits.  The idea is to group up any id codes that depend on exactly
; the same bits.
;
; This compression can be pretty significant.  On 2012-04-25, for instance,
; we have:
;
;    fadd: 44,521 vectors --> 13,935 multivectors
;    iu:   10,925 vectors --> 4,738 multivectors
;
; How can we take advantage of this?  To start with, we compute the diffs on a
; multivector basis instead of a vector basis.  When we determine that a
; multivector has changed, we can compute the update string for it and then
; just rifle through the idcodes, binding each of them to the same update
; string.

(defaggregate vcd-multivector
  (bits idcodes)
  :tag :vcd-multivector
  :legiblep nil
  :require ((string-listp-of-vcd-multivector->idcodes
             (string-listp idcodes))))

(deflist vcd-multivectorlist-p (x)
  (vcd-multivector-p x)
  :elementp-of-nil nil)



(defsection vcd-compress-vectors

  (defund vcd-compress-vectors1 (vecs alist)
    (declare (xargs :guard (and (vcd-vectorlist-p vecs)
                                (vl-string-list-values-p alist))))
    (b* (((when (atom vecs))
          alist)
         ;; The paths are already canonical, so this probably isn't much
         ;; additional honsing.  Maybe.
         (vec1   (car vecs))
         (bits   (hons-copy (vcd-vector->bits vec1)))
         (others (cdr (hons-get bits alist)))
         (alist  (hons-acons bits (cons (vcd-vector->idcode vec1) others) alist)))
      (vcd-compress-vectors1 (cdr vecs) alist)))

  (defthm vl-string-list-values-p-of-vcd-compress-vectors1
    (implies (and (vcd-vectorlist-p vecs)
                  (vl-string-list-values-p alist))
             (vl-string-list-values-p (vcd-compress-vectors1 vecs alist)))
    :hints(("Goal" :in-theory (enable vcd-compress-vectors1))))

  (defund vcd-make-multivectors (alist)
    ;; Alist should be shrunk first
    (declare (xargs :guard (vl-string-list-values-p alist)))
    (if (atom alist)
        nil
      (cons (make-vcd-multivector :bits (caar alist)
                                  :idcodes (cdar alist))
            (vcd-make-multivectors (cdr alist)))))

  (defund vcd-compress-vectors (vecs)
    (declare (xargs :guard (vcd-vectorlist-p vecs)))
    (b* ((alist (vcd-compress-vectors1 vecs nil))
         (shrink (hons-shrink-alist alist nil)))
      (fast-alist-free alist)
      (fast-alist-free shrink)
      (vcd-make-multivectors shrink)))

  (defthm vcd-multivectorlist-p-of-vcd-make-multivectors
    (implies (vl-string-list-values-p alist)
             (vcd-multivectorlist-p (vcd-make-multivectors alist)))
    :hints(("Goal" :in-theory (enable vcd-make-multivectors))))

  (defthm vcd-multivectorlist-p-of-vcd-compress-vectors
    (implies (force (vcd-vectorlist-p vecs))
             (vcd-multivectorlist-p (vcd-compress-vectors vecs)))
    :hints(("Goal" :in-theory (enable vcd-compress-vectors))))

  ;; This probably isn't especially important because compressing vectors
  ;; takes very little time, but it might save a tiny bit of time when doing
  ;; repeated vcd-dumps in the same session, and will give us the same
  ;; multivectors so we can memoize pathmap construction later.
  (memoize 'vcd-compress-vectors
           :recursive nil))




; -----------------------------------------------------------------------------
;
;                             Printing Updates
;
; -----------------------------------------------------------------------------

(defsection vcd-lookup-msb-paths

  (defund vcd-value->char (x)
    (declare (xargs :guard t))
    (cond ((eq x (acl2::4vf)) #\0)
          ((eq x (acl2::4vt)) #\1)
          ((eq x (acl2::4vx)) #\X)
          ((eq x (acl2::4vz)) #\Z)
          (t
           (or (er hard? 'vcd-value->char
                   "Unrecognized value for VCD dump: ~x0~%" x)
               #\X))))

  (defund vcd-lookup-msb-paths (paths snapshot)
    (declare (xargs :guard t))
    (b* (((when (atom paths))
          nil)
         (look (hons-get (car paths) snapshot))

         ;; Originally I wanted to cause an error here.  But some designwires
         ;; may just never be driven and hence may be omitted from the snapshot,
         ;; so now I just turn any such wire into a Z.
         ;; ((unless look)
         ;;  (er hard? 'vcd-lookup-msb-paths
         ;;      "Path ~x0 not found in snapshot!" (car paths))))

         (char (if look
                   (vcd-value->char (cdr look))
                 #\Z)))
      (cons char
            (vcd-lookup-msb-paths (cdr paths) snapshot))))

  (defthm character-listp-of-vcd-lookup-msb-paths
    (character-listp (vcd-lookup-msb-paths paths snapshot))
    :hints(("Goal" :in-theory (enable vcd-lookup-msb-paths)))))


(defsection maybe-compress-chars

; Changes to vectors may omit the first character in certain cases.  So this
; just drops any unnecessary leading bits.

  (defund maybe-compress-chars (x)
    (declare (xargs :guard (character-listp x)))
    (cond ((atom x)
           nil)
          ((atom (cdr x))
           x)
          (t
           (case (first x)
             (#\1 x) ;; no way to shorten
             (#\0
              (if (or (eql (second x) #\0)
                      (eql (second x) #\1))
                  (maybe-compress-chars (cdr x))
                x))
             (#\X
              (if (eql (second x) #\X)
                  (maybe-compress-chars (cdr x))
                x))
             (#\Z
              (if (eql (second x) #\Z)
                  (maybe-compress-chars (cdr x))
                x))))))

  (defthm character-listp-of-maybe-compress-chars
    (implies (character-listp x)
             (character-listp (maybe-compress-chars x)))
    :hints(("Goal" :in-theory (enable maybe-compress-chars)))))


(defsection vcd-multivec-print-updates

; Print the updates for a multivector that presumably needs to be updated.

  (defund vcd-update-chars (paths snapshot)
    ;; Look up paths, turn them into characters, and put the update into the
    ;; right format for a value_change in the vcd file format.
    (declare (xargs :guard t))
    (b* (((when (atom paths))
          (er hard? 'vcd-update-chars "no paths?"))
         (chars (vcd-lookup-msb-paths paths snapshot)))
      (if (atom (cdr paths))
          ;; Scalar update, just the value.
          chars
        ;; Vector update.  We just use b followed by the compressed chars.
        (cons #\b (append (maybe-compress-chars chars) (list #\Space))))))

  (defthm character-listp-of-vcd-update-chars
    (character-listp (vcd-update-chars paths snapshots))
    :hints(("Goal" :in-theory (enable vcd-update-chars))))


  (defund vcd-multivec-print-updates1
    (idcodes ; list of identifier codes that all share the same update
     update  ; update string for these vectors
     ostream)
    (declare (xargs :guard (and (string-listp idcodes)
                                (stringp update)
                                (vl-printedlist-p ostream))))
    (b* (((when (atom idcodes))
          ostream)
         (ostream (cons update ostream))
         (ostream (cons (car idcodes) ostream))
         (ostream (cons #\Newline ostream)))
      (vcd-multivec-print-updates1 (cdr idcodes) update ostream)))

  (defthm vl-printedlist-p-of-vcd-multivec-print-updates1
    (implies (and (string-listp idcodes)
                  (stringp update)
                  (vl-printedlist-p ostream))
             (vl-printedlist-p
              (vcd-multivec-print-updates1 idcodes update ostream)))
    :hints(("Goal" :in-theory (enable vcd-multivec-print-updates1))))

  (defund vcd-multivec-print-updates (x snapshot ostream)
    (declare (xargs :guard (and (vcd-multivector-p x)
                                (vl-printedlist-p ostream))))
    (b* (((vcd-multivector x) x)
         (update-chars (vcd-update-chars x.bits snapshot))
         (update       (coerce update-chars 'string)))
      (vcd-multivec-print-updates1 x.idcodes update ostream)))

  (defthm vl-printedlist-p-of-vcd-multivec-print-updates
    (implies (and (force (vcd-multivector-p x))
                  (force (vl-printedlist-p ostream)))
             (vl-printedlist-p (vcd-multivec-print-updates x snapshot ostream)))
    :hints(("Goal" :in-theory (enable vcd-multivec-print-updates)))))


(defsection vcd-multiveclist-print-updates

  (defund vcd-multiveclist-print-updates (x snapshot ostream)
    (declare (xargs :guard (and (vcd-multivectorlist-p x)
                                (vl-printedlist-p ostream))))
    (b* (((when (atom x))
          ostream)
         (ostream (vcd-multivec-print-updates (car x) snapshot ostream)))
      (vcd-multiveclist-print-updates (cdr x) snapshot ostream)))

  (defthm vl-printedlist-p-of-vcd-multiveclist-print-updates
    (implies (and (force (vcd-multivectorlist-p x))
                  (force (vl-printedlist-p ostream)))
             (vl-printedlist-p (vcd-multiveclist-print-updates x snapshot ostream)))
    :hints(("Goal" :in-theory (enable vcd-multiveclist-print-updates)))))



; ----------------------------------------------------------------------------
;
;                     Identifying Updated Multivectors
;
; ----------------------------------------------------------------------------

; When we do a diff between two snapshots, we are going to find that certain
; canonical paths have changed.  But the VCD file doesn't have dumps of
; canonical path differences, it has dumps of vector differences.  So we need
; to be able to figure out which multivectors are affected when a particular
; signal has changed.
;
; We start by setting up two mappings.
;
; First, we stuff all of the multivectors into the VCDARR array.  The basic
; point of this is that it gives us a way to refer to multivectors by index,
; instead of having to hons them and stick them in hash tables.
;
; Next, we build a PATHMAP that binds each canonical path to the list of array
; indices that it affects.
;
; Then, when we do our diff, we can
;
;  (1) sweep through the snapshots and construct an IDXHASH that records
;      the indices of which multivectors have been updated, and
;
;  (2) sweep through the IDXHASH, looking up the affected multivectors,
;      and print out their updates.

(defund anyp (x)
  (declare (ignore x)
           (xargs :guard t))
  t)

(defund vcd-index-p (x len)
  (declare (xargs :guard (natp len)))
  (and (natp x)
       (< x len)))

(defthm vcd-index-p-of-nil
  (equal (vcd-index-p nil len)
         nil)
  :hints(("Goal" :in-theory (enable vcd-index-p))))

(deflist vcd-indexlist-p (x len)
  (vcd-index-p x len)
  :elementp-of-nil nil
  :guard (natp len))

(defalist vcd-pathmap-p (x len)
  :key (anyp x)
  :val (vcd-indexlist-p x len)
  :guard (natp len)
  :keyp-of-nil t
  :valp-of-nil t)

(defalist vcd-idxhash-p (x len)
  :key (vcd-index-p x len)
  :val (not x)
  :keyp-of-nil nil
  :valp-of-nil t
  :guard (natp len))

(defsection vcdarr

  (defstobj vcd$
    (vcdarr :type (array t (0))
            :initially nil
            :resizable t)
    :inline t)

  (defthm vcdarrp-removal
    (equal (vcdarrp x)
           (true-listp x))))

;; (VCD-LOADARR X VCD$) loads the array X into VCDARR.
(acl2::def-load-stobj-array vcd-loadarr
                            :stobj vcd$
                            :stobjp vcd$p
                            :index *vcdarri*
                            :arrp vcdarrp
                            :update-arri update-vcdarri
                            :resize-arr resize-vcdarr
                            :arr-length vcdarr-length
                            :elemp nil
                            :default nil)

(defsection vcd-pathmap

; The PATHMAP binds paths to lists of indices of multivectors that depend on
; that path.

  (defund vcd-pathmap-aux
    (n     ; index of some vector in the vcdarr
     paths ; list of canonical paths associated with this multivector
     acc ; that path map we're building (binds canonical paths to vcdarr indices)
     )
    (declare (xargs :guard (natp n)))
    (b* (((when (atom paths))
          acc)
         (path1 (car paths))
         (look  (cdr (hons-get path1 acc)))
         (acc   (hons-acons path1 (cons n look) acc)))
      (vcd-pathmap-aux n (cdr paths) acc)))

  (local (defthm vcd-pathmap-p-of-vcd-pathmap-aux
           (implies (and (vcd-pathmap-p acc len)
                         (natp n)
                         (< n len))
                    (vcd-pathmap-p (vcd-pathmap-aux n paths acc) len))
           :hints(("Goal" :in-theory (enable vcd-pathmap-aux
                                             vcd-index-p)))))

  (defund vcd-pathmap-main (n vecs acc)
    (declare (xargs :guard (and (natp n)
                                (vcd-multivectorlist-p vecs))))
    (b* (((when (atom vecs))
          acc)
         (vec1 (car vecs))
         (bits (vcd-multivector->bits vec1))
         (acc  (vcd-pathmap-aux n bits acc)))
      (vcd-pathmap-main (+ 1 n) (cdr vecs) acc)))

  (local (defthm main-lemma
           (implies (and (vcd-pathmap-p acc len)
                         (natp n)
                         (<= (+ n (len vecs)) len))
                    (vcd-pathmap-p (vcd-pathmap-main n vecs acc) len))
           :hints(("Goal" :in-theory (enable vcd-pathmap-main)))))

  (defund vcd-pathmap (vecs)
    (declare (xargs :guard (vcd-multivectorlist-p vecs)))
    (vcd-pathmap-main 0 vecs (len vecs)))

  (defthm vcd-pathmap-p-of-vcd-pathmap
    (vcd-pathmap-p (vcd-pathmap vecs) (len vecs))
    :hints(("Goal" :in-theory (enable vcd-pathmap))))

  ;; We probably don't especially need to memoize this, but it might be
  ;; convenient and save a tiny bit of time when doing multiple vcd-dumps
  ;; during the same session.
  (memoize 'vcd-pathmap))


(defsection vcd-diff-snapshots

; This is our main diffing function.  We are given two snapshots, the pathmap,
; and IDXHASH which we are constructing.  The IDXHASH will eventually bind the
; index of every affected multivector to NIL.  We'll then compute the updates
; for these vectors and print them out.

; FUNDAMENTAL: We expect the snapshots to have identical keys!

  (defund vcd-update-idxhash (indices idxhash)
    ;; We just want to bind everything in INDICIES to NIL.  We take care not to
    ;; rebind things that are already bound, so the IDXHASH alist will just
    ;; have one entry for every index it binds.
    (declare (xargs :guard t))
    (b* (((when (atom indices))
          idxhash)
         ((when (hons-get (car indices) idxhash))
          (vcd-update-idxhash (cdr indices) idxhash))
         (idxhash (hons-acons (car indices) nil idxhash)))
      (vcd-update-idxhash (cdr indices) idxhash)))

  (local (defthm vcd-idxhash-p-of-vcd-update-idxhash
           (implies (and (vcd-indexlist-p indices len)
                         (vcd-idxhash-p idxhash len))
                    (vcd-idxhash-p (vcd-update-idxhash indices idxhash) len))
           :hints(("Goal" :in-theory (enable vcd-update-idxhash)))))

  (local (defthm alist-keys-under-iff-when-cons-listp
           (implies (cons-listp x)
                    (iff (alist-keys x)
                         (consp x)))
           :hints(("Goal" :in-theory (enable alist-keys)))))

  (defund vcd-diff-snapshots
    (snap1   ; old snapshot
     snap2   ; new snapshot
     pathmap ; fal of canonical path -> vcdarr index list
     idxhash ; fal whose keys are the vcdarr indices that need to be updated
     )
    "Returns IDXHASH'"
    (declare (xargs :guard (and (cons-listp snap1)
                                (cons-listp snap2))))
    (b* (((when (atom snap1))
          idxhash)
         ((when (atom snap2))
          (er hard? 'vcd-diff-snapshots "snapshots have different lengths!")
          idxhash)
         (val1 (cdar snap1))
         (val2 (cdar snap2))
         ((when (equal val1 val2))
          (vcd-diff-snapshots (cdr snap1) (cdr snap2) pathmap idxhash))
         (path    (caar snap1))
         (indices (cdr (hons-get path pathmap)))
         (idxhash (vcd-update-idxhash indices idxhash)))
      (vcd-diff-snapshots (cdr snap1) (cdr snap2) pathmap idxhash)))

  (defthm vcd-idxhash-p-of-vcd-diff-snapshots
    (implies (and (vcd-pathmap-p pathmap len)
                  (vcd-idxhash-p idxhash len))
             (vcd-idxhash-p (vcd-diff-snapshots snap1 snap2 pathmap idxhash) len))
    :hints(("Goal" :in-theory (enable vcd-diff-snapshots)))))


(defsection vcd-print-idxhash

; Now we just iterate over the idxhash, look up the corresponding multivectors,
; and print them out.

  (defund vcd-print-idxhash (idxhash snapshot vcd$ ostream)
    (declare (xargs :stobjs vcd$
                    :guard (and (vcd-idxhash-p idxhash (vcdarr-length vcd$))
                                (vcd-multivectorlist-p (acl2::nth-nx *vcdarri* vcd$))
                                (vl-printedlist-p ostream))
                    :guard-hints(("Goal" :in-theory (enable vcd-index-p)))))
    (b* (((when (atom idxhash))
          ostream)
         (entry   (vcdarri (caar idxhash) vcd$))
         (ostream (vcd-multivec-print-updates entry snapshot ostream)))
      (vcd-print-idxhash (cdr idxhash) snapshot vcd$ ostream)))

  (defthm vl-printedlist-p-of-vcd-print-idxhash
    (implies (and (vl-printedlist-p ostream)
                  (vcd-idxhash-p idxhash (vcdarr-length vcd$))
                  (vcd-multivectorlist-p (acl2::nth-nx *vcdarri* vcd$)))
             (vl-printedlist-p (vcd-print-idxhash idxhash snapshot vcd$ ostream)))
    :hints(("Goal" :in-theory (enable vcd-print-idxhash vcd-index-p)))))


(defsection vcd-write-diff

; Just bundles up vcd-diff-snapshots and vcd-print-idxhash.

  (defund vcd-write-diff (snap1 snap2 pathmap vcd$ ostream)
    (declare (xargs :guard (and (cons-listp snap1)
                                (cons-listp snap2)
                                (vcd-pathmap-p pathmap (vcdarr-length vcd$))
                                (vcd-multivectorlist-p (acl2::nth-nx *vcdarri* vcd$))
                                (vl-printedlist-p ostream))
                    :stobjs vcd$))
    (b* ((idxhash
          ;; The size of the vcd array should be a good upper bound for the idxhash
          ;; size, to avoid needing to ever rehash it.
          (vcdarr-length vcd$))
         (idxhash (vcd-diff-snapshots snap1 snap2 pathmap idxhash))
         (ostream
          ;; Snap2 is the new snapshot, so get the values for the affected vectors
          ;; out of it.
          (with-fast-alist snap2
            (vcd-print-idxhash idxhash snap2 vcd$ ostream))))
      (fast-alist-free idxhash)
      ostream))

  (defthm vl-printedlist-p-of-vcd-write-diff
    (implies (and (vl-printedlist-p ostream)
                  (vcd-pathmap-p pathmap (vcdarr-length vcd$))
                  (vcd-multivectorlist-p (acl2::nth-nx *vcdarri* vcd$)))
             (vl-printedlist-p (vcd-write-diff snap1 snap2 pathmap vcd$ ostream)))
    :hints(("Goal" :in-theory (enable vcd-write-diff)))))


; ----------------------------------------------------------------------------
;
;                         Full Snapshot Dumping
;
; ----------------------------------------------------------------------------

(defsection vcd-dump-snapshots-loop

; BOZO if we start using parallelism, this loop could be split up and
; parallelized

  (defund vcd-dump-snapshots-loop (prev-snapshot snapshots pathmap vcd$ ostream time)
    (declare (xargs :guard (and (natp time)
                                (cons-listp prev-snapshot)
                                (cons-list-listp snapshots)
                                (vcd-pathmap-p pathmap (vcdarr-length vcd$))
                                (vcd-multivectorlist-p (acl2::nth-nx *vcdarri* vcd$))
                                (vl-printedlist-p ostream))
                    :stobjs vcd$))
    (b* (((when (atom snapshots))
          ostream)

; Basic format for each diff inside the VCD file is:
;   #3
;   $dumpall
;     [snapshot data]
;   $end

         (ostream (cons #\# ostream))
         (ostream (cons (str::natstr time) ostream))
         (ostream (cons "
$dumpall
" ostream))
         (ostream (vcd-write-diff prev-snapshot (car snapshots) pathmap vcd$ ostream))
         (ostream (cons "$end
" ostream)))
      (vcd-dump-snapshots-loop (car snapshots) (cdr snapshots)
                               pathmap vcd$ ostream (+ 1 time))))

  (defthm vl-printedlist-p-of-vcd-dump-snapshots-loop
    (implies (and (vl-printedlist-p ostream)
                  (vcd-pathmap-p pathmap (vcdarr-length vcd$))
                  (vcd-multivectorlist-p (acl2::nth-nx *vcdarri* vcd$)))
             (vl-printedlist-p (vcd-dump-snapshots-loop prev-snapshot snapshots
                                                        pathmap vcd$ ostream time)))
    :hints(("Goal" :in-theory (enable vcd-dump-snapshots-loop)))))



(defsection vcd-dump-main

  (defund vcd-dump-main (mod snapshots date)
    (declare (xargs :guard (and (good-esim-modulep mod)
                                (true-listp snapshots)
                                (cons-list-listp snapshots)
                                (stringp date))))
    (b* (((unless (consp snapshots))
          (er hard? 'vcd-dump-main "No snapshots?"))

         ;; Main prelude stuff, which is all memoized
         ((mv vecs vcd-hierarchy) (cwtime (vcd-gather-vectors mod)))
         (mvecs (cwtime (vcd-compress-vectors vecs)))
         (pathmap (cwtime (vcd-pathmap mvecs)))

         ;; Initializing the array can't really be memoized since it's stobj
         ;; based, but should be very cheap
         ((acl2::local-stobjs vcd$) (mv vcd$ ostream))
         (vcd$ (cwtime (vcd-loadarr mvecs vcd$)))

         ;; Initialize ostream with random header junk, hierarchy stuff
         (ostream nil)
         (ostream (cons "$date " ostream))
         (ostream (cons (string-fix date) ostream))
         ;; Timescale doesn't really make any sense, so we put in a totally
         ;; nonsensical timescale of 1 second.
         (ostream (cons "
$end
$version ESIM Simulation
$end
$timescale 1 s
$end
" ostream))
         (ostream (cons vcd-hierarchy ostream))
         (ostream (cons "
$enddefinitions
$end
" ostream))

         ;; Duplicate the final snapshot because otherwise it looks like the
         ;; simulation ends early in a weird way
         (snapshots (append snapshots (last snapshots)))

         ;; We have to print all the vectors in the first time slot.
         (ostream (cons "#0
$dumpall
" ostream))
         (snap1 (car snapshots))
         (ostream (cwtime (with-fast-alist snap1
                            (vcd-multiveclist-print-updates mvecs snap1 ostream))))
         (ostream (cons "$end
" ostream))
         (ostream (cwtime (vcd-dump-snapshots-loop (car snapshots)
                                                   (cdr snapshots)
                                                   pathmap vcd$ ostream 1))))
      ;; This really only returns one value, vcd$ is local
      (mv vcd$ ostream)))

  (local (in-theory (enable vcd-dump-main)))

  (defthm vl-printedlist-p-of-vcd-dump-main
    (vl-printedlist-p (vcd-dump-main mod snapshots date))))







#||



(defconst *vecs* 
  (vcd-gather-vectors acl2::|*fadd*|))

(time$ (load-vcdarr *vecs* vcd$)) ;; 0.1 second 

(defconsts *pathmap*
  (vcd-pathmap 0 *vecs* (* 2 (len *vecs*))))




(i-am-here)



(include-book ;; fool dependency scanner
 "tools/defconsts" :dir :system)
(include-book ;; fool dependency scanner
 "tools/plev" :dir :system)
(include-book ;; fool dependency scanner
 "centaur/misc/memory-mgmt-raw" :dir :system)
(value-triple (acl2::set-max-mem (* 8 (expt 2 30))))

(defconsts (*mods* state)
  (serialize-read "/n/fv2/translations/nightly/cnr/esims.sao" :verbosep t))

(defun esims-to-defconsts-fn (esims)
  (if (atom esims)
      nil
    (cons `(defconst ,(gpl :n (car esims))
             ',(car esims))
          (esims-to-defconsts-fn (cdr esims)))))

(defmacro esims-to-defconsts ()
  (cons 'progn (esims-to-defconsts-fn *mods*)))

(esims-to-defconsts)


(plev-mid)

(time$ (vcd-gather-vectors acl2::|*iu*|))    ;; 2.42 seconds, 270 MB
(time$ (vcd-gather-vectors acl2::|*fadd*|))  ;; 3.6 seconds, 367 MB
(time$ (vcd-gather-vectors acl2::|*mmx*|))   ;; 1.04 sec, 104 MB
(time$ (vcd-gather-vectors acl2::|*fdmul*|)) ;; 5.22 seconds, 649 MB


(defconsts (*iu-vecs* *iu-hier*)
  (vcd-gather-vectors acl2::|*iu*|))

(defconsts (*fadd-vecs* *fadd-hier*)
  (vcd-gather-vectors acl2::|*fadd*|))
  
(defconsts *iu-mvecs*
  (vcd-compress-vectors *iu-vecs*))

(defconsts *fadd-mvecs*
  (vcd-compress-vectors *fadd-vecs*))



#!ACL2
;; (local
;;  (defsection esim-sexpr-simp-alist-orderings

;;    (local (include-book "../esim/esim-sexpr"))
;;    (local (include-book "../esim/steps"))

;;    (local (defthm alist-keys-of-esim-sexpr-simp-out
;;             (equal (alist-keys (esim-sexpr-simp-out mod in st))
;;                    (alist-keys (esim-sexpr-simp-out mod in2 st2)))
;;             :rule-classes nil
;;             :hints(("Goal"
;;                     :expand ((esim-sexpr-simp-out mod in st)
;;                              (esim-sexpr-simp-out mod in2 st2))))))

;;    (local (defthm alist-keys-of-esim-sexpr-simp-int
;;             (equal (alist-keys (esim-sexpr-simp-int mod in st))
;;                    (alist-keys (esim-sexpr-simp-int mod in2 st2)))
;;             :rule-classes nil
;;             :hints(("Goal"
;;                     :expand ((esim-sexpr-simp-int mod in st)
;;                              (esim-sexpr-simp-int mod in2 st2))))))))


||#