Merge from master for release.

This commit is contained in:
Wilson Snyder 2025-08-30 18:14:20 -04:00
commit 0a9d9db5ae
1120 changed files with 36202 additions and 13893 deletions

View File

@ -11,11 +11,11 @@ permissions:
contents: write
jobs:
format:
runs-on: ubuntu-22.04
name: Ubuntu 22.04 | format
runs-on: ubuntu-24.04
name: Ubuntu 24.04 | format
env:
CI_OS_NAME: linux
CI_RUNS_ON: ubuntu-22.04
CI_RUNS_ON: ubuntu-24.04
CI_COMMIT: ${{ github.sha }}
steps:
- name: Checkout
@ -27,7 +27,7 @@ jobs:
CI_BUILD_STAGE_NAME: build
run: |
bash ci/ci-install.bash &&
sudo apt-get install clang-format-14 yapf3 &&
sudo apt-get install clang-format-18 yapf3 &&
sudo pip3 install gersemi mbake &&
git config --global user.email "action@example.com" &&
git config --global user.name "github action"
@ -35,7 +35,7 @@ jobs:
run: |
autoconf &&
./configure &&
make -j 2 format CLANGFORMAT=clang-format-14 &&
make -j 2 format CLANGFORMAT=clang-format-18 &&
git status
- name: Push
run: |

View File

@ -27,7 +27,7 @@ concurrency:
jobs:
windows:
name: run on windows
runs-on: windows-latest
runs-on: windows-2025
steps:
- uses: actions/checkout@v4
with:

View File

@ -1,6 +1,5 @@
---
# DESCRIPTION: Github actions config
# This name is key to badges in README.rst, so we use the name build
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
name: reusable-build

View File

@ -1,6 +1,5 @@
---
# DESCRIPTION: Github actions config
# This name is key to badges in README.rst, so we use the name build
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
name: reusable-rtlmeter-build

View File

@ -1,6 +1,5 @@
---
# DESCRIPTION: Github actions config
# This name is key to badges in README.rst, so we use the name build
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
name: reusable-rtlmeter-run
@ -8,6 +7,10 @@ name: reusable-rtlmeter-run
on:
workflow_call:
inputs:
tag:
description: "Unique identifier for storing results"
type: string
required: true
runs-on:
description: "Runner to use, e.g.: ubuntu-24.04"
type: string
@ -114,7 +117,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
path: results-${{ steps.results.outputs.hash }}.json
name: rtlmeter-results-${{ steps.results.outputs.hash }}
name: rtlmeter-results-${{ inputs.tag }}-${{ steps.results.outputs.hash }}
overwrite: true
retention-days: 2

View File

@ -1,9 +1,8 @@
---
# DESCRIPTION: Github actions config
# This name is key to badges in README.rst, so we use the name build
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
name: reusable-build
name: reusable-test
on:
workflow_call:

View File

@ -9,6 +9,13 @@ on:
workflow_dispatch:
schedule:
- cron: '0 2 * * *' # Daily, starting at 02:00 UTC
pull_request:
types:
- opened
- synchronize
- reopened
- labeled
- unlabeled
permissions:
contents: read
@ -18,14 +25,29 @@ defaults:
shell: bash
concurrency:
group: RTLMeter
cancel-in-progress: false
group: ${{ github.ref }}
cancel-in-progress: ${{ github.event_name != 'schedule' }}
jobs:
start:
name: Start
# Only run scheduled jobs if explicitly enabled for that repo (e.g.: not on forks)
# Only run pull request jobs if labelled as needing an RTLMeter run
# Always run workflow dispatch jobs
if: |
(github.event_name == 'schedule'
&& vars.ENABLE_SCHEDULED_JOBS == 'true') ||
(github.event_name == 'pull_request'
&& contains(github.event.pull_request.labels.*.name, 'pr: rtlmeter')) ||
(github.event_name == 'workflow_dispatch')
runs-on: ubuntu-24.04
steps:
- name: Startup
run: echo
build-gcc:
name: Build GCC
# Only run scheduled jobs if explicitly enabled for that repo (e.g.: not on forks)
if: ${{ github.event_name != 'schedule' || vars.ENABLE_SCHEDULED_JOBS == 'true' }}
needs: start
uses: ./.github/workflows/reusable-rtlmeter-build.yml
with:
runs-on: ubuntu-24.04
@ -33,8 +55,7 @@ jobs:
build-clang:
name: Build Clang
# Only run scheduled jobs if explicitly enabled for that repo (e.g.: not on forks)
if: ${{ github.event_name != 'schedule' || vars.ENABLE_SCHEDULED_JOBS == 'true' }}
needs: start
uses: ./.github/workflows/reusable-rtlmeter-build.yml
with:
runs-on: ubuntu-24.04
@ -45,6 +66,7 @@ jobs:
needs: build-gcc
uses: ./.github/workflows/reusable-rtlmeter-run.yml
with:
tag: gcc
runs-on: ubuntu-24.04
cc: gcc
cases: ${{ matrix.cases }}
@ -53,7 +75,7 @@ jobs:
executeArgs: ""
strategy:
fail-fast: false
max-parallel: 2
max-parallel: ${{ github.event == 'schedule' && 2 || 7 }}
matrix:
cases:
- "NVDLA:*"
@ -86,6 +108,7 @@ jobs:
needs: build-clang
uses: ./.github/workflows/reusable-rtlmeter-run.yml
with:
tag: clang
runs-on: ubuntu-24.04
cc: clang
cases: ${{ matrix.cases }}
@ -94,7 +117,7 @@ jobs:
executeArgs: ""
strategy:
fail-fast: false
max-parallel: 2
max-parallel: ${{ github.event == 'schedule' && 2 || 7 }}
matrix:
cases:
- "NVDLA:*"
@ -129,29 +152,40 @@ jobs:
# That is: do not run if all skipped, or the workflow was cancelled.
if: ${{ (contains(needs.*.result, 'success') || contains(needs.*.result, 'failure')) && !cancelled() }}
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
tag:
- gcc
- clang
steps:
- name: Checkout RTLMeter
uses: actions/checkout@v4
with:
repository: "verilator/rtlmeter"
path: rtlmeter
- name: Setup RTLMeter venv
working-directory: rtlmeter
run: make venv
- name: Download all results
uses: actions/download-artifact@v4
with:
pattern: rtlmeter-results-*
path: all-results-${{ github.run_id }}
pattern: rtlmeter-results-${{ matrix.tag }}-*
path: all-results-${{ matrix.tag }}
merge-multiple: true
- name: Tar up all results into single archive
- name: Combine results
working-directory: rtlmeter
run: |
# Ensure combined result directory exists in case of no results
mkdir -p all-results-${{ github.run_id }}
ls -la all-results-${{ github.run_id }}
# Tar up the results directory
tar --posix -c -z -f all-results-${{ github.run_id }}.tar.gz all-results-${{ github.run_id }}
./rtlmeter collate ../all-results-${{ matrix.tag }}/*.json > ../all-results-${{ matrix.tag }}.json
- name: Upload combined results
uses: actions/upload-artifact@v4
with:
path: all-results-${{ github.run_id }}.tar.gz
name: all-results
path: all-results-${{ matrix.tag }}.json
name: all-results-${{ matrix.tag }}
overwrite: true
retention-days: 30
publish-results:
publish-scheduled-results:
name: Publish results to verilator/verilator-rtlmeter-results
needs: combine-results
# Only run on scheduled builds on the main repository. We also restrict
@ -165,22 +199,24 @@ jobs:
- name: Download combined results
uses: actions/download-artifact@v4
with:
name: all-results
pattern: all-results-*
path: results
- name: Extract combined results
working-directory: results
run: |
tar xzfv all-results-${{ github.run_id }}.tar.gz
ls -la
merge-multiple: true
- name: Upload published results
uses: actions/upload-artifact@v4
with:
path: results/*.json
name: published-results
# Pushing to verilator/verilator-rtlmeter-results requires elevated permissions
- name: Generate access token
id: generate-token
uses: actions/create-github-app-token@v2.0.6
with:
app-id: ${{ vars.RTLMETER_RESULTS_CI_APP_ID }}
private-key: ${{ secrets.RTLMETER_RESULTS_CI_APP_PRIVATE_KEY }}
app-id: ${{ vars.VERILATOR_CI_ID }}
private-key: ${{ secrets.VERILATOR_CI_KEY }}
owner: verilator
repositories: verilator-rtlmeter-results
permission-contents: write
- name: Checkout verilator-rtlmeter-results
uses: actions/checkout@v4
with:
@ -205,3 +241,82 @@ jobs:
git add .
git commit -m "Verilator CI: Results of 'RTLMeter' workflow run #${{ github.run_number }}"
git push origin
#publish-pr-results:
# name: Publish results to Pull Request
# needs: combine-results
# if: ${{ github.event_name == 'pull_request' && github.repository == 'verilator/verilator' && github.run_attempt == 1 && contains(needs.*.result, 'success') && !cancelled() }}
# runs-on: ubuntu-24.04
# steps:
# - name: Checkout RTLMeter
# uses: actions/checkout@v4
# with:
# repository: "verilator/rtlmeter"
# path: rtlmeter
# - name: Setup RTLMeter venv
# working-directory: rtlmeter
# run: make venv
# - name: Download combined results
# uses: actions/download-artifact@v4
# with:
# pattern: all-results-*
# path: all-results
# merge-multiple: true
# - name: Get scheduled run info
# id: scheduled-info
# run:
# IDLATEST=$(gh run list --workflow RTLMeter --event schedule --status success --limit 1 --json databaseId --jq ".[0].databaseId")
# echo "id=$IDLATEST" >> $GITHUB_OUTPUT
# URL=$(gh run view $IDLATEST --json url --jq ".url")
# echo "url=$URL" >> $GITHUB_OUTPUT
# NUM=$(gh run view $IDLATEST --json number --jq ".number")
# echo "num=$NUM" >> $GITHUB_OUTPUT
# # Fetching artifacts from different workflow requires elevated privilege
# - name: Generate access token
# id: generate-token
# uses: actions/create-github-app-token@v2.0.6
# with:
# app-id: ${{ vars.VERILATOR_CI_ID }}
# private-key: ${{ secrets.VERILATOR_CI_KEY }}
# owner: verilator
# repositories: verilator
# permission-actions: read
# - name: Download scheduled run results
# uses: actions/download-artifact@v4
# with:
# name: published-results
# path: nightly-results
# run-id: ${{ steps.schedueld-info.outputs.id }}
# github-token: ${{ steps.generate-token.outputs.token }}
# # TODO: diff and present
# Create GitHub issue for failed schedueld jobs
# This should always be the last job (we want an issue if anything breaks)
create-issue:
name: Create issue on failure
needs: publish-scheduled-results
if: ${{ github.event_name == 'schedule' && github.repository == 'verilator/verilator' && github.run_attempt == 1 && failure() && !cancelled() }}
runs-on: ubuntu-24.04
steps:
# Creating issues requires elevated privilege
- name: Generate access token
id: generate-token
uses: actions/create-github-app-token@v2.0.6
with:
app-id: ${{ vars.VERILATOR_CI_ID }}
private-key: ${{ secrets.VERILATOR_CI_KEY }}
owner: verilator
repositories: verilator
permission-issues: write
- name: Create issue
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
run: |
echo "This issue was created automatically by the GitHub Actions CI due to the failure of a scheduled RTLMeter run." >> body.txt
echo "" >> body.txt
echo "Workflow status: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> body.txt
gh issue --repo ${{ github.repository }} create \
--title "RTLMeter run #${{ github.run_number }} Failed" \
--body-file body.txt \
--label new \
--assignee gezalore,wsnyder

View File

@ -16,7 +16,7 @@ cmake_minimum_required(VERSION 3.15)
cmake_policy(SET CMP0091 NEW) # Use MSVC_RUNTIME_LIBRARY to select the runtime
project(
Verilator
VERSION 5.038
VERSION 5.040
HOMEPAGE_URL https://verilator.org
LANGUAGES CXX
)

114
Changes
View File

@ -8,6 +8,111 @@ The changes in each Verilator version are described below. The
contributors that suggested or implemented a given issue are shown in []. Thanks!
Verilator 5.040 2025-08-30
==========================
**Other:**
* Add ENUMITEMWIDTH error, and apply to X-extended and ranged values.
* Add NOEFFECT warning, replacing previous `foreach` error.
* Add SPECIFYIGN warning for specify constructs that were previously silently ignored.
* Add PARAMNODEFAULT error, for parameters without defaults.
* Add enum base data type, wire data type, and I/O versus data declaration checking per IEEE.
* Add PROTOTYPEMIS error on missing and mismatching prototypes (#6206) (#6207). [Alex Solomatnikov]
* Add error when trying to assign class object to variable of non-class types (#6237). [Igor Zaworski, Antmicro Ltd.]
* Add ALWNEVER warning, for `always @*` that never execute (#6291).
* Add separate coverage counters for toggles 0->1 and 1->0 (#6086). [Ryszard Rozak, Antmicro Ltd.]
* Add error on class 'function static'.
* Add error on force/release non-constant selects.
* Add `-DVERILATOR=1` definition to compiler flags when using verilated.mk.
* Support member-level triggers for virtual interfaces (#5166) (#6148). [Yilou Wang]
* Support unassigned virtual interfaces (#5265) (#6245). [Szymon Gizler, Antmicro Ltd.]
* Support randomization of scope variables with 'std::randomize()' (#5438) (#6185). [Yilou Wang]
* Support disabling a fork in additional contexts (#5432 partial) (#6174) (#6183). [Ryszard Rozak, Antmicro Ltd.]
* Support bit queue streaming (#5830) (#6103). [Paul Swirhun]
* Support `$fread` with missing start (#6125). [Iztok Jeras]
* Support unpacked array `with` methods (#6134).
* Support Verilog real ports as SystemC double ports (#6136) (#6158). [George Polack]
* Support `$countones` in constraints (#6144 partial) (#6235). [Ryszard Rozak, Antmicro Ltd.]
* Support disable dotted references (#6154). [Ryszard Rozak, Antmicro Ltd.]
* Support randomize() on class member selects (#6161) (#6195). [Igor Zaworski, Ryszard Rozak, Antmicro Ltd.]
* Support multiple variables on RHS of a `force` assignment (#6163). [Artur Bieniek, Antmicro Ltd.]
* Support covergroup extends, etc., as unsupported (#6160). [Artur Bieniek, Antmicro Ltd.]
* Support parameter resolution of 1D unpacked array slices (#6257) (#6268). [Michael Bedford Taylor]
* Support generic interfaces (#6272). [Igor Zaworski, Antmicro Ltd.]
* Support disabling a fork from within that fork (#6314). [Ryszard Rozak, Antmicro Ltd.]
* Support future sampled value functions.
* Support simple disable within task (#6334). [Ryszard Rozak, Antmicro Ltd.]
* Support recursive constant functions.
* Change control file `public_flat_*` and other signal attributes to support __ in names (#6140).
* Change runtime to exit() instead of abort(), unless under +verilated+debug.
* Change `$display("%p")` to remove space after `}`.
* Improve `--skip-identical` to skip on identical input file contents (#6109).
* Improve testing on FreeBSD (#6328). [Aleksander Kiryk]
* Optimize to return memory when using -build (#6192) (#6226). [Michael B. Taylor]
* Optimize 2 ** X to 1 << X if base is signed (#6203). [Max Wipfli]
* Optimize more complex combinational logic in DFG (#6205) (#6209) (#6298). [Geza Lore]
* Optimize combinational cycles through arrays in DFG (#6210). [Geza Lore]
* Optimize variable removal in scoped DFG (#6260). [Geza Lore]
* Optimize acyclic DFG components into the original acyclic sub-graph. (#6261). [Geza Lore]
* Optimize multiplexers in DFG synthesis (#6331). [Geza Lore]
* Optimize interfaces in DFG (#6332). [Geza Lore]
* Optimize logic in non-virtual interfaces with DFG (#6347). [Geza Lore]
* Fix loop initialization visibility outside loop (#4237).
* Fix constructor parameters in inheritance hierarchies (#6036) (#6070). [Petr Nohavica]
* Fix replicate of negative giving 'REPLICATE has no expected width' internal error (#6048) (#6229).
* Fix cmake `-Wno` compiler flag testing (#6145). [Martin Stadler]
* Fix class extends dotted error (#6162). [Igor Zaworski, Antmicro Ltd.]
* Fix genvar error with `-O0` (#6165). [Max Wipfli]
* Fix uninitialized thread PGO counters (#6167). [Bartłomiej Chmiel, Antmicro Ltd.]
* Fix additional UNOPTFLAT combinational cycles automatically in DFG (#6168) (#6173) (#6176). [Geza Lore]
* Fix omitting error when assigning to an input (#6169). [Artur Bieniek, Antmicro Ltd.]
* Fix parameter-dependent type linking (#6170). [Igor Zaworski, Antmicro Ltd.]
* Fix param-dependent class typedef linking (#6171). [Igor Zaworski, Antmicro Ltd.]
* Fix virtual interface member propagation (#6175) (#6184). [Yilou Wang]
* Fix `--coverage-expr` null pointer dereference (#6181). [Igor Zaworski, Antmicro Ltd.]
* Fix conflicting function/class name linking error (#6182). [Igor Zaworski, Antmicro Ltd.]
* Fix negate of wide structure selections (#6186).
* Fix VPI signal range order (#6189) (#6200). [Ibrahim Burak Yorulmaz]
* Fix structure select causing 'Wide Op' error (#6191). [Danny Oler]
* Fix automatic task variables in unrolled loops with forks (#6194) (#6201). [Danny Oler]
* Fix 'driver same component' assertion (#6211) (#6215). [Geza Lore]
* Fix `--stats` overridden by skipping identical build (#6220). [Geza Lore]
* Fix MODDUP with duplicate packages to take first package (#6222).
* Fix replicate with unsigned count but MSB set (#6231) (#6233). [Geza Lore]
* Fix randomize on function-local variable (#6234).
* Fix queue typedef with unbounded slice (#6236).
* Fix error when force assignment is used with ref function args (#6244). [Ryszard Rozak, Antmicro Ltd.]
* Fix write of 0 in '%c' (#6248) (#6249). [Rodrigo Batista de Moraes]
* Fix coverage of variables of complex types (#6250). [Ryszard Rozak, Antmicro Ltd.]
* Fix broken support of unassigned virtual interfaces (#6253) (#6338). [Szymon Gizler, Antmicro Ltd.]
* Fix partial DFG conversion of concat assignments (#6255). [Geza Lore]
* Fix dynamic cast purity (#6267). [Igor Zaworski, Antmicro Ltd.]
* Fix same variable on the RHS forced to two different LHSs. (#6269). [Artur Bieniek, Antmicro Ltd.]
* Fix spurious VPI value change callbacks (#6274). [Todd Strader]
* Fix stray ']' in Verilog code output for non-constant select (#6277). [Geza Lore]
* Fix hierarchical NBAs (#6286) (#6300). [Geza Lore]
* Fix variables hiding package imports (#6289). [Johan Wouters]
* Fix DFG circular driver tracing. [Geza Lore]
* Fix no matching function calls for randomized `VlWide` in unpacked and dynamic arrays (#6290). [Mateusz Gancarz, Antmicro Ltd.]
* Fix PowerPC support (#6292). [Sergey Fedorov]
* Fix referencing module variables above classes (#6304). [Artur Bieniek, Antmicro Ltd.]
* Fix direct NBA to dynamically-sized variable (#6310). [Artur Bieniek, Antmicro Ltd.]
* Fix static vars under member select (#6313). [Igor Zaworski, Antmicro Ltd.]
* Fix expression type comparison (#6316). [Igor Zaworski, Antmicro Ltd.]
* Fix of inline constraints with member selects (#6321). [Igor Zaworski, Antmicro Ltd.]
* Fix corner case bugs in module and variable inlining (#6322). [Geza Lore]
* Fix queue extend to check bounds (#6324). [Aleksander Kiryk]
* Fix gathering sensitivities from virtual interface members (#6325). [Aleksander Kiryk]
* Fix FreeBSD missing headers (#6326). [Aleksander Kiryk]
* Fix to select UDPs when they are the only candidate for a top module.
* Fix splitting of assignments to SC variables (#6329) (#6336). [Geza Lore]
* Fix to localize for super constructors with function calls as arguments (#6330). [Igor Zaworski, Antmicro Ltd.]
* Fix wide select expansion and substitution (#6341) (#6345). [Geza Lore]
* Fix upcasting class type parameters (#6344). [Krzysztof Bieganski, Antmicro Ltd.]
* Fix undefined weak link for Apple GCC etc (#6348). [Congcong Cai]
Verilator 5.038 2025-07-08
==========================
@ -19,6 +124,7 @@ Verilator 5.038 2025-07-08
* Support redeclaring type as non-type; major parsing change (#2412) (#6020) (#6042) (#6044).
* Support scoped `new` (#4199).
* Support elaboration-time printing of unpacked array with `%p` (#4732).
* Support constrained random for associative arrays (#5985) (#5986). [Yilou Wang]
* Support assignments to concatenations with impure RHS (#6002). [Ryszard Rozak, Antmicro Ltd.]
* Support SARIF JSON diagnostic output with `--diagnostics-sarif`. (#6017)
@ -96,7 +202,9 @@ Verilator 5.038 2025-07-08
* Fix wide non-blocking assignment mis-optimization (#6150) (#6152) (#6155). [Todd Strader]
* Fix interface array connections with non-zero low declaration index.
* Fix developer build error on MacOS/Flex2.6.4 (#6153). [Paul Swirhun]
* Fix crash with --dumpi-V3LinkDot without --debug (#6159). [Igor Zaworski]
* Fix crash with --dumpi-V3LinkDot without --debug (#6159). [Igor Zaworski, Antmicro Ltd.]
* Fix dereferencing stale iterator in DfgVertex::scopep() (#6225) (#6227). [Geza Lore]
* Fix component numbers of new Vertices in V3DfgBreakCycles (#6225) (#6228). [Geza Lore]
Verilator 5.036 2025-04-27
@ -107,7 +215,7 @@ Verilator 5.036 2025-04-27
* Change `--output-groups` to default to value of `--build-jobs` (#5751).
Those using build farms may need to now use `--output-groups 0` or otherwise.
* Support user-defined primitives (UDPs) (#468) (#5807) (#5936). [Zhou Shen, Krzysztof Sychla, et al]
* Add `--trace-saif` for SAIF power traces (#5812) (#5914). [Mateusz Gancarz]
* Add `--trace-saif` for SAIF power traces (#5812) (#5914). [Mateusz Gancarz, Antmicro Ltd.]
**Other:**
@ -835,7 +943,7 @@ Verilator 5.016 2023-09-16
* Fix nested assignments on the LHS (#4435). [Ryszard Rozak, Antmicro Ltd]
* Fix false MULTITOP on bound interfaces (#4438). [Alex Solomatnikov]
* Fix internal error on real conversion (#4447). [vdhotre-ventana]
* Fix lifetime unknown error on enum.name (#4448). [jwoutersymatra]
* Fix lifetime unknown error on enum.name (#4448). [Johan Wouters]
* Fix unstable output of VHashSha256 (#4453). [Anthony Donlon]
* Fix static cast from a stream type (#4469) (#4485). [Ryszard Rozak, Antmicro Ltd]
* Fix error on enum with VARHIDDEN of cell (#4482). [Michail Rontionov]

View File

@ -392,53 +392,50 @@ install-msg:
######################################################################
# Format/Lint
# Use --xml flag to see the cppcheck code to use for suppression
CPPCHECK1_CPP = $(wildcard $(srcdir)/include/*.cpp)
CPPCHECK2_CPP = $(wildcard $(srcdir)/examples/*/*.cpp)
CPPCHECK3_CPP = $(wildcard $(srcdir)/src/Vlc*.cpp)
CPPCHECK4_CPP = $(wildcard $(srcdir)/src/V3[A-D]*.cpp $(srcdir)/src/Verilator*.cpp)
CPPCHECK5_CPP = $(wildcard $(srcdir)/src/V3[E-I]*.cpp)
CPPCHECK6_CPP = $(wildcard $(srcdir)/src/V3[P-Z]*.cpp)
CPPCHECK7_CPP = $(wildcard $(srcdir)/src/V3[L-R]*.cpp)
CPPCHECK8_CPP = $(wildcard $(srcdir)/src/V3[S-Z]*.cpp)
CHECK_CPP = $(CPPCHECK1_CPP) $(CPPCHECK2_CPP) $(CPPCHECK3_CPP) $(CPPCHECK4_CPP) \
$(CPPCHECK5_CPP) $(CPPCHECK6_CPP) $(CPPCHECK7_CPP) $(CPPCHECK8_CPP)
CPPCHECK_VERILATOR_CPP = $(wildcard $(srcdir)/src/V3*.cpp $(srcdir)/src/Verilator*.cpp)
CPPCHECK_RUNTIME_CPP = $(wildcard $(srcdir)/include/*.cpp)
CPPCHECK_VLC_CPP = $(wildcard $(srcdir)/src/Vlc*.cpp)
CPPCHECK_EXAMPLES_CPP = $(wildcard $(srcdir)/examples/*/*.cpp)
CHECK_CPP = $(CPPCHECK_VERILATOR_CPP) $(CPPCHECK_RUNTIME_CPP) $(CPPCHECK_VLC_CPP) $(CPPCHECK_EXAMPLES_CPP)
CHECK_H = $(wildcard \
$(srcdir)/include/*.h \
$(srcdir)/src/*.h )
CHECK_YL = $(wildcard \
$(srcdir)/src/*.y \
$(srcdir)/src/*.l )
CPPCHECK = src/cppcheck_filtered cppcheck
CPPCHECK_FLAGS = --enable=all --inline-suppr \
--suppress=cstyleCast --suppress=ctunullpointer \
--suppress=derefInvalidIteratorRedundantCheck \
--suppress=nullPointer --suppress=nullPointerRedundantCheck \
--suppress=templateRecursion \
--suppress=unusedFunction --suppress=unusedScopedObject \
--suppress=useInitializationList --suppress=useStlAlgorithm \
CPPCHECK = cppcheck
CPPCHECK_JOBS = $(shell nproc)
CPPCHECK_CACHE = $(srcdir)/src/obj_dbg/cppcheck-cache
CPPCHECK_FLAGS = --enable=all
CPPCHECK_FLAGS += --inline-suppr
CPPCHECK_FLAGS += --suppressions-list=$(srcdir)/src/cppcheck-suppressions.txt
CPPCHECK_FLAGS += --cppcheck-build-dir=$(CPPCHECK_CACHE)
CPPCHECK_FLAGS += -DVL_DEBUG=1 -DVL_CPPCHECK=1 -DINFILTER_PIPE=1 -D__GNUC__=1
CPPCHECK_FLAGS += -j$(CPPCHECK_JOBS)
CPPCHECK_INC = -I$(srcdir)/include
CPPCHECK_INC += -I$(srcdir)/include/gtkwave
CPPCHECK_INC += -I$(srcdir)/include/vltstd
CPPCHECK_INC += -I$(srcdir)/src/obj_dbg
CPPCHECK_INC += -I$(srcdir)/src
CPPCHECK_FLAGS += --xml
CPPCHECK_DEP = $(subst .cpp,.cppcheck,$(CHECK_CPP))
CPPCHECK_INC = -I$(srcdir)/include -I$(srcdir)/include/gtkwave -I$(srcdir)/include/vltstd -I$(srcdir)/src/obj_dbg -I$(srcdir)/src
$(CPPCHECK_CACHE): $(CHECK_CPP) $(CHECK_H) $(CHECK_YL)
/bin/rm -rf $@
/bin/mkdir -p $@
cppcheck: cppcheck-1 cppcheck-2 cppcheck-3 cppcheck-4 cppcheck-5 cppcheck-6 cppcheck-7 cppcheck-8
cppcheck-1:
$(CPPCHECK) $(CPPCHECK_FLAGS) -DVL_DEBUG=1 -DVL_CPPCHECK=1 $(CPPCHECK_INC) $(CPPCHECK1_CPP)
cppcheck-2:
$(CPPCHECK) $(CPPCHECK_FLAGS) -DVL_DEBUG=1 -DVL_CPPCHECK=1 $(CPPCHECK_INC) $(CPPCHECK2_CPP)
cppcheck-3:
$(CPPCHECK) $(CPPCHECK_FLAGS) -DVL_DEBUG=1 -DVL_CPPCHECK=1 $(CPPCHECK_INC) $(CPPCHECK3_CPP)
cppcheck-4:
$(CPPCHECK) $(CPPCHECK_FLAGS) -DVL_DEBUG=1 -DVL_CPPCHECK=1 $(CPPCHECK_INC) $(CPPCHECK4_CPP)
cppcheck-5:
$(CPPCHECK) $(CPPCHECK_FLAGS) -DVL_DEBUG=1 -DVL_CPPCHECK=1 $(CPPCHECK_INC) $(CPPCHECK5_CPP)
cppcheck-6:
$(CPPCHECK) $(CPPCHECK_FLAGS) -DVL_DEBUG=1 -DVL_CPPCHECK=1 $(CPPCHECK_INC) $(CPPCHECK6_CPP)
cppcheck-7:
$(CPPCHECK) $(CPPCHECK_FLAGS) -DVL_DEBUG=1 -DVL_CPPCHECK=1 $(CPPCHECK_INC) $(CPPCHECK7_CPP)
cppcheck-8:
$(CPPCHECK) $(CPPCHECK_FLAGS) -DVL_DEBUG=1 -DVL_CPPCHECK=1 $(CPPCHECK_INC) $(CPPCHECK8_CPP)
cppcheck-verilator: | $(CPPCHECK_CACHE)
$(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_INC) $(CPPCHECK_VERILATOR_CPP)
cppcheck-runtime: | $(CPPCHECK_CACHE)
$(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_INC) $(CPPCHECK_RUNTIME_CPP)
cppcheck-vlc: | $(CPPCHECK_CACHE)
$(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_INC) $(CPPCHECK_VLC_CPP)
cppcheck-examples: | $(CPPCHECK_CACHE)
$(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_INC) $(CPPCHECK_EXAMPLES_CPP)
cppcheck:
$(MAKE) cppcheck-vlc
$(MAKE) cppcheck-examples
$(MAKE) cppcheck-runtime
$(MAKE) cppcheck-verilator
CLANGTIDY = clang-tidy
CLANGTIDY_FLAGS = -config='' \
@ -465,13 +462,14 @@ analyzer-include:
format:
$(MAKE) -j 5 format-c format-cmake format-exec format-py
CLANGFORMAT = clang-format-14
CLANGFORMAT = clang-format-18
CLANGFORMAT_FLAGS = -i
CLANGFORMAT_FILES = $(CHECK_CPP) $(CHECK_H) $(CHECK_YL) test_regress/t/*.c* test_regress/t/*.h
format-c clang-format:
@$(CLANGFORMAT) --version | egrep 14.0 > /dev/null \
|| echo "*** You are not using clang-format-14, indents may differ from master's ***"
$(CLANGFORMAT) --version
@$(CLANGFORMAT) --version | fgrep 'version 18' > /dev/null \
|| echo "*** You are not using clang-format-18, indents may differ from master's ***"
$(CLANGFORMAT) $(CLANGFORMAT_FLAGS) $(CLANGFORMAT_FILES)
YAMLFIX = YAMLFIX_WHITELINES=1 YAMLFIX_LINE_LENGTH=130 YAMLFIX_preserve_quotes=true yamlfix
@ -511,7 +509,6 @@ PY_PROGRAMS = \
src/astgen \
src/bisonpre \
src/config_rev \
src/cppcheck_filtered \
src/flexfix \
src/vlcovgen \
src/.gdbinit.py \
@ -538,6 +535,7 @@ YAPF = yapf3
YAPF_FLAGS = -i --parallel
format-py yapf:
$(YAPF) --version
$(YAPF) $(YAPF_FLAGS) $(PY_FILES)
GERSEMI = gersemi

View File

@ -238,6 +238,7 @@ def report_mtasks():
thread_mtask_time = collections.defaultdict(lambda: 0)
long_mtask_time = 0
long_mtask = None
long_mtask_hier_block = None
predict_mtask_time = 0
predict_elapsed = 0
for (hier_block, mtask_id) in Mtasks:
@ -279,6 +280,9 @@ def report_mtasks():
max_p2e = -1000000
max_mtask = None
min_hier_block = None
max_hier_block = None
for (hier_block, mtask_id) in sorted(Mtasks.keys()):
mtask = Mtasks[(hier_block, mtask_id)]
if mtask['elapsed'] > 0:

View File

@ -7,10 +7,11 @@
# When releasing, also update header of Changes file, and CMakeLists.txt,
# and commit using "devel release" or "Version bump" message
# Then 'make distclean' 'autoconf' './configure' 'make' 'make test'
# Then 'make maintainer-dist'
#AC_INIT([Verilator],[#.### YYYY-MM-DD])
#AC_INIT([Verilator],[#.### devel])
AC_INIT([Verilator],[5.038 2025-07-08],
AC_INIT([Verilator],[5.040 2025-08-30],
[https://verilator.org],
[verilator],[https://verilator.org])
@ -68,6 +69,7 @@ AC_ARG_ENABLE([tcmalloc],
*) AC_MSG_ERROR([bad value '${enableval}' for --enable-tcmalloc]) ;;
esac],
[CFG_WITH_TCMALLOC=check;])
AC_SUBST(CFG_WITH_TCMALLOC)
AC_MSG_RESULT($CFG_WITH_TCMALLOC)
# Flag to enable coverage build
@ -177,7 +179,7 @@ AC_PROG_INSTALL
AC_LANG_PUSH(C++)
CFG_CXX_VERSION=`$CXX --version | head -1`
AC_MSG_RESULT([compiler $CXX --version = $CFG_CXX_VERSION])
AC_MSG_RESULT([compiler version... $CXX --version = $CFG_CXX_VERSION])
AC_SUBST(CFG_CXX_VERSION)
AC_MSG_CHECKING([that C++ compiler can compile simple program])
@ -476,6 +478,7 @@ m4_foreach([cflag],[
[-Wno-bool-operation],
[-Wno-c++11-narrowing],
[-Wno-constant-logical-operand],
[-Wno-int-in-bool-context],
[-Wno-non-pod-varargs],
[-Wno-parentheses-equality],
[-Wno-shadow],
@ -546,10 +549,12 @@ _MY_LDLIBS_CHECK_IFELSE(
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-fno-builtin-calloc)
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-fno-builtin-realloc)
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-fno-builtin-free)
AC_DEFINE([HAVE_TCMALLOC],[1],[Defined if have tcmalloc])
fi],
[if test "$CFG_WITH_TCMALLOC" = "yes"; then
AC_MSG_ERROR([--enable-tcmalloc was given but test for ${LTCMALLOC} failed])
fi])
AC_SUBST(HAVE_TCMALLOC)
AC_SUBST(CFG_LIBS)
# Need C++14 at least

View File

@ -24,6 +24,7 @@ Anthony Donlon
Anthony Moore
Arkadiusz Kozdra
Arthur Rosa
Artur Bieniek
Aylon Chaim Porat
Bartłomiej Chmiel
Brian Li
@ -34,8 +35,10 @@ Chris Randall
Christopher Taylor
Chuxuan Wang
Chykon
Congcong Cai
Conor McCullough
Dan Petrisko
Danny Oler
Daniel Bates
Dave Sargeant
David Horton
@ -64,6 +67,7 @@ Fuad Ismail
Furqan Nadir
G-A. Kamendje
Garrett Smith
George Polack
Geza Lore
Gianfranco Costamagna
Gijs Burghoorn
@ -83,6 +87,7 @@ Huang Rui
Huanghuang Zhou
HungMingWu
HyungKi Jeong
Ibrahim Burak Yorulmaz
Igor Zaworski
Ilya Barkov
Iru Cai
@ -156,6 +161,8 @@ Martin Schmidt
Martin Stadler
Mateusz Gancarz
Matthew Ballance
Max Wipfli
Michael Bedford Taylor
Michael Bikovitsky
Michael Killough
Michal Czyz
@ -201,12 +208,14 @@ Richard Myers
Risto Pejašinović
Robert Balas
Robin Heinemann
Rodrigo Batista de Moraes
Rupert Swarbrick
Ryan Ziegler
Ryszard Rozak
Samuel Riedel
Sean Cross
Sebastien Van Cauwenberghe
Sergey Fedorov
Sergi Granell
Seth Pellegrino
Shou-Li Hsu

View File

@ -68,11 +68,16 @@ pdf:
######################################################################
html latex linkcheck spelling::
html latex linkcheck::
$(MAKE) vl-extract
$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS)
$(PYTHON3) bin/vl_sphinx_fix _build
spelling::
$(MAKE) vl-extract
$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS)
sort -o guide/spelling.txt guide/spelling.txt
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS)

View File

@ -0,0 +1,4 @@
.. comment: generated by t_event_control_star_never_bad
.. code-block:: sv
always @* a = 100;

View File

@ -0,0 +1,4 @@
.. comment: generated by t_event_control_star_never_bad
.. code-block::
%Warning-ALWNEVER: example.v:1:3 'always @*' will never execute as expression list is empty (no variables read)

View File

@ -1,5 +1,5 @@
.. comment: generated by t_lint_didnotconverge_bad
.. code-block::
-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] a)
-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] b)
%Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge.

View File

@ -0,0 +1,7 @@
.. comment: generated by t_lint_paramnodefault_bad
.. code-block:: sv
:linenos:
:emphasize-lines: 2
module sub;
parameter NODEF; //<--- Warning

View File

@ -0,0 +1,6 @@
.. comment: generated by t_lint_paramnodefault_bad
.. code-block::
%Error-PARAMNODEFAULT: example.v:1:13 Parameter without default requires ANSI-style parameter list (IEEE 1800-2023 6.20.1): 'NODEF'
8 | parameter NODEF;
| ^~~~~

View File

@ -3,6 +3,6 @@
:linenos:
:emphasize-lines: 3
int array[5];
bit [1:0] rd_addr;
wire int rd_value = array[rd_addr]; //<--- Warning
logic [31:0] array[5];
bit [1:0] rd_addr;
wire [31:0] rd_value = array[rd_addr]; //<--- Warning

View File

@ -2,4 +2,4 @@
.. code-block:: sv
:emphasize-lines: 1
wire int rd_value = array[{1'b0, rd_addr}]; //<--- Fixed
wire [31:0] rd_value = array[{1'b0, rd_addr}]; //<--- Fixed

View File

@ -1,4 +1,4 @@
.. comment: generated by t_lint_widthexpand_docs_bad
.. code-block::
%Warning-WIDTHEXPAND: example.v:3:29 Bit extraction of array[4:0] requires 3 bit index, not 2 bits.
%Warning-WIDTHEXPAND: example.v:3:31 Bit extraction of array[4:0] requires 3 bit index, not 2 bits.

View File

@ -32,7 +32,9 @@ Summary:
makefile's VPATH the appropriate directory to find the file.
See also :vlopt:`-CFLAGS` and :vlopt:`-LDFLAGS` options, which are
useful when the C++ files need special compiler flags.
useful when the C++ files need special compiler flags. The compiler
flags add by default `-DVERILATOR=1`, so an `#ifdef VERILATOR` may be
used to conditionally preprocess .cpp code for different simulators.
.. option:: <file.v>
@ -562,6 +564,10 @@ Summary:
Any :code:`$VAR`, :code:`$(VAR)`, or :code:`${VAR}` will be replaced
with the specified environment variable.
.. option:: -fdfg-synthesize-all
Rarely needed. Attempt to synthesize all combinational logic in DFG.
.. option:: -FI <file>
Force include of the specified C++ header file. All generated C++ files
@ -606,6 +612,10 @@ Summary:
optimizer. Alias for :vlopt:`-fno-dfg-pre-inline`,
:vlopt:`-fno-dfg-post-inline` and :vlopt:`-fno-dfg-scoped`.
.. option:: -fno-dfg-break-cycles
Rarely needed. Disable breaking combinational cycles during DFG.
.. option:: -fno-dfg-peephole
Rarely needed. Disable the DFG peephole optimizer.
@ -1963,14 +1973,13 @@ Summary:
different seeds on different executions. This method is the slowest,
but safest for finding reset bugs.
If using `--x-assign unique`, you may want to seed your random number
generator such that each regression run gets a different randomization
sequence. The simplest is to use the
:vlopt:`+verilator+seed+\<value\>` runtime option. Alternatively, use
the system's :code:`srand48()` or for Windows :code:`srand()` function
to do this. You'll probably also want to print any seeds selected,
and code to enable rerunning with that same seed so you can reproduce
bugs.
If using `--x-assign unique`, use the
:vlopt:`+verilator+rand+reset+2 <+verilator+rand+reset+\<value\>>`
runtime option, and seed the runtime random number generator such that
each regression run gets a different randomization sequence with
:vlopt:`+verilator+seed+\<value\>`. You'll probably also want to
print any seeds selected, and code to enable rerunning with that same
seed, so you can reproduce bugs.
.. note::
@ -1993,6 +2002,14 @@ Summary:
use for each initialization. This gives the greatest flexibility and
allows for finding reset bugs. See :ref:`Unknown states`.
If using `--x-initial unique`, use the
:vlopt:`+verilator+rand+reset+2 <+verilator+rand+reset+\<value\>>`
runtime option, and seed the runtime random number generator such that
each regression run gets a different randomization sequence with
:vlopt:`+verilator+seed+\<value\>`. You'll probably also want to
print any seeds selected, and code to enable rerunning with that same
seed, so you can reproduce bugs.
With "--x-initial fast",
is best for performance, and initializes all variables to a state
Verilator determines is optimal. This may allow further code

View File

@ -153,11 +153,11 @@ need to be present to run Verilator:
sudo apt-get install git autoconf flex bison
Those developing Verilator itself may also want these (see internals.rst):
Those developing Verilator itself also need these (see internals.rst):
.. code-block:: shell
sudo apt-get install clang clang-format-14 cmake gdb gprof graphviz lcov
sudo apt-get install clang clang-format-18 cmake gdb gprof graphviz lcov
sudo apt-get install python3-clang python3-distro yapf3 bear jq
sudo pip3 install sphinx sphinx_rtd_theme sphinxcontrib-spelling breathe gersemi mbake ruff sarif-tools
sudo pip3 install git+https://github.com/antmicro/astsee.git

View File

@ -147,8 +147,6 @@ Hierarchy blocks have some limitations, including:
* The hierarchy block cannot be accessed using dot (.) from the upper
module(s) or other hierarchy blocks.
* Signals in the block cannot be traced.
* Modport cannot be used at the hierarchical block boundary.
* The simulation speed is likely not as fast as flat Verilation, in which

View File

@ -111,6 +111,27 @@ List Of Warnings
simulate correctly.
.. option:: ALWNEVER
Warning that an `always @*` statement has no variables being read,
therefore the event list is empty, and as there are no events to wake
the process up, the always will never execute.
Faulty example:
.. include:: ../../docs/gen/ex_ALWNEVER_faulty.rst
Results in:
.. include:: ../../docs/gen/ex_ALWNEVER_msg.rst
To repair, assuming the intent was to execute the statements at e.g.
time zero, instead use an `always_comb` statement.
Ignoring this warning will only suppress the lint check; it will
simulate correctly.
.. option:: ASCRANGE
.. TODO better example
@ -458,7 +479,7 @@ List Of Warnings
Warns that Verilator does not support certain forms of
:code:`constraint`, :code:`constraint_mode`, or :code:`rand_mode`, and
the construct was are ignored.
the construct was ignored.
Ignoring this warning may make Verilator randomize() simulations differ
from other simulators.
@ -486,7 +507,7 @@ List Of Warnings
Warns that Verilator does not support certain forms of
:code:`covergroup`, :code:`coverpoint`, and coverage options, and the
construct was are ignored.
construct was ignored.
Disabling the :option:`UNSUPPORTED` error also disables this warning.
@ -709,6 +730,28 @@ List Of Warnings
missing."
.. option:: ENUMITEMWIDTH
An error that an enum item value is being assigned from a value which
would be truncated (similar to :option:`WIDTHTRUNC`), or from a sized
literal constant with a different bit width (similar to
:option:`WIDTHTRUNC` or :option:`WIDTHEXPAND`). IEEE requires this
error, but it may be disabled.
Faulty example:
.. code-block:: sv
:linenos:
:emphasize-lines: 2
typedef enum [3:0] {
WRONG_WIDTH = 33'h3 //<--- Warning
} enum_t;
To repair, correct the size of the item's value directly, or use a cast,
so the resulting width matches the enum's width.
.. option:: ENUMVALUE
An error that an enum data type value is being assigned from another data
@ -1286,6 +1329,21 @@ List Of Warnings
simulate correctly.
.. option:: NOEFFECT
Warns that the statement will have no effect and is roughly equivalent
to not being present. This is only issued when it is "non-obvious",
e.g. a :code:`if (0)` will not result in this warning.
Faulty example:
.. code-block:: sv
foreach (array[]) begin ... end //<--- Warning
For a fix, remove the statement.
.. option:: NOLATCH
.. TODO better example
@ -1341,6 +1399,27 @@ List Of Warnings
simulate correctly.
.. option:: PARAMNODEFAULT
An error that a parameter is being declared that has no default value,
and this is being done in a non-ANSI block while this is only legal in
ANSI-style `#(...)` declarations. IEEE 1800-2023 6.20.1 requires this
error, but some simulators accept this syntax.
Faulty example:
.. include:: ../../docs/gen/ex_PARAMNODEFAULT_faulty.rst
Results in:
.. include:: ../../docs/gen/ex_PARAMNODEFAULT_msg.rst
To fix the issue, move to an ANSI-style declaration.
Suppressing this error will only suppress the IEEE-required check; it
will simulate correctly.
.. option:: PINCONNECTEMPTY
.. TODO better example
@ -1554,6 +1633,19 @@ List Of Warnings
accepts the protected code.
.. option:: PROTOTYPEMIS
Error that a function prototype does not match in some respects the
out-of-block declaration of that function. IEEE requires this error.
The typical solution is to fix the prototype to match the declaration
exactly, including in number of arguments, name of arguments, argument
data types, and return data type (for functions).
Disabling this error will cause Verilator to ignore the prototype and
may make the code illegal in other tools.
.. option:: RANDC
Historical, never issued since version 5.018, when :code:`randc` became
@ -1742,6 +1834,18 @@ List Of Warnings
simulators.
.. option:: SPECIFYIGN
Warns that Verilator does not support certain constructs in
:code:`specify` blocks, nor :code:`$sdf_annotate`, and the construct was
ignored.
Disabling the :option:`UNSUPPORTED` error also disables this warning.
Ignoring this warning may make Verilator ignore lint checking on the
construct, and get different results from other simulators.
.. option:: SPLITVAR
Warns that a variable with a :option:`/*verilator&32;split_var*/`

View File

@ -450,7 +450,7 @@ as defined in the standard:
- All other logic is assigned to the 'nba' region.
For completeness, note that a subset of the 'act' region logic, specifically,
the logic related to the pre-assignments of NBA updates (i.e., AstAssignPre
the logic related to the pre-assignments of NBA updates (i.e., AstAlwaysPre
nodes), is handled separately, but is executed as part of the 'act' region.
Also note that all logic representing the committing of an NBA (i.e., Ast*Post)
@ -1131,7 +1131,7 @@ Indentation and Naming Style
----------------------------
We will work with contributors to fix up indentation style issues, but it
is appreciated if you could match our style:
is appreciated if you could match our style for C/C++:
- Use "mixedCapsSymbols" instead of "underlined_symbols".
@ -1143,9 +1143,8 @@ is appreciated if you could match our style:
calls. (This convention has not been applied retroactively.)
C and Python indentation is automatically maintained with "make format"
using clang-format version 10.0.0, and yapf for python, and is
automatically corrected in the CI actions. For those manually formatting C
code:
using clang-format version 18, and yapf for Python, and is automatically
corrected in the CI actions. For those manually formatting C code:
- Use four spaces per level, and no tabs.
@ -1157,6 +1156,58 @@ code:
- No spaces before semicolons, nor between a function's name and open
parenthesis (only applies to functions; if/else has a following space).
Verilog indentation is not yet automatically maintained, but:
- Use two spaces per level, and no tabs.
- Place `begin` on the same line as the earlier starting statement
e.g. `if`, and place the `end` on a separate line indented the same as
the starting statement.
Commit messages
---------------
Pull requests do not typically edit ``Changes`` in order to reduce
potential merge conflicts. Instead, contributors should use an appropriate
first line of the git commit message. Maintainers periodically run
``nodist/log_changes`` which analyzes the commit messages to suggest edits
to the ``Changes`` file.
The format of a commit message should be typically of the forms below. The
github issue number, and github pull number if different should be
included.
"Add <some feature> (#1234)"
For adding a new feature.
"Fix <some item> (#1234)"
For a bug fix.
"Improve <some item> (#1234)"
For improving something that previously existed.
"Optimize <some item> (#1234)"
For improving performance.
"Support <some feature> (#1234)"
For features that are described by IEEE were previously not supported.
"Commentary"
For a super-trivial change to e.g. documentation.
These will typically not get described in the Changes file.
"CI: <Add/Improve/etc as above> (#1234)"
For changes that only concern github actions.
These will typically not get described in the Changes file.
"Tests: <Add/Improve/etc as above> (#1234)"
For changes that only concern tests.
These will typically not get described in the Changes file.
"Internals: <Add/Improve/etc as above> (#1234)"
For changes that only concern developers, e.g. code cleanups.
These will typically not get described in the Changes file.
The line's grammar should match the phrasing used in the Changes file.
If the change does not affect user-visible function, also add "No
functional change." if it's 99% certain not to change behavior, or "No
functional change expected." if no change was expected but there may be
uncertainty.
The ``astgen`` Script
---------------------
@ -1164,7 +1215,7 @@ The ``astgen`` Script
The ``astgen`` script is used to generate some of the repetitive C++ code
related to the ``AstNode`` type hierarchy. An example is the abstract ``visit``
methods in ``VNVisitor``. There are other uses; please see the ``*__gen*``
files in the bulid directories and the ``astgen`` script for details. A
files in the build directories and the ``astgen`` script for details. A
description of the more advanced features of ``astgen`` are provided here.
@ -1573,7 +1624,7 @@ Benchmarking
------------
For benchmarking the effects of changes (simulation speed, memory consumption,
verilation time, etc.), you can use `RTLMeter
Verilation time, etc.), you can use `RTLMeter
<https://github.com/verilator/rtlmeter>`__, a benchmark suite designed for this
purpose. The scripts provided with RTLMeter have many capabilities. For full
details, see the `documentation of RTLMeter
@ -1995,10 +2046,8 @@ Generally, what would you do to add a new feature?
2. Make a testcase in the test_regress/t/t_EXAMPLE format, see `Testing`.
3. If grammar changes are needed, look at the git version of VerilogPerl's
src/VParseGrammar.y, as this grammar supports the full SystemVerilog
language and has a lot of back-and-forth with Verilator's grammar. Copy
the appropriate rules to src/verilog.y and modify the productions.
3. If grammar changes are needed, look at the IEEE 1800-2023 Appendix A, as
src/verilog.y generally follows the same rule layout.
4. If a new Ast type is needed, add it to the appropriate V3AstNode*.h.
Follow the convention described above about the AstNode type hierarchy.
@ -2065,6 +2114,14 @@ the regression tests with OBJCACHE enabled and in parallel on a machine
with many cores. See the -j option and OBJCACHE environment variable.
driver.py Pass-Thru Arguments
-----------------------------
Arguments not understood by `driver.py` which begin with `+verilator+` are
passed through as Verilated executable arguments. Other not-understood
arguments are passed as Verilator arguments.
driver.py Non-Scenario Arguments
--------------------------------

View File

@ -314,6 +314,7 @@ Noack
Nodine
Ober
Oleg
Oler
Olof
Olofsson
Oron
@ -470,6 +471,7 @@ Verilog
Vighnesh
Viktor
Vilp
VlWide
Vlip
Vm
Vukobratovic
@ -481,6 +483,7 @@ Wfuture
Whatson
Wildman
Wim
Wipfli
Wmisleading
Wno
Wojciech
@ -511,6 +514,7 @@ Zhang
abirkmanis
accessor
accessors
acyclic
adrienlemasle
agrobman
ahouska
@ -585,7 +589,9 @@ combinational
combinatorial
commandArgsPlusMatch
compilable
computable
concat
concatenatable
concats
conf
config
@ -600,6 +606,7 @@ coroutines
countbits
countones
cout
covergroup
cpp
cppstyle
cpu
@ -624,9 +631,11 @@ defparam
demangling
dep
deparametrized
deque
der
dereference
dereferenced
dereferencing
desassign
deserialize
destructor
@ -636,6 +645,7 @@ dev
devcontainer
devel
dir
disambiguates
displayb
distcc
doxygen
@ -664,6 +674,7 @@ elab
eliasphanna
elike
elsif
enablement
endcase
endcelldefine
endclass
@ -969,6 +980,8 @@ realtime
realtobits
recoding
recrem
recurse
recurses
redeclaring
regs
reloop
@ -1100,6 +1113,7 @@ unroller
unsized
unsup
untyped
upcasting
urandom
uselib
utimes

View File

@ -72,7 +72,7 @@
# include <execinfo.h>
# define _VL_HAVE_STACKTRACE
#endif
#if defined(__linux) || (defined(__APPLE__) && defined(__MACH__))
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
# include <sys/time.h>
# include <sys/resource.h>
# define _VL_HAVE_GETRLIMIT
@ -168,7 +168,12 @@ void vl_fatal(const char* filename, int linenum, const char* hier, const char* m
// Callbacks prior to termination
Verilated::runExitCallbacks();
std::abort();
if (Verilated::debug()) {
std::abort();
} else {
std::exit(1);
}
}
#endif
@ -622,11 +627,9 @@ WDataOutP VL_POW_WWW(int obits, int, int rbits, WDataOutP owp, const WDataInP lw
VL_DEBUG_IFDEF(assert(owords <= VL_MULS_MAX_WORDS););
owp[0] = 1;
for (int i = 1; i < VL_WORDS_I(obits); ++i) owp[i] = 0;
// cppcheck-has-bug-suppress variableScope
VlWide<VL_MULS_MAX_WORDS> powstore; // Fixed size, as MSVC++ doesn't allow [words] here
VlWide<VL_MULS_MAX_WORDS> lastpowstore; // Fixed size, as MSVC++ doesn't allow [words] here
VlWide<VL_MULS_MAX_WORDS> lastoutstore; // Fixed size, as MSVC++ doesn't allow [words] here
// cppcheck-has-bug-suppress variableScope
VL_ASSIGN_W(obits, powstore, lwp);
for (int bit = 0; bit < rbits; ++bit) {
if (bit > 0) { // power = power*power
@ -1379,7 +1382,6 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
_vl_vsss_skipspace(fp, floc, fromp, fstr);
_vl_vsss_read_str(fp, floc, fromp, fstr, t_tmp, "+-.0123456789eE");
if (!t_tmp[0]) goto done;
// cppcheck-has-bug-suppress unusedStructMember, unreadVariable
union {
double r;
int64_t ld;
@ -1753,7 +1755,7 @@ uint64_t VL_MURMUR64_HASH(const char* key) VL_PURE {
uint64_t h = seed ^ (len * m);
const uint64_t* data = (const uint64_t*)key;
const uint64_t* data = reinterpret_cast<const uint64_t*>(key);
const uint64_t* end = data + (len / 8);
while (data != end) {
@ -1767,7 +1769,7 @@ uint64_t VL_MURMUR64_HASH(const char* key) VL_PURE {
h *= m;
}
const unsigned char* data2 = (const unsigned char*)data;
const unsigned char* data2 = reinterpret_cast<const unsigned char*>(data);
switch (len & 7) {
case 7: h ^= uint64_t(data2[6]) << 48; /* fallthrough */
@ -2146,7 +2148,6 @@ VlReadMem::VlReadMem(bool hex, int bits, const std::string& filename, QData star
if (VL_UNLIKELY(!m_fp)) {
// We don't report the Verilog source filename as it slow to have to pass it down
VL_WARN_MT(filename.c_str(), 0, "", "$readmem file not found");
// cppcheck-has-bug-suppress resourceLeak // m_fp is nullptr
return;
}
}
@ -2286,7 +2287,6 @@ VlWriteMem::VlWriteMem(bool hex, int bits, const std::string& filename, QData st
m_fp = std::fopen(filename.c_str(), "w");
if (VL_UNLIKELY(!m_fp)) {
VL_FATAL_MT(filename.c_str(), 0, "", "$writemem file not found");
// cppcheck-has-bug-suppress resourceLeak // m_fp is nullptr
return;
}
}
@ -2470,7 +2470,6 @@ static const char* vl_time_str(int scale) VL_PURE {
double vl_time_multiplier(int scale) VL_PURE {
// Return timescale multiplier -18 to +18
// For speed, this does not check for illegal values
// cppcheck-has-bug-suppress arrayIndexOutOfBoundsCond
if (scale < 0) {
static const double neg10[] = {1.0,
0.1,
@ -2491,7 +2490,6 @@ double vl_time_multiplier(int scale) VL_PURE {
0.0000000000000001,
0.00000000000000001,
0.000000000000000001};
// cppcheck-has-bug-suppress arrayIndexOutOfBoundsCond
return neg10[-scale];
} else {
static const double pow10[] = {1.0,
@ -2513,7 +2511,6 @@ double vl_time_multiplier(int scale) VL_PURE {
10000000000000000.0,
100000000000000000.0,
1000000000000000000.0};
// cppcheck-has-bug-suppress arrayIndexOutOfBoundsCond
return pow10[scale];
}
}
@ -2809,7 +2806,7 @@ void VerilatedContext::internalsDump() const VL_MT_SAFE {
VerilatedImp::userDump();
}
void VerilatedContext::addModel(VerilatedModel* modelp) {
void VerilatedContext::addModel(const VerilatedModel* modelp) {
if (!quiet()) {
// CPU time isn't read as starting point until model creation, so that quiet() is set
// Thus if quiet(), avoids slow OS read affecting some usages that make many models
@ -2847,7 +2844,7 @@ VerilatedVirtualBase* VerilatedContext::threadPoolp() {
void VerilatedContext::prepareClone() { delete m_threadPool.release(); }
VerilatedVirtualBase* VerilatedContext::threadPoolpOnClone() {
if (VL_UNLIKELY(m_threadPool)) m_threadPool.release();
if (VL_UNLIKELY(m_threadPool)) (void)m_threadPool.release();
m_threadPool = std::unique_ptr<VlThreadPool>(new VlThreadPool{this, m_threads - 1});
return m_threadPool.get();
}
@ -3061,7 +3058,9 @@ void VerilatedContext::statsPrintSummary() VL_MT_UNSAFE {
= vl_timescaled_double((cputime != 0.0) ? (simtimeInUnits / cputime) : 0, "%0.3f %s");
VL_PRINTF("- Verilator: %s at %s; walltime %0.3f s; speed %s/s\n", endwhy.c_str(),
simtime.c_str(), walltime, simtimePerf.c_str());
const double modelMB = VlOs::memUsageBytes() / 1024.0 / 1024.0;
uint64_t memPeak, memCurrent;
VlOs::memUsageBytes(memPeak /*ref*/, memCurrent /*ref*/);
const double modelMB = memPeak / 1024.0 / 1024.0;
VL_PRINTF("- Verilator: cpu %0.3f s on %u threads; alloced %0.0f MB\n", cputime,
threadsInModels(), modelMB);
}
@ -3126,7 +3125,7 @@ void VerilatedContext::trace(VerilatedTraceBaseC* tfp, int levels, int options)
VL_FATAL_MT("", 0, "",
"Testbench C call to 'VerilatedContext::trace()' requires model(s) Verilated"
" with --trace-fst or --trace-vcd option");
for (auto& cbr : m_ns.m_traceBaseModelCbs) cbr(tfp, levels, options);
for (const auto& cbr : m_ns.m_traceBaseModelCbs) cbr(tfp, levels, options);
}
void VerilatedContext::traceBaseModelCbAdd(traceBaseModelCb_t cb) VL_MT_SAFE {
// Model creation registering a callback for when Verilated::trace() called
@ -3141,7 +3140,6 @@ VerilatedSyms::VerilatedSyms(VerilatedContext* contextp)
: _vm_contextp__(contextp ? contextp : Verilated::threadContextp()) {
VerilatedContext::checkMagic(_vm_contextp__);
Verilated::threadContextp(_vm_contextp__);
// cppcheck-has-bug-suppress noCopyConstructor
__Vm_evalMsgQp = new VerilatedEvalMsgQueue;
}
@ -3381,7 +3379,7 @@ VerilatedModule::VerilatedModule(const char* namep)
VerilatedModule::~VerilatedModule() {
// Memory cleanup - not called during normal operation
// NOLINTNEXTLINE(google-readability-casting)
// cppcheck-suppress cstyleCast // NOLINTNEXTLINE(google-readability-casting)
if (m_namep) VL_DO_CLEAR(free((void*)(m_namep)), m_namep = nullptr);
}
@ -3543,16 +3541,16 @@ void VerilatedScope::scopeDump() const {
VerilatedImp::exportName(i));
}
}
if (const VerilatedVarNameMap* const varsp = this->varsp()) {
for (const auto& i : *varsp) VL_PRINTF_MT(" VAR %p: %s\n", &(i.second), i.first);
if (const VerilatedVarNameMap* const ivarsp = this->varsp()) {
for (const auto& i : *ivarsp) VL_PRINTF_MT(" VAR %p: %s\n", &(i.second), i.first);
}
}
void VerilatedHierarchy::add(VerilatedScope* fromp, VerilatedScope* top) {
void VerilatedHierarchy::add(const VerilatedScope* fromp, const VerilatedScope* top) {
VerilatedImp::hierarchyAdd(fromp, top);
}
void VerilatedHierarchy::remove(VerilatedScope* fromp, VerilatedScope* top) {
void VerilatedHierarchy::remove(const VerilatedScope* fromp, const VerilatedScope* top) {
VerilatedImp::hierarchyRemove(fromp, top);
}

View File

@ -644,7 +644,7 @@ public:
}
// Internal: Model and thread setup
void addModel(VerilatedModel*);
void addModel(const VerilatedModel* modelp);
VerilatedVirtualBase* threadPoolp();
void prepareClone();
VerilatedVirtualBase* threadPoolpOnClone();
@ -763,8 +763,8 @@ public: // But internals only - called from VerilatedModule's
class VerilatedHierarchy final {
public:
static void add(VerilatedScope* fromp, VerilatedScope* top);
static void remove(VerilatedScope* fromp, VerilatedScope* top);
static void add(const VerilatedScope* fromp, const VerilatedScope* top);
static void remove(const VerilatedScope* fromp, const VerilatedScope* top);
};
//===========================================================================

View File

@ -84,6 +84,7 @@ VK_CPPFLAGS_ALWAYS += \
-MMD \
-I$(VERILATOR_ROOT)/include \
-I$(VERILATOR_ROOT)/include/vltstd \
-DVERILATOR=1 \
-DVM_COVERAGE=$(VM_COVERAGE) \
-DVM_SC=$(VM_SC) \
-DVM_TIMING=$(VM_TIMING) \

View File

@ -255,17 +255,21 @@ private:
public:
// PUBLIC METHODS
// cppcheck-suppress duplInheritedMember
std::string defaultFilename() VL_MT_SAFE { return m_contextp->coverageFilename(); }
// cppcheck-suppress duplInheritedMember
void forcePerInstance(const bool flag) VL_MT_SAFE_EXCLUDES(m_mutex) {
Verilated::quiesce();
const VerilatedLockGuard lock{m_mutex};
m_forcePerInstance = flag;
}
// cppcheck-suppress duplInheritedMember
void clear() VL_MT_SAFE_EXCLUDES(m_mutex) {
Verilated::quiesce();
const VerilatedLockGuard lock{m_mutex};
clearGuts();
}
// cppcheck-suppress duplInheritedMember
void clearNonMatch(const char* const matchp) VL_MT_SAFE_EXCLUDES(m_mutex) {
Verilated::quiesce();
const VerilatedLockGuard lock{m_mutex};
@ -281,10 +285,11 @@ public:
m_items = newlist;
}
}
// cppcheck-suppress duplInheritedMember
void zero() VL_MT_SAFE_EXCLUDES(m_mutex) {
Verilated::quiesce();
const VerilatedLockGuard lock{m_mutex};
for (const auto& itemp : m_items) itemp->zero();
for (const VerilatedCovImpItem* const itemp : m_items) itemp->zero();
}
// We assume there's always call to i/f/p in that order
@ -307,6 +312,7 @@ public:
valps[0] = m_insertFilenamep;
const std::string linestr = std::to_string(m_insertLineno);
ckeyps[1] = "lineno";
// cppcheck-suppress autoVariables // Used only below for insert
valps[1] = linestr.c_str();
// Default page if not specified
const char* fnstartp = m_insertFilenamep;
@ -316,6 +322,7 @@ public:
const size_t page_len = fnendp - fnstartp;
const std::string page_default = "sp_user/" + std::string{fnstartp, page_len};
ckeyps[2] = "page";
// cppcheck-suppress autoVariables // Used only below for insert
valps[2] = page_default.c_str();
// Keys -> strings
@ -357,6 +364,7 @@ public:
m_insertp = nullptr;
}
// cppcheck-suppress duplInheritedMember
void write(const std::string& filename) VL_MT_SAFE_EXCLUDES(m_mutex) {
Verilated::quiesce();
const VerilatedLockGuard lock{m_mutex};

View File

@ -75,6 +75,70 @@ class VerilatedCovImp;
ccontextp->_insertp("hier", name, __VA_ARGS__); \
} while (false)
static inline void VL_COV_TOGGLE_CHG_ST_I(const int width, uint32_t* covp, const IData newData,
const IData oldData) {
const IData chgData = newData ^ oldData;
for (int i = 0; i < width; ++i) {
*(covp + 2 * i + ((newData >> i) & 1)) += (chgData >> i) & 1;
}
}
static inline void VL_COV_TOGGLE_CHG_ST_Q(const int width, uint32_t* covp, const QData newData,
const QData oldData) {
const QData chgData = newData ^ oldData;
for (int i = 0; i < width; ++i) {
*(covp + 2 * i + ((newData >> i) & 1)) += (chgData >> i) & 1;
}
}
static inline void VL_COV_TOGGLE_CHG_ST_W(const int width, uint32_t* covp, WDataInP newData,
WDataInP oldData) {
for (int i = 0; i < VL_WORDS_I(width); ++i) {
const EData chgData = newData[i] ^ oldData[i];
if (chgData) {
for (int j = 0; j < width - i * VL_EDATASIZE; ++j) {
*(covp + (i * VL_EDATASIZE + j) * 2 + ((newData[i] >> j) & 1))
+= (chgData >> j) & 1;
}
}
}
}
static inline void VL_COV_TOGGLE_CHG_MT_I(const int width, std::atomic<uint32_t>* covp,
const IData newData, const IData oldData) VL_MT_SAFE {
const IData chgData = newData ^ oldData;
for (int i = 0; i < width; ++i) {
if (VL_BITISSET_I(chgData, i)) {
(covp + 2 * i + ((newData >> i) & 1))->fetch_add(1, std::memory_order_relaxed);
}
}
}
static inline void VL_COV_TOGGLE_CHG_MT_Q(const int width, std::atomic<uint32_t>* covp,
const QData newData, const QData oldData) VL_MT_SAFE {
const QData chgData = newData ^ oldData;
for (int i = 0; i < width; ++i) {
if (VL_BITISSET_Q(chgData, i)) {
(covp + 2 * i + ((newData >> i) & 1))->fetch_add(1, std::memory_order_relaxed);
}
}
}
static inline void VL_COV_TOGGLE_CHG_MT_W(const int width, std::atomic<uint32_t>* covp,
WDataInP newData, WDataInP oldData) VL_MT_SAFE {
for (int i = 0; i < VL_WORDS_I(width); ++i) {
const EData chgData = newData[i] ^ oldData[i];
if (chgData) {
for (int j = 0; j < width - i * VL_EDATASIZE; ++j) {
if (VL_BITISSET_E(chgData, j)) {
(covp + (i * VL_EDATASIZE + j) * 2 + ((newData[i] >> j) & 1))
->fetch_add(1, std::memory_order_relaxed);
}
}
}
}
}
//=============================================================================
// VerilatedCov
/// Per-VerilatedContext coverage data class.

View File

@ -80,6 +80,7 @@ void svPutBitselLogic(svLogicVecVal* dp, int bit, svLogic s) {
}
void svGetPartselBit(svBitVecVal* dp, const svBitVecVal* sp, int lsb, int width) {
// Verilator supports > 32 bit widths, which is an extension to IEEE DPI
// See also VL_SEL_WWI
const int msb = lsb + width - 1;
const int word_shift = VL_BITWORD_I(lsb);
@ -103,6 +104,7 @@ void svGetPartselBit(svBitVecVal* dp, const svBitVecVal* sp, int lsb, int width)
dp[VL_WORDS_I(width) - 1] &= VL_MASK_I(width);
}
void svGetPartselLogic(svLogicVecVal* dp, const svLogicVecVal* sp, int lsb, int width) {
// Verilator supports > 32 bit widths, which is an extension to IEEE DPI
const int msb = lsb + width - 1;
const int word_shift = VL_BITWORD_I(lsb);
if (VL_BITBIT_I(lsb) == 0) {
@ -288,7 +290,7 @@ static void _vl_svGetBitArrElemVecVal(svBitVecVal* d, const svOpenArrayHandle s,
break;
}
case VLVT_WDATA: {
WDataOutP wdatap = (reinterpret_cast<WDataOutP>(datap));
WDataInP wdatap = (reinterpret_cast<WDataInP>(datap));
for (int i = 0; i < VL_WORDS_I(varp->entBits()); ++i) d[i] = wdatap[i];
return;
}
@ -327,7 +329,7 @@ static void _vl_svGetLogicArrElemVecVal(svLogicVecVal* d, const svOpenArrayHandl
break;
}
case VLVT_WDATA: {
WDataOutP wdatap = (reinterpret_cast<WDataOutP>(datap));
WDataInP wdatap = (reinterpret_cast<WDataInP>(datap));
for (int i = 0; i < VL_WORDS_I(varp->entBits()); ++i) {
d[i].aval = wdatap[i];
d[i].bval = 0;

View File

@ -297,7 +297,7 @@ VerilatedFst::Buffer* VerilatedFst::getTraceBuffer(uint32_t fidx) {
void VerilatedFst::commitTraceBuffer(VerilatedFst::Buffer* bufp) {
if (offload()) {
OffloadBuffer* const offloadBufferp = static_cast<OffloadBuffer*>(bufp);
const OffloadBuffer* const offloadBufferp = static_cast<const OffloadBuffer*>(bufp);
if (offloadBufferp->m_offloadBufferWritep) {
m_offloadBufferWritep = offloadBufferp->m_offloadBufferWritep;
return; // Buffer will be deleted by the offload thread

View File

@ -279,11 +279,11 @@ static inline IData VL_RTOI_I_D(double lhs) VL_PURE { return static_cast<int32_t
// Sign extend such that if MSB set, we get ffff_ffff, else 0s
// (Requires clean input)
#define VL_SIGN_I(nbits, lhs) ((lhs) >> VL_BITBIT_I((nbits)-VL_UL(1)))
#define VL_SIGN_Q(nbits, lhs) ((lhs) >> VL_BITBIT_Q((nbits)-1ULL))
#define VL_SIGN_E(nbits, lhs) ((lhs) >> VL_BITBIT_E((nbits)-VL_EUL(1)))
#define VL_SIGN_I(nbits, lhs) ((lhs) >> VL_BITBIT_I((nbits) - VL_UL(1)))
#define VL_SIGN_Q(nbits, lhs) ((lhs) >> VL_BITBIT_Q((nbits) - 1ULL))
#define VL_SIGN_E(nbits, lhs) ((lhs) >> VL_BITBIT_E((nbits) - VL_EUL(1)))
#define VL_SIGN_W(nbits, rwp) \
((rwp)[VL_BITWORD_E((nbits)-VL_EUL(1))] >> VL_BITBIT_E((nbits)-VL_EUL(1)))
((rwp)[VL_BITWORD_E((nbits) - VL_EUL(1))] >> VL_BITBIT_E((nbits) - VL_EUL(1)))
#define VL_SIGNONES_E(nbits, lhs) (-(VL_SIGN_E(nbits, lhs)))
// Sign bit extended up to MSB, doesn't include unsigned portion
@ -458,9 +458,11 @@ static inline void VL_ASSIGNBIT_WO(int bit, WDataOutP owp) VL_MT_SAFE {
//===================================================================
// SYSTEMC OPERATORS
// Copying verilog format to systemc integers and bit vectors.
// Copying verilog format to systemc integers, doubles, and bit vectors.
// Get a SystemC variable
#define VL_ASSIGN_DSD(obits, vvar, svar) \
{ (vvar) = (svar).read(); }
#define VL_ASSIGN_ISI(obits, vvar, svar) \
{ (vvar) = VL_CLEAN_II((obits), (obits), (svar).read()); }
#define VL_ASSIGN_QSQ(obits, vvar, svar) \
@ -504,9 +506,11 @@ static inline void VL_ASSIGNBIT_WO(int bit, WDataOutP owp) VL_MT_SAFE {
(owp)[words - 1] &= VL_MASK_E(obits); \
}
// Copying verilog format from systemc integers and bit vectors.
// Copying verilog format from systemc integers, doubles, and bit vectors.
// Set a SystemC variable
#define VL_ASSIGN_SDD(obits, svar, vvar) \
{ (svar).write(vvar); }
#define VL_ASSIGN_SII(obits, svar, vvar) \
{ (svar).write(vvar); }
#define VL_ASSIGN_SQQ(obits, svar, vvar) \
@ -554,8 +558,8 @@ static inline void VL_ASSIGNBIT_WO(int bit, WDataOutP owp) VL_MT_SAFE {
lsb += BITS_PER_DIGIT; \
} \
if (lsb < (obits)) { \
const uint32_t msb_data = VL_SEL_IWII((obits) + 1, (rwp).data(), lsb, (obits)-lsb); \
*chunkp = msb_data & VL_MASK_E((obits)-lsb); \
const uint32_t msb_data = VL_SEL_IWII((obits) + 1, (rwp).data(), lsb, (obits) - lsb); \
*chunkp = msb_data & VL_MASK_E((obits) - lsb); \
} \
_butemp.set(0, *(rwp).data() & 1); /* force update the sign */ \
(svar).write(_butemp); \
@ -648,7 +652,7 @@ static inline IData VL_REDAND_IW(int lbits, WDataInP const lwp) VL_PURE {
EData combine = lwp[0];
for (int i = 1; i < words - 1; ++i) combine &= lwp[i];
combine &= ~VL_MASK_E(lbits) | lwp[words - 1];
// cppcheck-has-bug-suppress knownConditionTrueFalse
// cppcheck-suppress knownConditionTrueFalse
return ((~combine) == 0);
}
@ -1090,14 +1094,12 @@ static inline WDataOutP VL_MULS_WWW(int lbits, WDataOutP owp, WDataInP const lwp
if (lneg) { // Negate lhs
lwusp = lwstore;
VL_NEGATE_W(words, lwstore, lwp);
// cppcheck-has-bug-suppress unreadVariable
lwstore[words - 1] &= VL_MASK_E(lbits); // Clean it
}
const EData rneg = VL_SIGN_E(lbits, rwp[words - 1]);
if (rneg) { // Negate rhs
rwusp = rwstore;
VL_NEGATE_W(words, rwstore, rwp);
// cppcheck-has-bug-suppress unreadVariable
rwstore[words - 1] &= VL_MASK_E(lbits); // Clean it
}
VL_MUL_W(words, owp, lwusp, rwusp);
@ -2228,6 +2230,169 @@ static inline WDataOutP VL_SEL_WWII(int obits, int lbits, WDataOutP owp, WDataIn
return owp;
}
template <typename T>
static inline VlQueue<T> VL_CLONE_Q(const VlQueue<T>& from, int lbits, int srcElementBits,
int dstElementBits) {
VlQueue<T> ret;
VL_COPY_Q(ret, from, lbits, srcElementBits, dstElementBits);
return ret;
}
template <typename T>
static inline VlQueue<T> VL_REVCLONE_Q(const VlQueue<T>& from, int lbits, int srcElementBits,
int dstElementBits) {
VlQueue<T> ret;
VL_REVCOPY_Q(ret, from, lbits, srcElementBits, dstElementBits);
return ret;
}
// Helper function to get a bit from a queue at a specific bit index
template <typename T>
static inline bool VL_GET_QUEUE_BIT(const VlQueue<T>& queue, int srcElementBits, size_t bitIndex) {
const size_t elemIdx = bitIndex / srcElementBits;
if (VL_UNLIKELY(elemIdx >= queue.size())) return false;
const T element = queue.at(elemIdx);
if (srcElementBits == 1) {
return element & 1;
} else {
const size_t bitInElem = bitIndex % srcElementBits;
const size_t actualBitPos = srcElementBits - 1 - bitInElem;
return (element >> actualBitPos) & 1;
}
}
// Helper function to set a bit in the destination queue
template <typename T>
static inline void VL_SET_QUEUE_BIT(VlQueue<T>& queue, int dstElementBits, size_t bitIndex,
bool value) {
if (dstElementBits == 1) {
if (VL_UNLIKELY(bitIndex >= queue.size())) return;
queue.atWrite(bitIndex) = value ? 1 : 0;
} else {
const size_t elemIdx = bitIndex / dstElementBits;
if (VL_UNLIKELY(elemIdx >= queue.size())) return;
const size_t bitInElem = bitIndex % dstElementBits;
const size_t actualBitPos = dstElementBits - 1 - bitInElem;
if (value) {
queue.atWrite(elemIdx) |= (static_cast<T>(1) << actualBitPos);
} else {
queue.atWrite(elemIdx) &= ~(static_cast<T>(1) << actualBitPos);
}
}
}
// Helper function to get a bit from a VlWide queue at a specific bit index
template <std::size_t N_Words>
static inline bool VL_GET_QUEUE_BIT(const VlQueue<VlWide<N_Words>>& queue, int srcElementBits,
size_t bitIndex) {
const size_t elemIdx = bitIndex / srcElementBits;
if (VL_UNLIKELY(elemIdx >= queue.size())) return false;
const VlWide<N_Words>& element = queue.at(elemIdx);
const size_t bitInElem = bitIndex % srcElementBits;
const size_t actualBitPos = srcElementBits - 1 - bitInElem;
return VL_BITISSET_W(element.data(), actualBitPos);
}
// Helper function to set a bit in a VlWide queue at a specific bit index
template <std::size_t N_Words>
static inline void VL_SET_QUEUE_BIT(VlQueue<VlWide<N_Words>>& queue, int dstElementBits,
size_t bitIndex, bool value) {
const size_t elemIdx = bitIndex / dstElementBits;
if (VL_UNLIKELY(elemIdx >= queue.size())) return;
const size_t bitInElem = bitIndex % dstElementBits;
const size_t actualBitPos = dstElementBits - 1 - bitInElem;
VlWide<N_Words>& element = queue.atWrite(elemIdx);
if (value) {
VL_ASSIGNBIT_WO(actualBitPos, element.data());
} else {
VL_ASSIGNBIT_WI(actualBitPos, element.data(), 0);
}
}
template <typename T>
static inline void VL_ZERO_INIT_QUEUE_ELEM(T& elem) {
elem = 0;
}
template <std::size_t N_Words>
static inline void VL_ZERO_INIT_QUEUE_ELEM(VlWide<N_Words>& elem) {
for (size_t j = 0; j < N_Words; ++j) { elem.at(j) = 0; }
}
// This specialization works for both VlQueue<CData> (and similar) as well
// as VlQueue<VlWide<N>>.
template <typename T>
static inline void VL_COPY_Q(VlQueue<T>& q, const VlQueue<T>& from, int lbits, int srcElementBits,
int dstElementBits) {
if (srcElementBits == dstElementBits) {
// Simple case: same element bit width, direct copy of each element
if (VL_UNLIKELY(&q == &from)) return; // Skip self-assignment when it's truly a no-op
q = from;
} else {
// Different element bit widths: use streaming conversion
VlQueue<T> srcCopy = from;
const size_t srcTotalBits = from.size() * srcElementBits;
const size_t dstSize = (srcTotalBits + dstElementBits - 1) / dstElementBits;
q.renew(dstSize);
for (size_t i = 0; i < dstSize; ++i) { VL_ZERO_INIT_QUEUE_ELEM(q.atWrite(i)); }
for (size_t bitIndex = 0; bitIndex < srcTotalBits; ++bitIndex) {
VL_SET_QUEUE_BIT(q, dstElementBits, bitIndex,
VL_GET_QUEUE_BIT(srcCopy, srcElementBits, bitIndex));
}
}
}
// This specialization works for both VlQueue<CData> (and similar) as well
// as VlQueue<VlWide<N>>.
template <typename T>
static inline void VL_REVCOPY_Q(VlQueue<T>& q, const VlQueue<T>& from, int lbits,
int srcElementBits, int dstElementBits) {
const size_t srcTotalBits = from.size() * srcElementBits;
const size_t dstSize = (srcTotalBits + dstElementBits - 1) / dstElementBits;
// Always make a copy to handle the case where q and from are the same queue
VlQueue<T> srcCopy = from;
// Initialize all elements to zero using appropriate method
q.renew(dstSize);
for (size_t i = 0; i < dstSize; ++i) VL_ZERO_INIT_QUEUE_ELEM(q.atWrite(i));
if (lbits == 1) {
// Simple bit reversal: write directly to destination
for (int i = srcTotalBits - 1; i >= 0; --i) {
VL_SET_QUEUE_BIT(q, dstElementBits, srcTotalBits - 1 - i,
VL_GET_QUEUE_BIT(srcCopy, srcElementBits, i));
}
} else {
// Generalized block-reversal for lbits > 1:
// 1. Reverse all bits using 1-bit blocks
// 2. Split into lbits-sized blocks and pad incomplete blocks on the left
// 3. Reverse each lbits-sized block using 1-bit blocks
const size_t numCompleteBlocks = srcTotalBits / lbits;
const size_t remainderBits = srcTotalBits % lbits;
const size_t srcBlocks = numCompleteBlocks + (remainderBits > 0 ? 1 : 0);
size_t dstBitIndex = 0;
for (size_t block = 0; block < srcBlocks; ++block) {
const size_t blockStart = block * lbits;
const int bitsToProcess = VL_LIKELY(block < numCompleteBlocks) ? lbits : remainderBits;
for (int bit = bitsToProcess - 1; bit >= 0; --bit) {
const size_t reversedBitIndex = blockStart + bit;
const size_t originalBitIndex = srcTotalBits - 1 - reversedBitIndex;
VL_SET_QUEUE_BIT(q, dstElementBits, dstBitIndex++,
VL_GET_QUEUE_BIT(srcCopy, srcElementBits, originalBitIndex));
}
dstBitIndex += lbits - bitsToProcess;
}
}
}
//======================================================================
// Expressions needing insert/select
@ -2235,49 +2400,49 @@ static inline void VL_UNPACK_RI_I(int lbits, int rbits, VlQueue<CData>& q, IData
const size_t size = (rbits + lbits - 1) / lbits;
q.renew(size);
const IData mask = VL_MASK_I(lbits);
for (size_t i = 0; i < size; ++i) q.atWrite(q.size() - 1 - i) = (from >> (i * lbits)) & mask;
for (size_t i = 0; i < size; ++i) q.atWrite(size - 1 - i) = (from >> (i * lbits)) & mask;
}
static inline void VL_UNPACK_RI_I(int lbits, int rbits, VlQueue<SData>& q, IData from) {
const size_t size = (rbits + lbits - 1) / lbits;
q.renew(size);
const IData mask = VL_MASK_I(lbits);
for (size_t i = 0; i < size; ++i) q.atWrite(q.size() - 1 - i) = (from >> (i * lbits)) & mask;
for (size_t i = 0; i < size; ++i) q.atWrite(size - 1 - i) = (from >> (i * lbits)) & mask;
}
static inline void VL_UNPACK_RI_I(int lbits, int rbits, VlQueue<IData>& q, IData from) {
const size_t size = (rbits + lbits - 1) / lbits;
q.renew(size);
const IData mask = VL_MASK_I(lbits);
for (size_t i = 0; i < size; ++i) q.atWrite(q.size() - 1 - i) = (from >> (i * lbits)) & mask;
for (size_t i = 0; i < size; ++i) q.atWrite(size - 1 - i) = (from >> (i * lbits)) & mask;
}
static inline void VL_UNPACK_RI_Q(int lbits, int rbits, VlQueue<CData>& q, QData from) {
const size_t size = (rbits + lbits - 1) / lbits;
q.renew(size);
const IData mask = VL_MASK_I(lbits);
for (size_t i = 0; i < size; ++i) q.atWrite(q.size() - 1 - i) = (from >> (i * lbits)) & mask;
for (size_t i = 0; i < size; ++i) q.atWrite(size - 1 - i) = (from >> (i * lbits)) & mask;
}
static inline void VL_UNPACK_RI_Q(int lbits, int rbits, VlQueue<SData>& q, QData from) {
const size_t size = (rbits + lbits - 1) / lbits;
q.renew(size);
const IData mask = VL_MASK_I(lbits);
for (size_t i = 0; i < size; ++i) q.atWrite(q.size() - 1 - i) = (from >> (i * lbits)) & mask;
for (size_t i = 0; i < size; ++i) q.atWrite(size - 1 - i) = (from >> (i * lbits)) & mask;
}
static inline void VL_UNPACK_RI_Q(int lbits, int rbits, VlQueue<IData>& q, QData from) {
const size_t size = (rbits + lbits - 1) / lbits;
q.renew(size);
const IData mask = VL_MASK_I(lbits);
for (size_t i = 0; i < size; ++i) q.atWrite(q.size() - 1 - i) = (from >> (i * lbits)) & mask;
for (size_t i = 0; i < size; ++i) q.atWrite(size - 1 - i) = (from >> (i * lbits)) & mask;
}
static inline void VL_UNPACK_RQ_Q(int lbits, int rbits, VlQueue<QData>& q, QData from) {
const size_t size = (rbits + lbits - 1) / lbits;
q.renew(size);
const QData mask = VL_MASK_Q(lbits);
for (size_t i = 0; i < size; ++i) q.atWrite(q.size() - 1 - i) = (from >> (i * lbits)) & mask;
for (size_t i = 0; i < size; ++i) q.atWrite(size - 1 - i) = (from >> (i * lbits)) & mask;
}
static inline void VL_UNPACK_RI_W(int lbits, int rbits, VlQueue<CData>& q, WDataInP rwp) {
@ -2285,7 +2450,11 @@ static inline void VL_UNPACK_RI_W(int lbits, int rbits, VlQueue<CData>& q, WData
q.renew(size);
const IData mask = VL_MASK_I(lbits);
for (size_t i = 0; i < size; ++i) {
q.atWrite(i) = VL_SEL_IWII(rbits, rwp, i * lbits, lbits) & mask;
// Extract from MSB to LSB: MSB goes to index 0
const int bitPos = rbits - (i + 1) * lbits;
const int actualBitPos = (bitPos < 0) ? 0 : bitPos;
const int actualWidth = (bitPos < 0) ? (lbits + bitPos) : lbits;
q.atWrite(i) = VL_SEL_IWII(rbits, rwp, actualBitPos, actualWidth) & mask;
}
}
@ -2294,7 +2463,11 @@ static inline void VL_UNPACK_RI_W(int lbits, int rbits, VlQueue<SData>& q, WData
q.renew(size);
const IData mask = VL_MASK_I(lbits);
for (size_t i = 0; i < size; ++i) {
q.atWrite(i) = VL_SEL_IWII(rbits, rwp, i * lbits, lbits) & mask;
// Extract from MSB to LSB: MSB goes to index 0
const int bitPos = rbits - (i + 1) * lbits;
const int actualBitPos = (bitPos < 0) ? 0 : bitPos;
const int actualWidth = (bitPos < 0) ? (lbits + bitPos) : lbits;
q.atWrite(i) = VL_SEL_IWII(rbits, rwp, actualBitPos, actualWidth) & mask;
}
}
@ -2303,7 +2476,11 @@ static inline void VL_UNPACK_RI_W(int lbits, int rbits, VlQueue<IData>& q, WData
q.renew(size);
const IData mask = VL_MASK_I(lbits);
for (size_t i = 0; i < size; ++i) {
q.atWrite(i) = VL_SEL_IWII(rbits, rwp, i * lbits, lbits) & mask;
// Extract from MSB to LSB: MSB goes to index 0
const int bitPos = rbits - (i + 1) * lbits;
const int actualBitPos = (bitPos < 0) ? 0 : bitPos;
const int actualWidth = (bitPos < 0) ? (lbits + bitPos) : lbits;
q.atWrite(i) = VL_SEL_IWII(rbits, rwp, actualBitPos, actualWidth) & mask;
}
}
@ -2312,7 +2489,11 @@ static inline void VL_UNPACK_RQ_W(int lbits, int rbits, VlQueue<QData>& q, WData
q.renew(size);
const QData mask = VL_MASK_Q(lbits);
for (size_t i = 0; i < size; ++i) {
q.atWrite(i) = VL_SEL_QWII(rbits, rwp, i * lbits, lbits) & mask;
// Extract from MSB to LSB: MSB goes to index 0
const int bitPos = rbits - (i + 1) * lbits;
const int actualBitPos = (bitPos < 0) ? 0 : bitPos;
const int actualWidth = (bitPos < 0) ? (lbits + bitPos) : lbits;
q.atWrite(i) = VL_SEL_QWII(rbits, rwp, actualBitPos, actualWidth) & mask;
}
}
@ -2322,7 +2503,11 @@ static inline void VL_UNPACK_RW_W(int lbits, int rbits, VlQueue<VlWide<N_Words>>
const int size = (rbits + lbits - 1) / lbits;
q.renew(size);
for (size_t i = 0; i < size; ++i) {
VL_SEL_WWII(lbits, rbits, q.atWrite(i), rwp, i * lbits, lbits);
// Extract from MSB to LSB: MSB goes to index 0
const int bitPos = rbits - (i + 1) * lbits;
const int actualBitPos = (bitPos < 0) ? 0 : bitPos;
const int actualWidth = (bitPos < 0) ? (lbits + bitPos) : lbits;
VL_SEL_WWII(actualWidth, rbits, q.atWrite(i), rwp, actualBitPos, actualWidth);
}
}
@ -2573,6 +2758,7 @@ static inline void VL_SELASSIGN_WW(int rbits, int obits, WDataOutP iowp, WDataIn
const int w = obits < upperbits ? obits : upperbits;
const int insmask = VL_MASK_E(w);
iowp[0] = (iowp[0] & ~insmask) | ((rwp[wordoff] >> lsb) & insmask);
// cppcheck-suppress knownConditionTrueFalse
if (w == obits) return;
obits -= w;
}
@ -2804,6 +2990,12 @@ extern void VL_TIMEFORMAT_IINI(bool hasUnits, int units, bool hasPrecision, int
bool hasSuffix, const std::string& suffix, bool hasWidth, int width,
VerilatedContext* contextp) VL_MT_SAFE;
extern IData VL_VALUEPLUSARGS_INW(int rbits, const std::string& ld, WDataOutP rwp) VL_MT_SAFE;
inline IData VL_VALUEPLUSARGS_IND(int rbits, const std::string& ld, double& rdr) VL_MT_SAFE {
VlWide<2> rwp;
const IData got = VL_VALUEPLUSARGS_INW(rbits, ld, rwp);
if (got) rdr = VL_CVT_D_Q(VL_SET_QW(rwp));
return got;
}
inline IData VL_VALUEPLUSARGS_INI(int rbits, const std::string& ld, CData& rdr) VL_MT_SAFE {
VlWide<2> rwp;
const IData got = VL_VALUEPLUSARGS_INW(rbits, ld, rwp);

View File

@ -363,7 +363,7 @@ private:
VerilatedFpList fdToFpList(IData fdi) VL_REQUIRES(m_fdMutex) {
VerilatedFpList fp;
// cppverilator-suppress integerOverflow shiftTooManyBitsSigned
if ((fdi & (1 << 31)) != 0) {
if (VL_BITISSET_I(fdi, 31)) {
// Non-MCD case
const IData idx = fdi & VL_MASK_I(31);
switch (idx) {

View File

@ -145,7 +145,7 @@ void VlExecutionProfiler::dump(const char* filenamep, uint64_t tickEnd)
fprintf(fp, "VLPROF arg +verilator+prof+exec+window+%u\n",
Verilated::threadContextp()->profExecWindow());
std::string numa = "no threads";
if (VlThreadPool* const threadPoolp
if (const VlThreadPool* const threadPoolp
= static_cast<VlThreadPool*>(Verilated::threadContextp()->threadPoolp())) {
numa = threadPoolp->numaStatus();
}

View File

@ -218,7 +218,7 @@ class VlPgoProfiler final {
};
// Counters are stored packed, all together to reduce cache effects
std::array<uint64_t, N_Entries> m_counters; // Time spent on this record
std::array<uint64_t, N_Entries> m_counters{}; // Time spent on this record
std::vector<Record> m_records; // Record information
public:

View File

@ -455,8 +455,8 @@ bool VlRandomizer::parseSolution(std::iostream& f) {
const auto it = m_vars.find(name);
if (it == m_vars.end()) continue;
const VlRandomVar& varr = *it->second;
if (m_randmode && !varr.randModeIdxNone()) {
if (!(m_randmode->at(varr.randModeIdx()))) continue;
if (m_randmodep && !varr.randModeIdxNone()) {
if (!m_randmodep->at(varr.randModeIdx())) continue;
}
if (!indices.empty()) {
std::ostringstream oss;

View File

@ -200,7 +200,7 @@ class VlRandomizer final {
std::map<std::string, std::shared_ptr<const VlRandomVar>> m_vars; // Solver-dependent
// variables
ArrayInfoMap m_arr_vars; // Tracks each element in array structures for iteration
const VlQueue<CData>* m_randmode; // rand_mode state;
const VlQueue<CData>* m_randmodep = nullptr; // rand_mode state;
int m_index = 0; // Internal counter for key generation
// PRIVATE METHODS
@ -408,7 +408,7 @@ public:
// Record a flat (non-class) element into the array variable table
template <typename T>
typename std::enable_if<!std::is_class<T>::value, void>::type
typename std::enable_if<!std::is_class<T>::value || VlIsVlWide<T>::value, void>::type
record_arr_table(T& var, const std::string& name, int dimension, std::vector<IData> indices,
std::vector<size_t> idxWidths) {
const std::string key = generateKey(name, m_index);
@ -574,10 +574,28 @@ public:
void hard(std::string&& constraint);
void clear();
void set_randmode(const VlQueue<CData>& randmode) { m_randmode = &randmode; }
void set_randmode(const VlQueue<CData>& randmode) { m_randmodep = &randmode; }
#ifdef VL_DEBUG
void dump() const;
#endif
};
//=============================================================================
// VlStdRandomizer provides a light wrapper for RNG used by std::randomize()
// to support scope-level randomization.
class VlStdRandomizer final {
// MEMBERS
VlRNG m_rng; // Random number generator
public:
// CONSTRUCTORS
VlStdRandomizer() = default;
~VlStdRandomizer() = default;
template <typename T>
bool basicStdRandomization(T& value, size_t width) {
value = VL_MASK_I(width) & VL_RANDOM_RNG_I(m_rng);
return true;
}
};
#endif // Guard

View File

@ -220,7 +220,7 @@ void VerilatedRestore::fill() VL_MT_UNSAFE_ONE {
if (VL_UNLIKELY(!isOpen())) return;
// Move remaining characters down to start of buffer. (No memcpy, overlaps allowed)
uint8_t* rp = m_bufp;
for (uint8_t* sp = m_cp; sp < m_endp; *rp++ = *sp++) {} // Overlaps
for (const uint8_t* sp = m_cp; sp < m_endp; *rp++ = *sp++) {} // Overlaps
m_endp = m_bufp + (m_endp - m_cp);
m_cp = m_bufp; // Reset buffer
// Read into buffer starting at m_endp

View File

@ -47,8 +47,10 @@ protected:
std::string m_filename; // Filename, for error messages
VerilatedAssertOneThread m_assertOne; // Assert only called from single thread
static constexpr size_t bufferSize() { return 256 * 1024; } // See below for slack calculation
static constexpr size_t bufferInsertSize() { return 16 * 1024; }
static constexpr size_t bufferSize() {
return 256 * 1024L;
} // See below for slack calculation
static constexpr size_t bufferInsertSize() { return 16 * 1024L; }
void header() VL_MT_UNSAFE_ONE;
void trailer() VL_MT_UNSAFE_ONE;
@ -119,8 +121,10 @@ protected:
std::string m_filename; // Filename, for error messages
VerilatedAssertOneThread m_assertOne; // Assert only called from single thread
static constexpr size_t bufferSize() { return 256 * 1024; } // See below for slack calculation
static constexpr size_t bufferInsertSize() { return 16 * 1024; }
static constexpr size_t bufferSize() {
return 256 * 1024L;
} // See below for slack calculation
static constexpr size_t bufferInsertSize() { return 16 * 1024L; }
virtual void fill() = 0;
void header() VL_MT_UNSAFE_ONE;
@ -294,7 +298,7 @@ inline VerilatedDeserialize& operator>>(VerilatedDeserialize& os, std::string& r
uint32_t len = 0;
os >> len;
rhs.resize(len);
// NOLINTNEXTLINE(google-readability-casting)
// cppcheck-suppress cstyleCast // NOLINTNEXTLINE(google-readability-casting)
return os.read((void*)(rhs.data()), len);
}
VerilatedSerialize& operator<<(VerilatedSerialize& os, VerilatedContext* rhsp);

View File

@ -60,7 +60,7 @@ class VerilatedScTraceBase VL_NOT_FINAL : private sc_core::sc_object,
private sc_core::sc_trace_file {
bool m_enableDeltaCycles = false;
bool m_traceFileAdded = false;
static void stubReportHandler(const sc_core::sc_report&, const sc_core::sc_actions&){};
static void stubReportHandler(const sc_core::sc_report&, const sc_core::sc_actions&) {};
public:
void enableDeltaCycles(bool flag = true) {

View File

@ -175,6 +175,14 @@ package std;
`endif
endtask
static task killQueue(ref process processQueue[$]);
`ifdef VERILATOR_TIMING
while (processQueue.size() > 0) begin
processQueue.pop_back().kill();
end
`endif
endtask
// Two process references are equal if the different classes' containing
// m_process are equal. Can't yet use <=> as the base class template
// comparisons doesn't define <=> as they don't yet require --timing and C++20.

View File

@ -99,7 +99,7 @@ void VlDelayScheduler::dump() const {
VL_DBG_MSGF(" No delayed processes:\n");
} else {
VL_DBG_MSGF(" Delayed processes:\n");
for (auto& susp : m_zeroDelayed) {
for (const auto& susp : m_zeroDelayed) {
VL_DBG_MSGF(" Awaiting #0-delayed resumption, "
"time () %" PRIu64 ": ",
m_context.time());

View File

@ -905,7 +905,7 @@ VerilatedTraceOffloadBuffer<VL_BUF_T>::VerilatedTraceOffloadBuffer(VL_SUB_T& own
using This = VerilatedTraceBuffer<VL_BUF_T>*;
// Tack on the buffer address
static_assert(2 * sizeof(uint32_t) >= sizeof(This),
"This should be enough on all plafrorms");
"This should be enough on all platforms");
*m_offloadBufferWritep++ = VerilatedTraceOffloadCommand::TRACE_BUFFER;
*reinterpret_cast<This*>(m_offloadBufferWritep) = static_cast<This>(this);
m_offloadBufferWritep += 2;

View File

@ -495,11 +495,11 @@ public:
private:
// MEMBERS
Deque m_deque; // State of the assoc array
T_Value m_defaultValue; // Default value
T_Value m_defaultValue{}; // Default value
public:
// CONSTRUCTORS
// m_defaultValue isn't defaulted. Caller's constructor must do it.
// cppcheck-suppress uninitMemberVar // m_defaultValue isn't defaulted, caller must
VlQueue() = default;
~VlQueue() = default;
VlQueue(const VlQueue&) = default;
@ -621,11 +621,8 @@ public:
T_Value& atWriteAppend(int32_t index) {
// cppcheck-suppress variableScope
static thread_local T_Value t_throwAway;
if (index == m_deque.size()) push_back(atDefault());
if (VL_UNLIKELY(index < 0 || index >= m_deque.size())) {
if (index == m_deque.size()) {
push_back(atDefault());
return m_deque[index];
}
t_throwAway = atDefault();
return t_throwAway;
}
@ -917,7 +914,7 @@ public:
out += comma + VL_TO_STRING(i);
comma = ", ";
}
return out + "} ";
return out + "}";
}
};
@ -1256,7 +1253,7 @@ public:
comma = ", ";
}
// Default not printed - maybe random init data
return out + "} ";
return out + "}";
}
};
@ -1313,6 +1310,9 @@ class VlUnpacked final {
using Unpacked = T_Value[N_Depth];
public:
template <typename T_Func>
using WithFuncReturnType = decltype(std::declval<T_Func>()(0, std::declval<T_Value>()));
// MEMBERS
// This should be the only data member, otherwise generated static initializers need updating
Unpacked m_storage; // Contents of the unpacked array
@ -1561,6 +1561,63 @@ public:
return VlQueue<T_Value>::consV(*it);
}
T_Value r_sum() const {
T_Value out(0); // Type must have assignment operator
for (const auto& i : m_storage) out += i;
return out;
}
template <typename T_Func>
T_Value r_sum(T_Func with_func) const {
T_Value out(0); // Type must have assignment operator
for (const auto& i : m_storage) out += with_func(0, i);
return out;
}
T_Value r_product() const {
T_Value out = T_Value(1);
for (const auto& i : m_storage) out *= i;
return out;
}
template <typename T_Func>
T_Value r_product(T_Func with_func) const {
T_Value out = T_Value(1);
for (const auto& i : m_storage) out *= with_func(0, i);
return out;
}
T_Value r_and() const {
if (m_storage.empty()) return T_Value(0); // The big three do it this way
T_Value out = ~T_Value(0);
for (const auto& i : m_storage) out &= i;
return out;
}
template <typename T_Func>
T_Value r_and(T_Func with_func) const {
T_Value out = ~T_Value(0);
for (const auto& i : m_storage) out &= with_func(0, i);
return out;
}
T_Value r_or() const {
T_Value out = T_Value(0);
for (const auto& i : m_storage) out |= i;
return out;
}
template <typename T_Func>
T_Value r_or(T_Func with_func) const {
T_Value out = T_Value(0);
for (const auto& i : m_storage) out |= with_func(0, i);
return out;
}
T_Value r_xor() const {
T_Value out = T_Value(0);
for (const auto& i : m_storage) out ^= i;
return out;
}
template <typename T_Func>
T_Value r_xor(T_Func with_func) const {
T_Value out = T_Value(0);
for (const auto& i : m_storage) out ^= with_func(0, i);
return out;
}
// Dumping. Verilog: str = $sformatf("%p", assoc)
std::string to_string() const {
std::string out = "'{";
@ -1569,7 +1626,7 @@ public:
out += comma + VL_TO_STRING(m_storage[i]);
comma = ", ";
}
return out + "} ";
return out + "}";
}
private:

View File

@ -178,7 +178,7 @@ void VerilatedVcd::openNextImp(bool incFilename) {
}
}
m_isOpen = true;
constDump(true); // First dump must containt the const signals
constDump(true); // First dump must contain the const signals
fullDump(true); // First dump must be full
m_wroteBytes = 0;
}
@ -490,6 +490,7 @@ VerilatedVcd::Buffer* VerilatedVcd::getTraceBuffer(uint32_t fidx) {
// Note: This is called from VerilatedVcd::dump, which already holds the lock
// If no buffer available, allocate a new one
if (m_freeBuffers.empty()) {
// cppcheck-suppress unreadVariable // cppcheck bug, used below
constexpr size_t pageSize = 4096;
// 4 * m_maxSignalBytes, so we can reserve 2 * m_maxSignalBytes at the end for safety
size_t startingSize = roundUpToMultipleOf<pageSize>(4 * m_maxSignalBytes);

View File

@ -105,7 +105,7 @@ protected:
void commitTraceBuffer(Buffer*) override;
// Configure sub-class
void configure(const VerilatedTraceConfig&) override{};
void configure(const VerilatedTraceConfig&) override {};
public:
//=========================================================================

View File

@ -114,6 +114,7 @@ public:
#endif
}
// MEMBERS
// cppcheck-suppress duplInheritedMember
static VerilatedVpio* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpio*>(reinterpret_cast<VerilatedVpio*>(h));
}
@ -144,6 +145,7 @@ public:
, m_time{time}
, m_reason{reason} {}
~VerilatedVpioReasonCb() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioReasonCb* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioReasonCb*>(reinterpret_cast<VerilatedVpioReasonCb*>(h));
}
@ -158,6 +160,7 @@ public:
explicit VerilatedVpioConst(int32_t num)
: m_num{num} {}
~VerilatedVpioConst() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioConst* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioConst*>(reinterpret_cast<VerilatedVpio*>(h));
}
@ -187,6 +190,7 @@ public:
m_indexedDim = varp->m_indexedDim;
}
}
// cppcheck-suppress duplInheritedMember
static VerilatedVpioVarBase* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioVarBase*>(reinterpret_cast<VerilatedVpio*>(h));
}
@ -258,6 +262,7 @@ public:
: VerilatedVpioVarBase{varp, scopep} {}
~VerilatedVpioParam() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioParam* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioParam*>(reinterpret_cast<VerilatedVpio*>(h));
}
@ -283,6 +288,7 @@ public:
explicit VerilatedVpioRange(const VerilatedRange* rangep)
: m_rangep{rangep} {}
~VerilatedVpioRange() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioRange* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioRange*>(reinterpret_cast<VerilatedVpio*>(h));
}
@ -301,6 +307,7 @@ public:
m_iter = m_ranges.begin();
}
~VerilatedVpioRangeIter() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioRangeIter* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioRangeIter*>(reinterpret_cast<VerilatedVpio*>(h));
}
@ -310,7 +317,7 @@ public:
delete this; // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle
return nullptr;
}
VerilatedRange* const rangep = new VerilatedRange(*m_iter);
VerilatedRange* const rangep = new VerilatedRange{*m_iter};
++m_iter;
return ((new VerilatedVpioRange{rangep})->castVpiHandle());
}
@ -333,6 +340,7 @@ public:
m_defname = m_scopep->defname();
}
~VerilatedVpioScope() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioScope* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioScope*>(reinterpret_cast<VerilatedVpio*>(h));
}
@ -379,6 +387,7 @@ public:
~VerilatedVpioVar() override {
if (m_prevDatap) VL_DO_CLEAR(delete[] m_prevDatap, m_prevDatap = nullptr);
}
// cppcheck-suppress duplInheritedMember
static VerilatedVpioVar* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioVar*>(reinterpret_cast<VerilatedVpio*>(h));
}
@ -429,7 +438,7 @@ public:
void createPrevDatap() {
if (VL_UNLIKELY(!m_prevDatap)) {
m_prevDatap = new uint8_t[entSize()];
std::memcpy(prevDatap(), varp()->datap(), entSize());
std::memcpy(prevDatap(), m_varDatap, entSize());
}
}
};
@ -450,6 +459,7 @@ public:
m_topscopep = Verilated::threadContextp()->scopeFind("TOP");
}
~VerilatedVpioVarIter() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioVarIter* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioVarIter*>(reinterpret_cast<VerilatedVpio*>(h));
}
@ -508,6 +518,7 @@ public:
for (auto it : m_ranges) m_nextIndex.push_back(it.right());
}
~VerilatedVpioRegIter() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioRegIter* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioRegIter*>(reinterpret_cast<VerilatedVpio*>(h));
}
@ -562,6 +573,7 @@ public:
}
if (VL_UNLIKELY(pos == std::string::npos)) m_toplevel = true;
}
// cppcheck-suppress duplInheritedMember
static VerilatedVpioModule* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioModule*>(reinterpret_cast<VerilatedVpio*>(h));
}
@ -578,6 +590,7 @@ public:
m_it = m_vec->begin();
}
~VerilatedVpioModuleIter() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioModuleIter* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioModuleIter*>(reinterpret_cast<VerilatedVpio*>(h));
}
@ -607,6 +620,7 @@ public:
m_it = m_vec->begin();
}
~VerilatedVpioScopeIter() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioScopeIter* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioScopeIter*>(reinterpret_cast<VerilatedVpio*>(h));
}
@ -640,6 +654,7 @@ public:
if (strcmp(m_name, "\\$unit ") == 0) m_name = d_unit;
}
// cppcheck-suppress duplInheritedMember
static VerilatedVpioPackage* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioPackage*>(reinterpret_cast<VerilatedVpio*>(h));
}
@ -657,6 +672,7 @@ public:
m_it = m_vec->begin();
}
~VerilatedVpioInstanceIter() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioInstanceIter* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioInstanceIter*>(reinterpret_cast<VerilatedVpio*>(h));
}
@ -1041,7 +1057,7 @@ public:
}
if (was_last) break;
}
for (const auto& ip : update) {
for (const VerilatedVpioVar* const ip : update) {
std::memcpy(ip->prevDatap(), ip->varDatap(), ip->entSize());
}
return called;
@ -2059,7 +2075,10 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) {
const VerilatedScope* scopep;
const VerilatedVpioScope* const voScopep = VerilatedVpioScope::castp(scope);
std::string scopeAndName = namep;
if (voScopep) {
if (0 == std::strncmp(namep, "$root.", std::strlen("$root."))) {
namep += std::strlen("$root.");
scopeAndName = namep;
} else if (voScopep) {
const bool scopeIsPackage = VerilatedVpioPackage::castp(scope) != nullptr;
scopeAndName = std::string{voScopep->fullname()} + (scopeIsPackage ? "" : ".") + namep;
namep = const_cast<PLI_BYTE8*>(scopeAndName.c_str());
@ -2537,7 +2556,7 @@ void vl_vpi_put_word_gen(const VerilatedVpioVar* vop, T word, size_t bitCount, s
= (info.m_datap[info.m_wordOffset + 1] & ~info.m_maskHi)
| ((word >> (wordBits - info.m_bitOffset)) & info.m_maskHi);
}
// cppcheck-has-bug-suppress unreadVariable
// cppcheck-suppress unreadVariable
info.m_datap[info.m_wordOffset] = (info.m_datap[info.m_wordOffset] & ~info.m_maskLo)
| ((word << info.m_bitOffset) & info.m_maskLo);
}
@ -3050,7 +3069,7 @@ void vl_get_value_array_rawvals(unsigned index, unsigned num, const unsigned siz
const unsigned packedSize, const bool leftIsLow,
const bool fourState, const T* src, PLI_BYTE8* dst) {
static_assert(std::is_unsigned<T>::value,
"type T is not unsigned"); //ensure loigcal right shift
"type T is not unsigned"); //ensure logical right shift
const unsigned element_size_bytes VL_BYTES_I(packedSize);
const unsigned element_size_repr = (element_size_bytes + sizeof(T) - 1) / sizeof(T);
size_t dst_index = 0;
@ -3128,7 +3147,7 @@ void vl_get_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, const P
"increase and recompile");
}
PLI_INT16* shortintsp = (PLI_INT16*)out_data;
PLI_INT16* shortintsp = reinterpret_cast<PLI_INT16*>(out_data);
arrayvalue_p->value.shortints = shortintsp;
if (varp->vltype() == VLVT_UINT8) {
@ -3147,7 +3166,7 @@ void vl_get_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, const P
"increase and recompile");
}
PLI_INT32* integersp = (PLI_INT32*)out_data;
PLI_INT32* integersp = reinterpret_cast<PLI_INT32*>(out_data);
arrayvalue_p->value.integers = integersp;
if (varp->vltype() == VLVT_UINT8) {
@ -3169,7 +3188,7 @@ void vl_get_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, const P
"increase and recompile");
}
PLI_INT64* longintsp = (PLI_INT64*)out_data;
PLI_INT64* longintsp = reinterpret_cast<PLI_INT64*>(out_data);
arrayvalue_p->value.longints = longintsp;
if (varp->vltype() == VLVT_UINT8) {
@ -3194,7 +3213,7 @@ void vl_get_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, const P
"increase and recompile");
}
p_vpi_vecval vectorsp = (p_vpi_vecval)out_data;
p_vpi_vecval vectorsp = reinterpret_cast<p_vpi_vecval>(out_data);
arrayvalue_p->value.vectors = vectorsp;
if (varp->vltype() == VLVT_UINT8) {
@ -3222,7 +3241,7 @@ void vl_get_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, const P
"increase and recompile");
}
PLI_BYTE8* valuep = (PLI_BYTE8*)out_data;
PLI_BYTE8* valuep = reinterpret_cast<PLI_BYTE8*>(out_data);
arrayvalue_p->value.rawvals = valuep;
if (varp->vltype() == VLVT_UINT8) {
@ -3250,7 +3269,7 @@ void vl_get_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, const P
"increase and recompile");
}
PLI_BYTE8* valuep = (PLI_BYTE8*)out_data;
PLI_BYTE8* valuep = reinterpret_cast<PLI_BYTE8*>(out_data);
arrayvalue_p->value.rawvals = valuep;
if (varp->vltype() == VLVT_UINT8) {
@ -3586,12 +3605,14 @@ PLI_INT32 vpi_printf(PLI_BYTE8* formatp, ...) {
return chars;
}
// cppcheck-suppress constParameterPointer
PLI_INT32 vpi_vprintf(PLI_BYTE8* formatp, va_list ap) {
VerilatedVpiImp::assertOneCheck();
VL_VPI_ERROR_RESET_();
return VL_VPRINTF(formatp, ap);
}
// cppcheck-suppress constParameterPointer
PLI_INT32 vpi_mcd_vprintf(PLI_UINT32 mcd, PLI_BYTE8* format, va_list ap) {
VerilatedVpiImp::assertOneCheck();
FILE* const fp = VL_CVT_I_FP(mcd);
@ -3628,10 +3649,10 @@ PLI_INT32 vpi_chk_error(p_vpi_error_info error_info_p) {
// executing vpi_chk_error does not reset error
// error_info_p can be nullptr, so only return level in that case
VerilatedVpiImp::assertOneCheck();
p_vpi_error_info const _error_info_p = VerilatedVpiImp::error_info()->getError();
if (error_info_p && _error_info_p) *error_info_p = *_error_info_p;
if (!_error_info_p) return 0; // no error occurred
return _error_info_p->level; // return error severity level
const p_vpi_error_info imp_info_p = VerilatedVpiImp::error_info()->getError();
if (error_info_p && imp_info_p) *error_info_p = *imp_info_p;
if (!imp_info_p) return 0; // no error occurred
return imp_info_p->level; // return error severity level
}
#ifndef VL_NO_LEGACY

View File

@ -52,15 +52,13 @@
// clang and gcc-8.0+ support no_sanitize("string") style attribute
# if defined(__clang__) || (__GNUC__ >= 8)
# define VL_ATTR_NO_SANITIZE_ALIGN __attribute__((no_sanitize("alignment")))
#else // The entire undefined sanitizer has to be disabled for older gcc
# else // The entire undefined sanitizer has to be disabled for older gcc
# define VL_ATTR_NO_SANITIZE_ALIGN __attribute__((no_sanitize_undefined))
#endif
# endif
# define VL_ATTR_PRINTF(fmtArgNum) __attribute__((format(printf, (fmtArgNum), (fmtArgNum) + 1)))
# define VL_ATTR_PURE __attribute__((pure))
# define VL_ATTR_UNUSED __attribute__((unused))
#ifndef VL_ATTR_WARN_UNUSED_RESULT
# define VL_ATTR_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
#endif
# if !defined(_WIN32) && !defined(__MINGW32__)
// All VL_ATTR_WEAK symbols must be marked with the macOS -U linker flag in verilated.mk.in
# define VL_ATTR_WEAK __attribute__((weak))
@ -575,7 +573,9 @@ static inline double VL_ROUND(double n) {
# define VL_CPU_RELAX() asm volatile("nop" ::: "memory")
#elif defined(__mips64el__) || defined(__mips__) || defined(__mips64__) || defined(__mips64)
# define VL_CPU_RELAX() asm volatile("pause" ::: "memory")
#elif defined(__powerpc64__)
#elif defined(__POWERPC__) && defined(__APPLE__) // First check for a special case of macOS
# define VL_CPU_RELAX() asm volatile("or r1, r1, r1; or r2, r2, r2;" ::: "memory")
#elif defined(__powerpc64__) || defined(__powerpc__) // Generic powerpc
# define VL_CPU_RELAX() asm volatile("or 1, 1, 1; or 2, 2, 2;" ::: "memory")
#elif defined(__riscv) // RiscV does not currently have yield/pause, but one is proposed
# define VL_CPU_RELAX() asm volatile("nop" ::: "memory")
@ -637,7 +637,7 @@ extern std::string getenvStr(const std::string& envvar,
extern uint16_t getcpu() VL_MT_SAFE;
/// Return memory usage in bytes, or 0 if unknown
extern uint64_t memUsageBytes() VL_MT_SAFE;
extern void memUsageBytes(uint64_t& peakr, uint64_t& currentr) VL_MT_SAFE;
// Internal: Record CPU time, starting point on construction, and current delta from that
class DeltaCpuTime final {
@ -691,7 +691,7 @@ struct reverse_wrapper final {
const T& m_v;
explicit reverse_wrapper(const T& a_v)
: m_v(a_v) {}
: m_v(a_v) {} // Need () constructor
auto begin() -> decltype(m_v.rbegin()) { return m_v.rbegin(); }
auto end() -> decltype(m_v.rend()) { return m_v.rend(); }
};

View File

@ -25,6 +25,9 @@
#include "verilatedos.h"
#include <fstream>
#include <sstream>
// clang-format off
#if defined(_WIN32) || defined(__MINGW32__)
# include <windows.h> // LONG for bcrypt.h on MINGW
@ -35,7 +38,7 @@
#if defined(__linux)
# include <sched.h> // For sched_getcpu()
#endif
#if defined(__APPLE__) && !defined(__arm64__)
#if defined(__APPLE__) && !defined(__arm64__) && !defined(__POWERPC__)
# include <cpuid.h> // For __cpuid_count()
#endif
// clang-format on
@ -85,7 +88,7 @@ double DeltaWallTime::gettime() VL_MT_SAFE {
uint16_t getcpu() VL_MT_SAFE {
#if defined(__linux)
return sched_getcpu(); // TODO: this is a system call. Not exactly cheap.
#elif defined(__APPLE__) && !defined(__arm64__)
#elif defined(__APPLE__) && !defined(__arm64__) && !defined(__POWERPC__)
uint32_t info[4];
__cpuid_count(1, 0, info[0], info[1], info[2], info[3]);
// info[1] is EBX, bits 24-31 are APIC ID
@ -102,29 +105,42 @@ uint16_t getcpu() VL_MT_SAFE {
}
//=========================================================================
// VlOs::memUsageBytes implementation
// VlOs::memPeakUsageBytes implementation
uint64_t memUsageBytes() VL_MT_SAFE {
void memUsageBytes(uint64_t& peakr, uint64_t& currentr) VL_MT_SAFE {
peakr = 0;
currentr = 0;
#if defined(_WIN32) || defined(__MINGW32__)
const HANDLE process = GetCurrentProcess();
PROCESS_MEMORY_COUNTERS pmc;
if (GetProcessMemoryInfo(process, &pmc, sizeof(pmc))) {
// The best we can do using simple Windows APIs is to get the size of the working set.
return pmc.WorkingSetSize;
peakr = pmc.PeakWorkingSetSize;
currentr = pmc.WorkingSetSize;
}
return 0;
#else
// Highly unportable. Sorry
const char* const statmFilename = "/proc/self/statm";
FILE* const fp = fopen(statmFilename, "r");
if (!fp) return 0;
uint64_t size, resident, share, text, lib, data, dt; // All in pages
const int items = fscanf(
fp, "%" SCNu64 " %" SCNu64 " %" SCNu64 " %" SCNu64 " %" SCNu64 " %" SCNu64 " %" SCNu64,
&size, &resident, &share, &text, &lib, &data, &dt);
fclose(fp);
if (VL_UNCOVERABLE(7 != items)) return 0;
return (text + data) * getpagesize();
std::ifstream is{"/proc/self/status"};
if (!is) return;
std::string line;
uint64_t vmPeak = 0;
uint64_t vmRss = 0;
uint64_t vmSwap = 0;
std::string field;
while (std::getline(is, line)) {
if (line.rfind("VmPeak:", 0) == 0) {
std::stringstream ss{line};
ss >> field >> vmPeak;
} else if (line.rfind("VmRSS:", 0) == 0) {
std::stringstream ss{line};
ss >> field >> vmRss;
} else if (line.rfind("VmSwap:", 0) == 0) {
std::stringstream ss{line};
ss >> field >> vmSwap;
}
}
peakr = vmPeak * 1024;
currentr = (vmRss + vmSwap) * 1024;
#endif
}

View File

@ -48,6 +48,7 @@ set(HEADERS
V3AstNodeDType.h
V3AstNodeExpr.h
V3AstNodeOther.h
V3AstNodeStmt.h
V3AstUserAllocator.h
V3Begin.h
V3Branch.h
@ -56,6 +57,7 @@ set(HEADERS
V3CUse.h
V3Case.h
V3Cast.h
V3Cfg.h
V3Class.h
V3Clean.h
V3Clock.h
@ -72,10 +74,11 @@ set(HEADERS
V3Descope.h
V3Dfg.h
V3DfgCache.h
V3DfgContext.h
V3DfgOptimizer.h
V3DfgPasses.h
V3DfgPatternStats.h
V3DfgPeephole.h
V3DfgPeepholePatterns.h
V3DfgVertices.h
V3DiagSarif.h
V3DupFinder.h
@ -211,6 +214,9 @@ set(COMMON_SOURCES
V3CUse.cpp
V3Case.cpp
V3Cast.cpp
V3Cfg.cpp
V3CfgBuilder.cpp
V3CfgLiveVariables.cpp
V3Class.cpp
V3Clean.cpp
V3Clock.cpp
@ -227,13 +233,16 @@ set(COMMON_SOURCES
V3Descope.cpp
V3Dfg.cpp
V3DfgAstToDfg.cpp
V3DfgBreakCycles.cpp
V3DfgCache.cpp
V3DfgColorSCCs.cpp
V3DfgDecomposition.cpp
V3DfgDfgToAst.cpp
V3DfgOptimizer.cpp
V3DfgPasses.cpp
V3DfgPeephole.cpp
V3DfgRegularize.cpp
V3DfgSynthesize.cpp
V3DiagSarif.cpp
V3DupFinder.cpp
V3EmitCBase.cpp
@ -369,8 +378,8 @@ add_custom_command(
COMMAND ${PYTHON3}
ARGS
${ASTGEN} -I "${srcdir}" --astdef V3AstNodeDType.h --astdef
V3AstNodeExpr.h --astdef V3AstNodeOther.h --dfgdef V3DfgVertices.h
--classes
V3AstNodeExpr.h --astdef V3AstNodeOther.h --astdef V3AstNodeStmt.h
--dfgdef V3DfgVertices.h --classes
)
list(
APPEND
@ -482,8 +491,8 @@ foreach(astgen_name ${ASTGENERATED_NAMES})
COMMAND ${PYTHON3}
ARGS
${ASTGEN} -I "${srcdir}" --astdef V3AstNodeDType.h --astdef
V3AstNodeExpr.h --astdef V3AstNodeOther.h --dfgdef V3DfgVertices.h
${astgen_name}.cpp
V3AstNodeExpr.h --astdef V3AstNodeOther.h --astdef V3AstNodeStmt.h
--dfgdef V3DfgVertices.h ${astgen_name}.cpp
)
list(APPEND GENERATED_FILES ${astgen_name}__gen.cpp)
endforeach()

View File

@ -226,6 +226,9 @@ RAW_OBJS_PCH_ASTNOMT = \
V3CUse.o \
V3Case.o \
V3Cast.o \
V3Cfg.o \
V3CfgBuilder.o \
V3CfgLiveVariables.o \
V3Class.o \
V3Clean.o \
V3Clock.o \
@ -240,13 +243,16 @@ RAW_OBJS_PCH_ASTNOMT = \
V3Descope.o \
V3Dfg.o \
V3DfgAstToDfg.o \
V3DfgBreakCycles.o \
V3DfgCache.o \
V3DfgColorSCCs.o \
V3DfgDecomposition.o \
V3DfgDfgToAst.o \
V3DfgOptimizer.o \
V3DfgPasses.o \
V3DfgPeephole.o \
V3DfgRegularize.o \
V3DfgSynthesize.o \
V3DiagSarif.o \
V3DupFinder.o \
V3EmitCMain.o \
@ -329,6 +335,7 @@ NON_STANDALONE_HEADERS = \
V3AstNodeDType.h \
V3AstNodeExpr.h \
V3AstNodeOther.h \
V3AstNodeStmt.h \
V3DfgVertices.h \
V3ThreadPool.h \
V3WidthRemove.h \
@ -337,6 +344,7 @@ AST_DEFS := \
V3AstNodeDType.h \
V3AstNodeExpr.h \
V3AstNodeOther.h \
V3AstNodeStmt.h \
DFG_DEFS := \
V3DfgVertices.h

View File

@ -97,6 +97,7 @@ protected:
result = vertexp->user();
break;
case LatchDetectGraphVertex::VT_BLOCK: // (OR of potentially many siblings)
// cppcheck-suppress constVariableReference
for (V3GraphEdge& edge : vertexp->outEdges()) {
if (latchCheckInternal(castVertexp(edge.top()))) {
result = true;
@ -128,6 +129,7 @@ public:
}
// Clear out userp field of referenced outputs on destruction
// (occurs at the end of each combinational always block)
// cppcheck-suppress duplInheritedMember
void clear() {
m_outputs.clear();
// Calling base class clear will unlink & delete all edges & vertices
@ -166,7 +168,7 @@ public:
// paths make an assignment. Detected latches are flagged in the variables AstVar
void latchCheck(AstNode* nodep, bool latch_expected) {
bool latch_detected = false;
for (const auto& vrp : m_outputs) {
for (const AstVarRef* const vrp : m_outputs) {
LatchDetectGraphVertex* const vertp = castVertexp(vrp->varp()->user1p());
vertp->user(true); // Identify the output vertex we are checking paths _to_
if (!latchCheckInternal(castVertexp(vertices().frontp()))) latch_detected = true;
@ -240,7 +242,7 @@ public:
// Make a new AstActive sensitive to the given sentree and return it
AstActive* makeActive(FileLine* const fl, AstSenTree* const senTreep) {
AstActive* const activep = new AstActive{fl, "", senTreep};
activep->sensesStorep(activep->sensesp());
activep->senTreeStorep(activep->sentreep());
addActive(activep);
return activep;
}
@ -261,17 +263,17 @@ public:
}
// Return an AstActive that is sensitive to a SenTree equivalent to the given sentreep.
AstActive* getActive(FileLine* fl, AstSenTree* sensesp) {
UASSERT(sensesp, "Must be non-null");
AstActive* getActive(FileLine* fl, AstSenTree* sentreep) {
UASSERT(sentreep, "Must be non-null");
auto it = m_activeMap.find(*sensesp);
auto it = m_activeMap.find(*sentreep);
// If found matching AstActive, return it
if (it != m_activeMap.end()) return it->second;
// No such AstActive yet, creat it, and add to map.
AstSenTree* const newsenp = sensesp->cloneTree(false);
AstSenTree* const newsenp = sentreep->cloneTree(false);
AstActive* const activep = new AstActive{fl, "sequent", newsenp};
activep->sensesStorep(activep->sensesp());
activep->senTreeStorep(activep->sentreep());
addActive(activep);
m_activeMap.emplace(*newsenp, activep);
return activep;
@ -441,11 +443,11 @@ class ActiveVisitor final : public VNVisitor {
wantactivep->addStmtsp(nodep);
}
void visitAlways(AstNode* nodep, AstSenTree* oldsensesp, VAlwaysKwd kwd) {
void visitAlways(AstNode* nodep, AstSenTree* oldsentreep, VAlwaysKwd kwd) {
// Move always to appropriate ACTIVE based on its sense list
if (oldsensesp && oldsensesp->sensesp() && oldsensesp->sensesp()->isNever()) {
if (oldsentreep && oldsentreep->sensesp() && oldsentreep->sensesp()->isNever()) {
// Never executing. Kill it.
UASSERT_OBJ(!oldsensesp->sensesp()->nextp(), nodep,
UASSERT_OBJ(!oldsentreep->sensesp()->nextp(), nodep,
"Never senitem should be alone, else the never should be eliminated.");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return;
@ -457,9 +459,9 @@ class ActiveVisitor final : public VNVisitor {
// Walk sensitivity list
m_clockedProcess = false;
m_allChanged = true;
if (oldsensesp) {
oldsensesp->unlinkFrBack();
iterateChildrenConst(oldsensesp);
if (oldsentreep) {
oldsentreep->unlinkFrBack();
iterateChildrenConst(oldsentreep);
}
// If all SenItems are ET_CHANGE, then walk the body to determine if this process
@ -476,9 +478,9 @@ class ActiveVisitor final : public VNVisitor {
AstActive* const wantactivep
= !m_clockedProcess ? m_namer.getSpecialActive<AstSenItem::Combo>(nodep->fileline())
: oldsensesp ? m_namer.getActive(nodep->fileline(), oldsensesp)
// Clocked, no sensitivity lists, it's a suspendable, put it in initial
: m_namer.getSpecialActive<AstSenItem::Initial>(nodep->fileline());
: oldsentreep ? m_namer.getActive(nodep->fileline(), oldsentreep)
// Clocked, no sensitivity lists, it's a suspendable, put it in initial
: m_namer.getSpecialActive<AstSenItem::Initial>(nodep->fileline());
// Move node to new active
nodep->unlinkFrBack();
@ -487,12 +489,12 @@ class ActiveVisitor final : public VNVisitor {
// Warn and convert any delayed assignments
{
ActiveDlyVisitor{nodep, !m_clockedProcess ? ActiveDlyVisitor::CT_COMB
: oldsensesp ? ActiveDlyVisitor::CT_SEQ
: oldsentreep ? ActiveDlyVisitor::CT_SEQ
: ActiveDlyVisitor::CT_SUSPENDABLE};
}
// Delete sensitivity list
if (oldsensesp) VL_DO_DANGLING(oldsensesp->deleteTree(), oldsensesp);
if (oldsentreep) VL_DO_DANGLING(oldsentreep->deleteTree(), oldsentreep);
// check combinational processes for latches
if (!m_clockedProcess || kwd == VAlwaysKwd::ALWAYS_LATCH) {
@ -534,7 +536,7 @@ class ActiveVisitor final : public VNVisitor {
return;
}
visitSenItems(nodep);
visitAlways(nodep, nodep->sensesp(), nodep->keyword());
visitAlways(nodep, nodep->sentreep(), nodep->keyword());
}
void visit(AstAlwaysPostponed* nodep) override {
// Might be empty with later optimizations, so this assertion can be removed,
@ -545,23 +547,23 @@ class ActiveVisitor final : public VNVisitor {
activep->addStmtsp(nodep->unlinkFrBack());
}
void visit(AstAlwaysObserved* nodep) override {
UASSERT_OBJ(nodep->sensesp(), nodep, "Should have a sentree");
AstSenTree* const sensesp = nodep->sensesp();
sensesp->unlinkFrBack();
UASSERT_OBJ(nodep->sentreep(), nodep, "Should have a sentree");
AstSenTree* const sentreep = nodep->sentreep();
sentreep->unlinkFrBack();
// Make a new active for it, needs to be the only item under the active for V3Sched
AstActive* const activep = m_namer.makeActive(nodep->fileline(), sensesp);
AstActive* const activep = m_namer.makeActive(nodep->fileline(), sentreep);
activep->addStmtsp(nodep->unlinkFrBack());
}
void visit(AstAlwaysReactive* nodep) override {
UASSERT_OBJ(nodep->sensesp(), nodep, "Should have a sentree");
AstSenTree* const sensesp = nodep->sensesp();
sensesp->unlinkFrBack();
UASSERT_OBJ(nodep->sentreep(), nodep, "Should have a sentree");
AstSenTree* const sentreep = nodep->sentreep();
sentreep->unlinkFrBack();
// Make a new active for it, needs to be the only item under the active for V3Sched
AstActive* const activep = m_namer.makeActive(nodep->fileline(), sensesp);
AstActive* const activep = m_namer.makeActive(nodep->fileline(), sentreep);
activep->addStmtsp(nodep->unlinkFrBack());
}
void visit(AstAlwaysPublic* nodep) override {
visitAlways(nodep, nodep->sensesp(), VAlwaysKwd::ALWAYS);
visitAlways(nodep, nodep->sentreep(), VAlwaysKwd::ALWAYS);
}
void visit(AstCFunc* nodep) override { visitSenItems(nodep); }
void visit(AstSenItem* nodep) override {

View File

@ -68,11 +68,11 @@ class ActiveTopVisitor final : public VNVisitor {
UINFO(4, " ACTIVE " << nodep);
// Remove duplicate clocks and such; sensesp() may change!
V3Const::constifyExpensiveEdit(nodep);
AstSenTree* sensesp = nodep->sensesp();
UASSERT_OBJ(sensesp, nodep, "nullptr");
if (sensesp->sensesp() && sensesp->sensesp()->isNever()) {
AstSenTree* sentreep = nodep->sentreep();
UASSERT_OBJ(sentreep, nodep, "nullptr");
if (sentreep->sensesp() && sentreep->sensesp()->isNever()) {
// Never executing. Kill it.
UASSERT_OBJ(!sensesp->sensesp()->nextp(), nodep,
UASSERT_OBJ(!sentreep->sensesp()->nextp(), nodep,
"Never senitem should be alone, else the never should be eliminated.");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return;
@ -94,21 +94,21 @@ class ActiveTopVisitor final : public VNVisitor {
// Move the SENTREE for each active up to the global level.
// This way we'll easily see what clock domains are identical
AstSenTree* const wantp = m_finder.getSenTree(sensesp);
AstSenTree* const wantp = m_finder.getSenTree(sentreep);
UINFO(4, " lookdone");
if (wantp != sensesp) {
if (wantp != sentreep) {
// Move the active's contents to the other active
UINFO(4, " merge active " << sensesp << " into " << wantp);
if (nodep->sensesStorep()) {
UASSERT_OBJ(sensesp == nodep->sensesStorep(), nodep,
UINFO(4, " merge active " << sentreep << " into " << wantp);
if (nodep->senTreeStorep()) {
UASSERT_OBJ(sentreep == nodep->senTreeStorep(), nodep,
"sensesStore should have been deleted earlier if different");
sensesp->unlinkFrBack();
sentreep->unlinkFrBack();
// There may be other references to same sense tree,
// we'll be removing all references when we get to them,
// but don't dangle our pointer yet!
VL_DO_DANGLING(pushDeletep(sensesp), sensesp);
VL_DO_DANGLING(pushDeletep(sentreep), sentreep);
}
nodep->sensesp(wantp);
nodep->sentreep(wantp);
}
// If this is combinational logic that does not read any variables, then it really is an
@ -116,7 +116,7 @@ class ActiveTopVisitor final : public VNVisitor {
// prune these otherwise.
// TODO: we should warn for these if they were 'always @*' as some (including strictly
// compliant) simulators will never execute these.
if (nodep->sensesp()->hasCombo()) {
if (nodep->sentreep()->hasCombo()) {
FileLine* const flp = nodep->fileline();
AstActive* initialp = nullptr;
for (AstNode *logicp = nodep->stmtsp(), *nextp; logicp; logicp = nextp) {

View File

@ -23,7 +23,99 @@
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// Assert class functions
// AssertDeFuture
// If any AstFuture, then move all non-future varrefs to be one cycle behind,
// see IEEE 1800-2023 16.9.4.
class AssertDeFuture final : public VNVisitor {
// STATE - across all visitors
AstNodeModule* const m_modp; // Module future is underneath
const AstFuture* m_futurep; // First AstFuture found
const unsigned m_pastNum; // Prefix unique number for this module
std::map<AstVar*, AstVar*> m_delayedVars; // Old to delayed variable mapping
// STATE - for current visit position (use VL_RESTORER)
bool m_inFuture = false; // Inside a future
bool m_unsupported = false; // Printed unsupported
// METHODS
void unsupported(AstNode* nodep) {
if (m_unsupported) return;
m_unsupported = true;
nodep->v3warn(E_UNSUPPORTED,
"Unsupported/illegal: future value function used with expression with "
<< nodep->prettyOperatorName());
}
// VISITORS
void visit(AstFuture* nodep) override {
VL_RESTORER(m_inFuture);
m_inFuture = true;
iterateChildren(nodep);
// Done with the future, this subexpression is current-time
nodep->replaceWith(nodep->exprp()->unlinkFrBack());
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstNodeVarRef* nodep) override {
if (nodep->user1SetOnce()) return;
if (m_inFuture || m_unsupported)
return; // Need user1 set above, don't process when Future is removed
if (nodep->access().isWriteOrRW()) {
unsupported(nodep);
return;
}
auto it = m_delayedVars.find(nodep->varp());
AstVar* outvarp;
if (it == m_delayedVars.end()) {
AstSenTree* const sentreep = m_futurep->sentreep();
AstAlways* const alwaysp = new AstAlways{nodep->fileline(), VAlwaysKwd::ALWAYS,
sentreep->cloneTree(false), nullptr};
m_modp->addStmtsp(alwaysp);
outvarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP,
"__Vnotfuture" + cvtToStr(m_pastNum) + "_" + nodep->name(),
nodep->dtypep()};
m_modp->addStmtsp(outvarp);
AstVarRef* varRefAWritep = new AstVarRef{nodep->fileline(), outvarp, VAccess::WRITE};
varRefAWritep->user1(true);
AstNodeVarRef* varRefAReadp = nodep->cloneTree(false);
varRefAReadp->user1(true);
AstNode* const assp = new AstAssignDly{nodep->fileline(), varRefAWritep, varRefAReadp};
alwaysp->addStmtsp(assp);
m_delayedVars.emplace(nodep->varp(), outvarp);
} else {
outvarp = it->second;
}
AstVarRef* newp = new AstVarRef{nodep->fileline(), outvarp, VAccess::READ};
newp->user1(true);
UINFO(9, "DeFuture " << nodep << " becomes " << newp);
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstNodeFTaskRef* nodep) override { unsupported(nodep); }
void visit(AstMethodCall* nodep) override { unsupported(nodep); }
void visit(AstNode* nodep) override {
if (!nodep->isPure()) unsupported(nodep);
iterateChildren(nodep);
}
public:
// CONSTRUCTORS
explicit AssertDeFuture(AstNode* nodep, AstNodeModule* modp, unsigned pastNum)
: m_modp{modp}
, m_pastNum{pastNum} {
// See if any Future before we process
if (nodep->forall([&](const AstFuture* futurep) -> bool {
m_futurep = futurep;
return false;
}))
return;
// UINFOTREE(9, nodep, "", "defuture-in");
visit(nodep); // Nodep may get deleted
// UINFOTREE(9, nodep, "", "defuture-ou");
}
~AssertDeFuture() = default;
};
//######################################################################
// AssertVisitor
class AssertVisitor final : public VNVisitor {
// CONSTANTS
@ -32,7 +124,7 @@ class AssertVisitor final : public VNVisitor {
// NODE STATE/TYPES
// Cleared on netlist
// AstNode::user() -> bool. True if processed
// AstNode::user1() -> bool. True if processed
const VNUser1InUse m_inuser1;
// STATE
@ -48,6 +140,7 @@ class AssertVisitor final : public VNVisitor {
VDouble0 m_statAsNotImm; // Statistic tracking
VDouble0 m_statAsImm; // Statistic tracking
VDouble0 m_statAsFull; // Statistic tracking
VDouble0 m_statPastVars; // Statistic tracking
bool m_inSampled = false; // True inside a sampled expression
// METHODS
@ -86,7 +179,7 @@ class AssertVisitor final : public VNVisitor {
}
VL_UNREACHABLE;
}
string assertDisplayMessage(AstNode* nodep, const string& prefix, const string& message,
string assertDisplayMessage(const AstNode* nodep, const string& prefix, const string& message,
VDisplayType severity) {
if (severity == VDisplayType::DT_ERROR || severity == VDisplayType::DT_FATAL) {
return ("[%0t] "s + prefix + ": " + nodep->fileline()->filebasename() + ":"
@ -148,7 +241,7 @@ class AssertVisitor final : public VNVisitor {
sampledp->dtypeFrom(nodep);
return sampledp;
}
AstVarRef* newMonitorNumVarRefp(AstNode* nodep, VAccess access) {
AstVarRef* newMonitorNumVarRefp(const AstNode* nodep, VAccess access) {
if (!m_monitorNumVarp) {
m_monitorNumVarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP, "__VmonitorNum",
nodep->findUInt64DType()};
@ -158,7 +251,7 @@ class AssertVisitor final : public VNVisitor {
varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgAddp());
return varrefp;
}
AstVarRef* newMonitorOffVarRefp(AstNode* nodep, VAccess access) {
AstVarRef* newMonitorOffVarRefp(const AstNode* nodep, VAccess access) {
if (!m_monitorOffVarp) {
m_monitorOffVarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP, "__VmonitorOff",
nodep->findBitDType()};
@ -181,7 +274,7 @@ class AssertVisitor final : public VNVisitor {
return newp;
}
AstNodeStmt* newFireAssertUnchecked(AstNodeStmt* nodep, const string& message,
AstNodeStmt* newFireAssertUnchecked(const AstNodeStmt* nodep, const string& message,
AstNodeExpr* exprsp = nullptr) {
// Like newFireAssert() but omits the asserts-on check
AstDisplay* const dispp
@ -194,7 +287,7 @@ class AssertVisitor final : public VNVisitor {
return bodysp;
}
AstNodeStmt* newFireAssert(AstNodeStmt* nodep, VAssertDirectiveType directiveType,
AstNodeStmt* newFireAssert(const AstNodeStmt* nodep, VAssertDirectiveType directiveType,
VAssertType assertType, const string& message,
AstNodeExpr* exprsp = nullptr) {
AstNodeStmt* bodysp = newFireAssertUnchecked(nodep, message, exprsp);
@ -202,9 +295,12 @@ class AssertVisitor final : public VNVisitor {
return bodysp;
}
void newPslAssertion(AstNodeCoverOrAssert* nodep, AstNode* failsp) {
void visitAssertionIterate(AstNodeCoverOrAssert* nodep, AstNode* failsp) {
if (m_beginp && nodep->name() == "") nodep->name(m_beginp->name());
{ AssertDeFuture{nodep->propp(), m_modp, m_modPastNum++}; }
iterateChildren(nodep);
AstNodeExpr* const propp = VN_AS(nodep->propp()->unlinkFrBackWithNext(), NodeExpr);
AstSenTree* const sentreep = nodep->sentreep();
const string& message = nodep->name();
@ -448,6 +544,13 @@ class AssertVisitor final : public VNVisitor {
}
}
void visit(AstFuture* nodep) override {
nodep->v3error("Future sampled value function called outside property or sequence "
"expression (IEEE 16.9.4)");
nodep->replaceWith(new AstConst{nodep->fileline(), 0});
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
//========== Past
void visit(AstPast* nodep) override {
iterateChildren(nodep);
@ -466,14 +569,18 @@ class AssertVisitor final : public VNVisitor {
= new AstAlways{nodep->fileline(), VAlwaysKwd::ALWAYS, sentreep, nullptr};
m_modp->addStmtsp(alwaysp);
for (uint32_t i = 0; i < ticks; ++i) {
// TODO recognize AstVarRef is getting delayed and share variables between
// $pasts with same reference (or same expression). Saves downstream
// optimizations from identifying and removing duplication.
AstVar* const outvarp = new AstVar{
nodep->fileline(), VVarType::MODULETEMP,
"_Vpast_" + cvtToStr(m_modPastNum++) + "_" + cvtToStr(i), inp->dtypep()};
++m_statPastVars;
m_modp->addStmtsp(outvarp);
AstNode* const assp = new AstAssignDly{
nodep->fileline(), new AstVarRef{nodep->fileline(), outvarp, VAccess::WRITE}, inp};
alwaysp->addStmtsp(assp);
// if (debug() >= 9) assp->dumpTree("- ass: ");
// UINFOTREE(9, assp, "", "ass");
invarp = outvarp;
inp = new AstVarRef{nodep->fileline(), invarp, VAccess::READ};
}
@ -594,9 +701,8 @@ class AssertVisitor final : public VNVisitor {
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstAssert* nodep) override {
iterateChildren(nodep);
newPslAssertion(nodep, nodep->failsp());
void visit(AstAssert* nodep) override { //
visitAssertionIterate(nodep, nodep->failsp());
}
void visit(AstAssertCtl* nodep) override {
if (VN_IS(m_modp, Class) || VN_IS(m_modp, Iface)) {
@ -673,13 +779,11 @@ class AssertVisitor final : public VNVisitor {
}
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstAssertIntrinsic* nodep) override {
iterateChildren(nodep);
newPslAssertion(nodep, nodep->failsp());
void visit(AstAssertIntrinsic* nodep) override { //
visitAssertionIterate(nodep, nodep->failsp());
}
void visit(AstCover* nodep) override {
iterateChildren(nodep);
newPslAssertion(nodep, nullptr);
void visit(AstCover* nodep) override { //
visitAssertionIterate(nodep, nullptr);
}
void visit(AstRestrict* nodep) override {
iterateChildren(nodep);
@ -719,6 +823,7 @@ public:
V3Stats::addStat("Assertions, assert immediate statements", m_statAsImm);
V3Stats::addStat("Assertions, cover statements", m_statCover);
V3Stats::addStat("Assertions, full/parallel case", m_statAsFull);
V3Stats::addStat("Assertions, $past variables", m_statPastVars);
}
};

View File

@ -102,7 +102,7 @@ private:
}
AstPropSpec* substitutePropertyCall(AstPropSpec* nodep) {
if (AstFuncRef* const funcrefp = VN_CAST(nodep->propp(), FuncRef)) {
if (AstProperty* const propp = VN_CAST(funcrefp->taskp(), Property)) {
if (const AstProperty* const propp = VN_CAST(funcrefp->taskp(), Property)) {
AstPropSpec* propExprp = getPropertyExprp(propp);
// Substitute inner property call before copying in order to not doing the same for
// each call of outer property call.
@ -114,6 +114,7 @@ private:
const V3TaskConnects tconnects = V3Task::taskConnects(funcrefp, propp->stmtsp());
for (const auto& tconnect : tconnects) {
const AstVar* const portp = tconnect.first;
// cppcheck-suppress constVariablePointer // 'exprp' unlinked below
AstArg* const argp = tconnect.second;
AstNode* const pinp = argp->exprp()->unlinkFrBack();
replaceVarRefsWithExprRecurse(propExprp, portp, pinp);
@ -183,7 +184,7 @@ private:
m_clockingp->addNextHere(varp->unlinkFrBack());
varp->user1p(nodep);
if (nodep->direction() == VDirection::OUTPUT) {
exprp->foreach([](AstNodeVarRef* varrefp) {
exprp->foreach([](const AstNodeVarRef* varrefp) {
// Prevent confusing BLKANDNBLK warnings on clockvars due to generated assignments
varrefp->fileline()->warnOff(V3ErrorCode::BLKANDNBLK, true);
});
@ -318,7 +319,7 @@ private:
if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBackWithNext());
FileLine* const flp = nodep->fileline();
AstNodeExpr* valuep = V3Const::constifyEdit(nodep->lhsp()->unlinkFrBack());
AstConst* const constp = VN_CAST(valuep, Const);
const AstConst* const constp = VN_CAST(valuep, Const);
if (constp->isZero()) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: ##0 delays");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
@ -438,8 +439,8 @@ private:
}
}
void visit(AstAlways* nodep) override {
iterateAndNextNull(nodep->sensesp());
if (nodep->sensesp()) m_seniAlwaysp = nodep->sensesp()->sensesp();
iterateAndNextNull(nodep->sentreep());
if (nodep->sentreep()) m_seniAlwaysp = nodep->sentreep()->sensesp();
iterateAndNextNull(nodep->stmtsp());
m_seniAlwaysp = nullptr;
}
@ -452,6 +453,19 @@ private:
if (!nodep->immediate()) nodep->sentreep(newSenTree(nodep));
clearAssertInfo();
}
void visit(AstFalling* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
FileLine* const fl = nodep->fileline();
AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1};
AstNodeExpr* const futurep = new AstFuture{fl, exprp, newSenTree(nodep)};
futurep->dtypeFrom(exprp);
exprp = new AstAnd{fl, exprp->cloneTreePure(false), new AstNot{fl, futurep}};
exprp->dtypeSetBit();
nodep->replaceWith(exprp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstFell* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
@ -460,19 +474,39 @@ private:
if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1};
AstSenTree* sentreep = nodep->sentreep();
if (sentreep) sentreep->unlinkFrBack();
AstNodeExpr* const past = new AstPast{fl, exprp};
past->dtypeFrom(exprp);
exprp = new AstAnd{fl, past, new AstNot{fl, exprp->cloneTreePure(false)}};
AstNodeExpr* const pastp = new AstPast{fl, exprp};
pastp->dtypeFrom(exprp);
exprp = new AstAnd{fl, pastp, new AstNot{fl, exprp->cloneTreePure(false)}};
exprp->dtypeSetBit();
nodep->replaceWith(exprp);
nodep->sentreep(newSenTree(nodep, sentreep));
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstFuture* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
AstSenTree* const sentreep = nodep->sentreep();
if (sentreep) sentreep->unlinkFrBack();
nodep->sentreep(newSenTree(nodep));
}
void visit(AstPast* nodep) override {
if (nodep->sentreep()) return; // Already processed
iterateChildren(nodep);
nodep->sentreep(newSenTree(nodep));
}
void visit(AstRising* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
FileLine* const fl = nodep->fileline();
AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1};
AstNodeExpr* const futurep = new AstFuture{fl, exprp, newSenTree(nodep)};
futurep->dtypeFrom(exprp);
exprp = new AstAnd{fl, new AstNot{fl, exprp->cloneTreePure(false)}, futurep};
exprp->dtypeSetBit();
nodep->replaceWith(exprp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstRose* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
@ -481,9 +515,9 @@ private:
if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1};
AstSenTree* sentreep = nodep->sentreep();
if (sentreep) sentreep->unlinkFrBack();
AstNodeExpr* const past = new AstPast{fl, exprp};
past->dtypeFrom(exprp);
exprp = new AstAnd{fl, new AstNot{fl, past}, exprp->cloneTreePure(false)};
AstNodeExpr* const pastp = new AstPast{fl, exprp};
pastp->dtypeFrom(exprp);
exprp = new AstAnd{fl, new AstNot{fl, pastp}, exprp->cloneTreePure(false)};
exprp->dtypeSetBit();
nodep->replaceWith(exprp);
nodep->sentreep(newSenTree(nodep, sentreep));
@ -496,14 +530,27 @@ private:
AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
AstSenTree* sentreep = nodep->sentreep();
if (sentreep) sentreep->unlinkFrBack();
AstNodeExpr* const past = new AstPast{fl, exprp};
past->dtypeFrom(exprp);
exprp = new AstEq{fl, past, exprp->cloneTreePure(false)};
AstNodeExpr* const pastp = new AstPast{fl, exprp};
pastp->dtypeFrom(exprp);
exprp = new AstEq{fl, pastp, exprp->cloneTreePure(false)};
exprp->dtypeSetBit();
nodep->replaceWith(exprp);
nodep->sentreep(newSenTree(nodep, sentreep));
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstSteady* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
FileLine* const fl = nodep->fileline();
AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1};
AstNodeExpr* const futurep = new AstFuture{fl, exprp, newSenTree(nodep)};
futurep->dtypeFrom(exprp);
exprp = new AstEq{fl, exprp->cloneTreePure(false), futurep};
exprp->dtypeSetBit();
nodep->replaceWith(exprp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstImplication* nodep) override {
if (nodep->sentreep()) return; // Already processed
@ -514,9 +561,9 @@ private:
if (m_disablep) lhsp = new AstAnd{fl, new AstNot{fl, m_disablep}, lhsp};
AstNodeExpr* const past = new AstPast{fl, lhsp};
past->dtypeFrom(lhsp);
AstNodeExpr* const exprp = new AstOr{fl, new AstNot{fl, past}, rhsp};
AstNodeExpr* const pastp = new AstPast{fl, lhsp};
pastp->dtypeFrom(lhsp);
AstNodeExpr* const exprp = new AstOr{fl, new AstNot{fl, pastp}, rhsp};
exprp->dtypeSetBit();
nodep->replaceWith(exprp);
nodep->sentreep(newSenTree(nodep));

View File

@ -58,6 +58,28 @@ const VNTypeInfo VNType::typeInfoTable[] = {
std::ostream& operator<<(std::ostream& os, VNType rhs);
//######################################################################
// VFwdType
bool VFwdType::isNodeCompatible(const AstNode* nodep) const {
const AstNode* defp = nodep;
if (const AstTypedef* const adefp = VN_CAST(defp, Typedef)) defp = adefp->subDTypep();
if (const AstNodeDType* const adefp = VN_CAST(defp, NodeDType))
defp = adefp->skipRefToNonRefp();
switch (m_e) {
case VFwdType::NONE: return true; break;
case VFwdType::ENUM: return VN_IS(defp, EnumDType); break;
case VFwdType::STRUCT: return VN_IS(defp, StructDType); break;
case VFwdType::UNION: return VN_IS(defp, UnionDType); break;
case VFwdType::INTERFACE_CLASS: // FALLTHRU // TODO: Over permissive for now
case VFwdType::CLASS: return VN_IS(defp, ClassRefDType) || VN_IS(defp, Class); break;
case VFwdType::GENERIC_INTERFACE: return VN_IS(defp, IfaceRefDType); break;
default: v3fatalSrc("Bad case");
}
VL_UNREACHABLE;
return false; // LCOV_EXCL_LINE
}
//######################################################################
// VSelfPointerText
@ -234,14 +256,13 @@ string AstNode::vpiName(const string& namein) {
// This is slightly different from prettyName, in that when we encounter escaped characters,
// we change that identifier to an escaped identifier, wrapping it with '\' and ' '
// as specified in LRM 23.6
string name = namein;
if (0 == namein.substr(0, 7).compare("__SYM__")) name = namein.substr(7);
const size_t offset = VString::startsWith(namein, "__SYM__") ? 7 : 0;
string pretty;
pretty.reserve(name.length());
pretty.reserve(namein.length());
bool inEscapedIdent = false;
int lastIdent = 0;
for (const char* pos = name.c_str(); *pos;) {
for (const char* pos = namein.c_str() + offset; *pos;) {
char specialChar = 0;
if (pos[0] == '-' && pos[1] == '>') { // ->
specialChar = '.';
@ -259,7 +280,7 @@ string AstNode::vpiName(const string& namein) {
} else if (0 == std::strncmp(pos, "__PVT__", 7)) {
pos += 7;
continue;
} else if (pos[0] == '_' && pos[1] == '_' && pos[2] == '0' && std::isxdigit(pos[3])
} else if (0 == std::strncmp(pos, "__0", 3) && std::isxdigit(pos[3])
&& std::isxdigit(pos[4])) {
char value = 0;
value += 16
@ -546,6 +567,7 @@ AstNode* AstNode::unlinkFrBackWithNext(VNRelinker* linkerp) {
AstNode* const oldp = this;
UASSERT_OBJ(oldp->m_backp, oldp, "Node has no back, already unlinked?");
oldp->editCountInc();
// cppcheck-suppress shadowFunction
AstNode* const backp = oldp->m_backp;
if (linkerp) {
linkerp->m_oldp = oldp;
@ -608,6 +630,7 @@ AstNode* AstNode::unlinkFrBack(VNRelinker* linkerp) {
AstNode* const oldp = this;
UASSERT_OBJ(oldp->m_backp, oldp, "Node has no back, already unlinked?");
oldp->editCountInc();
// cppcheck-suppress shadowFunction
AstNode* const backp = oldp->m_backp;
if (linkerp) {
linkerp->m_oldp = oldp;
@ -689,6 +712,7 @@ void AstNode::relink(VNRelinker* linkerp) {
cout << endl;
}
// cppcheck-suppress shadowFunction
AstNode* const backp = linkerp->m_backp;
debugTreeChange(this, "-relinkNew: ", __LINE__, true);
debugTreeChange(backp, "-relinkTre: ", __LINE__, true);
@ -752,6 +776,7 @@ void AstNode::addHereThisAsNext(AstNode* newp) {
UASSERT_OBJ(this->m_backp, this, "'this' node has no back, already unlinked?");
UASSERT_OBJ(newp->m_headtailp, newp, "m_headtailp not set on new node");
//
// cppcheck-suppress shadowFunction
AstNode* const backp = this->m_backp;
AstNode* const newLastp = newp->m_headtailp;
//
@ -1164,6 +1189,7 @@ void AstNode::checkTreeIter(const AstNode* prevBackp) const VL_MT_STABLE {
break;
case VNTypeInfo::OP_LIST:
if (const AstNode* const headp = nodep) {
// cppcheck-suppress shadowFunction
const AstNode* backp = this;
const AstNode* tailp;
const AstNode* opp = headp;
@ -1233,7 +1259,7 @@ char* AstNode::dumpTreeJsonGdb(const char* str) { return strdup(str); }
// allow for passing pointer literals like 0x42.. without manual cast
char* AstNode::dumpTreeJsonGdb(intptr_t nodep) {
if (!nodep) return strdup("{\"addr\":\"NULL\"}\n");
return dumpTreeJsonGdb((const AstNode*)nodep);
return dumpTreeJsonGdb(reinterpret_cast<const AstNode*>(nodep));
}
// cppcheck-suppress unusedFunction // Debug only
void AstNode::dumpGdb(const AstNode* nodep) { // For GDB only // LCOV_EXCL_LINE
@ -1368,8 +1394,8 @@ void AstNode::dumpTreeFile(const string& filename, bool doDump) {
static void drawChildren(std::ostream& os, const AstNode* thisp, const AstNode* childp,
const std::string& childName) {
if (childp) {
os << "\tn" << cvtToHex(thisp) << " -> n" << cvtToHex(childp) << " ["
<< "label=\"" << childName << "\" color=red];\n";
os << "\tn" << cvtToHex(thisp) << " -> n" << cvtToHex(childp) << " [" << "label=\""
<< childName << "\" color=red];\n";
for (const AstNode* nodep = childp; nodep; nodep = nodep->nextp()) {
nodep->dumpTreeDot(os);
if (nodep->nextp()) {
@ -1383,8 +1409,7 @@ static void drawChildren(std::ostream& os, const AstNode* thisp, const AstNode*
}
void AstNode::dumpTreeDot(std::ostream& os) const {
os << "\tn" << cvtToHex(this) << "\t["
<< "label=\"" << typeName() << "\\n"
os << "\tn" << cvtToHex(this) << "\t[" << "label=\"" << typeName() << "\\n"
<< name() << "\"];\n";
drawChildren(os, this, m_op1p, "op1");
drawChildren(os, this, m_op2p, "op2");
@ -1421,8 +1446,7 @@ void AstNode::dumpTreeDotFile(const string& filename, bool doDump) {
const std::unique_ptr<std::ofstream> treedotp{V3File::new_ofstream(filename)};
if (treedotp->fail()) v3fatal("Can't write file: " << filename);
*treedotp << "digraph vTree{\n";
*treedotp << "\tgraph\t[label=\"" << filename + ".dot"
<< "\",\n";
*treedotp << "\tgraph\t[label=\"" << filename + ".dot" << "\",\n";
*treedotp << "\t\t labelloc=t, labeljust=l,\n";
*treedotp << "\t\t //size=\"7.5,10\",\n"
<< "];\n";
@ -1436,22 +1460,23 @@ string AstNode::instanceStr() const {
// in case we have some circular reference bug.
constexpr unsigned maxIterations = 10000;
unsigned iterCount = 0;
for (const AstNode* backp = this; backp; backp = backp->backp(), ++iterCount) {
// Walk 'backp' chain
for (const AstNode* currp = this; currp; currp = currp->backp(), ++iterCount) {
if (VL_UNCOVERABLE(iterCount >= maxIterations)) return ""; // LCOV_EXCL_LINE
// Prefer the enclosing scope, if there is one. This is always under the enclosing module,
// so just pick it up when encountered
if (const AstScope* const scopep = VN_CAST(backp, Scope)) {
if (const AstScope* const scopep = VN_CAST(currp, Scope)) {
return scopep->isTop() ? "" : "... note: In instance " + scopep->prettyNameQ();
}
// If scopes don't exist, report an example instance of the enclosing module
if (const AstModule* const modp = VN_CAST(backp, Module)) {
if (const AstModule* const modp = VN_CAST(currp, Module)) {
const string instanceName = modp->someInstanceName();
return instanceName.empty() ? "" : "... note: In instance '" + instanceName + "'";
}
}
return "";
}
void AstNode::v3errorEnd(std::ostringstream& str) const VL_RELEASE(V3Error::s().m_mutex) {
void AstNode::v3errorEnd(const std::ostringstream& str) const VL_RELEASE(V3Error::s().m_mutex) {
// Don't look for instance name when warning is disabled.
// In case of large number of warnings, this can
// take significant amount of time
@ -1471,7 +1496,8 @@ void AstNode::v3errorEnd(std::ostringstream& str) const VL_RELEASE(V3Error::s().
m_fileline->v3errorEnd(nsstr, instanceStrExtra);
}
}
void AstNode::v3errorEndFatal(std::ostringstream& str) const VL_RELEASE(V3Error::s().m_mutex) {
void AstNode::v3errorEndFatal(const std::ostringstream& str) const
VL_RELEASE(V3Error::s().m_mutex) {
v3errorEnd(str);
assert(0); // LCOV_EXCL_LINE
VL_UNREACHABLE;
@ -1592,11 +1618,14 @@ static VCastable computeCastableImp(const AstNodeDType* toDtp, const AstNodeDTyp
if (VN_IS(fromBaseDtp, EnumDType) && toDtp->sameTree(fromDtp))
return VCastable::ENUM_IMPLICIT;
if (fromNumericable) return VCastable::ENUM_EXPLICIT;
} else if (VN_IS(toDtp, QueueDType)
&& (VN_IS(fromDtp, BasicDType) || VN_IS(fromDtp, StreamDType))) {
return VCastable::COMPATIBLE;
} else if (VN_IS(toDtp, ClassRefDType) && VN_IS(fromConstp, Const)) {
if (fromConstp->isNull()) return VCastable::COMPATIBLE;
} else if (VN_IS(toDtp, ClassRefDType) && VN_IS(fromDtp, ClassRefDType)) {
const auto toClassp = VN_AS(toDtp, ClassRefDType)->classp();
const auto fromClassp = VN_AS(fromDtp, ClassRefDType)->classp();
const AstClass* const toClassp = VN_AS(toDtp, ClassRefDType)->classp();
const AstClass* const fromClassp = VN_AS(fromDtp, ClassRefDType)->classp();
const bool downcast = AstClass::isClassExtendedFrom(toClassp, fromClassp);
const bool upcast = AstClass::isClassExtendedFrom(fromClassp, toClassp);
if (upcast) {
@ -1640,7 +1669,7 @@ AstNodeDType* AstNode::getCommonClassTypep(AstNode* node1p, AstNode* node2p) {
while (classDtypep1) {
const VCastable castable = computeCastable(classDtypep1, node2p->dtypep(), node2p);
if (castable == VCastable::COMPATIBLE) return classDtypep1;
AstClassExtends* const extendsp = classDtypep1->classp()->extendsp();
const AstClassExtends* const extendsp = classDtypep1->classp()->extendsp();
classDtypep1 = extendsp ? VN_AS(extendsp->dtypep(), ClassRefDType) : nullptr;
}
return nullptr;

View File

@ -116,9 +116,8 @@ public:
// Above include has:
// enum en {...};
// const char* ascii() const {...};
enum en m_e;
// cppcheck-suppress uninitVar // responsibility of each subclass
VNType() = default;
const enum en m_e;
VNType() = delete;
// cppcheck-suppress noExplicitConstructor
constexpr VNType(en _e) VL_MT_SAFE : m_e{_e} {}
explicit VNType(int _e)
@ -289,11 +288,11 @@ public:
class VFwdType final {
public:
enum en : uint8_t { NONE, ENUM, STRUCT, UNION, CLASS, INTERFACE_CLASS };
enum en : uint8_t { NONE, ENUM, STRUCT, UNION, CLASS, INTERFACE_CLASS, GENERIC_INTERFACE };
enum en m_e;
const char* ascii() const {
static const char* const names[]
= {"none", "enum", "struct", "union", "class", "interface class"};
= {"none", "enum", "struct", "union", "class", "interface class", "generic interface"};
return names[m_e];
}
VFwdType()
@ -304,6 +303,8 @@ public:
explicit VFwdType(int _e)
: m_e(static_cast<en>(_e)) {} // Need () or GCC 4.8 false warning
constexpr operator en() const { return m_e; }
// Is a node type compatible with the declaration
bool isNodeCompatible(const AstNode* nodep) const;
};
constexpr bool operator==(const VFwdType& lhs, const VFwdType& rhs) { return lhs.m_e == rhs.m_e; }
constexpr bool operator==(const VFwdType& lhs, VFwdType::en rhs) { return lhs.m_e == rhs; }
@ -404,6 +405,7 @@ public:
ET_TRUE,
//
ET_COMBO, // Sensitive to all combo inputs to this block
ET_COMBO_STAR, // Sensitive to all combo inputs to this block (from .*)
ET_HYBRID, // This is like ET_COMB, but with explicit sensitivity to an expression
ET_STATIC, // static variable initializers (runs before 'initial')
ET_INITIAL, // 'initial' statements
@ -421,6 +423,7 @@ public:
true, // ET_TRUE
false, // ET_COMBO
false, // ET_COMBO_STAR
false, // ET_HYBRID
false, // ET_STATIC
false, // ET_INITIAL
@ -441,13 +444,13 @@ public:
}
const char* ascii() const {
static const char* const names[]
= {"CHANGED", "BOTH", "POS", "NEG", "EVENT", "TRUE",
"COMBO", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"};
= {"CHANGED", "BOTH", "POS", "NEG", "EVENT", "TRUE", "COMBO",
"COMBO_STAR", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"};
return names[m_e];
}
const char* verilogKwd() const {
static const char* const names[]
= {"[changed]", "edge", "posedge", "negedge", "[event]", "[true]",
= {"[changed]", "edge", "posedge", "negedge", "[event]", "[true]", "*",
"*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"};
return names[m_e];
}
@ -506,12 +509,16 @@ public:
ENUM_NAME, // V3Width processes
ENUM_VALID, // V3Width processes
//
FUNC_ARG_PROTO, // V3WidthCommit processes
FUNC_RETURN_PROTO, // V3WidthCommit processes
//
TYPEID, // V3Width processes
TYPENAME, // V3Width processes
//
VAR_BASE, // V3LinkResolve creates for AstPreSel, V3LinkParam removes
VAR_CLOCK_ENABLE, // Ignored, accepted for compatibility
VAR_FORCEABLE, // V3LinkParse moves to AstVar::isForceable
VAR_PORT_DTYPE, // V3LinkDot for V3Width to check port dtype
VAR_PUBLIC, // V3LinkParse moves to AstVar::sigPublic
VAR_PUBLIC_FLAT, // V3LinkParse moves to AstVar::sigPublic
VAR_PUBLIC_FLAT_RD, // V3LinkParse moves to AstVar::sigPublic
@ -535,8 +542,9 @@ public:
"DT_PUBLIC",
"ENUM_FIRST", "ENUM_LAST", "ENUM_NUM",
"ENUM_NEXT", "ENUM_PREV", "ENUM_NAME", "ENUM_VALID",
"FUNC_ARG_PROTO", "FUNC_RETURN_PROTO",
"TYPEID", "TYPENAME",
"VAR_BASE", "VAR_CLOCK_ENABLE", "VAR_FORCEABLE", "VAR_PUBLIC",
"VAR_BASE", "VAR_CLOCK_ENABLE", "VAR_FORCEABLE", "VAR_PORT_DTYPE", "VAR_PUBLIC",
"VAR_PUBLIC_FLAT", "VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW",
"VAR_ISOLATE_ASSIGNMENTS", "VAR_SC_BV", "VAR_SFORMAT", "VAR_CLOCKER",
"VAR_NO_CLOCKER", "VAR_SPLIT_VAR"
@ -591,6 +599,7 @@ public:
FORK_SYNC,
PROCESS_REFERENCE,
RANDOM_GENERATOR,
RANDOM_STDGENERATOR,
// Unsigned and two state; fundamental types
UINT32,
UINT64,
@ -625,6 +634,7 @@ public:
"VlFork",
"VlProcessRef",
"VlRandomizer",
"VlStdRandomizer",
"IData",
"QData",
"LOGIC_IMPLICIT",
@ -632,13 +642,16 @@ public:
return names[m_e];
}
const char* dpiType() const {
static const char* const names[]
= {"%E-unk", "svBit", "char", "void*", "char",
"int", "%E-integer", "svLogic", "long long", "double",
"short", "%E-time", "const char*", "%E-untyped", "dpiScope",
"const char*", "%E-mtaskstate", "%E-triggervec", "%E-dly-sched", "%E-trig-sched",
"%E-dyn-sched", "%E-fork", "%E-proc-ref", "%E-rand-gen", "IData",
"QData", "%E-logic-implct", " MAX"};
static const char* const names[] = {"%E-unk", "svBit", "char",
"void*", "char", "int",
"%E-integer", "svLogic", "long long",
"double", "short", "%E-time",
"const char*", "%E-untyped", "dpiScope",
"const char*", "%E-mtaskstate", "%E-triggervec",
"%E-dly-sched", "%E-trig-sched", "%E-dyn-sched",
"%E-fork", "%E-proc-ref", "%E-rand-gen",
"%E-stdrand-gen", "IData", "QData",
"%E-logic-implct", " MAX"};
return names[m_e];
}
static void selfTest() {
@ -679,6 +692,7 @@ public:
case FORK_SYNC: return 0; // opaque
case PROCESS_REFERENCE: return 0; // opaque
case RANDOM_GENERATOR: return 0; // opaque
case RANDOM_STDGENERATOR: return 0; // opaque
case UINT32: return 32;
case UINT64: return 64;
default: return 0;
@ -718,8 +732,8 @@ public:
return (m_e == EVENT || m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR
|| m_e == MTASKSTATE || m_e == TRIGGERVEC || m_e == DELAY_SCHEDULER
|| m_e == TRIGGER_SCHEDULER || m_e == DYNAMIC_TRIGGER_SCHEDULER || m_e == FORK_SYNC
|| m_e == PROCESS_REFERENCE || m_e == RANDOM_GENERATOR || m_e == DOUBLE
|| m_e == UNTYPED);
|| m_e == PROCESS_REFERENCE || m_e == RANDOM_GENERATOR
|| m_e == RANDOM_STDGENERATOR || m_e == DOUBLE || m_e == UNTYPED);
}
bool isDouble() const VL_MT_SAFE { return m_e == DOUBLE; }
bool isEvent() const { return m_e == EVENT; }
@ -771,6 +785,8 @@ public:
/* DYNAMIC_TRIGGER_SCHEDULER: */ "", // Should not be traced
/* FORK_SYNC: */ "", // Should not be traced
/* PROCESS_REFERENCE: */ "", // Should not be traced
/* RANDOM_GENERATOR: */ "", // Should not be traced
/* RANDOM_STD_GENERATOR: */ "", // Should not be traced
/* UINT32: */ "BIT",
/* UINT64: */ "BIT",
/* LOGIC_IMPLICIT: */ "", // Should not be traced
@ -1925,8 +1941,6 @@ public:
inline void iterateChildrenBackwardsConst(AstNode* nodep);
/// Call visit()s on const nodep (maybe nullptr) and nodep's nextp() list
inline void iterateAndNextConstNull(AstNode* nodep);
/// Call visit()s on const nodep (maybe nullptr) and nodep's nextp() list, in reverse order
inline void iterateAndNextConstNullBackwards(AstNode* nodep);
virtual void visit(AstNode* nodep) = 0;
virtual ~VNVisitorConst() {}
@ -2121,7 +2135,7 @@ protected:
AstNode(VNType t, FileLine* fl);
virtual AstNode* clone() = 0; // Generally, cloneTree is what you want instead
virtual void cloneRelink() { cloneRelinkGen(); }
virtual void cloneRelinkGen() = 0; // Generated by 'astgen'
virtual void cloneRelinkGen() {}; // Overrides generated by 'astgen'
void cloneRelinkTree();
// METHODS
@ -2160,7 +2174,8 @@ protected:
virtual bool sameNode(const AstNode*) const { return true; }
// Generated by 'astgen'. If do an oldp->replaceNode(newp), would cause a broken()
virtual bool wouldBreakGen(const AstNode* const oldp,
const AstNode* const newp) const = 0; // Generated by 'astgen'
const AstNode* const newp) const
= 0; // Generated by 'astgen'
public:
// ACCESSORS
@ -2194,8 +2209,6 @@ public:
// Used by AstNode::broken()
bool brokeExists() const { return V3Broken::isLinkable(this); }
bool brokeExistsAbove() const { return brokeExists() && (m_brokenState >> 7); }
bool brokeExistsBelow() const { return brokeExists() && !(m_brokenState >> 7); }
// Note: brokeExistsBelow is not quite precise, as it is true for sibling nodes as well
// CONSTRUCTORS
virtual ~AstNode() = default;
@ -2222,11 +2235,16 @@ public:
// ACCESSORS
virtual string name() const VL_MT_STABLE { return ""; }
virtual string origName() const { return ""; }
string prettyOrigOrName() const {
return prettyName(origName().empty() ? name() : origName());
}
virtual void name(const string& name) {
this->v3fatalSrc("name() called on object without name() method");
}
virtual void tag(const string& text) {}
virtual string tag() const { return ""; }
virtual uint32_t declTokenNum() const { return 0; }
virtual void declTokenNumSetMin(uint32_t tokenNum) {}
virtual string verilogKwd() const { return ""; }
string nameProtect() const VL_MT_STABLE; // Name with --protect-id applied
string origNameProtect() const; // origName with --protect-id applied
@ -2420,8 +2438,8 @@ public:
static AstNodeDType* getCommonClassTypep(AstNode* nodep1, AstNode* nodep2);
// METHODS - dump and error
void v3errorEnd(std::ostringstream& str) const VL_RELEASE(V3Error::s().m_mutex);
void v3errorEndFatal(std::ostringstream& str) const VL_ATTR_NORETURN
void v3errorEnd(const std::ostringstream& str) const VL_RELEASE(V3Error::s().m_mutex);
void v3errorEndFatal(const std::ostringstream& str) const VL_ATTR_NORETURN
VL_RELEASE(V3Error::s().m_mutex);
string warnContextPrimary() const VL_REQUIRES(V3Error::s().m_mutex) {
return fileline()->warnContextPrimary();
@ -2642,20 +2660,24 @@ public:
// For use via privateAs or the VN_DBG_AS macro only
template <typename T, typename E>
static T* unsafePrivateAs(AstNode* nodep) VL_PURE {
static_assert(!uselessCast<T, E>(), "Unnecessary VN_AS, node known to have target type.");
static_assert(!impossibleCast<T, E>(), "Unnecessary VN_AS, node cannot be this type.");
static_assert(!uselessCast<T, E>(),
"Unnecessary VN_DBG_AS, node known to have target type.");
static_assert(!impossibleCast<T, E>(), "Unnecessary VN_DBG_AS, node cannot be this type.");
return reinterpret_cast<T*>(nodep);
}
template <typename T, typename E>
static const T* unsafePrivateAs(const AstNode* nodep) VL_PURE {
static_assert(!uselessCast<T, E>(), "Unnecessary VN_AS, node known to have target type.");
static_assert(!impossibleCast<T, E>(), "Unnecessary VN_AS, node cannot be this type.");
static_assert(!uselessCast<T, E>(),
"Unnecessary VN_DBG_AS, node known to have target type.");
static_assert(!impossibleCast<T, E>(), "Unnecessary VN_DBG_AS, node cannot be this type.");
return reinterpret_cast<const T*>(nodep);
}
// For use via the VN_AS macro only
template <typename T, typename E>
static T* privateAs(AstNode* nodep) VL_PURE {
static_assert(!uselessCast<T, E>(), "Unnecessary VN_AS, node known to have target type.");
static_assert(!impossibleCast<T, E>(), "Unnecessary VN_AS, node cannot be this type.");
UASSERT_OBJ(!nodep || privateTypeTest<T>(nodep), nodep,
"AstNode is not of expected type, but instead has type '" << nodep->typeName()
<< "'");
@ -2663,6 +2685,8 @@ public:
}
template <typename T, typename E>
static const T* privateAs(const AstNode* nodep) VL_PURE {
static_assert(!uselessCast<T, E>(), "Unnecessary VN_AS, node known to have target type.");
static_assert(!impossibleCast<T, E>(), "Unnecessary VN_AS, node cannot be this type.");
UASSERT_OBJ(!nodep || privateTypeTest<T>(nodep), nodep,
"AstNode is not of expected type, but instead has type '" << nodep->typeName()
<< "'");
@ -3103,9 +3127,6 @@ void VNVisitorConst::iterateChildrenConst(AstNode* nodep) { nodep->iterateChildr
void VNVisitorConst::iterateChildrenBackwardsConst(AstNode* nodep) {
nodep->iterateChildrenBackwardsConst(*this);
}
void VNVisitorConst::iterateAndNextConstNullBackwards(AstNode* nodep) {
if (VL_LIKELY(nodep)) nodep->iterateListBackwardsConst(*this);
}
void VNVisitorConst::iterateAndNextConstNull(AstNode* nodep) {
if (VL_LIKELY(nodep)) nodep->iterateAndNextConst(*this);
}
@ -3135,6 +3156,7 @@ AstNode* VNVisitor::iterateSubtreeReturnEdits(AstNode* nodep) {
#include "V3AstNodeDType.h"
#include "V3AstNodeExpr.h"
#include "V3AstNodeOther.h"
#include "V3AstNodeStmt.h"
// Inline function definitions need to go last
#include "V3AstInlines.h"

View File

@ -94,11 +94,11 @@ AstRange::AstRange(FileLine* fl, const VNumRange& range)
rightp(new AstConst{fl, static_cast<uint32_t>(range.right())});
}
int AstRange::leftConst() const VL_MT_STABLE {
AstConst* const constp = VN_CAST(leftp(), Const);
const AstConst* const constp = VN_CAST(leftp(), Const);
return (constp ? constp->toSInt() : 0);
}
int AstRange::rightConst() const VL_MT_STABLE {
AstConst* const constp = VN_CAST(rightp(), Const);
const AstConst* const constp = VN_CAST(rightp(), Const);
return (constp ? constp->toSInt() : 0);
}
@ -129,7 +129,7 @@ AstPackArrayDType::AstPackArrayDType(FileLine* fl, AstNodeDType* dtp, AstRange*
}
int AstQueueDType::boundConst() const VL_MT_STABLE {
AstConst* const constp = VN_CAST(boundp(), Const);
const AstConst* const constp = VN_CAST(boundp(), Const);
return (constp ? constp->toSInt() : 0);
}
@ -148,8 +148,8 @@ bool AstBasicDType::ascending() const {
return (rangep() ? rangep()->ascending() : m.m_nrange.ascending());
}
bool AstActive::hasClocked() const { return m_sensesp->hasClocked(); }
bool AstActive::hasCombo() const { return m_sensesp->hasCombo(); }
bool AstActive::hasClocked() const { return m_sentreep->hasClocked(); }
bool AstActive::hasCombo() const { return m_sentreep->hasCombo(); }
AstElabDisplay::AstElabDisplay(FileLine* fl, VDisplayType dispType, AstNodeExpr* exprsp)
: ASTGEN_SUPER_ElabDisplay(fl) {
@ -193,7 +193,7 @@ bool AstVarRef::sameNode(const AstVarRef* samep) const {
&& (varp() && samep->varp() && varp()->name() == samep->varp()->name()));
}
}
bool AstVarRef::sameNoLvalue(AstVarRef* samep) const {
bool AstVarRef::sameNoLvalue(const AstVarRef* samep) const {
if (varScopep()) {
return (varScopep() == samep->varScopep());
} else {

View File

@ -162,11 +162,12 @@ public:
uint32_t arrayUnpackedElements() const; // 1, or total multiplication of all dimensions
static int uniqueNumInc() { return ++s_uniqueNum; }
const char* charIQWN() const {
return (isString() ? "N" : isWide() ? "W" : isQuad() ? "Q" : "I");
return (isString() ? "N" : isWide() ? "W" : isDouble() ? "D" : isQuad() ? "Q" : "I");
}
string cType(const string& name, bool forFunc, bool isRef, bool packed = false) const;
// Represents a C++ LiteralType? (can be constexpr)
bool isLiteralType() const VL_MT_STABLE;
virtual bool isDynamicallySized() const { return false; }
private:
class CTypeRecursed;
@ -383,6 +384,7 @@ public:
int widthAlignBytes() const override { return subDTypep()->widthAlignBytes(); }
int widthTotalBytes() const override { return subDTypep()->widthTotalBytes(); }
bool isCompound() const override { return true; }
bool isDynamicallySized() const override { return true; }
};
class AstBasicDType final : public AstNodeDType {
// Builtin atomic/vectored data type
@ -473,6 +475,9 @@ public:
bool isRandomGenerator() const VL_MT_SAFE {
return keyword() == VBasicDTypeKwd::RANDOM_GENERATOR;
}
bool isStdRandomGenerator() const VL_MT_SAFE {
return keyword() == VBasicDTypeKwd::RANDOM_STDGENERATOR;
}
bool isOpaque() const VL_MT_SAFE { return keyword().isOpaque(); }
bool isString() const VL_MT_STABLE { return keyword().isString(); }
bool isZeroInit() const { return keyword().isZeroInit(); }
@ -753,6 +758,7 @@ public:
int widthAlignBytes() const override { return subDTypep()->widthAlignBytes(); }
int widthTotalBytes() const override { return subDTypep()->widthTotalBytes(); }
bool isCompound() const override { return true; }
bool isDynamicallySized() const override { return true; }
};
class AstEmptyQueueDType final : public AstNodeDType {
// For EmptyQueue
@ -786,7 +792,7 @@ public:
private:
string m_name; // Name from upper typedef, if any
const int m_uniqueNum = 0;
const int m_uniqueNum;
TableMap m_tableMap; // Created table for V3Width only to remove duplicates
public:
@ -839,6 +845,40 @@ public:
const TableMap& tableMap() const { return m_tableMap; }
};
class AstIfaceGenericDType final : public AstNodeDType {
// Generic interface that will be replaced with AstIfaceRefDType
FileLine* m_modportFileline; // Where modport token was
string m_modportName; // "" = no modport
public:
explicit AstIfaceGenericDType(FileLine* fl)
: ASTGEN_SUPER_IfaceGenericDType(fl) {
dtypep(this);
}
AstIfaceGenericDType(FileLine* fl, FileLine* modportFl, const string& modport)
: ASTGEN_SUPER_IfaceGenericDType(fl)
, m_modportFileline{modportFl}
, m_modportName{modport} {
dtypep(this);
}
ASTGEN_MEMBERS_AstIfaceGenericDType;
void dumpSmall(std::ostream& str) const override;
bool hasDType() const override VL_MT_SAFE { return true; }
bool maybePointedTo() const override VL_MT_SAFE { return true; }
bool undead() const override { return true; }
AstNodeDType* subDTypep() const override VL_MT_STABLE { return nullptr; }
AstNodeDType* virtRefDTypep() const override { return nullptr; }
void virtRefDTypep(AstNodeDType* nodep) override {}
bool similarDTypeNode(const AstNodeDType* samep) const override { return this == samep; }
AstBasicDType* basicp() const override VL_MT_STABLE { return nullptr; }
int widthAlignBytes() const override { return 1; }
int widthTotalBytes() const override { return 1; }
string modportName() const { return m_modportName; }
bool isModport() { return !m_modportName.empty(); }
bool isCompound() const override { return true; }
FileLine* modportFileline() const { return m_modportFileline; }
string name() const override { return m_modportName; }
};
class AstIfaceRefDType final : public AstNodeDType {
// Reference to an interface, either for a port, or inside parent cell
// @astgen op1 := paramsp : List[AstPin]
@ -892,11 +932,13 @@ public:
if (flag) v3Global.setHasVirtIfaces();
}
FileLine* modportFileline() const { return m_modportFileline; }
void modportFileline(FileLine* const modportFileline) { m_modportFileline = modportFileline; }
string cellName() const { return m_cellName; }
void cellName(const string& name) { m_cellName = name; }
string ifaceName() const { return m_ifaceName; }
string ifaceNameQ() const { return "'" + prettyName(ifaceName()) + "'"; }
void ifaceName(const string& name) { m_ifaceName = name; }
void modportName(const string& modportName) { m_modportName = modportName; }
string modportName() const { return m_modportName; }
AstIface* ifaceViaCellp() const; // Use cellp or ifacep
AstIface* ifacep() const { return m_ifacep; }
@ -1121,6 +1163,7 @@ public:
int widthAlignBytes() const override { return subDTypep()->widthAlignBytes(); }
int widthTotalBytes() const override { return subDTypep()->widthTotalBytes(); }
bool isCompound() const override { return true; }
bool isDynamicallySized() const override { return true; }
};
class AstRefDType final : public AstNodeDType {
// @astgen op1 := typeofp : Optional[AstNode<AstNodeExpr|AstNodeDType>]
@ -1329,7 +1372,7 @@ public:
AstNodeDType* subDTypep() const override VL_MT_STABLE { return nullptr; }
AstNodeDType* virtRefDTypep() const override { return nullptr; }
void virtRefDTypep(AstNodeDType* nodep) override {}
bool similarDTypeNode(const AstNodeDType* samep) const override { return this == samep; }
bool similarDTypeNode(const AstNodeDType* samep) const override { return true; }
AstBasicDType* basicp() const override VL_MT_STABLE { return nullptr; }
int widthAlignBytes() const override { return 1; }
int widthTotalBytes() const override { return 1; }

View File

@ -56,7 +56,7 @@ public:
virtual string emitVerilog() = 0; /// Format string for verilog writing; see V3EmitV
// For documentation on emitC format see EmitCFunc::emitOpName
virtual string emitC() = 0;
virtual string emitSMT() const { V3ERROR_NA_RETURN(""); };
virtual string emitSMT() const { return ""; };
virtual string emitSimpleOperator() { return ""; } // "" means not ok to use
virtual bool emitCheckMaxWords() { return false; } // Check VL_MULS_MAX_WORDS
virtual bool cleanOut() const = 0; // True if output has extra upper bits zero
@ -406,31 +406,6 @@ private:
return lhsp()->isPure() && rhsp()->isPure() && thsp()->isPure();
}
};
class AstNodeCond VL_NOT_FINAL : public AstNodeTriop {
// @astgen alias op1 := condp
// @astgen alias op2 := thenp
// @astgen alias op3 := elsep
protected:
AstNodeCond(VNType t, FileLine* fl, AstNodeExpr* condp, AstNodeExpr* thenp,
AstNodeExpr* elsep);
public:
ASTGEN_MEMBERS_AstNodeCond;
void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs,
const V3Number& ths) override;
string emitVerilog() override { return "%k(%l %f? %r %k: %t)"; }
string emitC() override { return "VL_COND_%nq%lq%rq%tq(%nw, %P, %li, %ri, %ti)"; }
string emitSMT() const override { return "(ite (__Vbool %l) %r %t)"; }
bool cleanOut() const override { return false; } // clean if e1 & e2 clean
bool cleanLhs() const override { return true; }
bool cleanRhs() const override { return false; }
bool cleanThs() const override { return false; } // Propagates up
bool sizeMattersLhs() const override { return false; }
bool sizeMattersRhs() const override { return false; }
bool sizeMattersThs() const override { return false; }
int instrCount() const override { return INSTR_COUNT_BRANCH; }
virtual AstNodeExpr* cloneType(AstNodeExpr* condp, AstNodeExpr* thenp, AstNodeExpr* elsep) = 0;
};
class AstNodeDistTriop VL_NOT_FINAL : public AstNodeTriop {
public:
AstNodeDistTriop(VNType t, FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp,
@ -528,7 +503,7 @@ public:
AstVar* varp() const VL_MT_STABLE { return m_varp; } // [After Link] Pointer to variable
void varp(AstVar* varp) {
m_varp = varp;
dtypeFrom((AstNode*)varp);
dtypeFrom(reinterpret_cast<AstNode*>(varp));
}
AstVarScope* varScopep() const { return m_varScopep; }
void varScopep(AstVarScope* varscp) { m_varScopep = varscp; }
@ -561,6 +536,7 @@ public:
public:
ASTGEN_MEMBERS_AstAddrOfCFunc;
void dump(std::ostream& str) const override;
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
string emitC() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { return true; }
@ -593,6 +569,7 @@ class AstAttrOf final : public AstNodeExpr {
// @astgen op1 := fromp : Optional[AstNode<AstNodeExpr|AstNodeDType>]
// @astgen op2 := dimp : Optional[AstNodeExpr]
VAttrType m_attrType; // What sort of extraction
string m_name; // Name for some attributes
public:
AstAttrOf(FileLine* fl, VAttrType attrtype, AstNode* fromp = nullptr,
AstNodeExpr* dimp = nullptr)
@ -602,10 +579,11 @@ public:
m_attrType = attrtype;
}
ASTGEN_MEMBERS_AstAttrOf;
string name() const override VL_MT_STABLE { return m_name; } // * = Var name
void name(const string& name) override { m_name = name; }
VAttrType attrType() const { return m_attrType; }
void dump(std::ostream& str = std::cout) const override;
void dumpJson(std::ostream& str = std::cout) const override;
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
string emitC() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { V3ERROR_NA_RETURN(true); }
@ -678,6 +656,8 @@ public:
childDTypep(dtp);
dtypeFrom(dtp);
}
// cppcheck-suppress constParameterPointer
// cppcheck-suppress constParameterCallback
AstCast(FileLine* fl, AstNodeExpr* fromp, AstNodeDType* dtp)
: ASTGEN_SUPER_Cast(fl) {
this->fromp(fromp);
@ -787,7 +767,9 @@ public:
AstNode* classOrPackageNodep() const { return m_classOrPackageNodep; }
void classOrPackageNodep(AstNode* nodep) { m_classOrPackageNodep = nodep; }
AstPackage* packagep() const { return VN_CAST(classOrPackageNodep(), Package); }
void classOrPackagep(AstNodeModule* nodep) { m_classOrPackageNodep = (AstNode*)nodep; }
void classOrPackagep(AstNodeModule* nodep) {
m_classOrPackageNodep = reinterpret_cast<AstNode*>(nodep);
}
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
string emitC() override { V3ERROR_NA_RETURN(""); }
@ -813,14 +795,17 @@ class AstConsDynArray final : public AstNodeExpr {
// Construct a queue and return object, '{}. '{lhs}, '{lhs. rhs}
// @astgen op1 := lhsp : Optional[AstNode]
// @astgen op2 := rhsp : Optional[AstNode]
const bool m_lhsIsValue = false; // LHS constructs value inside the queue, not concat
const bool m_rhsIsValue = false; // RHS constructs value inside the queue, not concat
const bool m_lhsIsValue; // LHS constructs value inside the queue, not concat
const bool m_rhsIsValue; // RHS constructs value inside the queue, not concat
public:
explicit AstConsDynArray(FileLine* fl)
: ASTGEN_SUPER_ConsDynArray(fl) {}
: ASTGEN_SUPER_ConsDynArray(fl)
, m_lhsIsValue{false}
, m_rhsIsValue{false} {}
explicit AstConsDynArray(FileLine* fl, bool lhsIsValue, AstNode* lhsp)
: ASTGEN_SUPER_ConsDynArray(fl)
, m_lhsIsValue{lhsIsValue} {
, m_lhsIsValue{lhsIsValue}
, m_rhsIsValue{false} {
this->lhsp(lhsp);
}
explicit AstConsDynArray(FileLine* fl, bool lhsIsValue, AstNode* lhsp, bool rhsIsValue,
@ -847,7 +832,7 @@ public:
bool rhsIsValue() const { return m_rhsIsValue; }
};
class AstConsPackMember final : public AstNodeExpr {
// Construct a packed array single emement [member1: value1]
// Construct a packed array single element [member1: value1]
// Don't need the member we are constructing, as the dtypep can get us to it
// @astgen op2 := rhsp : AstNodeExpr
public:
@ -895,14 +880,17 @@ class AstConsQueue final : public AstNodeExpr {
// Construct a queue and return object, '{}. '{lhs}, '{lhs. rhs}
// @astgen op1 := lhsp : Optional[AstNode]
// @astgen op2 := rhsp : Optional[AstNode]
const bool m_lhsIsValue = false; // LHS constructs value inside the queue, not concat
const bool m_rhsIsValue = false; // RHS constructs value inside the queue, not concat
const bool m_lhsIsValue; // LHS constructs value inside the queue, not concat
const bool m_rhsIsValue; // RHS constructs value inside the queue, not concat
public:
explicit AstConsQueue(FileLine* fl)
: ASTGEN_SUPER_ConsQueue(fl) {}
: ASTGEN_SUPER_ConsQueue(fl)
, m_lhsIsValue{false}
, m_rhsIsValue{false} {}
explicit AstConsQueue(FileLine* fl, bool lhsIsValue, AstNode* lhsp)
: ASTGEN_SUPER_ConsQueue(fl)
, m_lhsIsValue{lhsIsValue} {
, m_lhsIsValue{lhsIsValue}
, m_rhsIsValue{false} {
this->lhsp(lhsp);
}
explicit AstConsQueue(FileLine* fl, bool lhsIsValue, AstNode* lhsp, bool rhsIsValue,
@ -962,26 +950,26 @@ class AstConst final : public AstNodeExpr {
public:
AstConst(FileLine* fl, const V3Number& num)
: ASTGEN_SUPER_Const(fl)
, m_num(num) {
, m_num{num} {
initWithNumber();
}
class WidthedValue {}; // for creator type-overload selection
AstConst(FileLine* fl, WidthedValue, int width, uint32_t value)
: ASTGEN_SUPER_Const(fl)
, m_num(this, width, value) {
, m_num(this, width, value) { // Need () constructor
initWithNumber();
}
class DTyped {}; // for creator type-overload selection
// Zero/empty constant with a type matching nodetypep
AstConst(FileLine* fl, DTyped, const AstNodeDType* nodedtypep)
: ASTGEN_SUPER_Const(fl)
, m_num(this, nodedtypep) {
, m_num{this, nodedtypep} {
initWithNumber();
}
class StringToParse {}; // for creator type-overload selection
AstConst(FileLine* fl, StringToParse, const char* sourcep)
: ASTGEN_SUPER_Const(fl)
, m_num(this, sourcep) {
, m_num{this, sourcep} {
initWithNumber();
}
class VerilogStringLiteral {}; // for creator type-overload selection
@ -992,41 +980,41 @@ public:
}
AstConst(FileLine* fl, uint32_t num)
: ASTGEN_SUPER_Const(fl)
, m_num(this, 32, num) {
, m_num(this, 32, num) { // Need () constructor
dtypeSetLogicUnsized(m_num.width(), 0, VSigning::UNSIGNED);
}
class Unsized32 {}; // for creator type-overload selection
AstConst(FileLine* fl, Unsized32, uint32_t num) // Unsized 32-bit integer of specified value
: ASTGEN_SUPER_Const(fl)
, m_num(this, 32, num) {
, m_num(this, 32, num) { // Need () constructor
m_num.width(32, false);
dtypeSetLogicUnsized(32, m_num.widthToFit(), VSigning::UNSIGNED);
}
class Signed32 {}; // for creator type-overload selection
AstConst(FileLine* fl, Signed32, int32_t num) // Signed 32-bit integer of specified value
: ASTGEN_SUPER_Const(fl)
, m_num(this, 32, num) {
, m_num(this, 32, num) { // Need () constructor
m_num.width(32, true);
dtypeSetLogicUnsized(32, m_num.widthToFit(), VSigning::SIGNED);
}
class Unsized64 {}; // for creator type-overload selection
AstConst(FileLine* fl, Unsized64, uint64_t num)
: ASTGEN_SUPER_Const(fl)
, m_num(this, 64, 0) {
, m_num(this, 64, 0) { // Need () constructor
m_num.setQuad(num);
dtypeSetLogicSized(64, VSigning::UNSIGNED);
}
class SizedEData {}; // for creator type-overload selection
AstConst(FileLine* fl, SizedEData, uint64_t num)
: ASTGEN_SUPER_Const(fl)
, m_num(this, VL_EDATASIZE, 0) {
, m_num(this, VL_EDATASIZE, 0) { // Need () constructor
m_num.setQuad(num);
dtypeSetLogicSized(VL_EDATASIZE, VSigning::UNSIGNED);
}
class RealDouble {}; // for creator type-overload selection
AstConst(FileLine* fl, RealDouble, double num)
: ASTGEN_SUPER_Const(fl)
, m_num(this, 64) {
, m_num(this, 64) { // Need () constructor
m_num.setDouble(num);
dtypeSetDouble();
}
@ -1039,27 +1027,27 @@ public:
class BitFalse {};
AstConst(FileLine* fl, BitFalse) // Shorthand const 0, dtype should be a logic of size 1
: ASTGEN_SUPER_Const(fl)
, m_num(this, 1, 0) {
, m_num(this, 1, 0) { // Need () constructor
dtypeSetBit();
}
// Shorthand const 1 (or with argument 0/1), dtype should be a logic of size 1
class BitTrue {};
AstConst(FileLine* fl, BitTrue, bool on = true)
: ASTGEN_SUPER_Const(fl)
, m_num(this, 1, on) {
, m_num(this, 1, on) { // Need () constructor
dtypeSetBit();
}
class All0 {};
AstConst(FileLine* fl, All0)
: ASTGEN_SUPER_Const(fl)
, m_num(this, "'0") {
, m_num(this, "'0") { // Need () constructor
initWithNumber();
fl->warnOff(V3ErrorCode::NEWERSTD, true);
}
class All1 {};
AstConst(FileLine* fl, All1)
: ASTGEN_SUPER_Const(fl)
, m_num(this, "'1") {
, m_num(this, "'1") { // Need () constructor
initWithNumber();
fl->warnOff(V3ErrorCode::NEWERSTD, true);
}
@ -1128,10 +1116,45 @@ public:
string emitC() final override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const final override { V3ERROR_NA_RETURN(true); }
};
class AstCvtArrayToArray final : public AstNodeExpr {
// Copy/Cast from dynamic/unpacked types to dynamic/unpacked types
// @astgen op1 := fromp : AstNodeExpr
private:
const bool m_reverse; // whether ordering gets reversed in this operation (ex: {<<{expr}})
const int m_blockSize; // num bits per block in a streaming operation (ex: 4 in {<<4{expr}}))
const int m_dstElementBits; // num bits in lhs (ex: 8 if to byte-queue, 1 if to bit-queue)
const int m_srcElementBits; // num bits in rhs (ex 8 if from byte-queue, 1 if from bit-queue)
public:
// cppcheck-suppress constParameterPointer
// cppcheck-suppress constParameterCallback
AstCvtArrayToArray(FileLine* fl, AstNodeExpr* fromp, AstNodeDType* dtp, bool reverse,
int blockSize, int dstElementBits, int srcElementBits)
: ASTGEN_SUPER_CvtArrayToArray(fl)
, m_reverse{reverse}
, m_blockSize{blockSize}
, m_dstElementBits{dstElementBits}
, m_srcElementBits{srcElementBits} {
this->fromp(fromp);
dtypeFrom(dtp);
}
ASTGEN_MEMBERS_AstCvtArrayToArray;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
string emitC() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { return true; }
bool reverse() const { return m_reverse; }
int blockSize() const { return m_blockSize; }
int dstElementBits() const { return m_dstElementBits; }
int srcElementBits() const { return m_srcElementBits; }
};
class AstCvtArrayToPacked final : public AstNodeExpr {
// Cast from dynamic queue data type to packed array
// @astgen op1 := fromp : AstNodeExpr
public:
// cppcheck-suppress constParameterPointer
// cppcheck-suppress constParameterCallback
AstCvtArrayToPacked(FileLine* fl, AstNodeExpr* fromp, AstNodeDType* dtp)
: ASTGEN_SUPER_CvtArrayToPacked(fl) {
this->fromp(fromp);
@ -1146,6 +1169,8 @@ class AstCvtPackedToArray final : public AstNodeExpr {
// Cast from packed array to dynamic/unpacked queue data type
// @astgen op1 := fromp : AstNodeExpr
public:
// cppcheck-suppress constParameterPointer
// cppcheck-suppress constParameterCallback
AstCvtPackedToArray(FileLine* fl, AstNodeExpr* fromp, AstNodeDType* dtp)
: ASTGEN_SUPER_CvtPackedToArray(fl) {
this->fromp(fromp);
@ -1160,6 +1185,8 @@ class AstCvtUnpackedToQueue final : public AstNodeExpr {
// Cast from unpacked array to dynamic/unpacked queue data type
// @astgen op1 := fromp : AstNodeExpr
public:
// cppcheck-suppress constParameterPointer
// cppcheck-suppress constParameterCallback
AstCvtUnpackedToQueue(FileLine* fl, AstNodeExpr* fromp, AstNodeDType* dtp)
: ASTGEN_SUPER_CvtUnpackedToQueue(fl) {
this->fromp(fromp);
@ -1462,6 +1489,22 @@ public:
bool cleanOut() const override { return false; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstFalling final : public AstNodeExpr {
// Verilog $falling_gclk
// @astgen op1 := exprp : AstNodeExpr
public:
AstFalling(FileLine* fl, AstNodeExpr* exprp)
: ASTGEN_SUPER_Falling(fl) {
this->exprp(exprp);
}
ASTGEN_MEMBERS_AstFalling;
string emitVerilog() override { return "$falling_gclk(%l)"; }
string emitC() override { V3ERROR_NA_RETURN(""); }
string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
int instrCount() const override { return widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstFell final : public AstNodeExpr {
// Verilog $fell
// @astgen op1 := exprp : AstNodeExpr
@ -1480,6 +1523,24 @@ public:
int instrCount() const override { return widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstFuture final : public AstNodeExpr {
// Verilog $future_gclk
// @astgen op1 := exprp : AstNodeExpr
// @astgen op2 := sentreep : Optional[AstSenTree]
public:
AstFuture(FileLine* fl, AstNodeExpr* exprp, AstSenTree* sentreep)
: ASTGEN_SUPER_Future(fl) {
this->exprp(exprp);
this->sentreep(sentreep);
}
ASTGEN_MEMBERS_AstFuture;
string emitVerilog() override { return "$future_gclk(%l)"; }
string emitC() override { V3ERROR_NA_RETURN(""); }
string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
int instrCount() const override { return widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstGatePin final : public AstNodeExpr {
// Possibly expand a gate primitive input pin value to match the range of the gate primitive
// @astgen op1 := exprp : AstNodeExpr // Pin expression
@ -1595,7 +1656,6 @@ class AstLambdaArgRef final : public AstNodeExpr {
// Lambda argument usage
// These are not AstVarRefs because we need to be able to delete/clone lambdas during
// optimizations and AstVar's are painful to remove.
ASTGEN_MEMBERS_AstLambdaArgRef;
private:
string m_name; // Name of variable
@ -1606,6 +1666,7 @@ public:
: ASTGEN_SUPER_LambdaArgRef(fl)
, m_name{name}
, m_index{index} {}
ASTGEN_MEMBERS_AstLambdaArgRef;
bool sameNode(const AstNode* /*samep*/) const override { return true; }
string emitVerilog() override { return name(); }
string emitC() override { V3ERROR_NA_RETURN(""); }
@ -1803,7 +1864,7 @@ class AstRand final : public AstNodeExpr {
// $random/$random(seed) or $urandom/$urandom(seed)
// Return a random number, based upon width()
// @astgen op1 := seedp : Optional[AstNode]
const bool m_urandom = false; // $urandom vs $random
const bool m_urandom; // $urandom vs $random
public:
class Reset {};
AstRand(FileLine* fl, AstNode* seedp, bool urandom)
@ -1861,6 +1922,22 @@ public:
int instrCount() const override { return INSTR_COUNT_PLI; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstRising final : public AstNodeExpr {
// Verilog $rising_gclk
// @astgen op1 := exprp : AstNodeExpr
public:
AstRising(FileLine* fl, AstNodeExpr* exprp)
: ASTGEN_SUPER_Rising(fl) {
this->exprp(exprp);
}
ASTGEN_MEMBERS_AstRising;
string emitVerilog() override { return "$rising_gclk(%l)"; }
string emitC() override { V3ERROR_NA_RETURN(""); }
string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
int instrCount() const override { return widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstRose final : public AstNodeExpr {
// Verilog $rose
// @astgen op1 := exprp : AstNodeExpr
@ -1985,7 +2062,7 @@ class AstScopeName final : public AstNodeExpr {
// @astgen op1 := scopeAttrp : List[AstText]
// @astgen op2 := scopeEntrp : List[AstText]
bool m_dpiExport = false; // Is for dpiExport
const bool m_forFormat = false; // Is for a format %m
const bool m_forFormat; // Is for a format %m
string scopeNameFormatter(AstText* scopeTextp) const;
string scopePrettyNameFormatter(AstText* scopeTextp) const;
@ -2120,6 +2197,22 @@ public:
bool cleanOut() const override { return true; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstSteady final : public AstNodeExpr {
// Verilog $steady_gclk
// @astgen op1 := exprp : AstNodeExpr
public:
AstSteady(FileLine* fl, AstNodeExpr* exprp)
: ASTGEN_SUPER_Steady(fl) {
this->exprp(exprp);
}
ASTGEN_MEMBERS_AstSteady;
string emitVerilog() override { return "$steady_gclk(%l)"; }
string emitC() override { V3ERROR_NA_RETURN(""); }
string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
int instrCount() const override { return widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstStructSel final : public AstNodeExpr {
// Unpacked struct/union member access
// Parents: math|stmt
@ -2433,7 +2526,7 @@ public:
bool sizeMattersLhs() const override { return false; }
bool sizeMattersRhs() const override { return false; }
int instrCount() const override { return widthInstrs() * 20; }
bool isPure() override { return true; }
bool isPure() override { return false; }
};
class AstCompareNN final : public AstNodeBiop {
// Verilog str.compare() and str.icompare()
@ -3366,7 +3459,12 @@ public:
: ASTGEN_SUPER_Replicate(fl, lhsp, rhsp) {
if (lhsp) {
if (const AstConst* const constp = VN_CAST(rhsp, Const)) {
dtypeSetLogicSized(lhsp->width() * constp->toUInt(), VSigning::UNSIGNED);
if (constp->num().isFourState()
|| (constp->dtypep()->isSigned() && constp->num().isNegative())) {
dtypeSetLogicSized(lhsp->width(), VSigning::UNSIGNED); // V3Width warns
} else {
dtypeSetLogicSized(lhsp->width() * constp->toSInt(), VSigning::UNSIGNED);
}
}
}
}
@ -3858,6 +3956,7 @@ public:
AstNodeExpr* cloneType(AstNodeExpr* lhsp, AstNodeExpr* rhsp) override {
return new AstNeq{fileline(), lhsp, rhsp};
}
static AstNodeBiop* newTyped(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp);
void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) override {
out.opNeq(lhs, rhs);
}
@ -4196,7 +4295,7 @@ public:
// === AstNodeSel ===
class AstArraySel final : public AstNodeSel {
void init(AstNode* fromp) {
void init(const AstNode* fromp) {
if (fromp && VN_IS(fromp->dtypep()->skipRefp(), NodeArrayDType)) {
// Strip off array to find what array references
dtypeFrom(VN_AS(fromp->dtypep()->skipRefp(), NodeArrayDType)->subDTypep());
@ -4236,7 +4335,7 @@ public:
static AstNode* baseFromp(AstNode* nodep, bool overMembers);
};
class AstAssocSel final : public AstNodeSel {
void init(AstNode* fromp) {
void init(const AstNode* fromp) {
if (fromp && VN_IS(fromp->dtypep()->skipRefp(), AssocArrayDType)) {
// Strip off array to find what array references
dtypeFrom(VN_AS(fromp->dtypep()->skipRefp(), AssocArrayDType)->subDTypep());
@ -4270,7 +4369,7 @@ public:
int instrCount() const override { return widthInstrs(); }
};
class AstWildcardSel final : public AstNodeSel {
void init(AstNode* fromp) {
void init(const AstNode* fromp) {
if (fromp && VN_IS(fromp->dtypep()->skipRefp(), WildcardArrayDType)) {
// Strip off array to find what array references
dtypeFrom(VN_AS(fromp->dtypep()->skipRefp(), WildcardArrayDType)->subDTypep());
@ -4443,7 +4542,7 @@ class AstFuncRef final : public AstNodeFTaskRef {
public:
inline AstFuncRef(FileLine* fl, AstFunc* taskp, AstNodeExpr* pinsp);
AstFuncRef(FileLine* fl, AstParseRef* namep, AstNodeExpr* pinsp)
: ASTGEN_SUPER_FuncRef(fl, (AstNode*)namep, pinsp) {}
: ASTGEN_SUPER_FuncRef(fl, reinterpret_cast<AstNode*>(namep), pinsp) {}
AstFuncRef(FileLine* fl, const string& name, AstNodeExpr* pinsp)
: ASTGEN_SUPER_FuncRef(fl, name, pinsp) {}
ASTGEN_MEMBERS_AstFuncRef;
@ -4486,6 +4585,7 @@ public:
void isImplicit(bool flag) { m_isImplicit = flag; }
bool isScoped() const { return m_isScoped; }
void isScoped(bool flag) { m_isScoped = flag; }
bool isPure() override { return false; }
};
class AstTaskRef final : public AstNodeFTaskRef {
// A reference to a task
@ -4493,7 +4593,7 @@ class AstTaskRef final : public AstNodeFTaskRef {
public:
inline AstTaskRef(FileLine* fl, AstTask* taskp, AstNodeExpr* pinsp);
AstTaskRef(FileLine* fl, AstParseRef* namep, AstNodeExpr* pinsp)
: ASTGEN_SUPER_TaskRef(fl, (AstNode*)namep, pinsp) {
: ASTGEN_SUPER_TaskRef(fl, reinterpret_cast<AstNode*>(namep), pinsp) {
dtypeSetVoid();
}
AstTaskRef(FileLine* fl, const string& name, AstNodeExpr* pinsp)
@ -4640,6 +4740,30 @@ public:
};
// === AstNodeTriop ===
class AstCond final : public AstNodeTriop {
// @astgen alias op1 := condp
// @astgen alias op2 := thenp
// @astgen alias op3 := elsep
public:
AstCond(FileLine* fl, AstNodeExpr* condp, AstNodeExpr* thenp, AstNodeExpr* elsep);
ASTGEN_MEMBERS_AstCond;
void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs,
const V3Number& ths) override {
out.opAssign(lhs.isNeqZero() ? rhs : ths);
}
string emitVerilog() override { return "%k(%l %f? %r %k: %t)"; }
string emitC() override { return "VL_COND_%nq%lq%rq%tq(%nw, %P, %li, %ri, %ti)"; }
string emitSMT() const override { return "(ite (__Vbool %l) %r %t)"; }
bool cleanOut() const override { return false; } // clean if e1 & e2 clean
bool cleanLhs() const override { return true; }
bool cleanRhs() const override { return false; }
bool cleanThs() const override { return false; } // Propagates up
bool sizeMattersLhs() const override { return false; }
bool sizeMattersRhs() const override { return false; }
bool sizeMattersThs() const override { return false; }
int instrCount() const override { return INSTR_COUNT_BRANCH; }
};
class AstPostAdd final : public AstNodeTriop {
// Post-increment/add
// Children: lhsp: AstConst (1) as currently support only ++ not +=
@ -4818,28 +4942,6 @@ public:
bool sizeMattersThs() const override { return false; }
};
// === AstNodeCond ===
class AstCond final : public AstNodeCond {
// Conditional ?: expression
public:
AstCond(FileLine* fl, AstNodeExpr* condp, AstNodeExpr* thenp, AstNodeExpr* elsep)
: ASTGEN_SUPER_Cond(fl, condp, thenp, elsep) {}
ASTGEN_MEMBERS_AstCond;
AstNodeExpr* cloneType(AstNodeExpr* condp, AstNodeExpr* thenp, AstNodeExpr* elsep) override {
return new AstCond{fileline(), condp, thenp, elsep};
}
};
class AstCondBound final : public AstNodeCond {
// Conditional ?: expression, specially made for safety checking of array bounds
public:
AstCondBound(FileLine* fl, AstNodeExpr* condp, AstNodeExpr* thenp, AstNodeExpr* elsep)
: ASTGEN_SUPER_CondBound(fl, condp, thenp, elsep) {}
ASTGEN_MEMBERS_AstCondBound;
AstNodeExpr* cloneType(AstNodeExpr* condp, AstNodeExpr* thenp, AstNodeExpr* elsep) override {
return new AstCondBound{fileline(), condp, thenp, elsep};
}
};
// === AstNodeDistTriop ===
class AstDistErlang final : public AstNodeDistTriop {
public:
@ -4927,17 +5029,17 @@ class AstCAwait final : public AstNodeUniop {
// Emit C++'s co_await expression
// @astgen alias op1 := exprp
//
// @astgen ptr := m_sensesp : Optional[AstSenTree] // Sentree related to this await
// @astgen ptr := m_sentreep : Optional[AstSenTree] // Sentree related to this await
public:
AstCAwait(FileLine* fl, AstNodeExpr* exprp, AstSenTree* sensesp = nullptr)
AstCAwait(FileLine* fl, AstNodeExpr* exprp, AstSenTree* sentreep = nullptr)
: ASTGEN_SUPER_CAwait(fl, exprp)
, m_sensesp{sensesp} {}
, m_sentreep{sentreep} {}
ASTGEN_MEMBERS_AstCAwait;
bool isTimingControl() const override { return true; }
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
AstSenTree* sensesp() const { return m_sensesp; }
void clearSensesp() { m_sensesp = nullptr; }
AstSenTree* sentreep() const { return m_sentreep; }
void clearSentreep() { m_sentreep = nullptr; }
void numberOperate(V3Number& out, const V3Number& lhs) override { V3ERROR_NA; }
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
string emitC() override { V3ERROR_NA_RETURN(""); }
@ -4958,6 +5060,8 @@ public:
dtypeSetLogicUnsized(setwidth, minwidth, VSigning::UNSIGNED);
}
}
// cppcheck-suppress constParameterPointer
// cppcheck-suppress constParameterCallback
AstCCast(FileLine* fl, AstNodeExpr* lhsp, AstNode* typeFromp)
: ASTGEN_SUPER_CCast(fl, lhsp) {
dtypeFrom(typeFromp);
@ -5496,6 +5600,20 @@ public:
bool cleanLhs() const override { return true; }
bool sizeMattersLhs() const override { return false; }
};
class AstToStringN final : public AstNodeUniop {
public:
AstToStringN(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_ToStringN(fl, lhsp) {
dtypeSetString();
}
ASTGEN_MEMBERS_AstToStringN;
void numberOperate(V3Number& out, const V3Number& lhs) override { V3ERROR_NA; }
string emitVerilog() override { return "$sformatf(\"%p\", %l)"; }
string emitC() override { return isWide() ? "VL_TO_STRING_W(%nw, %li)" : "VL_TO_STRING(%li)"; }
bool cleanOut() const override { return true; }
bool cleanLhs() const override { return true; }
bool sizeMattersLhs() const override { return false; }
};
class AstToUpperN final : public AstNodeUniop {
// string.toupper()
public:
@ -5750,7 +5868,7 @@ public:
const char* broken() const override;
bool sameNode(const AstNode* samep) const override;
inline bool sameNode(const AstVarRef* samep) const;
inline bool sameNoLvalue(AstVarRef* samep) const;
inline bool sameNoLvalue(const AstVarRef* samep) const;
int instrCount() const override;
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
string emitC() override { V3ERROR_NA_RETURN(""); }

File diff suppressed because it is too large Load Diff

1320
src/V3AstNodeStmt.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -87,6 +87,7 @@ bool AstNodeFTaskRef::isPure() {
}
bool AstNodeFTaskRef::getPurityRecurse() const {
// cppcheck-suppress shadowFunction
AstNodeFTask* const taskp = this->taskp();
// Unlinked yet, so treat as impure
if (!taskp) return false;
@ -166,9 +167,8 @@ const char* AstNodeQuadop::broken() const {
return nullptr;
}
AstNodeCond::AstNodeCond(VNType t, FileLine* fl, AstNodeExpr* condp, AstNodeExpr* thenp,
AstNodeExpr* elsep)
: AstNodeTriop{t, fl, condp, thenp, elsep} {
AstCond::AstCond(FileLine* fl, AstNodeExpr* condp, AstNodeExpr* thenp, AstNodeExpr* elsep)
: ASTGEN_SUPER_Cond(fl, condp, thenp, elsep) {
UASSERT_OBJ(thenp, this, "No thenp expression");
UASSERT_OBJ(elsep, this, "No elsep expression");
if (thenp->isClassHandleValue() && elsep->isClassHandleValue()) {
@ -180,13 +180,11 @@ AstNodeCond::AstNodeCond(VNType t, FileLine* fl, AstNodeExpr* condp, AstNodeExpr
dtypeFrom(thenp);
}
}
void AstNodeCond::numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs,
const V3Number& ths) {
if (lhs.isNeqZero()) {
out.opAssign(rhs);
} else {
out.opAssign(ths);
}
void AstAddrOfCFunc::dump(std::ostream& str) const {
this->AstNodeExpr::dump(str);
str << " -> ";
funcp()->dump(str);
}
void AstBasicDType::init(VBasicDTypeKwd kwd, VSigning numer, int wantwidth, int wantwidthmin,
@ -311,6 +309,16 @@ AstNodeBiop* AstEqWild::newTyped(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* r
}
}
AstNodeBiop* AstNeq::newTyped(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) {
if (lhsp->isString() && rhsp->isString()) {
return new AstNeqN{fl, lhsp, rhsp};
} else if (lhsp->isDouble() && rhsp->isDouble()) {
return new AstNeqD{fl, lhsp, rhsp};
} else {
return new AstNeq{fl, lhsp, rhsp};
}
}
AstExecGraph::AstExecGraph(FileLine* fileline, const string& name) VL_MT_DISABLED
: ASTGEN_SUPER_ExecGraph(fileline),
m_depGraphp{new V3Graph},
@ -483,7 +491,20 @@ bool AstVar::isScBigUint() const {
return ((isSc() && v3Global.opt.pinsScBigUint() && width() >= 65 && width() <= 512)
&& !isScBv());
}
void AstVar::combineType(const AstVar* otherp) {
// "this" is the port var. otherp is the reg var, or vice-versa
propagateAttrFrom(otherp);
combineType(otherp->varType());
if (otherp->isSigPublic()) sigPublic(true);
if (otherp->isSigModPublic()) sigModPublic(true);
if (otherp->isSigUserRdPublic()) sigUserRdPublic(true);
if (otherp->isSigUserRWPublic()) sigUserRWPublic(true);
if (otherp->attrScClocked()) attrScClocked(true);
if (otherp->varType() == VVarType::PORT) {
varType(otherp->varType());
direction(otherp->direction());
}
}
void AstVar::combineType(VVarType type) {
// These flags get combined with the existing settings of the flags.
// We don't test varType for certain types, instead set flags since
@ -522,18 +543,16 @@ string AstVar::vlArgType(bool named, bool forReturn, bool forFunc, const string&
string ostatic;
if (isStatic() && namespc.empty()) ostatic = "static ";
const bool isRef = isDpiOpenArray()
|| (forFunc && (isWritable() || this->isRef() || this->isConstRef()))
|| asRef;
asRef = asRef || isDpiOpenArray() || (forFunc && (isWritable() || isRef() || isConstRef()));
if (forFunc && isReadOnly() && isRef) ostatic = ostatic + "const ";
if (forFunc && isReadOnly() && asRef) ostatic = ostatic + "const ";
string oname;
if (named) {
if (!namespc.empty()) oname += namespc + "::";
oname += VIdProtect::protectIf(name(), protect());
}
return ostatic + dtypep()->cType(oname, forFunc, isRef);
return ostatic + dtypep()->cType(oname, forFunc, asRef);
}
string AstVar::vlEnumType() const {
@ -667,7 +686,7 @@ string AstVar::cPubArgType(bool named, bool forReturn) const {
if (forReturn) named = false;
string arg;
if (isWide() && isReadOnly()) arg += "const ";
const bool isRef = !forReturn && (isWritable() || this->isRef() || this->isConstRef());
const bool asRef = !forReturn && (isWritable() || this->isRef() || this->isConstRef());
if (VN_IS(dtypeSkipRefp(), BasicDType) && !dtypeSkipRefp()->isDouble()
&& !dtypeSkipRefp()->isString()) {
// Backward compatible type declaration
@ -688,12 +707,12 @@ string AstVar::cPubArgType(bool named, bool forReturn) const {
arg += " (& " + name();
arg += ")[" + cvtToStr(widthWords()) + "]";
} else {
if (isRef) arg += "&";
if (asRef) arg += "&";
if (named) arg += " " + name();
}
} else {
// Newer internal-compatible types
arg += dtypep()->cType((named ? name() : std::string{}), true, isRef);
arg += dtypep()->cType((named ? name() : std::string{}), true, asRef);
}
return arg;
}
@ -755,7 +774,7 @@ string AstVar::dpiTmpVarType(const string& varName) const {
class converter final : public dpiTypesToStringConverter {
const string m_name;
string arraySuffix(const AstVar* varp, size_t n) const {
if (AstUnpackArrayDType* const unpackp
if (const AstUnpackArrayDType* const unpackp
= VN_CAST(varp->dtypep()->skipRefp(), UnpackArrayDType)) {
// Convert multi dimensional unpacked array to 1D array
if (n == 0) n = 1;
@ -812,6 +831,8 @@ string AstVar::scType() const {
} else {
return "uint32_t";
}
} else if (isDouble()) {
return "double";
} else {
return "uint64_t";
}
@ -827,13 +848,13 @@ AstVar* AstVar::scVarRecurse(AstNode* nodep) {
} else {
return nullptr;
}
} else if (AstVarRef* const vrefp = VN_CAST(nodep, VarRef)) {
} else if (const AstVarRef* const vrefp = VN_CAST(nodep, VarRef)) {
if (vrefp->varp()->isSc()) {
return vrefp->varp();
} else {
return nullptr;
}
} else if (AstArraySel* const arraySelp = VN_CAST(nodep, ArraySel)) {
} else if (const AstArraySel* const arraySelp = VN_CAST(nodep, ArraySel)) {
if (AstVar* const p = scVarRecurse(arraySelp->fromp())) return p;
}
return nullptr;
@ -933,10 +954,10 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound, bool packe
info.m_type = "VlSampleQueue<" + sub.m_type + ">";
} else if (const auto* const adtypep = VN_CAST(dtypep, ClassRefDType)) {
UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union");
info.m_type = "VlClassRef<" + EmitCBase::prefixNameProtect(adtypep) + ">";
info.m_type = "VlClassRef<" + EmitCUtil::prefixNameProtect(adtypep) + ">";
} else if (const auto* const adtypep = VN_CAST(dtypep, IfaceRefDType)) {
UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union");
info.m_type = EmitCBase::prefixNameProtect(adtypep->ifaceViaCellp()) + "*";
info.m_type = EmitCUtil::prefixNameProtect(adtypep->ifaceViaCellp()) + "*";
} else if (const auto* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) {
UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union");
if (adtypep->isCompound()) compound = true;
@ -970,7 +991,7 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound, bool packe
const AstNodeUOrStructDType* const sdtypep = VN_AS(dtypep, NodeUOrStructDType);
UASSERT_OBJ(!packed || sdtypep->packed(), this,
"Unsupported type for packed struct or union");
info.m_type = EmitCBase::prefixNameProtect(sdtypep);
info.m_type = EmitCUtil::prefixNameProtect(sdtypep);
} else if (const AstBasicDType* const bdtypep = dtypep->basicp()) {
// We don't print msb()/lsb() as multidim packed would require recursion,
// and may confuse users as C++ data is stored always with bit 0 used
@ -1001,6 +1022,8 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound, bool packe
info.m_type = "VlProcessRef";
} else if (bdtypep->isRandomGenerator()) {
info.m_type = "VlRandomizer";
} else if (bdtypep->isStdRandomGenerator()) {
info.m_type = "VlStdRandomizer";
} else if (bdtypep->isEvent()) {
info.m_type = v3Global.assignsEvents() ? "VlAssignableEvent" : "VlEvent";
} else if (dtypep->widthMin() <= 8) { // Handle unpacked arrays; not bdtypep->width
@ -1076,6 +1099,7 @@ std::pair<uint32_t, uint32_t> AstNodeDType::dimensions(bool includeBasic) const
int AstNodeDType::widthPow2() const {
// I.e. width 30 returns 32, width 32 returns 32.
// cppcheck-suppress shadowFunction
const uint32_t width = this->width();
for (int p2 = 30; p2 >= 0; p2--) {
if (width > (1UL << p2)) return (1UL << (p2 + 1));
@ -1110,6 +1134,9 @@ AstNode* AstArraySel::baseFromp(AstNode* nodep, bool overMembers) {
} else if (overMembers && VN_IS(nodep, MemberSel)) {
nodep = VN_AS(nodep, MemberSel)->fromp();
continue;
} else if (overMembers && VN_IS(nodep, StructSel)) {
nodep = VN_AS(nodep, StructSel)->fromp();
continue;
}
// AstNodePreSel stashes the associated variable under an ATTROF
// of VAttrType::VAR_BASE so it isn't constified
@ -1130,10 +1157,6 @@ AstNode* AstArraySel::baseFromp(AstNode* nodep, bool overMembers) {
return nodep;
}
const char* AstJumpBlock::broken() const {
BROKEN_RTN(!labelp()->brokeExistsBelow());
return nullptr;
}
bool AstJumpBlock::isPure() {
if (!m_purity.isCached()) m_purity.set(getPurityRecurse());
return m_purity.get();
@ -1171,7 +1194,7 @@ AstVarScope* AstScope::createTemp(const string& name, AstNodeDType* dtypep) {
return vscp;
}
AstVarScope* AstScope::createTempLike(const string& name, AstVarScope* vscp) {
AstVarScope* AstScope::createTempLike(const string& name, const AstVarScope* vscp) {
return createTemp(name, vscp->dtypep());
}
@ -1231,7 +1254,7 @@ bool AstSenTree::hasFinal() const {
bool AstSenTree::hasCombo() const {
UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
if (senp->isCombo()) return true;
if (senp->isComboOrStar()) return true;
}
return false;
}
@ -1353,6 +1376,7 @@ AstBasicDType* AstTypeTable::findLogicBitDType(FileLine* fl, VBasicDTypeKwd kwd,
return newp;
}
// cppcheck-suppress duplInheritedMember
AstBasicDType* AstTypeTable::findInsertSameDType(AstBasicDType* nodep) {
const VBasicTypeKey key{nodep->width(), nodep->widthMin(), nodep->numeric(), nodep->keyword(),
nodep->nrange()};
@ -1441,7 +1465,7 @@ AstVarScope* AstConstPool::findTable(AstInitArray* initp) {
UASSERT_OBJ(VN_IS(valuep, Const), valuep, "Const pool table entry must be Const");
}
// Try to find an existing table with the same content
// cppcheck-has-bug-suppress unreadVariable
// cppcheck-suppress unreadVariable
const V3Hash hash = V3Hasher::uncachedHash(initp);
const auto& er = m_tables.equal_range(hash.value());
for (auto it = er.first; it != er.second; ++it) {
@ -1471,7 +1495,7 @@ static bool sameInit(const AstConst* ap, const AstConst* bp) {
AstVarScope* AstConstPool::findConst(AstConst* initp, bool mergeDType) {
// Try to find an existing constant with the same value
// cppcheck-has-bug-suppress unreadVariable
// cppcheck-suppress unreadVariable
const V3Hash hash = initp->num().toHash();
const auto& er = m_consts.equal_range(hash.value());
for (auto it = er.first; it != er.second; ++it) {
@ -1509,12 +1533,7 @@ void AstNodeStmt::addNextStmt(AstNode* newp, AstNode*) {
void AstWhile::addNextStmt(AstNode* newp, AstNode* belowp) {
// Special, as statements need to be put in different places
// Belowp is how we came to recurse up to this point
// Preconditions insert first just before themselves (the normal rule
// for other statement types)
if (belowp == precondsp()) {
// Next in precond list
belowp->addNextHere(newp);
} else if (belowp == condp()) {
if (belowp == condp()) {
// Becomes first statement in body, body may have been empty
if (stmtsp()) {
stmtsp()->addHereThisAsNext(newp);
@ -1550,7 +1569,7 @@ void AstNode::dump(std::ostream& str) const {
} else {
str << " @dt=" << nodeAddr(dtypep()) << "@";
}
if (AstNodeDType* const dtp = dtypep()) dtp->dumpSmall(str);
if (const AstNodeDType* const dtp = dtypep()) dtp->dumpSmall(str);
} else { // V3Broken will throw an error
if (dtypep()) str << " %Error-dtype-exp=null,got=" << nodeAddr(dtypep());
}
@ -1692,6 +1711,20 @@ void AstCCast::dumpJson(std::ostream& str) const {
dumpJsonNumFunc(str, size);
dumpJsonGen(str);
}
void AstCvtArrayToArray::dump(std::ostream& str) const {
this->AstNodeExpr::dump(str);
str << " reverse=" << reverse();
str << " blockSize=" << blockSize();
str << " dstElementBits=" << dstElementBits();
str << " srcElementBits=" << srcElementBits();
}
void AstCvtArrayToArray::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, reverse);
dumpJsonNumFunc(str, blockSize);
dumpJsonNumFunc(str, dstElementBits);
dumpJsonNumFunc(str, srcElementBits);
dumpJsonGen(str);
}
void AstCell::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (recursive()) str << " [RECURSIVE]";
@ -1753,6 +1786,7 @@ void AstClass::dump(std::ostream& str) const {
if (useVirtualPublic()) str << " [VIRPUB]";
}
void AstClass::dumpJson(std::ostream& str) const {
// dumpJsonNumFunc(str, declTokenNum); // Not dumped as adding token changes whole file
dumpJsonBoolFunc(str, isExtended);
dumpJsonBoolFunc(str, isInterfaceClass);
dumpJsonBoolFunc(str, isVirtual);
@ -1973,21 +2007,6 @@ AstNodeExpr* AstInitArray::getIndexDefaultedValuep(uint64_t index) const {
}
void AstJumpGo::dump(std::ostream& str) const {
this->AstNodeStmt::dump(str);
str << " -> ";
if (labelp()) {
labelp()->dump(str);
} else {
str << "%E:UNLINKED";
}
}
void AstJumpGo::dumpJson(std::ostream& str) const { dumpJsonGen(str); }
const char* AstJumpGo::broken() const {
BROKEN_RTN(!labelp()->brokeExistsBelow());
return nullptr;
}
void AstJumpLabel::dump(std::ostream& str) const {
this->AstNodeStmt::dump(str);
str << " -> ";
if (blockp()) {
@ -1996,7 +2015,11 @@ void AstJumpLabel::dump(std::ostream& str) const {
str << "%E:UNLINKED";
}
}
void AstJumpLabel::dumpJson(std::ostream& str) const { dumpJsonGen(str); }
void AstJumpGo::dumpJson(std::ostream& str) const { dumpJsonGen(str); }
const char* AstJumpGo::broken() const {
BROKEN_RTN(!blockp()->brokeExistsAbove());
return nullptr;
}
void AstMemberDType::dump(std::ostream& str) const {
this->AstNodeDType::dump(str);
@ -2035,7 +2058,7 @@ AstMemberSel::AstMemberSel(FileLine* fl, AstNodeExpr* fromp, AstVar* varp)
bool AstMemberSel::sameNode(const AstNode* samep) const {
const AstMemberSel* const sp = VN_DBG_AS(samep, MemberSel);
return sp && access() == sp->access() && fromp()->isSame(sp->fromp()) && name() == sp->name()
&& (varp() && sp->varp() && varp()->sameNode(sp->varp()));
&& (varp() == sp->varp() || (varp() && sp->varp() && varp()->sameNode(sp->varp())));
}
void AstMemberSel::dump(std::ostream& str) const {
@ -2082,10 +2105,12 @@ void AstModule::dump(std::ostream& str) const {
this->AstNodeModule::dump(str);
if (isChecker()) str << " [CHECKER]";
if (isProgram()) str << " [PROGRAM]";
if (hasGenericIface()) str << " [HASGENERICIFACE]";
}
void AstModule::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isChecker);
dumpJsonBoolFunc(str, isProgram);
dumpJsonBoolFunc(str, hasGenericIface);
dumpJsonGen(str);
}
void AstPin::dump(std::ostream& str) const {
@ -2148,13 +2173,24 @@ void AstTimeImport::dumpJson(std::ostream& str) const {
void AstTypedef::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (attrPublic()) str << " [PUBLIC]";
if (isUnderClass()) str << " [UNDCLS]";
if (subDTypep()) {
str << " -> ";
subDTypep()->dump(str);
}
}
void AstTypedef::dumpJson(std::ostream& str) const {
// dumpJsonNumFunc(str, declTokenNum); // Not dumped as adding token changes whole file
dumpJsonBoolFunc(str, attrPublic);
dumpJsonBoolFunc(str, isUnderClass);
dumpJsonGen(str);
}
void AstTypedefFwd::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " [" << fwdType().ascii() << "]";
}
void AstTypedefFwd::dumpJson(std::ostream& str) const {
dumpJsonStr(str, "fwdType", fwdType().ascii());
dumpJsonGen(str);
}
void AstNodeRange::dump(std::ostream& str) const { this->AstNode::dump(str); }
@ -2184,10 +2220,10 @@ void AstRefDType::dump(std::ostream& str) const {
if (!s_recursing) { // Prevent infinite dump if circular typedefs
s_recursing = true;
str << " -> ";
if (const auto subp = subDTypep()) {
if (const AstNodeDType* const subp = subDTypep()) {
if (typedefp()) str << "typedef=" << static_cast<void*>(typedefp()) << " -> ";
subp->dump(str);
} else if (const auto subp = typedefp()) {
} else if (const AstTypedef* const subp = typedefp()) {
subp->dump(str);
}
s_recursing = false;
@ -2231,7 +2267,7 @@ string AstNodeUOrStructDType::prettyDTypeName(bool full) const {
void AstNodeDType::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (generic()) str << " [GENERIC]";
if (AstNodeDType* const dtp = virtRefDTypep()) {
if (const AstNodeDType* const dtp = virtRefDTypep()) {
str << " refdt=" << nodeAddr(dtp);
dtp->dumpSmall(str);
}
@ -2307,6 +2343,25 @@ void AstNetlist::dumpJson(std::ostream& str) const {
dumpJsonStr(str, "timeprecision", timeprecision().ascii());
dumpJsonGen(str);
}
void AstNetlist::deleteContents() {
// Delete all netlist memory. Only for use by Verilator.cpp
m_typeTablep = nullptr;
m_constPoolp = nullptr;
m_dollarUnitPkgp = nullptr;
m_stdPackagep = nullptr;
m_evalp = nullptr;
m_evalNbap = nullptr;
m_dpiExportTriggerp = nullptr;
m_delaySchedulerp = nullptr;
m_nbaEventp = nullptr;
m_nbaEventTriggerp = nullptr;
m_topScopep = nullptr;
if (op1p()) op1p()->unlinkFrBackWithNext()->deleteTree();
if (op2p()) op2p()->unlinkFrBackWithNext()->deleteTree();
if (op3p()) op3p()->unlinkFrBackWithNext()->deleteTree();
if (op4p()) op4p()->unlinkFrBackWithNext()->deleteTree();
#undef VN_DELETE_ONE
}
AstPackage* AstNetlist::dollarUnitPkgAddp() {
if (!m_dollarUnitPkgp) {
m_dollarUnitPkgp = new AstPackage{fileline(), AstPackage::dollarUnitName(), "work"};
@ -2424,7 +2479,7 @@ void AstMTaskBody::dumpJson(std::ostream& str) const {
void AstTypeTable::dump(std::ostream& str) const {
this->AstNode::dump(str);
for (int i = 0; i < static_cast<int>(VBasicDTypeKwd::_ENUM_MAX); ++i) {
if (AstBasicDType* const subnodep = m_basicps[i]) {
if (const AstBasicDType* const subnodep = m_basicps[i]) {
str << '\n'; // Newline from caller, so newline first
str << "\t\t" << std::setw(8) << VBasicDTypeKwd{i}.ascii();
str << " -> ";
@ -2434,7 +2489,7 @@ void AstTypeTable::dump(std::ostream& str) const {
{
const DetailedMap& mapr = m_detailedMap;
for (const auto& itr : mapr) {
AstBasicDType* const dtypep = itr.second;
const AstBasicDType* const dtypep = itr.second;
str << '\n'; // Newline from caller, so newline first
str << "\t\tdetailed -> ";
dtypep->dump(str);
@ -2504,6 +2559,10 @@ void AstVoidDType::dumpSmall(std::ostream& str) const {
this->AstNodeDType::dumpSmall(str);
str << "void";
}
void AstIfaceGenericDType::dumpSmall(std::ostream& str) const {
this->AstNodeDType::dumpSmall(str);
str << "generic_interface";
}
void AstStreamDType::dumpSmall(std::ostream& str) const {
this->AstNodeDType::dumpSmall(str);
str << "stream";
@ -2539,15 +2598,21 @@ void AstNodeVarRef::dumpJson(std::ostream& str) const {
AstNodeVarRef* AstNodeVarRef::varRefLValueRecurse(AstNode* nodep) {
// Given a (possible) lvalue expression, recurse to find the being-set NodeVarRef, else nullptr
if (AstNodeVarRef* const anodep = VN_CAST(nodep, NodeVarRef)) return anodep;
if (AstNodeSel* const anodep = VN_CAST(nodep, NodeSel))
if (const AstNodeSel* const anodep = VN_CAST(nodep, NodeSel)) {
return varRefLValueRecurse(anodep->fromp());
if (AstSel* const anodep = VN_CAST(nodep, Sel)) return varRefLValueRecurse(anodep->fromp());
if (AstArraySel* const anodep = VN_CAST(nodep, ArraySel))
}
if (const AstSel* const anodep = VN_CAST(nodep, Sel)) {
return varRefLValueRecurse(anodep->fromp());
if (AstMemberSel* const anodep = VN_CAST(nodep, MemberSel))
}
if (const AstArraySel* const anodep = VN_CAST(nodep, ArraySel)) {
return varRefLValueRecurse(anodep->fromp());
if (AstStructSel* const anodep = VN_CAST(nodep, StructSel))
}
if (const AstMemberSel* const anodep = VN_CAST(nodep, MemberSel)) {
return varRefLValueRecurse(anodep->fromp());
}
if (const AstStructSel* const anodep = VN_CAST(nodep, StructSel)) {
return varRefLValueRecurse(anodep->fromp());
}
return nullptr;
}
@ -2604,6 +2669,8 @@ void AstVar::dump(std::ostream& str) const {
if (isPulldown()) str << " [PULLDOWN]";
if (isUsedClock()) str << " [CLK]";
if (isSigPublic()) str << " [P]";
if (isSigUserRdPublic()) str << " [PRD]";
if (isSigUserRWPublic()) str << " [PWR]";
if (isInternal()) str << " [INTERNAL]";
if (isLatched()) str << " [LATCHED]";
if (isUsedLoopIdx()) str << " [LOOP]";
@ -2742,9 +2809,12 @@ AstNodeModule* AstClassOrPackageRef::classOrPackageSkipp() const {
if (AstNodeDType* const anodep = VN_CAST(foundp, NodeDType)) {
foundp = anodep->skipRefOrNullp();
}
if (AstTypedef* const anodep = VN_CAST(foundp, Typedef)) foundp = anodep->subDTypep();
if (AstClassRefDType* const anodep = VN_CAST(foundp, ClassRefDType))
if (const AstTypedef* const anodep = VN_CAST(foundp, Typedef)) {
foundp = anodep->subDTypep();
}
if (const AstClassRefDType* const anodep = VN_CAST(foundp, ClassRefDType)) {
foundp = anodep->classp();
}
}
return VN_CAST(foundp, NodeModule);
}
@ -2760,8 +2830,8 @@ void AstDot::dumpJson(std::ostream& str) const {
void AstActive::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " => ";
if (sensesp()) {
sensesp()->dump(str);
if (sentreep()) {
sentreep()->dump(str);
} else {
str << "UNLINKED";
}
@ -2859,10 +2929,9 @@ void AstBegin::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, needProcess);
dumpJsonGen(str);
}
void AstCoverDecl::dump(std::ostream& str) const {
this->AstNodeStmt::dump(str);
void AstNodeCoverDecl::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (!page().empty()) str << " page=" << page();
if (!linescov().empty()) str << " lc=" << linescov();
if (this->dataDeclNullp()) {
static bool s_recursing = false;
str << " -> ";
@ -2877,12 +2946,30 @@ void AstCoverDecl::dump(std::ostream& str) const {
if (binNum()) str << " bin" << std::dec << binNum();
}
}
void AstCoverDecl::dumpJson(std::ostream& str) const {
void AstNodeCoverDecl::dumpJson(std::ostream& str) const {
dumpJsonStrFunc(str, page);
dumpJsonStrFunc(str, linescov);
dumpJsonNumFunc(str, binNum);
dumpJsonGen(str);
}
void AstCoverOtherDecl::dump(std::ostream& str) const {
this->AstNodeCoverDecl::dump(str);
if (!linescov().empty()) str << " lc=" << linescov();
}
void AstCoverOtherDecl::dumpJson(std::ostream& str) const {
this->AstNodeCoverDecl::dumpJson(str);
dumpJsonStrFunc(str, linescov);
}
void AstCoverToggleDecl::dump(std::ostream& str) const {
this->AstNodeCoverDecl::dump(str);
if (range().ranged()) str << " range=[" << range().left() << ":" << range().right() << "]";
}
void AstCoverToggleDecl::dumpJson(std::ostream& str) const {
this->AstNodeCoverDecl::dumpJson(str);
if (range().ranged()) {
dumpJsonStr(str, "range",
std::to_string(range().left()) + ":" + std::to_string(range().right()));
}
}
void AstCoverInc::dump(std::ostream& str) const {
this->AstNodeStmt::dump(str);
str << " -> ";
@ -2998,14 +3085,14 @@ void AstCFunc::dumpJson(std::ostream& str) const {
}
void AstCAwait::dump(std::ostream& str) const {
this->AstNodeUniop::dump(str);
if (sensesp()) {
if (sentreep()) {
str << " => ";
sensesp()->dump(str);
sentreep()->dump(str);
}
}
void AstCAwait::dumpJson(std::ostream& str) const { dumpJsonGen(str); }
int AstCMethodHard::instrCount() const {
if (AstBasicDType* const basicp = fromp()->dtypep()->basicp()) {
if (const AstBasicDType* const basicp = fromp()->dtypep()->basicp()) {
// TODO: add a more structured description of library methods, rather than using string
// matching. See issue #3715.
if (basicp->isTriggerVec() && m_name == "word") {
@ -3088,13 +3175,14 @@ void AstCMethodHard::setPurity() {
{"unique", true},
{"unique_index", true},
{"word", true},
{"write_var", false}};
{"write_var", false},
{"basicStdRandomization", false}};
if (name() == "atWriteAppend" || name() == "atWriteAppendBack") {
m_pure = false;
// Treat atWriteAppend as pure if the argument is a loop iterator
if (AstNodeExpr* const argp = pinsp()) {
if (AstVarRef* const varrefp = VN_CAST(argp, VarRef)) {
if (const AstNodeExpr* const argp = pinsp()) {
if (const AstVarRef* const varrefp = VN_CAST(argp, VarRef)) {
if (varrefp->varp()->isUsedLoopIdx()) m_pure = true;
}
}
@ -3177,6 +3265,20 @@ void AstDelay::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isCycleDelay);
dumpJsonGen(str);
}
const char* AstDisable::broken() const {
BROKEN_RTN((m_targetp && targetRefp()) || ((!m_targetp && !targetRefp())));
return nullptr;
}
void AstDisable::dump(std::ostream& str) const {
this->AstNodeStmt::dump(str);
str << " -> ";
if (targetp()) {
targetp()->dump(str);
} else {
str << "UNLINKED";
}
}
const char* AstAnd::widthMismatch() const VL_MT_STABLE {
BROKEN_RTN(lhsp()->widthMin() != rhsp()->widthMin());
BROKEN_RTN(lhsp()->widthMin() != widthMin());

View File

@ -289,7 +289,7 @@ class BeginVisitor final : public VNVisitor {
}
iterateChildren(nodep);
}
void visit(AstCoverDecl* nodep) override {
void visit(AstNodeCoverDecl* nodep) override {
// Don't need to fix path in coverage statements, they're not under
// any BEGINs, but V3Coverage adds them all under the module itself.
iterateChildren(nodep);
@ -423,7 +423,7 @@ static AstNode* createForeachLoopRanged(AstNodeForeach* nodep, AstNode* bodysp,
: VNType::atGteS);
}
AstNode* V3Begin::convertToWhile(AstForeach* nodep) {
// if (debug()) dumpTree("- foreach-old: ");
// UINFOTREE(1, nodep, "", "foreach-old");
const AstSelLoopVars* const loopsp = VN_CAST(nodep->arrayp(), SelLoopVars);
UASSERT_OBJ(loopsp, nodep, "No loop variables under foreach");
AstNodeExpr* const fromp = loopsp->fromp();
@ -455,7 +455,7 @@ AstNode* V3Begin::convertToWhile(AstForeach* nodep) {
lastp->unlinkFrBack(&handle);
if (const AstNodeArrayDType* const adtypep = VN_CAST(fromDtp, NodeArrayDType)) {
loopp = createForeachLoopRanged(nodep, bodyPointp, varp, adtypep->declRange());
} else if (AstBasicDType* const adtypep = VN_CAST(fromDtp, BasicDType)) {
} else if (const AstBasicDType* const adtypep = VN_CAST(fromDtp, BasicDType)) {
if (adtypep->isString()) {
AstConst* const leftp = new AstConst{fl, 0};
AstNodeExpr* const rightp = new AstLenN{fl, fromp->cloneTreePure(false)};
@ -530,7 +530,11 @@ AstNode* V3Begin::convertToWhile(AstForeach* nodep) {
}
// The parser validates we don't have "foreach (array[,,,])"
AstNode* const bodyp = nodep->stmtsp();
UASSERT_OBJ(newp, nodep, "foreach has no non-empty loop variable");
if (!newp) {
nodep->v3warn(NOEFFECT, "foreach with no loop variable has no effect");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return nullptr;
}
if (bodyp) {
bodyPointp->replaceWith(bodyp->unlinkFrBackWithNext());
} else {
@ -538,6 +542,6 @@ AstNode* V3Begin::convertToWhile(AstForeach* nodep) {
}
VL_DO_DANGLING(bodyPointp->deleteTree(), bodyPointp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
// if (debug()) newp->dumpTreeAndNext(cout, "- foreach-new: ");
// UINFOTREE(1, newp, "", "foreach-new");
return newp;
}

View File

@ -51,7 +51,7 @@ class BranchVisitor final : public VNVisitorConst {
m_likely = false;
m_unlikely = false;
}
void checkUnlikely(AstNode* nodep) {
void checkUnlikely(const AstNode* nodep) {
if (nodep->isUnlikely()) {
UINFO(4, " UNLIKELY: " << nodep);
m_unlikely++;

View File

@ -67,7 +67,7 @@ class V3CCtorsBuilder final {
funcp->slow(!m_type.isClass()); // Only classes construct on fast path
string preventUnusedStmt;
if (m_type.isClass()) {
funcp->argTypes(EmitCBase::symClassVar());
funcp->argTypes(EmitCUtil::symClassVar());
preventUnusedStmt = "(void)vlSymsp; // Prevent unused variable warning\n";
} else if (m_type.isCoverage()) {
funcp->argTypes("bool first");
@ -161,10 +161,10 @@ class CCtorsVisitor final : public VNVisitor {
if (v3Global.opt.coverage()) {
V3CCtorsBuilder configure_coverage{nodep, "_configure_coverage", VCtorType::COVERAGE};
for (AstNode* np = nodep->stmtsp(); np; np = np->nextp()) {
if (AstCoverDecl* const coverp = VN_CAST(np, CoverDecl)) {
if (AstNodeCoverDecl* const coverp = VN_CAST(np, NodeCoverDecl)) {
// ... else we don't have a static VlSym to be able to coverage insert
UASSERT_OBJ(!VN_IS(nodep, Class), coverp,
"CoverDecl should be in class's package, not class itself");
"NodeCoverDecl should be in class's package, not class itself");
np = coverp->backp();
configure_coverage.add(coverp->unlinkFrBack());
}

View File

@ -43,7 +43,7 @@ class CUseVisitor final : public VNVisitorConst {
std::map<std::string, std::pair<FileLine*, VUseType>> m_didUse; // What we already used
// METHODS
void addNewUse(AstNode* nodep, VUseType useType, const string& name) {
void addNewUse(const AstNode* nodep, VUseType useType, const string& name) {
auto e = m_didUse.emplace(name, std::make_pair(nodep->fileline(), useType));
if (e.second || ((e.first->second.second & useType) != useType)) {
e.first->second.second = e.first->second.second | useType;
@ -73,7 +73,8 @@ class CUseVisitor final : public VNVisitorConst {
if (stypep && stypep->classOrPackagep()) {
addNewUse(nodep, VUseType::INT_INCLUDE, stypep->classOrPackagep()->name());
iterateChildrenConst(stypep);
} else if (AstClassRefDType* const classp = VN_CAST(nodep->skipRefp(), ClassRefDType)) {
} else if (const AstClassRefDType* const classp
= VN_CAST(nodep->skipRefp(), ClassRefDType)) {
addNewUse(nodep, VUseType::INT_FWD_CLASS, classp->name());
}
}
@ -95,7 +96,7 @@ public:
: m_modp{modp} {
iterateConst(modp);
for (auto& used : m_didUse) {
for (const auto& used : m_didUse) {
AstCUse* const newp = new AstCUse{used.second.first, used.second.second, used.first};
m_modp->addStmtsp(newp);
UINFO(8, "Insert " << newp);

View File

@ -201,7 +201,7 @@ class CaseVisitor final : public VNVisitor {
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), CaseItem)) {
for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) {
// if (debug() >= 9) icondp->dumpTree("- caseitem: ");
// UINFOTREE(9, icondp, "", "caseitem");
AstConst* const iconstp = VN_AS(icondp, Const);
UASSERT_OBJ(iconstp, nodep, "above 'can't parse' should have caught this");
if (neverItem(nodep, iconstp)) {
@ -216,7 +216,7 @@ class CaseVisitor final : public VNVisitor {
const uint32_t val = numval.toUInt();
uint32_t firstOverlap = 0;
AstNode* overlappedCondp = nullptr;
const AstNode* overlappedCondp = nullptr;
bool foundHit = false;
for (uint32_t i = 0; i < numCases; ++i) {
if ((i & mask) == val) {
@ -307,7 +307,7 @@ class CaseVisitor final : public VNVisitor {
// Not done earlier, as we may now have a nullptr because it's just a ";" NOP branch
for (uint32_t i = 0; i < numCases; ++i) {
if (AstNode* const condp = m_valueItem[i]) {
AstCaseItem* caseItemp = caseItemMap[condp];
const AstCaseItem* const caseItemp = caseItemMap[condp];
UASSERT(caseItemp, "caseItemp should exist");
m_valueItem[i] = caseItemp->stmtsp();
}
@ -325,7 +325,7 @@ class CaseVisitor final : public VNVisitor {
} else {
// Make left and right subtrees
// cexpr[msb:lsb] == 1
AstNode* tree0p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue | 0);
AstNode* tree0p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue);
AstNode* tree1p = replaceCaseFastRecurse(
cexprp, msb - 1, upperValue | (1UL << static_cast<uint32_t>(msb)));
@ -396,7 +396,7 @@ class CaseVisitor final : public VNVisitor {
}
VL_DO_DANGLING(nodep->deleteTree(), nodep);
VL_DO_DANGLING(cexprp->deleteTree(), cexprp);
if (debug() >= 9) ifrootp->dumpTree("- _simp: ");
UINFOTREE(9, ifrootp, "", "_simp");
}
void replaceCaseComplicated(AstCase* nodep) {
@ -407,7 +407,7 @@ class CaseVisitor final : public VNVisitor {
AstNodeExpr* const cexprp = nodep->exprp()->unlinkFrBack();
// We'll do this in two stages. First stage, convert the conditions to
// the appropriate IF AND terms.
if (debug() >= 9) nodep->dumpTree("- _comp_IN::: ");
UINFOTREE(9, nodep, "", "_comp_IN::");
bool hadDefault = false;
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), CaseItem)) {
@ -474,7 +474,7 @@ class CaseVisitor final : public VNVisitor {
nodep->addItemsp(new AstCaseItem{
nodep->fileline(), new AstConst{nodep->fileline(), AstConst::BitTrue{}}, nullptr});
}
if (debug() >= 9) nodep->dumpTree("- _comp_COND: ");
UINFOTREE(9, nodep, "", "_comp_COND");
// Now build the IF statement tree
// The tree can be quite huge. Pull ever group of 8 out, and make a OR tree.
// This reduces the depth for the bottom elements, at the cost of
@ -524,12 +524,12 @@ class CaseVisitor final : public VNVisitor {
itemnextp = newp;
}
}
if (debug() >= 9) nodep->dumpTree("- _comp_TREE: ");
UINFOTREE(9, nodep, "", "_comp_TREE");
// Handle any assertions
replaceCaseParallel(nodep, false);
// Replace the CASE... with IF...
if (debug() >= 9 && grouprootp) grouprootp->dumpTree("- _new: ");
if (grouprootp) {
UINFOTREE(9, grouprootp, "", "_new");
nodep->replaceWith(grouprootp);
} else {
nodep->unlinkFrBack();
@ -548,7 +548,7 @@ class CaseVisitor final : public VNVisitor {
}
}
bool neverItem(AstCase* casep, AstConst* itemp) {
bool neverItem(const AstCase* casep, const AstConst* itemp) {
// Xs in case or casez are impossible due to two state simulations
if (casep->casex()) {
} else if (casep->casez() || casep->caseInside()) {
@ -563,7 +563,7 @@ class CaseVisitor final : public VNVisitor {
void visit(AstCase* nodep) override {
V3Case::caseLint(nodep);
iterateChildren(nodep);
if (debug() >= 9) nodep->dumpTree("- case_old: ");
UINFOTREE(9, nodep, "", "case_old");
if (isCaseTreeFast(nodep) && v3Global.opt.fCase()) {
// It's a simple priority encoder or complete statement
// we can make a tree of statements to avoid extra comparisons

View File

@ -64,12 +64,12 @@ class CastVisitor final : public VNVisitor {
= new AstCCast{nodep->fileline(), nodep, needsize, nodep->widthMin()};
UINFO(4, " MadeCast " << static_cast<void*>(castp) << " for " << nodep);
relinkHandle.relink(castp);
// if (debug() > 8) castp->dumpTree("- castins: ");
// UINFOTREE(9, castp, "", "castins");
//
ensureLower32Cast(castp);
nodep->user1(1); // Now must be of known size
}
static int castSize(AstNode* nodep) {
static int castSize(const AstNode* nodep) {
if (nodep->isQuad()) {
return VL_QUADSIZE;
} else if (nodep->width() <= 8) {
@ -85,6 +85,7 @@ class CastVisitor final : public VNVisitor {
if (!nodep->isNull()) insertCast(nodep, castSize(nodep->backp()));
}
}
// cppcheck-suppress constParameterPointer // lhsp might be changed
void ensureLower32Cast(AstCCast* nodep) {
// If we have uint64 = CAST(uint64(x)) then the upcasting
// really needs to be CAST(uint64(CAST(uint32(x))).
@ -115,7 +116,7 @@ class CastVisitor final : public VNVisitor {
if (nodep->sizeMattersLhs()) ensureCast(nodep->lhsp());
if (nodep->sizeMattersRhs()) ensureCast(nodep->rhsp());
}
void visit(AstNodeCond* nodep) override {
void visit(AstCond* nodep) override {
// All class types are castable to each other. If they are of different types,
// a compilation error will be thrown, so an explicit cast is required. Types were
// already checked by V3Width and dtypep of a condition operator is a type of their
@ -173,13 +174,13 @@ class CastVisitor final : public VNVisitor {
void visit(AstNegate* nodep) override {
iterateChildren(nodep);
nodep->user1(nodep->lhsp()->user1());
if (nodep->lhsp()->widthMin() == 1) {
if (nodep->lhsp()->widthMin() == 1 && !nodep->lhsp()->isWide()) {
// We want to avoid a GCC "converting of negative value" warning
// from our expansion of
// out = {32{a<b}} => out = - (a<b)
insertCast(nodep->lhsp(), castSize(nodep));
} else {
ensureCast(nodep->lhsp());
if (nodep->sizeMattersLhs()) ensureCast(nodep->lhsp());
}
}
void visit(AstVarRef* nodep) override {
@ -221,6 +222,11 @@ class CastVisitor final : public VNVisitor {
void visit(AstMemberSel* nodep) override {
iterateChildren(nodep);
ensureNullChecked(nodep->fromp());
nodep->user1(true);
}
void visit(AstStructSel* nodep) override {
iterateChildren(nodep);
nodep->user1(true);
}
// NOPs

321
src/V3Cfg.cpp Normal file
View File

@ -0,0 +1,321 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Control flow graph (CFG) implementation
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2025 by Wilson Snyder. This program is free software; you
// can redistribute it and/or modify it under the terms of either the GNU
// Lesser General Public License Version 3 or the Perl Artistic License
// Version 2.0.
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
//
// Control flow graph (CFG) implementation
//
//*************************************************************************
#include "config_build.h"
#include "verilatedos.h"
#include "V3Cfg.h"
#include "V3Ast.h"
#include "V3EmitV.h"
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// CfgBlock method definitions
std::string CfgBlock::name() const {
std::stringstream ss;
ss << "BB " + std::to_string(id()) + ":\n";
for (AstNode* nodep : m_stmtps) {
if (const AstIf* const ifp = VN_CAST(nodep, If)) {
ss << "if (";
V3EmitV::debugVerilogForTree(ifp->condp(), ss);
ss << ") ...";
} else if (const AstWhile* const whilep = VN_CAST(nodep, While)) {
ss << "while (";
V3EmitV::debugVerilogForTree(whilep->condp(), ss);
ss << ") ...";
} else {
V3EmitV::debugVerilogForTree(nodep, ss);
}
}
std::string text = VString::replaceSubstr(
VString::replaceSubstr(ss.str(), "\n", "\\l "), "\"", "\\\"");
if (isEnter()) text = "**ENTER**\n" + text;
if (isExit()) text = text + "\n**EXIT**";
return text;
}
std::string CfgBlock::dotShape() const { return "rect"; }
std::string CfgBlock::dotRank() const {
if (isEnter()) return "source";
if (isExit()) return "sink";
return "";
}
//######################################################################
// CfgEdge method definitions
std::string CfgEdge::dotLabel() const {
std::string label = "E" + std::to_string(id());
const CfgEdge* const untknp = srcp()->untknEdgep();
if (this == untknp) {
label += " / F";
} else if (untknp) {
label += " / T";
}
return label;
}
//######################################################################
// CfgGraph method definitions
static void cfgOrderVisitBlock(std::vector<CfgBlock*>& postOrderEnumeration, CfgBlock* bbp) {
// Mark visited
bbp->user(1);
// Visit un-visited successors
if (CfgBlock* const takenp = bbp->takenp()) {
if (!takenp->user()) cfgOrderVisitBlock(postOrderEnumeration, takenp);
if (CfgBlock* const untknp = bbp->untknp()) {
if (!untknp->user()) cfgOrderVisitBlock(postOrderEnumeration, untknp);
}
}
// Add to post order enumeration
postOrderEnumeration.emplace_back(bbp);
};
void CfgGraph::rpoBlocks() {
UASSERT_OBJ(m_nEdits != m_nLastOrdered, m_enterp, "Redundant 'CfgGraph::order' call");
m_nLastOrdered = m_nEdits;
// Reset marks
for (V3GraphVertex& v : vertices()) v.user(0);
// Compute post-order enumeration. Simple recursive algorith will do.
std::vector<CfgBlock*> postOrderEnumeration;
postOrderEnumeration.reserve(m_nBlocks);
cfgOrderVisitBlock(postOrderEnumeration, m_enterp);
UASSERT_OBJ(postOrderEnumeration.size() == m_nBlocks, m_enterp, "Inconsistent block count");
// Assign block IDs equal to the reverse post-order number and sort vertices
for (size_t i = 0; i < postOrderEnumeration.size(); ++i) {
CfgBlock* const bbp = postOrderEnumeration[m_nBlocks - 1 - i];
bbp->m_rpoNumber = i;
vertices().unlink(bbp);
vertices().linkBack(bbp);
}
// Assign edge IDs
size_t edgeCount = 0;
for (V3GraphVertex& v : vertices()) {
for (V3GraphEdge& e : v.outEdges()) static_cast<CfgEdge&>(e).m_id = edgeCount++;
}
UASSERT_OBJ(edgeCount == m_nEdges, m_enterp, "Inconsistent edge count");
}
bool CfgGraph::containsLoop() const {
for (const V3GraphVertex& vtx : vertices()) {
const CfgBlock& current = static_cast<const CfgBlock&>(vtx);
for (const V3GraphEdge& edge : current.outEdges()) {
const CfgBlock& successor = *static_cast<const CfgBlock*>(edge.top());
// IDs are the reverse post-order numbering, so easy to check for a back-edge
if (successor.id() < current.id()) return true;
}
}
return false;
}
void CfgGraph::minimize() {
// Remove empty blocks (except enter and exit)
for (V3GraphVertex* const vtxp : vertices().unlinkable()) {
CfgBlock* const bbp = static_cast<CfgBlock*>(vtxp);
if (bbp->isEnter()) continue;
if (bbp->isExit()) continue;
if (!bbp->stmtps().empty()) continue;
UASSERT(!bbp->isBranch(), "Empty block should have a single successor");
CfgBlock* const succp = bbp->takenp();
for (V3GraphEdge* const edgep : bbp->inEdges().unlinkable()) edgep->relinkTop(succp);
++m_nEdits;
--m_nEdges;
--m_nBlocks;
VL_DO_DANGLING(bbp->unlinkDelete(this), bbp);
}
// Combine sequential blocks
for (V3GraphVertex* const vtxp : vertices().unlinkable()) {
CfgBlock* const srcp = static_cast<CfgBlock*>(vtxp);
if (srcp->isExit()) continue;
if (srcp->isBranch()) continue;
CfgBlock* const dstp = srcp->takenp();
if (dstp->isJoin()) continue;
// Combine them
if (srcp->isEnter()) m_enterp = dstp;
std::vector<AstNodeStmt*> stmtps{std::move(srcp->m_stmtps)};
stmtps.reserve(stmtps.size() + dstp->m_stmtps.size());
stmtps.insert(stmtps.end(), dstp->m_stmtps.begin(), dstp->m_stmtps.end());
dstp->m_stmtps = std::move(stmtps);
for (V3GraphEdge* const edgep : srcp->inEdges().unlinkable()) edgep->relinkTop(dstp);
++m_nEdits;
--m_nEdges;
--m_nBlocks;
VL_DO_DANGLING(srcp->unlinkDelete(this), srcp);
}
if (m_nEdits != m_nLastOrdered) rpoBlocks();
if (dumpGraphLevel() >= 9) dumpDotFilePrefixed("cfg-minimize");
}
void CfgGraph::breakCriticalEdges() {
// Gather critical edges
std::vector<CfgEdge*> criticalEdges;
criticalEdges.reserve(m_nEdges);
for (V3GraphVertex& vtx : vertices()) {
const CfgBlock& bb = static_cast<const CfgBlock&>(vtx);
if (!bb.isBranch()) continue;
for (V3GraphEdge& edge : vtx.outEdges()) {
const CfgBlock& succ = static_cast<const CfgBlock&>(*edge.top());
if (!succ.isJoin()) continue;
criticalEdges.emplace_back(static_cast<CfgEdge*>(&edge));
}
}
// Insert blocks
for (CfgEdge* const edgep : criticalEdges) {
CfgBlock* const newp = addBlock();
addTakenEdge(newp, edgep->dstp());
edgep->relinkTop(newp);
}
if (m_nEdits != m_nLastOrdered) rpoBlocks();
if (dumpGraphLevel() >= 9) dumpDotFilePrefixed("cfg-breakCriticalEdges");
}
// Given a branching basic block, if the sub-graph below this branch, up until
// the point where all of its control flow path convertes is series-parallel,
// then return the (potentially newly created) basic block with exactly 2
// predecessors where the two control flow paths from this branch have joined.
// If the relevant sub-graph is not series-parallel (there is a control flow
// path between the branches, or to a path not dominated by the given branch),
// then return nullptr. Cached results in the given map
CfgBlock* CfgGraph::getOrCreateTwoWayJoinFor(CfgBlock* bbp) {
UASSERT_OBJ(bbp->isBranch(), bbp, "Not a branch");
// Mark visited
UASSERT_OBJ(!bbp->user(), bbp, "Should not visit twice");
bbp->user(1);
// We need the edge converting to a join block along both path. This is how we find it:
const auto chaseEdge = [&](CfgEdge* edgep) -> CfgEdge* {
while (true) {
CfgBlock* dstp = edgep->dstp();
// Stop if found the joining block along this path
if (dstp->isJoin()) return edgep;
// If the successor is a branch, recursively get it's 2-way join block
while (dstp->isBranch()) {
dstp = getOrCreateTwoWayJoinFor(dstp);
// If the subgarph below dstp is not series-parallel, then no solution
if (!dstp) return nullptr;
}
UASSERT_OBJ(!dstp->isExit(), bbp, "Non-convergent branch - multiple Exit blocks?");
edgep = dstp->takenEdgep();
}
};
// Walk down both paths
CfgEdge* const takenEdgep = chaseEdge(bbp->takenEdgep());
if (!takenEdgep) return nullptr;
CfgEdge* const untknEdgep = chaseEdge(bbp->untknEdgep());
if (!untknEdgep) return nullptr;
// If we ended up at different joining blocks, then there is a path from one
// of the branches into a path of another branch before 'bbp', no solution
if (takenEdgep->dstp() != untknEdgep->dstp()) return nullptr;
// Pick up the common successor
CfgBlock* const succp = takenEdgep->dstp();
// If the common successor is a 2-way join, we can use it directly
if (succp->isTwoWayJoin()) return succp;
// Otherwise insert a new block to join the 2 paths of the original block
CfgBlock* const joinp = addBlock();
addTakenEdge(joinp, succp);
takenEdgep->relinkTop(joinp);
untknEdgep->relinkTop(joinp);
return joinp;
}
bool CfgGraph::insertTwoWayJoins() {
// Reset marks
for (V3GraphVertex& v : vertices()) v.user(0);
bool isSeriesParallel = true;
// We will be adding vertices at the end. That's OK, they don't need to be visited again
for (V3GraphVertex& v : vertices()) {
CfgBlock& bb = static_cast<CfgBlock&>(v);
// Skip if already visited
if (bb.user()) continue;
// Skip if not a branch
if (!bb.isBranch()) continue;
// Fix it up, record if failed
if (!getOrCreateTwoWayJoinFor(&bb)) isSeriesParallel = false;
}
if (m_nEdits != m_nLastOrdered) rpoBlocks();
if (dumpGraphLevel() >= 9) dumpDotFilePrefixed("cfg-insertTwoWayJoins");
return isSeriesParallel;
}
//######################################################################
// CfgDominatorTree
const CfgBlock* CfgDominatorTree::intersect(const CfgBlock* ap, const CfgBlock* bp) {
while (ap != bp) {
while (*ap > *bp) ap = m_bb2Idom[*ap];
while (*bp > *ap) bp = m_bb2Idom[*bp];
}
return ap;
}
CfgDominatorTree::CfgDominatorTree(const CfgGraph& cfg)
: m_bb2Idom{cfg.makeBlockMap<const CfgBlock*>()} {
// Build the immediate dominator map, using algorithm from:
// "A Simple, Fast Dominance Algorithm", Keith D. Cooper et al., 2006
// Immediate dominator of the enter block
// Point enteer block to itself, while computing below
m_bb2Idom[cfg.enter()] = &cfg.enter();
// Iterate until settled
for (bool changed = true; changed;) {
changed = false;
// For each vertex except enter block
for (const V3GraphVertex& vtx : cfg.vertices()) {
const CfgBlock& curr = static_cast<const CfgBlock&>(vtx);
if (curr.isEnter()) continue; // Skip entry block
// For each predecessor of current block
const CfgBlock* idom = nullptr;
for (const V3GraphEdge& edge : curr.inEdges()) {
const CfgBlock& pred = static_cast<const CfgBlock&>(*edge.fromp());
// Skip if perdecessor not yet processed
if (!m_bb2Idom[pred]) continue;
// Pick first, then use intersect
idom = !idom ? &pred : intersect(&pred, idom);
}
// If chenged, record it, else move on
if (idom == m_bb2Idom[curr]) continue;
m_bb2Idom[curr] = idom;
changed = true;
}
}
// The enter block is the root of the tree and does not itself have an immediate dominator
m_bb2Idom[cfg.enter()] = nullptr;
}

571
src/V3Cfg.h Normal file
View File

@ -0,0 +1,571 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Control flow graph (CFG)
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2025 by Wilson Snyder. This program is free software; you
// can redistribute it and/or modify it under the terms of either the GNU
// Lesser General Public License Version 3 or the Perl Artistic License
// Version 2.0.
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
//
// Control flow graph (CFG) and related data structures.
//
// A control flow graph is a directed graph with with basic blocks as
// vertices connected by control flow edges. A basic block is a sequence
// of statements that are always executed as a unit (there are no branches
// targeting the middle of a basic block). The last statement in a basic
// block is called the terminator statemet. The terminator statements are
// the control flow transfer statements (branches) in the program. In this
// implementation, an unconditoinal jump (goto) is implicit and not stored
// in the basic block. A consequence of the implicit jump is that a basic
// block might be empty (not contain any statements). Conditional branches
// are represented by storing the corresponding conditional AstNodeStmt as
// the last statement in the basic block. Most importantly, only the actual
// condition check (e.g.: the 'condp' part of an AstIf) and branch is
// executed in the host basic block, but not any of the body statements of
// the conditoinal. The control flow graph has 2 unique basic blocks. The
// 'enter' block is the unique entry point of the represented program. It
// has no predecessors and itself dominates all other basic blocks. The
// 'exit' block is the unique exit point. It has no successors, and itself
// post-dominates all other basic blocks.
//
// The current implementation is designed to be immutable after
// construction. This can be relaxed in the future if necessary, however
// it is unlikely to be necessary as we cannot as of now convert a control
// flow graph back to Ast or any other form of output. We can also only
// represent 2-way conditionals, therefore all basic blocks have up to 2
// successors. The exit block has 0, blocks terminated by an unconditional
// implicit jump have exactly 1, and blocks terminated by a conditional
// branch have exactly 2 successors.
//
// Basic blocks have a unique ID, these are assigned based on the reverse
// post-ordering of the basic blocks within the control flow graph, and
// therefore they define a topological ordering of the basic blocks. The
// basic blocks within the graph are stored in this order. This means that
// in control flow graphs without back edges (loops), a basic block is
// always stored after its predecessors.
//
//*************************************************************************
#ifndef VERILATOR_V3CFG_H_
#define VERILATOR_V3CFG_H_
#include "config_build.h"
#include "verilatedos.h"
#include "V3Ast.h"
#include "V3Graph.h"
#include <functional>
#include <limits>
#include <memory>
#include <vector>
//######################################################################
// Control Flow Graph (CFG) and related data structures
class CfgGraph;
class CfgBlock;
class CfgEdge;
template <typename T_Key, typename T_Value>
class CfgMap;
template <typename T_Value>
class CfgBlockMap;
template <typename T_Value>
class CfgEdgeMap;
//######################################################################
// CfgBlock - A basic block (verticies of the control flow graph)
class CfgBlock final : public V3GraphVertex {
friend class CfgGraph;
template <typename T_Key, typename T_Value>
friend class CfgMap;
friend class CfgBuilder;
// STATE
CfgGraph* const m_cfgp; // The control flow graph this CfgBlock is under
size_t m_rpoNumber; // Reverse post-order number and unique ID of this CfgBlock
// V3GraphEdge::user() is set to the unique id by CfgBuilder
std::vector<AstNodeStmt*> m_stmtps; // statements in this CfgBlock
// PRIVATE METHODS
// ID (reverse post-order numpber) of this block
inline size_t id();
inline size_t id() const;
// CONSTRUCTOR/DESTRUCTOR - via CfgGraph only
inline explicit CfgBlock(CfgGraph* cfgp);
~CfgBlock() override = default;
public:
// PUBLIC METHODS
// Is this the entry block of the CFG?
bool isEnter() const { return inEmpty(); }
// Is this the exit block of the CFG?
bool isExit() const { return outEmpty(); }
// Is this a branching block (multiple successors)?
bool isBranch() const { return outEdges().hasMultipleElements(); }
// Is this a control flow convergence block (multiple predecessors)?
bool isJoin() const { return inEdges().hasMultipleElements(); }
// Is this a join of exactly 2 paths?
bool isTwoWayJoin() const { return inEdges().hasTwoElements(); }
// The edge going to the taken (or uncinditional) successor, or nullptr if exit block
inline CfgEdge* takenEdgep();
inline const CfgEdge* takenEdgep() const;
// The edge going to the untaken successor, or nullptr if not a branch, or exit block
inline CfgEdge* untknEdgep();
inline const CfgEdge* untknEdgep() const;
// The taken successor block, or nullptr if exit block
inline CfgBlock* takenp();
inline const CfgBlock* takenp() const;
// The untakens successor block, or nullptr if not a branch, or exit block
inline CfgBlock* untknp();
inline const CfgBlock* untknp() const;
// The first predecessor edge, or nullptr if enter block
inline CfgEdge* firstPredecessorEdgep();
inline const CfgEdge* firstPredecessorEdgep() const;
// The last predecessor edge, or nullptr if enter block
inline CfgEdge* lastPredecessorEdgep();
inline const CfgEdge* lastPredecessorEdgep() const;
// The first predecessor block, or nullptr if enter block
inline CfgBlock* firstPredecessorp();
inline const CfgBlock* firstPredecessorp() const;
// The last predecessor block, or nullptr if enter block
inline CfgBlock* lastPredecessorp();
inline const CfgBlock* lastPredecessorp() const;
// Statements in this CfgBlock
const std::vector<AstNodeStmt*>& stmtps() const { return m_stmtps; }
// Ordering is done on reverese post-order numbering. For loop-free graph
// this ensures that a block that compares less than another is not a
// successor of the other block (it is an ancestor, or sibling).
bool operator<(const CfgBlock& that) const { return id() < that.id(); }
bool operator>(const CfgBlock& that) const { return id() > that.id(); }
bool operator==(const CfgBlock& that) const { return this == &that; }
// Iterators
void forEachSuccessor(std::function<void(const CfgBlock&)> f) const {
for (const V3GraphEdge& edge : outEdges()) f(*static_cast<CfgBlock*>(edge.top()));
}
void forEachPredecessor(std::function<void(const CfgBlock&)> f) const {
for (const V3GraphEdge& edge : inEdges()) f(*static_cast<CfgBlock*>(edge.fromp()));
}
// Source location for debugging
FileLine* fileline() const override {
return !m_stmtps.empty() ? m_stmtps.front()->fileline() : nullptr;
}
// For Graphviz dumps only
std::string name() const override;
std::string dotShape() const override;
std::string dotRank() const override;
};
//######################################################################
// CfgEdge - An edges of the control flow graph
class CfgEdge final : public V3GraphEdge {
friend class CfgGraph;
template <typename T_Key, typename T_Value>
friend class CfgMap;
// STATE - Immutable after construction, set by CfgBuilder
CfgGraph* const m_cfgp; // The control flow graph this CfgEdge is under
size_t m_id; // Unique ID of this vertex
// PRIVATE METHODS
// Unique ID of this CfgEdge - no particular meaning
inline size_t id();
inline size_t id() const;
// CONSTRUCTOR/DESTRUCTOR - via CfgGraph only
inline CfgEdge(CfgGraph* graphp, CfgBlock* srcp, CfgBlock* dstp);
~CfgEdge() override = default;
public:
// METHODS - all const
// Source/destination CfgBlock
const CfgBlock* srcp() const { return static_cast<const CfgBlock*>(fromp()); }
const CfgBlock* dstp() const { return static_cast<const CfgBlock*>(top()); }
CfgBlock* srcp() { return static_cast<CfgBlock*>(fromp()); }
CfgBlock* dstp() { return static_cast<CfgBlock*>(top()); }
// For Graphviz dumps only
std::string dotLabel() const override;
};
//######################################################################
// CfgGraph - The control flow graph
class CfgGraph final : public V3Graph {
friend class CfgBlock;
friend class CfgEdge;
template <typename T_Key, typename T_Value>
friend class CfgMap;
friend class CfgBuilder;
// STATE
size_t m_nEdits = 0; // Edit count of this graph
size_t m_nLastOrdered = 0; // Last edit count blocks were ordered
CfgBlock* m_enterp = nullptr; // The singular entry vertex
CfgBlock* m_exitp = nullptr; // The singular exit vertex
size_t m_nBlocks = 0; // Number of CfgBlocks in this CfgGraph
size_t m_nEdges = 0; // Number of CfgEdges in this CfgGraph
// PRIVATE METHODS
// Compute reverse post-order enumeration of blocks, and sort them
// accordingly. Assign blocks, and edge IDs. Invalidates all previous IDs.
void rpoBlocks();
// Add a new CfgBlock to this graph
CfgBlock* addBlock() {
++m_nEdits;
++m_nBlocks;
return new CfgBlock{this};
}
// Add a new taken (or unconditional) CfgEdge to this CFG
void addTakenEdge(CfgBlock* srcp, CfgBlock* dstp) {
UASSERT_OBJ(srcp->m_cfgp == this, srcp, "'srcp' is not in this graph");
UASSERT_OBJ(dstp->m_cfgp == this, dstp, "'dstp' is not in this graph");
UASSERT_OBJ(!srcp->takenEdgep(), srcp, "Taken edge should be added first");
//
UASSERT_OBJ(dstp != m_enterp, dstp, "Enter block cannot have a predecessor");
UASSERT_OBJ(srcp != m_exitp, srcp, "Exit block cannot have a successor");
++m_nEdits;
++m_nEdges;
new CfgEdge{this, srcp, dstp};
}
// Add a new untaken CfgEdge to this CFG
void addUntknEdge(CfgBlock* srcp, CfgBlock* dstp) {
UASSERT_OBJ(srcp->m_cfgp == this, srcp, "'srcp' is not in this graph");
UASSERT_OBJ(dstp->m_cfgp == this, dstp, "'dstp' is not in this graph");
UASSERT_OBJ(srcp->takenEdgep(), srcp, "Untaken edge shold be added second");
UASSERT_OBJ(srcp->takenp() != dstp, srcp, "Untaken branch targets the same block");
//
UASSERT_OBJ(dstp != m_enterp, dstp, "Enter block cannot have a predecessor");
UASSERT_OBJ(srcp != m_exitp, srcp, "Exit block cannot have a successor");
++m_nEdits;
++m_nEdges;
new CfgEdge{this, srcp, dstp};
}
size_t idOf(const CfgBlock* bbp) const {
UASSERT_OBJ(m_nEdits == m_nLastOrdered, m_enterp, "Cfg was edited but not re-ordered");
return bbp->m_rpoNumber;
}
size_t idOf(const CfgEdge* edgep) const {
UASSERT_OBJ(m_nEdits == m_nLastOrdered, m_enterp, "Cfg was edited but not re-ordered");
return edgep->m_id;
}
// Implementation for insertTwoWayJoins
CfgBlock* getOrCreateTwoWayJoinFor(CfgBlock* bbp);
// CONSTRUCTOR - use CfgGraph::build, which might fail, so this can't be public
CfgGraph() = default;
public:
~CfgGraph() override = default;
// STATIC FUNCTIONS
// Build CFG for the given list of statements
static std::unique_ptr<CfgGraph> build(AstNode* stmtsp);
// PUBLIC METHODS
// Accessors
const CfgBlock& enter() const { return *m_enterp; }
const CfgBlock& exit() const { return *m_exitp; }
// Number of basic blocks in this graph
size_t nBlocks() const { return m_nBlocks; }
// Number of control flow edges in this graph
size_t nEdges() const { return m_nEdges; }
// Create a CfgBlock map for this graph
template <typename T_Value>
inline CfgBlockMap<T_Value> makeBlockMap() const;
// Create a CfgEdgeMap map for this graph
template <typename T_Value>
inline CfgEdgeMap<T_Value> makeEdgeMap() const;
// Returns true iff the graph contains a loop (back-edge)
bool containsLoop() const;
//------------------------------------------------------------------
// The following methods mutate this CFG and invalidate CfgBlock and
// CfgEdge IDs and associated CfgBlockMap, CfgEdgeMap and other
// query instances.
// Remove empty blocks, combine sequential blocks. Keeps the enter/exit block, even if empty.
void minimize();
// Insert empty blocks to fix critical edges (edges that have a source with
// multiple successors, and a destination with multiple predecessors)
void breakCriticalEdges();
bool insertTwoWayJoins();
};
//######################################################################
// CfgMap - Map from CfgBlock or CfgEdge to generic values
template <typename T_Key, typename T_Value>
class CfgMap VL_NOT_FINAL {
// As opposed to a std::map/std::unordered_map, all entries are value
// initialized at construction, and the map is always dense. This can
// be improved if necessary but is sufficient for our current purposes.
const CfgGraph* m_cfgp; // The control flow graph this map is for
size_t m_created; // Edit count of CFG this map was created at
std::vector<T_Value> m_map; // The map, stored as a vector
protected:
// CONSTRUCTOR
explicit CfgMap(const CfgGraph* cfgp, size_t size)
: m_cfgp{cfgp}
, m_created{cfgp->m_nEdits}
, m_map{size} {}
public:
// Can create an empty map
CfgMap()
: m_cfgp{nullptr}
, m_created{0} {}
// Copyable, movable
CfgMap(const CfgMap<T_Key, T_Value>&) = default;
CfgMap(CfgMap<T_Key, T_Value>&&) = default;
CfgMap<T_Key, T_Value>& operator=(const CfgMap<T_Key, T_Value>&) = default;
CfgMap<T_Key, T_Value>& operator=(CfgMap<T_Key, T_Value>&&) = default;
T_Value& operator[](const T_Key& key) {
UASSERT_OBJ(m_created == m_cfgp->m_nEdits, m_cfgp->m_enterp, "Map is stale");
UASSERT_OBJ(m_cfgp == key.m_cfgp, m_cfgp->m_enterp, "Key not in this CFG");
return m_map.at(key.id());
}
const T_Value& operator[](const T_Key& key) const {
UASSERT_OBJ(m_created == m_cfgp->m_nEdits, m_cfgp->m_enterp, "Map is stale");
UASSERT_OBJ(m_cfgp == key.m_cfgp, m_cfgp->m_enterp, "Key not in this CFG");
return m_map.at(key.id());
}
T_Value& operator[](const T_Key* keyp) {
UASSERT_OBJ(m_created == m_cfgp->m_nEdits, m_cfgp->m_enterp, "Map is stale");
UASSERT_OBJ(m_cfgp == keyp->m_cfgp, m_cfgp->m_enterp, "Key not in this CFG");
return m_map.at(keyp->id());
}
const T_Value& operator[](const T_Key* keyp) const {
UASSERT_OBJ(m_created == m_cfgp->m_nEdits, m_cfgp->m_enterp, "Map is stale");
UASSERT_OBJ(m_cfgp == keyp->m_cfgp, m_cfgp->m_enterp, "Key not in this CFG");
return m_map.at(keyp->id());
}
};
template <typename T_Value>
class CfgBlockMap final : public CfgMap<CfgBlock, T_Value> {
friend class CfgGraph;
// CONSTRUCTOR - Create one via CfgGraph::makeBlockMap
explicit CfgBlockMap(const CfgGraph* cfgp)
: CfgMap<CfgBlock, T_Value>{cfgp, cfgp->nBlocks()} {}
public:
// Can create an empty map
CfgBlockMap() = default;
// Copyable, movable
CfgBlockMap(const CfgBlockMap&) = default;
CfgBlockMap(CfgBlockMap&&) = default;
CfgBlockMap& operator=(const CfgBlockMap&) = default;
CfgBlockMap& operator=(CfgBlockMap&&) = default;
};
template <typename T_Value>
class CfgEdgeMap final : public CfgMap<CfgEdge, T_Value> {
friend class CfgGraph;
// CONSTRUCTOR - Create one via CfgGraph::makeEdgeMap
explicit CfgEdgeMap(const CfgGraph* cfgp)
: CfgMap<CfgEdge, T_Value>{cfgp, cfgp->nEdges()} {}
public:
// Can create an empty map
CfgEdgeMap() = default;
// Copyable, movable
CfgEdgeMap(const CfgEdgeMap&) = default;
CfgEdgeMap(CfgEdgeMap&&) = default;
CfgEdgeMap& operator=(const CfgEdgeMap&) = default;
CfgEdgeMap& operator=(CfgEdgeMap&&) = default;
};
//######################################################################
// Innline method definitions
// --- CfgBlock ---
CfgBlock::CfgBlock(CfgGraph* cfgp)
: V3GraphVertex{cfgp}
, m_cfgp{cfgp} {}
size_t CfgBlock::id() { return m_cfgp->idOf(this); }
size_t CfgBlock::id() const { return m_cfgp->idOf(this); }
// Successor edges
CfgEdge* CfgBlock::takenEdgep() { // It's always the first edge
return isExit() ? nullptr : static_cast<CfgEdge*>(outEdges().frontp());
}
const CfgEdge* CfgBlock::takenEdgep() const { // It's always the first edge
return isExit() ? nullptr : static_cast<const CfgEdge*>(outEdges().frontp());
}
CfgEdge* CfgBlock::untknEdgep() { // It's always the second (last) edge
return isBranch() ? static_cast<CfgEdge*>(outEdges().backp()) : nullptr;
}
const CfgEdge* CfgBlock::untknEdgep() const { // It's always the second (last) edge
return isBranch() ? static_cast<const CfgEdge*>(outEdges().backp()) : nullptr;
}
CfgBlock* CfgBlock::takenp() {
return isExit() ? nullptr : static_cast<CfgBlock*>(outEdges().frontp()->top());
}
const CfgBlock* CfgBlock::takenp() const {
return isExit() ? nullptr : static_cast<CfgBlock*>(outEdges().frontp()->top());
}
CfgBlock* CfgBlock::untknp() {
return isBranch() ? static_cast<CfgBlock*>(outEdges().backp()->top()) : nullptr;
}
const CfgBlock* CfgBlock::untknp() const {
return isBranch() ? static_cast<const CfgBlock*>(outEdges().backp()->top()) : nullptr;
}
// Predecessor edges
CfgEdge* CfgBlock::firstPredecessorEdgep() { //
return static_cast<CfgEdge*>(inEdges().frontp());
}
const CfgEdge* CfgBlock::firstPredecessorEdgep() const { //
return static_cast<const CfgEdge*>(inEdges().frontp());
}
CfgEdge* CfgBlock::lastPredecessorEdgep() { //
return static_cast<CfgEdge*>(inEdges().backp());
}
const CfgEdge* CfgBlock::lastPredecessorEdgep() const { //
return static_cast<const CfgEdge*>(inEdges().backp());
}
CfgBlock* CfgBlock::firstPredecessorp() { //
return isEnter() ? nullptr : firstPredecessorEdgep()->srcp();
}
const CfgBlock* CfgBlock::firstPredecessorp() const { //
return isEnter() ? nullptr : firstPredecessorEdgep()->srcp();
}
CfgBlock* CfgBlock::lastPredecessorp() { //
return isEnter() ? nullptr : lastPredecessorEdgep()->srcp();
}
const CfgBlock* CfgBlock::lastPredecessorp() const { //
return isEnter() ? nullptr : lastPredecessorEdgep()->srcp();
}
// --- CfgEdge ---
CfgEdge::CfgEdge(CfgGraph* cfgp, CfgBlock* srcp, CfgBlock* dstp)
: V3GraphEdge{cfgp, srcp, dstp, 1, false}
, m_cfgp{cfgp} {}
size_t CfgEdge::id() { return m_cfgp->idOf(this); }
size_t CfgEdge::id() const { return m_cfgp->idOf(this); }
// --- CfgGraph ---
template <typename T_Value>
CfgBlockMap<T_Value> CfgGraph::makeBlockMap() const {
return CfgBlockMap<T_Value>{this};
}
template <typename T_Value>
CfgEdgeMap<T_Value> CfgGraph::makeEdgeMap() const {
return CfgEdgeMap<T_Value>{this};
}
//######################################################################
// CfgDominatorTree
class CfgDominatorTree final {
// STATE
CfgBlockMap<const CfgBlock*> m_bb2Idom; // Map from CfgBlock to its immediate dominator
// PRIVATE METHODS
// Part of algorithm to compute m_bb2Idom, see consructor
const CfgBlock* intersect(const CfgBlock* ap, const CfgBlock* bp);
public:
// CONSTRUCTOR
explicit CfgDominatorTree(const CfgGraph& cfg);
// Can create an empty map
CfgDominatorTree() = default;
// Copyable, movable
CfgDominatorTree(const CfgDominatorTree&) = default;
CfgDominatorTree(CfgDominatorTree&&) = default;
CfgDominatorTree& operator=(const CfgDominatorTree&) = default;
CfgDominatorTree& operator=(CfgDominatorTree&&) = default;
// PUBLIC METHODS
// Return unique CfgBlock that dominates both of the given blocks, but does
// not strictly dominate any other block that dominates both blocks. It
// will return 'ap' or 'bp' if one dominates the other (or are the same)
const CfgBlock* closestCommonDominator(const CfgBlock* ap, const CfgBlock* bp) const {
while (ap != bp) {
if (*ap < *bp) {
bp = m_bb2Idom[bp];
} else {
ap = m_bb2Idom[ap];
}
}
return ap;
}
// Returns true if 'ap' dominates 'bp'
bool dominates(const CfgBlock* const ap, const CfgBlock* bp) {
// Walk up the dominator tree from 'bp' until reaching 'ap' or the root
while (true) {
// True if 'ap' is above (or same as) 'bp'
if (ap == bp) return true;
// Step up the dominator tree
bp = m_bb2Idom[bp];
// False if reached the root
if (!bp) return false;
}
}
};
//######################################################################
// V3Cfg
namespace V3Cfg {
// Compute AstVars live on entry to given CFG. That is, variables that might
// be read before wholly assigned in the CFG. Returns nullptr if the analysis
// failed due to unhandled statements or data types involved in the CFG.
// On success, returns a vector of AstVar or AstVarScope nodes live on entry.
std::unique_ptr<std::vector<AstVar*>> liveVars(const CfgGraph&);
// Same as liveVars, but return AstVarScopes insted
std::unique_ptr<std::vector<AstVarScope*>> liveVarScopes(const CfgGraph&);
} //namespace V3Cfg
#endif // VERILATOR_V3CFG_H_

233
src/V3CfgBuilder.cpp Normal file
View File

@ -0,0 +1,233 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Control flow graph (CFG) builder
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2025 by Wilson Snyder. This program is free software; you
// can redistribute it and/or modify it under the terms of either the GNU
// Lesser General Public License Version 3 or the Perl Artistic License
// Version 2.0.
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
//
// Control flow graph (CFG) builder
//
//*************************************************************************
#include "config_build.h"
#include "verilatedos.h"
#include "V3Ast.h"
#include "V3Cfg.h"
#include "V3EmitV.h"
#include <deque>
#include <unordered_map>
#include <unordered_set>
VL_DEFINE_DEBUG_FUNCTIONS;
class CfgBuilder final : public VNVisitorConst {
// STATE
// The graph being built, or nullptr if failed to build one
std::unique_ptr<CfgGraph> m_cfgp{new CfgGraph};
// Current basic block to add statements to
CfgBlock* m_currBBp = nullptr;
// Continuation block for given JumpBlock
std::unordered_map<AstJumpBlock*, CfgBlock*> m_jumpBlockContp;
// METHODS
// Add the given statement to the current CfgBlock
void addStmt(AstNodeStmt* nodep) { m_currBBp->m_stmtps.emplace_back(nodep); }
// Used to handle statements not representable in the CFG
void nonRepresentable(AstNodeStmt*) {
if (!m_cfgp) return;
m_cfgp.reset();
}
// Used to handle simple (non-branching) statements in the CFG
void simpleStatement(AstNodeStmt* nodep, bool representable = true) {
if (!m_cfgp) return;
// If non-representable, reset graph
if (!representable) {
m_cfgp.reset();
return;
}
// Just add to current block
addStmt(nodep);
}
// VISITORS
// Eventually we should handle all procedural statements, however, what
// is a procedural statemen is a bit unclear (#6280), so in the first
// instance we will only handle select statemetns that cover the requied
// use cases, and in the base case we conservatively assume the statement
// is non-representable. More visits can be added case by case if needed.
void visit(AstNode* nodep) override {
if (!m_cfgp) return;
UINFO(9, "Unhandled AstNode type " << nodep->typeName());
m_cfgp.reset();
}
// Non-representable statements
void visit(AstAssignDly* nodep) override { nonRepresentable(nodep); }
void visit(AstCase* nodep) override { nonRepresentable(nodep); } // V3Case will eliminate
void visit(AstCReset* nodep) override { nonRepresentable(nodep); }
void visit(AstDelay* nodep) override { nonRepresentable(nodep); }
// Representable non control-flow statements
void visit(AstAssign* nodep) override { simpleStatement(nodep, !nodep->timingControlp()); }
void visit(AstComment*) override {} // ignore entirely
void visit(AstDisplay* nodep) override { simpleStatement(nodep); }
void visit(AstFinish* nodep) override { simpleStatement(nodep); }
void visit(AstStmtExpr* nodep) override { simpleStatement(nodep); }
void visit(AstStop* nodep) override { simpleStatement(nodep); }
// Representable control flow statements
void visit(AstIf* nodep) override {
if (!m_cfgp) return;
// Add terminator statement to current block - semantically the condition check only ...
addStmt(nodep);
// Create then/else/continuation blocks
CfgBlock* const thenBBp = m_cfgp->addBlock();
CfgBlock* const elseBBp = m_cfgp->addBlock();
CfgBlock* const contBBp = m_cfgp->addBlock();
m_cfgp->addTakenEdge(m_currBBp, thenBBp);
m_cfgp->addUntknEdge(m_currBBp, elseBBp);
// Build then branch
m_currBBp = thenBBp;
iterateAndNextConstNull(nodep->thensp());
if (!m_cfgp) return;
if (m_currBBp) m_cfgp->addTakenEdge(m_currBBp, contBBp);
// Build else branch
m_currBBp = elseBBp;
iterateAndNextConstNull(nodep->elsesp());
if (!m_cfgp) return;
if (m_currBBp) m_cfgp->addTakenEdge(m_currBBp, contBBp);
// Set continuation
m_currBBp = contBBp;
}
void visit(AstWhile* nodep) override {
if (!m_cfgp) return;
// Create the header block
CfgBlock* const headBBp = m_cfgp->addBlock();
m_cfgp->addTakenEdge(m_currBBp, headBBp);
// The While goes in the header block - semantically the condition check only ...
m_currBBp = headBBp;
addStmt(nodep);
// Create the body/continuation blocks
CfgBlock* const bodyBBp = m_cfgp->addBlock();
CfgBlock* const contBBp = m_cfgp->addBlock();
m_cfgp->addTakenEdge(headBBp, bodyBBp);
m_cfgp->addUntknEdge(headBBp, contBBp);
// Build the body
m_currBBp = bodyBBp;
iterateAndNextConstNull(nodep->stmtsp());
iterateAndNextConstNull(nodep->incsp());
if (!m_cfgp) return;
if (m_currBBp) m_cfgp->addTakenEdge(m_currBBp, headBBp);
// Set continuation
m_currBBp = contBBp;
}
void visit(AstJumpBlock* nodep) override {
if (!m_cfgp) return;
// Don't acutally need to add this 'nodep' to any block
// Create continuation block
CfgBlock* const contBBp = m_cfgp->addBlock();
const bool newEntry = m_jumpBlockContp.emplace(nodep, contBBp).second;
UASSERT_OBJ(newEntry, nodep, "AstJumpBlock visited twice");
// Build the body
iterateAndNextConstNull(nodep->stmtsp());
if (!m_cfgp) return;
if (m_currBBp) m_cfgp->addTakenEdge(m_currBBp, contBBp);
// Set continuation
m_currBBp = contBBp;
}
void visit(AstJumpGo* nodep) override {
if (!m_cfgp) return;
// Non-representable if not last in statement list (V3Const will fix this later)
if (nodep->nextp()) {
m_cfgp.reset();
return;
}
// Don't acutally need to add this 'nodep' to any block
// Make current block go to the continuation of the JumpBlock
m_cfgp->addTakenEdge(m_currBBp, m_jumpBlockContp.at(nodep->blockp()));
// There should be no statements after a JumpGo!
m_currBBp = nullptr;
}
// CONSTRUCTOR
explicit CfgBuilder(AstNode* stmtsp) {
// Build the graph, starting from the entry block
m_currBBp = m_cfgp->addBlock();
m_cfgp->m_enterp = m_currBBp;
// Visit each statement to build the control flow graph
iterateAndNextConstNull(stmtsp);
// If failed, stop now
if (!m_cfgp) return;
// The final block is the exit block
m_cfgp->m_exitp = m_currBBp;
// Some blocks might not have predecessors if they are unreachable, remove them
{
std::vector<V3GraphVertex*> unreachableps;
for (V3GraphVertex* const vtxp : m_cfgp->vertices().unlinkable()) {
if (vtxp == m_cfgp->m_enterp) continue;
if (vtxp == m_cfgp->m_exitp) continue;
UASSERT_OBJ(!vtxp->outEmpty(), vtxp, "Block with no successor other than exit");
if (vtxp->inEmpty()) unreachableps.emplace_back(vtxp);
}
while (!unreachableps.empty()) {
V3GraphVertex* const vtxp = unreachableps.back();
unreachableps.pop_back();
for (V3GraphEdge& edge : vtxp->outEdges()) {
--m_cfgp->m_nEdges;
if (edge.top()->inSize1()) unreachableps.emplace_back(edge.top());
}
--m_cfgp->m_nBlocks;
VL_DO_DANGLING(vtxp->unlinkDelete(m_cfgp.get()), vtxp);
}
}
// Dump the initial graph
if (dumpGraphLevel() >= 9) {
m_cfgp->rpoBlocks();
m_cfgp->dumpDotFilePrefixed("cfg-builder-initial");
}
// Minimize it
m_cfgp->minimize();
// Dump the final graph
if (dumpGraphLevel() >= 8) m_cfgp->dumpDotFilePrefixed("cfg-builder");
}
public:
static std::unique_ptr<CfgGraph> apply(AstNode* stmtsp) {
return std::move(CfgBuilder{stmtsp}.m_cfgp);
}
};
std::unique_ptr<CfgGraph> CfgGraph::build(AstNode* stmtsp) { return CfgBuilder::apply(stmtsp); }

241
src/V3CfgLiveVariables.cpp Normal file
View File

@ -0,0 +1,241 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: CFG liveness analysis
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2025 by Wilson Snyder. This program is free software; you
// can redistribute it and/or modify it under the terms of either the GNU
// Lesser General Public License Version 3 or the Perl Artistic License
// Version 2.0.
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
//
// Classical data flow analysis computing live variables input to a CFG
// https://en.wikipedia.org/wiki/Live-variable_analysis
//
//*************************************************************************
#include "config_build.h"
#include "verilatedos.h"
#include "V3Ast.h"
#include "V3Cfg.h"
#include <limits>
#include <memory>
#include <unordered_map>
VL_DEFINE_DEBUG_FUNCTIONS;
template <bool T_Scoped>
class CfgLiveVariables final : VNVisitorConst {
// TYPES
using Variable = std::conditional_t<T_Scoped, AstVarScope, AstVar>;
// State associted with each basic block
struct BlockState final {
// Variables used in block, before a complete assignment in the same block
std::unordered_set<Variable*> m_gen;
// Variables that are assigned a complete value in the basic block
std::unordered_set<Variable*> m_kill;
std::unordered_set<Variable*> m_liveIn; // Variables live on entry to the block
std::unordered_set<Variable*> m_liveOut; // Variables live on exit from the block
bool m_isOnWorkList = false; // Block is on work list
bool m_wasProcessed = false; // Already processed at least once
};
// STATE
const CfgGraph& m_cfg; // The CFG beign analysed
// State for each block
CfgBlockMap<BlockState> m_blockState{m_cfg.makeBlockMap<BlockState>()};
BlockState* m_currp = nullptr; // State of current block being analysed
bool m_abort = false; // Abort analysis - unhandled construct
// METHODS
static Variable* getTarget(const AstNodeVarRef* refp) {
// TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works
if VL_CONSTEXPR_CXX17 (T_Scoped) {
return reinterpret_cast<Variable*>(refp->varScopep());
} else {
return reinterpret_cast<Variable*>(refp->varp());
}
}
// This is to match DFG, but can be extended independently - eventually should handle all
static bool isSupportedPackedDType(const AstNodeDType* dtypep) {
dtypep = dtypep->skipRefp();
if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) {
return typep->keyword().isIntNumeric();
}
if (const AstPackArrayDType* const typep = VN_CAST(dtypep, PackArrayDType)) {
return isSupportedPackedDType(typep->subDTypep());
}
if (const AstNodeUOrStructDType* const typep = VN_CAST(dtypep, NodeUOrStructDType)) {
return typep->packed();
}
return false;
}
// Check and return if variable is incompatible
bool incompatible(Variable* varp) {
if (!isSupportedPackedDType(varp->dtypep())) return true;
const AstVar* astVarp = nullptr;
// TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works
if VL_CONSTEXPR_CXX17 (T_Scoped) {
astVarp = reinterpret_cast<AstVarScope*>(varp)->varp();
} else {
astVarp = reinterpret_cast<AstVar*>(varp);
}
if (astVarp->ignoreSchedWrite()) return true;
return false;
}
void updateGen(AstNode* nodep) {
UASSERT_OBJ(!m_abort, nodep, "Doing useless work");
m_abort = nodep->exists([&](const AstNodeVarRef* refp) {
// Cross reference is ambiguous
if (VN_IS(refp, VarXRef)) return true;
// Only care about reads
if (refp->access().isWriteOnly()) return false;
// Grab referenced variable
Variable* const tgtp = getTarget(refp);
// Bail if not a compatible type
if (incompatible(tgtp)) return true;
// Add to gen set, if not killed - assume whole variable read
if (m_currp->m_kill.count(tgtp)) return false;
m_currp->m_gen.emplace(tgtp);
return false;
});
}
void updateKill(AstNode* nodep) {
UASSERT_OBJ(!m_abort, nodep, "Doing useless work");
m_abort = nodep->exists([&](const AstNodeVarRef* refp) {
// Cross reference is ambiguous
if (VN_IS(refp, VarXRef)) return true;
// Only care about writes
if (refp->access().isReadOnly()) return false;
// Grab referenced variable
Variable* const tgtp = getTarget(refp);
// Bail if not a compatible type
if (incompatible(tgtp)) return true;
// If whole written, add to kill set
if (refp->nextp()) return false;
if (VN_IS(refp->abovep(), Sel)) return false;
m_currp->m_kill.emplace(tgtp);
return false;
});
}
void single(AstNode* nodep) {
// Assume all reads hapen before any writes
if (m_abort) return;
updateGen(nodep);
if (m_abort) return;
updateKill(nodep);
}
// Apply transfer function of block, return true if changed
bool transfer(const CfgBlock& bb) {
BlockState& state = m_blockState[bb];
// liveIn = gen union (liveOut - kill)
std::unordered_set<Variable*> liveIn = state.m_gen;
for (Variable* const varp : state.m_liveOut) {
if (state.m_kill.count(varp)) continue;
liveIn.insert(varp);
}
if (liveIn == state.m_liveIn) return false;
std::swap(liveIn, state.m_liveIn);
return true;
}
// VISIT
void visit(AstNode* nodep) override { //
UASSERT_OBJ(!m_abort, nodep, "Repeat traversal after abort");
m_abort = true;
UINFO(9, "Unhandled AstNode type " << nodep->typeName());
}
void visit(AstAssign* nodep) override { single(nodep); }
void visit(AstDisplay* nodep) override { single(nodep); }
void visit(AstFinish* nodep) override { single(nodep); }
void visit(AstStmtExpr* nodep) override { single(nodep); }
void visit(AstStop* nodep) override { single(nodep); }
// Only the condition check belongs to the terminated basic block
void visit(AstIf* nodep) override { single(nodep->condp()); }
void visit(AstWhile* nodep) override { single(nodep->condp()); }
// CONSTRUCTOR
explicit CfgLiveVariables(const CfgGraph& cfg)
: m_cfg{cfg} {
// For each basic block, compute the gen and kill set via visit
for (const V3GraphVertex& vtx : cfg.vertices()) {
const CfgBlock& bb = static_cast<const CfgBlock&>(vtx);
if (m_abort) return;
VL_RESTORER(m_currp);
m_currp = &m_blockState[bb];
for (AstNode* const stmtp : bb.stmtps()) iterateConst(stmtp);
}
if (m_abort) return;
// Perform the flow analysis
std::deque<const CfgBlock*> workList;
const auto enqueue = [&](const CfgBlock& bb) {
BlockState& state = m_blockState[bb];
if (state.m_isOnWorkList) return;
state.m_isOnWorkList = true;
workList.emplace_back(&bb);
};
enqueue(cfg.exit());
while (!workList.empty()) {
const CfgBlock* const currp = workList.front();
workList.pop_front();
BlockState& state = m_blockState[*currp];
state.m_isOnWorkList = false;
// Compute meet (liveOut = union liveIn of successors)
currp->forEachSuccessor([&](const CfgBlock& bb) {
auto& liveIn = m_blockState[bb].m_liveIn;
state.m_liveOut.insert(liveIn.begin(), liveIn.end());
});
// Apply transfer function of block
const bool changed = transfer(*currp);
// Enqueue predecessors
if (changed || !state.m_wasProcessed) currp->forEachPredecessor(enqueue);
// Mark as done with first visit
state.m_wasProcessed = true;
}
}
public:
static std::unique_ptr<std::vector<Variable*>> apply(const CfgGraph& cfg) {
CfgLiveVariables analysis{cfg};
// If failed, return nullptr
if (analysis.m_abort) return nullptr;
// Gather variables live in to the entry blockstd::unique_ptr<std::vector<Variable*>>
const std::unordered_set<Variable*>& lin = analysis.m_blockState[cfg.enter()].m_liveIn;
std::vector<Variable*>* const resultp = new std::vector<Variable*>{lin.begin(), lin.end()};
// Sort for stability
std::stable_sort(resultp->begin(), resultp->end(), [](Variable* ap, Variable* bp) { //
return ap->name() < bp->name();
});
return std::unique_ptr<std::vector<Variable*>>{resultp};
}
};
std::unique_ptr<std::vector<AstVar*>> V3Cfg::liveVars(const CfgGraph& cfg) {
return CfgLiveVariables</* T_Scoped: */ false>::apply(cfg);
}
std::unique_ptr<std::vector<AstVarScope*>> V3Cfg::liveVarScopes(const CfgGraph& cfg) {
return CfgLiveVariables</* T_Scoped: */ true>::apply(cfg);
}

View File

@ -57,28 +57,18 @@ class ClassVisitor final : public VNVisitor {
// METHODS
bool recurseImplements(AstClass* nodep, bool setit) {
// Returns true to set useVirtualPublic().
// If there's an implements of an interface class then we have
// multiple classes that point to same object, that need same
// VlClass (the diamond problem). C++ will require we use 'virtual
// public' for VlClass. So, we need the interface class, and all
// classes above, and any below using any implements to use
// 'virtual public' via useVirtualPublic().
if (nodep->useVirtualPublic()) return true; // Short-circuit
if (nodep->isInterfaceClass()) setit = true;
void recurseImplements(AstClass* nodep) {
// In SystemVerilog, we have two inheritance chains:
// - extends of concrete clasess: mapped to non-virtual C++ inheritance
// as there is only single ancestor allowed
// - implements of concrete classes / extends of interface classes: mapped
// to virtual inheritance to allow diamond patterns with multiple ancestors
if (nodep->useVirtualPublic()) return; // Short-circuit to exit diamond cycles
if (nodep->isInterfaceClass()) { nodep->useVirtualPublic(true); }
for (const AstClassExtends* extp = nodep->extendsp(); extp;
extp = VN_AS(extp->nextp(), ClassExtends)) {
if (recurseImplements(extp->classp(), setit)) setit = true;
recurseImplements(extp->classp());
}
if (setit) {
nodep->useVirtualPublic(true);
for (const AstClassExtends* extp = nodep->extendsp(); extp;
extp = VN_AS(extp->nextp(), ClassExtends)) {
(void)recurseImplements(extp->classp(), true);
}
}
return setit;
}
// VISITORS
@ -89,7 +79,7 @@ class ClassVisitor final : public VNVisitor {
nodep->name(m_prefix + nodep->name());
nodep->unlinkFrBack();
v3Global.rootp()->addModulesp(nodep);
(void)recurseImplements(nodep, false);
recurseImplements(nodep);
// Make containing package
// Note origName is the same as the class origName so errors look correct
AstClassPackage* const packagep
@ -185,7 +175,7 @@ class ClassVisitor final : public VNVisitor {
// m_toScopeMoves.emplace_back(nodep, m_classScopep);
//}
}
void visit(AstCoverDecl* nodep) override {
void visit(AstNodeCoverDecl* nodep) override {
// Need to declare coverage in package, where we have access to symbol table
iterateChildren(nodep);
if (m_classPackagep) m_classPackagep->addStmtsp(nodep->unlinkFrBack());
@ -267,7 +257,7 @@ public:
modp->addStmtsp(nodep);
}
// BFS to mark public typedefs.
std::set<AstNodeUOrStructDType*> pubStrDtypeps;
std::set<const AstNodeUOrStructDType*> pubStrDtypeps;
while (!m_pubStrDtypeps.empty()) {
AstNodeUOrStructDType* const dtypep = m_pubStrDtypeps.front();
m_pubStrDtypeps.pop();
@ -280,7 +270,8 @@ public:
}
}
for (AstTypedef* typedefp : m_typedefps) {
AstNodeUOrStructDType* const sdtypep = VN_AS(typedefp->dtypep(), NodeUOrStructDType);
const AstNodeUOrStructDType* const sdtypep
= VN_AS(typedefp->dtypep(), NodeUOrStructDType);
if (pubStrDtypeps.count(sdtypep)) typedefp->attrPublic(true);
}
// Clear package pointer of non-public packed struct / union type, which will never be

View File

@ -51,7 +51,7 @@ class CleanVisitor final : public VNVisitor {
// METHODS
// Width resetting
int cppWidth(AstNode* nodep) {
int cppWidth(const AstNode* nodep) {
if (nodep->width() <= VL_IDATASIZE) {
return VL_IDATASIZE;
} else if (nodep->width() <= VL_QUADSIZE) {
@ -101,7 +101,9 @@ class CleanVisitor final : public VNVisitor {
// Store the clean state in the userp on each node
void setCleanState(AstNode* nodep, CleanState clean) { nodep->user1(clean); }
CleanState getCleanState(AstNode* nodep) { return static_cast<CleanState>(nodep->user1()); }
CleanState getCleanState(const AstNode* nodep) {
return static_cast<CleanState>(nodep->user1());
}
bool isClean(AstNode* nodep) {
const CleanState clstate = getCleanState(nodep);
if (clstate == CS_CLEAN) return true;
@ -261,7 +263,7 @@ class CleanVisitor final : public VNVisitor {
}
// Control flow operators
void visit(AstNodeCond* nodep) override {
void visit(AstCond* nodep) override {
iterateChildren(nodep);
ensureClean(nodep->condp());
setClean(nodep, isClean(nodep->thenp()) && isClean(nodep->elsep()));

View File

@ -80,15 +80,17 @@ class ClockVisitor final : public VNVisitor {
AstNodeExpr* senEqnp = nullptr;
for (AstSenItem* senp = nodesp; senp; senp = VN_AS(senp->nextp(), SenItem)) {
UASSERT_OBJ(senp->edgeType() == VEdgeType::ET_TRUE, senp, "Should have been lowered");
AstNodeExpr* const senOnep = senp->sensp()->cloneTree(false);
senEqnp = senEqnp ? new AstOr{senp->fileline(), senEqnp, senOnep} : senOnep;
if (senp->sensp()) {
AstNodeExpr* const senOnep = senp->sensp()->cloneTree(false);
senEqnp = senEqnp ? new AstOr{senp->fileline(), senEqnp, senOnep} : senOnep;
}
}
return senEqnp;
}
AstIf* makeActiveIf(AstSenTree* sensesp) {
AstNodeExpr* const senEqnp = createSenseEquation(sensesp->sensesp());
UASSERT_OBJ(senEqnp, sensesp, "No sense equation, shouldn't be in sequent activation.");
AstIf* const newifp = new AstIf{sensesp->fileline(), senEqnp};
AstIf* makeActiveIf(AstSenTree* sentreep) {
AstNodeExpr* const senEqnp = createSenseEquation(sentreep->sensesp());
UASSERT_OBJ(senEqnp, sentreep, "No sense equation, shouldn't be in sequent activation.");
AstIf* const newifp = new AstIf{sentreep->fileline(), senEqnp};
return newifp;
}
void clearLastSen() {
@ -97,21 +99,23 @@ class ClockVisitor final : public VNVisitor {
}
// VISITORS
void visit(AstCoverToggle* nodep) override {
// if (debug()) nodep->dumpTree("- ct: ");
// UINFOTREE(1, nodep, "", "ct");
// COVERTOGGLE(INC, ORIG, CHANGE) ->
// IF(ORIG ^ CHANGE) { INC; CHANGE = ORIG; }
AstNode* const incp = nodep->incp()->unlinkFrBack();
AstCoverInc* const incp = nodep->incp()->unlinkFrBack();
AstNodeExpr* const origp = nodep->origp()->unlinkFrBack();
AstNodeExpr* const changeWrp = nodep->changep()->unlinkFrBack();
AstNodeExpr* const changeRdp = ConvertWriteRefsToRead::main(changeWrp->cloneTree(false));
AstNodeExpr* comparedp = nullptr;
incp->toggleExprp(origp->cloneTree(false));
incp->toggleCovExprp(changeRdp->cloneTree(false));
// Xor will optimize better than Eq, when CoverToggle has bit selects,
// but can only use Xor with non-opaque types
if (const AstBasicDType* const bdtypep
= VN_CAST(origp->dtypep()->skipRefp(), BasicDType)) {
if (!bdtypep->isOpaque()) comparedp = new AstXor{nodep->fileline(), origp, changeRdp};
}
if (!comparedp) comparedp = AstEq::newTyped(nodep->fileline(), origp, changeRdp);
if (!comparedp) comparedp = AstNeq::newTyped(nodep->fileline(), origp, changeRdp);
AstIf* const newp = new AstIf{nodep->fileline(), comparedp, incp};
// We could add another IF to detect posedges, and only increment if so.
// It's another whole branch though versus a potential memory miss.
@ -131,11 +135,11 @@ class ClockVisitor final : public VNVisitor {
AstNode* const stmtsp = nodep->stmtsp()->unlinkFrBackWithNext();
// Create 'if' statement, if needed
if (!m_lastSenp || !nodep->sensesp()->sameTree(m_lastSenp)) {
if (!m_lastSenp || !nodep->sentreep()->sameTree(m_lastSenp)) {
VNRelinker relinker;
nodep->unlinkFrBack(&relinker);
clearLastSen();
m_lastSenp = nodep->sensesp();
m_lastSenp = nodep->sentreep();
// Make a new if statement
m_lastIfp = makeActiveIf(m_lastSenp);
relinker.relink(m_lastIfp);

View File

@ -150,6 +150,7 @@ class CombineVisitor final : VNVisitor {
return replaced;
}
// cppcheck-suppress constParameterPointer
void process(AstNetlist* netlistp) {
// First, remove empty functions. We need to do this separately, because removing
// calls can change the hashes of the callers.

View File

@ -52,7 +52,7 @@ string V3Common::makeToStringCall(AstNodeDType* nodep, const std::string& lhs) {
static void makeVlToString(AstClass* nodep) {
AstCFunc* const funcp
= new AstCFunc{nodep->fileline(), "VL_TO_STRING", nullptr, "std::string"};
funcp->argTypes("const VlClassRef<" + EmitCBase::prefixNameProtect(nodep) + ">& obj");
funcp->argTypes("const VlClassRef<" + EmitCUtil::prefixNameProtect(nodep) + ">& obj");
funcp->isMethod(false);
funcp->isConst(false);
funcp->isStatic(false);
@ -66,7 +66,7 @@ static void makeVlToString(AstClass* nodep) {
static void makeVlToString(AstIface* nodep) {
AstCFunc* const funcp
= new AstCFunc{nodep->fileline(), "VL_TO_STRING", nullptr, "std::string"};
funcp->argTypes("const " + EmitCBase::prefixNameProtect(nodep) + "* obj");
funcp->argTypes("const " + EmitCUtil::prefixNameProtect(nodep) + "* obj");
funcp->isMethod(false);
funcp->isConst(false);
funcp->isStatic(false);
@ -81,7 +81,7 @@ static void makeVlToString(AstNodeUOrStructDType* nodep) {
UASSERT_OBJ(modp, nodep, "Unlinked struct package");
AstCFunc* const funcp
= new AstCFunc{nodep->fileline(), "VL_TO_STRING", nullptr, "std::string"};
funcp->argTypes("const " + EmitCBase::prefixNameProtect(nodep) + "& obj");
funcp->argTypes("const " + EmitCUtil::prefixNameProtect(nodep) + "& obj");
funcp->isMethod(false);
funcp->isConst(false);
funcp->isStatic(false);
@ -127,7 +127,8 @@ static void makeToStringMiddle(AstClass* nodep) {
if (const auto* const varp = VN_CAST(itemp, Var)) {
if (!varp->isParam() && !varp->isInternal()
&& !(varp->dtypeSkipRefp()->basicp()
&& varp->dtypeSkipRefp()->basicp()->isRandomGenerator())) {
&& (varp->dtypeSkipRefp()->basicp()->isRandomGenerator()
|| varp->dtypeSkipRefp()->basicp()->isStdRandomGenerator()))) {
string stmt = "out += \"";
stmt += comma;
comma = ", ";
@ -144,7 +145,7 @@ static void makeToStringMiddle(AstClass* nodep) {
string stmt = "out += ";
if (!comma.empty()) stmt += "\", \"+ ";
// comma = ", "; // Nothing further so not needed
stmt += EmitCBase::prefixNameProtect(nodep->extendsp()->dtypep());
stmt += EmitCUtil::prefixNameProtect(nodep->extendsp()->dtypep());
stmt += "::to_string_middle();\n";
nodep->user1(true); // So what we extend dumps this
funcp->addStmtsp(new AstCStmt{nodep->fileline(), stmt});

File diff suppressed because it is too large Load Diff

View File

@ -26,15 +26,9 @@
class V3Const final {
public:
static AstNode* constifyParamsEdit(AstNode* nodep) VL_MT_DISABLED;
static AstNodeExpr* constifyParamsEdit(AstNodeExpr* exprp) VL_MT_DISABLED {
return VN_AS(constifyParamsEdit(static_cast<AstNode*>(exprp)), NodeExpr);
}
static AstNode* constifyParamsNoWarnEdit(AstNode* nodep) VL_MT_DISABLED;
static AstNodeExpr* constifyParamsNoWarnEdit(AstNodeExpr* exprp) VL_MT_DISABLED {
return VN_AS(constifyParamsNoWarnEdit(static_cast<AstNode*>(exprp)), NodeExpr);
}
static AstNode* constifyGenerateParamsEdit(AstNode* nodep) VL_MT_DISABLED;
static void constifyParamsEdit(AstNode* nodep) VL_MT_DISABLED;
static void constifyParamsNoWarnEdit(AstNode* nodep) VL_MT_DISABLED;
static void constifyGenerateParamsEdit(AstNode* nodep) VL_MT_DISABLED;
// Only do constant pushing, without removing dead logic
static void constifyAllLive(AstNetlist* nodep) VL_MT_DISABLED;
// Everything that's possible

View File

@ -276,7 +276,7 @@ public:
const VPragmaType pragma = VPragmaType::COVERAGE_BLOCK_OFF;
if (!nodep->unnamed()) {
for (const string& i : m_coverageOffBlocks) {
if (VString::wildmatch(nodep->name(), i)) {
if (VString::wildmatch(nodep->prettyOrigOrName(), i)) {
nodep->addStmtsp(new AstPragma{nodep->fileline(), pragma});
}
}
@ -441,7 +441,7 @@ using V3ControlFileResolver = V3ControlWildcardResolver<V3ControlFile>;
class V3ControlScopeTraceEntry final {
public:
const string m_scope; // Scope or regexp to match
const bool m_on = false; // True to enable message
const bool m_on; // True to enable message
int m_levels = 0; // # levels, 0 = all, 1 = only this, ...
// CONSTRUCTORS
V3ControlScopeTraceEntry(const string& scope, bool on, int levels)
@ -479,8 +479,7 @@ class V3ControlScopeTraceResolver final {
public:
void addScopeTraceOn(bool on, const string& scope, int levels) {
UINFO(9, "addScopeTraceOn " << on << " '" << scope << "' "
<< " levels=" << levels);
UINFO(9, "addScopeTraceOn " << on << " '" << scope << "' " << " levels=" << levels);
m_entries.emplace_back(V3ControlScopeTraceEntry{scope, on, levels});
m_matchCache.clear();
}
@ -494,7 +493,7 @@ public:
}
bool getScopeTraceOn(const string& scope) {
// Apply in the order the user provided them, so they can choose on/off preferencing
// Apply in the order the user provided them, so they can choose on/off preferences
int maxLevel = 1;
for (const auto& ch : scope) {
if (ch == '.') ++maxLevel;
@ -724,49 +723,51 @@ void V3Control::addVarAttr(FileLine* fl, const string& module, const string& fta
void V3Control::applyCase(AstCase* nodep) {
const string& filename = nodep->fileline()->filename();
V3ControlFile* filep = V3ControlResolver::s().files().resolve(filename);
V3ControlFile* const filep = V3ControlResolver::s().files().resolve(filename);
if (filep) filep->applyCase(nodep);
}
void V3Control::applyCoverageBlock(AstNodeModule* modulep, AstBegin* nodep) {
const string& filename = nodep->fileline()->filename();
V3ControlFile* filep = V3ControlResolver::s().files().resolve(filename);
V3ControlFile* const filep = V3ControlResolver::s().files().resolve(filename);
if (filep) filep->applyBlock(nodep);
const string& modname = modulep->name();
V3ControlModule* modp = V3ControlResolver::s().modules().resolve(modname);
const string& modname = modulep->prettyOrigOrName();
V3ControlModule* const modp = V3ControlResolver::s().modules().resolve(modname);
if (modp) modp->applyBlock(nodep);
}
void V3Control::applyIgnores(FileLine* filelinep) {
const string& filename = filelinep->filename();
V3ControlFile* filep = V3ControlResolver::s().files().resolve(filename);
V3ControlFile* const filep = V3ControlResolver::s().files().resolve(filename);
if (filep) filep->applyIgnores(filelinep);
}
void V3Control::applyModule(AstNodeModule* modulep) {
const string& modname = modulep->origName();
V3ControlModule* modp = V3ControlResolver::s().modules().resolve(modname);
const string& modname = modulep->prettyOrigOrName();
V3ControlModule* const modp = V3ControlResolver::s().modules().resolve(modname);
if (modp) modp->apply(modulep);
}
void V3Control::applyFTask(AstNodeModule* modulep, AstNodeFTask* ftaskp) {
const string& modname = modulep->name();
V3ControlModule* modp = V3ControlResolver::s().modules().resolve(modname);
const string& modname = modulep->prettyOrigOrName();
V3ControlModule* const modp = V3ControlResolver::s().modules().resolve(modname);
if (!modp) return;
const V3ControlFTask* const ftp = modp->ftasks().resolve(ftaskp->name());
const V3ControlFTask* const ftp = modp->ftasks().resolve(ftaskp->prettyOrigOrName());
if (ftp) ftp->apply(ftaskp);
}
void V3Control::applyVarAttr(AstNodeModule* modulep, AstNodeFTask* ftaskp, AstVar* varp) {
void V3Control::applyVarAttr(const AstNodeModule* modulep, const AstNodeFTask* ftaskp,
AstVar* varp) {
V3ControlVar* vp;
V3ControlModule* modp = V3ControlResolver::s().modules().resolve(modulep->name());
V3ControlModule* const modp
= V3ControlResolver::s().modules().resolve(modulep->prettyOrigOrName());
if (!modp) return;
if (ftaskp) {
V3ControlFTask* ftp = modp->ftasks().resolve(ftaskp->name());
V3ControlFTask* const ftp = modp->ftasks().resolve(ftaskp->prettyOrigOrName());
if (!ftp) return;
vp = ftp->vars().resolve(varp->name());
vp = ftp->vars().resolve(varp->prettyOrigOrName());
} else {
vp = modp->vars().resolve(varp->name());
vp = modp->vars().resolve(varp->prettyOrigOrName());
}
if (vp) vp->apply(varp);
}
@ -796,8 +797,8 @@ bool V3Control::containsMTaskProfileData() {
return V3ControlResolver::s().containsMTaskProfileData();
}
bool V3Control::waive(FileLine* filelinep, V3ErrorCode code, const string& message) {
V3ControlFile* filep = V3ControlResolver::s().files().resolve(filelinep->filename());
bool V3Control::waive(const FileLine* filelinep, V3ErrorCode code, const string& message) {
V3ControlFile* const filep = V3ControlResolver::s().files().resolve(filelinep->filename());
if (!filep) return false;
return filep->waive(code, message);
}

View File

@ -51,7 +51,8 @@ public:
static void applyFTask(AstNodeModule* modulep, AstNodeFTask* ftaskp);
static void applyIgnores(FileLine* filelinep);
static void applyModule(AstNodeModule* modulep);
static void applyVarAttr(AstNodeModule* modulep, AstNodeFTask* ftaskp, AstVar* varp);
static void applyVarAttr(const AstNodeModule* modulep, const AstNodeFTask* ftaskp,
AstVar* varp);
static int getHierWorkers(const string& model);
static FileLine* getHierWorkersFileLine(const string& model);
@ -64,7 +65,7 @@ public:
static bool containsMTaskProfileData();
static bool waive(FileLine* filelinep, V3ErrorCode code, const string& message);
static bool waive(const FileLine* filelinep, V3ErrorCode code, const string& message);
};
#endif // Guard

View File

@ -35,21 +35,32 @@
VL_DEFINE_DEBUG_FUNCTIONS;
class ExprCoverageEligibleVisitor final : public VNVisitor {
class ExprCoverageEligibleVisitor final : public VNVisitorConst {
// STATE
bool m_eligible = true;
static bool elemDTypeEligible(const AstNodeDType* dtypep) {
dtypep = dtypep->skipRefp();
if (const AstNodeDType* const dtp = dtypep->virtRefDTypep()) {
if (!elemDTypeEligible(dtp)) return false;
}
if (const AstNodeDType* const dtp = dtypep->virtRefDType2p()) {
if (!elemDTypeEligible(dtp)) return false;
}
return !VN_IS(dtypep, ClassRefDType);
}
void visit(AstNodeVarRef* nodep) override {
AstNodeDType* dtypep = nodep->varp()->dtypep();
// Class objecs and references not supported for expression coverage
const AstNodeDType* const dtypep = nodep->varp()->dtypep();
// Class objects and references not supported for expression coverage
// because the object may not persist until the point at which
// coverage data is gathered
// This could be resolved in the future by protecting against dereferrencing
// null pointers when cloning the expression for expression coverage
if (VN_CAST(dtypep, ClassRefDType)) {
m_eligible = false;
if (dtypep && elemDTypeEligible(dtypep)) {
iterateChildrenConst(nodep);
} else {
iterateChildren(nodep);
m_eligible = false;
}
}
@ -57,13 +68,13 @@ class ExprCoverageEligibleVisitor final : public VNVisitor {
if (!nodep->isExprCoverageEligible()) {
m_eligible = false;
} else {
iterateChildren(nodep);
iterateChildrenConst(nodep);
}
}
public:
// CONSTRUCTORS
explicit ExprCoverageEligibleVisitor(AstNode* nodep) { iterateChildren(nodep); }
explicit ExprCoverageEligibleVisitor(AstNode* nodep) { iterateChildrenConst(nodep); }
~ExprCoverageEligibleVisitor() override = default;
bool eligible() { return m_eligible; }
@ -151,7 +162,7 @@ class CoverageVisitor final : public VNVisitor {
// METHODS
const char* varIgnoreToggle(AstVar* nodep) {
const char* varIgnoreToggle(const AstVar* nodep) {
// Return true if this shouldn't be traced
// See also similar rule in V3TraceDecl::varIgnoreTrace
if (!nodep->isToggleCoverable()) return "Not relevant signal type";
@ -169,22 +180,8 @@ class CoverageVisitor final : public VNVisitor {
return nullptr;
}
AstCoverInc* newCoverInc(FileLine* fl, const string& hier, const string& page_prefix,
const string& comment, const string& linescov, int offset,
AstCoverInc* newCoverInc(FileLine* fl, AstNodeCoverDecl* const declp,
const string& trace_var_name) {
// We could use the basename of the filename to the page, but seems
// better for code from an include file to be listed under the
// module using it rather than the include file.
// Note the module name could have parameters appended, we'll consider this
// a feature as it allows for each parameterized block to be counted separately.
// Someday the user might be allowed to specify a different page suffix
const string page = page_prefix + "/" + m_modp->prettyName();
AstCoverDecl* const declp = new AstCoverDecl{fl, page, comment, linescov, offset};
declp->hier(hier);
m_modp->addStmtsp(declp);
UINFO(9, "new " << declp);
AstCoverInc* const incp = new AstCoverInc{fl, declp};
if (!trace_var_name.empty()
&& v3Global.opt.traceCoverage()
@ -206,7 +203,7 @@ class CoverageVisitor final : public VNVisitor {
}
return incp;
}
string traceNameForLine(AstNode* nodep, const string& type) {
string traceNameForLine(const AstNode* nodep, const string& type) {
string name = "vlCoverageLineTrace_" + nodep->fileline()->filebasenameNoExt() + "__"
+ cvtToStr(nodep->fileline()->lineno()) + "_" + type;
if (const uint32_t suffix = m_varnames[name]++) name += "_" + cvtToStr(suffix);
@ -299,16 +296,18 @@ class CoverageVisitor final : public VNVisitor {
{
VL_RESTORER(m_inLoopNotBody);
m_inLoopNotBody = true;
iterateAndNextNull(nodep->precondsp());
iterateNull(nodep->condp());
iterateAndNextNull(nodep->incsp());
}
iterateAndNextNull(nodep->stmtsp());
if (m_state.lineCoverageOn(nodep)) {
lineTrack(nodep);
AstCoverOtherDecl* const declp
= new AstCoverOtherDecl{nodep->fileline(), "v_line/" + m_modp->prettyName(),
"block", linesCov(m_state, nodep), 0};
m_modp->addStmtsp(declp);
AstNode* const newp
= newCoverInc(nodep->fileline(), "", "v_line", "block", linesCov(m_state, nodep),
0, traceNameForLine(nodep, "block"));
= newCoverInc(nodep->fileline(), declp, traceNameForLine(nodep, "block"));
insertProcStatement(nodep, newp);
}
}
@ -344,9 +343,12 @@ class CoverageVisitor final : public VNVisitor {
iterateChildren(nodep);
if (m_state.lineCoverageOn(nodep)) {
lineTrack(nodep);
AstCoverOtherDecl* const declp
= new AstCoverOtherDecl{nodep->fileline(), "v_line/" + m_modp->prettyName(),
"block", linesCov(m_state, nodep), 0};
m_modp->addStmtsp(declp);
AstNode* const newp
= newCoverInc(nodep->fileline(), "", "v_line", "block", linesCov(m_state, nodep),
0, traceNameForLine(nodep, "block"));
= newCoverInc(nodep->fileline(), declp, traceNameForLine(nodep, "block"));
insertProcStatement(nodep, newp);
}
}
@ -392,35 +394,23 @@ class CoverageVisitor final : public VNVisitor {
}
}
void toggleVarBottom(const ToggleEnt& above, const AstVar* varp) {
void toggleVarBottom(const ToggleEnt& above, const AstVar* varp, const VNumRange& range) {
const std::string hierPrefix
= (m_beginHier != "") ? AstNode::prettyName(m_beginHier) + "." : "";
AstCoverToggleDecl* const declp
= new AstCoverToggleDecl{varp->fileline(), "v_toggle/" + m_modp->prettyName(),
hierPrefix + varp->name() + above.m_comment, range};
m_modp->addStmtsp(declp);
AstCoverToggle* const newp = new AstCoverToggle{
varp->fileline(),
newCoverInc(varp->fileline(), "", "v_toggle",
hierPrefix + varp->name() + above.m_comment, "", 0, ""),
varp->fileline(), newCoverInc(varp->fileline(), declp, ""),
above.m_varRefp->cloneTree(false), above.m_chgRefp->cloneTree(false)};
m_modp->addStmtsp(newp);
}
void toggleVarRecurse(const AstNodeDType* const dtypep, const int depth, // per-iteration
const ToggleEnt& above, const AstVar* const varp) { // Constant
if (const AstBasicDType* const bdtypep = VN_CAST(dtypep, BasicDType)) {
if (bdtypep->isRanged()) {
for (int index_docs = bdtypep->lo(); index_docs < bdtypep->hi() + 1;
++index_docs) {
const int index_code = index_docs - bdtypep->lo();
ToggleEnt newent{above.m_comment + "["s + cvtToStr(index_docs) + "]",
new AstSel{varp->fileline(),
above.m_varRefp->cloneTree(false), index_code, 1},
new AstSel{varp->fileline(),
above.m_chgRefp->cloneTree(false), index_code, 1}};
toggleVarBottom(newent, varp);
newent.cleanup();
}
} else {
toggleVarBottom(above, varp);
}
if (const AstBasicDType* const basicp = VN_CAST(dtypep, BasicDType)) {
toggleVarBottom(above, varp, basicp->nrange());
} else if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) {
for (int index_docs = adtypep->lo(); index_docs <= adtypep->hi(); ++index_docs) {
const int index_code = index_docs - adtypep->lo();
@ -448,7 +438,7 @@ class CoverageVisitor final : public VNVisitor {
if (adtypep->packed()) {
for (AstMemberDType* itemp = adtypep->membersp(); itemp;
itemp = VN_AS(itemp->nextp(), MemberDType)) {
AstNodeDType* const subtypep = itemp->subDTypep()->skipRefp();
const AstNodeDType* const subtypep = itemp->subDTypep()->skipRefp();
const int index_code = itemp->lsb();
ToggleEnt newent{
above.m_comment + "."s + itemp->name(),
@ -533,20 +523,26 @@ class CoverageVisitor final : public VNVisitor {
iterate(nodep->thenp());
lineTrack(nodep);
AstNodeExpr* const thenp = nodep->thenp()->unlinkFrBack();
nodep->thenp(new AstExprStmt{thenp->fileline(),
newCoverInc(nodep->fileline(), "", "v_branch",
"cond_then", linesCov(m_state, nodep), 0,
traceNameForLine(nodep, "cond_then")),
thenp});
AstCoverOtherDecl* const thenDeclp
= new AstCoverOtherDecl{thenp->fileline(), "v_branch/" + m_modp->prettyName(),
"cond_then", linesCov(m_state, nodep), 0};
m_modp->addStmtsp(thenDeclp);
nodep->thenp(new AstExprStmt{
thenp->fileline(),
newCoverInc(nodep->fileline(), thenDeclp, traceNameForLine(nodep, "cond_then")),
thenp});
m_state = lastState;
createHandle(nodep);
iterate(nodep->elsep());
AstNodeExpr* const elsep = nodep->elsep()->unlinkFrBack();
nodep->elsep(new AstExprStmt{elsep->fileline(),
newCoverInc(nodep->fileline(), "", "v_branch",
"cond_else", linesCov(m_state, nodep), 1,
traceNameForLine(nodep, "cond_else")),
elsep});
AstCoverOtherDecl* const elseDeclp
= new AstCoverOtherDecl{thenp->fileline(), "v_branch/" + m_modp->prettyName(),
"cond_else", linesCov(m_state, nodep), 1};
m_modp->addStmtsp(elseDeclp);
nodep->elsep(new AstExprStmt{
elsep->fileline(),
newCoverInc(nodep->fileline(), elseDeclp, traceNameForLine(nodep, "cond_else")),
elsep});
m_state = lastState;
} else {
@ -603,22 +599,32 @@ class CoverageVisitor final : public VNVisitor {
// Normal if. Linecov shows what's inside the if (not condition that is
// always executed)
UINFO(4, " COVER-branch: " << nodep);
nodep->addThensp(newCoverInc(nodep->fileline(), "", "v_branch", "if",
linesCov(ifState, nodep), 0,
traceNameForLine(nodep, "if")));
AstCoverOtherDecl* const ifDeclp
= new AstCoverOtherDecl{nodep->fileline(), "v_branch/" + m_modp->prettyName(),
"if", linesCov(ifState, nodep), 0};
m_modp->addStmtsp(ifDeclp);
nodep->addThensp(
newCoverInc(nodep->fileline(), ifDeclp, traceNameForLine(nodep, "if")));
// The else has a column offset of 1 to uniquify it relative to the if
// As "if" and "else" are more than one character wide, this won't overlap
// another token
nodep->addElsesp(newCoverInc(nodep->fileline(), "", "v_branch", "else",
linesCov(elseState, nodep), 1,
traceNameForLine(nodep, "else")));
AstCoverOtherDecl* const elseDeclp
= new AstCoverOtherDecl{nodep->fileline(), "v_branch/" + m_modp->prettyName(),
"else", linesCov(elseState, nodep), 1};
m_modp->addStmtsp(elseDeclp);
nodep->addElsesp(
newCoverInc(nodep->fileline(), elseDeclp, traceNameForLine(nodep, "else")));
}
// If/else attributes to each block as non-branch coverage
else if (first_elsif || cont_elsif) {
UINFO(4, " COVER-elsif: " << nodep);
if (ifState.lineCoverageOn(nodep)) {
nodep->addThensp(newCoverInc(nodep->fileline(), "", "v_line", "elsif",
linesCov(ifState, nodep), 0,
AstCoverOtherDecl* const elsifDeclp = new AstCoverOtherDecl{
nodep->fileline(), "v_line/" + m_modp->prettyName(), "elsif",
linesCov(ifState, nodep), 0};
m_modp->addStmtsp(elsifDeclp);
nodep->addThensp(newCoverInc(nodep->fileline(), elsifDeclp,
traceNameForLine(nodep, "elsif")));
}
// and we don't insert the else as the child if-else will do so
@ -626,14 +632,21 @@ class CoverageVisitor final : public VNVisitor {
// Cover as separate blocks (not a branch as is not two-legged)
if (ifState.lineCoverageOn(nodep)) {
UINFO(4, " COVER-half-if: " << nodep);
nodep->addThensp(newCoverInc(nodep->fileline(), "", "v_line", "if",
linesCov(ifState, nodep), 0,
traceNameForLine(nodep, "if")));
AstCoverOtherDecl* const ifDeclp = new AstCoverOtherDecl{
nodep->fileline(), "v_line/" + m_modp->prettyName(), "if",
linesCov(ifState, nodep), 0};
m_modp->addStmtsp(ifDeclp);
nodep->addThensp(
newCoverInc(nodep->fileline(), ifDeclp, traceNameForLine(nodep, "if")));
}
if (elseState.lineCoverageOn(nodep)) {
UINFO(4, " COVER-half-el: " << nodep);
nodep->addElsesp(newCoverInc(nodep->fileline(), "", "v_line", "else",
linesCov(elseState, nodep), 1,
AstCoverOtherDecl* const elseDeclp = new AstCoverOtherDecl{
nodep->fileline(), "v_line/" + m_modp->prettyName(), "else",
linesCov(elseState, nodep), 1};
m_modp->addStmtsp(elseDeclp);
nodep->addElsesp(newCoverInc(nodep->fileline(), elseDeclp,
traceNameForLine(nodep, "else")));
}
}
@ -655,9 +668,12 @@ class CoverageVisitor final : public VNVisitor {
if (m_state.lineCoverageOn(nodep)) { // if the case body didn't disable it
lineTrack(nodep);
UINFO(4, " COVER: " << nodep);
nodep->addStmtsp(newCoverInc(nodep->fileline(), "", "v_line", "case",
linesCov(m_state, nodep), 0,
traceNameForLine(nodep, "case")));
AstCoverOtherDecl* const declp
= new AstCoverOtherDecl{nodep->fileline(), "v_line/" + m_modp->prettyName(),
"case", linesCov(m_state, nodep), 0};
m_modp->addStmtsp(declp);
nodep->addStmtsp(
newCoverInc(nodep->fileline(), declp, traceNameForLine(nodep, "case")));
}
}
}
@ -670,9 +686,13 @@ class CoverageVisitor final : public VNVisitor {
if (!nodep->coverincsp() && v3Global.opt.coverageUser()) {
// Note the name may be overridden by V3Assert processing
lineTrack(nodep);
nodep->addCoverincsp(newCoverInc(nodep->fileline(), m_beginHier, "v_user", "cover",
linesCov(m_state, nodep), 0,
m_beginHier + "_vlCoverageUserTrace"));
AstCoverOtherDecl* const declp
= new AstCoverOtherDecl{nodep->fileline(), "v_user/" + m_modp->prettyName(),
"cover", linesCov(m_state, nodep), 0};
declp->hier(m_beginHier);
m_modp->addStmtsp(declp);
nodep->addCoverincsp(
newCoverInc(nodep->fileline(), declp, m_beginHier + "_vlCoverageUserTrace"));
}
}
void visit(AstStop* nodep) override {
@ -744,8 +764,10 @@ class CoverageVisitor final : public VNVisitor {
}
comment += ") => ";
comment += (m_objective ? '1' : '0');
AstNode* const newp
= newCoverInc(fl, "", "v_expr", comment, "", 0, traceNameForLine(nodep, name));
AstCoverOtherDecl* const declp = new AstCoverOtherDecl{
nodep->fileline(), "v_expr/" + m_modp->prettyName(), comment, "", 0};
m_modp->addStmtsp(declp);
AstNode* const newp = newCoverInc(fl, declp, traceNameForLine(nodep, name));
UASSERT_OBJ(condp, nodep, "No terms in expression coverage branch");
AstIf* const ifp = new AstIf{fl, condp, newp, nullptr};
ifp->user2(true);
@ -833,7 +855,7 @@ class CoverageVisitor final : public VNVisitor {
// not be flagged as redundant or impossible, however the results will
// still be valid, albeit messier
for (CoverTerm& term : l) {
if (AstVarRef* const refp = VN_CAST(term.m_exprp, VarRef)) {
if (const AstVarRef* const refp = VN_CAST(term.m_exprp, VarRef)) {
varps[term.m_objective].insert(refp->varp());
} else {
strs[term.m_objective].insert(term.m_emitV);
@ -843,7 +865,7 @@ class CoverageVisitor final : public VNVisitor {
bool impossible = false;
for (CoverTerm& term : r) {
bool redundant = false;
if (AstNodeVarRef* const refp = VN_CAST(term.m_exprp, NodeVarRef)) {
if (const AstNodeVarRef* const refp = VN_CAST(term.m_exprp, NodeVarRef)) {
if (varps[term.m_objective].find(refp->varp())
!= varps[term.m_objective].end())
redundant = true;
@ -858,7 +880,6 @@ class CoverageVisitor final : public VNVisitor {
!= strs[!term.m_objective].end())
impossible = true;
}
if (!redundant) expr.push_back(term);
}
if (!impossible) m_exprs.push_back(std::move(expr));

View File

@ -48,7 +48,7 @@ class CoverageJoinVisitor final : public VNVisitor {
// Note uses user4
V3DupFinder dupFinder; // Duplicate code detection
// Hash all of the original signals we toggle cover
for (AstCoverToggle* nodep : m_toggleps) dupFinder.insert(nodep->origp());
for (const AstCoverToggle* const nodep : m_toggleps) dupFinder.insert(nodep->origp());
if (dumpLevel() || debug() >= 9)
dupFinder.dumpFile(v3Global.debugFilename("coveragejoin") + ".hash", false);
// Find if there are any duplicates
@ -75,7 +75,7 @@ class CoverageJoinVisitor final : public VNVisitor {
// The CoverDecl the duplicate pointed to now needs to point to the
// original's data. I.e. the duplicate will get the coverage number
// from the non-duplicate
AstCoverDecl* const datadeclp = nodep->incp()->declp()->dataDeclThisp();
AstNodeCoverDecl* const datadeclp = nodep->incp()->declp()->dataDeclThisp();
removep->incp()->declp()->dataDeclp(datadeclp);
UINFO(8, " new " << removep->incp()->declp());
// Mark the found node as a duplicate of the first node

View File

@ -85,6 +85,7 @@ class DeadVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
// cppcheck-suppress constParameterPointer
void checkAll(AstNode* nodep) {
if (AstNode* const subnodep = nodep->dtypep()) {
if (nodep != subnodep // Not NodeDTypes reference themselves
@ -334,8 +335,8 @@ class DeadVisitor final : public VNVisitor {
checkAll(typedefp);
}
}
bool shouldDeleteTypedef(AstTypedef* typedefp) {
if (auto* const structp = VN_CAST(typedefp->subDTypep(), NodeUOrStructDType)) {
bool shouldDeleteTypedef(const AstTypedef* typedefp) {
if (const auto* const structp = VN_CAST(typedefp->subDTypep(), NodeUOrStructDType)) {
if (structp->user1() && !structp->packed()) return false;
}
return m_elimCells && !typedefp->attrPublic();
@ -367,7 +368,7 @@ class DeadVisitor final : public VNVisitor {
}
}
}
bool mightElimVar(AstVar* nodep) const {
bool mightElimVar(const AstVar* nodep) const {
if (nodep->isSigPublic()) return false; // Can't elim publics!
if (nodep->isIO() || nodep->isClassMember() || nodep->sensIfacep()) return false;
if (nodep->isTemp() && !nodep->isTrace()) return true;
@ -473,7 +474,9 @@ class DeadVisitor final : public VNVisitor {
}
}
// cppcheck-suppress constParameterPointer
void preserveTopIfaces(AstNetlist* rootp) {
// cppcheck-suppress constVariablePointer
for (AstNodeModule* modp = rootp->modulesp(); modp && modp->level() <= 2;
modp = VN_AS(modp->nextp(), NodeModule)) {
for (AstNode* subnodep = modp->stmtsp(); subnodep; subnodep = subnodep->nextp()) {

View File

@ -19,7 +19,7 @@
// For the Pre/Post scheduling semantics, see V3OrderGraph.
//
// There are several "Schemes" we can choose from for implementing a
// non-blocking assignment (NBA), repserented by an AstAssignDly.
// non-blocking assignment (NBA), represented by an AstAssignDly.
//
// It is assumed and required in this pass that each NBA updates at
// most one variable. Earlier passes should have ensured this.
@ -220,6 +220,7 @@ class DelayedVisitor final : public VNVisitor {
// Remove duplicates
V3Const::constifyExpensiveEdit(m_senTreep);
}
// cppcheck-suppress constParameterPointer
void addSensitivity(AstSenTree* nodep) { addSensitivity(nodep->sensesp()); }
};
@ -245,7 +246,9 @@ class DelayedVisitor final : public VNVisitor {
// AstVar::user1() -> bool. Set true if already issued MULTIDRIVEN warning
// AstVarRef::user1() -> bool. Set true if target of NBA
// AstAssignDly::user1() -> bool. Set true if already visited
// AstAssignDly::user2p() -> AstVarScope*: Scope this AstAssignDelay is under
// AstNodeModule::user1p() -> std::unorded_map<std::string, AstVar*> temp map via m_varMap
// AstScope::user1() -> int: Temporary counter for this scope
// AstVarScope::user1p() -> VarScopeInfo via m_vscpInfo
// AstVarScope::user2p() -> AstVarRef*: First write reference to the Variable
// AstVarScope::user3p() -> std::vector<WriteReference> via m_writeRefs;
@ -263,6 +266,7 @@ class DelayedVisitor final : public VNVisitor {
AstActive* m_activep = nullptr; // Current activate
const AstCFunc* m_cfuncp = nullptr; // Current public C Function
AstNodeProcedure* m_procp = nullptr; // Current process
AstScope* m_scopep = nullptr; // Current scope
bool m_inLoop = false; // True in for loops
bool m_inSuspendableOrFork = false; // True in suspendable processes and forks
bool m_ignoreBlkAndNBlk = false; // Suppress delayed assignment BLKANDNBLK
@ -383,18 +387,18 @@ class DelayedVisitor final : public VNVisitor {
if (bIt != blkRefs.end() && nIt != nbaRefs.end()) {
const Ref& blkRef = *bIt;
const Ref& nbaRef = *nIt;
vscp->v3warn(BLKANDNBLK,
"Unsupported: Blocking and non-blocking assignments to "
"potentially overlapping bits of same packed variable: "
<< vscp->varp()->prettyNameQ() << '\n'
<< vscp->warnContextPrimary() << '\n'
<< blkRef.m_refp->warnOther() << "... Location of blocking assignment"
<< " (bits [" << blkRef.m_msb << ":" << blkRef.m_lsb << "])\n"
<< blkRef.m_refp->warnContextSecondary() << '\n'
<< nbaRef.m_refp->warnOther()
<< "... Location of nonblocking assignment"
<< " (bits [" << nbaRef.m_msb << ":" << nbaRef.m_lsb << "])\n"
<< nbaRef.m_refp->warnContextSecondary());
vscp->v3warn(BLKANDNBLK, "Unsupported: Blocking and non-blocking assignments to "
"potentially overlapping bits of same packed variable: "
<< vscp->varp()->prettyNameQ() << '\n'
<< vscp->warnContextPrimary() << '\n'
<< blkRef.m_refp->warnOther()
<< "... Location of blocking assignment" << " (bits ["
<< blkRef.m_msb << ":" << blkRef.m_lsb << "])\n"
<< blkRef.m_refp->warnContextSecondary() << '\n'
<< nbaRef.m_refp->warnOther()
<< "... Location of nonblocking assignment" << " (bits ["
<< nbaRef.m_msb << ":" << nbaRef.m_lsb << "])\n"
<< nbaRef.m_refp->warnContextSecondary());
}
return true;
@ -438,7 +442,7 @@ class DelayedVisitor final : public VNVisitor {
// Check for mixed usage (this also warns if not OK)
if (checkMixedUsage(vscp, isIntegralOrPacked)) {
// If it's a variable updated by both blocking and non-blocking
// asignments, use the ShadowVarMasked schem if masked update is
// assignments, use the ShadowVarMasked schem if masked update is
// possible. This can handle blocking and non-blocking updates to
// inpdendent parts correctly at run-time, and always works, even
// in loops or other dynamic context.
@ -466,6 +470,11 @@ class DelayedVisitor final : public VNVisitor {
varp = new AstVar{flp, VVarType::BLOCKTEMP, name, dtypep};
modp->addStmtsp(varp);
}
// We should be able to assert this here, but unfortuantely
// 'isAssignmentCompatible' does not exist as of right now.
// UASSERT_OBJ(isAssignmentCompatible(varp->dtypep(), dtypep), flp, "Invalid temporary");
// Create the AstVarScope
AstVarScope* const varscp = new AstVarScope{flp, scopep, varp};
scopep->addVarsp(varscp);
@ -531,6 +540,21 @@ class DelayedVisitor final : public VNVisitor {
return lhsp;
}
// Create a unique temporary variable name
std::string uniqueTmpName(AstScope* scopep, const AstVarScope* vscp, VarScopeInfo& vscpInfo) {
std::stringstream ss;
ss << "__" << vscp->varp()->shortName() + "__v";
// If the assignment is in the same scope as the variable, just
// use the temporary counter of the variable.
if (scopep == vscp->scopep()) {
ss << vscpInfo.m_nTmp++;
} else {
// Otherwise use the temporary counter of the scope of the assignment.
ss << scopep->user1Inc() << "_hierarchical";
}
return ss.str();
}
// Create a temporary variable in the given 'scopep', with the given 'name', and with 'dtypep'
// type, with the bits selected by 'sLsbp'/'sWidthp' set to 'valuep', other bits set to zero.
// Insert new statements before 'insertp'.
@ -562,16 +586,23 @@ class DelayedVisitor final : public VNVisitor {
const std::string name = "__Vdly__" + vscp->varp()->shortName();
AstVarScope* const shadowVscp = createTemp(flp, scopep, name, vscp->dtypep());
vscpInfo.shadowVariableKit().vscp = shadowVscp;
// Mark both for V3LifePsot
vscp->optimizeLifePost(true);
shadowVscp->optimizeLifePost(true);
// Create the AstActive for the Pre/Post logic
AstActive* const activep = new AstActive{flp, "nba-shadow-variable", vscpInfo.senTreep()};
activep->sensesStorep(vscpInfo.senTreep());
activep->senTreeStorep(vscpInfo.senTreep());
scopep->addBlocksp(activep);
// Add 'Pre' scheduled 'shadowVariable = originalVariable' assignment
activep->addStmtsp(new AstAssignPre{flp, new AstVarRef{flp, shadowVscp, VAccess::WRITE},
new AstVarRef{flp, vscp, VAccess::READ}});
AstAlwaysPre* const prep = new AstAlwaysPre{flp};
activep->addStmtsp(prep);
prep->addStmtsp(new AstAssign{flp, new AstVarRef{flp, shadowVscp, VAccess::WRITE},
new AstVarRef{flp, vscp, VAccess::READ}});
// Add 'Post' scheduled 'originalVariable = shadowVariable' assignment
activep->addStmtsp(new AstAssignPost{flp, new AstVarRef{flp, vscp, VAccess::WRITE},
new AstVarRef{flp, shadowVscp, VAccess::READ}});
AstAlwaysPost* const postp = new AstAlwaysPost{flp};
activep->addStmtsp(postp);
postp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, vscp, VAccess::WRITE},
new AstVarRef{flp, shadowVscp, VAccess::READ}});
}
void convertSchemeShadowVar(AstAssignDly* nodep, AstVarScope* vscp, VarScopeInfo& vscpInfo) {
UASSERT_OBJ(vscpInfo.m_scheme == Scheme::ShadowVar, vscp, "Inconsistent NBA scheme");
@ -603,7 +634,7 @@ class DelayedVisitor final : public VNVisitor {
// Create the AstActive for the Post logic
AstActive* const activep
= new AstActive{flp, "nba-shadow-var-masked", vscpInfo.senTreep()};
activep->sensesStorep(vscpInfo.senTreep());
activep->senTreeStorep(vscpInfo.senTreep());
scopep->addBlocksp(activep);
// Add 'Post' scheduled process for the commit and mask clear
AstAlwaysPost* const postp = new AstAlwaysPost{flp};
@ -657,7 +688,7 @@ class DelayedVisitor final : public VNVisitor {
AstScope* const scopep = vscp->scopep();
// Create the AstActive for the Pre/Post logic
AstActive* const activep = new AstActive{flp, "nba-flag-shared", vscpInfo.senTreep()};
activep->sensesStorep(vscpInfo.senTreep());
activep->senTreeStorep(vscpInfo.senTreep());
scopep->addBlocksp(activep);
vscpInfo.flagSharedKit().activep = activep;
// Add 'Post' scheduled process to be populated later
@ -671,11 +702,10 @@ class DelayedVisitor final : public VNVisitor {
void convertSchemeFlagShared(AstAssignDly* nodep, AstVarScope* vscp, VarScopeInfo& vscpInfo) {
UASSERT_OBJ(vscpInfo.m_scheme == Scheme::FlagShared, vscp, "Inconsistent NBA scheme");
FileLine* const flp = vscp->fileline();
AstScope* const scopep = vscp->scopep();
AstScope* const scopep = VN_AS(nodep->user2p(), Scope);
// Base name suffix for signals constructed below
const std::string baseName{"__" + vscp->varp()->shortName() + "__v"
+ std::to_string(vscpInfo.m_nTmp++)};
const std::string baseName = uniqueTmpName(scopep, vscp, vscpInfo);
// Unlink and capture the RHS value
AstNodeExpr* const capturedRhsp
@ -697,6 +727,8 @@ class DelayedVisitor final : public VNVisitor {
consecutive
// ... that use the same scheme
&& prevVscpInfop->m_scheme == Scheme::FlagShared
// ... and are in the same scope as the target variable
&& scopep == vscp->scopep()
// ... and share the same overall update domain
&& prevVscpInfop->senTreep()->sameTree(vscpInfo.senTreep());
@ -708,9 +740,10 @@ class DelayedVisitor final : public VNVisitor {
new AstAssign{flp, new AstVarRef{flp, flagVscp, VAccess::WRITE},
new AstConst{flp, AstConst::BitTrue{}}});
// Add the 'Pre' scheduled reset for the flag
vscpInfo.flagSharedKit().activep->addStmtsp(
new AstAssignPre{flp, new AstVarRef{flp, flagVscp, VAccess::WRITE},
new AstConst{flp, AstConst::BitFalse{}}});
AstAlwaysPre* const prep = new AstAlwaysPre{flp};
vscpInfo.flagSharedKit().activep->addStmtsp(prep);
prep->addStmtsp(new AstAssign{flp, new AstVarRef{flp, flagVscp, VAccess::WRITE},
new AstConst{flp, AstConst::BitFalse{}}});
// Add the 'Post' scheduled commit
AstIf* const ifp = new AstIf{flp, new AstVarRef{flp, flagVscp, VAccess::READ}};
vscpInfo.flagSharedKit().postp->addStmtsp(ifp);
@ -753,7 +786,7 @@ class DelayedVisitor final : public VNVisitor {
AstScope* const scopep = vscp->scopep();
// Create the AstActive for the Pre/Post logic
AstActive* const activep = new AstActive{flp, "nba-flag-unique", vscpInfo.senTreep()};
activep->sensesStorep(vscpInfo.senTreep());
activep->senTreeStorep(vscpInfo.senTreep());
scopep->addBlocksp(activep);
// Add 'Post' scheduled process to be populated later
AstAlwaysPost* const postp = new AstAlwaysPost{flp};
@ -763,11 +796,10 @@ class DelayedVisitor final : public VNVisitor {
void convertSchemeFlagUnique(AstAssignDly* nodep, AstVarScope* vscp, VarScopeInfo& vscpInfo) {
UASSERT_OBJ(vscpInfo.m_scheme == Scheme::FlagUnique, vscp, "Inconsistent NBA scheme");
FileLine* const flp = vscp->fileline();
AstScope* const scopep = vscp->scopep();
AstScope* const scopep = VN_AS(nodep->user2p(), Scope);
// Base name suffix for signals constructed below
const std::string baseName{"__" + vscp->varp()->shortName() + "__v"
+ std::to_string(vscpInfo.m_nTmp++)};
const std::string baseName = uniqueTmpName(scopep, vscp, vscpInfo);
// Unlink and capture the RHS value
AstNodeExpr* const capturedRhsp
@ -818,7 +850,7 @@ class DelayedVisitor final : public VNVisitor {
// Create the AstActive for the Post logic
AstActive* const activep
= new AstActive{flp, "nba-value-queue-whole", vscpInfo.senTreep()};
activep->sensesStorep(vscpInfo.senTreep());
activep->senTreeStorep(vscpInfo.senTreep());
scopep->addBlocksp(activep);
// Add 'Post' scheduled process for the commit
AstAlwaysPost* const postp = new AstAlwaysPost{flp};
@ -837,11 +869,10 @@ class DelayedVisitor final : public VNVisitor {
: vscpInfo.m_scheme == Scheme::ValueQueueWhole,
vscp, "Inconsistent NBA scheme");
FileLine* const flp = vscp->fileline();
AstScope* const scopep = vscp->scopep();
AstScope* const scopep = VN_AS(nodep->user2p(), Scope);
// Base name suffix for signals constructed below
const std::string baseName{"__" + vscp->varp()->shortName() + "__v"
+ std::to_string(vscpInfo.m_nTmp++)};
const std::string baseName = uniqueTmpName(scopep, vscp, vscpInfo);
// Unlink and capture the RHS value
AstNodeExpr* const capturedRhsp
@ -870,7 +901,7 @@ class DelayedVisitor final : public VNVisitor {
return dtypep;
}();
if (AstSel* const lSelp = VN_CAST(lhsNodep, Sel)) {
if (const AstSel* const lSelp = VN_CAST(lhsNodep, Sel)) {
// This is a partial assignment.
// Need to create a mask and widen the value to element size.
lhsNodep = lSelp->fromp();
@ -880,7 +911,7 @@ class DelayedVisitor final : public VNVisitor {
// Create mask value
maskp = [&]() -> AstNodeExpr* {
// Constant mask we can compute here
if (AstConst* const cLsbp = VN_CAST(sLsbp, Const)) {
if (const AstConst* const cLsbp = VN_CAST(sLsbp, Const)) {
AstConst* const cp = new AstConst{flp, AstConst::DTyped{}, eDTypep};
cp->num().setMask(sWidth, cLsbp->toSInt());
return cp;
@ -897,7 +928,7 @@ class DelayedVisitor final : public VNVisitor {
valuep = [&]() -> AstNodeExpr* {
// Constant value with constant select we can compute here
if (AstConst* const cValuep = VN_CAST(valuep, Const)) {
if (AstConst* const cLsbp = VN_CAST(sLsbp, Const)) {
if (const AstConst* const cLsbp = VN_CAST(sLsbp, Const)) {
AstConst* const cp = new AstConst{flp, AstConst::DTyped{}, eDTypep};
cp->num().setAllBits0();
cp->num().opSelInto(cValuep->num(), cLsbp->toSInt(), sWidth);
@ -1051,7 +1082,11 @@ class DelayedVisitor final : public VNVisitor {
}
}
}
void visit(AstScope* nodep) override { iterateChildren(nodep); }
void visit(AstScope* nodep) override {
VL_RESTORER(m_scopep);
m_scopep = nodep;
iterateChildren(nodep);
}
void visit(AstCFunc* nodep) override {
VL_RESTORER(m_cfuncp);
m_cfuncp = nodep;
@ -1063,7 +1098,7 @@ class DelayedVisitor final : public VNVisitor {
VL_RESTORER(m_ignoreBlkAndNBlk);
VL_RESTORER(m_inNonCombLogic);
m_activep = nodep;
AstSenTree* const senTreep = nodep->sensesp();
const AstSenTree* const senTreep = nodep->sentreep();
m_ignoreBlkAndNBlk = senTreep->hasStatic() || senTreep->hasInitial();
m_inNonCombLogic = senTreep->hasClocked();
iterateChildren(nodep);
@ -1087,13 +1122,14 @@ class DelayedVisitor final : public VNVisitor {
// There were some timing domains involved in the process. Add all of them as sensitivities
// of all NBA targets in this process. Note this is a bit of a sledgehammer, we should only
// need those that directly preceed the NBA in control flow, but that is hard to compute,
// need those that directly precede the NBA in control flow, but that is hard to compute,
// so we will hammer away.
// First gather all senItems
AstSenItem* senItemp = nullptr;
for (AstSenTree* const domainp : m_timingDomains) {
senItemp = AstNode::addNext(senItemp, domainp->sensesp()->cloneTree(true));
for (const AstSenTree* const domainp : m_timingDomains) {
if (domainp->sensesp())
senItemp = AstNode::addNext(senItemp, domainp->sensesp()->cloneTree(true));
}
m_timingDomains.clear();
// Add them to all nba targets we gathered in this process
@ -1109,7 +1145,7 @@ class DelayedVisitor final : public VNVisitor {
iterateChildren(nodep);
}
void visit(AstCAwait* nodep) override {
if (nodep->sensesp()) m_timingDomains.insert(nodep->sensesp());
if (nodep->sentreep()) m_timingDomains.insert(nodep->sentreep());
}
void visit(AstFireEvent* nodep) override {
UASSERT_OBJ(v3Global.hasEvents(), nodep, "Inconsistent");
@ -1125,7 +1161,7 @@ class DelayedVisitor final : public VNVisitor {
AstNode* newp = new AstCStmt{flp, blockp};
if (nodep->isDelayed()) {
AstVarRef* const vrefp = VN_AS(eventp, VarRef);
const AstVarRef* const vrefp = VN_AS(eventp, VarRef);
const std::string newvarname = "__Vdly__" + vrefp->varp()->shortName();
AstVarScope* const dlyvscp
= createTemp(flp, vrefp->varScopep()->scopep(), newvarname, 1);
@ -1134,8 +1170,9 @@ class DelayedVisitor final : public VNVisitor {
return new AstVarRef{flp, dlyvscp, access};
};
AstAssignPre* const prep = new AstAssignPre{flp, dlyRef(VAccess::WRITE),
new AstConst{flp, AstConst::BitFalse{}}};
AstAlwaysPre* const prep = new AstAlwaysPre{flp};
prep->addStmtsp(new AstAssign{flp, dlyRef(VAccess::WRITE),
new AstConst{flp, AstConst::BitFalse{}}});
AstAlwaysPost* const postp = new AstAlwaysPost{flp};
{
AstIf* const ifp = new AstIf{flp, dlyRef(VAccess::READ)};
@ -1144,7 +1181,7 @@ class DelayedVisitor final : public VNVisitor {
}
UASSERT_OBJ(m_activep, nodep, "No active to handle FireEvent");
AstActive* const activep = new AstActive{flp, "nba-event", m_activep->sensesp()};
AstActive* const activep = new AstActive{flp, "nba-event", m_activep->sentreep()};
m_activep->addNextHere(activep);
activep->addStmtsp(prep);
activep->addStmtsp(postp);
@ -1169,13 +1206,18 @@ class DelayedVisitor final : public VNVisitor {
}
UASSERT_OBJ(m_procp, nodep, "Delayed assignment not under process");
UASSERT_OBJ(m_activep, nodep, "<= not under sensitivity block");
UASSERT_OBJ(m_scopep, nodep, "<= not under scope");
UASSERT_OBJ(m_inSuspendableOrFork || m_activep->hasClocked(), nodep,
"<= assignment in non-clocked block, should have been converted in V3Active");
// Record scope of this NBA
nodep->user2p(m_scopep);
// Grab the reference to the target of the NBA, also lift ExprStmt statements on the LHS
VL_RESTORER(m_currNbaLhsRefp);
UASSERT_OBJ(!m_currNbaLhsRefp, nodep, "NBAs should not nest");
nodep->lhsp()->foreach([&](AstNode* currp) {
// cppcheck-suppress constVariablePointer
if (AstExprStmt* const exprp = VN_CAST(currp, ExprStmt)) {
// Move statements before the NBA
nodep->addHereThisAsNext(exprp->stmtsp()->unlinkFrBackWithNext());
@ -1212,7 +1254,7 @@ class DelayedVisitor final : public VNVisitor {
vscpInfo.m_inLoop |= m_inLoop;
vscpInfo.m_inSuspOrFork |= m_inSuspendableOrFork;
// Sensitivity might be non-clocked, in a suspendable process, which are handled elsewhere
if (m_activep->sensesp()->hasClocked()) {
if (m_activep->sentreep()->hasClocked()) {
if (vscpInfo.m_fistActivep != m_activep) {
AstVar* const varp = vscp->varp();
if (!varp->user1SetOnce()
@ -1229,7 +1271,7 @@ class DelayedVisitor final : public VNVisitor {
}
}
// Add this sensitivity to the variable
vscpInfo.addSensitivity(m_activep->sensesp());
vscpInfo.addSensitivity(m_activep->sentreep());
}
// Record the NBA for later processing
@ -1261,8 +1303,7 @@ class DelayedVisitor final : public VNVisitor {
}
// Pre/Post logic are created here and their content need no further changes, so ignore.
void visit(AstAssignPre*) override {}
void visit(AstAssignPost*) override {}
void visit(AstAlwaysPre*) override {}
void visit(AstAlwaysPost*) override {}
//--------------------

View File

@ -48,7 +48,7 @@ class DepthVisitor final : public VNVisitor {
void createDeepTemp(AstNodeExpr* nodep) {
UINFO(6, " Deep " << nodep);
// if (debug() >= 9) nodep->dumpTree("- deep: ");
// UINFOTREE(9, nodep, "", "deep");
AstVar* const varp = new AstVar{nodep->fileline(), VVarType::STMTTEMP,
m_tempNames.get(nodep), nodep->dtypep()};
if (m_cfuncp) {

View File

@ -57,7 +57,7 @@ class DepthBlockVisitor final : public VNVisitor {
AstCCall* const callp = new AstCCall{nodep->fileline(), funcp};
callp->dtypeSetVoid();
if (VN_IS(m_modp, Class)) {
funcp->argTypes(EmitCBase::symClassVar());
funcp->argTypes(EmitCUtil::symClassVar());
callp->argTypes("vlSymsp");
}
UINFO(6, " New " << callp);
@ -89,11 +89,11 @@ class DepthBlockVisitor final : public VNVisitor {
if (m_depth > v3Global.opt.compLimitBlocks()) { // Already done
UINFO(4, "DeepBlocks " << m_depth << " " << nodep);
const AstNode* const backp = nodep->backp(); // Only for debug
if (debug() >= 9) backp->dumpTree("- pre : ");
UINFOTREE(9, backp, "", "pre ");
AstCFunc* const funcp = createDeepFunc(nodep);
iterate(funcp);
if (debug() >= 9) backp->dumpTree("- post: ");
if (debug() >= 9) funcp->dumpTree("- func: ");
UINFOTREE(9, backp, "", "post");
UINFOTREE(9, funcp, "", "func");
} else {
iterateChildren(nodep);
}

View File

@ -55,11 +55,11 @@ class DescopeVisitor final : public VNVisitor {
// METHODS
static bool modIsSingleton(AstNodeModule* modp) {
static bool modIsSingleton(const AstNodeModule* modp) {
// True iff there's exactly one instance of this module in the design (including top).
if (modp->isTop()) return true;
int instances = 0;
for (AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
for (const AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (VN_IS(stmtp, Scope)) {
if (++instances > 1) return false;
}
@ -190,12 +190,12 @@ class DescopeVisitor final : public VNVisitor {
}
// Not really any way the user could do this, and we'd need
// to come up with some return value
// newfuncp->addStmtsp(new AstDisplay(newfuncp->fileline(),
// newfuncp->addStmtsp(new AstDisplay{newfuncp->fileline(),
// VDisplayType::DT_WARNING,
// "%%Error: "s+name+"() called with bad
// scope", nullptr));
// newfuncp->addStmtsp(new AstStop(newfuncp->fileline()));
if (debug() >= 9) newfuncp->dumpTree("- newfunc: ");
// scope", nullptr});
// newfuncp->addStmtsp(new AstStop{newfuncp->fileline()});
UINFOTREE(9, newfuncp, "", "newfunc");
} else {
// Only a single function under this name, we can rename it
UINFO(6, " Wrapping " << name << " just one " << topFuncp);

View File

@ -18,6 +18,7 @@
#include "V3Dfg.h"
#include "V3EmitV.h"
#include "V3File.h"
VL_DEFINE_DEBUG_FUNCTIONS;
@ -34,36 +35,220 @@ DfgGraph::~DfgGraph() {
forEachVertex([](DfgVertex& vtxp) { delete &vtxp; });
}
void DfgGraph::addGraph(DfgGraph& other) {
m_size += other.m_size;
other.m_size = 0;
std::unique_ptr<DfgGraph> DfgGraph::clone() const {
const bool scoped = !modulep();
for (DfgVertexVar& vtx : other.m_varVertices) {
vtx.m_userCnt = 0;
vtx.m_graphp = this;
DfgGraph* const clonep = new DfgGraph{modulep(), name()};
// Map from original vertex to clone
std::unordered_map<const DfgVertex*, DfgVertex*> vtxp2clonep(size() * 2);
// Clone constVertices
for (const DfgConst& vtx : m_constVertices) {
DfgConst* const cp = new DfgConst{*clonep, vtx.fileline(), vtx.num()};
vtxp2clonep.emplace(&vtx, cp);
}
m_varVertices.splice(m_varVertices.end(), other.m_varVertices);
for (DfgConst& vtx : other.m_constVertices) {
vtx.m_userCnt = 0;
vtx.m_graphp = this;
// Clone variable vertices
for (const DfgVertexVar& vtx : m_varVertices) {
const DfgVertexVar* const vp = vtx.as<DfgVertexVar>();
DfgVertexVar* cp = nullptr;
switch (vtx.type()) {
case VDfgType::atVarArray: {
if (scoped) {
cp = new DfgVarArray{*clonep, vp->varScopep()};
} else {
cp = new DfgVarArray{*clonep, vp->varp()};
}
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::atVarPacked: {
if (scoped) {
cp = new DfgVarPacked{*clonep, vp->varScopep()};
} else {
cp = new DfgVarPacked{*clonep, vp->varp()};
}
vtxp2clonep.emplace(&vtx, cp);
break;
}
default: {
vtx.v3fatalSrc("Unhandled variable vertex type: " + vtx.typeName());
VL_UNREACHABLE;
break;
}
}
if (AstNode* const tmpForp = vp->tmpForp()) cp->tmpForp(tmpForp);
}
m_constVertices.splice(m_constVertices.end(), other.m_constVertices);
for (DfgVertex& vtx : other.m_opVertices) {
vtx.m_userCnt = 0;
vtx.m_graphp = this;
// Clone operation vertices
for (const DfgVertex& vtx : m_opVertices) {
switch (vtx.type()) {
#include "V3Dfg__gen_clone_cases.h" // From ./astgen
case VDfgType::atSel: {
DfgSel* const cp = new DfgSel{*clonep, vtx.fileline(), vtx.dtypep()};
cp->lsb(vtx.as<DfgSel>()->lsb());
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::atUnitArray: {
DfgUnitArray* const cp = new DfgUnitArray{*clonep, vtx.fileline(), vtx.dtypep()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::atMux: {
DfgMux* const cp = new DfgMux{*clonep, vtx.fileline(), vtx.dtypep()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::atSpliceArray: {
DfgSpliceArray* const cp = new DfgSpliceArray{*clonep, vtx.fileline(), vtx.dtypep()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::atSplicePacked: {
DfgSplicePacked* const cp = new DfgSplicePacked{*clonep, vtx.fileline(), vtx.dtypep()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::atLogic: {
vtx.v3fatalSrc("DfgLogic cannot be cloned");
VL_UNREACHABLE;
break;
}
case VDfgType::atUnresolved: {
vtx.v3fatalSrc("DfgUnresolved cannot be cloned");
VL_UNREACHABLE;
break;
}
default: {
vtx.v3fatalSrc("Unhandled operation vertex type: " + vtx.typeName());
VL_UNREACHABLE;
break;
}
}
}
UASSERT(size() == clonep->size(), "Size of clone should be the same");
// Constants have no inputs
// Hook up inputs of cloned variables
for (const DfgVertexVar& vtx : m_varVertices) {
// All variable vertices are unary
if (const DfgVertex* const srcp = vtx.srcp()) {
vtxp2clonep.at(&vtx)->as<DfgVertexVar>()->srcp(vtxp2clonep.at(srcp));
}
}
// Hook up inputs of cloned operation vertices
for (const DfgVertex& vtx : m_opVertices) {
if (vtx.is<DfgVertexVariadic>()) {
switch (vtx.type()) {
case VDfgType::atSpliceArray: {
const DfgSpliceArray* const vp = vtx.as<DfgSpliceArray>();
DfgSpliceArray* const cp = vtxp2clonep.at(vp)->as<DfgSpliceArray>();
vp->forEachSourceEdge([&](const DfgEdge& edge, size_t i) {
if (const DfgVertex* const srcp = edge.sourcep()) {
cp->addDriver(vp->driverFileLine(i), //
vp->driverLo(i), //
vtxp2clonep.at(srcp));
}
});
break;
}
case VDfgType::atSplicePacked: {
const DfgSplicePacked* const vp = vtx.as<DfgSplicePacked>();
DfgSplicePacked* const cp = vtxp2clonep.at(vp)->as<DfgSplicePacked>();
vp->forEachSourceEdge([&](const DfgEdge& edge, size_t i) {
if (const DfgVertex* const srcVp = edge.sourcep()) {
DfgVertex* const srcCp = vtxp2clonep.at(srcVp);
UASSERT_OBJ(!srcCp->is<DfgLogic>(), srcCp, "Cannot clone DfgLogic");
if (srcVp == vp->defaultp()) {
cp->defaultp(srcCp);
} else {
cp->addDriver(vp->driverFileLine(i), vp->driverLo(i), srcCp);
}
}
});
break;
}
default: {
vtx.v3fatalSrc("Unhandled DfgVertexVariadic sub type: " + vtx.typeName());
VL_UNREACHABLE;
break;
}
}
} else {
DfgVertex* const cp = vtxp2clonep.at(&vtx);
const auto oSourceEdges = vtx.sourceEdges();
auto cSourceEdges = cp->sourceEdges();
UASSERT_OBJ(oSourceEdges.second == cSourceEdges.second, &vtx,
"Mismatched source count");
for (size_t i = 0; i < oSourceEdges.second; ++i) {
if (const DfgVertex* const srcp = oSourceEdges.first[i].sourcep()) {
cSourceEdges.first[i].relinkSource(vtxp2clonep.at(srcp));
}
}
}
}
return std::unique_ptr<DfgGraph>{clonep};
}
void DfgGraph::mergeGraphs(std::vector<std::unique_ptr<DfgGraph>>&& otherps) {
if (otherps.empty()) return;
// NODE STATE
// AstVar/AstVarScope::user2p() -> corresponding DfgVertexVar* in 'this' graph
const VNUser2InUse user2InUse;
// Set up Ast Variable -> DfgVertexVar map for 'this' graph
for (DfgVertexVar& vtx : m_varVertices) vtx.nodep()->user2p(&vtx);
// Merge in each of the other graphs
for (const std::unique_ptr<DfgGraph>& otherp : otherps) {
// Process variables
for (DfgVertexVar* const vtxp : otherp->m_varVertices.unlinkable()) {
// Variabels that are present in 'this', make them use the DfgVertexVar in 'this'.
if (DfgVertexVar* const altp = vtxp->nodep()->user2u().to<DfgVertexVar*>()) {
DfgVertex* const srcp = vtxp->srcp();
UASSERT_OBJ(!srcp || !altp->srcp(), vtxp, "At most one alias should be driven");
vtxp->replaceWith(altp);
if (srcp) altp->srcp(srcp);
VL_DO_DANGLING(vtxp->unlinkDelete(*otherp), vtxp);
continue;
}
// Otherwise they will be moved
vtxp->nodep()->user2p(vtxp);
vtxp->m_userCnt = 0;
vtxp->m_graphp = this;
}
m_varVertices.splice(m_varVertices.end(), otherp->m_varVertices);
// Process constants
for (DfgConst& vtx : otherp->m_constVertices) {
vtx.m_userCnt = 0;
vtx.m_graphp = this;
}
m_constVertices.splice(m_constVertices.end(), otherp->m_constVertices);
// Process operations
for (DfgVertex& vtx : otherp->m_opVertices) {
vtx.m_userCnt = 0;
vtx.m_graphp = this;
}
m_opVertices.splice(m_opVertices.end(), otherp->m_opVertices);
// Update graph sizes
m_size += otherp->m_size;
otherp->m_size = 0;
}
m_opVertices.splice(m_opVertices.end(), other.m_opVertices);
}
std::string DfgGraph::makeUniqueName(const std::string& prefix, size_t n) {
// Construct the tmpNameStub if we have not done so yet
if (m_tmpNameStub.empty()) {
// Use the hash of the graph name (avoid long names and non-identifiers)
const std::string name = V3Hash{m_name}.toString();
const std::string hash = V3Hash{m_name}.toString();
// We need to keep every variable globally unique, and graph hashed
// names might not be, so keep a static table to track multiplicity
static std::unordered_map<std::string, uint32_t> s_multiplicity;
m_tmpNameStub += '_' + name + '_' + std::to_string(s_multiplicity[name]++) + '_';
m_tmpNameStub += '_' + hash + '_' + std::to_string(s_multiplicity[hash]++) + '_';
}
// Assemble the globally unique name
return "__Vdfg" + prefix + m_tmpNameStub + std::to_string(n);
@ -96,17 +281,22 @@ DfgVertexVar* DfgGraph::makeNewVar(FileLine* flp, const std::string& name, AstNo
}
}
static const string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx) + '"'; }
static const std::string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx) + '"'; }
// Dump one DfgVertex in Graphviz format
static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
if (const DfgVarPacked* const varVtxp = vtx.cast<DfgVarPacked>()) {
AstNode* const nodep = varVtxp->nodep();
AstVar* const varp = varVtxp->varp();
const AstNode* const nodep = varVtxp->nodep();
const AstVar* const varp = varVtxp->varp();
os << toDotId(vtx);
os << " [label=\"" << nodep->name() << "\nW" << varVtxp->width() << " / F"
<< varVtxp->fanout() << '"';
os << " [label=\"" << nodep->prettyName() << '\n';
os << cvtToHex(varVtxp) << '\n';
if (const AstNode* const tmpForp = varVtxp->tmpForp()) {
os << "temporary for: " << tmpForp->prettyName() << "\n";
}
varVtxp->dtypep()->dumpSmall(os);
os << " / F" << varVtxp->fanout() << '"';
if (varp->direction() == VDirection::INPUT) {
os << ", shape=box, style=filled, fillcolor=chartreuse2"; // Green
@ -120,8 +310,8 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
os << ", shape=box, style=filled, fillcolor=darkorange1"; // Orange
} else if (varVtxp->hasDfgRefs()) {
os << ", shape=box, style=filled, fillcolor=gold2"; // Yellow
} else if (varVtxp->keep()) {
os << ", shape=box, style=filled, fillcolor=grey";
} else if (varVtxp->tmpForp()) {
os << ", shape=box, style=filled, fillcolor=gray80";
} else {
os << ", shape=box";
}
@ -130,11 +320,16 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
}
if (const DfgVarArray* const arrVtxp = vtx.cast<DfgVarArray>()) {
AstNode* const nodep = arrVtxp->nodep();
AstVar* const varp = arrVtxp->varp();
const int elements = VN_AS(arrVtxp->dtypep(), UnpackArrayDType)->elementsConst();
const AstNode* const nodep = arrVtxp->nodep();
const AstVar* const varp = arrVtxp->varp();
os << toDotId(vtx);
os << " [label=\"" << nodep->name() << "[" << elements << "]\"";
os << " [label=\"" << nodep->prettyName() << '\n';
os << cvtToHex(arrVtxp) << '\n';
if (const AstNode* const tmpForp = arrVtxp->tmpForp()) {
os << "temporary for: " << tmpForp->prettyName() << "\n";
}
arrVtxp->dtypep()->dumpSmall(os);
os << " / F" << arrVtxp->fanout() << '"';
if (varp->direction() == VDirection::INPUT) {
os << ", shape=box3d, style=filled, fillcolor=chartreuse2"; // Green
} else if (varp->direction() == VDirection::OUTPUT) {
@ -147,8 +342,8 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
os << ", shape=box3d, style=filled, fillcolor=darkorange1"; // Orange
} else if (arrVtxp->hasDfgRefs()) {
os << ", shape=box3d, style=filled, fillcolor=gold2"; // Yellow
} else if (arrVtxp->keep()) {
os << ", shape=box3d, style=filled, fillcolor=grey";
} else if (arrVtxp->tmpForp()) {
os << ", shape=box3d, style=filled, fillcolor=gray80";
} else {
os << ", shape=box3d";
}
@ -162,11 +357,12 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
os << toDotId(vtx);
os << " [label=\"";
if (num.width() <= 32 && !num.isSigned()) {
os << constVtxp->width() << "'d" << num.toUInt() << "\n";
os << constVtxp->width() << "'h" << std::hex << num.toUInt() << std::dec;
os << constVtxp->width() << "'d" << num.toUInt() << '\n';
os << constVtxp->width() << "'h" << std::hex << num.toUInt() << std::dec << '\n';
} else {
os << num.ascii();
os << num.ascii() << '\n';
}
os << cvtToHex(constVtxp) << '\n';
os << '"';
os << ", shape=plain";
os << "]\n";
@ -177,8 +373,10 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
const uint32_t lsb = selVtxp->lsb();
const uint32_t msb = lsb + selVtxp->width() - 1;
os << toDotId(vtx);
os << " [label=\"SEL\n_[" << msb << ":" << lsb << "]\nW" << vtx.width() << " / F"
<< vtx.fanout() << '"';
os << " [label=\"SEL _[" << msb << ":" << lsb << "]\n";
os << cvtToHex(selVtxp) << '\n';
vtx.dtypep()->dumpSmall(os);
os << " / F" << vtx.fanout() << '"';
if (vtx.hasMultipleSinks()) {
os << ", shape=doublecircle";
} else {
@ -188,8 +386,39 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
return;
}
if (vtx.is<DfgVertexSplice>() || vtx.is<DfgUnitArray>() || vtx.is<DfgUnresolved>()) {
os << toDotId(vtx);
os << " [label=\"" << vtx.typeName() << '\n';
os << cvtToHex(&vtx) << '\n';
vtx.dtypep()->dumpSmall(os);
os << " / F" << vtx.fanout() << '"';
if (vtx.hasMultipleSinks()) {
os << ", shape=doubleoctagon";
} else {
os << ", shape=octagon";
}
os << "]\n";
return;
}
if (const DfgLogic* const logicp = vtx.cast<DfgLogic>()) {
os << toDotId(vtx);
std::stringstream ss;
V3EmitV::debugVerilogForTree(logicp->nodep(), ss);
os << " [label=\"";
os << VString::replaceSubstr(VString::replaceSubstr(ss.str(), "\n", "\\l"), "\"", "\\\"");
os << "\\n" << cvtToHex(&vtx);
os << "\"\n";
os << ", shape=box, style=\"rounded,filled\", fillcolor=cornsilk, nojustify=true";
os << "]\n";
return;
}
os << toDotId(vtx);
os << " [label=\"" << vtx.typeName() << "\nW" << vtx.width() << " / F" << vtx.fanout() << '"';
os << " [label=\"" << vtx.typeName() << '\n';
os << cvtToHex(&vtx) << '\n';
vtx.dtypep()->dumpSmall(os);
os << " / F" << vtx.fanout() << '"';
if (vtx.hasMultipleSinks()) {
os << ", shape=doublecircle";
} else {
@ -199,144 +428,147 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
}
// Dump one DfgEdge in Graphviz format
static void dumpDotEdge(std::ostream& os, const DfgEdge& edge, const string& headlabel) {
os << toDotId(*edge.sourcep()) << " -> " << toDotId(*edge.sinkp());
if (!headlabel.empty()) os << " [headlabel=\"" << headlabel << "\"]";
os << "\n";
static void dumpDotEdge(std::ostream& os, const DfgEdge& edge, size_t idx) {
UASSERT(edge.sourcep(), "Can't dump unconnected DfgEdge");
const DfgVertex& sink = *edge.sinkp(); // sink is never nullptr
os << toDotId(*edge.sourcep()) << " -> " << toDotId(sink);
if (sink.arity() > 1 || sink.is<DfgVertexSplice>()) {
os << " [headlabel=\"" << sink.srcName(idx) << "\"]";
}
os << '\n';
}
// Dump one DfgVertex and all of its source DfgEdges in Graphviz format
static void dumpDotVertexAndSourceEdges(std::ostream& os, const DfgVertex& vtx) {
dumpDotVertex(os, vtx);
vtx.forEachSourceEdge([&](const DfgEdge& edge, size_t idx) { //
if (edge.sourcep()) {
string headLabel;
if (vtx.arity() > 1 || vtx.is<DfgVertexVar>()) headLabel = vtx.srcName(idx);
dumpDotEdge(os, edge, headLabel);
}
});
}
void DfgGraph::dumpDot(std::ostream& os, const std::string& label,
std::function<bool(const DfgVertex&)> p) const {
// This generates a graphviz dump, https://www.graphviz.org
void DfgGraph::dumpDot(std::ostream& os, const string& label) const {
// Header
os << "digraph dfg {\n";
os << "graph [label=\"" << name();
if (!label.empty()) os << "-" << label;
os << "\", labelloc=t, labeljust=l]\n";
os << "graph [rankdir=LR]\n";
os << "rankdir=LR\n";
// Emit all vertices
forEachVertex([&](const DfgVertex& vtx) { dumpDotVertexAndSourceEdges(os, vtx); });
// If predicate not given, dump everything
if (!p) p = [](const DfgVertex&) { return true; };
std::unordered_set<const DfgVertex*> emitted;
// Emit all vertices associated with a DfgLogic
forEachVertex([&](const DfgVertex& vtx) {
const DfgLogic* const logicp = vtx.cast<DfgLogic>();
if (!logicp) return;
if (logicp->synth().empty()) return;
if (!p(vtx)) return;
os << "subgraph cluster_" << cvtToHex(logicp) << " {\n";
dumpDotVertex(os, *logicp);
emitted.insert(logicp);
for (DfgVertex* const vtxp : logicp->synth()) {
if (!p(*vtxp)) continue;
dumpDotVertex(os, *vtxp);
emitted.insert(vtxp);
}
os << "}\n";
});
// Emit all remaining vertices
forEachVertex([&](const DfgVertex& vtx) {
if (emitted.count(&vtx)) return;
if (!p(vtx)) return;
dumpDotVertex(os, vtx);
});
// Emit all edges
forEachVertex([&](const DfgVertex& vtx) { //
if (!p(vtx)) return;
vtx.forEachSourceEdge([&](const DfgEdge& e, size_t i) { //
if (!e.sourcep() || !p(*e.sourcep())) return;
dumpDotEdge(os, e, i);
});
});
// Footer
os << "label=\"" << name() + (label.empty() ? "" : "-" + label) << "\"\n";
os << "labelloc=t\n";
os << "labeljust=l\n";
os << "}\n";
}
void DfgGraph::dumpDotFile(const string& filename, const string& label) const {
// This generates a file used by graphviz, https://www.graphviz.org
// "hardcoded" parameters:
std::string DfgGraph::dumpDotString(const std::string& label,
std::function<bool(const DfgVertex&)> p) const {
std::stringstream ss;
dumpDot(ss, label, p);
return ss.str();
}
void DfgGraph::dumpDotFile(const std::string& filename, const std::string& label,
std::function<bool(const DfgVertex&)> p) const {
const std::unique_ptr<std::ofstream> os{V3File::new_ofstream(filename)};
if (os->fail()) v3fatal("Can't write file: " << filename);
dumpDot(*os.get(), label);
dumpDot(*os.get(), label, p);
os->close();
}
void DfgGraph::dumpDotFilePrefixed(const string& label) const {
string filename = name();
void DfgGraph::dumpDotFilePrefixed(const std::string& label,
std::function<bool(const DfgVertex&)> p) const {
std::string filename = name();
if (!label.empty()) filename += "-" + label;
dumpDotFile(v3Global.debugFilename(filename) + ".dot", label);
dumpDotFile(v3Global.debugFilename(filename) + ".dot", label, p);
}
// Dump upstream logic cone starting from given vertex
static void dumpDotUpstreamConeFromVertex(std::ostream& os, const DfgVertex& vtx) {
// Work queue for depth first traversal starting from this vertex
std::vector<const DfgVertex*> queue{&vtx};
template <bool T_SinksNotSources>
static std::unique_ptr<std::unordered_set<const DfgVertex*>>
dfgGraphCollectCone(const std::vector<const DfgVertex*>& vtxps) {
// Work queue for traversal starting from all the seed vertices
std::vector<const DfgVertex*> queue = vtxps;
// Set of already visited vertices
std::unordered_set<const DfgVertex*> visited;
std::unique_ptr<std::unordered_set<const DfgVertex*>> resp{
new std::unordered_set<const DfgVertex*>{}};
// Depth first traversal
while (!queue.empty()) {
// Pop next work item
const DfgVertex* const itemp = queue.back();
const DfgVertex* const vtxp = queue.back();
queue.pop_back();
// Mark vertex as visited
const bool isFirstEncounter = visited.insert(itemp).second;
// If we have already visited this vertex during the traversal, then move on.
if (!isFirstEncounter) continue;
// Enqueue all sources of this vertex.
itemp->forEachSource([&](const DfgVertex& src) { queue.push_back(&src); });
// Emit this vertex and all of its source edges
dumpDotVertexAndSourceEdges(os, *itemp);
}
// Emit all DfgVarPacked vertices that have external references driven by this vertex
vtx.forEachSink([&](const DfgVertex& dst) {
if (const DfgVarPacked* const varVtxp = dst.cast<DfgVarPacked>()) {
if (varVtxp->hasNonLocalRefs()) dumpDotVertexAndSourceEdges(os, dst);
// Mark vertex as visited, move on if already visited
if (!resp->insert(vtxp).second) continue;
// Enqueue all siblings of this vertex.
if VL_CONSTEXPR_CXX17 (T_SinksNotSources) {
vtxp->forEachSink([&](const DfgVertex& sink) { queue.push_back(&sink); });
} else {
vtxp->forEachSource([&](const DfgVertex& src) { queue.push_back(&src); });
}
});
}
// LCOV_EXCL_START // Debug function for developer use only
void DfgGraph::dumpDotUpstreamCone(const string& fileName, const DfgVertex& vtx,
const string& name) const {
// Open output file
const std::unique_ptr<std::ofstream> os{V3File::new_ofstream(fileName)};
if (os->fail()) v3fatal("Can't write file: " << fileName);
// Header
*os << "digraph dfg {\n";
if (!name.empty()) *os << "graph [label=\"" << name << "\", labelloc=t, labeljust=l]\n";
*os << "graph [rankdir=LR]\n";
// Dump the cone
dumpDotUpstreamConeFromVertex(*os, vtx);
// Footer
*os << "}\n";
}
// Done
os->close();
return resp;
}
// LCOV_EXCL_STOP
void DfgGraph::dumpDotAllVarConesPrefixed(const string& label) const {
const string prefix = label.empty() ? name() + "-cone-" : name() + "-" + label + "-cone-";
forEachVertex([&](const DfgVertex& vtx) {
// Check if this vertex drives a variable referenced outside the DFG.
const DfgVarPacked* const sinkp
= vtx.findSink<DfgVarPacked>([](const DfgVarPacked& sink) { //
return sink.hasNonLocalRefs();
});
std::unique_ptr<std::unordered_set<const DfgVertex*>>
DfgGraph::sourceCone(const std::vector<const DfgVertex*>& vtxps) const {
return dfgGraphCollectCone<false>(vtxps);
}
// We only dump cones driving an externally referenced variable
if (!sinkp) return;
std::unique_ptr<std::unordered_set<const DfgVertex*>>
DfgGraph::sinkCone(const std::vector<const DfgVertex*>& vtxps) const {
return dfgGraphCollectCone<true>(vtxps);
}
// Open output file
const string coneName{prefix + sinkp->nodep()->name()};
const string fileName{v3Global.debugFilename(coneName) + ".dot"};
const std::unique_ptr<std::ofstream> os{V3File::new_ofstream(fileName)};
if (os->fail()) v3fatal("Can't write file: " << fileName);
// predicate for supported data types
static bool dfgGraphIsSupportedDTypePacked(const AstNodeDType* dtypep) {
dtypep = dtypep->skipRefp();
if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) {
return typep->keyword().isIntNumeric();
}
if (const AstPackArrayDType* const typep = VN_CAST(dtypep, PackArrayDType)) {
return dfgGraphIsSupportedDTypePacked(typep->subDTypep());
}
if (const AstNodeUOrStructDType* const typep = VN_CAST(dtypep, NodeUOrStructDType)) {
return typep->packed();
}
return false;
}
// Header
*os << "digraph dfg {\n";
*os << "graph [label=\"" << coneName << "\", labelloc=t, labeljust=l]\n";
*os << "graph [rankdir=LR]\n";
// Dump this cone
dumpDotUpstreamConeFromVertex(*os, vtx);
// Footer
*os << "}\n";
// Done with this logic cone
os->close();
});
bool DfgGraph::isSupported(const AstNodeDType* dtypep) {
dtypep = dtypep->skipRefp();
// Support 1 dimensional unpacked arrays of packed types
if (const AstUnpackArrayDType* const typep = VN_CAST(dtypep, UnpackArrayDType)) {
return dfgGraphIsSupportedDTypePacked(typep->subDTypep());
}
// Support packed types
return dfgGraphIsSupportedDTypePacked(dtypep);
}
//------------------------------------------------------------------------------
@ -347,12 +579,12 @@ void DfgEdge::unlinkSource() {
if (!m_sourcep) return;
#ifdef VL_DEBUG
{
DfgEdge* sinkp = m_sourcep->m_sinksp;
while (sinkp) {
if (sinkp == this) break;
sinkp = sinkp->m_nextp;
DfgEdge* currp = m_sourcep->m_sinksp;
while (currp) {
if (currp == this) break;
currp = currp->m_nextp;
}
UASSERT(sinkp, "'m_sourcep' does not have this edge as sink");
UASSERT(currp, "'m_sourcep' does not have this edge as sink");
}
#endif
// Relink pointers of predecessor and successor
@ -396,24 +628,34 @@ bool DfgVertex::selfEquals(const DfgVertex& that) const { return true; }
V3Hash DfgVertex::selfHash() const { return V3Hash{}; }
bool DfgVertex::equals(const DfgVertex& that, EqualsCache& cache) const {
// If same vertex, then equal
if (this == &that) return true;
// If different type, then not equal
if (this->type() != that.type()) return false;
// If different data type, then not equal
if (this->dtypep() != that.dtypep()) return false;
// If different number of inputs, then not equal
auto thisPair = this->sourceEdges();
const DfgEdge* const thisSrcEdgesp = thisPair.first;
const size_t thisArity = thisPair.second;
auto thatPair = that.sourceEdges();
const DfgEdge* const thatSrcEdgesp = thatPair.first;
const size_t thatArity = thatPair.second;
if (thisArity != thatArity) return false;
// Check vertex specifics
if (!this->selfEquals(that)) return false;
// Check sources
const auto key = (this < &that) ? EqualsCache::key_type{this, &that} //
: EqualsCache::key_type{&that, this};
// Note: the recursive invocation can cause a re-hash but that will not invalidate references
uint8_t& result = cache[key];
if (!result) {
result = 2; // Assume equals
auto thisPair = this->sourceEdges();
const DfgEdge* const thisSrcEdgesp = thisPair.first;
const size_t thisArity = thisPair.second;
auto thatPair = that.sourceEdges();
const DfgEdge* const thatSrcEdgesp = thatPair.first;
const size_t thatArity = thatPair.second;
UASSERT_OBJ(thisArity == thatArity, this, "Same type vertices must have same arity!");
for (size_t i = 0; i < thisArity; ++i) {
const DfgVertex* const thisSrcVtxp = thisSrcEdgesp[i].m_sourcep;
const DfgVertex* const thatSrcVtxp = thatSrcEdgesp[i].m_sourcep;
@ -436,12 +678,17 @@ V3Hash DfgVertex::hash() {
// variables, which we rely on.
if (!is<DfgVertexVar>()) {
hash += m_type;
hash += width(); // Currently all non-variable vertices are packed, so this is safe
if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep(), UnpackArrayDType)) {
hash += adtypep->elementsConst();
// TODO: maybe include sub-dtype, but not hugely important at the moment
} else {
hash += width();
}
const auto pair = sourceEdges();
const DfgEdge* const edgesp = pair.first;
const size_t arity = pair.second;
const size_t nEdges = pair.second;
// Sources must always be connected in well-formed graphs
for (size_t i = 0; i < arity; ++i) hash += edgesp[i].m_sourcep->hash();
for (size_t i = 0; i < nEdges; ++i) hash += edgesp[i].m_sourcep->hash();
}
result = hash;
}
@ -454,37 +701,43 @@ uint32_t DfgVertex::fanout() const {
return result;
}
DfgVarPacked* DfgVertex::getResultVar() {
UASSERT_OBJ(!this->is<DfgVarArray>(), this, "Arrays are not supported by " << __FUNCTION__);
DfgVertexVar* DfgVertex::getResultVar() {
// It's easy if the vertex is already a variable ...
if (DfgVarPacked* const varp = this->cast<DfgVarPacked>()) return varp;
if (DfgVertexVar* const varp = this->cast<DfgVertexVar>()) return varp;
// Inspect existing variables fully written by this vertex, and choose one
DfgVarPacked* resp = nullptr;
// Inspect existing variables written by this vertex, and choose one
DfgVertexVar* resp = nullptr;
// cppcheck-has-bug-suppress constParameter
this->forEachSink([&resp](DfgVertex& sink) {
DfgVarPacked* const varp = sink.cast<DfgVarPacked>();
DfgVertexVar* const varp = sink.cast<DfgVertexVar>();
if (!varp) return;
if (!varp->isDrivenFullyByDfg()) return;
// Ignore SystemC variables, they cannot participate in expressions or
// be assigned rvalue expressions.
if (varp->varp()->isSc()) return;
// First variable found
if (!resp) {
resp = varp;
return;
}
// Prefer those variables that must be kept anyway
const bool keepOld = resp->keep() || resp->hasDfgRefs();
const bool keepNew = varp->keep() || varp->hasDfgRefs();
if (keepOld != keepNew) {
if (!keepOld) resp = varp;
if (resp->hasExtRefs() != varp->hasExtRefs()) {
if (!resp->hasExtRefs()) resp = varp;
return;
}
if (resp->hasModWrRefs() != varp->hasModWrRefs()) {
if (!resp->hasModWrRefs()) resp = varp;
return;
}
if (resp->hasDfgRefs() != varp->hasDfgRefs()) {
if (!resp->hasDfgRefs()) resp = varp;
return;
}
// Prefer those that already have module references
if (resp->hasModRefs() != varp->hasModRefs()) {
if (!resp->hasModRefs()) resp = varp;
if (resp->hasModRdRefs() != varp->hasModRdRefs()) {
if (!resp->hasModRdRefs()) resp = varp;
return;
}
// Prefer real variabels over temporaries
if (!resp->tmpForp() != !varp->tmpForp()) {
if (resp->tmpForp()) resp = varp;
return;
}
// Prefer the earlier one in source order
@ -506,34 +759,40 @@ DfgVarPacked* DfgVertex::getResultVar() {
AstScope* DfgVertex::scopep(ScopeCache& cache, bool tryResultVar) VL_MT_DISABLED {
// If this is a variable, we are done
if (DfgVertexVar* const varp = this->cast<DfgVertexVar>()) return varp->varScopep()->scopep();
if (const DfgVertexVar* const varp = this->cast<DfgVertexVar>()) {
return varp->varScopep()->scopep();
}
// Try the result var first if instructed (usully only in the recursive case)
if (tryResultVar) {
if (DfgVertexVar* const varp = this->getResultVar()) return varp->varScopep()->scopep();
if (const DfgVertexVar* const varp = this->getResultVar()) {
return varp->varScopep()->scopep();
}
}
// Look up cache
const auto pair = cache.emplace(this, nullptr);
if (pair.second) {
// Note: the recursive invocation can cause a re-hash but that will not invalidate references
AstScope*& resultr = cache[this];
if (!resultr) {
// Mark to prevent infinite recursion on circular graphs - should never be called on such
resultr = reinterpret_cast<AstScope*>(1);
// Find scope based on sources, falling back on the root scope
AstScope* const rootp = v3Global.rootp()->topScopep()->scopep();
AstScope* foundp = rootp;
const auto edges = sourceEdges();
for (size_t i = 0; i < edges.second; ++i) {
DfgEdge& edge = edges.first[i];
const DfgEdge& edge = edges.first[i];
foundp = edge.sourcep()->scopep(cache, true);
if (foundp != rootp) break;
}
pair.first->second = foundp;
resultr = foundp;
}
// If the cache entry exists, but have not set the mapping yet, then we have a circualr graph
UASSERT_OBJ(pair.first->second, this,
// Die on a graph circular through operation vertices
UASSERT_OBJ(resultr != reinterpret_cast<AstScope*>(1), this,
"DfgVertex::scopep called on graph with circular operations");
// Done
return pair.first->second;
return resultr;
}
void DfgVertex::unlinkDelete(DfgGraph& dfg) {
@ -569,6 +828,29 @@ bool DfgSel::selfEquals(const DfgVertex& that) const { return lsb() == that.as<D
V3Hash DfgSel::selfHash() const { return V3Hash{lsb()}; }
// DfgVertexSplice ----------
bool DfgVertexSplice::selfEquals(const DfgVertex& that) const {
const DfgVertexSplice* const thatp = that.as<DfgVertexSplice>();
if (!defaultp() != !thatp->defaultp()) return false;
const size_t arity = this->arity();
for (size_t i = 0; i < arity; ++i) {
if (i == 0 && defaultp()) continue;
if (driverLo(i) != thatp->driverLo(i)) return false;
}
return true;
}
V3Hash DfgVertexSplice::selfHash() const {
V3Hash hash;
const size_t arity = this->arity();
for (size_t i = 0; i < arity; ++i) {
if (i == 0 && defaultp()) continue;
hash += driverLo(i);
}
return hash;
}
// DfgVertexVar ----------
bool DfgVertexVar::selfEquals(const DfgVertex& that) const {

View File

@ -34,6 +34,7 @@
#include "verilatedos.h"
#include "V3Ast.h"
#include "V3Cfg.h"
#include "V3Error.h"
#include "V3Global.h"
#include "V3Hash.h"
@ -78,8 +79,8 @@ struct std::hash<std::pair<const DfgVertex*, const DfgVertex*>> final {
class VDfgType final {
public:
#include "V3Dfg__gen_type_enum.h" // From ./astgen
enum en m_e;
VDfgType() = default;
const enum en m_e;
VDfgType() = delete;
// cppcheck-suppress noExplicitConstructor
constexpr VDfgType(en _e)
: m_e{_e} {}
@ -140,7 +141,7 @@ protected:
DfgGraph* m_graphp; // The containing DfgGraph
const VDfgType m_type; // Vertex type tag
uint32_t m_userCnt = 0; // User data generation number
UserDataStorage m_userDataStorage; // User data storage
UserDataStorage m_userDataStorage = nullptr; // User data storage
// CONSTRUCTOR
DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep) VL_MT_DISABLED;
@ -168,59 +169,15 @@ private:
virtual V3Hash selfHash() const VL_MT_DISABLED;
public:
// Supported packed types
static bool isSupportedPackedDType(const AstNodeDType* dtypep) {
dtypep = dtypep->skipRefp();
if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) {
return typep->keyword().isIntNumeric();
}
if (const AstPackArrayDType* const typep = VN_CAST(dtypep, PackArrayDType)) {
return isSupportedPackedDType(typep->subDTypep());
}
if (const AstNodeUOrStructDType* const typep = VN_CAST(dtypep, NodeUOrStructDType)) {
return typep->packed();
}
return false;
}
// The data type of the result of the vertex
AstNodeDType* dtypep() const { return m_dtypep; }
// Returns true if an AstNode with the given 'dtype' can be represented as a DfgVertex
static bool isSupportedDType(const AstNodeDType* dtypep) {
dtypep = dtypep->skipRefp();
// Support unpacked arrays of packed types
if (const AstUnpackArrayDType* const typep = VN_CAST(dtypep, UnpackArrayDType)) {
return isSupportedPackedDType(typep->subDTypep());
}
// Support packed types
return isSupportedPackedDType(dtypep);
}
// Return data type used to represent any packed value of the given 'width'. All packed types
// of a given width use the same canonical data type, as the only interesting information is
// the total width.
static AstNodeDType* dtypeForWidth(uint32_t width) {
return v3Global.rootp()->typeTablep()->findLogicDType(width, width, VSigning::UNSIGNED);
}
// Return data type used to represent the type of 'nodep' when converted to a DfgVertex
static AstNodeDType* dtypeFor(const AstNode* nodep) {
const AstNodeDType* const dtypep = nodep->dtypep()->skipRefp();
UDEBUGONLY(UASSERT_OBJ(isSupportedDType(dtypep), nodep, "Unsupported dtype"););
// For simplicity, all packed types are represented with a fixed type
if (const AstUnpackArrayDType* const typep = VN_CAST(dtypep, UnpackArrayDType)) {
AstNodeDType* const adtypep = new AstUnpackArrayDType{
typep->fileline(), dtypeForWidth(typep->subDTypep()->width()),
typep->rangep()->cloneTree(false)};
v3Global.rootp()->typeTablep()->addTypesp(adtypep);
return adtypep;
}
return dtypeForWidth(dtypep->width());
}
// Is it a packed type (instead of an array)
bool isPacked() const { return VN_IS(dtypep(), BasicDType); }
// Source location
FileLine* fileline() const { return m_filelinep; }
// The data type of the result of the nodes
AstNodeDType* dtypep() const { return m_dtypep; }
void dtypep(AstNodeDType* nodep) { m_dtypep = nodep; }
// The type of this vertex
VDfgType type() const { return m_type; }
@ -230,7 +187,13 @@ public:
// Retrieve user data, must be current.
template <typename T>
inline T& getUser();
inline const T& getUser() const;
// Retrieve user data, must be current.
template <typename T>
T& getUser() {
return const_cast<T&>(const_cast<const DfgVertex*>(this)->getUser<T>());
}
// Set user data, becomes current.
template <typename T>
@ -238,11 +201,16 @@ public:
// Width of result
uint32_t width() const {
// This is a hot enough function that this is an expensive check, so in debug build only.
UDEBUGONLY(UASSERT_OBJ(VN_IS(dtypep(), BasicDType), this, "non-packed has no 'width()'"););
UASSERT_OBJ(isPacked(), this, "non-packed has no 'width()'");
return dtypep()->width();
}
// Number of sub-elements in result vertex
uint32_t size() const {
if (isPacked()) return dtypep()->width();
return VN_AS(dtypep(), UnpackArrayDType)->elementsConst();
}
// Cache type for 'equals' below
using EqualsCache = std::unordered_map<std::pair<const DfgVertex*, const DfgVertex*>, uint8_t>;
@ -283,7 +251,7 @@ public:
// Return a canonical variable vertex that holds the value of this vertex,
// or nullptr if no such variable exists in the graph. This is O(fanout).
DfgVarPacked* getResultVar() VL_MT_DISABLED;
DfgVertexVar* getResultVar() VL_MT_DISABLED;
// Cache type for 'scopep' below
using ScopeCache = std::unordered_map<const DfgVertex*, AstScope*>;
@ -302,6 +270,9 @@ public:
return m_sinksp && !m_sinksp->m_nextp ? m_sinksp->m_sinkp : nullptr;
}
// First sink of the vertex, if any, otherwise nullptr
DfgVertex* firtsSinkp() const { return m_sinksp ? m_sinksp->m_sinkp : nullptr; }
// Unlink from container (graph or builder), then delete this vertex
void unlinkDelete(DfgGraph& dfg) VL_MT_DISABLED;
@ -572,7 +543,7 @@ class DfgVertexVariadic VL_NOT_FINAL : public DfgVertex {
protected:
DfgVertexVariadic(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep,
uint32_t initialCapacity = 1)
uint32_t initialCapacity)
: DfgVertex{dfg, type, flp, dtypep}
, m_srcsp{allocSources(initialCapacity)}
, m_srcCap{initialCapacity} {}
@ -593,12 +564,20 @@ protected:
m_srcCnt = 0;
}
public:
void clearSources() {
for (uint32_t i = 0; i < m_srcCnt; ++i) {
UASSERT_OBJ(m_srcsp[i].sourcep(), this, "Unconnected source");
m_srcsp[i].unlinkSource();
}
m_srcCnt = 0;
}
ASTGEN_MEMBERS_DfgVertexVariadic;
DfgEdge* sourceEdge(size_t idx) const { return &m_srcsp[idx]; }
DfgVertex* source(size_t idx) const { return m_srcsp[idx].sourcep(); }
public:
std::pair<DfgEdge*, size_t> sourceEdges() override { return {m_srcsp, m_srcCnt}; }
std::pair<const DfgEdge*, size_t> sourceEdges() const override { return {m_srcsp, m_srcCnt}; }
};
@ -706,8 +685,12 @@ public:
// 'const' variant of 'forEachVertex'. No mutation allowed.
inline void forEachVertex(std::function<void(const DfgVertex&)> f) const;
// Add contents of other graph to this graph. Leaves other graph empty.
void addGraph(DfgGraph& other) VL_MT_DISABLED;
// Return an identical, independent copy of this graph. Vertex and edge order might differ.
std::unique_ptr<DfgGraph> clone() const VL_MT_DISABLED;
// Merge contents of other graphs into this graph. Deletes the other graphs.
// DfgVertexVar instances representing the same Ast variable are unified.
void mergeGraphs(std::vector<std::unique_ptr<DfgGraph>>&& otherps) VL_MT_DISABLED;
// Genarete a unique name. The provided 'prefix' and 'n' values will be part of the name, and
// must be unique (as a pair) in each invocation for this graph.
@ -722,7 +705,8 @@ public:
// Split this graph into individual components (unique sub-graphs with no edges between them).
// Also removes any vertices that are not weakly connected to any variable.
// Leaves 'this' graph empty.
std::vector<std::unique_ptr<DfgGraph>> splitIntoComponents(std::string label) VL_MT_DISABLED;
std::vector<std::unique_ptr<DfgGraph>>
splitIntoComponents(const std::string& label) VL_MT_DISABLED;
// Extract cyclic sub-graphs from 'this' graph. Cyclic sub-graphs are those that contain at
// least one strongly connected component (SCC) plus any other vertices that feed or sink from
@ -733,26 +717,100 @@ public:
// to be a DAG (acyclic). 'this' will not necessarily be a connected graph at the end, even if
// it was originally connected.
std::vector<std::unique_ptr<DfgGraph>>
extractCyclicComponents(std::string label) VL_MT_DISABLED;
extractCyclicComponents(const std::string& label) VL_MT_DISABLED;
//-----------------------------------------------------------------------
// Debug dumping
// Dump graph in Graphviz format into the given stream 'os'. 'label' is added to the name of
// the graph which is included in the output.
void dumpDot(std::ostream& os, const string& label = "") const VL_MT_DISABLED;
// If the predicate function 'p' is provided, only those vertices are dumped that satifty it.
void dumpDot(std::ostream& os, const std::string& label,
std::function<bool(const DfgVertex&)> p = {}) const VL_MT_DISABLED;
// Dump graph in Graphviz format into a new file with the given 'filename'. 'label' is added to
// the name of the graph which is included in the output.
void dumpDotFile(const string& filename, const string& label = "") const VL_MT_DISABLED;
// If the predicate function 'p' is provided, only those vertices are dumped that satifty it.
void dumpDotFile(const std::string& filename, const std::string& label,
std::function<bool(const DfgVertex&)> p = {}) const VL_MT_DISABLED;
// Same as dumpDotFile, but returns the contents as a string.
std::string dumpDotString(const std::string& label,
std::function<bool(const DfgVertex&)> p = {}) const VL_MT_DISABLED;
// Dump graph in Graphviz format into a new automatically numbered debug file. 'label' is
// added to the name of the graph, which is included in the file name and the output.
void dumpDotFilePrefixed(const string& label = "") const VL_MT_DISABLED;
// Dump upstream (source) logic cone starting from given vertex into a file with the given
// 'filename'. 'name' is the name of the graph, which is included in the output.
void dumpDotUpstreamCone(const string& filename, const DfgVertex& vtx,
const string& name = "") const VL_MT_DISABLED;
// Dump all individual logic cones driving external variables in Graphviz format into separate
// new automatically numbered debug files. 'label' is added to the name of the graph, which is
// included in the file names and the output. This is useful for very large graphs that are
// otherwise difficult to browse visually due to their size.
void dumpDotAllVarConesPrefixed(const string& label = "") const VL_MT_DISABLED;
// If the predicate function 'p' is provided, only those vertices are dumped that satifty it.
void dumpDotFilePrefixed(const std::string& label,
std::function<bool(const DfgVertex&)> p = {}) const VL_MT_DISABLED;
// Returns the set of vertices in the upstream cones of the given vertices
std::unique_ptr<std::unordered_set<const DfgVertex*>>
sourceCone(const std::vector<const DfgVertex*>&) const VL_MT_DISABLED;
// Returns the set of vertices in the downstream cones of the given vertices
std::unique_ptr<std::unordered_set<const DfgVertex*>>
sinkCone(const std::vector<const DfgVertex*>&) const VL_MT_DISABLED;
//-----------------------------------------------------------------------
// Static methods for data types
// Some data types are interned, in order to facilitate type comparison
// via pointer compariosn. These are functoins to construct the canonical
// DFG data types
// Returns data type used to represent any packed value of the given 'width'.
static AstNodeDType* dtypePacked(uint32_t width) {
return v3Global.rootp()->typeTablep()->findLogicDType(width, width, VSigning::UNSIGNED);
}
// Returns data type used to represent any array with the given type and number of elements.
static AstNodeDType* dtypeArray(AstNodeDType* subDtypep, uint32_t size) {
UASSERT_OBJ(isSupported(subDtypep), subDtypep, "Unsupported element type");
FileLine* const flp = subDtypep->fileline();
AstRange* const rangep = new AstRange{flp, static_cast<int>(size - 1), 0};
AstNodeDType* const dtypep = new AstUnpackArrayDType{flp, subDtypep, rangep};
v3Global.rootp()->typeTablep()->addTypesp(dtypep);
return dtypep;
}
// Return data type used to represent the type of 'nodep' when converted to a DfgVertex
static AstNodeDType* toDfgDType(const AstNodeDType* dtypep) {
dtypep = dtypep->skipRefp();
UASSERT_OBJ(isSupported(dtypep), dtypep, "Unsupported dtype");
// For simplicity, all packed types are represented with a fixed type
if (const AstUnpackArrayDType* const uatp = VN_CAST(dtypep, UnpackArrayDType)) {
return dtypeArray(toDfgDType(uatp->subDTypep()), uatp->elementsConst());
}
return dtypePacked(dtypep->width());
}
//-----------------------------------------------------------------------
// Static methods for compatibility tests
// Returns true if the given data type can be represented in the graph
static bool isSupported(const AstNodeDType* dtypep) VL_MT_DISABLED;
// Returns true if variable can be represented in the graph
static bool isSupported(const AstVar* varp) {
if (varp->isIfaceRef()) return false; // Cannot handle interface references
if (varp->delayp()) return false; // Cannot handle delayed variables
if (varp->isSc()) return false; // SystemC variables are special and rare, we can ignore
if (varp->dfgMultidriven()) return false; // Discovered as multidriven on earlier DFG run
return isSupported(varp->dtypep());
}
// Returns true if variable can be represented in the graph
static bool isSupported(const AstVarScope* vscp) {
AstNodeModule* const modp = vscp->scopep()->modp();
if (VN_IS(modp, Module)) {
// Regular module supported
} else if (AstIface* const ifacep = VN_CAST(modp, Iface)) {
// Interfaces supported if there are no virtual interfaces for
// them, otherwise they cannot be resovled statically.
if (ifacep->hasVirtualRef()) return false;
} else {
return false; // Anything else (package, class, etc) not supported
}
// Check the AstVar
return isSupported(vscp->varp());
}
};
// Specializations of privateTypeTest
@ -773,20 +831,18 @@ T& DfgVertex::user() {
UDEBUGONLY(UASSERT_OBJ(userCurrent, this, "DfgVertex user data used without reserving"););
if (m_userCnt != userCurrent) {
m_userCnt = userCurrent;
// cppcheck-has-bug-suppress uninitvar
VL_ATTR_UNUSED T* const resultp = new (storagep) T{};
UDEBUGONLY(UASSERT_OBJ(resultp == storagep, this, "Something is odd"););
new (storagep) T{};
}
return *storagep;
}
template <typename T>
T& DfgVertex::getUser() {
const T& DfgVertex::getUser() const {
static_assert(sizeof(T) <= sizeof(UserDataStorage),
"Size of user data type 'T' is too large for allocated storage");
static_assert(alignof(T) <= alignof(UserDataStorage),
"Alignment of user data type 'T' is larger than allocated storage");
T* const storagep = reinterpret_cast<T*>(&m_userDataStorage);
const T* const storagep = reinterpret_cast<const T*>(&m_userDataStorage);
#if VL_DEBUG
const uint32_t userCurrent = m_graphp->m_userCurrent;
UASSERT_OBJ(userCurrent, this, "DfgVertex user data used without reserving");
@ -813,8 +869,8 @@ void DfgVertex::setUser(T value) {
void DfgVertex::forEachSource(std::function<void(DfgVertex&)> f) {
const auto pair = sourceEdges();
const DfgEdge* const edgesp = pair.first;
const size_t arity = pair.second;
for (size_t i = 0; i < arity; ++i) {
const size_t nEdges = pair.second;
for (size_t i = 0; i < nEdges; ++i) {
if (DfgVertex* const sourcep = edgesp[i].m_sourcep) f(*sourcep);
}
}
@ -822,8 +878,8 @@ void DfgVertex::forEachSource(std::function<void(DfgVertex&)> f) {
void DfgVertex::forEachSource(std::function<void(const DfgVertex&)> f) const {
const auto pair = sourceEdges();
const DfgEdge* const edgesp = pair.first;
const size_t arity = pair.second;
for (size_t i = 0; i < arity; ++i) {
const size_t nEdges = pair.second;
for (size_t i = 0; i < nEdges; ++i) {
if (DfgVertex* const sourcep = edgesp[i].m_sourcep) f(*sourcep);
}
}
@ -842,15 +898,15 @@ void DfgVertex::forEachSink(std::function<void(const DfgVertex&)> f) const {
void DfgVertex::forEachSourceEdge(std::function<void(DfgEdge&, size_t)> f) {
const auto pair = sourceEdges();
DfgEdge* const edgesp = pair.first;
const size_t arity = pair.second;
for (size_t i = 0; i < arity; ++i) f(edgesp[i], i);
const size_t nEdges = pair.second;
for (size_t i = 0; i < nEdges; ++i) f(edgesp[i], i);
}
void DfgVertex::forEachSourceEdge(std::function<void(const DfgEdge&, size_t)> f) const {
const auto pair = sourceEdges();
const DfgEdge* const edgesp = pair.first;
const size_t arity = pair.second;
for (size_t i = 0; i < arity; ++i) f(edgesp[i], i);
const size_t nEdges = pair.second;
for (size_t i = 0; i < nEdges; ++i) f(edgesp[i], i);
}
void DfgVertex::forEachSinkEdge(std::function<void(DfgEdge&)> f) {
@ -870,8 +926,8 @@ void DfgVertex::forEachSinkEdge(std::function<void(const DfgEdge&)> f) const {
const DfgEdge* DfgVertex::findSourceEdge(std::function<bool(const DfgEdge&, size_t)> p) const {
const auto pair = sourceEdges();
const DfgEdge* const edgesp = pair.first;
const size_t arity = pair.second;
for (size_t i = 0; i < arity; ++i) {
const size_t nEdges = pair.second;
for (size_t i = 0; i < nEdges; ++i) {
const DfgEdge& edge = edgesp[i];
if (p(edge, i)) return &edge;
}
@ -907,22 +963,66 @@ bool DfgVertex::isOnes() const {
return false;
}
//------------------------------------------------------------------------------
// Inline method definitions - for DfgConst
//------------------------------------------------------------------------------
DfgConst::DfgConst(DfgGraph& dfg, FileLine* flp, const V3Number& num)
: DfgVertex{dfg, dfgType(), flp, DfgGraph::dtypePacked(num.width())}
, m_num{num} {}
DfgConst::DfgConst(DfgGraph& dfg, FileLine* flp, uint32_t width, uint32_t value)
: DfgVertex{dfg, dfgType(), flp, DfgGraph::dtypePacked(width)}
, m_num{flp, static_cast<int>(width), value} {}
//------------------------------------------------------------------------------
// Inline method definitions - for DfgVertexSplice
//------------------------------------------------------------------------------
DfgVertex* DfgVertexSplice::wholep() const {
if (defaultp()) return nullptr;
if (arity() != 1) return nullptr;
if (driverLo(0) != 0) return nullptr;
DfgVertex* const srcp = DfgVertexVariadic::source(1);
if (srcp->size() != size()) return nullptr;
if (const DfgUnitArray* const uap = srcp->cast<DfgUnitArray>()) {
if (const DfgVertexSplice* sp = uap->srcp()->cast<DfgVertexSplice>()) {
if (!sp->wholep()) return nullptr;
}
}
return srcp;
}
//------------------------------------------------------------------------------
// Inline method definitions - for DfgVertexVar
//------------------------------------------------------------------------------
DfgVertexVar::DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVar* varp, uint32_t initialCapacity)
: DfgVertexVariadic{dfg, type, varp->fileline(), dtypeFor(varp), initialCapacity}
DfgVertexVar::DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVar* varp)
: DfgVertexUnary{dfg, type, varp->fileline(), DfgGraph::toDfgDType(varp->dtypep())}
, m_varp{varp}
, m_varScopep{nullptr} {
UASSERT_OBJ(dfg.modulep(), varp, "Un-scoped DfgVertexVar created in scoped DfgGraph");
UASSERT_OBJ(!m_varp->isSc(), varp, "SystemC variable is not representable by DfgVertexVar");
// Increment reference count
varp->user1(varp->user1() + 0x10);
UASSERT_OBJ((varp->user1() >> 4) > 0, varp, "Reference count overflow");
}
DfgVertexVar::DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVarScope* vscp,
uint32_t initialCapacity)
: DfgVertexVariadic{dfg, type, vscp->fileline(), dtypeFor(vscp), initialCapacity}
DfgVertexVar::DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVarScope* vscp)
: DfgVertexUnary{dfg, type, vscp->fileline(), DfgGraph::toDfgDType(vscp->varp()->dtypep())}
, m_varp{vscp->varp()}
, m_varScopep{vscp} {
UASSERT_OBJ(!dfg.modulep(), vscp, "Scoped DfgVertexVar created in un-scoped DfgGraph");
UASSERT_OBJ(!m_varp->isSc(), vscp, "SystemC variable is not representable by DfgVertexVar");
// Increment reference count
vscp->user1(vscp->user1() + 0x10);
UASSERT_OBJ((vscp->user1() >> 4) > 0, vscp, "Reference count overflow");
}
DfgVertexVar::~DfgVertexVar() {
// Decrement reference count
// cppcheck-suppress shadowFunction
AstNode* const nodep = this->nodep();
nodep->user1(nodep->user1() - 0x10);
UASSERT_OBJ((nodep->user1() >> 4) >= 0, nodep, "Reference count underflow");
}
//------------------------------------------------------------------------------
@ -930,7 +1030,7 @@ DfgVertexVar::DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVarScope* vscp,
//------------------------------------------------------------------------------
void DfgGraph::addVertex(DfgVertex& vtx) {
// Note: changes here need to be replicated in DfgGraph::addGraph
// Note: changes here need to be replicated in DfgGraph::mergeGraph
++m_size;
if (DfgConst* const cVtxp = vtx.cast<DfgConst>()) {
m_constVertices.linkBack(cVtxp);
@ -944,7 +1044,7 @@ void DfgGraph::addVertex(DfgVertex& vtx) {
}
void DfgGraph::removeVertex(DfgVertex& vtx) {
// Note: changes here need to be replicated in DfgGraph::addGraph
// Note: changes here need to be replicated in DfgGraph::mergeGraph
--m_size;
if (DfgConst* const cVtxp = vtx.cast<DfgConst>()) {
m_constVertices.unlink(cVtxp);

View File

@ -14,458 +14,232 @@
//
//*************************************************************************
//
// Convert and AstModule to a DfgGraph. We proceed by visiting convertible logic blocks (e.g.:
// AstAssignW of appropriate type and with no delays), recursively constructing DfgVertex instances
// for the expressions that compose the subject logic block. If all expressions in the current
// logic block can be converted, then we delete the logic block (now represented in the DfgGraph),
// and connect the corresponding DfgVertex instances appropriately. If some of the expressions were
// not convertible in the current logic block, we revert (delete) the DfgVertex instances created
// for the logic block, and leave the logic block in the AstModule. Any variable reference from
// non-converted logic blocks (or other constructs under the AstModule) are marked as being
// referenced in the AstModule, which is relevant for later optimization.
// Convert and AstModule (before V3Scope), or the entire AstNetlist
// (after V3Scope) to an initial DfgGraph composed onlyof DfgLogic,
// DfgUnresolved and DfgVertexVar vertices. This will later be synthesized
// into primitive operations by V3DfgPasses::synthesize.
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Cfg.h"
#include "V3Const.h"
#include "V3Dfg.h"
#include "V3DfgPasses.h"
#include <iterator>
VL_DEFINE_DEBUG_FUNCTIONS;
namespace {
// Create a DfgVertex out of a AstNodeExpr. For most AstNodeExpr subtypes, this can be done
// automatically. For the few special cases, we provide specializations below
template <typename T_Vertex, typename T_Node>
T_Vertex* makeVertex(const T_Node* nodep, DfgGraph& dfg) {
return new T_Vertex{dfg, nodep->fileline(), DfgVertex::dtypeFor(nodep)};
}
//======================================================================
// Currently unhandled nodes
// LCOV_EXCL_START
// AstCCast changes width, but should not exists where DFG optimization is currently invoked
template <>
DfgCCast* makeVertex<DfgCCast, AstCCast>(const AstCCast*, DfgGraph&) {
return nullptr;
}
// Unhandled in DfgToAst, but also operates on strings which we don't optimize anyway
template <>
DfgAtoN* makeVertex<DfgAtoN, AstAtoN>(const AstAtoN*, DfgGraph&) {
return nullptr;
}
// Unhandled in DfgToAst, but also operates on strings which we don't optimize anyway
template <>
DfgCompareNN* makeVertex<DfgCompareNN, AstCompareNN>(const AstCompareNN*, DfgGraph&) {
return nullptr;
}
// Unhandled in DfgToAst, but also operates on unpacked arrays which we don't optimize anyway
template <>
DfgSliceSel* makeVertex<DfgSliceSel, AstSliceSel>(const AstSliceSel*, DfgGraph&) {
return nullptr;
}
// LCOV_EXCL_STOP
} // namespace
template <bool T_Scoped>
class AstToDfgVisitor final : public VNVisitor {
// NODE STATE
// AstNode::user1p // DfgVertex for this AstNode
const VNUser1InUse m_user1InUse;
// AstVar/AstVarScope::user2() -> DfgVertexVar* : the corresponding variable vertex
// AstVar/AstVarScope::user3() -> bool : Already gathered - used fine grained below
const VNUser2InUse m_user2InUse;
// TYPES
// Represents a driver during canonicalization
struct Driver final {
FileLine* m_fileline;
DfgVertex* m_vtxp;
uint32_t m_lsb;
Driver(FileLine* flp, uint32_t lsb, DfgVertex* vtxp)
: m_fileline{flp}
, m_vtxp{vtxp}
, m_lsb{lsb} {}
};
using RootType = std::conditional_t<T_Scoped, AstNetlist, AstModule>;
using VariableType = std::conditional_t<T_Scoped, AstVarScope, AstVar>;
using Variable = std::conditional_t<T_Scoped, AstVarScope, AstVar>;
// STATE
DfgGraph* const m_dfgp; // The graph being built
V3DfgOptimizationContext& m_ctx; // The optimization context for stats
bool m_foundUnhandled = false; // Found node not implemented as DFG or not implemented 'visit'
std::vector<DfgVertex*> m_uncommittedVertices; // Vertices that we might decide to revert
bool m_converting = false; // We are trying to convert some logic at the moment
std::vector<DfgVarPacked*> m_varPackedps; // All the DfgVarPacked vertices we created.
std::vector<DfgVarArray*> m_varArrayps; // All the DfgVarArray vertices we created.
DfgGraph& m_dfg; // The graph being built
V3DfgAstToDfgContext& m_ctx; // The context for stats
// METHODS
static VariableType* getTarget(const AstVarRef* refp) {
static Variable* getTarget(const AstVarRef* refp) {
// TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works
if VL_CONSTEXPR_CXX17 (T_Scoped) {
return reinterpret_cast<VariableType*>(refp->varScopep());
return reinterpret_cast<Variable*>(refp->varScopep());
} else {
return reinterpret_cast<VariableType*>(refp->varp());
return reinterpret_cast<Variable*>(refp->varp());
}
}
static AstVar* getAstVar(VariableType* vp) {
std::unique_ptr<std::vector<Variable*>> getLiveVariables(const CfgGraph& cfg) {
// TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works
if VL_CONSTEXPR_CXX17 (T_Scoped) {
return reinterpret_cast<AstVarScope*>(vp)->varp();
std::unique_ptr<std::vector<AstVarScope*>> result = V3Cfg::liveVarScopes(cfg);
const auto resultp = reinterpret_cast<std::vector<Variable*>*>(result.release());
return std::unique_ptr<std::vector<Variable*>>{resultp};
} else {
return reinterpret_cast<AstVar*>(vp);
std::unique_ptr<std::vector<AstVar*>> result = V3Cfg::liveVars(cfg);
const auto resultp = reinterpret_cast<std::vector<Variable*>*>(result.release());
return std::unique_ptr<std::vector<Variable*>>{resultp};
}
}
void markReferenced(AstNode* nodep) {
nodep->foreach([this](const AstVarRef* refp) {
// No need to (and in fact cannot) mark variables with unsupported dtypes
if (!DfgVertex::isSupportedDType(refp->varp()->dtypep())) return;
VariableType* const tgtp = getTarget(refp);
// Mark vertex as having a module reference outside current DFG
getNet(tgtp)->setHasModRefs();
// Mark variable as written from non-DFG logic
if (refp->access().isWriteOrRW()) tgtp->user3(true);
// Mark variables referenced under node
static void markReferenced(const AstNode* nodep) {
nodep->foreach([](const AstVarRef* refp) {
Variable* const tgtp = getTarget(refp);
// Mark as read from non-DFG logic
if (refp->access().isReadOrRW()) DfgVertexVar::setHasModRdRefs(tgtp);
// Mark as written from non-DFG logic
if (refp->access().isWriteOrRW()) DfgVertexVar::setHasModWrRefs(tgtp);
});
}
void commitVertices() { m_uncommittedVertices.clear(); }
void revertUncommittedVertices() {
for (DfgVertex* const vtxp : m_uncommittedVertices) vtxp->unlinkDelete(*m_dfgp);
m_uncommittedVertices.clear();
DfgVertexVar* getVarVertex(Variable* varp) {
if (!varp->user2p()) {
const AstNodeDType* const dtypep = varp->dtypep()->skipRefp();
DfgVertexVar* const vtxp
= VN_IS(dtypep, UnpackArrayDType)
? static_cast<DfgVertexVar*>(new DfgVarArray{m_dfg, varp})
: static_cast<DfgVertexVar*>(new DfgVarPacked{m_dfg, varp});
varp->user2p(vtxp);
}
return varp->user2u().template to<DfgVertexVar*>();
}
DfgVertexVar* getNet(VariableType* vp) {
if (!vp->user1p()) {
// vp DfgVertexVar vertices are not added to m_uncommittedVertices, because we
// want to hold onto them via AstVar::user1p, and the AstVar might be referenced via
// multiple AstVarRef instances, so we will never revert a DfgVertexVar once
// created. We will delete unconnected variable vertices at the end.
if (VN_IS(vp->dtypep()->skipRefp(), UnpackArrayDType)) {
DfgVarArray* const vtxp = new DfgVarArray{*m_dfgp, vp};
vp->user1p();
m_varArrayps.push_back(vtxp);
vp->user1p(vtxp);
} else {
DfgVarPacked* const vtxp = new DfgVarPacked{*m_dfgp, vp};
m_varPackedps.push_back(vtxp);
vp->user1p(vtxp);
}
}
return vp->user1u().template to<DfgVertexVar*>();
}
DfgVertex* getVertex(AstNode* nodep) {
DfgVertex* vtxp = nodep->user1u().to<DfgVertex*>();
UASSERT_OBJ(vtxp, nodep, "Missing Dfg vertex");
return vtxp;
}
// Returns true if the expression cannot (or should not) be represented by DFG
bool unhandled(AstNodeExpr* nodep) {
// Short-circuiting if something was already unhandled
if (!m_foundUnhandled) {
// Impure nodes cannot be represented
if (!nodep->isPure()) {
m_foundUnhandled = true;
++m_ctx.m_nonRepImpure;
}
// Check node has supported dtype
if (!DfgVertex::isSupportedDType(nodep->dtypep())) {
m_foundUnhandled = true;
++m_ctx.m_nonRepDType;
}
}
return m_foundUnhandled;
}
// Build DfgEdge representing the LValue assignment. Returns false if unsuccessful.
bool convertAssignment(FileLine* flp, AstNode* nodep, DfgVertex* vtxp) {
if (AstVarRef* const vrefp = VN_CAST(nodep, VarRef)) {
m_foundUnhandled = false;
visit(vrefp);
// cppcheck-has-bug-suppress knownConditionTrueFalse
if (m_foundUnhandled) return false;
getVertex(vrefp)->template as<DfgVarPacked>()->addDriver(flp, 0, vtxp);
return true;
}
if (AstSel* const selp = VN_CAST(nodep, Sel)) {
AstVarRef* const vrefp = VN_CAST(selp->fromp(), VarRef);
const AstConst* const lsbp = VN_CAST(selp->lsbp(), Const);
if (!vrefp || !lsbp) {
++m_ctx.m_nonRepLhs;
return false;
}
m_foundUnhandled = false;
visit(vrefp);
// cppcheck-has-bug-suppress knownConditionTrueFalse
if (m_foundUnhandled) return false;
getVertex(vrefp)->template as<DfgVarPacked>()->addDriver(flp, lsbp->toUInt(), vtxp);
return true;
}
if (AstArraySel* const selp = VN_CAST(nodep, ArraySel)) {
AstVarRef* const vrefp = VN_CAST(selp->fromp(), VarRef);
const AstConst* const idxp = VN_CAST(selp->bitp(), Const);
if (!vrefp || !idxp) {
++m_ctx.m_nonRepLhs;
return false;
}
m_foundUnhandled = false;
visit(vrefp);
// cppcheck-has-bug-suppress knownConditionTrueFalse
if (m_foundUnhandled) return false;
getVertex(vrefp)->template as<DfgVarArray>()->addDriver(flp, idxp->toUInt(), vtxp);
return true;
}
if (AstConcat* const concatp = VN_CAST(nodep, Concat)) {
AstNode* const lhsp = concatp->lhsp();
AstNode* const rhsp = concatp->rhsp();
{
FileLine* const lFlp = lhsp->fileline();
DfgSel* const lVtxp = new DfgSel{*m_dfgp, lFlp, DfgVertex::dtypeFor(lhsp)};
lVtxp->fromp(vtxp);
lVtxp->lsb(rhsp->width());
if (!convertAssignment(flp, lhsp, lVtxp)) return false;
}
{
FileLine* const rFlp = rhsp->fileline();
DfgSel* const rVtxp = new DfgSel{*m_dfgp, rFlp, DfgVertex::dtypeFor(rhsp)};
rVtxp->fromp(vtxp);
rVtxp->lsb(0);
return convertAssignment(flp, rhsp, rVtxp);
}
}
++m_ctx.m_nonRepLhs;
return false;
}
bool convertEquation(AstNode* nodep, FileLine* flp, AstNode* lhsp, AstNode* rhsp) {
UASSERT_OBJ(m_uncommittedVertices.empty(), nodep, "Should not nest");
// Currently cannot handle direct assignments between unpacked types. These arise e.g.
// when passing an unpacked array through a module port.
if (!DfgVertex::isSupportedPackedDType(lhsp->dtypep())
|| !DfgVertex::isSupportedPackedDType(rhsp->dtypep())) {
markReferenced(nodep);
++m_ctx.m_nonRepDType;
// Gather variables written by the given logic node.
// Return nullptr if any are not supported.
std::unique_ptr<std::vector<DfgVertexVar*>> gatherWritten(const AstNode* nodep) {
const VNUser3InUse user3InUse;
std::unique_ptr<std::vector<DfgVertexVar*>> resp{new std::vector<DfgVertexVar*>{}};
// We can ignore AstVarXRef here. The only thing we can do with DfgLogic is
// synthesize it into regular vertices, which will fail on a VarXRef at that point.
const bool abort = nodep->exists([&](const AstNodeVarRef* vrefp) -> bool {
if (VN_IS(vrefp, VarXRef)) return true;
if (vrefp->access().isReadOnly()) return false;
Variable* const varp = getTarget(VN_AS(vrefp, VarRef));
if (!DfgGraph::isSupported(varp)) return true;
if (!varp->user3SetOnce()) resp->emplace_back(getVarVertex(varp));
return false;
});
if (abort) {
++m_ctx.m_nonRepVar;
return nullptr;
}
return resp;
}
// Gather variables read by the given logic node.
// Return nullptr if any are not supported.
std::unique_ptr<std::vector<DfgVertexVar*>> gatherRead(const AstNode* nodep) {
const VNUser3InUse user3InUse;
std::unique_ptr<std::vector<DfgVertexVar*>> resp{new std::vector<DfgVertexVar*>{}};
// We can ignore AstVarXRef here. The only thing we can do with DfgLogic is
// synthesize it into regular vertices, which will fail on a VarXRef at that point.
const bool abort = nodep->exists([&](const AstNodeVarRef* vrefp) -> bool {
if (VN_IS(vrefp, VarXRef)) return true;
if (vrefp->access().isWriteOnly()) return false;
Variable* const varp = getTarget(VN_AS(vrefp, VarRef));
if (!DfgGraph::isSupported(varp)) return true;
if (!varp->user3SetOnce()) resp->emplace_back(getVarVertex(varp));
return false;
});
if (abort) {
++m_ctx.m_nonRepVar;
return nullptr;
}
return resp;
}
// Gather variables live in to the given CFG.
// Return nullptr if any are not supported.
std::unique_ptr<std::vector<DfgVertexVar*>> gatherLive(const CfgGraph& cfg) {
// Run analysis
std::unique_ptr<std::vector<Variable*>> varps = getLiveVariables(cfg);
if (!varps) {
++m_ctx.m_nonRepLive;
return nullptr;
}
// Cannot handle mismatched widths. Mismatched assignments should have been fixed up in
// earlier passes anyway, so this should never be hit, but being paranoid just in case.
if (lhsp->width() != rhsp->width()) { // LCOV_EXCL_START
markReferenced(nodep);
++m_ctx.m_nonRepWidth;
return false;
} // LCOV_EXCL_STOP
VL_RESTORER(m_converting);
m_converting = true;
m_foundUnhandled = false;
iterate(rhsp);
// cppcheck-has-bug-suppress knownConditionTrueFalse
if (m_foundUnhandled) {
revertUncommittedVertices();
markReferenced(nodep);
return false;
// Convert to vertics
const VNUser3InUse user3InUse;
std::unique_ptr<std::vector<DfgVertexVar*>> resp{new std::vector<DfgVertexVar*>{}};
resp->reserve(varps->size());
for (Variable* const varp : *varps) {
if (!DfgGraph::isSupported(varp)) {
++m_ctx.m_nonRepVar;
return nullptr;
}
UASSERT_OBJ(!varp->user3SetOnce(), varp, "Live variables should be unique");
resp->emplace_back(getVarVertex(varp));
}
return resp;
}
if (!convertAssignment(flp, lhsp, getVertex(rhsp))) {
revertUncommittedVertices();
markReferenced(nodep);
return false;
// Connect inputs and outputs of a DfgLogic
void connect(DfgLogic& vtx, const std::vector<DfgVertexVar*>& iVarps,
const std::vector<DfgVertexVar*>& oVarps) {
// Connect inputs
for (DfgVertexVar* const iVarp : iVarps) vtx.addInput(iVarp);
// Connect outputs
for (DfgVertexVar* const oVarp : oVarps) {
if (!oVarp->srcp()) oVarp->srcp(new DfgUnresolved{m_dfg, oVarp});
oVarp->srcp()->as<DfgUnresolved>()->addDriver(&vtx);
}
}
// Connect the rhs vertex to the driven edge
commitVertices();
// Convert AstAssignW to DfgLogic, return true if successful.
bool convert(AstAssignW* nodep) {
// Cannot handle assignment with timing control
if (nodep->timingControlp()) return false;
// Remove node from Ast. Now represented by the Dfg.
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
//
// Potentially convertible block
++m_ctx.m_inputs;
// Gather written variables, give up if any are not supported
const std::unique_ptr<std::vector<DfgVertexVar*>> oVarpsp = gatherWritten(nodep);
if (!oVarpsp) return false;
// Gather read variables, give up if any are not supported
const std::unique_ptr<std::vector<DfgVertexVar*>> iVarpsp = gatherRead(nodep);
if (!iVarpsp) return false;
// Create the DfgLogic
DfgLogic* const logicp = new DfgLogic{m_dfg, nodep};
// Connect it up
connect(*logicp, *iVarpsp, *oVarpsp);
// Done
++m_ctx.m_representable;
return true;
}
// Sometime assignment ranges are coalesced by V3Const,
// so we unpack concatenations for better error reporting.
void addDriver(FileLine* flp, uint32_t lsb, DfgVertex* vtxp,
std::vector<Driver>& drivers) const {
if (DfgConcat* const concatp = vtxp->cast<DfgConcat>()) {
DfgVertex* const rhsp = concatp->rhsp();
auto const rhs_width = rhsp->width();
addDriver(rhsp->fileline(), lsb, rhsp, drivers);
DfgVertex* const lhsp = concatp->lhsp();
addDriver(lhsp->fileline(), lsb + rhs_width, lhsp, drivers);
concatp->unlinkDelete(*m_dfgp);
} else {
drivers.emplace_back(flp, lsb, vtxp);
}
}
// Canonicalize packed variables
void canonicalizePacked() {
for (DfgVarPacked* const varp : m_varPackedps) {
// Delete variables with no sinks nor sources (this can happen due to reverting
// uncommitted vertices, which does not remove variables)
if (!varp->hasSinks() && varp->arity() == 0) {
VL_DO_DANGLING(varp->unlinkDelete(*m_dfgp), varp);
continue;
}
// Gather (and unlink) all drivers
std::vector<Driver> drivers;
drivers.reserve(varp->arity());
varp->forEachSourceEdge([this, varp, &drivers](DfgEdge& edge, size_t idx) {
DfgVertex* const driverp = edge.sourcep();
UASSERT(driverp, "Should not have created undriven sources");
addDriver(varp->driverFileLine(idx), varp->driverLsb(idx), driverp, drivers);
edge.unlinkSource();
});
const auto cmp = [](const Driver& a, const Driver& b) {
if (a.m_lsb != b.m_lsb) return a.m_lsb < b.m_lsb;
return a.m_fileline->operatorCompare(*b.m_fileline) < 0;
};
// Sort drivers by LSB
std::stable_sort(drivers.begin(), drivers.end(), cmp);
// Vertices that might have become unused due to multiple driver resolution. Having
// multiple drivers is an error and is hence assumed to be rare, so performance is
// not very important, set will suffice.
std::set<DfgVertex*> prune;
// Fix multiply driven ranges
for (auto it = drivers.begin(); it != drivers.end();) {
Driver& a = *it++;
const uint32_t aWidth = a.m_vtxp->width();
const uint32_t aEnd = a.m_lsb + aWidth;
while (it != drivers.end()) {
Driver& b = *it;
// If no overlap, then nothing to do
if (b.m_lsb >= aEnd) break;
const uint32_t bWidth = b.m_vtxp->width();
const uint32_t bEnd = b.m_lsb + bWidth;
const uint32_t overlapEnd = std::min(aEnd, bEnd) - 1;
if (a.m_fileline->operatorCompare(*b.m_fileline) != 0
&& !varp->varp()->isUsedLoopIdx() // Loop index often abused, so suppress
) {
AstNode* const vp = varp->varScopep()
? static_cast<AstNode*>(varp->varScopep())
: static_cast<AstNode*>(varp->varp());
vp->v3warn( //
MULTIDRIVEN,
"Bits [" //
<< overlapEnd << ":" << b.m_lsb << "] of signal "
<< vp->prettyNameQ() << " have multiple combinational drivers\n"
<< a.m_fileline->warnOther() << "... Location of first driver\n"
<< a.m_fileline->warnContextPrimary() << '\n'
<< b.m_fileline->warnOther() << "... Location of other driver\n"
<< b.m_fileline->warnContextSecondary() << vp->warnOther()
<< "... Only the first driver will be respected");
}
// If the first driver completely covers the range of the second driver,
// we can just delete the second driver completely, otherwise adjust the
// second driver to apply from the end of the range of the first driver.
if (aEnd >= bEnd) {
prune.emplace(b.m_vtxp);
it = drivers.erase(it);
} else {
const auto dtypep = DfgVertex::dtypeForWidth(bEnd - aEnd);
DfgSel* const selp = new DfgSel{*m_dfgp, b.m_vtxp->fileline(), dtypep};
selp->fromp(b.m_vtxp);
selp->lsb(aEnd - b.m_lsb);
b.m_lsb = aEnd;
b.m_vtxp = selp;
std::stable_sort(it, drivers.end(), cmp);
}
}
}
// Coalesce adjacent ranges
for (size_t i = 0, j = 1; j < drivers.size(); ++j) {
Driver& a = drivers[i];
Driver& b = drivers[j];
// Coalesce adjacent range
const uint32_t aWidth = a.m_vtxp->width();
const uint32_t bWidth = b.m_vtxp->width();
if (a.m_lsb + aWidth == b.m_lsb) {
const auto dtypep = DfgVertex::dtypeForWidth(aWidth + bWidth);
DfgConcat* const concatp = new DfgConcat{*m_dfgp, a.m_fileline, dtypep};
concatp->rhsp(a.m_vtxp);
concatp->lhsp(b.m_vtxp);
a.m_vtxp = concatp;
b.m_vtxp = nullptr; // Mark as moved
++m_ctx.m_coalescedAssignments;
continue;
}
++i;
// Compact non-adjacent ranges within the vector
if (j != i) {
Driver& c = drivers[i];
UASSERT_OBJ(!c.m_vtxp, c.m_fileline, "Should have been marked moved");
c = b;
b.m_vtxp = nullptr; // Mark as moved
}
}
// Reinsert drivers in order
varp->resetSources();
for (const Driver& driver : drivers) {
if (!driver.m_vtxp) break; // Stop at end of compacted list
varp->addDriver(driver.m_fileline, driver.m_lsb, driver.m_vtxp);
}
// Prune vertices potentially unused due to resolving multiple drivers.
while (!prune.empty()) {
// Pop last vertex
const auto it = prune.begin();
DfgVertex* const vtxp = *it;
prune.erase(it);
// If used (or a variable), then done
if (vtxp->hasSinks() || vtxp->is<DfgVertexVar>()) continue;
// If unused, then add sources to work list and delete
vtxp->forEachSource([&](DfgVertex& src) { prune.emplace(&src); });
vtxp->unlinkDelete(*m_dfgp);
}
}
}
// Canonicalize array variables
void canonicalizeArray() {
for (DfgVarArray* const varp : m_varArrayps) {
// Delete variables with no sinks nor sources (this can happen due to reverting
// uncommitted vertices, which does not remove variables)
if (!varp->hasSinks() && varp->arity() == 0) {
VL_DO_DANGLING(varp->unlinkDelete(*m_dfgp), varp);
}
// Convert AstAlways to DfgLogic, return true if successful.
bool convert(AstAlways* nodep) {
// Can only handle combinational logic
if (nodep->sentreep()) return false;
const VAlwaysKwd kwd = nodep->keyword();
if (kwd != VAlwaysKwd::ALWAYS && kwd != VAlwaysKwd::ALWAYS_COMB) return false;
// Potentially convertible block
++m_ctx.m_inputs;
// Attempt to build CFG of AstAlways, give up if failed
std::unique_ptr<CfgGraph> cfgp = CfgGraph::build(nodep->stmtsp());
if (!cfgp) {
++m_ctx.m_nonRepCfg;
return false;
}
// Gather written variables, give up if any are not supported
const std::unique_ptr<std::vector<DfgVertexVar*>> oVarpsp = gatherWritten(nodep);
if (!oVarpsp) return false;
// Gather read variables, give up if any are not supported
const std::unique_ptr<std::vector<DfgVertexVar*>> iVarpsp = gatherLive(*cfgp);
if (!iVarpsp) return false;
// Create the DfgLogic
DfgLogic* const logicp = new DfgLogic{m_dfg, nodep, std::move(cfgp)};
// Connect it up
connect(*logicp, *iVarpsp, *oVarpsp);
// Done
++m_ctx.m_representable;
return true;
}
// VISITORS
void visit(AstNode* nodep) override {
// Conservatively treat this node as unhandled
if (!m_foundUnhandled && m_converting) ++m_ctx.m_nonRepUnknown;
m_foundUnhandled = true;
markReferenced(nodep);
}
// Unhandled node
void visit(AstNode* nodep) override { markReferenced(nodep); }
// Containers to descend through to find logic constructs
void visit(AstNetlist* nodep) override { iterateAndNextNull(nodep->modulesp()); }
void visit(AstModule* nodep) override { iterateAndNextNull(nodep->stmtsp()); }
void visit(AstIface* nodep) override {
if (!nodep->hasVirtualRef()) {
iterateAndNextNull(nodep->stmtsp());
} else {
markReferenced(nodep);
}
}
void visit(AstTopScope* nodep) override { iterate(nodep->scopep()); }
void visit(AstScope* nodep) override { iterateChildren(nodep); }
void visit(AstActive* nodep) override {
@ -476,220 +250,44 @@ class AstToDfgVisitor final : public VNVisitor {
}
}
// Non-representable constructs
void visit(AstCell* nodep) override { markReferenced(nodep); }
void visit(AstNodeProcedure* nodep) override { markReferenced(nodep); }
void visit(AstVar* nodep) override {
if VL_CONSTEXPR_CXX17 (T_Scoped) {
return;
} else {
if (nodep->isSc()) return;
// No need to (and in fact cannot) handle variables with unsupported dtypes
if (!DfgVertex::isSupportedDType(nodep->dtypep())) return;
// Mark variables with external references
if (nodep->isIO() // Ports
|| nodep->user2() // Target of a hierarchical reference
|| nodep->isForced() // Forced
) {
getNet(reinterpret_cast<VariableType*>(nodep))->setHasExtRefs();
}
}
}
void visit(AstVarScope* nodep) override {
if VL_CONSTEXPR_CXX17 (!T_Scoped) {
return;
} else {
if (nodep->varp()->isSc()) return;
// No need to (and in fact cannot) handle variables with unsupported dtypes
if (!DfgVertex::isSupportedDType(nodep->dtypep())) return;
// Mark variables with external references
if (nodep->varp()->isIO() // Ports
|| nodep->user2() // Target of a hierarchical reference
|| nodep->varp()->isForced() // Forced
) {
getNet(reinterpret_cast<VariableType*>(nodep))->setHasExtRefs();
}
}
}
// Potentially representable constructs
void visit(AstAssignW* nodep) override {
++m_ctx.m_inputEquations;
// Cannot handle assignment with timing control yet
if (nodep->timingControlp()) {
markReferenced(nodep);
++m_ctx.m_nonRepTiming;
return;
}
convertEquation(nodep, nodep->fileline(), nodep->lhsp(), nodep->rhsp());
if (!convert(nodep)) markReferenced(nodep);
}
void visit(AstAlways* nodep) override {
// Ignore sequential logic, or if there are multiple statements
const VAlwaysKwd kwd = nodep->keyword();
if (nodep->sensesp() || !nodep->isJustOneBodyStmt()
|| (kwd != VAlwaysKwd::ALWAYS && kwd != VAlwaysKwd::ALWAYS_COMB)) {
markReferenced(nodep);
return;
}
AstNode* const stmtp = nodep->stmtsp();
if (AstAssign* const assignp = VN_CAST(stmtp, Assign)) {
++m_ctx.m_inputEquations;
if (assignp->timingControlp()) {
markReferenced(stmtp);
++m_ctx.m_nonRepTiming;
return;
}
convertEquation(nodep, assignp->fileline(), assignp->lhsp(), assignp->rhsp());
} else if (AstIf* const ifp = VN_CAST(stmtp, If)) {
// Will only handle single assignments to the same LHS in both branches
AstAssign* const thenp = VN_CAST(ifp->thensp(), Assign);
AstAssign* const elsep = VN_CAST(ifp->elsesp(), Assign);
if (!thenp || !elsep || thenp->nextp() || elsep->nextp()
|| !thenp->lhsp()->sameTree(elsep->lhsp())) {
markReferenced(stmtp);
return;
}
++m_ctx.m_inputEquations;
if (thenp->timingControlp() || elsep->timingControlp()) {
markReferenced(stmtp);
++m_ctx.m_nonRepTiming;
return;
}
// Create a conditional for the rhs by borrowing the components from the AstIf
AstCond* const rhsp = new AstCond{ifp->fileline(), //
ifp->condp()->unlinkFrBack(), //
thenp->rhsp()->unlinkFrBack(), //
elsep->rhsp()->unlinkFrBack()};
if (!convertEquation(nodep, ifp->fileline(), thenp->lhsp(), rhsp)) {
// Failed to convert. Mark 'rhsp', as 'convertEquation' only marks 'nodep'.
markReferenced(rhsp);
// Put the AstIf back together
ifp->condp(rhsp->condp()->unlinkFrBack());
thenp->rhsp(rhsp->thenp()->unlinkFrBack());
elsep->rhsp(rhsp->elsep()->unlinkFrBack());
}
// Delete the auxiliary conditional
VL_DO_DANGLING(rhsp->deleteTree(), rhsp);
} else {
markReferenced(stmtp);
}
}
void visit(AstVarRef* nodep) override {
UASSERT_OBJ(!nodep->user1p(), nodep, "Already has Dfg vertex");
if (unhandled(nodep)) return;
if (nodep->access().isRW() // Cannot represent read-write references
|| nodep->varp()->isIfaceRef() // Cannot handle interface references
|| nodep->varp()->delayp() // Cannot handle delayed variables
|| nodep->classOrPackagep() // Cannot represent cross module references
) {
markReferenced(nodep);
m_foundUnhandled = true;
++m_ctx.m_nonRepVarRef;
return;
}
// If the referenced variable is not in a regular module, then do not
// convert it. This is especially needed for variabels in interfaces
// which might be referenced via virtual intefaces, which cannot be
// resovled statically.
if (T_Scoped && !VN_IS(nodep->varScopep()->scopep()->modp(), Module)) {
markReferenced(nodep);
m_foundUnhandled = true;
++m_ctx.m_nonRepVarRef;
return;
}
// Sadly sometimes AstVarRef does not have the same dtype as the referenced variable
if (!DfgVertex::isSupportedDType(nodep->varp()->dtypep())) {
m_foundUnhandled = true;
++m_ctx.m_nonRepVarRef;
return;
}
nodep->user1p(getNet(getTarget(nodep)));
}
void visit(AstConst* nodep) override {
UASSERT_OBJ(!nodep->user1p(), nodep, "Already has Dfg vertex");
if (unhandled(nodep)) return;
DfgVertex* const vtxp = new DfgConst{*m_dfgp, nodep->fileline(), nodep->num()};
m_uncommittedVertices.push_back(vtxp);
nodep->user1p(vtxp);
}
void visit(AstSel* nodep) override {
UASSERT_OBJ(!nodep->user1p(), nodep, "Already has Dfg vertex");
if (unhandled(nodep)) return;
iterate(nodep->fromp());
if (m_foundUnhandled) return;
FileLine* const flp = nodep->fileline();
DfgVertex* vtxp = nullptr;
if (AstConst* const constp = VN_CAST(nodep->lsbp(), Const)) {
DfgSel* const selp = new DfgSel{*m_dfgp, flp, DfgVertex::dtypeFor(nodep)};
selp->fromp(nodep->fromp()->user1u().to<DfgVertex*>());
selp->lsb(constp->toUInt());
vtxp = selp;
} else {
iterate(nodep->lsbp());
if (m_foundUnhandled) return;
DfgMux* const muxp = new DfgMux{*m_dfgp, flp, DfgVertex::dtypeFor(nodep)};
muxp->fromp(nodep->fromp()->user1u().to<DfgVertex*>());
muxp->lsbp(nodep->lsbp()->user1u().to<DfgVertex*>());
vtxp = muxp;
}
m_uncommittedVertices.push_back(vtxp);
nodep->user1p(vtxp);
}
// The rest of the 'visit' methods are generated by 'astgen'
#include "V3Dfg__gen_ast_to_dfg.h"
static DfgGraph* makeDfg(RootType& root) {
if VL_CONSTEXPR_CXX17 (T_Scoped) {
return new DfgGraph{nullptr, "netlist"};
} else {
AstModule* const modp = VN_AS((AstNode*)&(root), Module); // Remove this when C++17
return new DfgGraph{modp, modp->name()};
}
if (!convert(nodep)) markReferenced(nodep);
}
// CONSTRUCTOR
explicit AstToDfgVisitor(RootType& root, V3DfgOptimizationContext& ctx)
: m_dfgp{makeDfg(root)}
AstToDfgVisitor(DfgGraph& dfg, RootType& root, V3DfgAstToDfgContext& ctx)
: m_dfg{dfg}
, m_ctx{ctx} {
// Build the DFG
iterate(&root);
UASSERT_OBJ(m_uncommittedVertices.empty(), &root, "Uncommitted vertices remain");
// Canonicalize variables
canonicalizePacked();
canonicalizeArray();
}
public:
static DfgGraph* apply(RootType& root, V3DfgOptimizationContext& ctx) {
return AstToDfgVisitor{root, ctx}.m_dfgp;
static void apply(DfgGraph& dfg, RootType& root, V3DfgAstToDfgContext& ctx) {
// Convert all logic under 'root'
AstToDfgVisitor{dfg, root, ctx};
// Remove unread and undriven variables (created when something failed to convert)
for (DfgVertexVar* const varp : dfg.varVertices().unlinkable()) {
if (!varp->srcp() && !varp->hasSinks()) VL_DO_DANGLING(varp->unlinkDelete(dfg), varp);
}
}
};
DfgGraph* V3DfgPasses::astToDfg(AstModule& module, V3DfgOptimizationContext& ctx) {
return AstToDfgVisitor</* T_Scoped: */ false>::apply(module, ctx);
std::unique_ptr<DfgGraph> V3DfgPasses::astToDfg(AstModule& module, V3DfgContext& ctx) {
DfgGraph* const dfgp = new DfgGraph{&module, module.name()};
AstToDfgVisitor</* T_Scoped: */ false>::apply(*dfgp, module, ctx.m_ast2DfgContext);
return std::unique_ptr<DfgGraph>{dfgp};
}
DfgGraph* V3DfgPasses::astToDfg(AstNetlist& netlist, V3DfgOptimizationContext& ctx) {
return AstToDfgVisitor</* T_Scoped: */ true>::apply(netlist, ctx);
std::unique_ptr<DfgGraph> V3DfgPasses::astToDfg(AstNetlist& netlist, V3DfgContext& ctx) {
DfgGraph* const dfgp = new DfgGraph{nullptr, "netlist"};
AstToDfgVisitor</* T_Scoped: */ true>::apply(*dfgp, netlist, ctx.m_ast2DfgContext);
return std::unique_ptr<DfgGraph>{dfgp};
}

1342
src/V3DfgBreakCycles.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -282,22 +282,22 @@ inline void cache(CacheTernary& cache, DfgVertexTernary* vtxp) {
}
// These remove an existing vertex from the cache, if it is the cached vertex
inline void invalidateByValue(CacheSel& cache, DfgSel* vtxp) {
inline void invalidateByValue(CacheSel& cache, const DfgSel* vtxp) {
const auto it = find(cache, vtxp->dtypep(), vtxp->fromp(), vtxp->lsb());
if (it != cache.end() && it->second == vtxp) cache.erase(it);
}
inline void invalidateByValue(CacheUnary& cache, DfgVertexUnary* vtxp) {
inline void invalidateByValue(CacheUnary& cache, const DfgVertexUnary* vtxp) {
const auto it = find(cache, vtxp->dtypep(), vtxp->source<0>());
if (it != cache.end() && it->second == vtxp) cache.erase(it);
}
inline void invalidateByValue(CacheBinary& cache, DfgVertexBinary* vtxp) {
inline void invalidateByValue(CacheBinary& cache, const DfgVertexBinary* vtxp) {
const auto it = find(cache, vtxp->dtypep(), vtxp->source<0>(), vtxp->source<1>());
if (it != cache.end() && it->second == vtxp) cache.erase(it);
}
inline void invalidateByValue(CacheTernary& cache, DfgVertexTernary* vtxp) {
inline void invalidateByValue(CacheTernary& cache, const DfgVertexTernary* vtxp) {
const auto it
= find(cache, vtxp->dtypep(), vtxp->source<0>(), vtxp->source<1>(), vtxp->source<2>());
if (it != cache.end() && it->second == vtxp) cache.erase(it);

156
src/V3DfgColorSCCs.cpp Normal file
View File

@ -0,0 +1,156 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Cycle finding algorithm for DfgGraph
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2025 by Wilson Snyder. This program is free software; you
// can redistribute it and/or modify it under the terms of either the GNU
// Lesser General Public License Version 3 or the Perl Artistic License
// Version 2.0.
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
//
// Implements Pearce's algorithm to color the strongly connected components. For reference
// see "An Improved Algorithm for Finding the Strongly Connected Components of a Directed
// Graph", David J.Pearce, 2005.
//
//*************************************************************************
#include "V3Dfg.h"
#include "V3DfgPasses.h"
#include <limits>
#include <vector>
// Similar algorithm used in ExtractCyclicComponents.
// This one sets DfgVertex::user(). See the static 'apply' method below.
class ColorStronglyConnectedComponents final {
static constexpr uint32_t UNASSIGNED = std::numeric_limits<uint32_t>::max();
// TYPES
struct VertexState final {
uint32_t component = UNASSIGNED; // Result component number (0 means not in SCC)
uint32_t index = UNASSIGNED; // Used by Pearce's algorithm for detecting SCCs
VertexState() = default;
VertexState(uint32_t i, uint32_t n)
: component{n}
, index{i} {}
};
// STATE
DfgGraph& m_dfg; // The input graph
uint32_t m_nonTrivialSCCs = 0; // Number of non-trivial SCCs in the graph
uint32_t m_index = 0; // Visitation index counter
std::vector<DfgVertex*> m_stack; // The stack used by the algorithm
// METHODS
void visitColorSCCs(DfgVertex& vtx, VertexState& vtxState) {
UDEBUGONLY(UASSERT_OBJ(vtxState.index == UNASSIGNED, &vtx, "Already visited vertex"););
// Visiting vertex
const size_t rootIndex = vtxState.index = ++m_index;
// Visit children
vtx.forEachSink([&](DfgVertex& child) {
VertexState& childSatate = child.user<VertexState>();
// If the child has not yet been visited, then continue traversal
if (childSatate.index == UNASSIGNED) visitColorSCCs(child, childSatate);
// If the child is not in an SCC
if (childSatate.component == UNASSIGNED) {
if (vtxState.index > childSatate.index) vtxState.index = childSatate.index;
}
});
if (vtxState.index == rootIndex) {
// This is the 'root' of an SCC
// A trivial SCC contains only a single vertex
const bool isTrivial = m_stack.empty() //
|| m_stack.back()->getUser<VertexState>().index < rootIndex;
// We also need a separate component for vertices that drive themselves (which can
// happen for input like 'assign a = a'), as we want to extract them (they are cyclic).
const bool drivesSelf = vtx.findSink<DfgVertex>([&vtx](const DfgVertex& sink) { //
return &vtx == &sink;
});
if (!isTrivial || drivesSelf) {
// Allocate new component
++m_nonTrivialSCCs;
vtxState.component = m_nonTrivialSCCs;
while (!m_stack.empty()) {
VertexState& topState = m_stack.back()->getUser<VertexState>();
// Only higher nodes belong to the same SCC
if (topState.index < rootIndex) break;
m_stack.pop_back();
topState.component = m_nonTrivialSCCs;
}
} else {
// Trivial SCC (and does not drive itself), so acyclic. Keep it in original graph.
vtxState.component = 0;
}
} else {
// Not the root of an SCC
m_stack.push_back(&vtx);
}
}
void colorSCCs() {
// We know constant nodes have no input edges, so they cannot be part
// of a non-trivial SCC. Mark them as such without any real traversals.
for (DfgConst& vtx : m_dfg.constVertices()) vtx.setUser(VertexState{0, 0});
// Start traversals through variables
for (DfgVertexVar& vtx : m_dfg.varVertices()) {
VertexState& vtxState = vtx.user<VertexState>();
// If it has no input or no outputs, it cannot be part of a non-trivial SCC.
if (vtx.arity() == 0 || !vtx.hasSinks()) {
UDEBUGONLY(UASSERT_OBJ(vtxState.index == UNASSIGNED || vtxState.component == 0,
&vtx, "Non circular variable must be in a trivial SCC"););
vtxState.index = 0;
vtxState.component = 0;
continue;
}
// If not yet visited, start a traversal
if (vtxState.index == UNASSIGNED) visitColorSCCs(vtx, vtxState);
}
// Start traversals through operations
for (DfgVertex& vtx : m_dfg.opVertices()) {
VertexState& vtxState = vtx.user<VertexState>();
// If not yet visited, start a traversal
if (vtxState.index == UNASSIGNED) visitColorSCCs(vtx, vtxState);
}
}
explicit ColorStronglyConnectedComponents(DfgGraph& dfg)
: m_dfg{dfg} {
UASSERT(dfg.size() < UNASSIGNED, "Graph too big " << dfg.name());
// Yet another implementation of Pearce's algorithm.
colorSCCs();
// Re-assign user values
m_dfg.forEachVertex([](DfgVertex& vtx) {
const uint64_t component = vtx.getUser<VertexState>().component;
vtx.setUser<uint64_t>(component);
});
}
public:
// Sets DfgVertex::user<uint64_t>() for all vertext to:
// - 0, if the vertex is not part of a non-trivial strongly connected component
// and is not part of a self-loop. That is: the Vertex is not part of any cycle.
// - N, if the vertex is part of a non-trivial strongly conneced component or self-loop N.
// That is: each set of vertices that are reachable from each other will have the same
// non-zero value assigned.
// Returns the number of non-trivial SCCs (~distinct cycles)
static uint32_t apply(DfgGraph& dfg) {
return ColorStronglyConnectedComponents{dfg}.m_nonTrivialSCCs;
}
};
uint32_t V3DfgPasses::colorStronglyConnectedComponents(DfgGraph& dfg) {
return ColorStronglyConnectedComponents::apply(dfg);
}

398
src/V3DfgContext.h Normal file
View File

@ -0,0 +1,398 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Persistent context for DFG algorithms
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2025 by Wilson Snyder. This program is free software; you
// can redistribute it and/or modify it under the terms of either the GNU
// Lesser General Public License Version 3 or the Perl Artistic License
// Version 2.0.
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
//
// Various context objects hold data that need to persist across invocations
// of a DFG algorithms.
//
//*************************************************************************
#ifndef VERILATOR_V3DFGCONTEXT_H_
#define VERILATOR_V3DFGCONTEXT_H_
#include "config_build.h"
#include "verilatedos.h"
#include "V3DfgPatternStats.h"
#include "V3DfgPeepholePatterns.h"
#include "V3File.h"
#include "V3Stats.h"
#include "V3String.h"
class V3DfgContext;
//////////////////////////////////////////////////////////////////////////////
// Base class for all context objects
//////////////////////////////////////////////////////////////////////////////
class V3DfgSubContext VL_NOT_FINAL {
V3DfgContext& m_ctx; // The whole context
const std::string m_label; // Label to add to stats, etc.
const std::string m_name; // Pass/algorithm name, to be used in statistics
protected:
VL_DEFINE_DEBUG_FUNCTIONS;
V3DfgSubContext(V3DfgContext& ctx, const std::string& label, const char* name)
: m_ctx{ctx}
, m_label{label}
, m_name{name} {}
void addStat(const std::string& what, double value) {
V3Stats::addStat("Optimizations, DFG " + m_label + " " + m_name + ", " + what, value);
}
public:
inline const std::string& prefix() const;
const std::string& label() const { return m_label; }
};
//////////////////////////////////////////////////////////////////////////////
// Contexts for various algorithms - keep sorted
//////////////////////////////////////////////////////////////////////////////
class V3DfgAstToDfgContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
VDouble0 m_inputs; // Number of input processes (logic constructs)
VDouble0 m_representable; // Number of combinational equations representable
VDouble0 m_nonRepCfg; // Non-representable due to failing to build CFG
VDouble0 m_nonRepLive; // Non-representable due to failing liveness analysis
VDouble0 m_nonRepVar; // Non-representable due to unsupported variable properties
private:
V3DfgAstToDfgContext(V3DfgContext& ctx, const std::string& label)
: V3DfgSubContext{ctx, label, "AstToDfg"} {}
~V3DfgAstToDfgContext() {
addStat("input processes", m_inputs);
addStat("representable", m_representable);
addStat("non-representable (cfg)", m_nonRepCfg);
addStat("non-representable (live)", m_nonRepLive);
addStat("non-representable (var)", m_nonRepVar);
// Check the stats are consistent
UASSERT(m_representable + m_nonRepCfg + m_nonRepLive + m_nonRepVar == m_inputs,
"Inconsistent statistics");
}
};
class V3DfgBinToOneHotContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
VDouble0 m_decodersCreated; // Number of bianry to one-hot decoders created
private:
V3DfgBinToOneHotContext(V3DfgContext& ctx, const std::string& label)
: V3DfgSubContext{ctx, label, "BinToOneHot"} {}
~V3DfgBinToOneHotContext() { addStat("decoders created", m_decodersCreated); }
};
class V3DfgBreakCyclesContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
VDouble0 m_nFixed; // Number of graphs that became acyclic
VDouble0 m_nImproved; // Number of graphs that were imporoved but still cyclic
VDouble0 m_nUnchanged; // Number of graphs that were left unchanged
VDouble0 m_nTrivial; // Number of graphs that were not changed
VDouble0 m_nImprovements; // Number of changes made to graphs
private:
V3DfgBreakCyclesContext(V3DfgContext& ctx, const std::string& label)
: V3DfgSubContext{ctx, label, "BreakCycles"} {}
~V3DfgBreakCyclesContext() {
addStat("made acyclic", m_nFixed);
addStat("improved", m_nImproved);
addStat("left unchanged", m_nUnchanged);
addStat("trivial", m_nTrivial);
addStat("changes applied", m_nImprovements);
}
};
class V3DfgCseContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
VDouble0 m_eliminated; // Number of common sub-expressions eliminated
private:
V3DfgCseContext(V3DfgContext& ctx, const std::string& label)
: V3DfgSubContext{ctx, label, "CSE"} {}
~V3DfgCseContext() { addStat("expressions eliminated", m_eliminated); }
};
class V3DfgDfgToAstContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
VDouble0 m_outputVariables; // Number of output variables
VDouble0 m_outputVariablesWithDefault; // Number of outptu variables with a default driver
VDouble0 m_resultEquations; // Number of result combinational equations
private:
V3DfgDfgToAstContext(V3DfgContext& ctx, const std::string& label)
: V3DfgSubContext{ctx, label, "DfgToAst"} {}
~V3DfgDfgToAstContext() {
addStat("output variables", m_outputVariables);
addStat("output variables with default driver", m_outputVariablesWithDefault);
addStat("result equations", m_resultEquations);
}
};
class V3DfgEliminateVarsContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
std::vector<AstNode*> m_deleteps; // AstVar/AstVarScope that can be deleted at the end
VDouble0 m_varsReplaced; // Number of variables replaced
VDouble0 m_varsRemoved; // Number of variables removed
private:
V3DfgEliminateVarsContext(V3DfgContext& ctx, const std::string& label)
: V3DfgSubContext{ctx, label, "EliminateVars"} {}
~V3DfgEliminateVarsContext() {
for (AstNode* const nodep : m_deleteps) {
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
addStat("variables replaced", m_varsReplaced);
addStat("variables removed", m_varsRemoved);
}
};
class V3DfgPeepholeContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
// Enable flags for each optimization
std::array<bool, VDfgPeepholePattern::_ENUM_END> m_enabled;
// Count of applications for each optimization (for statistics)
std::array<VDouble0, VDfgPeepholePattern::_ENUM_END> m_count;
private:
V3DfgPeepholeContext(V3DfgContext& ctx, const std::string& label) VL_MT_DISABLED;
~V3DfgPeepholeContext() VL_MT_DISABLED;
};
class V3DfgRegularizeContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
VDouble0 m_temporariesIntroduced; // Number of temporaries introduced
private:
V3DfgRegularizeContext(V3DfgContext& ctx, const std::string& label)
: V3DfgSubContext{ctx, label, "Regularize"} {}
~V3DfgRegularizeContext() { addStat("temporaries introduced", m_temporariesIntroduced); }
};
class V3DfgSynthesisContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
// Stats for conversion
struct {
// Inputs
VDouble0 inputAssignments; // Number of input assignments
VDouble0 inputExpressions; // Number of input equations
// Successful
VDouble0 representable; // Number of representable constructs
// Unsuccessful
VDouble0 nonRepImpure; // Non representable: impure
VDouble0 nonRepDType; // Non representable: unsupported data type
VDouble0 nonRepLValue; // Non representable: unsupported LValue form
VDouble0 nonRepVarRef; // Non representable: unsupported var reference
VDouble0 nonRepNode; // Non representable: unsupported AstNode type
VDouble0 nonRepUnknown; // Non representable: unhandled AstNode type
} m_conv;
// Stats for synthesis
struct {
// Inputs
VDouble0 inputAlways; // Number of always blocks attempted
VDouble0 inputAssign; // Number of continuous assignments attempted
// Successful
VDouble0 synthAlways; // Number of always blocks successfully synthesized
VDouble0 synthAssign; // Number of continuous assignments successfully synthesized
// Unsuccessful
VDouble0 nonSynConv; // Non synthesizable: non representable (above)
VDouble0 nonSynExtWrite; // Non synthesizable: has externally written variable
VDouble0 nonSynLoop; // Non synthesizable: loop in CFG
VDouble0 nonSynStmt; // Non synthesizable: unsupported statement
VDouble0 nonSynMultidrive; // Non synthesizable: multidriven value within statement
VDouble0 nonSynArray; // Non synthesizable: array type unhandled
VDouble0 nonSynLatch; // Non synthesizable: maybe latch
VDouble0 nonSynJoinInput; // Non synthesizable: needing to join input variable
VDouble0 nonSynFalseWrite; // Non synthesizable: does not write output
// Reverted
VDouble0 revertNonSyn; // Reverted due to being driven from non-synthesizable vertex
VDouble0 revertMultidrive; // Reverted due to multiple drivers
// Additional stats
VDouble0 cfgTrivial; // Trivial input CFGs
VDouble0 cfgSp; // Series-paralel input CFGs
VDouble0 cfgDag; // Generic loop free input CFGs
VDouble0 cfgCyclic; // Cyclic input CFGs
VDouble0 joinUsingPathPredicate; // Num control flow joins using full path predicate
VDouble0 joinUsingBranchCondition; // Num Control flow joins using dominating branch cond
} m_synt;
private:
V3DfgSynthesisContext(V3DfgContext& ctx, const std::string& label)
: V3DfgSubContext{ctx, label, "Synthesis"} {}
~V3DfgSynthesisContext() {
// Conversion statistics
addStat("conv / input assignments", m_conv.inputAssignments);
addStat("conv / input expressions", m_conv.inputExpressions);
addStat("conv / representable inputs", m_conv.representable);
addStat("conv / non-representable (impure)", m_conv.nonRepImpure);
addStat("conv / non-representable (dtype)", m_conv.nonRepDType);
addStat("conv / non-representable (lhs)", m_conv.nonRepLValue);
addStat("conv / non-representable (varref)", m_conv.nonRepVarRef);
addStat("conv / non-representable (node)", m_conv.nonRepNode);
addStat("conv / non-representable (unknown)", m_conv.nonRepUnknown);
VDouble0 nConvNonRep;
nConvNonRep += m_conv.nonRepImpure;
nConvNonRep += m_conv.nonRepDType;
nConvNonRep += m_conv.nonRepLValue;
nConvNonRep += m_conv.nonRepVarRef;
nConvNonRep += m_conv.nonRepNode;
nConvNonRep += m_conv.nonRepUnknown;
VDouble0 nConvExpect;
nConvExpect += m_conv.inputAssignments;
nConvExpect += m_conv.inputExpressions;
nConvExpect -= m_conv.representable;
UASSERT(nConvNonRep == nConvExpect, "Inconsistent statistics / conv");
// Synthesis statistics
addStat("synt / always blocks considered", m_synt.inputAlways);
addStat("synt / always blocks synthesized", m_synt.synthAlways);
addStat("synt / continuous assignments considered", m_synt.inputAssign);
addStat("synt / continuous assignments synthesized", m_synt.synthAssign);
addStat("synt / non-synthesizable (conv)", m_synt.nonSynConv);
addStat("synt / non-synthesizable (ext write)", m_synt.nonSynExtWrite);
addStat("synt / non-synthesizable (loop)", m_synt.nonSynLoop);
addStat("synt / non-synthesizable (stmt)", m_synt.nonSynStmt);
addStat("synt / non-synthesizable (multidrive)", m_synt.nonSynMultidrive);
addStat("synt / non-synthesizable (array)", m_synt.nonSynArray);
addStat("synt / non-synthesizable (latch)", m_synt.nonSynLatch);
addStat("synt / non-synthesizable (join input)", m_synt.nonSynJoinInput);
addStat("synt / non-synthesizable (false write)", m_synt.nonSynFalseWrite);
addStat("synt / reverted (non-synthesizable)", m_synt.revertNonSyn);
addStat("synt / reverted (multidrive)", m_synt.revertMultidrive);
VDouble0 nSyntNonSyn;
nSyntNonSyn += m_synt.nonSynConv;
nSyntNonSyn += m_synt.nonSynExtWrite;
nSyntNonSyn += m_synt.nonSynLoop;
nSyntNonSyn += m_synt.nonSynStmt;
nSyntNonSyn += m_synt.nonSynMultidrive;
nSyntNonSyn += m_synt.nonSynArray;
nSyntNonSyn += m_synt.nonSynLatch;
nSyntNonSyn += m_synt.nonSynJoinInput;
nSyntNonSyn += m_synt.nonSynFalseWrite;
VDouble0 nSyntExpect;
nSyntExpect += m_synt.inputAlways;
nSyntExpect += m_synt.inputAssign;
nSyntExpect -= m_synt.synthAlways;
nSyntExpect -= m_synt.synthAssign;
UASSERT(nSyntNonSyn == nSyntExpect, "Inconsistent statistics / synt");
addStat("synt / input CFG trivial", m_synt.cfgTrivial);
addStat("synt / input CFG sp", m_synt.cfgSp);
addStat("synt / input CFG dag", m_synt.cfgDag);
addStat("synt / input CFG cyclic", m_synt.cfgCyclic);
addStat("synt / joins using path predicate", m_synt.joinUsingPathPredicate);
addStat("synt / joins using branch condition", m_synt.joinUsingBranchCondition);
}
};
//////////////////////////////////////////////////////////////////////////////
// Top level V3DfgContext
//////////////////////////////////////////////////////////////////////////////
class V3DfgContext final {
const std::string m_label; // Label to add to stats, etc.
const std::string m_prefix; // Prefix to add to file dumps (derived from label)
public:
// STATE
// Global statistics
VDouble0 m_modules; // Number of modules optimized
// Sub contexts - keep sorted by type
V3DfgAstToDfgContext m_ast2DfgContext{*this, m_label};
V3DfgBinToOneHotContext m_binToOneHotContext{*this, m_label};
V3DfgBreakCyclesContext m_breakCyclesContext{*this, m_label};
V3DfgCseContext m_cseContext0{*this, m_label + " 1st"};
V3DfgCseContext m_cseContext1{*this, m_label + " 2nd"};
V3DfgDfgToAstContext m_dfg2AstContext{*this, m_label};
V3DfgEliminateVarsContext m_eliminateVarsContext{*this, m_label};
V3DfgPeepholeContext m_peepholeContext{*this, m_label};
V3DfgRegularizeContext m_regularizeContext{*this, m_label};
V3DfgSynthesisContext m_synthContext{*this, m_label};
// Node pattern collector
V3DfgPatternStats m_patternStats;
// CONSTRUCTOR
explicit V3DfgContext(const std::string& label)
: m_label{label}
, m_prefix{VString::removeWhitespace(label) + "-"} {}
~V3DfgContext() {
const string front = "Optimizations, DFG " + label() + " General, ";
V3Stats::addStat(front + "modules", m_modules);
// Print the collected patterns
if (v3Global.opt.stats()) {
// Label to lowercase, without spaces
std::string ident = label();
std::transform(ident.begin(), ident.end(), ident.begin(), [](unsigned char c) { //
return c == ' ' ? '_' : std::tolower(c);
});
// File to dump to
const std::string filename = v3Global.opt.hierTopDataDir() + "/"
+ v3Global.opt.prefix() + "__stats_dfg_patterns__" + ident
+ ".txt";
// Open, write, close
const std::unique_ptr<std::ofstream> ofp{V3File::new_ofstream(filename)};
if (ofp->fail()) v3fatal("Can't write file: " << filename);
m_patternStats.dump(label(), *ofp);
}
}
// ACCESSORS
const std::string& label() const { return m_label; }
const std::string& prefix() const { return m_prefix; }
};
const std::string& V3DfgSubContext::prefix() const { return m_ctx.prefix(); }
#endif //VERILATOR_V3DFGCONTEXT_H_

Some files were not shown because too many files have changed in this diff Show More