Skip to content

Instantly share code, notes, and snippets.

@rrbutani
Last active February 18, 2026 02:57
Show Gist options
  • Select an option

  • Save rrbutani/e60bc6c0ddf7bd23f751c57390b2ff78 to your computer and use it in GitHub Desktop.

Select an option

Save rrbutani/e60bc6c0ddf7bd23f751c57390b2ff78 to your computer and use it in GitHub Desktop.

VCS UUM Library Experiments

....

Notes on Library Ordering

notes:

  • post-order (children before parents) order is what makes sense for -liblist because it keeps module resolution in line with analysis construct resolution: the latter can only operate on the transitive closure of libraries declared as deps, not reverse dependencies
  • by ordering the elab-time library search order the same way, we provide consistency
  • further, this (post-order instead of pre-order) is what makes sense from a composition standpoint: a library author gets to choose their dependencies (or at least, is aware of them and runs elab on their own designs with said dependencies) but cannot choose or know anything about reverse dependencies
    • thus, it's reasonable to expect a library author to deal with undesired name collisions between the library and its dependencies but its not reasonable to expect a library author to deal with name collision issues coming from a reverse dependency
    • for example, say we have library dependency chain: A -> B -> C
      • and lets say B has a module that uses frob from C but that A also has a frob module
      • post-order gives us: C,B,A: under this scheme B's use of frob will correctly resolve to C
        • this is the same as when B was elaborated (in testing) without A
      • pre-order would give us: A,B,C: under this scheme B's use of frob would incorrectly resolve to A!
        • the author of B doesn't really have any way to anticipate this or a way to deal with it (short of writing config files and such)
    • the counterargument to the above is the opposite scenario, e.g. say we have the same library dependency chain A -> B -> C but:
      • A has a module that top that instantiates foo (also defined in A)
      • C also has a module foo
      • when using the post-order ordering (C,B,A), A::top's instantiation of foo (incorrectly) resolves to C::foo
      • a few things distinguish this case from the above:
        • this case arises during A's development and is a result of a choice (the use of B which pulls in C) that's made by or at least known to the developers of A
        • by contrast the case above only comes up when B is composed into something; spookier
        • here it's reasonable to expect A's devs to work around this
        • this particular case is also trivially avoidable with -liblist_work

scenario

there are, however, thornier cases where the composition of multiple libraries with common dependencies can cause resolution issues; for example:

flowchart TD
  e
  d --> e
  c
  b --> c & d
  a --> e & b
Loading
sv_library(name = "e", ...)
sv_library(name = "d", deps = [":e"], ...)
sv_library(name = "c", ...)
sv_library(name = "b", deps = [":c", ":d"], ...)
sv_library(name = "a", deps = [":e", ":b"], ...)

sv_top(name = "d_top", lib = ":d") # lib-order: e, d
sv_top(name = "b_top", lib = ":b") # lib-order: c, e, d, b
sv_top(name = "a_top", lib = ":a") # lib-order: e, c, d, b, a

let's say that:

  • d has a module delta that instantiates module foo from e
  • c also has a module foo

for d_top, everything works fine: d::delta will resolve foo to e::foo

for b_top there is breakage: d::delta will resolve foo to c::foo

however in a_top, because a specified a dep on e, it is pulled to the start of the library search order and so d::delta once again resolves to e::foo...

we could solve this specific case by arranging for the lib-order of b_top to be e, d, c, b (i.e. shift around the order of deps on b_top) but this isn't a general solution, i.e. you can craft scenarios where no static ordering of libraries (even with -liblist_work) results in all modules being specified as desired...

I think the "solution" for such cases is "rename your modules" or "write some V2K configs"...

