Merge from master for release.
This commit is contained in:
commit
0a9d9db5ae
|
|
@ -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: |
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
114
Changes
|
|
@ -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]
|
||||
|
|
|
|||
84
Makefile.in
84
Makefile.in
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
.. comment: generated by t_event_control_star_never_bad
|
||||
.. code-block:: sv
|
||||
|
||||
always @* a = 100;
|
||||
|
|
@ -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)
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
.. comment: generated by t_lint_paramnodefault_bad
|
||||
.. code-block:: sv
|
||||
:linenos:
|
||||
:emphasize-lines: 2
|
||||
|
||||
module sub;
|
||||
parameter NODEF; //<--- Warning
|
||||
|
|
@ -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;
|
||||
| ^~~~~
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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*/`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
--------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
//===========================================================================
|
||||
|
|
|
|||
|
|
@ -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) \
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ protected:
|
|||
void commitTraceBuffer(Buffer*) override;
|
||||
|
||||
// Configure sub-class
|
||||
void configure(const VerilatedTraceConfig&) override{};
|
||||
void configure(const VerilatedTraceConfig&) override {};
|
||||
|
||||
public:
|
||||
//=========================================================================
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
141
src/V3Assert.cpp
141
src/V3Assert.cpp
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
88
src/V3Ast.h
88
src/V3Ast.h
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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(""); }
|
||||
|
|
|
|||
1656
src/V3AstNodeOther.h
1656
src/V3AstNodeOther.h
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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_
|
||||
|
|
@ -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); }
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
|
|
|
|||
494
src/V3Const.cpp
494
src/V3Const.cpp
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
||||
//--------------------
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
652
src/V3Dfg.cpp
652
src/V3Dfg.cpp
|
|
@ -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 {
|
||||
|
|
|
|||
292
src/V3Dfg.h
292
src/V3Dfg.h
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
Loading…
Reference in New Issue