Merge branch 'main' into sim

This commit is contained in:
Akash Levy 2026-03-03 20:58:07 -08:00
commit d05236907a
23 changed files with 2545 additions and 70 deletions

View File

@ -35,14 +35,14 @@ runs:
if: runner.os == 'Linux'
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
with:
packages: gawk git make python3 bison clang flex libffi-dev libfl-dev libreadline-dev pkg-config tcl-dev zlib1g-dev libnsl-dev libdwarf-dev libelf-dev elfutils libdw-dev ccache
packages: gawk git make python3 bison clang flex libffi-dev libfl-dev libreadline-dev pkg-config tcl-dev zlib1g-dev libnsl-dev libdwarf-dev libelf-dev elfutils libdw-dev ccache
version: ${{ inputs.runs-on }}-commonys
- name: Linux build dependencies
if: runner.os == 'Linux' && inputs.get-build-deps == 'true'
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
with:
packages: bison clang flex libffi-dev libfl-dev libreadline-dev pkg-config tcl-dev zlib1g-dev libgtest-dev
packages: gawk git make python3 bison clang flex libffi-dev libfl-dev libreadline-dev pkg-config tcl-dev zlib1g-dev libnsl-dev libdwarf-dev libelf-dev elfutils libdw-dev ccache libgtest-dev
version: ${{ inputs.runs-on }}-buildys
- name: Linux docs dependencies