outputs/
ucli.key
module frob;
import common::name;
initial begin
$display("FROB(a): %s", name());
end
endmodule: frob
module fibbit;
import common::name;
initial begin
$display("FIBBIT(a): %s", name());
end
endmodule: fibbit
module top;
import a_pkg::*;
import common::name;
frob f();
flub f2();
flummox f3();
initial begin
$display("A = %0d", A);
$display("%s", name());
end
endmodule
-- library A
LIB_E : ./outputs/lib_e
LIB_D : ./outputs/lib_d
LIB_C : ./outputs/lib_c
LIB_B : ./outputs/lib_b
LIB_A : ./outputs/lib_a
// package common;
// function static string name();
// return "hello from lib a";
// endfunction: name
// endpackage: common
package a_pkg;
parameter int A = 1 + b_pkg::B + e_pkg::E;
endpackage
module flub;
import common::name;
initial begin
$display("FLUB(a): %s", name());
end
endmodule: flub
-- library B
LIB_E : ./outputs/lib_e
LIB_D : ./outputs/lib_d
LIB_C : ./outputs/lib_c
LIB_B : ./outputs/lib_b
package common;
function static string name();
return "hello from lib b";
endfunction: name
endpackage: common
package b_pkg;
parameter int B = 2 + c_pkg::C + d_pkg::D;
endpackage
-- library C
LIB_C : ./outputs/lib_c
package common;
function static string name();
return "hello from lib c";
endfunction: name
endpackage: common
package c_pkg;
parameter int C = 3;
endpackage
-- library D
LIB_D : ./outputs/lib_d
LIB_E : ./outputs/lib_e
package common;
function static string name();
return "hello from lib d";
endfunction: name
endpackage: common
package d_pkg;
parameter int D = 4 + e_pkg::E;
endpackage
module frob;
import common::name;
initial begin
$display("FROB(e): %s", name());
end
endmodule: frob
module flub;
import common::name;
initial begin
$display("FLUB(e): %s", name());
end
endmodule: flub
module fibbit;
import common::name;
initial begin
$display("FIBBIT(e): %s", name());
end
endmodule: fibbit
module flummox;
import common::name;
fibbit f();
initial begin
$display("FLUMMOX(e): %s", name());
end
endmodule: flummox
-- library E
LIB_E : ./outputs/lib_e
package common;
function static string name();
return "hello from lib e";
endfunction: name
endpackage: common
package e_pkg;
parameter int E = 5;
endpackage
digraph lib_deps {
e
d -> e
c
b -> c; b -> d
a -> b; a -> e
}
#!/usr/bin/env bash
# (in the appropriate env with `vlogan` and `vcs` on the PATH)
set -euo pipefail
shopt -s extglob
# $1: prefix, $2: library name, $3+: dependent libs
vlogan() {
>&2 echo " Analyzing $2"
deps=("${@:3}")
SYNOPSYS_SIM_SETUP=$(realpath "./$1.synopsys_sim.setup") \
command vlogan \
-nc -q \
+lint=all \
-sverilog \
${1}*_pkg.sv \
$(find -iname "${1}*.sv" | grep -v '_pkg\.sv') \
${deps[@]/#/-liblist } \
-liblist "$2" \
-work "$2"
}
# $1: prefix, $2: top, $3+: dependent libs
vcs() {
>&2 echo "Elaborating ${@:2}"
deps=("${@:3}")
mkdir -p outputs/sim
SYNOPSYS_SIM_SETUP=$(realpath "./$1.synopsys_sim.setup") \
command vcs \
-nc -q \
"${2}" \
-Mdirectory=./outputs/sim/csrc \
${deps[@]/#/-liblist } \
-liblist_work \
-o "outputs/sim/simv"
}
#-------------------------------------------------------------------------------
rm -rf outputs
# post-order:
# pfx lib transitive deps
vlogan e LIB_E
vlogan d LIB_D LIB_E
vlogan c LIB_C
vlogan b LIB_B LIB_E LIB_D LIB_C # direct deps: LIB_D LIB_C
vlogan a LIB_A LIB_E LIB_D LIB_C LIB_B # direct deps: LIB_E LIB_B
# vcs a top LIB_E LIB_D LIB_C LIB_B LIB_A
vcs a top LIB_A LIB_B LIB_C LIB_D LIB_E
./outputs/sim/simv -nc -q
#-------------------------------------------------------------------------------
# findings re: libraries and three-step compile:
# - you have to give `vlogan` not just the direct library dependencies of the
# library being analyzed but all transitive library dependencies too!
# - it's not just `synopsys_sim.setup` that needs to have transitive library
# dependencies; `-liblist` (if specified) must contain them too
# - presumably there's a better way to get at this info but: if you remove a
# transitive dep from `-liblist`/`synopsys_sim.setup` then you get an error
# that prints the effective liblist
# - you can pass `-liblist` multiple times instead of needing 1 huge arg with
# `+`s
#-------------------------------------------------------------------------------
# things to try:
# - try changing the order of libraries to the `vlogan lib_a` call
# + this should result in the "hello from ..." message changing
#
# - with library order A..E, `vcs` will resolve `flub` in `a` to `LIB_A::flub`
# - with order E..A, `flub` in `a` resolves to `LIB_E::flub`
# - with order E..A (but with `-liblist_work`), `flub` in `a` resolves to
# `LIB_A::flub`
#
# - with A..E, `E::flummox`'s `fibbit` resolves to `LIB_A::fibbit`
# - with E..A, `E::flummox`'s `fibbit` resolves to `LIB_E::fibbit`
# - with A..E + liblist_work, `E::flummox`'s `fibbit` -> `LIB_E::fibbit`
# + this shows `liblist_work` working as expected
# - with E..A + liblist_work, `E::flummox`'s `fibbit` -> `LIB_E::fibbit`
# findings re: `liblist_work` and library resolution and ordering:
# - for at least some constructs (i.e. packages), the order of libraries at
# *analysis* time is what matters
# - `-liblist_work` semantics seem to be implied, for analysis time constructs
#
# - module resolution is based on the order of libraries given to `vcs`, not
# to the `vlogan` invocations
# - if `-liblist_work` is given, vcs will look in the current library first
# and will then fall back to the search order
# - module resolution is no different if a module is defined in the same file
# as its use (see `frob` defined in `a.sv` — when the search order is E..A,
# it gets resolved to `LIB_E::frob`)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment