mirror of
https://github.com/verilator/verilator.git
synced 2026-08-30 17:51:59 +02:00
Change WDataInP/WDataOutP to be opaque handles types instead of aliases to raw pointers. This subsequently eliminates needing an implicit cast operator in VlWide, which is replaced with implicit constructors of WDataInP/WDataOutP that can create a handle from a VlWide. This eliminates some unsafe conversions that the previous implicit cast operator unintentionally enabled (e.g. #7618). It also eliminates having to insert ".data()" in various places int he generated code, which simplifies internals (the only place ".data()" should be needed is in calls to variadic functions where the expected type of the argument is not WDataInP/WDataOutP). The handles otherwise behave like pointers, implementing the minimal amount of operators required to code the runtime. The handle is still only a single pointer, and will be passed in registers as before, so this patch should be performance neutral. As part of this removed WData, which used to be an alias for EData. All uses are now either EData*, WDataInP, WDataOutP, or VlWide directly.
303 lines
11 KiB
C++
303 lines
11 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
//
|
|
// Code available from: https://verilator.org
|
|
//
|
|
// Copyright 2026-2026 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-FileCopyrightText: 2026 Wilson Snyder
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
|
//
|
|
//*************************************************************************
|
|
///
|
|
/// \file
|
|
/// \brief Verilator: Runtime support for force/release statements
|
|
///
|
|
/// This file provides runtime data structures for efficient dynamic
|
|
/// resolution of force/release statements. A sorted list of active
|
|
/// forces is maintained that can be efficiently queried and modified
|
|
/// at runtime.
|
|
///
|
|
//*************************************************************************
|
|
|
|
#ifndef VERILATOR_VERILATED_FORCE_H_
|
|
#define VERILATOR_VERILATED_FORCE_H_
|
|
|
|
#include "verilatedos.h"
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cstddef>
|
|
#include <type_traits>
|
|
#include <vector>
|
|
|
|
template <typename T>
|
|
using VlForceBaseType = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
|
|
|
|
// VlForceRead - Helper functions to read a forced value
|
|
//
|
|
// These functions combine original value with forced values based on
|
|
// VlForceVec entries.
|
|
// This achieves O(k) complexity where k = number of active forces.
|
|
|
|
template <typename T>
|
|
struct VlForceTypeInfo final {
|
|
using Type = VlForceBaseType<T>;
|
|
static constexpr bool bitwise
|
|
= std::is_integral<Type>::value || std::is_enum<Type>::value || VlIsVlWide<Type>::value;
|
|
static constexpr bool unpackedArray = false;
|
|
};
|
|
|
|
template <typename T>
|
|
struct VlForceArrayIndexer final {
|
|
static constexpr std::size_t size = 1;
|
|
|
|
static T& elem(T& value, std::size_t) { return value; }
|
|
};
|
|
|
|
template <typename T, std::size_t N>
|
|
struct VlForceArrayIndexer<VlUnpacked<T, N>> final {
|
|
static constexpr std::size_t size = N * VlForceArrayIndexer<T>::size;
|
|
|
|
static auto& elem(VlUnpacked<T, N>& array, std::size_t index) {
|
|
constexpr std::size_t subSize = VlForceArrayIndexer<T>::size;
|
|
return VlForceArrayIndexer<T>::elem(array[index / subSize], index % subSize);
|
|
}
|
|
};
|
|
|
|
template <typename T, std::size_t N>
|
|
struct VlForceTypeInfo<VlUnpacked<T, N>> final {
|
|
using Type = VlUnpacked<T, N>;
|
|
static constexpr bool bitwise = false;
|
|
static constexpr bool unpackedArray = true;
|
|
};
|
|
|
|
template <typename T, bool = std::is_enum<T>::value>
|
|
struct VlForceStorageTypeOf final {
|
|
using type = typename std::make_unsigned<T>::type;
|
|
};
|
|
|
|
template <typename T>
|
|
struct VlForceStorageTypeOf<T, true> final {
|
|
using type = typename std::make_unsigned<typename std::underlying_type<T>::type>::type;
|
|
};
|
|
|
|
template <typename T>
|
|
using VlForceStorageType = typename VlForceStorageTypeOf<VlForceBaseType<T>>::type;
|
|
|
|
//=============================================================================
|
|
// VlForceVec - Vector of active force entries for a signal
|
|
//
|
|
// This class maintains a sorted vector of non-overlapping force entries.
|
|
// When a new force is added, it removes or trims existing entries that
|
|
// overlap with the new range.
|
|
//
|
|
// The generated code will:
|
|
// 1. Use addForce/release to update the active forces
|
|
// 2. Call a generated read function that iterates entries and evaluates RHS
|
|
|
|
class VlForceVec final {
|
|
private:
|
|
struct Entry final {
|
|
int m_lsb; // Inclusive lower bit
|
|
int m_msb; // Inclusive upper bit
|
|
int m_rhsLsb; // Destination index that maps to RHS index 0
|
|
const void* m_rhsDatap; // Pointer to RHS storage
|
|
|
|
bool operator<(const Entry& other) const { return m_msb < other.m_msb; }
|
|
};
|
|
|
|
std::vector<Entry> m_entries; // Sorted by msb, non-overlapping
|
|
|
|
std::vector<Entry>::iterator trimEntries(int lsb, int msb) {
|
|
auto it = std::lower_bound(m_entries.begin(), m_entries.end(), lsb,
|
|
[](const Entry& e, int bit) { return e.m_msb < bit; });
|
|
while (it != m_entries.end() && it->m_lsb <= msb) {
|
|
if (it->m_lsb < lsb && it->m_msb > msb) {
|
|
const Entry right{msb + 1, it->m_msb, it->m_rhsLsb, it->m_rhsDatap};
|
|
it->m_msb = lsb - 1;
|
|
return m_entries.insert(++it, right);
|
|
}
|
|
if (it->m_lsb < lsb) {
|
|
it->m_msb = lsb - 1;
|
|
++it;
|
|
continue;
|
|
}
|
|
if (it->m_msb > msb) {
|
|
it->m_lsb = msb + 1;
|
|
return it;
|
|
}
|
|
it = m_entries.erase(it);
|
|
}
|
|
return it;
|
|
}
|
|
|
|
static QData extractRhsChunk(const Entry& entry, int rhsLsb, int width) {
|
|
assert(width > 0 && width <= VL_QUADSIZE);
|
|
assert(rhsLsb >= 0);
|
|
|
|
const QData mask = static_cast<QData>(VL_MASK_Q(width));
|
|
const int rhsWidth = entry.m_msb - entry.m_rhsLsb + 1;
|
|
if (rhsWidth <= VL_QUADSIZE) {
|
|
const QData rhsVal = static_cast<QData>(*static_cast<const QData*>(entry.m_rhsDatap));
|
|
return (rhsVal >> rhsLsb) & mask;
|
|
}
|
|
|
|
WDataInP rhswp = WDataInP::external(static_cast<const EData*>(entry.m_rhsDatap));
|
|
return VL_SEL_QWII(rhsWidth, rhswp, rhsLsb, width) & mask;
|
|
}
|
|
|
|
template <typename T>
|
|
static T applyBits(T cur, const Entry& entry, int lsb, int width, int rhsLsb) {
|
|
const T lowMask = static_cast<T>(VL_MASK_Q(width));
|
|
const T mask = static_cast<T>(lowMask << lsb);
|
|
const T rhsBits = static_cast<T>(
|
|
(static_cast<T>(extractRhsChunk(entry, rhsLsb, width)) & lowMask) << lsb);
|
|
return static_cast<T>((cur & ~mask) | (rhsBits & mask));
|
|
}
|
|
|
|
static void applyEntry(WDataOutP reswp, const Entry& entry, int entryLsb, int entryMsb,
|
|
int lsbOffset) {
|
|
const int resLsb = entryLsb - lsbOffset;
|
|
const int resMsb = entryMsb - lsbOffset;
|
|
const int lword = VL_BITWORD_E(resLsb);
|
|
const int hword = VL_BITWORD_E(resMsb);
|
|
for (int word = lword; word <= hword; ++word) {
|
|
const int wordLsb = word * VL_EDATASIZE;
|
|
const int segLsb = std::max(resLsb, wordLsb);
|
|
const int segMsb = std::min(resMsb, wordLsb + VL_EDATASIZE - 1);
|
|
const int segWidth = segMsb - segLsb + 1;
|
|
const int bitOffset = segLsb - wordLsb;
|
|
const int rhsLsb = lsbOffset + segLsb - entry.m_rhsLsb;
|
|
reswp[word] = applyBits(reswp[word], entry, bitOffset, segWidth, rhsLsb);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
static typename std::enable_if<!VlIsVlWide<T>::value && VlForceTypeInfo<T>::bitwise, T>::type
|
|
applyEntry(T result, const Entry& entry) {
|
|
using U = VlForceStorageType<T>;
|
|
const int width = entry.m_msb - entry.m_lsb + 1;
|
|
const int bits = static_cast<int>(sizeof(U) * 8);
|
|
const int rhsLsb = entry.m_lsb - entry.m_rhsLsb;
|
|
const QData rhsChunk = extractRhsChunk(entry, rhsLsb, width);
|
|
if (width >= bits) return static_cast<T>(static_cast<U>(rhsChunk));
|
|
return static_cast<T>(
|
|
applyBits(static_cast<U>(result), entry, entry.m_lsb, width, rhsLsb));
|
|
}
|
|
|
|
template <typename T>
|
|
static typename std::enable_if<!VlForceTypeInfo<T>::bitwise, T>::type
|
|
applyEntry(T result, const Entry& entry) {
|
|
static_cast<void>(result);
|
|
return *static_cast<const VlForceBaseType<T>*>(entry.m_rhsDatap);
|
|
}
|
|
|
|
template <typename T>
|
|
typename std::enable_if<VlIsVlWide<T>::value>::type applyEntries(T& val) const {
|
|
for (const auto& entry : m_entries) {
|
|
applyEntry(val, entry, entry.m_lsb, entry.m_msb, 0);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
typename std::enable_if<!VlIsVlWide<T>::value>::type applyEntries(T& val) const {
|
|
for (const auto& entry : m_entries) val = applyEntry(val, entry);
|
|
}
|
|
|
|
void readSel(int lbits, WDataInP valp, WDataOutP reswp, int lsb, int width) const {
|
|
VL_SEL_WWII(width, lbits, reswp, valp, lsb, width);
|
|
const int msb = lsb + width - 1;
|
|
auto it = std::lower_bound(m_entries.begin(), m_entries.end(), lsb,
|
|
[](const Entry& e, int bit) { return e.m_msb < bit; });
|
|
while (it != m_entries.end() && it->m_lsb <= msb) {
|
|
applyEntry(reswp, *it, std::max(it->m_lsb, lsb), std::min(it->m_msb, msb), lsb);
|
|
++it;
|
|
}
|
|
}
|
|
|
|
public:
|
|
VlForceVec() = default;
|
|
|
|
template <typename T>
|
|
T read(const T& val) const {
|
|
T result = val;
|
|
if VL_CONSTEXPR_CXX17 (VlForceTypeInfo<T>::unpackedArray) {
|
|
// Handling the case of a nested flattened array using recursion
|
|
using ElemRef
|
|
= decltype(VlForceArrayIndexer<T>::elem(result, static_cast<std::size_t>(0)));
|
|
using Elem = VlForceBaseType<ElemRef>;
|
|
const int total = static_cast<int>(VlForceArrayIndexer<T>::size);
|
|
for (const auto& entry : m_entries) {
|
|
const Elem* const rhsBasep = static_cast<const Elem*>(entry.m_rhsDatap);
|
|
const int startIdx = entry.m_lsb;
|
|
const int endIdx = entry.m_msb;
|
|
for (int idx = startIdx; idx <= endIdx; idx++) {
|
|
const int rhsIndex = idx - entry.m_rhsLsb;
|
|
const std::size_t uidx = static_cast<std::size_t>(idx);
|
|
VlForceArrayIndexer<T>::elem(result, uidx) = rhsBasep[rhsIndex];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
applyEntries(result);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
T readIndex(T origVal, int index) const {
|
|
if (m_entries.empty()) return origVal;
|
|
|
|
const auto it = std::lower_bound(m_entries.begin(), m_entries.end(), index,
|
|
[](const Entry& e, int idx) { return e.m_msb < idx; });
|
|
if (it != m_entries.end() && it->m_lsb <= index) {
|
|
const int rhsIndex = index - it->m_rhsLsb;
|
|
const T* const rhsBasep = static_cast<const T*>(it->m_rhsDatap);
|
|
return rhsBasep[rhsIndex];
|
|
}
|
|
return origVal;
|
|
}
|
|
|
|
IData readSelI(int lbits, WDataInP valp, int lsb, int width) const {
|
|
IData result;
|
|
readSel(lbits, valp, WDataOutP::external(reinterpret_cast<EData*>(&result)), lsb, width);
|
|
result &= VL_MASK_I(width);
|
|
return result;
|
|
}
|
|
|
|
QData readSelQ(int lbits, WDataInP valp, int lsb, int width) const {
|
|
QData result;
|
|
readSel(lbits, valp, WDataOutP::external(reinterpret_cast<EData*>(&result)), lsb, width);
|
|
result &= VL_MASK_Q(width);
|
|
return result;
|
|
}
|
|
|
|
template <std::size_t N_Words>
|
|
VlWide<N_Words> readSelW(int lbits, WDataInP valp, int lsb, int width) const {
|
|
VlWide<N_Words> result;
|
|
readSel(lbits, valp, result, lsb, width);
|
|
result[N_Words - 1] &= VL_MASK_E(width);
|
|
return result;
|
|
}
|
|
|
|
void addForce(int lsb, int msb, const void* rhsDatap, int rhsLsb) {
|
|
assert(lsb <= msb);
|
|
assert(rhsDatap);
|
|
assert(rhsLsb <= lsb);
|
|
|
|
auto it = trimEntries(lsb, msb);
|
|
m_entries.insert(it, {lsb, msb, rhsLsb, rhsDatap});
|
|
}
|
|
|
|
void release(int lsb, int msb) {
|
|
assert(lsb <= msb);
|
|
trimEntries(lsb, msb);
|
|
}
|
|
|
|
void touch() {}
|
|
};
|
|
|
|
#endif // guard
|