259 lines
9.6 KiB
C++
259 lines
9.6 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;
|
|
}
|
|
|
|
const EData* const rhswp = 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));
|
|
}
|
|
|
|
template <typename T>
|
|
static typename std::enable_if<VlIsVlWide<T>::value, T>::type applyEntry(T result,
|
|
const Entry& entry) {
|
|
EData* const reswp = result.data();
|
|
const int lword = VL_BITWORD_E(entry.m_lsb);
|
|
const int hword = VL_BITWORD_E(entry.m_msb);
|
|
for (int word = lword; word <= hword; ++word) {
|
|
const int wordLsb = word * VL_EDATASIZE;
|
|
const int segLsb = std::max(entry.m_lsb, wordLsb);
|
|
const int segMsb = std::min(entry.m_msb, wordLsb + VL_EDATASIZE - 1);
|
|
const int segWidth = segMsb - segLsb + 1;
|
|
const int bitOffset = segLsb - wordLsb;
|
|
const int rhsLsb = segLsb - entry.m_rhsLsb;
|
|
reswp[word] = applyBits(reswp[word], entry, bitOffset, segWidth, rhsLsb);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
public:
|
|
VlForceVec() = default;
|
|
|
|
template <typename T>
|
|
T read(T val) const {
|
|
if VL_CONSTEXPR_CXX17 (VlForceTypeInfo<T>::unpackedArray) {
|
|
// Handling the case of a nested flattened array using recursion
|
|
using ElemRef
|
|
= decltype(VlForceArrayIndexer<T>::elem(val, 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(val, uidx) = rhsBasep[rhsIndex];
|
|
}
|
|
}
|
|
return val;
|
|
}
|
|
|
|
for (const auto& entry : m_entries) { val = applyEntry(val, entry); }
|
|
return val;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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
|