230
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,230 @@
name: Release (pyosys wheels)
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: write
jobs:
build-linux-wheel:
runs-on: ubuntu-latest
name: Build Linux amd64 wheel (musl)
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Build wheel in Alpine container
run: |
docker run --rm \
-v "${{ github.workspace }}:/src" \
-w /src \
--platform linux/amd64 \
alpine:3.20 sh -c '
set -ex
apk add --no-cache \
build-base bison flex flex-dev gperf \
tcl-dev readline-dev zlib-dev libffi-dev \
libdwarf-dev elfutils-dev \
python3 python3-dev py3-pip py3-setuptools py3-wheel \
git pkgconf ccache
git config --global --add safe.directory /src
git submodule foreach --recursive git config --global --add safe.directory \$toplevel/\$sm_path
# Alpine/musl compatibility shims for Verific tclmain link step
ln -sf /usr/lib/libtcl8.6.so /usr/lib/libtcl.so
echo "void dummy_nsl(void){}" | gcc -shared -o /usr/lib/libnsl.so -x c -
# Build Verific TCL main (needed before Yosys)
cd /src/verific/tclmain
make
cd /src
# Build the wheel via setup.py
pip install --break-system-packages pybind11 cxxheaderparser
_PYOSYS_OVERRIDE_VER=$(
grep "^YOSYS_VER " Makefile | head -1 | sed "s/.*:= *//" | tr "+" "."
) python3 setup.py bdist_wheel --dist-dir /src/dist
'
- uses: actions/upload-artifact@v4
with:
name: linux-musl-wheel
path: dist/*.whl
build-manylinux-wheel:
runs-on: ubuntu-latest
name: Build Linux amd64 wheel (manylinux2014, glibc 2.17+)
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Build wheel in manylinux2014 container
run: |
docker run --rm \
-v "${{ github.workspace }}:/src" \
-w /src \
--platform linux/amd64 \
quay.io/pypa/manylinux2014_x86_64 bash -c '
set -ex
yum install -y tcl-devel readline-devel zlib-devel libffi-devel \
flex gperf ccache patchelf \
elfutils-devel elfutils-libelf-devel libdwarf-devel
# Build bison >= 3.x from source
curl -L https://ftp.gnu.org/gnu/bison/bison-3.8.2.tar.gz | tar -xzC /tmp
cd /tmp/bison-3.8.2 && ./configure && make -j$(nproc) && make install
cd /src
# Use the manylinux2014 Python 3.13
export PATH=/opt/python/cp313-cp313/bin:$PATH
git config --global --add safe.directory /src
git submodule foreach --recursive git config --global --add safe.directory \$toplevel/\$sm_path
# musl/manylinux compatibility shim for Verific tclmain link step
echo "void dummy_nsl(void){}" | gcc -shared -o /usr/lib64/libnsl.so -x c - || true
# Build Verific TCL main
cd /src/verific/tclmain
make
cd /src
pip3 install --upgrade pip setuptools wheel pybind11 cxxheaderparser
_PYOSYS_OVERRIDE_VER=$(
grep "^YOSYS_VER " Makefile | head -1 | sed "s/.*:= *//" | tr "+" "."
) python3 setup.py bdist_wheel --dist-dir /src/dist-manylinux
# Tag wheel as manylinux2014
pip3 install auditwheel || true
for whl in /src/dist-manylinux/*.whl; do
auditwheel repair "$whl" --plat manylinux2014_x86_64 -w /src/dist-manylinux/ 2>/dev/null || true
done
'
- uses: actions/upload-artifact@v4
with:
name: linux-manylinux-wheel
path: dist-manylinux/*.whl
build-macos-wheel:
runs-on: macos-15
name: Build macOS arm64 wheel
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
- uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install dependencies
run: |
brew install bison flex gperf tcl-tk@8 readline libffi dwarfutils libelf ccache
pip3 install pybind11 cxxheaderparser setuptools wheel
- name: Build Verific tclmain
run: |
export PATH="$(brew --prefix bison)/bin:$(brew --prefix flex)/bin:$PATH"
export MACOSX_DEPLOYMENT_TARGET=11.0
cd verific/tclmain
make
- name: Build wheel
run: |
export PATH="$(brew --prefix bison)/bin:$(brew --prefix flex)/bin:$PATH"
export MACOSX_DEPLOYMENT_TARGET=11.0
_PYOSYS_OVERRIDE_VER=$(
grep "^YOSYS_VER " Makefile | head -1 | sed "s/.*:= *//" | tr "+" "."
) python3 setup.py bdist_wheel --dist-dir dist
- uses: actions/upload-artifact@v4
with:
name: macos-wheel
path: dist/*.whl
release:
runs-on: ubuntu-latest
needs: [build-linux-wheel, build-manylinux-wheel, build-macos-wheel]
name: Create GitHub releases
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: all-wheels
merge-multiple: true
- name: Generate release notes
id: meta
run: |
SHORT_SHA=$(git rev-parse --short HEAD)
FULL_SHA=$(git rev-parse HEAD)
DATE=$(date -u +%Y-%m-%d)
echo "short_sha=$SHORT_SHA" >> "$GITHUB_OUTPUT"
echo "date=$DATE" >> "$GITHUB_OUTPUT"
REPO_URL="${{ github.server_url }}/${{ github.repository }}"
printf '%s\n' \
"Automated build from \`main\` @ [\`${SHORT_SHA}\`](${REPO_URL}/commit/${FULL_SHA})" \
"" \
"**Built:** ${DATE}" \
"" \
"### Assets" \
"| File | Platform |" \
"|------|----------|" \
"| \`pyosys-*-linux_x86_64.whl\` | Linux x86-64 (musl) |" \
"| \`pyosys-*-manylinux2014*.whl\` | Linux x86-64 (manylinux2014, glibc 2.17+) |" \
"| \`pyosys-*-macosx_*_arm64.whl\` | macOS Apple Silicon |" \
"" \
"### Installation" \
"\`\`\`bash" \
"pip install pyosys-*.whl" \
"\`\`\`" \
> release_notes.md
- name: Create permanent release
run: |
TAG="build-${{ steps.meta.outputs.date }}-${{ steps.meta.outputs.short_sha }}"
gh release create "$TAG" \
all-wheels/*.whl \
--target "${{ github.sha }}" \
--title "Build ${{ steps.meta.outputs.date }} (${{ steps.meta.outputs.short_sha }})" \
--notes-file release_notes.md
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Update latest release
run: |
git tag -f latest HEAD
git push -f origin latest
gh release delete latest --yes 2>/dev/null || true
gh release create latest \
all-wheels/*.whl \
--target "${{ github.sha }}" \
--title "Latest Build (${{ steps.meta.outputs.date }})" \
--notes-file release_notes.md \
--prerelease
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -116,7 +116,7 @@ jobs:
uses: ./.github/actions/setup-build-env
with:
runs-on: ${{ matrix.os }}
get-test-deps: true
get-build-deps: true
get-iverilog: true
- name: Download build artifact

View File

@ -29,7 +29,6 @@ ENABLE_LIBYOSYS_STATIC := 0
ENABLE_ZLIB := 1
ENABLE_HELP_SOURCE := 0
ENABLE_BACKTRACE := 1
VERIFIC_LINEFILE_INCLUDES_LOOPS := 1
# python wrappers
ENABLE_PYOSYS := 1

View File

@ -114,13 +114,54 @@ void FstData::extractVarNames()
struct fstHier *h;
std::string fst_scope_name;
// Track nested fork scopes using a stack to handle nested packed structs
// Begins with outmost scope and ends with innermost scope
// Scopes are not normalized on the stack
std::vector<std::string> fork_scope_stack;
// Start fork handles after the maximum real handle from FST file to avoid collisions
fstHandle next_fork_handle = fstReaderGetMaxHandle(ctx) + 1;
// Map of fork scopes to their members, which are all normalized
std::map<std::string, std::vector<fstHandle>> fork_scopes;
while ((h = fstReaderIterateHier(ctx))) {
switch (h->htyp) {
case FST_HT_SCOPE: {
fst_scope_name = fstReaderPushScope(ctx, h->u.scope.name, NULL);
// Fork scopes are identified by FST_ST_VCD_FORK and are pushed onto the stack
if (h->u.scope.typ == FST_ST_VCD_FORK) {
fork_scope_stack.push_back(fst_scope_name);
// Create new vector that contains struct members
normalize_brackets(fst_scope_name);
fork_scopes[fst_scope_name] = std::vector<fstHandle>();
}
break;
}
case FST_HT_UPSCOPE: {
if (!fork_scope_stack.empty() && fork_scope_stack.back() == fst_scope_name) {
// Assign a unique handle to this fork scope and increment for future forks
fstHandle fork_handle = next_fork_handle++;
// Map normalized scope name to the handle for future lookups via getHandle()
normalize_brackets(fst_scope_name);
name_to_handle[fst_scope_name] = fork_handle;
// Copy the extracted members of the fork scope to the fork scope members map
// for value lookups in valueOf()
fork_scope_members[fork_handle] = fork_scopes[fst_scope_name];
// If this is a nested fork scope, add its handle to the parent fork scope
if (fork_scope_stack.size() > 1) {
std::string parent_fork = fork_scope_stack[fork_scope_stack.size() - 2];
normalize_brackets(parent_fork);
fork_scopes[parent_fork].push_back(fork_handle);
}
// Pop this fork scope from the stack
fork_scope_stack.pop_back();
}
fst_scope_name = fstReaderPopScope(ctx);
break;
}
@ -136,6 +177,14 @@ void FstData::extractVarNames()
vars.push_back(var);
if (!var.is_alias)
handle_to_var[h->u.var.handle] = var;
// Add variable to the innermost fork scope in the fork scope stack
if (!fork_scope_stack.empty()) {
std::string current_fork = fork_scope_stack.back();
normalize_brackets(current_fork);
fork_scopes[current_fork].push_back(h->u.var.handle);
}
std::string clean_name;
bool has_space = false;
for(size_t i=0;i<strlen(h->u.var.name);i++)
@ -275,8 +324,137 @@ void FstData::reconstructAllAtTimes(std::vector<fstHandle> &signal, uint64_t sta
std::string FstData::valueOf(fstHandle signal)
{
// Check if this is a fork scope (struct)
auto it = fork_scope_members.find(signal);
if (it != fork_scope_members.end()) {
std::string result;
const std::vector<fstHandle>& members = it->second;
// Iterate over members of the struct to get concatenated value.
// The first declared member is MSB in SystemVerilog packed structs
for (auto m = members.begin(); m != members.end(); m++) {
fstHandle member = *m;
std::string member_val;
// Check if this member is itself a nested fork scope (struct)
if (fork_scope_members.find(member) != fork_scope_members.end()) {
// Recursively get the value of the nested struct
member_val = valueOf(member);
} else {
// Regular variable - look up in past_data
int expected_width = 0;
// Get the declared width of this member
if (handle_to_var.find(member) != handle_to_var.end()) {
expected_width = handle_to_var[member].width;
}
// Get the current value of the member
if (past_data.find(member) != past_data.end()) {
member_val = past_data[member];
// Pad with zeros to the expected width of the member
if (expected_width > 0 && (int)member_val.length() < expected_width) {
member_val = std::string(expected_width - member_val.length(), '0') + member_val;
}
} else if (expected_width > 0) {
// No value yet, use X to pad
member_val = std::string(expected_width, 'x');
} else { // fallback to X
member_val = "x";
}
}
// Concatenate the member value to the overall struct value
result += member_val;
}
return result;
}
// Normal signal handling
if (past_data.find(signal) == past_data.end()) {
return std::string(handle_to_var[signal].width, 'x');
}
return past_data[signal];
}
// Auto-discover scope from FST by finding the top module
std::string FstData::autoScope(Module *topmod) {
log("Auto-discovering scope from file...\n");
std::string top = RTLIL::unescape_id(topmod->name);
log("Available scopes:\n");
std::set<std::string> unique_scopes;
for (const auto& var : vars) {
unique_scopes.insert(var.scope);
}
for (const auto& scope : unique_scopes) {
log(" %s\n", scope.c_str());
}
// Option 1 - Instance based scope matching
// Will fail if the DUT instance name != the top module name
log("Trying instance-based scope matching...\n");
for (const auto& var : vars) {
// Check if this scope ends with our top module
log_debug("Checking scope: %s\n", var.scope.c_str());
if (var.scope == top ||
var.scope.find("." + top) != std::string::npos) {
// Extract the full path up to (and including) the top module
size_t pos = var.scope.find(top);
if (pos != std::string::npos) {
std::string scope = var.scope.substr(0, pos + top.length());
return scope;
}
}
}
// Option 2 - Port based scope matching
// Matches based on exact port name matching of the top module
log("Trying port-based scope matching...\n");
// Map top module port name to their bit widths (RTL reference point)
dict<std::string, int> top2widths;
for (auto wire : topmod->wires()) {
if (wire->port_input || wire->port_output) {
top2widths[RTLIL::unescape_id(wire->name)] = wire->width;
}
}
log("Extracted %d ports from top module\n", GetSize(top2widths));
// For each scope, track the number of matching ports
dict<std::string, int> scopes2matches;
for (const auto& var : vars) {
// Strip array '[]' notation from variable name
std::string var_name = var.name;
size_t bracket = var_name.find('[');
if (bracket != std::string::npos) {
var_name = var_name.substr(0, bracket);
}
// Check if this variable name matches one of our top module port names and width
if (top2widths.count(var_name) && top2widths[var_name] == var.width) {
scopes2matches[var.scope] += 1;
}
}
// Find scopes with exact matches
// If there is a tie, return the longest scope
std::string result = "";
for (const auto& entry : scopes2matches) {
int num_matches = entry.second;
if (num_matches == GetSize(top2widths)) {
std::string scope = entry.first;
if (result.empty() || scope.length() > result.length()) {
result = scope;
}
}
}
if (!result.empty()) {
return result;
}
// No match found
log_warning("Could not auto-discover scope for module '%s'...\n",
RTLIL::unescape_id(topmod->name).c_str());
return "";
}

View File

@ -57,6 +57,7 @@ class FstData
dict<int,fstHandle> getMemoryHandles(std::string name);
double getTimescale() { return timescale; }
const char *getTimescaleString() { return timescale_str.c_str(); }
std::string autoScope(Module *topmod);
private:
void extractVarNames();
@ -69,6 +70,7 @@ private:
uint64_t last_time;
std::map<fstHandle, std::string> past_data;
uint64_t past_time;
std::map<fstHandle, std::vector<fstHandle>> fork_scope_members;
double timescale;
std::string timescale_str;
uint64_t start_time;

View File

@ -3151,7 +3151,7 @@ void RTLIL::Module::swap_names(RTLIL::Cell *c1, RTLIL::Cell *c2)
RTLIL::IdString RTLIL::Module::uniquify(RTLIL::IdString name)
{
int index = 0;
int &index = uniquify_cache_[name];
return uniquify(name, index);
}

View File

@ -2168,6 +2168,7 @@ public:
void swap_names(RTLIL::Wire *w1, RTLIL::Wire *w2);
void swap_names(RTLIL::Cell *c1, RTLIL::Cell *c2);
dict<RTLIL::IdString, int> uniquify_cache_;
RTLIL::IdString uniquify(RTLIL::IdString name);
RTLIL::IdString uniquify(RTLIL::IdString name, int &index);

View File

@ -1474,8 +1474,14 @@ struct SimWorker : SimShared
log_assert(top == nullptr);
fst = new FstData(sim_filename);
timescale = fst->getTimescaleString();
if (scope.empty())
log_error("Scope must be defined for co-simulation.\n");
if (scope.empty()) {
scope = fst->autoScope(topmod);
if (scope.empty()) {
log_error("No scope found for module '%s'. Please specify -scope explicitly.\n",
RTLIL::unescape_id(topmod->name).c_str());
}
}
log("Using scope: \"%s\"\n", scope.c_str());
top = new SimInstance(this, scope, topmod);
register_signals();

View File

@ -13,6 +13,7 @@ OBJS += passes/silimate/reg_rename.o
OBJS += passes/silimate/splitfanout.o
OBJS += passes/silimate/splitlarge.o
OBJS += passes/silimate/splitnetlist.o
OBJS += passes/silimate/opt_timing_balance.o
OBJS += passes/silimate/opt_expand.o
GENFILES += passes/silimate/peepopt_expand.h

View File

@ -81,33 +81,60 @@ struct NegoptPass : public Pass {
run_post = true;
}
constexpr int max_iterations = 100;
for (auto module : design->selected_modules()) {
if (run_pre) {
// manual2sub and sub2neg only need to run once: no downstream
// pre-subpass creates the patterns they match
// separate pm instances so sub2neg sees the $sub cells manual2sub creates.
{
peepopt_pm pm(module);
pm.setup(module->selected_cells());
pm.run_manual2sub();
log_flush();
}
{
peepopt_pm pm(module);
pm.setup(module->selected_cells());
pm.run_sub2neg();
log_flush();
}
// negexpand/negneg/negmux can feed each other.
did_something = true;
while (did_something) {
for (int iter = 0; iter < max_iterations && did_something; iter++) {
did_something = false;
peepopt_pm pm(module);
pm.setup(module->selected_cells());
pm.run_manual2sub(); // Reduce manual 2's complement to subtraction first
pm.run_sub2neg();
pm.run_negexpand();
log_flush();
pm.run_negneg();
log_flush();
pm.run_negmux();
log_flush();
}
if (did_something)
log_warning("NEGOPT pre reached max iterations (%d) in module %s without convergence.\n", max_iterations, log_id(module));
}
if (run_post) {
did_something = true;
while (did_something) {
for (int iter = 0; iter < max_iterations && did_something; iter++) {
did_something = false;
peepopt_pm pm(module);
pm.setup(module->selected_cells());
pm.run_negrebuild();
log_flush();
pm.run_muxneg();
log_flush();
pm.run_neg2sub();
log_flush();
}
if (did_something)
log_warning("NEGOPT post reached max iterations (%d) in module %s without convergence.\n", max_iterations, log_id(module));
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -12,9 +12,8 @@ pattern manual2sub
state <SigSpec> minuend subtrahend result_sig root_a root_b
state <bool> is_signed
state <SigSpec> inner_y
state <SigSpec> inner_y inner_port_sig_A root_port_sig inner_port_sig_B
// 1. Match the "root" add (the one that produces the final result)
match root_add
select root_add->type == $add
set root_a port(root_add, \A)
@ -23,8 +22,7 @@ match root_add
set is_signed root_add->getParam(ID::A_SIGNED).as_bool()
endmatch
// 2. Case A: (a + ~b) + 1
// Check if root_add has a constant 1
// Case A: (a + ~b) + 1
code root_add inner_y
{
SigSpec pa = root_a;
@ -51,20 +49,28 @@ code root_add inner_y
}
endcode
// Find the inner add
// inner_y is discovered in code so gating happens in the code block
// Find the inner add whose Y feeds the non-constant port of root_add
match inner_add_A
select inner_add_A->type == $add
index <SigSpec> port(inner_add_A, \Y) === inner_y
filter nusers(port(inner_add_A, \Y)) == 2
endmatch
// Find the NOT gate on one of the ports of inner_add_A
// Branch over both ports of inner_add_A to find the NOT gate
code inner_port_sig_A
inner_port_sig_A = port(inner_add_A, \A);
branch;
inner_port_sig_A = port(inner_add_A, \B);
endcode
match not_gate_A
select not_gate_A->type == $not
index <SigSpec> port(not_gate_A, \Y) === inner_port_sig_A
endmatch
code root_add inner_add_A not_gate_A subtrahend minuend result_sig is_signed
{
// Require consistent signedness on the root add.
// Require consistent signedness across the chain
if (root_add->getParam(ID::B_SIGNED).as_bool() != is_signed)
reject;
@ -73,11 +79,7 @@ code root_add inner_add_A not_gate_A subtrahend minuend result_sig is_signed
if (inner_add_A->getParam(ID::B_SIGNED).as_bool() != is_signed)
reject;
if (port(inner_add_A, \Y) != inner_y)
reject;
if (nusers(port(inner_add_A, \Y)) != 2)
reject;
// Determine which port is the NOT output and which is the minuend
SigSpec not_y = port(not_gate_A, \Y);
SigSpec add_a = port(inner_add_A, \A);
SigSpec add_b = port(inner_add_A, \B);
@ -92,7 +94,6 @@ code root_add inner_add_A not_gate_A subtrahend minuend result_sig is_signed
subtrahend = port(not_gate_A, \A);
// Create the subtraction cell
log("manual2sub in %s: Found (a + ~b) + 1 pattern, creating $sub for %s\n", log_id(module), log_signal(result_sig));
Cell *cell = root_add;
int width = GetSize(result_sig);
@ -100,27 +101,25 @@ code root_add inner_add_A not_gate_A subtrahend minuend result_sig is_signed
// Reject if the +1 wrap boundary is narrower than the final result
if (inner_width < width)
reject;
// Anchor both operands to the inner add width to preserve carry behavior
SigSpec minuend_rs = minuend;
SigSpec subtrahend_rs = subtrahend;
// Anchor both operands to the inner add width to preserve carry behavior
minuend_rs.extend_u0(inner_width, is_signed);
subtrahend_rs.extend_u0(inner_width, is_signed);
SigSpec sub_y = result_sig;
// Extend the sub result back to the root width when needed
if (inner_width != width) {
sub_y = module->addWire(NEW_ID2_SUFFIX("sub_y"), inner_width);
}
Cell *sub = module->addSub(NEW_ID2_SUFFIX("sub"), minuend_rs, subtrahend_rs, sub_y, is_signed);
// Extend the sub result back to root width when inner is wider
if (inner_width != width) {
SigSpec sub_y_rs = sub_y;
sub_y_rs.extend_u0(width, is_signed);
module->connect(result_sig, sub_y_rs);
}
// Let fixup_parameters handle width adjustments
sub->fixup_parameters();
// Remove old cells
autoremove(root_add);
autoremove(inner_add_A);
autoremove(not_gate_A);
@ -130,23 +129,36 @@ code root_add inner_add_A not_gate_A subtrahend minuend result_sig is_signed
}
endcode
// 3. Case B: a + (~b + 1)
// Case B: a + (~b + 1)
// Branch over both ports of root_add to find the inner (~b + 1) add
code root_port_sig
root_port_sig = port(root_add, \A);
branch;
root_port_sig = port(root_add, \B);
endcode
// Find the inner add on either port of root_add
match inner_add_B
select inner_add_B->type == $add
filter port(inner_add_B, \Y) == root_a || port(inner_add_B, \Y) == root_b
select nusers(port(inner_add_B, \Y)) == 2
index <SigSpec> port(inner_add_B, \Y) === root_port_sig
filter nusers(port(inner_add_B, \Y)) == 2
endmatch
// Check if inner_add_B has a constant 1 and a NOT gate
// Branch over both ports of inner_add_B to find the NOT gate
code inner_port_sig_B
inner_port_sig_B = port(inner_add_B, \A);
branch;
inner_port_sig_B = port(inner_add_B, \B);
endcode
match not_gate_B
select not_gate_B->type == $not
index <SigSpec> port(not_gate_B, \Y) === inner_port_sig_B
endmatch
code root_add inner_add_B not_gate_B minuend subtrahend result_sig is_signed
{
// Require consistent signedness on the root add.
// Require consistent signedness across the chain
if (root_add->getParam(ID::B_SIGNED).as_bool() != is_signed)
reject;
@ -155,6 +167,8 @@ code root_add inner_add_B not_gate_B minuend subtrahend result_sig is_signed
if (inner_add_B->getParam(ID::B_SIGNED).as_bool() != is_signed)
reject;
// Verify inner_add_B has the form (~b + 1): one port is constant 1,
// the other is the NOT output
SigSpec pa = inner_add_B->getPort(ID::A);
SigSpec pb = inner_add_B->getPort(ID::B);
SigSpec not_y = port(not_gate_B, \Y);
@ -175,13 +189,13 @@ code root_add inner_add_B not_gate_B minuend subtrahend result_sig is_signed
if (!valid) reject;
// The minuend is whichever root_add port is NOT the inner_add_B output
subtrahend = port(not_gate_B, \A);
if (inner_add_B->getPort(ID::Y) == root_add->getPort(ID::A))
minuend = root_b;
else
minuend = root_a;
// Create the subtraction cell
log("manual2sub in %s: Found a + (~b + 1) pattern, creating $sub for %s\n", log_id(module), log_signal(result_sig));
Cell *cell = root_add;
int width = GetSize(result_sig);
@ -190,32 +204,30 @@ code root_add inner_add_B not_gate_B minuend subtrahend result_sig is_signed
// Reject if the +1 wrap boundary is narrower than the final result
if (inner_width < width)
reject;
// Anchor both operands to the inner add width to preserve carry behavior
SigSpec minuend_rs = minuend;
SigSpec subtrahend_rs = subtrahend;
// Anchor both operands to the inner add width to preserve carry behavior
minuend_rs.extend_u0(inner_width, is_signed);
subtrahend_rs.extend_u0(inner_width, is_signed);
SigSpec sub_y = result_sig;
// Extend the sub result back to the root width when needed
if (inner_width != width) {
sub_y = module->addWire(NEW_ID2_SUFFIX("sub_y"), inner_width);
}
Cell *sub = module->addSub(NEW_ID2_SUFFIX("sub"), minuend_rs, subtrahend_rs, sub_y, is_signed);
// Extend the sub result back to root width when inner is wider
if (inner_width != width) {
SigSpec sub_y_rs = sub_y;
sub_y_rs.extend_u0(width, is_signed);
module->connect(result_sig, sub_y_rs);
}
// Let fixup_parameters handle width adjustments
sub->fixup_parameters();
// Remove old cells
autoremove(root_add);
autoremove(inner_add_B);
autoremove(not_gate_B);

View File

@ -20,7 +20,7 @@ endmatch
match neg_a
select neg_a->type == $neg
select nusers(port(neg_a, \Y)) == 2
filter nusers(port(neg_a, \Y)) == 2
index <SigSpec> port(neg_a, \Y) === mux_a
set neg_a_in port(neg_a, \A)
set neg_a_y port(neg_a, \Y)
@ -29,7 +29,7 @@ endmatch
match neg_b
select neg_b->type == $neg
select nusers(port(neg_b, \Y)) == 2
filter nusers(port(neg_b, \Y)) == 2
index <SigSpec> port(neg_b, \Y) === mux_b
set neg_b_in port(neg_b, \A)
set neg_b_y port(neg_b, \Y)

View File

@ -28,7 +28,7 @@ endcode
match neg
select neg->type == $neg
select nusers(port(neg, \Y)) == 2
filter nusers(port(neg, \Y)) == 2
index <SigSpec> port(neg, \Y) === (neg_on_a ? add_a : add_b)
set neg_a port(neg, \A)
set neg_y port(neg, \Y)

View File

@ -20,7 +20,7 @@ endmatch
match add
select add->type == $add
index <SigSpec> port(add, \Y) === neg_a
select nusers(port(add, \Y)) == 2
filter nusers(port(add, \Y)) == 2
set add_a port(add, \A)
set add_b port(add, \B)
endmatch

View File

@ -18,8 +18,8 @@ match neg
endmatch
match mux
select mux->type == $mux
select nusers(port(mux, \Y)) == 2
select mux->type == $mux
select nusers(port(mux, \Y)) == 2
set mux_a port(mux, \A)
set mux_b port(mux, \B)
set mux_s port(mux, \S)
@ -43,7 +43,7 @@ code neg_a neg_y mux_a mux_b mux_s mux_y a_signed
}
{
// Anchor negations to the original negation width
// Anchor negations to the original neg output width
int width = GetSize(neg_y);
Cell *cell = neg;

View File

@ -18,7 +18,7 @@ endmatch
match neg2
select neg2->type == $neg
index <SigSpec> port(neg2, \Y) === neg1_a
select nusers(port(neg2, \Y)) == 2
filter nusers(port(neg2, \Y)) == 2
set neg2_a port(neg2, \A)
endmatch

View File

@ -39,16 +39,16 @@ code add_a add_b add_y neg1_a neg1_y neg2_a neg2_y add_signed add_b_signed neg1_
if (add_signed != add_b_signed)
reject;
// Require negations to share the add signedness
// Require both negations share the add's signedness
if (neg1_signed != add_signed || neg2_signed != add_signed)
reject;
{
// Avoid matching the same neg cell twice
// Avoid matching the same neg cell for both inputs
if (neg1 == neg2)
reject;
// Require a single wrap boundary for both negations and the sum
// Require a single wrap boundary across both negations and the sum
if (GetSize(neg1_y) != GetSize(neg2_y))
reject;
if (GetSize(add_y) != GetSize(neg1_y))
@ -89,11 +89,13 @@ code add_a add_b add_y neg1_a neg1_y neg2_a neg2_y add_signed add_b_signed neg1_
neg1_a_rs.extend_u0(width, add_signed);
neg2_a_rs.extend_u0(width, add_signed);
// Build -(a + b) to replace (-a) + (-b)
SigSpec sum = module->addWire(NEW_ID2_SUFFIX("sum"), width);
Cell *new_add = module->addAdd(NEW_ID2_SUFFIX("add"), neg1_a_rs, neg2_a_rs, sum, add_signed);
SigSpec neg_out = module->addWire(NEW_ID2_SUFFIX("neg_y"), width);
Cell *new_neg = module->addNeg(NEW_ID2_SUFFIX("neg"), sum, neg_out, add_signed);
// Extend result back to original add output width
SigSpec neg_out_rs = neg_out;
neg_out_rs.extend_u0(GetSize(add_y), add_signed);
module->connect(add_y, neg_out_rs);

View File

@ -188,15 +188,25 @@ struct RegRenamePass : public Pass {
}
extra_args(args, argidx, design);
// Extract top module
Module *topmod = design->top_module();
if (!topmod)
log_error("No top module found!\n");
// Extract pre-optimization register widths from VCD file
dict<std::pair<std::string, std::string>, int> vcd_reg_widths;
if (!vcd_filename.empty()) {
if (scope.empty()) {
log_error("No scope provided. Use -scope option.\n");
}
log("Reading VCD file: %s\n", vcd_filename.c_str());
try {
FstData fst(vcd_filename);
if (scope.empty()) {
scope = fst.autoScope(topmod);
if (scope.empty()) {
log_error("No scope found for module '%s'. Please specify -scope explicitly.\n",
RTLIL::unescape_id(topmod->name).c_str());
}
}
log("Using scope: \"%s\"\n", scope.c_str());
for (auto &var : fst.getVars()) {
if (var.is_reg) {
std::string reg_vcd_scope = var.scope;
@ -223,9 +233,6 @@ struct RegRenamePass : public Pass {
}
// STEP 2: Build hierarchy and process
Module *topmod = design->top_module();
if (!topmod)
log_error("No top module found!\n");
log("Building hierarchy from scope: %s\n", scope.c_str());
// Build hierarchy and process register renamings

View File

@ -188,8 +188,11 @@ struct AbcProcess
~AbcProcess() {
if (pid == 0)
return;
if (to_child_pipe >= 0)
if (to_child_pipe >= 0) {
static const char quit_cmd[] = "quit\n";
if (write(to_child_pipe, quit_cmd, sizeof(quit_cmd) - 1)) {}
close(to_child_pipe);
}
int status;
int ret = waitpid(pid, &status, 0);
if (ret != pid) {

View File

@ -72,16 +72,46 @@ struct AigmapPass : public Pass {
dict<IdString, int> stat_not_replaced;
int orig_num_cells = GetSize(module->cells());
dict<std::string, Aig> aig_cache;
pool<IdString> new_sel;
for (auto cell : module->selected_cells())
{
Aig aig(cell);
if (cell->type.in(ID($_AND_), ID($_NOT_))) {
not_replaced_count++;
stat_not_replaced[cell->type]++;
if (select_mode)
new_sel.insert(cell->name);
continue;
}
if (cell->type.in(ID($_AND_), ID($_NOT_)))
aig.name.clear();
if (nand_mode && cell->type == ID($_NAND_)) {
not_replaced_count++;
stat_not_replaced[cell->type]++;
if (select_mode)
new_sel.insert(cell->name);
continue;
}
if (nand_mode && cell->type == ID($_NAND_))
aig.name.clear();
if (cell->type[0] != '$') {
not_replaced_count++;
stat_not_replaced[cell->type]++;
if (select_mode)
new_sel.insert(cell->name);
continue;
}
std::string cache_key = cell->type.str();
cell->parameters.sort();
for (auto &p : cell->parameters)
cache_key += stringf(":%s=%s", p.first.c_str(), p.second.as_string().c_str());
auto cache_it = aig_cache.find(cache_key);
if (cache_it == aig_cache.end()) {
auto r = aig_cache.insert(std::make_pair(cache_key, Aig(cell)));
cache_it = r.first;
}
const Aig &aig = cache_it->second;
if (aig.name.empty()) {
not_replaced_count++;
@ -110,8 +140,8 @@ struct AigmapPass : public Pass {
if (nand_mode && node.inverter) {
bit = module->addWire(NEW_ID2_SUFFIX("bit"));
auto gate = module->addNandGate(NEW_ID2_SUFFIX("nand"), A, B, bit);
for (auto attr : cell->attributes)
gate->attributes[attr.first] = attr.second;
for (const auto &attr : cell->attributes)
gate->attributes[attr.first] = attr.second;
if (select_mode)
new_sel.insert(gate->name);
@ -123,8 +153,8 @@ struct AigmapPass : public Pass {
else {
bit = module->addWire(NEW_ID2_SUFFIX("bit"));
auto gate = module->addAndGate(NEW_ID2_SUFFIX("and"), A, B, bit);
for (auto attr : cell->attributes)
gate->attributes[attr.first] = attr.second;
for (const auto &attr : cell->attributes)
gate->attributes[attr.first] = attr.second;
if (select_mode)
new_sel.insert(gate->name);
}
@ -134,8 +164,8 @@ struct AigmapPass : public Pass {
if (node.inverter) {
SigBit new_bit = module->addWire(NEW_ID2_SUFFIX("new_bit"));
auto gate = module->addNotGate(NEW_ID2_SUFFIX("inv"), bit, new_bit);
for (auto attr : cell->attributes)
gate->attributes[attr.first] = attr.second;
for (const auto &attr : cell->attributes)
gate->attributes[attr.first] = attr.second;
bit = new_bit;
if (select_mode)
new_sel.insert(gate->name);

View File

@ -0,0 +1,511 @@
#
# opt_timing_balance regression coverage
#
# ---------------------------------------------------------------------------
# Case: XOR chain with late leaf should be rewritten
# ---------------------------------------------------------------------------
log -header "opt_timing_balance: xor late leaf rewrite"
log -push
design -reset
read_verilog <<EOF
module top (
input wire [7:0] u0,
input wire [7:0] u1,
input wire [15:0] a,
input wire [15:0] b,
input wire [15:0] c,
input wire [15:0] d,
output wire [15:0] y
);
wire [15:0] late = u0 * u1;
assign y = late ^ a ^ b ^ c ^ d;
endmodule
EOF
check -assert
equiv_opt -assert opt_timing_balance -logic
design -load postopt
select -assert-count 4 t:$xor
select o:y %ci2 t:$xor %i -set root_xor
select @root_xor i:late -assert-count 1
design -reset
log -pop
# ---------------------------------------------------------------------------
# Case: Default mode should rewrite both logic and arithmetic cones
# ---------------------------------------------------------------------------
log -header "opt_timing_balance: default mode mixed categories"
log -push
design -reset
read_verilog <<EOF
module top (
input wire [7:0] u0,
input wire [7:0] u1,
input wire [7:0] u2,
input wire [7:0] u3,
input wire [15:0] a,
input wire [15:0] b,
input wire [15:0] c,
input wire [15:0] d,
output wire [15:0] y_add,
output wire [15:0] y_xor
);
wire [15:0] late_add = u0 * u1;
wire [15:0] late_xor = u2 * u3;
assign y_add = late_add + a + b + c + d;
assign y_xor = late_xor ^ a ^ b ^ c ^ d;
endmodule
EOF
check -assert
equiv_opt -assert opt_timing_balance
design -load postopt
select -assert-min 1 t:$add a:timing_balance_generated=1 %i
select -assert-min 1 t:$xor a:timing_balance_generated=1 %i
design -reset
log -pop
# ---------------------------------------------------------------------------
# Case: Running the pass twice should be idempotent on generated cones
# ---------------------------------------------------------------------------
log -header "opt_timing_balance: idempotent second invocation"
log -push
design -reset
read_verilog <<EOF
module top (
input wire [7:0] u0,
input wire [7:0] u1,
input wire [15:0] a,
input wire [15:0] b,
input wire [15:0] c,
input wire [15:0] d,
output wire [15:0] y
);
wire [15:0] late = u0 * u1;
assign y = late + a + b + c + d;
endmodule
EOF
check -assert
opt_timing_balance -arith
select -assert-count 4 t:$add a:timing_balance_generated=1 %i
opt_timing_balance -arith
select -assert-count 4 t:$add a:timing_balance_generated=1 %i
design -reset
log -pop
# ---------------------------------------------------------------------------
# Case: Without negopt -pre, subtraction chains should not be rewritten
# ---------------------------------------------------------------------------
log -header "opt_timing_balance: direct subtraction conservative skip"
log -push
design -reset
read_verilog <<EOF
module top (
input wire [7:0] a,
input wire [7:0] b,
input wire [7:0] c,
input wire [7:0] d,
output wire [7:0] y
);
assign y = a - b - c - d;
endmodule
EOF
check -assert
equiv_opt -assert opt_timing_balance -arith
design -load postopt
select -assert-count 3 t:$sub
select -assert-count 0 t:$add a:timing_balance_generated=1 %i
design -reset
log -pop
# ---------------------------------------------------------------------------
# Case: Signed-width logic extension semantics are preserved under rewrite
# ---------------------------------------------------------------------------
log -header "opt_timing_balance: signed logic extension equivalence"
log -push
design -reset
read_verilog <<EOF
module top (
input wire [7:0] u0,
input wire [7:0] u1,
input wire signed [1:0] a,
input wire [3:0] b,
input wire [3:0] c,
output wire [3:0] y
);
wire [3:0] late = u0 * u1;
assign y = late & a & b & c;
endmodule
EOF
check -assert
equiv_opt -assert opt_timing_balance -logic
design -load postopt
select -assert-min 1 t:$and a:timing_balance_generated=1 %i
select o:y %ci2 t:$and %i -set signed_logic_root
select @signed_logic_root i:late -assert-count 1
design -reset
log -pop
# ---------------------------------------------------------------------------
# Case: AND chain with late leaf should be rewritten
# ---------------------------------------------------------------------------
log -header "opt_timing_balance: and late leaf rewrite"
log -push
design -reset
read_verilog <<EOF
module top (
input wire [7:0] u0,
input wire [7:0] u1,
input wire [15:0] a,
input wire [15:0] b,
input wire [15:0] c,
input wire [15:0] d,
output wire [15:0] y
);
wire [15:0] late = u0 * u1;
assign y = late & a & b & c & d;
endmodule
EOF
check -assert
equiv_opt -assert opt_timing_balance -logic
design -load postopt
select -assert-count 4 t:$and
select o:y %ci2 t:$and %i -set root_and
select @root_and i:late -assert-count 1
design -reset
log -pop
# ---------------------------------------------------------------------------
# Case: OR chain with late leaf should be rewritten
# ---------------------------------------------------------------------------
log -header "opt_timing_balance: or late leaf rewrite"
log -push
design -reset
read_verilog <<EOF
module top (
input wire [7:0] u0,
input wire [7:0] u1,
input wire [15:0] a,
input wire [15:0] b,
input wire [15:0] c,
input wire [15:0] d,
output wire [15:0] y
);
wire [15:0] late = u0 * u1;
assign y = late | a | b | c | d;
endmodule
EOF
check -assert
equiv_opt -assert opt_timing_balance -logic
design -load postopt
select -assert-count 4 t:$or
select o:y %ci2 t:$or %i -set root_or
select @root_or i:late -assert-count 1
design -reset
log -pop
# ---------------------------------------------------------------------------
# Case: ADD chain with late leaf should be rewritten
# ---------------------------------------------------------------------------
log -header "opt_timing_balance: add late leaf rewrite"
log -push
design -reset
read_verilog <<EOF
module top (
input wire [7:0] u0,
input wire [7:0] u1,
input wire [15:0] a,
input wire [15:0] b,
input wire [15:0] c,
input wire [15:0] d,
output wire [15:0] y
);
wire [15:0] late = u0 * u1;
assign y = late + a + b + c + d;
endmodule
EOF
check -assert
equiv_opt -assert opt_timing_balance -arith
design -load postopt
select -assert-count 4 t:$add
select o:y %ci2 t:$add %i -set root_add
select @root_add i:late -assert-count 1
design -reset
log -pop
# ---------------------------------------------------------------------------
# Case: Balanced add tree should remain unchanged (no rewrite)
# ---------------------------------------------------------------------------
log -header "opt_timing_balance: balanced add no-op"
log -push
design -reset
read_verilog <<EOF
module top (
input wire [15:0] a,
input wire [15:0] b,
input wire [15:0] c,
input wire [15:0] d,
input wire [15:0] e,
input wire [15:0] f,
input wire [15:0] g,
input wire [15:0] h,
output wire [15:0] y
);
assign y = ((a+b)+(c+d))+((e+f)+(g+h));
endmodule
EOF
check -assert
equiv_opt -assert opt_timing_balance -arith
design -load postopt
select -assert-count 7 t:$add
select -assert-count 0 t:$add a:timing_balance_generated=1 %i
design -reset
log -pop
# ---------------------------------------------------------------------------
# Case: -logic mode must not rewrite arithmetic cones
# ---------------------------------------------------------------------------
log -header "opt_timing_balance: mode filter logic-only"
log -push
design -reset
read_verilog <<EOF
module top (
input wire [7:0] u0,
input wire [7:0] u1,
input wire [15:0] a,
input wire [15:0] b,
input wire [15:0] c,
input wire [15:0] d,
output wire [15:0] y
);
wire [15:0] late = u0 * u1;
assign y = late + a + b + c + d;
endmodule
EOF
check -assert
equiv_opt -assert opt_timing_balance -logic
design -load postopt
select -assert-count 4 t:$add
select -assert-count 0 t:$add a:timing_balance_generated=1 %i
design -reset
log -pop
# ---------------------------------------------------------------------------
# Case: -arith mode must not rewrite logic cones
# ---------------------------------------------------------------------------
log -header "opt_timing_balance: mode filter arith-only"
log -push
design -reset
read_verilog <<EOF
module top (
input wire [7:0] u0,
input wire [7:0] u1,
input wire [15:0] a,
input wire [15:0] b,
input wire [15:0] c,
input wire [15:0] d,
output wire [15:0] y
);
wire [15:0] late = u0 * u1;
assign y = late ^ a ^ b ^ c ^ d;
endmodule
EOF
check -assert
equiv_opt -assert opt_timing_balance -arith
design -load postopt
select -assert-count 4 t:$xor
select -assert-count 0 t:$xor a:timing_balance_generated=1 %i
design -reset
log -pop
# ---------------------------------------------------------------------------
# Case: Shared-fanout cone must preserve both tap and final output semantics
# ---------------------------------------------------------------------------
log -header "opt_timing_balance: shared fanout tap safety"
log -push
design -reset
read_verilog <<EOF
module top (
input wire [15:0] a,
input wire [15:0] b,
input wire [15:0] c,
input wire [15:0] d,
output wire [15:0] tap,
output wire [15:0] y
);
wire [15:0] x0 = a + b;
wire [15:0] x1 = x0 + c;
assign tap = x1;
assign y = x1 + d;
endmodule
EOF
check -assert
equiv_opt -assert opt_timing_balance -arith
design -load postopt
select o:tap %ci2 t:$add %i -assert-count 1
select o:y %ci2 t:$add %i -assert-count 1
design -reset
log -pop
# ---------------------------------------------------------------------------
# Case: Subtraction chain after negopt -pre should be balanced as add/neg
# ---------------------------------------------------------------------------
log -header "opt_timing_balance: negopt-pre normalized subtraction chain"
log -push
design -reset
read_verilog <<EOF
module top (
input wire [7:0] a,
input wire [7:0] b,
input wire [7:0] c,
input wire [7:0] d,
output wire [7:0] y
);
assign y = a - b - c - d;
endmodule
EOF
check -assert
negopt -pre
select -assert-count 0 t:$sub
equiv_opt -assert opt_timing_balance -arith
design -load postopt
select -assert-min 1 t:$add a:timing_balance_generated=1 %i
design -reset
log -pop
# ---------------------------------------------------------------------------
# Case: Mixed-sign add chains should be skipped conservatively
# ---------------------------------------------------------------------------
log -header "opt_timing_balance: mixed-sign conservative skip"
log -push
design -reset
read_verilog <<EOF
module top (
input wire signed [7:0] a,
input wire [7:0] b,
input wire [7:0] c,
output wire [8:0] y
);
assign y = a + b + c;
endmodule
EOF
check -assert
equiv_opt -assert opt_timing_balance -arith
design -load postopt
select -assert-count 2 t:$add
select -assert-count 0 t:$add a:timing_balance_generated=1 %i
design -reset
log -pop
# ---------------------------------------------------------------------------
# Case: Two independent arithmetic heads should both rewrite
# ---------------------------------------------------------------------------
log -header "opt_timing_balance: multi-head arithmetic rewrite"
log -push
design -reset
read_verilog <<EOF
module top (
input wire [7:0] u0,
input wire [7:0] u1,
input wire [7:0] u2,
input wire [7:0] u3,
input wire [15:0] a,
input wire [15:0] b,
input wire [15:0] c,
input wire [15:0] d,
input wire [15:0] e,
input wire [15:0] f,
input wire [15:0] g,
input wire [15:0] h,
output wire [15:0] y0,
output wire [15:0] y1
);
wire [15:0] late0 = u0 * u1;
wire [15:0] late1 = u2 * u3;
assign y0 = late0 + a + b + c + d;
assign y1 = late1 + e + f + g + h;
endmodule
EOF
check -assert
equiv_opt -assert opt_timing_balance -arith
design -load postopt
select -assert-min 2 t:$add
select o:y0 %ci2 t:$add %i -set root0
select o:y1 %ci2 t:$add %i -set root1
select @root0 i:late0 -assert-count 1
select @root1 i:late1 -assert-count 1
design -reset
log -pop
# ---------------------------------------------------------------------------
# Case: Generated-cell tagging exists for rewritten cones (contract test)
# ---------------------------------------------------------------------------
log -header "opt_timing_balance: generated tag contract"
log -push
design -reset
read_verilog <<EOF
module top (
input wire [7:0] u0,
input wire [7:0] u1,
input wire [15:0] a,
input wire [15:0] b,
input wire [15:0] c,
input wire [15:0] d,
output wire [15:0] y
);
wire [15:0] late = u0 * u1;
assign y = late + a + b + c + d;
endmodule
EOF
check -assert
equiv_opt -assert opt_timing_balance -arith
design -load postopt
select -assert-min 1 t:$add a:timing_balance_generated=1 %i
design -reset
log -pop